diff --git a/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx b/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx
new file mode 100644
index 0000000..daf5a55
--- /dev/null
+++ b/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx
@@ -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);
+
+ 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 ||
+ (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 && ( ) }
+
+ >
+ );
+
+ // 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;
+ 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 (
+ <>
+ } onClick={onOpen}>
+
+
+
+
+
+ Time Conflict
+
+
+
+
+ These are the entries submitted:
+
+
+
+
+ Associate Time: {hasAssociateTime ? convertMinutesToTime(props.associateEntry[props.field]) : "none"}
+ Supervisor Time: {hasSupervisorTime ? convertMinutesToTime(props.supervisorEntry[props.field]) : "none"}
+
+
+
+
+
+ >)
+}
\ No newline at end of file
diff --git a/src/components/TimeCardPage/CellTypes/HoursCell.tsx b/src/components/TimeCardPage/CellTypes/HoursCell.tsx
index 9de3e90..510d7da 100644
--- a/src/components/TimeCardPage/CellTypes/HoursCell.tsx
+++ b/src/components/TimeCardPage/CellTypes/HoursCell.tsx
@@ -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 {duration}
}
diff --git a/src/components/TimeCardPage/CellTypes/HoursPickerPopup.tsx b/src/components/TimeCardPage/CellTypes/HoursPickerPopup.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/components/TimeCardPage/CellTypes/TimeEntry.tsx b/src/components/TimeCardPage/CellTypes/TimeEntry.tsx
index 90ff346..5dd836b 100644
--- a/src/components/TimeCardPage/CellTypes/TimeEntry.tsx
+++ b/src/components/TimeCardPage/CellTypes/TimeEntry.tsx
@@ -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:""
- }
- }
+ // 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: "",
+ };
+ }
+ 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 (
- {
- updateClockTime(value);
- }}
- value={convertedTime}
- disableClock={true}
- />
- );
- }
return (
{
- updateClockTime(value);
- }}
- value={null}
+ onChange={handleChange}
+ value={convertedTime}
disableClock={true}
/>
);
-};
+}
diff --git a/src/components/TimeCardPage/Modals/TimeConflictModal.tsx b/src/components/TimeCardPage/Modals/TimeConflictModal.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/components/TimeCardPage/TimeSheet.tsx b/src/components/TimeCardPage/TimeSheet.tsx
index 42ae2b2..a49d5f9 100644
--- a/src/components/TimeCardPage/TimeSheet.tsx
+++ b/src/components/TimeCardPage/TimeSheet.tsx
@@ -189,6 +189,7 @@ export default function Page() {
if (newCurrentTimesheets.length > 0) {
setTab(newCurrentTimesheets[0].CompanyID)
}
+ console.log(newCurrentTimesheets[0]);
}
const renderWarning = () => {
@@ -241,7 +242,12 @@ export default function Page() {
{selectedTab === "Total" ?
()
- : (currentTimesheets.length > 0 && )}
+ : (currentTimesheets.length > 0 && )}
>
)
diff --git a/src/components/TimeCardPage/TimeTable.tsx b/src/components/TimeCardPage/TimeTable.tsx
index 2902bc0..561be13 100644
--- a/src/components/TimeCardPage/TimeTable.tsx
+++ b/src/components/TimeCardPage/TimeTable.tsx
@@ -54,6 +54,7 @@ interface TableProps {
timesheet: TimeSheetSchema;
columns: String[];
onTimesheetChange: Function;
+ userType: string; // Associate, Supervisor, or Admin
}
function TimeTable(props: TableProps) {
@@ -152,7 +153,7 @@ function TimeTable(props: TableProps) {
{
- onRowChange(row, index)} prevDate={dateToSend} TimesheetID={props.timesheet.TimesheetID} />
+ onRowChange(row, index)} prevDate={dateToSend} TimesheetID={props.timesheet.TimesheetID} userType={props.userType} />
}
);
}
diff --git a/src/components/TimeCardPage/TimeTableRow.tsx b/src/components/TimeCardPage/TimeTableRow.tsx
index d28cec5..4f3378a 100644
--- a/src/components/TimeCardPage/TimeTableRow.tsx
+++ b/src/components/TimeCardPage/TimeTableRow.tsx
@@ -1,84 +1,105 @@
-import React, { useEffect, useState } from 'react';
-import 'react-time-picker/dist/TimePicker.css';
-import 'react-clock/dist/Clock.css';
-import { Fragment } from 'react';
+import React, { useEffect, useState } from "react";
+import "react-time-picker/dist/TimePicker.css";
+import "react-clock/dist/Clock.css";
+import { Fragment } from "react";
-import { Td } from '@chakra-ui/react';
+import { Td } from "@chakra-ui/react";
-import { TimeEntry } from './CellTypes/TimeEntry';
-import { Duration } from './CellTypes/HoursCell'
-import { DateCell } from './CellTypes/DateCell';
-import { TypeCell } from './CellTypes/CellType';
-import { CommentCell } from './CellTypes/CommentCell';
-import { RowSchema } from '../../schemas/RowSchema';
-import ApiClient from 'src/components/Auth/apiClient'
+import { TimeEntry } from "./CellTypes/TimeEntry";
+import { Duration } from "./CellTypes/HoursCell";
+import { DateCell } from "./CellTypes/DateCell";
+import { TypeCell } from "./CellTypes/CellType";
+import { CommentCell } from "./CellTypes/CommentCell";
+import { RowSchema } from "../../schemas/RowSchema";
+import ApiClient from "src/components/Auth/apiClient";
-import * as updateSchemas from 'src/schemas/backend/UpdateTimesheet'
-import apiClient from 'src/components/Auth/apiClient';
+import * as updateSchemas from "src/schemas/backend/UpdateTimesheet";
+import apiClient from "src/components/Auth/apiClient";
+import { ConflicatableTimeEntry } from "./CellTypes/ConflictableTimeEntry";
interface RowProps {
- row: RowSchema;
- prevDate: number;
- onRowChange: Function;
- TimesheetID: number;
+ row: RowSchema;
+ prevDate: number;
+ onRowChange: Function;
+ TimesheetID: number;
+ userType: string; // Associate, Supervisor, or Admin
}
-
-
-
function Row(props: RowProps) {
-
- const [fields, setFields] = useState(undefined);
-
- const updateField = (key, value) => {
-
- const newFields = {
- ...fields,
- [key]: value
- }
-
- setFields(newFields);
- props.onRowChange(newFields);
- //Send a request to update the db on this item being changed
- ApiClient.updateTimesheet(updateSchemas.TimesheetUpdateRequest.parse({
- TimesheetID: props.TimesheetID,
- Operation: updateSchemas.TimesheetOperations.UPDATE,
- Payload: updateSchemas.UpdateRequest.parse({
- Type: updateSchemas.TimesheetListItems.TABLEDATA,
- Id: props.row.UUID,
- Attribute: key,
- Data: value
- })
- }));
-
- }
-
- useEffect(() => {
- if (props.row !== undefined) {
- setFields(RowSchema.parse(props.row));
- }
- }, [])
-
- if (fields !== undefined) {
- const items = {
- "Type": ,
- "Date": ,
- "Clock-in": ,
- "Clock-out": ,
- "Hours": ,
- "Comment": ,
- }
- const itemOrdering = ["Type", "Date", "Clock-in", "Clock-out", "Hours", "Comment"];
-
- return
- {itemOrdering.map((entry) => | {items[entry]} | )}
-
-
- } else {
- return
-
-
+ const [fields, setFields] = useState(undefined);
+
+ const updateField = (key, value) => {
+ const newFields = {
+ ...fields,
+ [key]: value,
+ };
+
+ setFields(newFields);
+ props.onRowChange(newFields);
+ //Send a request to update the db on this item being changed
+ ApiClient.updateTimesheet(
+ updateSchemas.TimesheetUpdateRequest.parse({
+ TimesheetID: props.TimesheetID,
+ Operation: updateSchemas.TimesheetOperations.UPDATE,
+ Payload: updateSchemas.UpdateRequest.parse({
+ Type: updateSchemas.TimesheetListItems.TABLEDATA,
+ Id: props.row.UUID,
+ Attribute: key,
+ Data: value,
+ }),
+ })
+ );
+ };
+
+ useEffect(() => {
+ if (props.row !== undefined) {
+ setFields(RowSchema.parse(props.row));
}
+ }, []);
+
+ if (fields !== undefined) {
+ const items = {
+ Type: ,
+ Date: ,
+ // TODO : The userType is likely able to be adjusted, only show conflictable time entry for admins.
+ "Clock-in": (
+
+ ),
+ "Clock-out": (
+
+ ),
+ Hours: ,
+ Comment: (
+
+ ),
+ };
+ const itemOrdering = [
+ "Type",
+ "Date",
+ "Clock-in",
+ "Clock-out",
+ "Hours",
+ "Comment",
+ ];
+
+ return (
+
+ {itemOrdering.map((entry) => (
+ {items[entry]} |
+ ))}
+
+ );
+ } else {
+ return ;
+ }
}
export default Row;