| @@ -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": "更改密碼" | |||||
| } | |||||