Skip to content
155 changes: 155 additions & 0 deletions src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import React, { useEffect, useState } from "react";
import { RowSchema, TimeRowEntry } from "../../../schemas/RowSchema";
import { TimeEntry } from "./TimeEntry";
import {
Button,
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalFooter,
ModalBody,
ModalCloseButton,
Editable,
EditablePreview,
EditableInput,
Input,
Box,
ButtonGroup,
Flex,
useDisclosure,
useEditableControls,
StackDivider,
HStack,
VStack,
Text,
IconButton
} from "@chakra-ui/react";

import {
ChatIcon,
CheckIcon,
CloseIcon,
EditIcon,
AddIcon,
DeleteIcon,
WarningIcon
} from "@chakra-ui/icons";



interface ConflicatableTimeEntryProps {
field: string;
row: RowSchema;
updateFields: Function;
}

/**
* This represents a time entry cell that accounts for multiple time entries (i.e. an associate's and a supervisor's entries)
* and allows for conflicts to be shown.
*/
export function ConflicatableTimeEntry(props: ConflicatableTimeEntryProps) {
const [selectedTime, setSelectedTime] = useState(null);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not undefined?


const handleTimeSelection = (time: number) => {
setSelectedTime(time);
};

// determine if the associate and supervisor times are conflicting for this cell
const hasAssociateTime: boolean = props.row.Associate !== undefined;
const hasSupervisorTime: boolean = props.row.Supervisor !== undefined;
const hasAdminTime: boolean = props.row.Admin !== undefined;

// TODO :
// -- only show conflict button on conflict
// -- auto-populate with the following rules:
// ---- if associate and supervisor times are the same, auto-populate with that time
// ---- if associate and supervisor times are different, auto-populate with null
// -- make sure that time conflict entry cells only show up on admins

// Conflicts are defined as:
// 1. associate submitted a time, and supervisor did not
// 2. Supervisor submitted a time for this entry, and associate did not
// 3. Associate time and supervisor reported time differ
var hasConflict = hasAssociateTime !== hasSupervisorTime ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possible issue of associate has time and supervisor doesnt which shouldn't lead to a conflict

(hasAssociateTime && hasSupervisorTime && props.row.Associate[props.field] !== props.row.Supervisor[props.field]);

useEffect(() => {
if (!hasConflict && hasAssociateTime && !hasAdminTime) {
setSelectedTime(props.row.Associate[props.field])
} else if (hasAdminTime) {
setSelectedTime(props.row.Admin[props.field])
}
}, []);

// TODO: Toggle clicker to display popup with conflict only if hasConflict is true
return (
<>
{ hasConflict && (<TimeConflictPopup
field={props.field}
associateEntry={props.row.Associate}
supervisorEntry={props.row.Supervisor}
onSelectTime={handleTimeSelection}
/> ) }
<TimeEntry
field={props.field}
row={props.row}
updateFields={props.updateFields}
userType="Admin"
time={selectedTime}
/>
</>
);

// TODO: make sure to update props field appropriately to trigger the correct autosaving
}

interface TimeConflictPopupProps {
field: string;
associateEntry: TimeRowEntry;
supervisorEntry: TimeRowEntry;
onSelectTime: Function;
}

export function TimeConflictPopup(props: TimeConflictPopupProps) {
const { isOpen, onOpen, onClose } = useDisclosure();

const hasAssociateTime: boolean = props.associateEntry !== undefined && props.associateEntry !== null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undefined instead?

const hasSupervisorTime: boolean = props.supervisorEntry !== undefined && props.supervisorEntry !== null;

const associateTime: number = hasAssociateTime ? props.associateEntry[props.field] : null;
const supervisorTime: number = hasSupervisorTime ? props.supervisorEntry[props.field] : null;

const convertMinutesToTime = (minutes) => {
const hours = Math.round(minutes / 60)
const mins = minutes % 60;
return hours + ":" + mins;
}

return (
<>
<IconButton aria-label='Conflict' icon={<WarningIcon />} onClick={onOpen}></IconButton>
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>
<HStack>
<Text>Time Conflict</Text>
</HStack>
</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Text>These are the entries submitted:</Text>
<HStack>
<Button onClick={() => { props.onSelectTime(associateTime); onClose(); }}>Use Associate Time</Button>
<Button onClick={() => { props.onSelectTime(supervisorTime); onClose();} }>Use Supervisor Time</Button>
</HStack>
<Text>Associate Time: {hasAssociateTime ? convertMinutesToTime(props.associateEntry[props.field]) : "none"}</Text>
<Text>Supervisor Time: {hasSupervisorTime ? convertMinutesToTime(props.supervisorEntry[props.field]) : "none"}</Text>
</ModalBody>
<ModalFooter>
</ModalFooter>
</ModalContent>
</Modal>
</>)
}
10 changes: 7 additions & 3 deletions src/components/TimeCardPage/CellTypes/HoursCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@ import { Box } from '@chakra-ui/react';

interface DurationProps {
row: RowSchema;
userType: string; // Associate, Supervisor, or Admin
}

/**
* Calculation component that shows the length of time for the given start and end times of the shift
*/
export function Duration(props: DurationProps) {
const row = props.row;
const [duration, setDuration] = useState("");

useEffect(() => {
if (row.Associate !== undefined && row.Associate.Start !== undefined && row.Associate.End !== undefined) {
setDuration(String(((row.Associate.End - row.Associate.Start) / 60).toFixed(2)));
if (row[props.userType] !== undefined && row[props.userType].Start !== undefined && row[props.userType].End !== undefined) {
setDuration(String(((row[props.userType].End - row[props.userType].Start) / 60).toFixed(2)));
}
}, [row.Associate?.Start, row.Associate?.End])
}, [row[props.userType]?.Start, row[props.userType]?.End])
return <Box>{duration}</Box>
}
Empty file.
106 changes: 61 additions & 45 deletions src/components/TimeCardPage/CellTypes/TimeEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,65 +6,81 @@ interface TimeEntryProps {
field: string;
row: RowSchema;
updateFields: Function;
userType: string; // Associate, Supervisor, or Admin
time: number;
}

