From 133bedcc4070009e7850b988fc34b9c385d5e749 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Thu, 16 May 2024 18:43:26 +0800 Subject: [PATCH] update --- src/app/api/user/actions.ts | 9 ++ src/app/api/user/index.ts | 15 +++ src/components/EditUser/EditUser.tsx | 51 +++++++++-- src/components/EditUser/EditUserWrapper.tsx | 7 +- src/components/EditUser/UserDetail.tsx | 8 +- .../GenerateMonthlyWorkHoursReport.tsx | 22 +++-- src/components/SearchBox/SearchBox.tsx | 91 ++++++++++++++----- 7 files changed, 153 insertions(+), 50 deletions(-) diff --git a/src/app/api/user/actions.ts b/src/app/api/user/actions.ts index b6329a8..b655631 100644 --- a/src/app/api/user/actions.ts +++ b/src/app/api/user/actions.ts @@ -11,6 +11,7 @@ export interface UserInputs { email?: string; addAuthIds?: number[]; removeAuthIds?: number[]; + password?: string; } export interface PasswordInputs { @@ -50,4 +51,12 @@ export const changePassword = async (data: any) => { body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, }); + }; + +export const adminChangePassword = async (data: any) => { + return serverFetchWithNoContent(`${BASE_API_URL}/user/admin-change-password`, { + method: "PATCH", + body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + }); }; \ No newline at end of file diff --git a/src/app/api/user/index.ts b/src/app/api/user/index.ts index 369c62d..a8d1d70 100644 --- a/src/app/api/user/index.ts +++ b/src/app/api/user/index.ts @@ -34,6 +34,15 @@ export interface UserDetail { auths: any[] } + export type passwordRule = { + min: number; + max: number; + number: boolean; + upperEng: boolean; + lowerEng: boolean; + specialChar: boolean; + } + export const preloadUser = () => { fetchUser(); }; @@ -52,4 +61,10 @@ export interface UserDetail { return serverFetchJson(`${BASE_API_URL}/user/${id}`, { next: { tags: ["user"] }, }); + }); + + export const fetchPwRules = cache(async () => { + return serverFetchJson(`${BASE_API_URL}/user/password-rule`, { + next: { tags: ["pwRule"] }, + }); }); \ No newline at end of file diff --git a/src/components/EditUser/EditUser.tsx b/src/components/EditUser/EditUser.tsx index 853de6a..ea478c5 100644 --- a/src/components/EditUser/EditUser.tsx +++ b/src/components/EditUser/EditUser.tsx @@ -26,17 +26,19 @@ import { } from "react-hook-form"; import { Check, Close, Error, RestartAlt } from "@mui/icons-material"; import { StaffResult } from "@/app/api/staff"; -import { UserInputs, editUser, fetchUserDetails } from "@/app/api/user/actions"; +import { UserInputs, adminChangePassword, editUser, fetchUserDetails } from "@/app/api/user/actions"; import UserDetail from "./UserDetail"; -import { UserResult } from "@/app/api/user"; +import { UserResult, passwordRule } from "@/app/api/user"; import { auth, fetchAuth } from "@/app/api/group/actions"; import AuthAllocation from "./AuthAllocation"; interface Props { - // users: UserResult[] -} + rules: passwordRule +} -const EditUser: React.FC = async ({ }) => { +const EditUser: React.FC = async ({ + rules +}) => { const { t } = useTranslation(); const formProps = useForm(); const searchParams = useSearchParams(); @@ -53,11 +55,11 @@ const EditUser: React.FC = async ({ }) => { }, [] ); + console.log(rules); const errors = formProps.formState.errors; const fetchUserDetail = async () => { - console.log(id); try { // fetch user info const userDetail = await fetchUserDetails(id); @@ -112,16 +114,45 @@ const EditUser: React.FC = async ({ }) => { const onSubmit = useCallback>( async (data) => { try { + let haveError = false + let regex_pw = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/ + let pw = '' + if (data.password && data.password.length > 0) { + pw = data.password + if (pw.length < rules.min) { + haveError = true + formProps.setError("password", { message: t("The password requires 8-20 characters."), type: "required" }) + } + if (pw.length > rules.max) { + haveError = true + formProps.setError("password", { message: t("The password requires 8-20 characters."), type: "required" }) + } + if (!regex_pw.test(pw)) { + haveError = true + formProps.setError("password", { message: "A combination of uppercase letters, lowercase letters, numbers, and symbols is required.", type: "required" }) + } + } console.log(data); - const tempData = { + const userData = { name: data.name, - email: data.email, + email: '', locked: false, addAuthIds: data.addAuthIds || [], removeAuthIds: data.removeAuthIds || [], } - console.log(tempData); - await editUser(id, tempData); + const pwData = { + id: id, + password: "", + newPassword: pw + } + console.log(userData); + if (haveError) { + return + } + await editUser(id, userData); + if (data.password && data.password.length > 0) { + await adminChangePassword(pwData); + } router.replace("/settings/staff"); } catch (e) { console.log(e); diff --git a/src/components/EditUser/EditUserWrapper.tsx b/src/components/EditUser/EditUserWrapper.tsx index 6d41a92..9273d4a 100644 --- a/src/components/EditUser/EditUserWrapper.tsx +++ b/src/components/EditUser/EditUserWrapper.tsx @@ -5,7 +5,7 @@ import EditUserLoading from "./EditUserLoading"; import { useSearchParams } from "next/navigation"; import { fetchTeam, fetchTeamDetail } from "@/app/api/team"; import { fetchStaff } from "@/app/api/staff"; -import { fetchUser, fetchUserDetail } from "@/app/api/user"; +import { fetchPwRules, fetchUser, fetchUserDetail } from "@/app/api/user"; interface SubComponents { Loading: typeof EditUserLoading; @@ -17,10 +17,9 @@ interface Props { const EditUserWrapper: React.FC & SubComponents = async ({ // id }) => { - // const users = await fetchUser() - // const userDetail = await fetchUserDetail(id) + const pwRule = await fetchPwRules() - return + return }; EditUserWrapper.Loading = EditUserLoading; diff --git a/src/components/EditUser/UserDetail.tsx b/src/components/EditUser/UserDetail.tsx index a3cf3ee..e4f6e8e 100644 --- a/src/components/EditUser/UserDetail.tsx +++ b/src/components/EditUser/UserDetail.tsx @@ -47,12 +47,10 @@ const UserDetail: React.FC = ({ diff --git a/src/components/GenerateMonthlyWorkHoursReport/GenerateMonthlyWorkHoursReport.tsx b/src/components/GenerateMonthlyWorkHoursReport/GenerateMonthlyWorkHoursReport.tsx index 417a182..3b5fa24 100644 --- a/src/components/GenerateMonthlyWorkHoursReport/GenerateMonthlyWorkHoursReport.tsx +++ b/src/components/GenerateMonthlyWorkHoursReport/GenerateMonthlyWorkHoursReport.tsx @@ -24,11 +24,18 @@ const GenerateMonthlyWorkHoursReport: React.FC = ({ staffs }) => { const searchCriteria: Criterion[] = useMemo( () => [ - { label: t("Staff"), - paramName: "staff", - type: "select", - options: staffCombo, - needAll: false}, + { + label: t("Staff"), + paramName: "staff", + type: "select", + options: staffCombo, + needAll: false + }, + { + label: t("date"), + paramName: "date", + type: "monthYear", + }, ], [t], ); @@ -38,9 +45,10 @@ return ( { - if (query.staff.length > 0 && query.staff.toLocaleLowerCase() !== "all") { + console.log(query) + 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: "2023-03" }) + const response = await fetchMonthlyWorkHoursReport({ id: staffs[index].id, yearMonth: query.date }) if (response) { downloadFile(new Uint8Array(response.blobValue), response.filename!!) } diff --git a/src/components/SearchBox/SearchBox.tsx b/src/components/SearchBox/SearchBox.tsx index 5ea1690..92eb204 100644 --- a/src/components/SearchBox/SearchBox.tsx +++ b/src/components/SearchBox/SearchBox.tsx @@ -21,6 +21,7 @@ import { DatePicker } from "@mui/x-date-pickers/DatePicker"; import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { Box } from "@mui/material"; +import { DateCalendar } from "@mui/x-date-pickers"; interface BaseCriterion { label: string; @@ -43,10 +44,15 @@ interface DateRangeCriterion extends BaseCriterion { type: "dateRange"; } +interface MonthYearCriterion extends BaseCriterion { + type: "monthYear"; +} + export type Criterion = | TextCriterion | SelectCriterion - | DateRangeCriterion; + | DateRangeCriterion + | MonthYearCriterion; interface Props { criteria: Criterion[]; @@ -66,19 +72,19 @@ function SearchBox({ (acc, c) => { return { ...acc, [c.paramName]: c.type === "select" ? "All" : "" }; }, - {} as Record, + {} as Record ), - [criteria], + [criteria] ); const [inputs, setInputs] = useState(defaultInputs); - + const makeInputChangeHandler = useCallback( (paramName: T): React.ChangeEventHandler => { return (e) => { setInputs((i) => ({ ...i, [paramName]: e.target.value })); }; }, - [], + [] ); const makeSelectChangeHandler = useCallback((paramName: T) => { @@ -93,6 +99,13 @@ function SearchBox({ }; }, []); + const makeMonthYearChangeHandler = useCallback((paramName: T) => { + return (e: any) => { + console.log(dayjs(e).format("YYYY-MM")) + setInputs((i) => ({ ...i, [paramName]: dayjs(e).format("YYYY-MM") })); + }; + }, []); + const makeDateToChangeHandler = useCallback((paramName: T) => { return (e: any) => { setInputs((i) => ({ @@ -135,7 +148,9 @@ function SearchBox({ onChange={makeSelectChangeHandler(c.paramName)} value={inputs[c.paramName]} > - {!(c.needAll === false) && {t("All")}} + {!(c.needAll === false) && ( + {t("All")} + )} {c.options.map((option, index) => ( {t(option)} @@ -144,6 +159,26 @@ function SearchBox({ )} + {c.type === "monthYear" && ( + + + + + + )} {c.type === "dateRange" && ( ({ ({ @@ -180,22 +223,22 @@ function SearchBox({ ); })} - - - - + + + + );