From 8d4d7064481c7645d61b73caf5dfd42cae8165a6 Mon Sep 17 00:00:00 2001 From: Areos Chen Date: Wed, 17 Apr 2024 14:47:19 +0930 Subject: [PATCH] reconstructure: await/defer orders/staff/setting/worklogs page --- src/apis/api_staff.ts | 3 +- .../table/tableBtn/PSDisplayBtn.tsx | 2 +- src/configs/zustore/payslipStore.ts | 1 - src/pages/client/MainContent.tsx | 22 +-- src/pages/dashboard/MainContent.tsx | 30 ++-- src/pages/dashboard/dashboard.tsx | 21 +-- src/pages/orders/MainContent.tsx | 118 ++++++++++++++ src/pages/orders/Orders.tsx | 112 ++------------ src/pages/orders/SubTable.tsx | 14 ++ src/pages/setting/MainContent.tsx | 60 +++++++ src/pages/setting/setting.tsx | 65 ++------ src/pages/staff/MainContent.tsx | 138 +++++++++++++++++ src/pages/staff/SubTable.tsx | 7 +- src/pages/staff/staff.tsx | 146 ++++-------------- src/pages/worklogs/MainContent.tsx | 88 +++++++++++ src/pages/worklogs/WorkLogs.tsx | 72 ++------- src/routerAccFns/actions/staffAction.ts | 12 +- src/routerAccFns/loaders/clientPageLoader.ts | 55 ++----- src/routerAccFns/loaders/dashboardLoader.ts | 4 +- src/routerAccFns/loaders/ordersPageLoader.ts | 59 +------ src/routerAccFns/loaders/settingPageLoader.ts | 34 ++-- src/routerAccFns/loaders/staffPageLoader.ts | 51 +----- src/routerAccFns/loaders/worklogsLoader.ts | 44 ++---- 23 files changed, 576 insertions(+), 582 deletions(-) create mode 100644 src/pages/orders/MainContent.tsx create mode 100644 src/pages/orders/SubTable.tsx create mode 100644 src/pages/setting/MainContent.tsx create mode 100644 src/pages/staff/MainContent.tsx create mode 100644 src/pages/worklogs/MainContent.tsx diff --git a/src/apis/api_staff.ts b/src/apis/api_staff.ts index 6f7fb08..5e9a4d2 100644 --- a/src/apis/api_staff.ts +++ b/src/apis/api_staff.ts @@ -11,8 +11,7 @@ import { Tstaff, TstaffForm } from "@/configs/schema/staffSchema"; export const staffAll = async (): Promise => { try { - const response = await apis.get(STAFF_ALL); - return response.data; + return await apis.get(STAFF_ALL).then((res) => res.data); } catch (err: unknown) { console.log("-> retrieve all staff error: ", err); return { diff --git a/src/components/table/tableBtn/PSDisplayBtn.tsx b/src/components/table/tableBtn/PSDisplayBtn.tsx index 2e61085..1e79325 100644 --- a/src/components/table/tableBtn/PSDisplayBtn.tsx +++ b/src/components/table/tableBtn/PSDisplayBtn.tsx @@ -20,7 +20,7 @@ type Tprops = { const PSDisplayBtn: FC = ({ payslip }) => { const [, setModalOpen] = useAtom(atModalOpen); const allStaff = useStaffStore((state) => state.allStaff); - const [, setStaff] = useAtom(atStaff); + const [, setStaff] = useAtom(atStaff); // single staff const allStaffWL = useStaffWLStore((state) => state.allStaffWL); const setPayslip = usePayslipStore((state) => state.setPayslip); const allBonus = usePayslipStore((state) => state.allBonus); diff --git a/src/configs/zustore/payslipStore.ts b/src/configs/zustore/payslipStore.ts index 0f6d17c..26b7219 100644 --- a/src/configs/zustore/payslipStore.ts +++ b/src/configs/zustore/payslipStore.ts @@ -39,7 +39,6 @@ export const payslipStore = createStore((set) => ({ deduction: [], allBonus: [], resetAll: () => { - console.log("--> reset all called"); set((state) => ({ ...state, dayRange: { from: undefined, to: undefined }, diff --git a/src/pages/client/MainContent.tsx b/src/pages/client/MainContent.tsx index 48c572a..e7851a5 100644 --- a/src/pages/client/MainContent.tsx +++ b/src/pages/client/MainContent.tsx @@ -63,10 +63,12 @@ const MainContent: FC = () => { work_logs: [], }; + setClient(client[0]); + setCompany(company); + setLogo(logo); + + // condition setState should be in useEffect useEffect(() => { - setClient(client[0]); - setCompany(company); - setLogo(logo); if (uniData) { setUniData(uniData); setServiceDesc({ @@ -82,18 +84,8 @@ const MainContent: FC = () => { netto: uniData?.services[0].unit_price as number, }); } - }, [ - setClient, - setCompany, - setLogo, - client, - company, - logo, - setUniData, - uniData, - setServiceDesc, - clientOrder, - ]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [clientOrder, uniData]); return (
diff --git a/src/pages/dashboard/MainContent.tsx b/src/pages/dashboard/MainContent.tsx index 175b5f7..15143b4 100644 --- a/src/pages/dashboard/MainContent.tsx +++ b/src/pages/dashboard/MainContent.tsx @@ -1,26 +1,36 @@ import { TwlTableRow } from "@/configs/schema/workSchema"; import type { FC } from "react"; -import { useEffect } from "react"; import DutyCard from "./DutyCard"; import { useTodayWLStore } from "@/configs/zustore/todayWLStore"; +import { useAsyncValue } from "react-router-dom"; +import { dateFormat, hmsTohm } from "@/lib/time"; -type Tprops = { - todayWLs: TwlTableRow[]; -}; +const MainContent: FC = () => { + const [todayWls] = useAsyncValue() as [TwlTableRow[]]; + + const setTodayWorklogs = useTodayWLStore((state) => state.setWorklogs); -const MainContent: FC = ({ todayWLs }) => { - const setWorklogs = useTodayWLStore((state) => state.setWorklogs); + const newTodayWLs = todayWls.map((wl: TwlTableRow) => { + return { + ...wl, + // convert the date format stored in mysql: yyyy-mm-dd to au: dd-mm-yyyy + // this format is related to date searching in the table + wl_date: dateFormat(wl.wl_date, "au"), + s_time: hmsTohm(wl.s_time as string), + e_time: hmsTohm(wl.e_time as string), + b_time: hmsTohm(wl.b_time as string), + b_hour: hmsTohm(wl.b_hour as string), + }; + }); - useEffect(() => { - setWorklogs(todayWLs); - }, [todayWLs, setWorklogs]); + setTodayWorklogs(newTodayWLs); return (
- +
); diff --git a/src/pages/dashboard/dashboard.tsx b/src/pages/dashboard/dashboard.tsx index ef6160e..193906e 100644 --- a/src/pages/dashboard/dashboard.tsx +++ b/src/pages/dashboard/dashboard.tsx @@ -2,7 +2,6 @@ import type { FC } from "react"; import { Suspense, useEffect } from "react"; import { Await, useActionData, useLoaderData } from "react-router-dom"; import SpinningEle from "@/components/loadingEle/SpinningEle"; -import { TwlTableRow } from "@/configs/schema/workSchema"; import MTimeTracker from "@/pageComponents/modals/mTimeTracker"; import { RES_STATUS } from "@/configs/types"; import { atModalOpen } from "@/configs/atoms"; @@ -10,7 +9,6 @@ import { useAtom } from "jotai"; import { mOpenOps } from "@/configs/utils/modal"; import { toastError, toastSuccess } from "@/lib/toaster"; import { useTranslation } from "react-i18next"; -import { dateFormat, hmsTohm } from "@/lib/time"; import ErrorTips from "@/components/ErrorTips"; import MainContent from "./MainContent"; @@ -50,24 +48,7 @@ const Dashboard: FC = () => {
}> }> - {(result) => { - const [todayWLs] = result; - const today = !todayWLs.data - ? [] - : todayWLs.data.map((wl: TwlTableRow) => { - return { - ...wl, - // convert the date format stored in mysql: yyyy-mm-dd to au: dd-mm-yyyy - // this format is related to date searching in the table - wl_date: dateFormat(wl.wl_date, "au"), - s_time: hmsTohm(wl.s_time as string), - e_time: hmsTohm(wl.e_time as string), - b_time: hmsTohm(wl.b_time as string), - b_hour: hmsTohm(wl.b_hour as string), - }; - }); - return ; - }} + diff --git a/src/pages/orders/MainContent.tsx b/src/pages/orders/MainContent.tsx new file mode 100644 index 0000000..612b3a6 --- /dev/null +++ b/src/pages/orders/MainContent.tsx @@ -0,0 +1,118 @@ +import type { FC } from "react"; +import { useMemo } from "react"; +import Card from "@/components/card"; +import { PTable } from "@/components/table"; +import SubTable from "./SubTable"; +import { useTranslation } from "react-i18next"; +import { Torder } from "@/configs/schema/orderSchema"; +import { Tunivers } from "@/configs/types"; +import { Tcompany } from "@/configs/schema/settingSchema"; +import { TstaffWPayslip } from "@/configs/schema/staffSchema"; +import { useAtom } from "jotai"; +import { + atAllStaff, + atCompany, + atLogo, + atOrder, + atSUData, +} from "@/configs/atoms"; +import { useAsyncValue } from "react-router-dom"; +import { hmsTohm } from "@/lib/time"; +import orderColumns from "@/configs/columnDefs/defOrders"; + +const MainContent: FC = () => { + const { t } = useTranslation(); + const [, setClientOrder] = useAtom(atOrder); + const [, setAllStaff] = useAtom(atAllStaff); + const [, setUniData] = useAtom(atSUData); + const [, setCompany] = useAtom(atCompany); + const [, setLogo] = useAtom(atLogo); + + const [orders, staff, uniData, company, logo] = useAsyncValue() as [ + Torder[], + TstaffWPayslip[], + Tunivers, + Tcompany, + string, + ]; + + const newOrders = useMemo(() => { + return orders.map((item) => { + return { + ...item, + order_services: item.order_services + .sort((a, b) => a.ranking - b.ranking) + .map((desc) => { + return { + ...desc, + taxable: Boolean(desc.taxable), + }; + }), + work_logs: item.work_logs.map((wl) => { + return { + ...wl, + assigned_work: wl.assigned_work.map((aw) => { + return { + ...aw, + s_time: hmsTohm(aw.s_time as string), + e_time: hmsTohm(aw.e_time as string), + b_hour: hmsTohm(aw.b_hour as string), + }; + }), + }; + }), + }; + }); + }, [orders]); + + setAllStaff(staff); + setCompany(company); + setLogo(logo); + setUniData(uniData); + + return ( +
+ {/* header area */} + + {/* table */} + {newOrders ? ( + + { + if (row.original.order_services.length > 0) { + return true; + } + return false; + }} + expandContent={SubTable} + cnSearch="my-3" + cnTable={`h-[70dvh]`} + cnHead="sticky z-10 bg-indigo-300" + cnTh="py-3" + /> + + ) : ( + + + {t("label.noContent")} + + + )} +
+ ); +}; + +export default MainContent; diff --git a/src/pages/orders/Orders.tsx b/src/pages/orders/Orders.tsx index cb14bfe..7bfe837 100644 --- a/src/pages/orders/Orders.tsx +++ b/src/pages/orders/Orders.tsx @@ -1,12 +1,9 @@ import { FC, Suspense, useEffect } from "react"; import { Await, useLoaderData, useActionData } from "react-router-dom"; import LoadingPage from "@/components/loadingEle"; -import Card from "@/components/card"; import { Torder } from "@/configs/schema/orderSchema"; import type { Tunivers } from "@/configs/types"; import { RES_STATUS } from "@/configs/types"; -import { PTable } from "@/components/table"; -import orderColumns from "@/configs/columnDefs/defOrders"; import { MJobAssign, MOrderDel, @@ -15,59 +12,24 @@ import { MpdfMaker, } from "@/pageComponents/modals"; import { useAtom } from "jotai"; -import { - atAllStaff, - atCompany, - atLogo, - atModalOpen, - atOrder, - atSUData, -} from "@/configs/atoms"; +import { atModalOpen } from "@/configs/atoms"; import { Tcompany } from "@/configs/schema/settingSchema"; import { mOpenOps } from "@/configs/utils/modal"; import { toastError, toastSuccess } from "@/lib/toaster"; import { useTranslation } from "react-i18next"; -import { orderSubTable } from "@/pageComponents/orderSubTables"; -import { Tstaff } from "@/configs/schema/staffSchema"; -import SubTableSwitch from "@/components/table/SubTableSwitch"; - -type Torders = { - orders: Torder[] | null; -}; +import { TstaffWPayslip } from "@/configs/schema/staffSchema"; +import ErrorTips from "@/components/ErrorTips"; +import MainContent from "./MainContent"; const Orders: FC = () => { const { t } = useTranslation(); - const { orders, uniData, company, logo, staff } = useLoaderData() as { - orders: Torder[]; - uniData: Tunivers; - company: Tcompany; - logo: string; - staff: Tstaff[]; - }; - const actionData = useActionData() as Tresponse; - const [, setClientOrder] = useAtom(atOrder); - const [, setAllStaff] = useAtom(atAllStaff); const [modalOpen, setModalOpen] = useAtom(atModalOpen); - const [, setUniData] = useAtom(atSUData); - const [, setCompany] = useAtom(atCompany); - const [, setLogo] = useAtom(atLogo); - - useEffect(() => { - setAllStaff(staff); - setCompany(company); - setLogo(logo); - setUniData(uniData); - }, [ - setAllStaff, - staff, - setCompany, - company, - setLogo, - logo, - setUniData, - uniData, - ]); + const { allPromise } = useLoaderData() as { + allPromise: Promise< + [Torder[], TstaffWPayslip[], Tunivers, Tcompany, string] + >; + }; useEffect(() => { if (!actionData) return; @@ -118,63 +80,11 @@ const Orders: FC = () => { actionData.status = RES_STATUS.DEFAULT; }, [actionData, modalOpen, setModalOpen, t]); - const SubTable = ({ data }: { data: Torder }) => { - return ; - }; - - const OrderTableContent: FC = ({ orders }) => { - return ( -
- {/* header area */} - - {/* table */} - {orders ? ( - - { - if (row.original.order_services.length > 0) { - return true; - } - return false; - }} - expandContent={SubTable} - cnSearch="my-3" - cnTable={`h-[70dvh]`} - cnHead="sticky z-10 bg-indigo-300" - cnTh="py-3" - /> - - ) : ( - - - {t("label.noContent")} - - - )} -
- ); - }; - return (
}> - - {(ordersList) => { - return ; - }} + }> + diff --git a/src/pages/orders/SubTable.tsx b/src/pages/orders/SubTable.tsx new file mode 100644 index 0000000..1cc8d44 --- /dev/null +++ b/src/pages/orders/SubTable.tsx @@ -0,0 +1,14 @@ +import type { FC } from "react"; +import SubTableSwitch from "@/components/table/SubTableSwitch"; +import { Torder } from "@/configs/schema/orderSchema"; +import { orderSubTable } from "@/pageComponents/orderSubTables"; + +type Tprops = { + data: Torder; +}; + +const SubTable: FC = ({ data }) => { + return ; +}; + +export default SubTable; diff --git a/src/pages/setting/MainContent.tsx b/src/pages/setting/MainContent.tsx new file mode 100644 index 0000000..55747c4 --- /dev/null +++ b/src/pages/setting/MainContent.tsx @@ -0,0 +1,60 @@ +import type { FC } from "react"; +import { Tab } from "@headlessui/react"; +import { mTabList } from "@/configs/utils/setting"; +import Uni from "./uni"; +import Company from "./company"; +import { Tunivers } from "@/configs/types"; +import { useAsyncValue } from "react-router-dom"; +import { Tcompany } from "@/configs/schema/settingSchema"; + +const MainContent: FC = () => { + const [univers, company, logo] = useAsyncValue() as [ + Tunivers, + Tcompany, + string, + ]; + + return ( +
+ + + {mTabList.map((item, index) => { + return ( + { + return ` + w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-blue-700 ring-white ring-opacity-60 ring-offset-2 ring-offset-blue-400 focus:outline-none focus:ring-2 + ${ + selected + ? " bg-white shadow " + : " text-blue-100 hover:bg-white/[0.12] hover:text-white " + } + `; + }} + > + {item.name} + + ); + })} + + + + + + + + + {/* + Content 3 + */} + + +
+ ); +}; + +export default MainContent; diff --git a/src/pages/setting/setting.tsx b/src/pages/setting/setting.tsx index d575d8b..040edf3 100644 --- a/src/pages/setting/setting.tsx +++ b/src/pages/setting/setting.tsx @@ -1,23 +1,24 @@ -import { Suspense, useEffect } from "react"; import type { FC } from "react"; -import { Tab } from "@headlessui/react"; -import { mTabList } from "@/configs/utils/setting"; -import Uni from "./uni"; +import { Suspense, useEffect } from "react"; import LoadingPage from "@/components/loadingEle"; import { Await, useLoaderData, useActionData } from "react-router-dom"; import { Tunivers } from "@/configs/types"; -import Company from "./company"; import { Tcompany } from "@/configs/schema/settingSchema"; import { toastError, toastSuccess } from "@/lib/toaster"; import { useTranslation } from "react-i18next"; import { RES_STATUS } from "@/configs/types"; +import ErrorTips from "@/components/ErrorTips"; +import MainContent from "./MainContent"; const Setting: FC = () => { - const { univers, company, logo } = useLoaderData() as { + const { allPromise } = useLoaderData() as { + allPromise: Promise<[Tunivers, Tcompany, string]>; + }; + /* const { univers, company, logo } = useLoaderData() as { univers: Tunivers | null; company: Tcompany | null; logo: string; - }; + }; */ const { t } = useTranslation(); const actionData = useActionData() as Tresponse; @@ -42,57 +43,11 @@ const Setting: FC = () => { actionData.status = RES_STATUS.DEFAULT; }, [actionData, t]); - const UniversContent: FC<{ univers: Tunivers }> = ({ univers }) => { - return ( -
- - - {mTabList.map((item, index) => { - return ( - { - return ` - w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-blue-700 ring-white ring-opacity-60 ring-offset-2 ring-offset-blue-400 focus:outline-none focus:ring-2 - ${ - selected - ? " bg-white shadow " - : " text-blue-100 hover:bg-white/[0.12] hover:text-white " - } - `; - }} - > - {item.name} - - ); - })} - - - - - - - - - {/* - Content 3 - */} - - -
- ); - }; - return (
}> - - {(univers) => { - return ; - }} + }> +
diff --git a/src/pages/staff/MainContent.tsx b/src/pages/staff/MainContent.tsx new file mode 100644 index 0000000..a911022 --- /dev/null +++ b/src/pages/staff/MainContent.tsx @@ -0,0 +1,138 @@ +import type { FC, TouchEvent, MouseEvent } from "react"; +import { useMemo } from "react"; +import Card from "@/components/card"; +import { PTable } from "@/components/table"; +import staffColumns from "@/configs/columnDefs/defStaff"; +import { useTranslation } from "react-i18next"; +import SubTable from "./SubTable"; +import { useAsyncValue } from "react-router-dom"; +import { TwlTableRow } from "@/configs/schema/workSchema"; +import { TstaffWPayslip } from "@/configs/schema/staffSchema"; +import { Tbonus } from "@/configs/schema/payslipSchema"; +import { Tcompany } from "@/configs/schema/settingSchema"; +import { atCompany, atLogo, atModalOpen, atStaff } from "@/configs/atoms"; +import { useAtom } from "jotai"; +import { RESET } from "jotai/utils"; +import { + usePayslipStore, + useStaffStore, + useStaffWLStore, +} from "@/configs/zustore"; +import { dateFormat, hmsTohm } from "@/lib/time"; + +const MainContent: FC = () => { + const { t } = useTranslation(); + const [, setCompany] = useAtom(atCompany); + const [, setLogo] = useAtom(atLogo); + const [, setStaff] = useAtom(atStaff); + const [, setModalOpen] = useAtom(atModalOpen); + const setAllStaff = useStaffStore((state) => state.setAllStaff); + const setAllStaffWL = useStaffWLStore((state) => state.setAllStaffWL); + const setAllBonus = usePayslipStore((state) => state.setAllBonus); + + const [worklogs, allStaff, allBonus, company, logo] = useAsyncValue() as [ + TwlTableRow[], + TstaffWPayslip[], + Tbonus[], + Tcompany, + string, + ]; + + const newWorklogs = useMemo( + () => + worklogs + .sort((a: TwlTableRow, b: TwlTableRow) => { + const dateA = new Date(a.wl_date); + const dateB = new Date(b.wl_date); + + // Compare dates + if (dateA > dateB) return -1; // Return -1 to indicate dateA comes before dateB + if (dateA < dateB) return 1; // Return 1 to indicate dateA comes after dateB + return 0; + }) + .map((wl: TwlTableRow) => { + return { + ...wl, + // convert the date format stored in mysql: yyyy-mm-dd to au: dd-mm-yyyy + // this format is related to date searching in the table + wl_date: dateFormat(wl.wl_date, "au"), + s_time: hmsTohm(wl.s_time as string), + e_time: hmsTohm(wl.e_time as string), + b_time: hmsTohm(wl.b_time as string), + b_hour: hmsTohm(wl.b_hour as string), + }; + }), + [worklogs] + ); + + setAllStaffWL(newWorklogs); + setAllStaff(allStaff || []); + setAllBonus(allBonus || []); + setCompany(company); + setLogo(logo); + + const handleAddNew = (e: MouseEvent | TouchEvent) => { + e.preventDefault(); + setStaff(RESET); + setModalOpen("Add"); + }; + + return ( + <> +
+ {/* header area */} +
+
+
+ +
+
+ {/* table */} + {allStaff ? ( + + { + if ( + row.original.payslips && + row.original.payslips.length > 0 + ) { + return true; + } + return false; + }} + expandContent={SubTable} + cnSearch="my-3" + cnTable={`h-[65dvh]`} + cnHead="sticky z-10 bg-indigo-300" + cnTh="py-3" + /> + + ) : ( + + + {t("pageText.noClient")} + + + )} +
+ + ); +}; + +export default MainContent; diff --git a/src/pages/staff/SubTable.tsx b/src/pages/staff/SubTable.tsx index ff151ab..3e4f242 100644 --- a/src/pages/staff/SubTable.tsx +++ b/src/pages/staff/SubTable.tsx @@ -1,10 +1,15 @@ +import type { FC } from "react"; import { PTable } from "@/components/table"; import payslipColumns from "@/configs/columnDefs/defPayslip"; import { TstaffWPayslip } from "@/configs/schema/staffSchema"; import { usePayslipStore } from "@/configs/zustore"; import { useTranslation } from "react-i18next"; -const SubTable = ({ data }: { data: TstaffWPayslip }) => { +type Tprops = { + data: TstaffWPayslip; +}; + +const SubTable: FC = ({ data }) => { const { t } = useTranslation(); const setPayslip = usePayslipStore((state) => state.setPayslip); diff --git a/src/pages/staff/staff.tsx b/src/pages/staff/staff.tsx index 245e0a1..dc99af0 100644 --- a/src/pages/staff/staff.tsx +++ b/src/pages/staff/staff.tsx @@ -1,67 +1,40 @@ +import type { FC } from "react"; import { Suspense, useState, useEffect } from "react"; -import type { FC, TouchEvent, MouseEvent } from "react"; import { Await, useLoaderData, useActionData } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { useAtom } from "jotai"; import { RESET } from "jotai/utils"; import LoadingPage from "@/components/loadingEle"; -import staffColumns from "@/configs/columnDefs/defStaff.tsx"; -import Card from "@/components/card"; import { toastError, toastSuccess } from "@/lib/toaster"; import { TstaffWPayslip } from "@/configs/schema/staffSchema.ts"; import { MStaffDel, MStaffForm, MStaffResetPW } from "@/pageComponents/modals"; -import { PTable } from "@/components/table"; -import { - atStaff, - at2ndModalOpen, - atModalOpen, - atCompany, - atLogo, -} from "@/configs/atoms"; +import { atStaff, at2ndModalOpen, atModalOpen } from "@/configs/atoms"; import type { TisConflict } from "@/configs/types"; import { RES_STATUS } from "@/configs/types"; import MPayslip from "@/pageComponents/modals/mPayslip"; import { TwlTableRow } from "@/configs/schema/workSchema"; -import { useStaffWLStore } from "@/configs/zustore/staffWLStore"; import { Tcompany } from "@/configs/schema/settingSchema"; import { mOpenOps } from "@/configs/utils/modal"; -import SubTable from "./SubTable"; import MPayslipDel from "@/pageComponents/modals/mPayslipDel"; -import { usePayslipStore, useStaffStore } from "@/configs/zustore"; import { Tbonus } from "@/configs/schema/payslipSchema"; import MPSDisplay from "@/pageComponents/modals/mPSDisplay"; - -type Tprops = { - allStaff: TstaffWPayslip[] | null; -}; +import MainContent from "./MainContent"; +import ErrorTips from "@/components/ErrorTips"; const Staff: FC = () => { - const [, setInfoConflict] = useState(RES_STATUS.SUCCESS); - const [secModalOpen, setSecModalOpen] = useAtom(at2ndModalOpen); - const [staff, setStaff] = useAtom(atStaff); - const [, setModalOpen] = useAtom(atModalOpen); - const [, setCompany] = useAtom(atCompany); - const [, setLogo] = useAtom(atLogo); const { t } = useTranslation(); - const setAllStaff = useStaffStore((state) => state.setAllStaff); - const { allStaff, worklogs, allBonus, company, logo } = useLoaderData() as { - allStaff: TstaffWPayslip[] | null; - worklogs: TwlTableRow[]; - allBonus: Tbonus[]; - company: Tcompany; - logo: string; + const { allPromise } = useLoaderData() as { + allPromise: Promise< + [TwlTableRow[], TstaffWPayslip[], Tbonus[], Tcompany, string] + >; }; - const setAllStaffWL = useStaffWLStore((state) => state.setAllStaffWL); - const setAllBonus = usePayslipStore((state) => state.setAllBonus); - const actionData = useActionData() as Tresponse; - setAllStaffWL(worklogs); - setAllStaff(allStaff || []); - setAllBonus(allBonus || []); - setCompany(company); - setLogo(logo); - /* useEffect(() => { - }, [worklogs, setAllStaffWL, company, logo, setCompany, setLogo]); */ + const [modalOpen, setModalOpen] = useAtom(atModalOpen); + const [secModalOpen, setSecModalOpen] = useAtom(at2ndModalOpen); + const [, setInfoConflict] = useState(RES_STATUS.SUCCESS); + const [staff, setStaff] = useAtom(atStaff); + + const actionData = useActionData() as Tresponse; useEffect(() => { if (!actionData) return; @@ -103,91 +76,30 @@ const Staff: FC = () => { } // set status to default, in case the stale value interfere the next action actionData.status = RES_STATUS.DEFAULT; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [ actionData, staff.uid, + secModalOpen, + modalOpen, + /* setStaff, setInfoConflict, setModalOpen, t, - secModalOpen, - setSecModalOpen, + setSecModalOpen, + */ ]); - const handleAddNew = (e: MouseEvent | TouchEvent) => { - e.preventDefault(); - setStaff(RESET); - setModalOpen("Add"); - }; - - const MainContent: FC = ({ allStaff }) => { - return ( - <> -
- {/* header area */} -
-
-
- -
-
- {/* table */} - {allStaff ? ( - - { - if ( - row.original.payslips && - row.original.payslips.length > 0 - ) { - return true; - } - return false; - }} - expandContent={SubTable} - cnSearch="my-3" - cnTable={`h-[65dvh]`} - cnHead="sticky z-10 bg-indigo-300" - cnTh="py-3" - /> - - ) : ( - - - {t("pageText.noClient")} - - - )} -
- - ); - }; - return ( -
- }> - - {(staffList) => { - return ; - }} - - + <> +
+ }> + }> + + + +
{/* Modal for add new staff, and this modal can not be insert into Await*/} {/* otherwise, the animation would get lost*/} @@ -197,7 +109,7 @@ const Staff: FC = () => { -
+ ); }; diff --git a/src/pages/worklogs/MainContent.tsx b/src/pages/worklogs/MainContent.tsx new file mode 100644 index 0000000..1496c04 --- /dev/null +++ b/src/pages/worklogs/MainContent.tsx @@ -0,0 +1,88 @@ +import type { FC } from "react"; +import { useMemo } from "react"; +import Card from "@/components/card"; +import { PTable } from "@/components/table"; +import { atWorkLogTableRow } from "@/configs/atoms"; +import wlColumns from "@/configs/columnDefs/defWorkLogs"; +import { TwlTableRow } from "@/configs/schema/workSchema"; +import { dateFormat, hmsTohm } from "@/lib/time"; +import { useAtom } from "jotai"; +import { useTranslation } from "react-i18next"; +import { useAsyncValue } from "react-router-dom"; + +const MainContent: FC = () => { + const [t] = useTranslation(); + const [, setWorkLog] = useAtom(atWorkLogTableRow); + + const [worklogs] = useAsyncValue() as [TwlTableRow[]]; + + const newWorklogs = useMemo( + () => + worklogs + .sort((a: TwlTableRow, b: TwlTableRow) => { + const dateA = new Date(a.wl_date); + const dateB = new Date(b.wl_date); + + // Compare dates + if (dateA > dateB) return -1; // Return -1 to indicate dateA comes before dateB + if (dateA < dateB) return 1; // Return 1 to indicate dateA comes after dateB + return 0; + }) + .map((wl: TwlTableRow) => { + return { + ...wl, + // convert the date format stored in mysql: yyyy-mm-dd to au: dd-mm-yyyy + // this format is related to date searching in the table + wl_date: dateFormat(wl.wl_date), + s_time: hmsTohm(wl.s_time as string), + e_time: hmsTohm(wl.e_time as string), + b_time: hmsTohm(wl.b_time as string), + b_hour: hmsTohm(wl.b_hour as string), + }; + }), + + [worklogs] + ); + + return ( +
+ {/* header area */} + + {/* table */} + {newWorklogs ? ( + + { + if (row.original. > 0) { + return true; + } + return false; + }} */ + //expandContent={orderSubTable} + cnSearch="my-3" + cnTable={`h-[65dvh]`} + cnHead="sticky z-10 bg-indigo-300" + cnTh="py-3" + /> + + ) : ( + + + {t("label.noContent")} + + + )} +
+ ); +}; + +export default MainContent; diff --git a/src/pages/worklogs/WorkLogs.tsx b/src/pages/worklogs/WorkLogs.tsx index 15fb3bb..410f5d4 100644 --- a/src/pages/worklogs/WorkLogs.tsx +++ b/src/pages/worklogs/WorkLogs.tsx @@ -2,27 +2,26 @@ import type { FC } from "react"; import { Suspense, useEffect } from "react"; import { Await, useLoaderData, useActionData } from "react-router-dom"; import LoadingPage from "@/components/loadingEle"; -import Card from "@/components/card"; -//import { RES_STATUS } from "@/utils/types"; -import { PTable } from "@/components/table"; import { useTranslation } from "react-i18next"; import { TwlTableRow } from "@/configs/schema/workSchema"; -import wlColumns from "@/configs/columnDefs/defWorkLogs"; -import { atModalOpen, atWorkLogTableRow } from "@/configs/atoms"; +import { atModalOpen } from "@/configs/atoms"; import { useAtom } from "jotai"; import MJobEdit from "@/pageComponents/modals/mJobEdit/mJobEdit"; import { RES_STATUS } from "@/configs/types"; import { mOpenOps } from "@/configs/utils/modal"; import { toastError, toastSuccess } from "@/lib/toaster"; import MWorkLogDel from "@/pageComponents/modals/mWorkLogDel"; +import ErrorTips from "@/components/ErrorTips"; +import MainContent from "./MainContent"; const WorkLogs: FC = () => { const [t] = useTranslation(); - const { worklogs } = useLoaderData() as { - worklogs: TwlTableRow[]; + const { allPromise } = useLoaderData() as { + allPromise: Promise<[TwlTableRow[]]>; }; + const actionData = useActionData() as Tresponse; - const [, setWorkLog] = useAtom(atWorkLogTableRow); + const [modalOpen, setModalOpen] = useAtom(atModalOpen); useEffect(() => { @@ -45,7 +44,7 @@ const WorkLogs: FC = () => { setModalOpen(mOpenOps.default); toastSuccess(t("toastS.delWorkLog")); break; - case RES_STATUS.FAILED_DELETE_WORKLOG: + case RES_STATUS.FAILED_DEL_WORKLOG: setModalOpen(mOpenOps.default); toastError(t("toastF.delWorkLog")); break; @@ -53,61 +52,14 @@ const WorkLogs: FC = () => { break; } actionData.status = RES_STATUS.DEFAULT; - }, [actionData, modalOpen, setModalOpen, t]); - - const WorkLogsTableContent = ({ - workLogs, - }: { - workLogs: TwlTableRow[]; - }) => { - return ( -
- {/* header area */} - - {/* table */} - {workLogs ? ( - - { - if (row.original. > 0) { - return true; - } - return false; - }} */ - //expandContent={orderSubTable} - cnSearch="my-3" - cnTable={`h-[65dvh]`} - cnHead="sticky z-10 bg-indigo-300" - cnTh="py-3" - /> - - ) : ( - - - {t("label.noContent")} - - - )} -
- ); - }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [actionData, modalOpen]); return (
}> - - {(workLogs) => { - return ; - }} + }> + diff --git a/src/routerAccFns/actions/staffAction.ts b/src/routerAccFns/actions/staffAction.ts index 2414a6e..6db9b48 100644 --- a/src/routerAccFns/actions/staffAction.ts +++ b/src/routerAccFns/actions/staffAction.ts @@ -5,8 +5,11 @@ import type { Tstaff } from "@/configs/schema/staffSchema"; export const staffAction = async ({ request, }: ActionFunctionArgs): Promise => { - console.log("-> staff action request: ", request); const data = await request.formData(); + data.get("req") && + console.log("-> staff action req data: ", data.get("req")); + + /* add a new staff */ if ("POST" === request.method && data.get("req") === "addStaff") { const result = await API_STAFF.staffAdd({ first_name: data.get("first_name") as string, @@ -35,11 +38,9 @@ export const staffAction = async ({ }); return result; } else if ("POST" === request.method && data.get("req") === "newPayslip") { - console.log("-> action gen new payslip"); const bonus = JSON.parse(data.get("bonus") as string); const payslip = JSON.parse(data.get("payslip") as string); - const result = await API_PAYSLIP.psSingleInsert(bonus, payslip); - return result; + return await API_PAYSLIP.psSingleInsert(bonus, payslip); } else if ("DELETE" === request.method && data.get("req") === "delStaff") { return await API_STAFF.staffSingleDel(data.get("uid") as string); } else if ( @@ -48,7 +49,7 @@ export const staffAction = async ({ ) { return await API_PAYSLIP.psSingleDel(data.get("psid") as string); } else if ("PUT" === request.method && data.get("req") === "updateStaff") { - const result = await API_STAFF.staffSingleUpdate({ + return await API_STAFF.staffSingleUpdate({ uid: data.get("uid"), first_name: data.get("first_name") as string, last_name: data.get("last_name") as string, @@ -74,7 +75,6 @@ export const staffAction = async ({ bsb: data.get("bsb") as string, account: data.get("account") as string, } as Tstaff); - return result; } else if ("PUT" === request.method && data.get("req") === "resetPW") { return await API_STAFF.staffUpdatePW( data.get("uid") as string, diff --git a/src/routerAccFns/loaders/clientPageLoader.ts b/src/routerAccFns/loaders/clientPageLoader.ts index dc22540..6c30a5d 100644 --- a/src/routerAccFns/loaders/clientPageLoader.ts +++ b/src/routerAccFns/loaders/clientPageLoader.ts @@ -1,8 +1,4 @@ import { API_ADMIN, API_CLIENT, API_SETTING, API_ORDER } from "@/apis"; -import { Tclient } from "@/configs/schema/clientSchema"; -import { Torder } from "@/configs/schema/orderSchema"; -import { Tcompany } from "@/configs/schema/settingSchema"; -import { Tunivers } from "@/configs/types"; import { menuList } from "@/configs/utils/router"; import { routerStore } from "@/configs/zustore"; import { LoaderFunctionArgs, defer, redirect } from "react-router-dom"; @@ -15,52 +11,23 @@ import { LoaderFunctionArgs, defer, redirect } from "react-router-dom"; export const clientLoader = async ({ params }: LoaderFunctionArgs) => { routerStore.setState({ currentRouter: "client" }); try { - await API_ADMIN.accessCheck(menuList[1].id).then((res) => { - return !res.data && redirect("/login"); - }); + await API_ADMIN.accessCheck(menuList[1].id) + .then((res) => { + return !res.data && redirect("/login"); + }) + .catch((error) => { + console.log("-> Error: orders page admin check: ", error); + return redirect("/login"); + }); const cid = params.cid as string; - //const [clientInfo, orders, uniData, company, logo] = await Promise.all([ - /* const allPromise = Promise.all([ - API_CLIENT.clientInfo(cid), - API_ORDER.orderWClient(cid) - .then((res) => res.data as Torder[]) - .then((res) => - res.map((item) => { - return { - ...item, - // convert date format from yyyy-mm-dd to dd-mm-yyyy - created_date: dateFormat(item.created_date, "au"), - // desc sort order_services by ranking - // conver taxable from 0/1 to boolean - order_services: item.order_services - .sort((a, b) => a.ranking - b.ranking) - .map((desc) => { - return { - ...desc, - taxable: Boolean(desc.taxable), - }; - }), - }; - }) - ), + const allPromise = Promise.all([ + API_CLIENT.clientInfo(cid).then((res) => res.data), + API_ORDER.orderWClient(cid).then((res) => res.data), API_SETTING.uniAll().then((res) => res.data), API_SETTING.companyGet().then((res) => res.data), API_SETTING.logo().then((res) => res.data), - ]); */ - const allPromise = Promise.all([ - API_CLIENT.clientInfo(cid).then((res) => res.data) as Promise< - Tclient[] - >, - API_ORDER.orderWClient(cid).then((res) => res.data) as Promise< - Torder[] - >, - API_SETTING.uniAll().then((res) => res.data) as Promise, - API_SETTING.companyGet().then( - (res) => res.data - ) as Promise, - API_SETTING.logo().then((res) => res.data) as Promise, ]); return defer({ allPromise }); diff --git a/src/routerAccFns/loaders/dashboardLoader.ts b/src/routerAccFns/loaders/dashboardLoader.ts index a4b8388..d08b86d 100644 --- a/src/routerAccFns/loaders/dashboardLoader.ts +++ b/src/routerAccFns/loaders/dashboardLoader.ts @@ -10,8 +10,8 @@ export const dashboardLoader = async () => { return !res.data && redirect("/login"); }); - const allPromise: Promise<[Tresponse]> = Promise.all([ - API_WORKLOGS.wlGetToday(), + const allPromise = Promise.all([ + API_WORKLOGS.wlGetToday().then((res) => res.data), ]); return defer({ allPromise }); diff --git a/src/routerAccFns/loaders/ordersPageLoader.ts b/src/routerAccFns/loaders/ordersPageLoader.ts index 957cf42..7d6b6a3 100644 --- a/src/routerAccFns/loaders/ordersPageLoader.ts +++ b/src/routerAccFns/loaders/ordersPageLoader.ts @@ -1,9 +1,7 @@ import { API_ADMIN, API_SETTING, API_ORDER, API_STAFF } from "@/apis"; import { menuList } from "@/configs/utils/router"; -import { Torder } from "@/configs/schema/orderSchema"; import { routerStore } from "@/configs/zustore"; import { defer, redirect } from "react-router-dom"; -import { hmsTohm } from "@/lib/time"; export const ordersLoader = async () => { routerStore.setState({ currentRouter: "orders" }); @@ -17,64 +15,15 @@ export const ordersLoader = async () => { return redirect("/login"); }); - const [orders, uniData, company, staff, logo] = await Promise.all([ - API_ORDER.orderAll() - .then((res) => res.data as Torder[]) - .then((res) => - // desc sort order_services by ranking - // conver taxable from 0/1 to boolean - res.map((item) => { - return { - ...item, - order_services: item.order_services - .sort((a, b) => a.ranking - b.ranking) - .map((desc) => { - return { - ...desc, - taxable: Boolean(desc.taxable), - }; - }), - work_logs: item.work_logs.map((wl) => { - return { - ...wl, - assigned_work: wl.assigned_work.map( - (aw) => { - return { - ...aw, - s_time: hmsTohm( - aw.s_time as string - ), - e_time: hmsTohm( - aw.e_time as string - ), - b_hour: hmsTohm( - aw.b_hour as string - ), - }; - } - ), - }; - }), - }; - }) - ) - .catch((error) => { - console.log("-> error fetching orders: ", error); - return []; - }), + const allPromise = Promise.all([ + API_ORDER.orderAll().then((res) => res.data), + API_STAFF.staffAll().then((res) => res.data), API_SETTING.uniAll().then((res) => res.data), API_SETTING.companyGet().then((res) => res.data), - API_STAFF.staffAll().then((res) => res.data), API_SETTING.logo().then((res) => res.data), ]); - return defer({ - orders, - uniData, - company, - logo, - staff, - }); + return defer({ allPromise }); } catch (err) { console.log("-> order page loader error: ", err); return redirect("/login"); diff --git a/src/routerAccFns/loaders/settingPageLoader.ts b/src/routerAccFns/loaders/settingPageLoader.ts index 59b89ec..4610256 100644 --- a/src/routerAccFns/loaders/settingPageLoader.ts +++ b/src/routerAccFns/loaders/settingPageLoader.ts @@ -5,19 +5,25 @@ import { defer, redirect } from "react-router-dom"; export const settingLoader = async () => { routerStore.setState({ currentRouter: "setting" }); - await API_ADMIN.accessCheck(menuList[6].id) - .then((res) => { - return !res.data && redirect("/login"); - }) - .catch((error) => { - console.log("-> Error: setting page admin check: ", error); - return redirect("/login"); - }); + try { + await API_ADMIN.accessCheck(menuList[6].id) + .then((res) => { + return !res.data && redirect("/login"); + }) + .catch((error) => { + console.log("-> Error: setting page admin check: ", error); + return redirect("/login"); + }); - const [univers, company, logo] = await Promise.all([ - API_SETTING.uniAll().then((res) => res.data), - API_SETTING.companyGet().then((res) => res.data), - API_SETTING.logo().then((res) => res.data), - ]); - return defer({ univers, company, logo }); + const allPromise = Promise.all([ + API_SETTING.uniAll().then((res) => res.data), + API_SETTING.companyGet().then((res) => res.data), + API_SETTING.logo().then((res) => res.data), + ]); + + return defer({ allPromise }); + } catch (error) { + console.log("-> setting page loader error: ", error); + return redirect("/login"); + } }; diff --git a/src/routerAccFns/loaders/staffPageLoader.ts b/src/routerAccFns/loaders/staffPageLoader.ts index 6bdd915..4e4292c 100644 --- a/src/routerAccFns/loaders/staffPageLoader.ts +++ b/src/routerAccFns/loaders/staffPageLoader.ts @@ -5,10 +5,8 @@ import { API_STAFF, API_WORKLOGS, } from "@/apis"; -import { TwlTableRow } from "@/configs/schema/workSchema"; import { menuList } from "@/configs/utils/router"; import { routerStore } from "@/configs/zustore"; -import { dateFormat, hmsTohm } from "@/lib/time"; import { defer, redirect } from "react-router-dom"; export const staffLoader = async () => { @@ -22,49 +20,16 @@ export const staffLoader = async () => { console.log("-> Error: staff page admin check: ", error); return redirect("/login"); }); - const [worklogs, allStaff, allBonus, company, logo] = await Promise.all( - [ - API_WORKLOGS.wlAll() - .then((res) => res.data as TwlTableRow[]) - .then((res) => { - return ( - res - // sort worklogs by date in descending order - .sort((a: TwlTableRow, b: TwlTableRow) => { - const dateA = new Date(a.wl_date); - const dateB = new Date(b.wl_date); - // Compare dates - if (dateA > dateB) return -1; // Return -1 to indicate dateA comes before dateB - if (dateA < dateB) return 1; // Return 1 to indicate dateA comes after dateB - return 0; - }) - .map((wl: TwlTableRow) => { - return { - ...wl, - // convert the date format stored in mysql: yyyy-mm-dd to au: dd-mm-yyyy - // this format is related to date searching in the table - wl_date: dateFormat(wl.wl_date, "au"), - s_time: hmsTohm(wl.s_time as string), - e_time: hmsTohm(wl.e_time as string), - b_time: hmsTohm(wl.b_time as string), - b_hour: hmsTohm(wl.b_hour as string), - }; - }) - ); - }) - .catch((error) => { - console.error("Error fetching worklogs:", error); - return []; // Return an empty array in case of an error - }), - API_STAFF.staffAll().then((res) => res.data), - API_PAYSLIP.psBonusAll().then((res) => res.data), - API_SETTING.companyGet().then((res) => res.data), - API_SETTING.logo().then((res) => res.data), - ] - ); + const allPromise = Promise.all([ + API_WORKLOGS.wlAll().then((res) => res.data), + API_STAFF.staffAll().then((res) => res.data), + API_PAYSLIP.psBonusAll().then((res) => res.data), + API_SETTING.companyGet().then((res) => res.data), + API_SETTING.logo().then((res) => res.data), + ]); - return defer({ worklogs, allStaff, allBonus, company, logo }); + return defer({ allPromise }); } catch (error) { console.error("Error in staffLoader:", error); return redirect("/login"); diff --git a/src/routerAccFns/loaders/worklogsLoader.ts b/src/routerAccFns/loaders/worklogsLoader.ts index 184733d..86babed 100644 --- a/src/routerAccFns/loaders/worklogsLoader.ts +++ b/src/routerAccFns/loaders/worklogsLoader.ts @@ -3,51 +3,25 @@ import { menuList } from "@/configs/utils/router"; import { routerStore } from "@/configs/zustore"; import { defer, redirect } from "react-router-dom"; import { TwlTableRow } from "@/configs/schema/workSchema"; -import { dateFormat, hmsTohm } from "@/lib/time"; export const wlLoader = async () => { routerStore.setState({ currentRouter: "workLogs" }); try { - await API_ADMIN.accessCheck(menuList[3].id).then((res) => { - return !res.data && redirect("/login"); - }); - - const worklogs = await API_WORKLOGS.wlAll() - .then((res) => res.data as TwlTableRow[]) + await API_ADMIN.accessCheck(menuList[3].id) .then((res) => { - return ( - res - // sort worklogs by date in descending order - .sort((a: TwlTableRow, b: TwlTableRow) => { - const dateA = new Date(a.wl_date); - const dateB = new Date(b.wl_date); - - // Compare dates - if (dateA > dateB) return -1; // Return -1 to indicate dateA comes before dateB - if (dateA < dateB) return 1; // Return 1 to indicate dateA comes after dateB - return 0; - }) - .map((wl: TwlTableRow) => { - return { - ...wl, - // convert the date format stored in mysql: yyyy-mm-dd to au: dd-mm-yyyy - // this format is related to date searching in the table - wl_date: dateFormat(wl.wl_date), - s_time: hmsTohm(wl.s_time as string), - e_time: hmsTohm(wl.e_time as string), - b_time: hmsTohm(wl.b_time as string), - b_hour: hmsTohm(wl.b_hour as string), - }; - }) - ); + return !res.data && redirect("/login"); }) .catch((error) => { - console.error("Error fetching worklogs:", error); - return []; // Return an empty array in case of an error + console.log("-> Error: orders page admin check: ", error); + return redirect("/login"); }); + const allPromise = Promise.all([ + API_WORKLOGS.wlAll().then((res) => res.data as TwlTableRow[]), + ]); + return defer({ - worklogs, + allPromise, }); } catch (err) { console.log("-> worklogs page loader error: ", err);