From 30e6514679ba81c29d935c3ece50810cd2d66b2d Mon Sep 17 00:00:00 2001 From: izzyconner <32255130+izzyconner@users.noreply.github.com> Date: Tue, 18 Jul 2023 11:35:32 -0400 Subject: [PATCH 1/8] Beginning to setup components for the conflictable time entry cell --- .../CellTypes/ConflictableTimeEntry.tsx | 37 +++++++++++++++++++ .../CellTypes/HoursPickerPopup.tsx | 0 .../TimeCardPage/CellTypes/TimeEntry.tsx | 3 ++ 3 files changed, 40 insertions(+) create mode 100644 src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx create mode 100644 src/components/TimeCardPage/CellTypes/HoursPickerPopup.tsx diff --git a/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx b/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx new file mode 100644 index 0000000..e7fee29 --- /dev/null +++ b/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx @@ -0,0 +1,37 @@ +import React, { useEffect, useState } from "react"; +import { RowSchema } from "../../../schemas/RowSchema"; +import TimePicker from "react-time-picker"; +import { boolean } from "zod"; + +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(''); + + // 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; + + // 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) + || props.row.Associate[props.field] !== props.row.Supervisor[props.field]; + + // TODO: Time picker, similar to TimeEntry component + + // TODO: Toggle clicker to display popup with conflict only if hasConflict is true + + // TODO: make sure to update props field appropriately to trigger the correct autosaving + +} \ No newline at end of file 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..e20e8d1 100644 --- a/src/components/TimeCardPage/CellTypes/TimeEntry.tsx +++ b/src/components/TimeCardPage/CellTypes/TimeEntry.tsx @@ -12,6 +12,9 @@ export function TimeEntry(props: TimeEntryProps) { const [minutes, setMinutes] = useState(undefined); const onChange = (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.Associate; if (rowToMutate === undefined) { rowToMutate = { From 6f6bdfe0dc7694518334dc04d41132a41daa5cb2 Mon Sep 17 00:00:00 2001 From: izzyconner Date: Tue, 18 Jul 2023 19:57:48 -0400 Subject: [PATCH 2/8] Adding in some beginnings to the popup and conflict time entry --- .../CellTypes/ConflictableTimeEntry.tsx | 46 +++++++++++++++++-- .../TimeCardPage/CellTypes/TimeEntry.tsx | 7 +-- src/components/TimeCardPage/TimeTableRow.tsx | 5 +- 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx b/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx index e7fee29..369ddd7 100644 --- a/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx +++ b/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState } from "react"; -import { RowSchema } from "../../../schemas/RowSchema"; -import TimePicker from "react-time-picker"; -import { boolean } from "zod"; +import { RowSchema, TimeRowEntry } from "../../../schemas/RowSchema"; +import { TimeEntry } from "./TimeEntry"; interface ConflicatableTimeEntryProps { field: string; @@ -13,10 +12,19 @@ interface ConflicatableTimeEntryProps { * 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 [isPopupOpen, setIsPopupOpen] = useState(false); const [selectedTime, setSelectedTime] = useState(''); + const handleTimePickerClick = () => { + setIsPopupOpen(true); + }; + + const handleTimeSelection = (time: string) => { + setSelectedTime(time); + setIsPopupOpen(false); + } + // 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; @@ -29,9 +37,37 @@ export function ConflicatableTimeEntry(props: ConflicatableTimeEntryProps) { || props.row.Associate[props.field] !== props.row.Supervisor[props.field]; // TODO: Time picker, similar to TimeEntry component + // How should we autofill this? We might need to rework TImeEntry or create a new timepicker + // The Admin time should autofill to the correct time if matching, or the associate time if conflicting + const timePicker = TimeEntry({field: props.field, + row: props.row, + updateFields: props.updateFields, + userType: "Admin" + }) // TODO: Toggle clicker to display popup with conflict only if hasConflict is true + return <> + {hasConflict && } + // TODO : hook up this popup with the button + + timePicker + // 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) { + } \ No newline at end of file diff --git a/src/components/TimeCardPage/CellTypes/TimeEntry.tsx b/src/components/TimeCardPage/CellTypes/TimeEntry.tsx index e20e8d1..beeba53 100644 --- a/src/components/TimeCardPage/CellTypes/TimeEntry.tsx +++ b/src/components/TimeCardPage/CellTypes/TimeEntry.tsx @@ -6,6 +6,7 @@ interface TimeEntryProps { field: string; row: RowSchema; updateFields: Function; + userType: string; // Associate, Supervisor, or Admin } export function TimeEntry(props: TimeEntryProps) { @@ -15,7 +16,7 @@ export function TimeEntry(props: TimeEntryProps) { // 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.Associate; + var rowToMutate = props.row[props.userType]; if (rowToMutate === undefined) { rowToMutate = { Start:undefined, End:undefined, AuthorID:"" @@ -34,12 +35,12 @@ export function TimeEntry(props: TimeEntryProps) { setMinutes(undefined); } //Triggering parent class to update its references here as well - props.updateFields("Associate", rowToMutate); + props.updateFields(props.userType, rowToMutate); } useEffect(() => { if (props.row.Associate !== undefined) { - setMinutes(props.row.Associate[props.field]); + setMinutes(props.row[props.userType][props.field]); } }, []); diff --git a/src/components/TimeCardPage/TimeTableRow.tsx b/src/components/TimeCardPage/TimeTableRow.tsx index d28cec5..598e693 100644 --- a/src/components/TimeCardPage/TimeTableRow.tsx +++ b/src/components/TimeCardPage/TimeTableRow.tsx @@ -63,8 +63,9 @@ function Row(props: RowProps) { const items = { "Type": , "Date": , - "Clock-in": , - "Clock-out": , + // TODO : The userType is likely able to be adjusted, add this to the props for TimeTableRow + "Clock-in": , + "Clock-out": , "Hours": , "Comment": , } From dd581a4f8784bd8d4597b2e7638ccb101430799a Mon Sep 17 00:00:00 2001 From: izzyconner <32255130+izzyconner@users.noreply.github.com> Date: Thu, 20 Jul 2023 18:20:02 -0400 Subject: [PATCH 3/8] Starting to fix up the conflictable time entry, changing time entry component to allow for reactive time changing automatically Co-authored-by: Kaylee Wu --- .../CellTypes/ConflictableTimeEntry.tsx | 61 +++---- .../TimeCardPage/CellTypes/TimeEntry.tsx | 97 +++++----- src/components/TimeCardPage/TimeTableRow.tsx | 168 ++++++++++-------- 3 files changed, 176 insertions(+), 150 deletions(-) diff --git a/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx b/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx index 369ddd7..3dbb4a6 100644 --- a/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx +++ b/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx @@ -10,53 +10,56 @@ interface ConflicatableTimeEntryProps { /** * 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. + * and allows for conflicts to be shown. */ export function ConflicatableTimeEntry(props: ConflicatableTimeEntryProps) { const [isPopupOpen, setIsPopupOpen] = useState(false); - const [selectedTime, setSelectedTime] = useState(''); + const [selectedTime, setSelectedTime] = useState(undefined); const handleTimePickerClick = () => { setIsPopupOpen(true); }; - const handleTimeSelection = (time: string) => { + const handleTimeSelection = (time: number) => { setSelectedTime(time); setIsPopupOpen(false); - } + }; // 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; - - // Conflicts are defined as: + + // 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) - || props.row.Associate[props.field] !== props.row.Supervisor[props.field]; - - // TODO: Time picker, similar to TimeEntry component - // How should we autofill this? We might need to rework TImeEntry or create a new timepicker - // The Admin time should autofill to the correct time if matching, or the associate time if conflicting - const timePicker = TimeEntry({field: props.field, - row: props.row, - updateFields: props.updateFields, - userType: "Admin" - }) + var hasConflict = + hasAssociateTime !== hasSupervisorTime || + props.row.Associate[props.field] !== props.row.Supervisor[props.field]; // TODO: Toggle clicker to display popup with conflict only if hasConflict is true - return <> - {hasConflict && } - // TODO : hook up this popup with the button - - timePicker + return ( + <> + + {hasConflict && ( + <> + + {/* */} + + )} + ); // TODO: make sure to update props field appropriately to trigger the correct autosaving } @@ -68,6 +71,4 @@ interface TimeConflictPopupProps { onSelectTime: Function; } -export function TimeConflictPopup(props: TimeConflictPopupProps) { - -} \ No newline at end of file +export function TimeConflictPopup(props: TimeConflictPopupProps) {} diff --git a/src/components/TimeCardPage/CellTypes/TimeEntry.tsx b/src/components/TimeCardPage/CellTypes/TimeEntry.tsx index beeba53..a81f757 100644 --- a/src/components/TimeCardPage/CellTypes/TimeEntry.tsx +++ b/src/components/TimeCardPage/CellTypes/TimeEntry.tsx @@ -7,68 +7,71 @@ interface TimeEntryProps { row: RowSchema; updateFields: Function; userType: string; // Associate, Supervisor, or Admin + time: number; } export function TimeEntry(props: TimeEntryProps) { - const [minutes, setMinutes] = useState(undefined); - - const onChange = (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:"" - } - } + const [convertedTime, setConvertedTime] = useState(undefined); + const onChange = (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) - 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(props.userType, rowToMutate); + if (time !== null) { + const [hours, parsedMinutes] = time.split(":"); + const calculatedTime = Number(hours) * 60 + Number(parsedMinutes); + rowToMutate[props.field] = calculatedTime; + } else { + // Value is null, so mark it as undefined in our processing + rowToMutate[props.field] = undefined; } - + //Triggering parent class to update its references here as well + props.updateFields(props.userType, rowToMutate); + }; + useEffect(() => { - if (props.row.Associate !== undefined) { - setMinutes(props.row[props.userType][props.field]); + let minutes; + if (props.row[props.userType] !== undefined) { + minutes = (props.row[props.userType][props.field]); + } + + if (minutes !== undefined) { + const newTime = new Date(); + newTime.setHours(Math.round(minutes / 60)); + newTime.setMinutes(minutes % 60); + setConvertedTime(newTime); + } else { + setConvertedTime(null); } }, []); - return renderClockTime(minutes, onChange); -} + useEffect(() => { + if (props.time !== undefined) { + const newTime = new Date(); + newTime.setHours(Math.round(props.time / 60)); + newTime.setMinutes(props.time % 60); + setConvertedTime(newTime); + } else { + setConvertedTime(null); + } + }, [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); + onChange(value); }} - value={null} + value={convertedTime} disableClock={true} /> ); -}; +} diff --git a/src/components/TimeCardPage/TimeTableRow.tsx b/src/components/TimeCardPage/TimeTableRow.tsx index 598e693..6bdc66b 100644 --- a/src/components/TimeCardPage/TimeTableRow.tsx +++ b/src/components/TimeCardPage/TimeTableRow.tsx @@ -1,85 +1,107 @@ -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"; interface RowProps { - row: RowSchema; - prevDate: number; - onRowChange: Function; - TimesheetID: number; + row: RowSchema; + prevDate: number; + onRowChange: Function; + TimesheetID: number; } - - - 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": , - // TODO : The userType is likely able to be adjusted, add this to the props for TimeTableRow - "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, add this to the props for TimeTableRow + "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; From d3d0a5d930b7dac24a2128d79b04769e9eacf98c Mon Sep 17 00:00:00 2001 From: izzyconner Date: Fri, 4 Aug 2023 19:50:02 -0400 Subject: [PATCH 4/8] Fixed the bug where the time picker wasn't getting updated --- .../TimeCardPage/CellTypes/TimeEntry.tsx | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/src/components/TimeCardPage/CellTypes/TimeEntry.tsx b/src/components/TimeCardPage/CellTypes/TimeEntry.tsx index a81f757..9dce0ab 100644 --- a/src/components/TimeCardPage/CellTypes/TimeEntry.tsx +++ b/src/components/TimeCardPage/CellTypes/TimeEntry.tsx @@ -11,9 +11,22 @@ interface TimeEntryProps { } export function TimeEntry(props: TimeEntryProps) { - const [convertedTime, setConvertedTime] = useState(undefined); + // Date object for the time picker to take in + const [convertedTime, setConvertedTime] = useState(null); - const onChange = (time) => { + // converted given epoch to Date() object, and update the converted time + const convertEpochToDate = (minutes) => { + if (minutes !== undefined) { + 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? @@ -30,9 +43,17 @@ export function TimeEntry(props: TimeEntryProps) { const [hours, parsedMinutes] = time.split(":"); const calculatedTime = Number(hours) * 60 + Number(parsedMinutes); rowToMutate[props.field] = calculatedTime; + + // 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); @@ -43,33 +64,13 @@ export function TimeEntry(props: TimeEntryProps) { if (props.row[props.userType] !== undefined) { minutes = (props.row[props.userType][props.field]); } - - if (minutes !== undefined) { - const newTime = new Date(); - newTime.setHours(Math.round(minutes / 60)); - newTime.setMinutes(minutes % 60); - setConvertedTime(newTime); - } else { - setConvertedTime(null); - } + convertEpochToDate(minutes); + console.log(convertedTime); }, []); - useEffect(() => { - if (props.time !== undefined) { - const newTime = new Date(); - newTime.setHours(Math.round(props.time / 60)); - newTime.setMinutes(props.time % 60); - setConvertedTime(newTime); - } else { - setConvertedTime(null); - } - }, [props.time]); - return ( { - onChange(value); - }} + onChange={handleChange} value={convertedTime} disableClock={true} /> From 99f3521daa0e54a261f6a442be8dbad4eefe05e9 Mon Sep 17 00:00:00 2001 From: izzyconner Date: Sat, 5 Aug 2023 09:01:31 -0400 Subject: [PATCH 5/8] starting to work on conflict modal --- .../CellTypes/ConflictableTimeEntry.tsx | 59 +++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx b/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx index 3dbb4a6..64f59a6 100644 --- a/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx +++ b/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx @@ -1,6 +1,12 @@ import React, { useEffect, useState } from "react"; import { RowSchema, TimeRowEntry } from "../../../schemas/RowSchema"; import { TimeEntry } from "./TimeEntry"; +import { + IconButton, +} from '@chakra-ui/react' +import { WarningIcon } from '@chakra-ui/icons'; + + interface ConflicatableTimeEntryProps { field: string; @@ -49,13 +55,13 @@ export function ConflicatableTimeEntry(props: ConflicatableTimeEntryProps) { /> {hasConflict && ( <> - - {/* } /> + {isPopupOpen && ( */} + />)} )} @@ -71,4 +77,49 @@ interface TimeConflictPopupProps { onSelectTime: Function; } -export function TimeConflictPopup(props: TimeConflictPopupProps) {} +export function TimeConflictPopup(props: TimeConflictPopupProps) { + return + + + + + + View {CommentType.Comment} + + + + + + {comments.map( + (comment) => ( + + {/* add UserDisplay card once pr merged in*/} + saveEditedComment(setComments, comments, CommentType.Comment, comment, createNewComment(user, CommentType.Comment, value))} + > + + + {isEditable && ( + <> + + + + } onClick={() => deleteComment(onCloseDisplay, setComments, comments, CommentType.Comment, comment)} /> + + + )} + + + + ))} + + + + + + +} From 136368735179ab9eda8ba71ec571d5e51f382dca Mon Sep 17 00:00:00 2001 From: izzyconner Date: Sat, 5 Aug 2023 16:41:33 -0400 Subject: [PATCH 6/8] Adding in complete-ish modal and functional buttons to select associate or supervisor times --- .../CellTypes/ConflictableTimeEntry.tsx | 151 ++++++++++-------- .../TimeCardPage/CellTypes/TimeEntry.tsx | 30 ++-- .../TimeCardPage/Modals/TimeConflictModal.tsx | 0 src/components/TimeCardPage/TimeSheet.tsx | 1 + src/components/TimeCardPage/TimeTableRow.tsx | 5 +- 5 files changed, 104 insertions(+), 83 deletions(-) create mode 100644 src/components/TimeCardPage/Modals/TimeConflictModal.tsx diff --git a/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx b/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx index 64f59a6..de6fc39 100644 --- a/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx +++ b/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx @@ -2,9 +2,39 @@ import React, { useEffect, useState } from "react"; import { RowSchema, TimeRowEntry } from "../../../schemas/RowSchema"; import { TimeEntry } from "./TimeEntry"; import { - IconButton, -} from '@chakra-ui/react' -import { WarningIcon } from '@chakra-ui/icons'; + 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"; @@ -19,16 +49,10 @@ interface ConflicatableTimeEntryProps { * and allows for conflicts to be shown. */ export function ConflicatableTimeEntry(props: ConflicatableTimeEntryProps) { - const [isPopupOpen, setIsPopupOpen] = useState(false); - const [selectedTime, setSelectedTime] = useState(undefined); - - const handleTimePickerClick = () => { - setIsPopupOpen(true); - }; + const [selectedTime, setSelectedTime] = useState(null); const handleTimeSelection = (time: number) => { setSelectedTime(time); - setIsPopupOpen(false); }; // determine if the associate and supervisor times are conflicting for this cell @@ -39,13 +63,19 @@ export function ConflicatableTimeEntry(props: ConflicatableTimeEntryProps) { // 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 = + /*var hasConflict = hasAssociateTime !== hasSupervisorTime || - props.row.Associate[props.field] !== props.row.Supervisor[props.field]; + props.row.Associate[props.field] !== props.row.Supervisor[props.field];*/ // TODO: Toggle clicker to display popup with conflict only if hasConflict is true return ( <> + - {hasConflict && ( - <> - } /> - {isPopupOpen && ()} - - )} ); @@ -78,48 +97,44 @@ interface TimeConflictPopupProps { } export function TimeConflictPopup(props: TimeConflictPopupProps) { - return - - - - - - View {CommentType.Comment} - - - - - - {comments.map( - (comment) => ( - - {/* add UserDisplay card once pr merged in*/} - saveEditedComment(setComments, comments, CommentType.Comment, comment, createNewComment(user, CommentType.Comment, value))} - > - - - {isEditable && ( - <> - - - - } onClick={() => deleteComment(onCloseDisplay, setComments, comments, CommentType.Comment, comment)} /> - - - )} - - - - ))} - - - - - - -} + 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/TimeEntry.tsx b/src/components/TimeCardPage/CellTypes/TimeEntry.tsx index 9dce0ab..82f6267 100644 --- a/src/components/TimeCardPage/CellTypes/TimeEntry.tsx +++ b/src/components/TimeCardPage/CellTypes/TimeEntry.tsx @@ -14,17 +14,17 @@ export function TimeEntry(props: TimeEntryProps) { // Date object for the time picker to take in const [convertedTime, setConvertedTime] = useState(null); - // converted given epoch to Date() object, and update the converted time - const convertEpochToDate = (minutes) => { - if (minutes !== undefined) { - const newTime = new Date(); - newTime.setHours(Math.round(minutes / 60)); - newTime.setMinutes(minutes % 60); - setConvertedTime(newTime); - } else { - setConvertedTime(null); - } - }; + // 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. @@ -64,10 +64,16 @@ export function TimeEntry(props: TimeEntryProps) { if (props.row[props.userType] !== undefined) { minutes = (props.row[props.userType][props.field]); } - convertEpochToDate(minutes); + convertMinutesToTime(minutes); console.log(convertedTime); }, []); + useEffect(() => { + convertMinutesToTime(props.time); + //handleChange(props.time); + console.log("Input time has been changed, " + props.time); + }, [props.time]); + return ( 0) { setTab(newCurrentTimesheets[0].CompanyID) } + console.log(newCurrentTimesheets[0]); } const renderWarning = () => { diff --git a/src/components/TimeCardPage/TimeTableRow.tsx b/src/components/TimeCardPage/TimeTableRow.tsx index 6bdc66b..84933b9 100644 --- a/src/components/TimeCardPage/TimeTableRow.tsx +++ b/src/components/TimeCardPage/TimeTableRow.tsx @@ -15,6 +15,7 @@ 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; @@ -61,12 +62,10 @@ function Row(props: RowProps) { Date: , // TODO : The userType is likely able to be adjusted, add this to the props for TimeTableRow "Clock-in": ( - ), "Clock-out": ( From c9e335fee119d367e1bf634844f38afc376e6990 Mon Sep 17 00:00:00 2001 From: izzyconner Date: Sat, 5 Aug 2023 17:11:57 -0400 Subject: [PATCH 7/8] Adding in flexibility for user types --- src/components/TimeCardPage/CellTypes/HoursCell.tsx | 10 +++++++--- src/components/TimeCardPage/TimeSheet.tsx | 7 ++++++- src/components/TimeCardPage/TimeTable.tsx | 3 ++- src/components/TimeCardPage/TimeTableRow.tsx | 3 ++- 4 files changed, 17 insertions(+), 6 deletions(-) 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/TimeSheet.tsx b/src/components/TimeCardPage/TimeSheet.tsx index 0d38890..a49d5f9 100644 --- a/src/components/TimeCardPage/TimeSheet.tsx +++ b/src/components/TimeCardPage/TimeSheet.tsx @@ -242,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 84933b9..9c880e1 100644 --- a/src/components/TimeCardPage/TimeTableRow.tsx +++ b/src/components/TimeCardPage/TimeTableRow.tsx @@ -22,6 +22,7 @@ interface RowProps { prevDate: number; onRowChange: Function; TimesheetID: number; + userType: string; // Associate, Supervisor, or Admin } function Row(props: RowProps) { @@ -77,7 +78,7 @@ function Row(props: RowProps) { time={null} /> ), - Hours: , + Hours: , Comment: ( ), From 3b4fc8ced978da671c4d26388a32da36565102ba Mon Sep 17 00:00:00 2001 From: izzyconner Date: Mon, 14 Aug 2023 15:49:58 -0400 Subject: [PATCH 8/8] fixing up bug where no times would be shown initially --- .../CellTypes/ConflictableTimeEntry.tsx | 25 +++++++++++++++---- .../TimeCardPage/CellTypes/TimeEntry.tsx | 4 ++- src/components/TimeCardPage/TimeTableRow.tsx | 6 ++--- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx b/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx index de6fc39..daf5a55 100644 --- a/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx +++ b/src/components/TimeCardPage/CellTypes/ConflictableTimeEntry.tsx @@ -58,24 +58,39 @@ export function ConflicatableTimeEntry(props: ConflicatableTimeEntryProps) { // 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 || - props.row.Associate[props.field] !== props.row.Supervisor[props.field];*/ + 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 ( <> - + /> ) } { let minutes; - if (props.row[props.userType] !== undefined) { + 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); diff --git a/src/components/TimeCardPage/TimeTableRow.tsx b/src/components/TimeCardPage/TimeTableRow.tsx index 9c880e1..4f3378a 100644 --- a/src/components/TimeCardPage/TimeTableRow.tsx +++ b/src/components/TimeCardPage/TimeTableRow.tsx @@ -61,7 +61,7 @@ function Row(props: RowProps) { const items = { Type: , Date: , - // TODO : The userType is likely able to be adjusted, add this to the props for TimeTableRow + // TODO : The userType is likely able to be adjusted, only show conflictable time entry for admins. "Clock-in": ( ), "Clock-out": ( - ), Hours: ,