Browse Source

update

tags/Baseline_30082024_FRONTEND_UAT
MSI\derek 1 year ago
parent
commit
ea1cfb60be
23 changed files with 197 additions and 154 deletions
  1. +1
    -1
      src/app/(main)/analytics/ResourceOverconsumptionReport/page.tsx
  2. +1
    -1
      src/app/(main)/analytics/StaffMonthlyWorkHoursAnalysisReport/page.tsx
  3. +2
    -2
      src/app/(main)/settings/changepassword/page.tsx
  4. +1
    -1
      src/app/(main)/settings/group/create/page.tsx
  5. +2
    -2
      src/app/(main)/settings/group/page.tsx
  6. +2
    -2
      src/components/ChangePassword/ChangePasswordForm.tsx
  7. +19
    -20
      src/components/CreateGroup/AuthorityAllocation.tsx
  8. +1
    -1
      src/components/CreateGroup/CreateGroup.tsx
  9. +3
    -3
      src/components/CreateGroup/GroupInfo.tsx
  10. +13
    -16
      src/components/CreateGroup/UserAllocation.tsx
  11. +3
    -6
      src/components/CreateTeam/StaffAllocation.tsx
  12. +1
    -1
      src/components/EditUser/EditUser.tsx
  13. +12
    -16
      src/components/EditUserGroup/AuthorityAllocation.tsx
  14. +3
    -3
      src/components/EditUserGroup/GroupInfo.tsx
  15. +12
    -15
      src/components/EditUserGroup/UserAllocation.tsx
  16. +44
    -39
      src/components/GenerateMonthlyWorkHoursReport/GenerateMonthlyWorkHoursReport.tsx
  17. +2
    -2
      src/components/ResourceOverconsumptionReport/ResourceOverconsumptionReport.tsx
  18. +12
    -13
      src/components/SearchBox/SearchBox.tsx
  19. +13
    -9
      src/components/UserGroupSearch/UserGroupSearch.tsx
  20. +7
    -0
      src/i18n/zh/changePassword.json
  21. +27
    -0
      src/i18n/zh/group.json
  22. +9
    -1
      src/i18n/zh/report.json
  23. +7
    -0
      src/i18n/zh/user.json

+ 1
- 1
src/app/(main)/analytics/ResourceOverconsumptionReport/page.tsx View File

@@ -9,7 +9,7 @@ export const metadata: Metadata = {
}; };