export function TimeEntry(props: TimeEntryProps) {
const [minutes, setMinutes] = useState(undefined);
// Date object for the time picker to take in
const [convertedTime, setConvertedTime] = useState(null);

const onChange = (time) => {
var rowToMutate = props.row.Associate;
if (rowToMutate === undefined) {
rowToMutate = {
Start:undefined, End:undefined, AuthorID:"<TODO-add ID>"
}
}
// converted given minutes (number of minutes from 0:00) to Date() object, and update the converted time
const convertMinutesToTime = (minutes) => {
if (minutes !== undefined && minutes !== null) {
const newTime = new Date();
newTime.setHours(Math.round(minutes / 60));
newTime.setMinutes(minutes % 60);
setConvertedTime(newTime);
} else {
setConvertedTime(null);
}
};

const handleChange = (time) => {
// TODO : This seems to be only able to hook up to the associates.
// We probably want this to easily switch between supervisor vs. associated,
// maybe an enum passed in via the props, similar to the field?
var rowToMutate = props.row[props.userType];
if (rowToMutate === undefined) {
rowToMutate = {
Start: undefined,
End: undefined,
AuthorID: "<TODO-add ID>",
};
}

if (time !== null) {
const [hours, parsedMinutes] = time.split(":");
const calculatedTime = Number(hours) * 60 + Number(parsedMinutes);
rowToMutate[props.field] = calculatedTime;

if (time !== null) {
const [hours, parsedMinutes] = time.split(":");
const calculatedTime = Number(hours) * 60 + Number(parsedMinutes)
setMinutes(calculatedTime);
rowToMutate[props.field] = calculatedTime;
} else {
// Value is null, so mark it as undefined in our processing
rowToMutate[props.field] = undefined;
setMinutes(undefined);
}
//Triggering parent class to update its references here as well
props.updateFields("Associate", rowToMutate);
// Reset the Date object to the new time - this handles what is actually
// displayed in the time picker visually before page refresh
const newTime = new Date();
newTime.setHours(hours);
newTime.setMinutes(parsedMinutes);
setConvertedTime(newTime);
} else {
// Value is null, so mark it as undefined in our processing
rowToMutate[props.field] = undefined;
setConvertedTime(null);
}

//Triggering parent class to update its references here as well
props.updateFields(props.userType, rowToMutate);
};

useEffect(() => {
if (props.row.Associate !== undefined) {
setMinutes(props.row.Associate[props.field]);
let minutes;
if (props.row[props.userType] !== undefined && props.row[props.userType] !== null) {
minutes = (props.row[props.userType][props.field]);
} else if (props.userType === "Admin") {
minutes = props.time;
}
convertMinutesToTime(minutes);
console.log(convertedTime);
}, []);

return renderClockTime(minutes, onChange);
}
useEffect(() => {
convertMinutesToTime(props.time);
//handleChange(props.time);
console.log("Input time has been changed, " + props.time);
}, [props.time]);

const renderClockTime = (minutes: number, updateClockTime) => {
if (minutes !== undefined) {
const convertedTime = new Date();
convertedTime.setHours(Math.round(minutes / 60));
convertedTime.setMinutes(minutes % 60);
return (
<TimePicker
onChange={(value) => {
updateClockTime(value);
}}
value={convertedTime}
disableClock={true}
/>
);
}
return (
<TimePicker
onChange={(value) => {
updateClockTime(value);
}}
value={null}
onChange={handleChange}
value={convertedTime}
disableClock={true}
/>
);
};
}
Empty file.
8 changes: 7 additions & 1 deletion src/components/TimeCardPage/TimeSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ export default function Page() {
if (newCurrentTimesheets.length > 0) {
setTab(newCurrentTimesheets[0].CompanyID)
}
console.log(newCurrentTimesheets[0]);
}

const renderWarning = () => {
Expand Down Expand Up @@ -241,7 +242,12 @@ export default function Page() {
</Tabs>
{selectedTab === "Total" ?
(<AggregationTable Date={selectedDate} timesheets={currentTimesheets} />)
: (currentTimesheets.length > 0 && <TimeTable columns={TABLE_COLUMNS} timesheet={selectedTimesheet} onTimesheetChange={processTimesheetChange} />)}
: (currentTimesheets.length > 0 && <TimeTable
columns={TABLE_COLUMNS}
timesheet={selectedTimesheet}
onTimesheetChange={processTimesheetChange}
userType={user?.Type}
/>)}

</>
)
Expand Down
3 changes: 2 additions & 1 deletion src/components/TimeCardPage/TimeTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ interface TableProps {
timesheet: TimeSheetSchema;
columns: String[];
onTimesheetChange: Function;
userType: string; // Associate, Supervisor, or Admin
}

function TimeTable(props: TableProps) {
Expand Down Expand Up @@ -152,7 +153,7 @@ function TimeTable(props: TableProps) {
</ButtonGroup>
</Td>
{
<TimeTableRow row={row} onRowChange={(row) => onRowChange(row, index)} prevDate={dateToSend} TimesheetID={props.timesheet.TimesheetID} />
<TimeTableRow row={row} onRowChange={(row) => onRowChange(row, index)} prevDate={dateToSend} TimesheetID={props.timesheet.TimesheetID} userType={props.userType} />
}
</Tr>);
}
Expand Down
Loading