@@ -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 ( | ||||
<> | <> | ||||
@@ -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 ( | ||||
<> | <> | ||||
@@ -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> | ||||
@@ -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> | ||||
@@ -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> | ||||
@@ -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}> | ||||
@@ -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} | ||||
@@ -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; | ||||
@@ -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> | ||||
@@ -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"> | ||||
@@ -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]); | ||||
@@ -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"); | ||||
@@ -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"> | ||||
@@ -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> | ||||
@@ -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"> | ||||
@@ -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; |
@@ -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) | ||||
@@ -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> | ||||
)} | )} | ||||
@@ -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} /> | ||||
@@ -0,0 +1,7 @@ | |||||
{ | |||||
"Change Password": "更改密碼", | |||||
"Please Fill in All Fields": "請填寫以下項目", | |||||
"Input Old Password": "舊密碼", | |||||
"Input New Password": "新密碼", | |||||
"Input New Password Again": "重複輸入新密碼" | |||||
} |
@@ -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": "權限" | |||||
} |
@@ -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": "員工" | |||||
} | } |
@@ -0,0 +1,7 @@ | |||||
{ | |||||
"Edit User": "編輯用戶", | |||||
"User Detail": "用戶資料", | |||||
"User Authority": "權限", | |||||
"username": "用戶名", | |||||
"password": "更改密碼" | |||||
} |