const StaffMonthlyWorkHoursAnalysisReport: React.FC = async () => { const StaffMonthlyWorkHoursAnalysisReport: React.FC = async () => {
const { t } = await getServerI18n("User Group");
const { t } = await getServerI18n("report");


return ( return (
<> <>


+ 1
- 1
src/app/(main)/analytics/StaffMonthlyWorkHoursAnalysisReport/page.tsx View File

@@ -9,7 +9,7 @@ export const metadata: Metadata = {
}; };


const StaffMonthlyWorkHoursAnalysisReport: React.FC = async () => { const StaffMonthlyWorkHoursAnalysisReport: React.FC = async () => {
const { t } = await getServerI18n("User Group");
const { t } = await getServerI18n("report");


return ( return (
<> <>


+ 2
- 2
src/app/(main)/settings/changepassword/page.tsx View File

@@ -19,7 +19,7 @@ export const metadata: Metadata = {
}; };


const ChangePasswordPage: React.FC = async () => { const ChangePasswordPage: React.FC = async () => {
const { t } = await getServerI18n("User Group");
const { t } = await getServerI18n("changePassword");
// preloadTeamLeads(); // preloadTeamLeads();
// preloadStaff(); // preloadStaff();
return ( return (
@@ -34,7 +34,7 @@ const ChangePasswordPage: React.FC = async () => {
{t("Change Password")} {t("Change Password")}
</Typography> </Typography>
</Stack> </Stack>
<I18nProvider namespaces={["User Group", "common"]}>
<I18nProvider namespaces={["common", "changePassword"]}>
<Suspense fallback={<ChangePassword.Loading />}> <Suspense fallback={<ChangePassword.Loading />}>
<ChangePassword /> <ChangePassword />
</Suspense> </Suspense>


+ 1
- 1
src/app/(main)/settings/group/create/page.tsx View File

@@ -11,7 +11,7 @@ const CreateStaff: React.FC = async () => {


return ( return (
<> <>
<Typography variant="h4">{t("Create Group")}</Typography>
<Typography variant="h4">{t("Create User Group")}</Typography>
<I18nProvider namespaces={["group"]}> <I18nProvider namespaces={["group"]}>
<CreateGroup /> <CreateGroup />
</I18nProvider> </I18nProvider>


+ 2
- 2
src/app/(main)/settings/group/page.tsx View File

@@ -20,7 +20,7 @@ export const metadata: Metadata = {




const UserGroup: React.FC = async () => { const UserGroup: React.FC = async () => {
const { t } = await getServerI18n("User Group");
const { t } = await getServerI18n("group");
// preloadTeamLeads(); // preloadTeamLeads();
// preloadStaff(); // preloadStaff();
return ( return (
@@ -43,7 +43,7 @@ export const metadata: Metadata = {
{t("Create User Group")} {t("Create User Group")}
</Button> </Button>
</Stack> </Stack>
<I18nProvider namespaces={["User Group", "common"]}>
<I18nProvider namespaces={["group", "common"]}>
<Suspense fallback={<UserGroupSearch.Loading />}> <Suspense fallback={<UserGroupSearch.Loading />}>
<UserGroupSearch /> <UserGroupSearch />
</Suspense> </Suspense>


+ 2
- 2
src/components/ChangePassword/ChangePasswordForm.tsx View File

@@ -14,7 +14,7 @@ import { Visibility, VisibilityOff } from "@mui/icons-material";
import { IconButton, InputAdornment } from "@mui/material"; import { IconButton, InputAdornment } from "@mui/material";


const ChagnePasswordForm: React.FC = () => { const ChagnePasswordForm: React.FC = () => {
const { t } = useTranslation();
const { t } = useTranslation("changePassword");
const [showNewPassword, setShowNewPassword] = useState(false); const [showNewPassword, setShowNewPassword] = useState(false);
const handleClickShowNewPassword = () => setShowNewPassword(!showNewPassword); const handleClickShowNewPassword = () => setShowNewPassword(!showNewPassword);
@@ -38,7 +38,7 @@ const ChagnePasswordForm: React.FC = () => {
<CardContent component={Stack} spacing={4}> <CardContent component={Stack} spacing={4}>
<Box> <Box>
<Typography variant="overline" display="block" marginBlockEnd={1}> <Typography variant="overline" display="block" marginBlockEnd={1}>
{t("Please Fill in all the Fields")}
{t("Please Fill in All Fields")}
</Typography> </Typography>
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
<Grid item xs={6}> <Grid item xs={6}>


+ 19
- 20
src/components/CreateGroup/AuthorityAllocation.tsx View File

@@ -84,8 +84,8 @@ const AuthorityAllocation: React.FC<Props> = ({ auth }) => {
onClick: addAuth, onClick: addAuth,
buttonIcon: <Add />, buttonIcon: <Add />,
}, },
{ label: t("authority"), name: "authority" },
{ label: t("Auth Name"), name: "name" },
{ label: t("Authority"), name: "authority" },
{ label: t("Description"), name: "name" },
// { label: t("Current Position"), name: "currentPosition" }, // { label: t("Current Position"), name: "currentPosition" },
], ],
[addAuth, t] [addAuth, t]
@@ -97,10 +97,10 @@ const AuthorityAllocation: React.FC<Props> = ({ auth }) => {
label: t("Remove"), label: t("Remove"),
name: "id", name: "id",
onClick: removeAuth, onClick: removeAuth,
buttonIcon: <Remove color="warning"/>,
buttonIcon: <Remove color="warning" />,
}, },
{ label: t("authority"), name: "authority" },
{ label: t("Auth Name"), name: "name" },
{ label: t("Authority"), name: "authority" },
{ label: t("Description"), name: "name" },
], ],
[removeAuth, selectedAuths, t] [removeAuth, selectedAuths, t]
); );
@@ -115,16 +115,13 @@ const AuthorityAllocation: React.FC<Props> = ({ auth }) => {
}, []); }, []);


React.useEffect(() => { React.useEffect(() => {
// setFilteredStaff(
// initialStaffs.filter((s) => {
// const q = query.toLowerCase();
// // s.staffId.toLowerCase().includes(q)
// // const q = query.toLowerCase();
// // return s.name.toLowerCase().includes(q);
// // s.code.toString().includes(q) ||
// // (s.brNo != null && s.brNo.toLowerCase().includes(q))
// })
// );
setFilteredAuths(
initialAuths.filter(
(a) =>
a.authority.toLowerCase().includes(query.toLowerCase()) ||
a.name?.toLowerCase().includes(query.toLowerCase())
)
);
}, [auth, query]); }, [auth, query]);


const resetAuth = React.useCallback(() => { const resetAuth = React.useCallback(() => {
@@ -152,7 +149,7 @@ const AuthorityAllocation: React.FC<Props> = ({ auth }) => {
> >
<Stack gap={2}> <Stack gap={2}>
<Typography variant="overline" display="block"> <Typography variant="overline" display="block">
{t("Authority")}
{/* {t("Authority")} */}
</Typography> </Typography>
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
<Grid item xs={6} display="flex" alignItems="center"> <Grid item xs={6} display="flex" alignItems="center">
@@ -162,7 +159,7 @@ const AuthorityAllocation: React.FC<Props> = ({ auth }) => {
fullWidth fullWidth
onChange={onQueryInputChange} onChange={onQueryInputChange}
value={query} value={query}
placeholder={t("Search by staff ID, name or position.")}
placeholder={t("Search by ") + t("Authority") + " / " + t("Description")}
InputProps={{ InputProps={{
endAdornment: query && ( endAdornment: query && (
<InputAdornment position="end"> <InputAdornment position="end">
@@ -178,18 +175,20 @@ const AuthorityAllocation: React.FC<Props> = ({ auth }) => {
<Tabs value={tabIndex} onChange={handleTabChange}> <Tabs value={tabIndex} onChange={handleTabChange}>
<Tab label={t("Authority Pool")} /> <Tab label={t("Authority Pool")} />
<Tab <Tab
label={`${t("Allocated Authority")} (${selectedAuths.length})`}
label={`${t("Allocated Authority")} (${
selectedAuths.length
})`}
/> />
</Tabs> </Tabs>
<Box sx={{ marginInline: -3 }}> <Box sx={{ marginInline: -3 }}>
{tabIndex === 0 && (
{tabIndex === 0 && (
<SearchResults <SearchResults
noWrapper noWrapper
items={differenceBy(filteredAuths, selectedAuths, "id")} items={differenceBy(filteredAuths, selectedAuths, "id")}
columns={AuthPoolColumns} columns={AuthPoolColumns}
/> />
)} )}
{tabIndex === 1 && (
{tabIndex === 1 && (
<SearchResults <SearchResults
noWrapper noWrapper
items={selectedAuths} items={selectedAuths}


+ 1
- 1
src/components/CreateGroup/CreateGroup.tsx View File

@@ -22,7 +22,7 @@ const CreateGroup: React.FC<Props> = ({ auth, users }) => {
const [serverError, setServerError] = useState(""); const [serverError, setServerError] = useState("");
const router = useRouter(); const router = useRouter();
const [tabIndex, setTabIndex] = useState(0); const [tabIndex, setTabIndex] = useState(0);
const { t } = useTranslation();
const { t } = useTranslation("group");


const errors = formProps.formState.errors; const errors = formProps.formState.errors;




+ 3
- 3
src/components/CreateGroup/GroupInfo.tsx View File

@@ -51,13 +51,13 @@ const GroupInfo: React.FC = () => {
Boolean(errors.name) && Boolean(errors.name) &&
(errors.name?.message (errors.name?.message
? t(errors.name.message) ? t(errors.name.message)
: t("Please input correct name"))
: t("Please input correct ") + t("Group Name"))
} }
/> />
</Grid> </Grid>
<Grid item xs={12}> <Grid item xs={12}>
<TextField <TextField
label={t("Group Description")}
label={t("Description")}
fullWidth fullWidth
multiline multiline
rows={4} rows={4}
@@ -67,7 +67,7 @@ const GroupInfo: React.FC = () => {
Boolean(errors.description) && Boolean(errors.description) &&
(errors.description?.message (errors.description?.message
? t(errors.description.message) ? t(errors.description.message)
: t("Please input correct description"))
: t("Please input correct ") + t("Description"))
} }
/> />
</Grid> </Grid>


+ 13
- 16
src/components/CreateGroup/UserAllocation.tsx View File

@@ -85,8 +85,8 @@ const UserAllocation: React.FC<Props> = ({ users }) => {
onClick: addUser, onClick: addUser,
buttonIcon: <Add />, buttonIcon: <Add />,
}, },
{ label: t("User Name"), name: "username" },
{ label: t("name"), name: "name" },
{ label: t("Username"), name: "username" },
{ label: t("Staff Name"), name: "name" },
], ],
[addUser, t] [addUser, t]
); );
@@ -99,8 +99,8 @@ const UserAllocation: React.FC<Props> = ({ users }) => {
onClick: removeUser, onClick: removeUser,
buttonIcon: <Remove color="warning" />, buttonIcon: <Remove color="warning" />,
}, },
{ label: t("User Name"), name: "username" },
{ label: t("name"), name: "name" },
{ label: t("Username"), name: "username" },
{ label: t("Staff Name"), name: "name" },
], ],
[removeUser, selectedUsers, t] [removeUser, selectedUsers, t]
); );
@@ -116,16 +116,13 @@ const UserAllocation: React.FC<Props> = ({ users }) => {
}, []); }, []);


React.useEffect(() => { React.useEffect(() => {
// setFilteredStaff(
// initialStaffs.filter((s) => {
// const q = query.toLowerCase();
// // s.staffId.toLowerCase().includes(q)
// // const q = query.toLowerCase();
// // return s.name.toLowerCase().includes(q);
// // s.code.toString().includes(q) ||
// // (s.brNo != null && s.brNo.toLowerCase().includes(q))
// })
// );
const q = query.toLowerCase();
setFilteredUsers(
initialUsers.filter((u) => (
u.username.toLowerCase().includes(q) ||
u.name.toLowerCase().includes(q)
))
);
}, [users, query]); }, [users, query]);


const resetUser = React.useCallback(() => { const resetUser = React.useCallback(() => {
@@ -153,7 +150,7 @@ const UserAllocation: React.FC<Props> = ({ users }) => {
> >
<Stack gap={2}> <Stack gap={2}>
<Typography variant="overline" display="block"> <Typography variant="overline" display="block">
{t("User")}
{/* {t("User")} */}
</Typography> </Typography>
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
<Grid item xs={6} display="flex" alignItems="center"> <Grid item xs={6} display="flex" alignItems="center">
@@ -163,7 +160,7 @@ const UserAllocation: React.FC<Props> = ({ users }) => {
fullWidth fullWidth
onChange={onQueryInputChange} onChange={onQueryInputChange}
value={query} value={query}
placeholder={t("Search by staff ID, name or position.")}
placeholder={t("Search by ") + t("Username") + " / " + t("Staff Name")}
InputProps={{ InputProps={{
endAdornment: query && ( endAdornment: query && (
<InputAdornment position="end"> <InputAdornment position="end">


+ 3
- 6
src/components/CreateTeam/StaffAllocation.tsx View File

@@ -48,7 +48,6 @@ const StaffAllocation: React.FC<Props> = ({ allStaffs: staff }) => {
} = useFormContext<CreateTeamInputs>(); } = useFormContext<CreateTeamInputs>();


const initialStaffs = staff.map((s) => ({ ...s })); const initialStaffs = staff.map((s) => ({ ...s }));
// console.log(initialStaffs)
const [filteredStaff, setFilteredStaff] = useState(initialStaffs); const [filteredStaff, setFilteredStaff] = useState(initialStaffs);
const [selectedStaff, setSelectedStaff] = useState<typeof filteredStaff>( const [selectedStaff, setSelectedStaff] = useState<typeof filteredStaff>(
initialStaffs.filter((s) => getValues("addStaffIds")?.includes(s.id)) initialStaffs.filter((s) => getValues("addStaffIds")?.includes(s.id))
@@ -158,15 +157,13 @@ const StaffAllocation: React.FC<Props> = ({ allStaffs: staff }) => {
}, []); }, []);


React.useEffect(() => { React.useEffect(() => {
const q = query.toLowerCase();
setFilteredStaff( setFilteredStaff(
initialStaffs.filter((i) => {
const q = query.toLowerCase();
return (
initialStaffs.filter((i) => (
i.staffId.toLowerCase().includes(q) || i.staffId.toLowerCase().includes(q) ||
i.name.toLowerCase().includes(q) || i.name.toLowerCase().includes(q) ||
i.currentPosition.toLowerCase().includes(q) i.currentPosition.toLowerCase().includes(q)
);
})
))
); );
}, [staff, query]); }, [staff, query]);




+ 1
- 1
src/components/EditUser/EditUser.tsx View File

@@ -50,7 +50,7 @@ interface Props {
} }


const EditUser: React.FC<Props> = async ({ user, rules, auths }) => { const EditUser: React.FC<Props> = async ({ user, rules, auths }) => {
const { t } = useTranslation();
const { t } = useTranslation("user");
const formProps = useForm<UserInputs>(); const formProps = useForm<UserInputs>();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const id = parseInt(searchParams.get("id") || "0"); const id = parseInt(searchParams.get("id") || "0");


+ 12
- 16
src/components/EditUserGroup/AuthorityAllocation.tsx View File

@@ -41,7 +41,6 @@ const AuthorityAllocation: React.FC<Props> = ({ auth }) => {
reset, reset,
resetField, resetField,
} = useFormContext<CreateGroupInputs>(); } = useFormContext<CreateGroupInputs>();
console.log(auth)
const initialAuths = auth.map((a) => ({ ...a })).sort((a, b) => a.id - b.id); const initialAuths = auth.map((a) => ({ ...a })).sort((a, b) => a.id - b.id);
const [filteredAuths, setFilteredAuths] = useState(initialAuths); const [filteredAuths, setFilteredAuths] = useState(initialAuths);
const [selectedAuths, setSelectedAuths] = useState<typeof filteredAuths>( const [selectedAuths, setSelectedAuths] = useState<typeof filteredAuths>(
@@ -86,8 +85,8 @@ const AuthorityAllocation: React.FC<Props> = ({ auth }) => {
onClick: addAuth, onClick: addAuth,
buttonIcon: <Add />, buttonIcon: <Add />,
}, },
{ label: t("authority"), name: "authority" },
{ label: t("Auth Name"), name: "name" },
{ label: t("Authority"), name: "authority" },
{ label: t("Description"), name: "name" },
// { label: t("Current Position"), name: "currentPosition" }, // { label: t("Current Position"), name: "currentPosition" },
], ],
[addAuth, t] [addAuth, t]
@@ -101,8 +100,8 @@ const AuthorityAllocation: React.FC<Props> = ({ auth }) => {
onClick: removeAuth, onClick: removeAuth,
buttonIcon: <Remove color="warning"/>, buttonIcon: <Remove color="warning"/>,
}, },
{ label: t("authority"), name: "authority" },
{ label: t("Auth Name"), name: "name" },
{ label: t("Authority"), name: "authority" },
{ label: t("Description"), name: "name" },
], ],
[removeAuth, selectedAuths, t] [removeAuth, selectedAuths, t]
); );
@@ -117,16 +116,13 @@ const AuthorityAllocation: React.FC<Props> = ({ auth }) => {
}, []); }, []);


React.useEffect(() => { React.useEffect(() => {
// setFilteredStaff(
// initialStaffs.filter((s) => {
// const q = query.toLowerCase();
// // s.staffId.toLowerCase().includes(q)
// // const q = query.toLowerCase();
// // return s.name.toLowerCase().includes(q);
// // s.code.toString().includes(q) ||
// // (s.brNo != null && s.brNo.toLowerCase().includes(q))
// })
// );
const q = query.toLowerCase();
setFilteredAuths(
initialAuths.filter((a) => (
a.authority.toLowerCase().includes(q) ||
a.name.toLowerCase().includes(q)
))
);
}, [auth, query]); }, [auth, query]);


const resetAuth = React.useCallback(() => { const resetAuth = React.useCallback(() => {
@@ -164,7 +160,7 @@ const AuthorityAllocation: React.FC<Props> = ({ auth }) => {
fullWidth fullWidth
onChange={onQueryInputChange} onChange={onQueryInputChange}
value={query} value={query}
placeholder={t("Search by staff ID, name or position.")}
placeholder={t("Search by ")+ t("Authority") + " / " + t("Description")}
InputProps={{ InputProps={{
endAdornment: query && ( endAdornment: query && (
<InputAdornment position="end"> <InputAdornment position="end">


+ 3
- 3
src/components/EditUserGroup/GroupInfo.tsx View File

@@ -51,13 +51,13 @@ const GroupInfo: React.FC = () => {
Boolean(errors.name) && Boolean(errors.name) &&
(errors.name?.message (errors.name?.message
? t(errors.name.message) ? t(errors.name.message)
: t("Please input correct name"))
: t("Please input correct ") + t("name"))
} }
/> />
</Grid> </Grid>
<Grid item xs={12}> <Grid item xs={12}>
<TextField <TextField
label={t("Group Description")}
label={t("Description")}
fullWidth fullWidth
multiline multiline
rows={4} rows={4}
@@ -67,7 +67,7 @@ const GroupInfo: React.FC = () => {
Boolean(errors.description) && Boolean(errors.description) &&
(errors.description?.message (errors.description?.message
? t(errors.description.message) ? t(errors.description.message)
: t("Please input correct description"))
: t("Please input correct ") + t("Description"))
} }
/> />
</Grid> </Grid>


+ 12
- 15
src/components/EditUserGroup/UserAllocation.tsx View File

@@ -92,8 +92,8 @@ const UserAllocation: React.FC<Props> = ({ users }) => {
onClick: addUser, onClick: addUser,
buttonIcon: <Add />, buttonIcon: <Add />,
}, },
{ label: t("User Name"), name: "username" },
{ label: t("name"), name: "name" },
{ label: t("Username"), name: "username" },
{ label: t("Staff Name"), name: "name" },
], ],
[addUser, t] [addUser, t]
); );
@@ -106,8 +106,8 @@ const UserAllocation: React.FC<Props> = ({ users }) => {
onClick: removeUser, onClick: removeUser,
buttonIcon: <Remove color="warning" />, buttonIcon: <Remove color="warning" />,
}, },
{ label: t("User Name"), name: "username" },
{ label: t("name"), name: "name" },
{ label: t("Username"), name: "username" },
{ label: t("Staff Name"), name: "name" },
], ],
[removeUser, selectedUsers, t] [removeUser, selectedUsers, t]
); );
@@ -123,16 +123,13 @@ const UserAllocation: React.FC<Props> = ({ users }) => {
}, []); }, []);


React.useEffect(() => { React.useEffect(() => {
// setFilteredStaff(
// initialStaffs.filter((s) => {
// const q = query.toLowerCase();
// // s.staffId.toLowerCase().includes(q)
// // const q = query.toLowerCase();
// // return s.name.toLowerCase().includes(q);
// // s.code.toString().includes(q) ||
// // (s.brNo != null && s.brNo.toLowerCase().includes(q))
// })
// );
const q = query.toLowerCase();
setFilteredUsers(
initialUsers.filter((item) => (
item.username.toLowerCase().includes(q) ||
item.name.toLowerCase().includes(q)
))
);
}, [users, query]); }, [users, query]);


const resetUser = React.useCallback(() => { const resetUser = React.useCallback(() => {
@@ -170,7 +167,7 @@ const UserAllocation: React.FC<Props> = ({ users }) => {
fullWidth fullWidth
onChange={onQueryInputChange} onChange={onQueryInputChange}
value={query} value={query}
placeholder={t("Search by staff ID, name or position.")}
placeholder={t("Search by ") + t("Username") + " / " + t("Staff Name")}
InputProps={{ InputProps={{
endAdornment: query && ( endAdornment: query && (
<InputAdornment position="end"> <InputAdornment position="end">


+ 44
- 39
src/components/GenerateMonthlyWorkHoursReport/GenerateMonthlyWorkHoursReport.tsx View File

@@ -3,61 +3,66 @@ import React, { useMemo } from "react";
import SearchBox, { Criterion } from "../SearchBox"; import SearchBox, { Criterion } from "../SearchBox";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { ProjectResult } from "@/app/api/projects"; import { ProjectResult } from "@/app/api/projects";
import { fetchMonthlyWorkHoursReport, fetchProjectCashFlowReport } from "@/app/api/reports/actions";
import {
fetchMonthlyWorkHoursReport,
fetchProjectCashFlowReport,
} from "@/app/api/reports/actions";
import { downloadFile } from "@/app/utils/commonUtil"; import { downloadFile } from "@/app/utils/commonUtil";
import { BASE_API_URL } from "@/config/api"; import { BASE_API_URL } from "@/config/api";
import { MonthlyWorkHoursReportFilter } from "@/app/api/reports"; import { MonthlyWorkHoursReportFilter } from "@/app/api/reports";
import { records } from "@/app/api/staff/actions"; import { records } from "@/app/api/staff/actions";
import { StaffResult } from "@/app/api/staff"; import { StaffResult } from "@/app/api/staff";
import dayjs from "dayjs";


interface Props { interface Props {
staffs: StaffResult[]
staffs: StaffResult[];
} }


type SearchQuery = Partial<Omit<MonthlyWorkHoursReportFilter, "id">>; type SearchQuery = Partial<Omit<MonthlyWorkHoursReportFilter, "id">>;
type SearchParamNames = keyof SearchQuery; type SearchParamNames = keyof SearchQuery;


const GenerateMonthlyWorkHoursReport: React.FC<Props> = ({ staffs }) => { const GenerateMonthlyWorkHoursReport: React.FC<Props> = ({ staffs }) => {
const { t } = useTranslation();
const staffCombo = staffs.map(staff => `${staff.name} - ${staff.staffId}`)
console.log(staffs)
const { t } = useTranslation("report");
const staffCombo = staffs.map((staff) => `${staff.name} - ${staff.staffId}`);


const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [
{
label: t("Staff"),
paramName: "staff",
type: "select",
options: staffCombo,
needAll: false
},
{
label: t("date"),
paramName: "date",
type: "monthYear",
},
],
[t],
);
const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [
{
label: t("Staff"),
paramName: "staff",
type: "select",
options: staffCombo,
needAll: false,
},
{
label: t("Date"),
paramName: "date",
type: "monthYear",
},
],
[t]
);


return (
return (
<> <>
<SearchBox
<SearchBox
criteria={searchCriteria} criteria={searchCriteria}
onSearch={async (query: any) => { onSearch={async (query: any) => {
const index = staffCombo.findIndex(staff => staff === query.staff)
if (query.staff.length > 0 && query.staff.toLocaleLowerCase() !== "all" && query.date.length > 0) {
const index = staffCombo.findIndex(staff => staff === query.staff)
const response = await fetchMonthlyWorkHoursReport({ id: staffs[index].id, yearMonth: query.date })
if (response) {
downloadFile(new Uint8Array(response.blobValue), response.filename!!)
}
}
}
}
/>
</>
)
}
const index = staffCombo.findIndex((staff) => staff === query.staff);
const response = await fetchMonthlyWorkHoursReport({
id: staffs[index].id,
yearMonth: query.date,
});
if (response) {
downloadFile(
new Uint8Array(response.blobValue),
response.filename!!
);
}
}}
/>
</>
);
};


export default GenerateMonthlyWorkHoursReport
export default GenerateMonthlyWorkHoursReport;

+ 2
- 2
src/components/ResourceOverconsumptionReport/ResourceOverconsumptionReport.tsx View File

@@ -20,10 +20,10 @@ type SearchQuery = Partial<Omit<ProjectResourceOverconsumptionReportFilter, "id"
type SearchParamNames = keyof SearchQuery; type SearchParamNames = keyof SearchQuery;


const ResourceOverconsumptionReport: React.FC<Props> = ({ team, customer }) => { const ResourceOverconsumptionReport: React.FC<Props> = ({ team, customer }) => {
const { t } = useTranslation();
const { t } = useTranslation("report");
const teamCombo = team.map(t => `${t.name} - ${t.code}`) const teamCombo = team.map(t => `${t.name} - ${t.code}`)
const custCombo = customer.map(c => `${c.name} - ${c.code}`) const custCombo = customer.map(c => `${c.name} - ${c.code}`)
const statusCombo = ["Within Budget, Overconsumption", "Potential Overconsumption"]
const statusCombo = ["Overconsumption", "Potential Overconsumption"]
// const staffCombo = staffs.map(staff => `${staff.name} - ${staff.staffId}`) // const staffCombo = staffs.map(staff => `${staff.name} - ${staff.staffId}`)
// console.log(staffs) // console.log(staffs)




+ 12
- 13
src/components/SearchBox/SearchBox.tsx View File

@@ -226,19 +226,18 @@ function SearchBox<T extends string>({
adapterLocale="zh-hk" adapterLocale="zh-hk"
> >
<Box display="flex"> <Box display="flex">
<InputLabel>
{c.label}
</InputLabel>
<DateCalendar
views={["month", "year"]}
openTo="month"
onChange={makeMonthYearChangeHandler(c.paramName)}
value={
inputs[c.paramName]
? dayjs(inputs[c.paramName])
: null
}
/>
<FormControl fullWidth>
<DatePicker
label={c.label}
onChange={makeMonthYearChangeHandler(c.paramName)}
value={
inputs[c.paramName]
? dayjs(inputs[c.paramName])
: dayjs()
}
views={["month","year"]}
/>
</FormControl>
</Box> </Box>
</LocalizationProvider> </LocalizationProvider>
)} )}


+ 13
- 9
src/components/UserGroupSearch/UserGroupSearch.tsx View File

@@ -26,10 +26,15 @@ const UserGroupSearch: React.FC<Props> = ({ users }) => {
const searchCriteria: Criterion<SearchParamNames>[] = useMemo( const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [ () => [
{ {
label: t("User Name"),
label: t("Group Name"),
paramName: "name", paramName: "name",
type: "text", type: "text",
}, },
{
label: t("Description"),
paramName: "description",
type: "text",
},
], ],
[t] [t]
); );
@@ -75,14 +80,13 @@ const UserGroupSearch: React.FC<Props> = ({ users }) => {
<SearchBox <SearchBox
criteria={searchCriteria} criteria={searchCriteria}
onSearch={(query) => { onSearch={(query) => {
// setFilteredUser(
// users.filter(
// (t) =>
// t.name.toLowerCase().includes(query.name.toLowerCase()) &&
// t.code.toLowerCase().includes(query.code.toLowerCase()) &&
// t.description.toLowerCase().includes(query.description.toLowerCase())
// )
// )
setFilteredUser(
users.filter(
(u) =>
u.name.toLowerCase().includes(query.name.toLowerCase()) &&
u.description.toLowerCase().includes(query.description.toLowerCase())
)
)
}} }}
/> />
<SearchResults<UserGroupResult> items={filteredUser} columns={columns} /> <SearchResults<UserGroupResult> items={filteredUser} columns={columns} />


+ 7
- 0
src/i18n/zh/changePassword.json View File

@@ -0,0 +1,7 @@
{
"Change Password": "更改密碼",
"Please Fill in All Fields": "請填寫以下項目",
"Input Old Password": "舊密碼",
"Input New Password": "新密碼",
"Input New Password Again": "重複輸入新密碼"
}

+ 27
- 0
src/i18n/zh/group.json View File

@@ -0,0 +1,27 @@
{
"User Group": "用戶群組",
"Create User Group": "建立用戶群組",
"Edit User Group": "編輯用戶群組",
"Edit": "編輯",
"Group Name": "名稱",
"Description": "描述",
"Group Info": "群組資料",
"Add": "新增",
"User": "用戶",
"Remove": "移除",
"Confirm": "確定",
"Cancel": "取消",
"Delete": "刪除",
"Search by ": "搜尋",
"Delete Success": "刪除成功",
"Please input correct ": "請輸入正確",
"Authority Allocation": "權限分配",
"Authority Pool": "權限池",
"Allocated Authority": "已分配權限",
"User Allocation": "用戶分配",
"User Pool": "用戶池",
"Allocated Users": "已分配用戶",
"Username": "用戶名",
"Staff Name": "員工名字",
"Authority": "權限"
}

+ 9
- 1
src/i18n/zh/report.json View File

@@ -1,4 +1,12 @@
{ {
"Staff Monthly Work Hours Analysis Report": "Staff Monthly Work Hours Analysis Report",
"Project Resource Overconsumption Report": "Project Resource Overconsumption Report",

"Project": "項目", "Project": "項目",
"Date Type": "日期類型"
"Date Type": "日期類型",
"Date": "日期",
"Team": "隊伍",
"Client": "客戶",
"Status": "狀態",
"Staff": "員工"
} }

+ 7
- 0
src/i18n/zh/user.json View File

@@ -0,0 +1,7 @@
{
"Edit User": "編輯用戶",
"User Detail": "用戶資料",
"User Authority": "權限",
"username": "用戶名",
"password": "更改密碼"
}

Loading…
Cancel
Save