From 56a43d90e179a491629cd157c684fb2551067ce1 Mon Sep 17 00:00:00 2001 From: ethankong150 Date: Thu, 28 Sep 2023 18:37:06 -0400 Subject: [PATCH 01/11] Co-authored-by: Izzy Conner --- src/components/Auth/apiClient.tsx | 2 +- src/components/TimeCardPage/SubmitCard.tsx | 4 ++-- src/schemas/TimesheetSchema.tsx | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/Auth/apiClient.tsx b/src/components/Auth/apiClient.tsx index 568c090..68930fd 100644 --- a/src/components/Auth/apiClient.tsx +++ b/src/components/Auth/apiClient.tsx @@ -92,7 +92,7 @@ export class ApiClient { UserID: "abc", FirstName: "john", LastName: "doe", - Type: "Supervisor", + Type: "Associate", Picture: "https://www.google.com/koala.png", }; } diff --git a/src/components/TimeCardPage/SubmitCard.tsx b/src/components/TimeCardPage/SubmitCard.tsx index 39f70e5..edc1954 100644 --- a/src/components/TimeCardPage/SubmitCard.tsx +++ b/src/components/TimeCardPage/SubmitCard.tsx @@ -33,11 +33,11 @@ export default function SubmitCard() { - + {submitted && {submitDate} diff --git a/src/schemas/TimesheetSchema.tsx b/src/schemas/TimesheetSchema.tsx index bbd835e..a2e53cf 100644 --- a/src/schemas/TimesheetSchema.tsx +++ b/src/schemas/TimesheetSchema.tsx @@ -14,7 +14,6 @@ export const StatusEntryType = z.union( export const StatusType = z.object({ HoursSubmitted: StatusEntryType, HoursReviewed: StatusEntryType, - ScheduleSubmitted: StatusEntryType, Finalized: StatusEntryType }); From f28ec3150aee7466737d2a87d3c1a713e1330ea8 Mon Sep 17 00:00:00 2001 From: Neeti Date: Mon, 2 Oct 2023 18:37:33 -0400 Subject: [PATCH 02/11] Co-authored-by: Izzy Conner --- src/components/Auth/apiClient.tsx | 12 ++--- src/components/TimeCardPage/SubmitCard.tsx | 49 ++++++++++++++++-- src/components/TimeCardPage/TimeSheet.tsx | 3 +- src/components/TimeCardPage/types.tsx | 12 +++-- src/schemas/StatusSchema.tsx | 20 ++++++++ src/schemas/TimesheetSchema.tsx | 17 +------ src/schemas/UserSchema.tsx | 3 +- src/schemas/backend/UpdateTimesheet.ts | 59 ++++++++++++++++++---- 8 files changed, 135 insertions(+), 40 deletions(-) create mode 100644 src/schemas/StatusSchema.tsx diff --git a/src/components/Auth/apiClient.tsx b/src/components/Auth/apiClient.tsx index 68930fd..643f9ee 100644 --- a/src/components/Auth/apiClient.tsx +++ b/src/components/Auth/apiClient.tsx @@ -74,12 +74,12 @@ export class ApiClient { return this.get("auth/timesheet") as Promise; } - public async updateUserTimesheet(updatedEntry): Promise { - //TODO - Format json? - return this.post("/auth/timesheet", { - timesheet: updatedEntry, - }) as Promise; - } + // public async updateUserTimesheet(updatedEntry): Promise { + // //TODO - Format json? + // return this.post("/auth/timesheet", { + // timesheet: updatedEntry, + // }) as Promise; + // } public async getPasswordTest(): Promise { return this.get("/auth/timesheet") as Promise; diff --git a/src/components/TimeCardPage/SubmitCard.tsx b/src/components/TimeCardPage/SubmitCard.tsx index edc1954..403bc49 100644 --- a/src/components/TimeCardPage/SubmitCard.tsx +++ b/src/components/TimeCardPage/SubmitCard.tsx @@ -4,25 +4,68 @@ import { CardState } from './types' import React, { useState, useEffect } from 'react' import { Box, Card, CardHeader, CardBody, CardFooter, Button } from '@chakra-ui/react'; import { DEFAULT_COLORS } from 'src/constants'; +import ApiClient from 'src/components/Auth/apiClient' +import * as updateSchemas from 'src/schemas/backend/UpdateTimesheet' +import { StatusType, StatusEntryType } from 'src/schemas/StatusSchema'; +import { UserTypes } from './types'; +import { TimesheetStatus } from 'src/schemas/backend/Timesheet'; +import moment from 'moment'; +interface submitCardProps{ + timesheetId: number, + associateId: string, + userType: UserTypes, + timesheetStatus: StatusType +} -export default function SubmitCard() { +export default function SubmitCard(props: submitCardProps) { const [submitted, setSubmitted] = useState(false); const [submitDate, setSubmitDate] = useState(null); const [state, setState] = useState(CardState.Unsubmitted); + let statusEntry: StatusEntryType = undefined; + useEffect(() => { + switch(props.userType){ + case UserTypes.Associate: + statusEntry = props.timesheetStatus.HoursSubmitted + break; + + case UserTypes.Supervisor: + statusEntry = props.timesheetStatus.HoursReviewed + break; + + case UserTypes.Admin: + statusEntry = props.timesheetStatus.Finalized + break; + } + + const isSubmitted = statusEntry === undefined + setSubmitted(isSubmitted) + if (submitted) { + setSubmitDate(moment.unix(statusEntry.Date)) + } + + if() { + + } + //TODO - API Call to determine if the table has been submitted or not. //Will set submitted? here and also submitDate if it was submitted to grab the date }, []) const submitAction = () => { + ApiClient.updateTimesheet(updateSchemas.StatusChangeRequest.parse({ + TimesheetID: props.timesheetId, + AssociateID: props.associateId})) + + // TODO : setup info to read from current db entry setSubmitted(!submitted); const currentTime = new Date(); setSubmitDate(currentTime.toString()); if (state === CardState.Unsubmitted) { - setState(CardState.InReviewEmployer); + setState(CardState.InReviewSupervisor); } else { setState(CardState.Unsubmitted); @@ -32,7 +75,7 @@ export default function SubmitCard() { return ( diff --git a/src/components/TimeCardPage/TimeSheet.tsx b/src/components/TimeCardPage/TimeSheet.tsx index 42ae2b2..a0947e2 100644 --- a/src/components/TimeCardPage/TimeSheet.tsx +++ b/src/components/TimeCardPage/TimeSheet.tsx @@ -35,6 +35,7 @@ import apiClient from '../Auth/apiClient'; import AggregationTable from './AggregationTable'; import { v4 as uuidv4 } from 'uuid'; import { UserSchema } from '../../schemas/UserSchema' +import { TimeSheetSchema } from 'src/schemas/TimesheetSchema'; import { SearchIcon, WarningIcon, DownloadIcon } from '@chakra-ui/icons'; import { Select, components } from 'chakra-react-select' @@ -122,7 +123,7 @@ export default function Page() { // TODO: add types const [userTimesheets, setUserTimesheets] = useState([]); const [currentTimesheets, setCurrentTimesheets] = useState([]); - const [selectedTimesheet, setTimesheet] = useState(undefined); + const [selectedTimesheet, setTimesheet] = useState(undefined); const [selectedTab, setTab] = useState(undefined); diff --git a/src/components/TimeCardPage/types.tsx b/src/components/TimeCardPage/types.tsx index 4c3d219..aeca14e 100644 --- a/src/components/TimeCardPage/types.tsx +++ b/src/components/TimeCardPage/types.tsx @@ -25,8 +25,14 @@ export const TABLE_COLUMNS = ['Type', 'Date', 'Clock-in', 'Clock-Out', 'Hours', export enum CardState { Rejected = "Rejected", - InReviewEmployer = "In Review - Employer", - InReviewBreaktime = "In Review - Breaktime", - Completed = "Completed", + InReviewSupervisor = "In Review - Supervisor", + InReviewAdmin = "In Review - Admin", + AdminFinalized = "Admin Finalized", Unsubmitted = "Unsubmitted" +} + +export enum UserTypes { + Associate = "Associate", + Supervisor = "Supervisor", + Admin = "Admin" } \ No newline at end of file diff --git a/src/schemas/StatusSchema.tsx b/src/schemas/StatusSchema.tsx new file mode 100644 index 0000000..72ea12a --- /dev/null +++ b/src/schemas/StatusSchema.tsx @@ -0,0 +1,20 @@ +import { z } from "zod"; + +// The status is either undefined, for not being at that stage yet, or +// contains the date and author of approving this submission +export const StatusEntryType = z.union( + [z.object({ + Date: z.number(), + AuthorID: z.string() + }), + z.undefined()]); + +// Status type contains the four stages of the pipeline we have defined +export const StatusType = z.object({ + HoursSubmitted: StatusEntryType, + HoursReviewed: StatusEntryType, + Finalized: StatusEntryType +}); + +export type StatusEntryType = z.infer +export type StatusType = z.infer \ No newline at end of file diff --git a/src/schemas/TimesheetSchema.tsx b/src/schemas/TimesheetSchema.tsx index a2e53cf..ba81d74 100644 --- a/src/schemas/TimesheetSchema.tsx +++ b/src/schemas/TimesheetSchema.tsx @@ -1,21 +1,6 @@ import { z } from "zod"; import { RowSchema, ScheduledRowSchema, CommentSchema } from './RowSchema'; - -// The status is either undefined, for not being at that stage yet, or -// contains the date and author of approving this submission -export const StatusEntryType = z.union( - [z.object({ - Date: z.number(), - AuthorID: z.string() - }), - z.undefined()]); - -// Status type contains the four stages of the pipeline we have defined -export const StatusType = z.object({ - HoursSubmitted: StatusEntryType, - HoursReviewed: StatusEntryType, - Finalized: StatusEntryType -}); +import { StatusType } from "./StatusSchema"; export const TimeSheetSchema = z.object({ TimesheetID: z.number(), diff --git a/src/schemas/UserSchema.tsx b/src/schemas/UserSchema.tsx index a51af17..e73e4c0 100644 --- a/src/schemas/UserSchema.tsx +++ b/src/schemas/UserSchema.tsx @@ -1,10 +1,11 @@ import { z } from "zod"; +import { UserTypes } from "src/components/TimeCardPage/types"; export const UserSchema = z.object({ UserID: z.string(), FirstName: z.string(), LastName: z.string(), - Type: z.enum(["Associate", "Supervisor", "Admin"]), + Type: z.enum([UserTypes.Associate, UserTypes.Supervisor, UserTypes.Admin]), Picture: z.string().optional(), }); diff --git a/src/schemas/backend/UpdateTimesheet.ts b/src/schemas/backend/UpdateTimesheet.ts index 851382d..5c93c6f 100644 --- a/src/schemas/backend/UpdateTimesheet.ts +++ b/src/schemas/backend/UpdateTimesheet.ts @@ -11,8 +11,18 @@ import * as dbtypes from './Timesheet' ------------------------------------------------------------------------------------------------------------------- */ +/* + The supported timesheet operations currently supported. + Most operations relate to items that are inside the timesheet, whether it is the rows of the timesheet, the comments someone left + on it for example. + + INSERT - Inserting an item into the timesheet + UPDATE - Updating a specific item in the timesheet + DELETE - Deleting a speciic item in the timesheet -// Currently supported timesheet operations + STATUS_CHANGE - When the timesheet has been submitted / should be advanced to the next stage + CREATE-TIMESHEET - Operation for creating a timesheet, if it would be useful to have in the future. +*/ export const enum TimesheetOperations { INSERT = "INSERT", UPDATE = "UPDATE", @@ -22,21 +32,36 @@ export const enum TimesheetOperations { } - +/* + The available types of items that are currently supported in the timesheet that list operations can be performed on. + TABLEDATA - the rows of the timesheet- basically their worked schedule + SCHEDULEDATA - the expected schedule they should have worked + WEEKNOTES - the comments left by an employer for that week +*/ export const enum TimesheetListItems { TABLEDATA = "TABLEDATA", - SCHEDULEDATA = "SCHEDULEDATA", + SCHEDULEDATA = "SCHEDULEDATA", // TODO : delete this WEEKNOTES = "WEEKNOTES" } const availableListTypes = z.enum([TimesheetListItems.TABLEDATA, TimesheetListItems.SCHEDULEDATA, TimesheetListItems.WEEKNOTES]) +/* + The schema for a delete request + @Type: The type of the item this delete request is processing - see available types in TimesheetListItems + @Id: The id of the item we are deleting - to know what to remove +*/ export const DeleteRequest = z.object({ Type: availableListTypes, Id: z.string() }) export type DeleteRequest = z.infer +/* + The schema for an insert request for an item + @Type: The type of the item that we are inserting, to know what we should be adding this item to + @Item: The item we are actually inserting, should be the actual item itself. +*/ export const InsertRequest = z.object({ Type: availableListTypes, Item: z.union([RowSchema, CommentSchema, ScheduledRowSchema, dbtypes.TimesheetEntrySchema]), @@ -44,10 +69,10 @@ export const InsertRequest = z.object({ export type InsertRequest = z.infer /* Schema for updating an item from the three possible list of items in the timesheet - Type: The field of the timesheet we are updating from the three supported - Id: the id of the entry we are updating - correlates to that row / entry in the list of items - Attribute: The specific attribute of the object we are updating - Data: The payload we are updating this attribute to be - can be a wide range of things currently + @Type: The field of the timesheet we are updating from the three supported + @Id: the id of the entry we are updating - correlates to that row / entry in the list of items + @Attribute: The specific attribute of the object we are updating + @Data: The payload we are updating this attribute to be - can be a wide range of things currently */ export const UpdateRequest = z.object({ Type: availableListTypes, @@ -57,8 +82,22 @@ export const UpdateRequest = z.object({ }) export type UpdateRequest = z.infer +/* + Schema for changing the status of a timesheet + @TimesheetId: The id of the timesheet we are updating + @AssociateId: The id of the associate whose timesheet is being submitted +*/ +export const StatusChangeRequest = z.object({ + TimesheetId: z.string(), + AssociateId: z.string() +}) +export type StatusChangeRequest = z.infer -// The main request body that is used to determine what we should be updating in a request +/* The main request body that is used to determine what we should be updating in a request + @TimesheetID: The id of the timesheet we are updating + @Operation: The type of operation we are performing on this timesheet + @Payload: The contents to be used in the operation for updating this. +*/ export const TimesheetUpdateRequest = z.object({ TimesheetID: z.number(), Operation: z.enum([ @@ -67,8 +106,8 @@ export const TimesheetUpdateRequest = z.object({ TimesheetOperations.DELETE, TimesheetOperations.STATUS_CHANGE, TimesheetOperations.CREATE_TIMESHEET - ]), - Payload: z.any(), + ]), + Payload: z.any() }) export type TimesheetUpdateRequest = z.infer From 915dc430d651cb68cbf04a515df5f5e4c5fbfd50 Mon Sep 17 00:00:00 2001 From: izzyconner <32255130+izzyconner@users.noreply.github.com> Date: Mon, 2 Oct 2023 19:37:39 -0400 Subject: [PATCH 03/11] Minor formatting; Comments; Adding in initial card state for submit card --- src/components/TimeCardPage/SubmitCard.tsx | 150 +++++++++++++-------- src/components/TimeCardPage/types.tsx | 54 ++++---- 2 files changed, 126 insertions(+), 78 deletions(-) diff --git a/src/components/TimeCardPage/SubmitCard.tsx b/src/components/TimeCardPage/SubmitCard.tsx index 403bc49..e11fdf3 100644 --- a/src/components/TimeCardPage/SubmitCard.tsx +++ b/src/components/TimeCardPage/SubmitCard.tsx @@ -1,94 +1,136 @@ -import CommentModal from './CommentModal'; -import { CardState } from './types' - -import React, { useState, useEffect } from 'react' -import { Box, Card, CardHeader, CardBody, CardFooter, Button } from '@chakra-ui/react'; -import { DEFAULT_COLORS } from 'src/constants'; -import ApiClient from 'src/components/Auth/apiClient' -import * as updateSchemas from 'src/schemas/backend/UpdateTimesheet' -import { StatusType, StatusEntryType } from 'src/schemas/StatusSchema'; -import { UserTypes } from './types'; -import { TimesheetStatus } from 'src/schemas/backend/Timesheet'; -import moment from 'moment'; - -interface submitCardProps{ - timesheetId: number, - associateId: string, - userType: UserTypes, - timesheetStatus: StatusType +import CommentModal from "./CommentModal"; +import { CardState } from "./types"; + +import React, { useState, useEffect } from "react"; +import { + Box, + Card, + CardHeader, + CardBody, + CardFooter, + Button, +} from "@chakra-ui/react"; +import { DEFAULT_COLORS } from "src/constants"; +import ApiClient from "src/components/Auth/apiClient"; +import * as updateSchemas from "src/schemas/backend/UpdateTimesheet"; +import { StatusType, StatusEntryType } from "src/schemas/StatusSchema"; +import { UserTypes } from "./types"; +import { TimesheetStatus } from "src/schemas/backend/Timesheet"; +import moment from "moment"; + +interface submitCardProps { + timesheetId: number; + associateId: string; + userType: UserTypes; // TODO : This should really be in global context for react + timesheetStatus: StatusType; } export default function SubmitCard(props: submitCardProps) { + /** Whether or not the logged-in user has submitted this timesheet yet.*/ const [submitted, setSubmitted] = useState(false); + + /** The date and time (as a moment) that the logged-in user submitted/reviewed/finalized this timesheet.*/ const [submitDate, setSubmitDate] = useState(null); - const [state, setState] = useState(CardState.Unsubmitted); - let statusEntry: StatusEntryType = undefined; + /** + * The card state which corresponds to the latest status update from the timesheet. Corresponds to card color. + * Note that this is *not* dependent on the logged in user. I.e. if the latest status update was that + * the supervisor had submitted their timesheet review, the card state would be CardState.InReviewAdmin for + * any associate, supervisor, or admin that was viewing the timesheet. + */ + const [state, setState] = useState(CardState.Unsubmitted); + // TODO: Add information about who submitted when as state variables? i.e. included the authorIds somewhere useEffect(() => { - switch(props.userType){ + let statusEntry: StatusEntryType = undefined; + + // Determine the appropriate status entry to match up with the logged in user's role + switch (props.userType) { case UserTypes.Associate: - statusEntry = props.timesheetStatus.HoursSubmitted + statusEntry = props.timesheetStatus.HoursSubmitted; break; - + case UserTypes.Supervisor: - statusEntry = props.timesheetStatus.HoursReviewed + statusEntry = props.timesheetStatus.HoursReviewed; break; - + case UserTypes.Admin: - statusEntry = props.timesheetStatus.Finalized + statusEntry = props.timesheetStatus.Finalized; break; } - const isSubmitted = statusEntry === undefined - setSubmitted(isSubmitted) + const isSubmitted = statusEntry === undefined; + setSubmitted(isSubmitted); + + // Set the submitted date to when this if (submitted) { - setSubmitDate(moment.unix(statusEntry.Date)) + setSubmitDate(moment.unix(statusEntry.Date)); } - if() { - + // Determine the latest status update to set the card state + if (props.timesheetStatus.Finalized !== undefined) { + setState(CardState.AdminFinalized); + } else if (props.timesheetStatus.HoursReviewed !== undefined) { + setState(CardState.InReviewAdmin); + } else if (props.timesheetStatus.HoursSubmitted !== undefined) { + setState(CardState.InReviewSupervisor); + } else { + setState(CardState.Unsubmitted); } - - //TODO - API Call to determine if the table has been submitted or not. - //Will set submitted? here and also submitDate if it was submitted to grab the date - }, []) + }, []); const submitAction = () => { - ApiClient.updateTimesheet(updateSchemas.StatusChangeRequest.parse({ - TimesheetID: props.timesheetId, - AssociateID: props.associateId})) + // Update the current timesheet to be submitted + ApiClient.updateTimesheet( + updateSchemas.StatusChangeRequest.parse({ + TimesheetID: props.timesheetId, + AssociateID: props.associateId, + }) + ); - // TODO : setup info to read from current db entry + // TODO : setup info to read from current db entry setSubmitted(!submitted); const currentTime = new Date(); setSubmitDate(currentTime.toString()); if (state === CardState.Unsubmitted) { setState(CardState.InReviewSupervisor); - } - else { + } else { setState(CardState.Unsubmitted); } - } + }; return ( - + + className="mb-2 text-center" + > - + - {submitted && - {submitDate} - {state} - - - } + {submitted && ( + + {submitDate} + {state} + + + )} - + ); -} \ No newline at end of file +} diff --git a/src/components/TimeCardPage/types.tsx b/src/components/TimeCardPage/types.tsx index aeca14e..c4d5169 100644 --- a/src/components/TimeCardPage/types.tsx +++ b/src/components/TimeCardPage/types.tsx @@ -1,38 +1,44 @@ export enum CellType { - Regular = "Time Worked", - PTO = "PTO" -}; + Regular = "Time Worked", + PTO = "PTO", +} export enum CellStatus { - Active = "Active", - Deleted = "Deleted" + Active = "Active", + Deleted = "Deleted", } export enum CommentType { - Comment = "Comment", - Report = "Report", -}; + Comment = "Comment", + Report = "Report", +} export const enum Review_Stages { - UNSUBMITTED = "Not-Submitted", - EMPLOYEE_SUBMITTED = "Employee Submitted", - ADMIN_REVIEW = "Review (Breaktime)", - APPROVED = "Approved" -}; - -export const TABLE_COLUMNS = ['Type', 'Date', 'Clock-in', 'Clock-Out', 'Hours', 'Comment']; + UNSUBMITTED = "Not-Submitted", + EMPLOYEE_SUBMITTED = "Employee Submitted", + ADMIN_REVIEW = "Review (Breaktime)", + APPROVED = "Approved", +} +export const TABLE_COLUMNS = [ + "Type", + "Date", + "Clock-in", + "Clock-Out", + "Hours", + "Comment", +]; export enum CardState { - Rejected = "Rejected", - InReviewSupervisor = "In Review - Supervisor", - InReviewAdmin = "In Review - Admin", - AdminFinalized = "Admin Finalized", - Unsubmitted = "Unsubmitted" + Rejected = "Rejected", + InReviewSupervisor = "In Review - Supervisor", + InReviewAdmin = "In Review - Admin", + AdminFinalized = "Finalized by Admin", + Unsubmitted = "Unsubmitted", } export enum UserTypes { - Associate = "Associate", - Supervisor = "Supervisor", - Admin = "Admin" -} \ No newline at end of file + Associate = "Associate", + Supervisor = "Supervisor", + Admin = "Admin", +} From efb29c76ba18e4ba6479ea654f8d116947b06262 Mon Sep 17 00:00:00 2001 From: izzyconner <32255130+izzyconner@users.noreply.github.com> Date: Mon, 2 Oct 2023 20:00:09 -0400 Subject: [PATCH 04/11] formatting; fixing bugs --- src/components/Auth/apiClient.tsx | 7 +- src/components/TimeCardPage/SubmitCard.tsx | 17 +- src/components/TimeCardPage/TimeSheet.tsx | 278 +++++++++++++-------- src/schemas/backend/UpdateTimesheet.ts | 2 +- 4 files changed, 193 insertions(+), 111 deletions(-) diff --git a/src/components/Auth/apiClient.tsx b/src/components/Auth/apiClient.tsx index 643f9ee..07ec819 100644 --- a/src/components/Auth/apiClient.tsx +++ b/src/components/Auth/apiClient.tsx @@ -2,6 +2,7 @@ import { Auth } from "aws-amplify"; import axios, { AxiosInstance } from "axios"; import { TimeSheetSchema } from "../../schemas/TimesheetSchema"; import { UserSchema } from "../../schemas/UserSchema"; +import { UserTypes } from "../TimeCardPage/types"; const defaultBaseUrl = process.env.REACT_APP_API_BASE_URL ?? "http://localhost:3000"; @@ -66,7 +67,7 @@ export class ApiClient { } public async updateTimesheet(req): Promise { - return this.axiosInstance.post('/auth/timesheet', req) + return this.axiosInstance.post("/auth/timesheet", req); } // TODO: setup endpoint for associate/supervisor/admin so it returns a list of timesheets for given uuid @@ -92,7 +93,7 @@ export class ApiClient { UserID: "abc", FirstName: "john", LastName: "doe", - Type: "Associate", + Type: UserTypes.Associate, Picture: "https://www.google.com/koala.png", }; } @@ -104,7 +105,7 @@ export class ApiClient { UserID: "bcd", FirstName: "joe", LastName: "jane", - Type: "Associate", + Type: UserTypes.Associate, Picture: "https://www.google.com/panda.png", }, ]; diff --git a/src/components/TimeCardPage/SubmitCard.tsx b/src/components/TimeCardPage/SubmitCard.tsx index e11fdf3..ba1807e 100644 --- a/src/components/TimeCardPage/SubmitCard.tsx +++ b/src/components/TimeCardPage/SubmitCard.tsx @@ -26,7 +26,6 @@ interface submitCardProps { } export default function SubmitCard(props: submitCardProps) { - /** Whether or not the logged-in user has submitted this timesheet yet.*/ const [submitted, setSubmitted] = useState(false); @@ -35,7 +34,7 @@ export default function SubmitCard(props: submitCardProps) { /** * The card state which corresponds to the latest status update from the timesheet. Corresponds to card color. - * Note that this is *not* dependent on the logged in user. I.e. if the latest status update was that + * Note that this is *not* dependent on the logged in user. I.e. if the latest status update was that * the supervisor had submitted their timesheet review, the card state would be CardState.InReviewAdmin for * any associate, supervisor, or admin that was viewing the timesheet. */ @@ -81,16 +80,20 @@ export default function SubmitCard(props: submitCardProps) { }, []); const submitAction = () => { - // Update the current timesheet to be submitted + console.log(props); + // Update the current timesheet to be submitted by the logged-in user. + // The type of status can be determined on the backend by the user type + // TODO: Might be easiest to just include the user type here though tbh... otherwise more work for backend, + // and we have it readily available here already ApiClient.updateTimesheet( updateSchemas.StatusChangeRequest.parse({ - TimesheetID: props.timesheetId, - AssociateID: props.associateId, + TimesheetId: props.timesheetId, + AssociateId: props.associateId, }) ); - // TODO : setup info to read from current db entry - setSubmitted(!submitted); + // TODO : setup info to read from current db entry, or at least based on response code from API POST + setSubmitted(submitted); const currentTime = new Date(); setSubmitDate(currentTime.toString()); if (state === CardState.Unsubmitted) { diff --git a/src/components/TimeCardPage/TimeSheet.tsx b/src/components/TimeCardPage/TimeSheet.tsx index a0947e2..2d14467 100644 --- a/src/components/TimeCardPage/TimeSheet.tsx +++ b/src/components/TimeCardPage/TimeSheet.tsx @@ -1,8 +1,8 @@ -import React, { useState, useMemo } from 'react'; -import TimeTable from './TimeTable' -import { useEffect } from 'react'; -import SubmitCard from './SubmitCard'; -import DateSelectorCard from './SelectWeekCard'; +import React, { useState, useMemo } from "react"; +import TimeTable from "./TimeTable"; +import { useEffect } from "react"; +import SubmitCard from "./SubmitCard"; +import DateSelectorCard from "./SelectWeekCard"; import { Alert, @@ -22,58 +22,91 @@ import { Spacer, HStack, VStack, - ButtonGroup -} from '@chakra-ui/react' + ButtonGroup, +} from "@chakra-ui/react"; +import { + TIMESHEET_DURATION, + TIMEZONE, + EXAMPLE_TIMESHEET, + EXAMPLE_TIMESHEET_2, +} from "src/constants"; -import { TIMESHEET_DURATION, TIMEZONE, EXAMPLE_TIMESHEET, EXAMPLE_TIMESHEET_2 } from 'src/constants'; - -import { Review_Stages, TABLE_COLUMNS } from './types'; -import moment, { Moment } from 'moment-timezone'; +import { Review_Stages, TABLE_COLUMNS } from "./types"; +import moment, { Moment } from "moment-timezone"; -import apiClient from '../Auth/apiClient'; -import AggregationTable from './AggregationTable'; -import { v4 as uuidv4 } from 'uuid'; -import { UserSchema } from '../../schemas/UserSchema' -import { TimeSheetSchema } from 'src/schemas/TimesheetSchema'; +import apiClient from "../Auth/apiClient"; +import AggregationTable from "./AggregationTable"; +import { v4 as uuidv4 } from "uuid"; +import { UserSchema } from "../../schemas/UserSchema"; +import { TimeSheetSchema } from "src/schemas/TimesheetSchema"; -import { SearchIcon, WarningIcon, DownloadIcon } from '@chakra-ui/icons'; -import { Select, components } from 'chakra-react-select' +import { SearchIcon, WarningIcon, DownloadIcon } from "@chakra-ui/icons"; +import { Select, components } from "chakra-react-select"; -//TODO - Eventually automate this -const user = 'Example User' +//TODO - Eventually automate this +const user = "Example User"; const testingEmployees = [ - { UserID: "abc", FirstName: "joe", LastName: "jane", Type: "Employee", Picture: "https://www.google.com/koala.png" }, - { UserID: "bcd", FirstName: "david", LastName: "lev", Type: "Employee", Picture: "https://www.google.com/panda.png" }, - { UserID: "cde", FirstName: "crys", LastName: "tal", Type: "Employee", Picture: "https://www.google.com/capybara.png" }, - { UserID: "def", FirstName: "ken", LastName: "ney", Type: "Employee", Picture: "https://www.google.com/koala.png" }, -] + { + UserID: "abc", + FirstName: "joe", + LastName: "jane", + Type: "Employee", + Picture: "https://www.google.com/koala.png", + }, + { + UserID: "bcd", + FirstName: "david", + LastName: "lev", + Type: "Employee", + Picture: "https://www.google.com/panda.png", + }, + { + UserID: "cde", + FirstName: "crys", + LastName: "tal", + Type: "Employee", + Picture: "https://www.google.com/capybara.png", + }, + { + UserID: "def", + FirstName: "ken", + LastName: "ney", + Type: "Employee", + Picture: "https://www.google.com/koala.png", + }, +]; function ProfileCard({ employee }) { - return ( - + {employee?.FirstName + " " + employee?.LastName} - ) + ); } function SearchEmployeeTimesheet({ employees, setSelected }) { - const handleChange = (selectedOption) => { setSelected(selectedOption); - } + }; const customStyles = { control: (base) => ({ ...base, - flexDirection: 'row-reverse', + flexDirection: "row-reverse", }), - } + }; const DropdownIndicator = (props) => { return ( @@ -84,29 +117,37 @@ function SearchEmployeeTimesheet({ employees, setSelected }) { }; return ( - - `${option.FirstName + " " + option.LastName}`} - getOptionValue={option => `${option.FirstName + " " + option.LastName}`} /> + getOptionLabel={(option) => + `${option.FirstName + " " + option.LastName}` + } + getOptionValue={(option) => + `${option.FirstName + " " + option.LastName}` + } + /> - ) + ); } export default function Page() { - //const today = moment(); - const [selectedDate, setSelectedDate] = useState(moment().startOf('week').day(0)); + //const today = moment(); + const [selectedDate, setSelectedDate] = useState( + moment().startOf("week").day(0) + ); const updateDateRange = (date: Moment) => { setSelectedDate(date); - //TODO - Refactor this to use the constant in merge with contants branch + //TODO - Refactor this to use the constant in merge with contants branch setCurrentTimesheetsToDisplay(userTimesheets, date); - } + }; // fetch the information of the user whos timesheet is being displayed // if user is an employee selected and user would be the same @@ -123,35 +164,35 @@ export default function Page() { // TODO: add types const [userTimesheets, setUserTimesheets] = useState([]); const [currentTimesheets, setCurrentTimesheets] = useState([]); - const [selectedTimesheet, setTimesheet] = useState(undefined); + const [selectedTimesheet, setTimesheet] = + useState(undefined); const [selectedTab, setTab] = useState(undefined); - // this hook should always run first useEffect(() => { - apiClient.getUser().then(userInfo => { + apiClient.getUser().then((userInfo) => { setUser(userInfo); if (userInfo.Type === "Supervisor" || userInfo.Type === "Admin") { - apiClient.getAllUsers().then(users => { + apiClient.getAllUsers().then((users) => { setAssociates(users); setSelectedUser(users[0]); - }) + }); } - setSelectedUser(userInfo) - }) + setSelectedUser(userInfo); + }); // if employee setSelectedUSer to be userinfo // if supervisor/admin get all users // set selected user - }, []) + }, []); // Pulls user timesheets, marking first returned as the active one useEffect(() => { - apiClient.getUserTimesheets(selectedUser?.UserID).then(timesheets => { + apiClient.getUserTimesheets(selectedUser?.UserID).then((timesheets) => { setUserTimesheets(timesheets); //By Default just render / select the first timesheet for now setCurrentTimesheetsToDisplay(timesheets, selectedDate); }); - }, [selectedUser]) + }, [selectedUser]); const processTimesheetChange = (updated_sheet) => { // Updating the rows of the selected timesheets from our list of timesheets @@ -159,91 +200,128 @@ export default function Page() { if (entry.TimesheetID === selectedTimesheet.TimesheetID) { return { ...entry, - TableData: updated_sheet.TableData - } + TableData: updated_sheet.TableData, + }; } - return entry + return entry; }); setUserTimesheets(modifiedTimesheets); //Also need to update our list of currently selected - TODO come up with a way to not need these duplicated lists - setCurrentTimesheets(currentTimesheets.map( - (entry) => { + setCurrentTimesheets( + currentTimesheets.map((entry) => { if (entry.TimesheetID === selectedTimesheet.TimesheetID) { return { ...entry, - TableData: updated_sheet.TableData - } + TableData: updated_sheet.TableData, + }; } - return entry - } - )); + return entry; + }) + ); // selectedTimesheet.TableData = rows; - } + }; - const setCurrentTimesheetsToDisplay = (timesheets, currentStartDate: Moment) => { - const newCurrentTimesheets = timesheets.filter(sheet => moment.unix(sheet.StartDate).isSame(currentStartDate, 'day')); + const setCurrentTimesheetsToDisplay = ( + timesheets, + currentStartDate: Moment + ) => { + const newCurrentTimesheets = timesheets.filter((sheet) => + moment.unix(sheet.StartDate).isSame(currentStartDate, "day") + ); setCurrentTimesheets(newCurrentTimesheets); setTimesheet(newCurrentTimesheets[0]); if (newCurrentTimesheets.length > 0) { - setTab(newCurrentTimesheets[0].CompanyID) + setTab(newCurrentTimesheets[0].CompanyID); } - } + }; const renderWarning = () => { const currentDate = moment().tz(TIMEZONE); const dateToCheck = moment(selectedDate); - dateToCheck.add(TIMESHEET_DURATION, 'days'); - if (currentDate.isAfter(dateToCheck, 'days')) { - return - - Your timesheet is late! - Please submit this as soon as possible - + dateToCheck.add(TIMESHEET_DURATION, "days"); + if (currentDate.isAfter(dateToCheck, "days")) { + return ( + + + Your timesheet is late! + + Please submit this as soon as possible + + + ); } else { - const dueDuration = dateToCheck.diff(currentDate, 'days'); - return - - Your timesheet is due in {dueDuration} days! - Remember to press the submit button! - + const dueDuration = dateToCheck.diff(currentDate, "days"); + return ( + + + Your timesheet is due in {dueDuration} days! + + Remember to press the submit button! + + + ); } - } - - + }; return ( <> - {(user?.Type === "Supervisor" || user?.Type === "Admin") ? + {user?.Type === "Supervisor" || user?.Type === "Admin" ? ( <> - - } /> - } /> - : <>} + + } /> + } /> + + ) : ( + <> + )} - {selectedTimesheet && } - + {selectedTimesheet && ( + + )} {useMemo(() => renderWarning(), [selectedDate])} - {currentTimesheets.map( - (sheet) => ( - { setTimesheet(sheet); setTab(sheet.CompanyID) }}>{sheet.CompanyID} - ) + {currentTimesheets.map((sheet) => ( + { + setTimesheet(sheet); + setTab(sheet.CompanyID); + }} + > + {sheet.CompanyID} + + ))} + {currentTimesheets.length > 1 && ( + setTab("Total")}>Total )} - {currentTimesheets.length > 1 && setTab("Total")}>Total} - {selectedTab === "Total" ? - () - : (currentTimesheets.length > 0 && )} - + {selectedTab === "Total" ? ( + + ) : ( + currentTimesheets.length > 0 && ( + + ) + )} - ) -} \ No newline at end of file + ); +} diff --git a/src/schemas/backend/UpdateTimesheet.ts b/src/schemas/backend/UpdateTimesheet.ts index 5c93c6f..5fd727d 100644 --- a/src/schemas/backend/UpdateTimesheet.ts +++ b/src/schemas/backend/UpdateTimesheet.ts @@ -88,7 +88,7 @@ export type UpdateRequest = z.infer @AssociateId: The id of the associate whose timesheet is being submitted */ export const StatusChangeRequest = z.object({ - TimesheetId: z.string(), + TimesheetId: z.number(), AssociateId: z.string() }) export type StatusChangeRequest = z.infer From 27f95825fefa379c7d6018d271056326b1f28ca5 Mon Sep 17 00:00:00 2001 From: ethankong150 Date: Thu, 5 Oct 2023 17:40:15 -0400 Subject: [PATCH 05/11] Co-authored-by: Izzy Conner --- package-lock.json | 13 ++++++ package.json | 1 + src/components/TimeCardPage/SubmitCard.tsx | 49 ++++++++++++++-------- src/components/TimeCardPage/TimeSheet.tsx | 35 ++++++++++++++-- 4 files changed, 77 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index 60bdf62..94b3a5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "react-scripts": "^5.0.1", "react-tabs": "^6.0.0", "react-time-picker": "^6.0.4", + "react-toastify": "^9.1.3", "reactjs-popup": "^2.0.5", "typescript": "^5.0.4", "uuid": "^9.0.0", @@ -27786,6 +27787,18 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-toastify": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.3.tgz", + "integrity": "sha512-fPfb8ghtn/XMxw3LkxQBk3IyagNpF/LIKjOBflbexr2AWxAH1MJgvnESwEwBn9liLFXgTKWgBSdZpw9m4OTHTg==", + "dependencies": { + "clsx": "^1.1.1" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "license": "BSD-3-Clause", diff --git a/package.json b/package.json index 9fb1438..562a8c5 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "react-scripts": "^5.0.1", "react-tabs": "^6.0.0", "react-time-picker": "^6.0.4", + "react-toastify": "^9.1.3", "reactjs-popup": "^2.0.5", "typescript": "^5.0.4", "uuid": "^9.0.0", diff --git a/src/components/TimeCardPage/SubmitCard.tsx b/src/components/TimeCardPage/SubmitCard.tsx index ba1807e..db935da 100644 --- a/src/components/TimeCardPage/SubmitCard.tsx +++ b/src/components/TimeCardPage/SubmitCard.tsx @@ -17,15 +17,19 @@ import { StatusType, StatusEntryType } from "src/schemas/StatusSchema"; import { UserTypes } from "./types"; import { TimesheetStatus } from "src/schemas/backend/Timesheet"; import moment from "moment"; +import { useToast } from "@chakra-ui/react"; interface submitCardProps { timesheetId: number; associateId: string; userType: UserTypes; // TODO : This should really be in global context for react timesheetStatus: StatusType; + refreshTimesheetCallback: Function; } export default function SubmitCard(props: submitCardProps) { + const toast = useToast() + /** Whether or not the logged-in user has submitted this timesheet yet.*/ const [submitted, setSubmitted] = useState(false); @@ -80,27 +84,36 @@ export default function SubmitCard(props: submitCardProps) { }, []); const submitAction = () => { - console.log(props); // Update the current timesheet to be submitted by the logged-in user. // The type of status can be determined on the backend by the user type - // TODO: Might be easiest to just include the user type here though tbh... otherwise more work for backend, - // and we have it readily available here already - ApiClient.updateTimesheet( - updateSchemas.StatusChangeRequest.parse({ - TimesheetId: props.timesheetId, - AssociateId: props.associateId, - }) - ); - - // TODO : setup info to read from current db entry, or at least based on response code from API POST - setSubmitted(submitted); - const currentTime = new Date(); - setSubmitDate(currentTime.toString()); - if (state === CardState.Unsubmitted) { - setState(CardState.InReviewSupervisor); - } else { - setState(CardState.Unsubmitted); + try{ + ApiClient.updateTimesheet( + updateSchemas.StatusChangeRequest.parse({ + TimesheetId: props.timesheetId, + AssociateId: props.associateId, + }) + ); + + // TODO: Confirm successful 2xx code response from API + props.refreshTimesheetCallback(); + + toast({title: 'Successful submission!', status: 'success', duration: 3000, isClosable: true}) + } catch(err) { + // TODO: Send toast error message + // toast.error('Uh oh - something went wrong with submitting...') + toast({title: 'Uh oh, something went wrong...', status: 'error', duration: 3000, isClosable: true}) + return; } + + + // setSubmitted(submitted); + // const currentTime = new Date(); + // setSubmitDate(currentTime.toString()); + // if (state === CardState.Unsubmitted) { + // setState(CardState.InReviewSupervisor); + // } else { + // setState(CardState.Unsubmitted); + // } }; return ( diff --git a/src/components/TimeCardPage/TimeSheet.tsx b/src/components/TimeCardPage/TimeSheet.tsx index 2d14467..30e7779 100644 --- a/src/components/TimeCardPage/TimeSheet.tsx +++ b/src/components/TimeCardPage/TimeSheet.tsx @@ -43,6 +43,7 @@ import { TimeSheetSchema } from "src/schemas/TimesheetSchema"; import { SearchIcon, WarningIcon, DownloadIcon } from "@chakra-ui/icons"; import { Select, components } from "chakra-react-select"; +import { useToast } from '@chakra-ui/react' //TODO - Eventually automate this const user = "Example User"; @@ -138,6 +139,7 @@ function SearchEmployeeTimesheet({ employees, setSelected }) { } export default function Page() { + const toast = useToast() //const today = moment(); const [selectedDate, setSelectedDate] = useState( moment().startOf("week").day(0) @@ -185,15 +187,29 @@ export default function Page() { // set selected user }, []); - // Pulls user timesheets, marking first returned as the active one - useEffect(() => { - apiClient.getUserTimesheets(selectedUser?.UserID).then((timesheets) => { + const getUpdatedTimesheet = (userId) => { + apiClient.getUserTimesheets(userId).then((timesheets) => { setUserTimesheets(timesheets); //By Default just render / select the first timesheet for now setCurrentTimesheetsToDisplay(timesheets, selectedDate); }); + } + + // Pulls user timesheets, marking first returned as the active one + useEffect(() => { + getUpdatedTimesheet(selectedUser?.UserID) }, [selectedUser]); + // Callback function that triggers GET request to the API to grab the most recent version of timesheets for the current selected user, + // and re-sets the current timesheet state variable + const forceRefreshTimesheet = () => { + if (selectedUser !== undefined) { + getUpdatedTimesheet(selectedUser.UserID); + } else { + toast({title: 'error', status: 'error', duration: 3000, isClosable: true}) + } + } + const processTimesheetChange = (updated_sheet) => { // Updating the rows of the selected timesheets from our list of timesheets const modifiedTimesheets = userTimesheets.map((entry) => { @@ -290,6 +306,7 @@ export default function Page() { associateId={selectedTimesheet.UserID} userType={user.Type} timesheetStatus={selectedTimesheet.Status} + refreshTimesheetCallback={forceRefreshTimesheet} /> )} @@ -322,6 +339,18 @@ export default function Page() { /> ) )} + {/* */} ); } From 64d673491766727e80a9806ffb88c4c15e8e20dc Mon Sep 17 00:00:00 2001 From: izzyconner <32255130+izzyconner@users.noreply.github.com> Date: Wed, 25 Oct 2023 20:22:55 -0400 Subject: [PATCH 06/11] Brought frontend shared schema for status change requests up to date with backend --- src/components/TimeCardPage/SubmitCard.tsx | 24 +++++++++++++++------- src/schemas/TimesheetSchema.tsx | 22 ++++++++++++-------- src/schemas/backend/Timesheet.ts | 10 ++++----- src/schemas/backend/UpdateTimesheet.ts | 6 +++++- 4 files changed, 40 insertions(+), 22 deletions(-) diff --git a/src/components/TimeCardPage/SubmitCard.tsx b/src/components/TimeCardPage/SubmitCard.tsx index db935da..7cb2617 100644 --- a/src/components/TimeCardPage/SubmitCard.tsx +++ b/src/components/TimeCardPage/SubmitCard.tsx @@ -15,7 +15,7 @@ import ApiClient from "src/components/Auth/apiClient"; import * as updateSchemas from "src/schemas/backend/UpdateTimesheet"; import { StatusType, StatusEntryType } from "src/schemas/StatusSchema"; import { UserTypes } from "./types"; -import { TimesheetStatus } from "src/schemas/backend/Timesheet"; +import { TimesheetStatusSchema } from "src/schemas/backend/Timesheet"; import moment from "moment"; import { useToast } from "@chakra-ui/react"; @@ -28,7 +28,7 @@ interface submitCardProps { } export default function SubmitCard(props: submitCardProps) { - const toast = useToast() + const toast = useToast(); /** Whether or not the logged-in user has submitted this timesheet yet.*/ const [submitted, setSubmitted] = useState(false); @@ -86,8 +86,9 @@ export default function SubmitCard(props: submitCardProps) { const submitAction = () => { // Update the current timesheet to be submitted by the logged-in user. // The type of status can be determined on the backend by the user type - try{ + try { ApiClient.updateTimesheet( + // TODO: This needs to get updated; match up status change request schema with backend) updateSchemas.StatusChangeRequest.parse({ TimesheetId: props.timesheetId, AssociateId: props.associateId, @@ -97,15 +98,24 @@ export default function SubmitCard(props: submitCardProps) { // TODO: Confirm successful 2xx code response from API props.refreshTimesheetCallback(); - toast({title: 'Successful submission!', status: 'success', duration: 3000, isClosable: true}) - } catch(err) { + toast({ + title: "Successful submission!", + status: "success", + duration: 3000, + isClosable: true, + }); + } catch (err) { // TODO: Send toast error message // toast.error('Uh oh - something went wrong with submitting...') - toast({title: 'Uh oh, something went wrong...', status: 'error', duration: 3000, isClosable: true}) + toast({ + title: "Uh oh, something went wrong...", + status: "error", + duration: 3000, + isClosable: true, + }); return; } - // setSubmitted(submitted); // const currentTime = new Date(); // setSubmitDate(currentTime.toString()); diff --git a/src/schemas/TimesheetSchema.tsx b/src/schemas/TimesheetSchema.tsx index ba81d74..3ce5e00 100644 --- a/src/schemas/TimesheetSchema.tsx +++ b/src/schemas/TimesheetSchema.tsx @@ -1,16 +1,22 @@ import { z } from "zod"; -import { RowSchema, ScheduledRowSchema, CommentSchema } from './RowSchema'; +import { RowSchema, ScheduledRowSchema, CommentSchema } from "./RowSchema"; import { StatusType } from "./StatusSchema"; export const TimeSheetSchema = z.object({ - TimesheetID: z.number(), - UserID: z.string(), + TimesheetID: z.number(), + UserID: z.string(), StartDate: z.number(), - Status: StatusType, - CompanyID: z.string(), - TableData: z.array(RowSchema), + Status: StatusType, + CompanyID: z.string(), + TableData: z.array(RowSchema), ScheduleTableData: z.union([z.undefined(), z.array(ScheduledRowSchema)]), - WeekNotes: z.union([z.undefined(), z.array(CommentSchema)]), -}); + WeekNotes: z.union([z.undefined(), z.array(CommentSchema)]), +}); export type TimeSheetSchema = z.infer; + +export enum TimesheetStatus { + HOURS_SUBMITTED = "HoursSubmitted", + HOURS_REVIEWED = "HoursReviewed", + FINALIZED = "Finalized", +} diff --git a/src/schemas/backend/Timesheet.ts b/src/schemas/backend/Timesheet.ts index 949513e..541cfcc 100644 --- a/src/schemas/backend/Timesheet.ts +++ b/src/schemas/backend/Timesheet.ts @@ -73,15 +73,13 @@ export const StatusEntryType = z.union( z.undefined()]); // Status type contains the four stages of the pipeline we have defined -export const TimesheetStatus = z.object({ +export const TimesheetStatusSchema = z.object({ HoursSubmitted: StatusEntryType, HoursReviewed: StatusEntryType, ScheduleSubmitted: StatusEntryType, Finalized: StatusEntryType }); - - /** * Represents the database schema for a weekly timesheet */ @@ -89,16 +87,16 @@ export const TimeSheetSchema = z.object({ TimesheetID: z.number(), UserID: z.string(), StartDate: z.number(), - Status: TimesheetStatus, + Status: TimesheetStatusSchema, CompanyID: z.string(), HoursData: z.array(TimesheetEntrySchema).default([]), ScheduleData: z.array(ScheduleEntrySchema).default([]), WeekNotes: z.array(NoteSchema).default([]), }) -export type TimesheetStatus = z.infer +export type TimesheetStatus = z.infer export type TimeEntrySchema = z.infer export type ScheduleEntrySchema = z.infer export type NoteSchema = z.infer export type TimesheetEntrySchema = z.infer -export type TimeSheetSchema = z.infer +export type TimeSheetSchema = z.infer \ No newline at end of file diff --git a/src/schemas/backend/UpdateTimesheet.ts b/src/schemas/backend/UpdateTimesheet.ts index 5fd727d..a546ff7 100644 --- a/src/schemas/backend/UpdateTimesheet.ts +++ b/src/schemas/backend/UpdateTimesheet.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { RowSchema, CommentSchema, ScheduledRowSchema } from "../RowSchema" import * as dbtypes from './Timesheet' +import { TimesheetStatus } from "../TimesheetSchema"; /* ------------------------------------------------------------------------------------------------------------------- @@ -89,7 +90,10 @@ export type UpdateRequest = z.infer */ export const StatusChangeRequest = z.object({ TimesheetId: z.number(), - AssociateId: z.string() + AssociateId: z.string(), + authorId: z.string(), + dateSubmitted: z.number(), + statusType: z.enum([TimesheetStatus.FINALIZED, TimesheetStatus.HOURS_REVIEWED, TimesheetStatus.HOURS_SUBMITTED]) }) export type StatusChangeRequest = z.infer From d15b51a28195c3f12941a3f45971a6b57f35c120 Mon Sep 17 00:00:00 2001 From: izzyconner <32255130+izzyconner@users.noreply.github.com> Date: Wed, 25 Oct 2023 20:23:55 -0400 Subject: [PATCH 07/11] adding todos --- src/components/TimeCardPage/SubmitCard.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/TimeCardPage/SubmitCard.tsx b/src/components/TimeCardPage/SubmitCard.tsx index 7cb2617..a3d9f26 100644 --- a/src/components/TimeCardPage/SubmitCard.tsx +++ b/src/components/TimeCardPage/SubmitCard.tsx @@ -87,6 +87,7 @@ export default function SubmitCard(props: submitCardProps) { // Update the current timesheet to be submitted by the logged-in user. // The type of status can be determined on the backend by the user type try { + // TODO comment this out if not testing end-to-end functionality ApiClient.updateTimesheet( // TODO: This needs to get updated; match up status change request schema with backend) updateSchemas.StatusChangeRequest.parse({ From d51c51bd89c087b316350b9f2352463f6fd3fc1b Mon Sep 17 00:00:00 2001 From: izzyconner <32255130+izzyconner@users.noreply.github.com> Date: Wed, 25 Oct 2023 20:38:16 -0400 Subject: [PATCH 08/11] adding in placeholder text and todos --- src/components/TimeCardPage/SubmitCard.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/TimeCardPage/SubmitCard.tsx b/src/components/TimeCardPage/SubmitCard.tsx index a3d9f26..efed88b 100644 --- a/src/components/TimeCardPage/SubmitCard.tsx +++ b/src/components/TimeCardPage/SubmitCard.tsx @@ -154,6 +154,10 @@ export default function SubmitCard(props: submitCardProps) { {submitDate} {state} + {/* TODO: this should come from each StatusEntry in the props.timesheetStatus object */} + Associate: abcb34993-1289378457-abdbd, 10-12-2023 + Supervisor: some-toher-id, 10-14-2023 + Admin: not submitted )} From 7e6a62a75f4ea234cdf785bb701649904b1aa9f7 Mon Sep 17 00:00:00 2001 From: na-rachl Date: Tue, 7 Nov 2023 10:08:30 -0500 Subject: [PATCH 09/11] added status --- src/components/TimeCardPage/SubmitCard.tsx | 56 ++++++++++++++++++++-- src/components/TimeCardPage/TimeSheet.tsx | 20 ++++++++ src/setupTests.js | 1 + 3 files changed, 72 insertions(+), 5 deletions(-) diff --git a/src/components/TimeCardPage/SubmitCard.tsx b/src/components/TimeCardPage/SubmitCard.tsx index efed88b..d21c7d2 100644 --- a/src/components/TimeCardPage/SubmitCard.tsx +++ b/src/components/TimeCardPage/SubmitCard.tsx @@ -27,7 +27,10 @@ interface submitCardProps { refreshTimesheetCallback: Function; } + + export default function SubmitCard(props: submitCardProps) { + const toast = useToast(); /** Whether or not the logged-in user has submitted this timesheet yet.*/ @@ -153,15 +156,58 @@ export default function SubmitCard(props: submitCardProps) { {submitted && ( {submitDate} - {state} + {/*{state}*/} {/* TODO: this should come from each StatusEntry in the props.timesheetStatus object */} - Associate: abcb34993-1289378457-abdbd, 10-12-2023 - Supervisor: some-toher-id, 10-14-2023 - Admin: not submitted - + {/*Associate: abcb34993-1289378457-abdbd, 10-12-2023*/} + {/*Supervisor: some-toher-id, 10-14-2023*/} + {/*Admin: not submitted*/} + +
+ {props.timesheetStatus.HoursSubmitted && props.timesheetStatus.HoursSubmitted.Date ? ( +

+ Associate: {props.timesheetStatus.HoursSubmitted.AuthorID}, {customDateFormat(props.timesheetStatus.HoursSubmitted.Date)} +

+ ) : (

Associate: Unsubmitted

)} + + {props.timesheetStatus.HoursReviewed && props.timesheetStatus.HoursReviewed.Date ? ( +

+ Supervsior: {props.timesheetStatus.HoursReviewed.AuthorID}, {customDateFormat(props.timesheetStatus.HoursReviewed.Date)} +

+ ) : (

Supervsior:Unsubmitted

)} + + {props.timesheetStatus.Finalized && props.timesheetStatus.Finalized.Date ? ( +

+ Admin: {props.timesheetStatus.Finalized.AuthorID}, {customDateFormat(props.timesheetStatus.Finalized.Date)} +

+ ) :(

Admin: Unsubmitted

)} + + + +
+ + + + + + + +
)}
); } + +// function customDateFormat(date) { +// const dateX = new Date(date * 1000); +// const year = dateX.getFullYear(); // Get the full year (e.g., 2023) +// const month = (dateX.getMonth() + 1).toString().padStart(2, '0'); // Get the month (1-12) and pad with leading zero if needed +// const day = dateX.getDate().toString().padStart(2, '0'); // Get the day of the month (1-31) and pad with leading zero if needed +// +// return `${month}/${day}/${year.toString().slice(-2)}`; +// } +function customDateFormat(date) { + const dateX = new Date(date * 1000); + return dateX.toLocaleDateString('en-US', { year: '2-digit', month: '2-digit', day: '2-digit' }); +} \ No newline at end of file diff --git a/src/components/TimeCardPage/TimeSheet.tsx b/src/components/TimeCardPage/TimeSheet.tsx index 30e7779..ec7101c 100644 --- a/src/components/TimeCardPage/TimeSheet.tsx +++ b/src/components/TimeCardPage/TimeSheet.tsx @@ -79,6 +79,25 @@ const testingEmployees = [ }, ]; + +const mockStatusObject ={ + HoursSubmitted:{ + Date:1698590844, + AuthorID: "John", + }, + HoursReviewed: { + Date: 1698590844, + AuthorID: "Jane", + }, + Finalized: { + Date: 1698590844, + AuthorID: "Jackie", + }, + +}; + + + function ProfileCard({ employee }) { return ( @@ -306,6 +325,7 @@ export default function Page() { associateId={selectedTimesheet.UserID} userType={user.Type} timesheetStatus={selectedTimesheet.Status} + //timesheetStatus={mockStatusObject} refreshTimesheetCallback={forceRefreshTimesheet} /> )} diff --git a/src/setupTests.js b/src/setupTests.js index 1dd407a..4c2ef26 100644 --- a/src/setupTests.js +++ b/src/setupTests.js @@ -3,3 +3,4 @@ // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom import "@testing-library/jest-dom"; + From b61b25f856cfbd2f43578c24b78b98c4bcb4c85a Mon Sep 17 00:00:00 2001 From: na-rachl Date: Tue, 7 Nov 2023 10:12:25 -0500 Subject: [PATCH 10/11] added status info --- src/components/TimeCardPage/SubmitCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TimeCardPage/SubmitCard.tsx b/src/components/TimeCardPage/SubmitCard.tsx index d21c7d2..16a430a 100644 --- a/src/components/TimeCardPage/SubmitCard.tsx +++ b/src/components/TimeCardPage/SubmitCard.tsx @@ -46,7 +46,7 @@ export default function SubmitCard(props: submitCardProps) { * any associate, supervisor, or admin that was viewing the timesheet. */ const [state, setState] = useState(CardState.Unsubmitted); - // TODO: Add information about who submitted when as state variables? i.e. included the authorIds somewhere + // TODO: Add information about who submitted when as state variables? i.e. included the authorIds somewhere useEffect(() => { let statusEntry: StatusEntryType = undefined; From 12251725007a6cce344bf5783105a1c3db331a19 Mon Sep 17 00:00:00 2001 From: na-rachl Date: Thu, 7 Dec 2023 17:32:31 -0500 Subject: [PATCH 11/11] submit card? --- src/components/TimeCardPage/SubmitCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TimeCardPage/SubmitCard.tsx b/src/components/TimeCardPage/SubmitCard.tsx index 16a430a..4df6e61 100644 --- a/src/components/TimeCardPage/SubmitCard.tsx +++ b/src/components/TimeCardPage/SubmitCard.tsx @@ -179,7 +179,7 @@ export default function SubmitCard(props: submitCardProps) {

Admin: {props.timesheetStatus.Finalized.AuthorID}, {customDateFormat(props.timesheetStatus.Finalized.Date)}

- ) :(

Admin: Unsubmitted

)} + ) :(

Admin: Unsubmitted

)}