Pārlūkot izejas kodu

update

tags/Baseline_30082024_FRONTEND_UAT
MSI\derek pirms 1 gada
vecāks
revīzija
133bedcc40
7 mainītis faili ar 153 papildinājumiem un 50 dzēšanām
  1. +9
    -0
      src/app/api/user/actions.ts
  2. +15
    -0
      src/app/api/user/index.ts
  3. +41
    -10
      src/components/EditUser/EditUser.tsx
  4. +3
    -4
      src/components/EditUser/EditUserWrapper.tsx
  5. +3
    -5
      src/components/EditUser/UserDetail.tsx
  6. +15
    -7
      src/components/GenerateMonthlyWorkHoursReport/GenerateMonthlyWorkHoursReport.tsx
  7. +67
    -24
      src/components/SearchBox/SearchBox.tsx

+ 9
- 0
src/app/api/user/actions.ts Parādīt failu

@@ -11,6 +11,7 @@ export interface UserInputs {
email?: string; email?: string;
addAuthIds?: number[]; addAuthIds?: number[];
removeAuthIds?: number[]; removeAuthIds?: number[];
password?: string;
} }


export interface PasswordInputs { export interface PasswordInputs {
@@ -50,4 +51,12 @@ export const changePassword = async (data: any) => {
body: JSON.stringify(data), body: JSON.stringify(data),
headers: { "Content-Type": "application/json" }, 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" },
});
}; };

+ 15
- 0
src/app/api/user/index.ts Parādīt failu

@@ -34,6 +34,15 @@ export interface UserDetail {
auths: any[] auths: any[]
} }


export type passwordRule = {
min: number;
max: number;
number: boolean;
upperEng: boolean;
lowerEng: boolean;
specialChar: boolean;
}

export const preloadUser = () => { export const preloadUser = () => {
fetchUser(); fetchUser();
}; };
@@ -52,4 +61,10 @@ export interface UserDetail {
return serverFetchJson<UserResult[]>(`${BASE_API_URL}/user/${id}`, { return serverFetchJson<UserResult[]>(`${BASE_API_URL}/user/${id}`, {
next: { tags: ["user"] }, next: { tags: ["user"] },
}); });
});

export const fetchPwRules = cache(async () => {
return serverFetchJson<passwordRule>(`${BASE_API_URL}/user/password-rule`, {
next: { tags: ["pwRule"] },
});
}); });

+ 41
- 10
src/components/EditUser/EditUser.tsx Parādīt failu

@@ -26,17 +26,19 @@ import {
} from "react-hook-form"; } from "react-hook-form";
import { Check, Close, Error, RestartAlt } from "@mui/icons-material"; import { Check, Close, Error, RestartAlt } from "@mui/icons-material";
import { StaffResult } from "@/app/api/staff"; 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 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 { auth, fetchAuth } from "@/app/api/group/actions";
import AuthAllocation from "./AuthAllocation"; import AuthAllocation from "./AuthAllocation";


interface Props { interface Props {
// users: UserResult[]
}
rules: passwordRule
}


const EditUser: React.FC<Props> = async ({ }) => {
const EditUser: React.FC<Props> = async ({
rules
}) => {
const { t } = useTranslation(); const { t } = useTranslation();
const formProps = useForm<UserInputs>(); const formProps = useForm<UserInputs>();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
@@ -53,11 +55,11 @@ const EditUser: React.FC<Props> = async ({ }) => {
}, },
[] []
); );
console.log(rules);


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


const fetchUserDetail = async () => { const fetchUserDetail = async () => {
console.log(id);
try { try {
// fetch user info // fetch user info
const userDetail = await fetchUserDetails(id); const userDetail = await fetchUserDetails(id);
@@ -112,16 +114,45 @@ const EditUser: React.FC<Props> = async ({ }) => {
const onSubmit = useCallback<SubmitHandler<UserInputs>>( const onSubmit = useCallback<SubmitHandler<UserInputs>>(
async (data) => { async (data) => {
try { 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); console.log(data);
const tempData = {
const userData = {
name: data.name, name: data.name,
email: data.email,
email: '',
locked: false, locked: false,
addAuthIds: data.addAuthIds || [], addAuthIds: data.addAuthIds || [],
removeAuthIds: data.removeAuthIds || [], 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"); router.replace("/settings/staff");
} catch (e) { } catch (e) {
console.log(e); console.log(e);


+ 3
- 4
src/components/EditUser/EditUserWrapper.tsx Parādīt failu

@@ -5,7 +5,7 @@ import EditUserLoading from "./EditUserLoading";
import { useSearchParams } from "next/navigation"; import { useSearchParams } from "next/navigation";
import { fetchTeam, fetchTeamDetail } from "@/app/api/team"; import { fetchTeam, fetchTeamDetail } from "@/app/api/team";
import { fetchStaff } from "@/app/api/staff"; import { fetchStaff } from "@/app/api/staff";
import { fetchUser, fetchUserDetail } from "@/app/api/user";
import { fetchPwRules, fetchUser, fetchUserDetail } from "@/app/api/user";


interface SubComponents { interface SubComponents {
Loading: typeof EditUserLoading; Loading: typeof EditUserLoading;
@@ -17,10 +17,9 @@ interface Props {
const EditUserWrapper: React.FC<Props> & SubComponents = async ({ const EditUserWrapper: React.FC<Props> & SubComponents = async ({
// id // id
}) => { }) => {
// const users = await fetchUser()
// const userDetail = await fetchUserDetail(id)
const pwRule = await fetchPwRules()


return <EditUser />
return <EditUser rules={pwRule} />
}; };


EditUserWrapper.Loading = EditUserLoading; EditUserWrapper.Loading = EditUserLoading;


+ 3
- 5
src/components/EditUser/UserDetail.tsx Parādīt failu

@@ -47,12 +47,10 @@ const UserDetail: React.FC<Props> = ({
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={6}>
<TextField <TextField
label={t("email")}
label={t("password")}
fullWidth fullWidth
{...register("email", {
required: "email required!",
})}
error={Boolean(errors.email)}
{...register("password")}
error={Boolean(errors.password)}
/> />
</Grid> </Grid>
</Grid> </Grid>


+ 15
- 7
src/components/GenerateMonthlyWorkHoursReport/GenerateMonthlyWorkHoursReport.tsx Parādīt failu

@@ -24,11 +24,18 @@ const GenerateMonthlyWorkHoursReport: React.FC<Props> = ({ staffs }) => {


const searchCriteria: Criterion<SearchParamNames>[] = useMemo( const searchCriteria: Criterion<SearchParamNames>[] = 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], [t],
); );
@@ -38,9 +45,10 @@ return (
<SearchBox <SearchBox
criteria={searchCriteria} criteria={searchCriteria}
onSearch={async (query: any) => { onSearch={async (query: any) => {
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 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) { if (response) {
downloadFile(new Uint8Array(response.blobValue), response.filename!!) downloadFile(new Uint8Array(response.blobValue), response.filename!!)
} }


+ 67
- 24
src/components/SearchBox/SearchBox.tsx Parādīt failu

@@ -21,6 +21,7 @@ import { DatePicker } from "@mui/x-date-pickers/DatePicker";
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import { Box } from "@mui/material"; import { Box } from "@mui/material";
import { DateCalendar } from "@mui/x-date-pickers";


interface BaseCriterion<T extends string> { interface BaseCriterion<T extends string> {
label: string; label: string;
@@ -43,10 +44,15 @@ interface DateRangeCriterion<T extends string> extends BaseCriterion<T> {
type: "dateRange"; type: "dateRange";
} }


interface MonthYearCriterion<T extends string> extends BaseCriterion<T> {
type: "monthYear";
}

export type Criterion<T extends string> = export type Criterion<T extends string> =
| TextCriterion<T> | TextCriterion<T>
| SelectCriterion<T> | SelectCriterion<T>
| DateRangeCriterion<T>;
| DateRangeCriterion<T>
| MonthYearCriterion<T>;


interface Props<T extends string> { interface Props<T extends string> {
criteria: Criterion<T>[]; criteria: Criterion<T>[];
@@ -66,19 +72,19 @@ function SearchBox<T extends string>({
(acc, c) => { (acc, c) => {
return { ...acc, [c.paramName]: c.type === "select" ? "All" : "" }; return { ...acc, [c.paramName]: c.type === "select" ? "All" : "" };
}, },
{} as Record<T, string>,
{} as Record<T, string>
), ),
[criteria],
[criteria]
); );
const [inputs, setInputs] = useState(defaultInputs); const [inputs, setInputs] = useState(defaultInputs);
const makeInputChangeHandler = useCallback( const makeInputChangeHandler = useCallback(
(paramName: T): React.ChangeEventHandler<HTMLInputElement> => { (paramName: T): React.ChangeEventHandler<HTMLInputElement> => {
return (e) => { return (e) => {
setInputs((i) => ({ ...i, [paramName]: e.target.value })); setInputs((i) => ({ ...i, [paramName]: e.target.value }));
}; };
}, },
[],
[]
); );


const makeSelectChangeHandler = useCallback((paramName: T) => { const makeSelectChangeHandler = useCallback((paramName: T) => {
@@ -93,6 +99,13 @@ function SearchBox<T extends string>({
}; };
}, []); }, []);


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) => { const makeDateToChangeHandler = useCallback((paramName: T) => {
return (e: any) => { return (e: any) => {
setInputs((i) => ({ setInputs((i) => ({
@@ -135,7 +148,9 @@ function SearchBox<T extends string>({
onChange={makeSelectChangeHandler(c.paramName)} onChange={makeSelectChangeHandler(c.paramName)}
value={inputs[c.paramName]} value={inputs[c.paramName]}
> >
{!(c.needAll === false) && <MenuItem value={"All"}>{t("All")}</MenuItem>}
{!(c.needAll === false) && (
<MenuItem value={"All"}>{t("All")}</MenuItem>
)}
{c.options.map((option, index) => ( {c.options.map((option, index) => (
<MenuItem key={`${option}-${index}`} value={option}> <MenuItem key={`${option}-${index}`} value={option}>
{t(option)} {t(option)}
@@ -144,6 +159,26 @@ function SearchBox<T extends string>({
</Select> </Select>
</FormControl> </FormControl>
)} )}
{c.type === "monthYear" && (
<LocalizationProvider
dateAdapter={AdapterDayjs}
// TODO: Should maybe use a custom adapterLocale here to support YYYY-MM-DD
adapterLocale="zh-hk"
>
<Box display="flex">
<DateCalendar
views={["month", "year"]}
openTo="month"
onChange={makeMonthYearChangeHandler(c.paramName)}
value={
inputs[c.paramName]
? dayjs(inputs[c.paramName])
: null
}
/>
</Box>
</LocalizationProvider>
)}
{c.type === "dateRange" && ( {c.type === "dateRange" && (
<LocalizationProvider <LocalizationProvider
dateAdapter={AdapterDayjs} dateAdapter={AdapterDayjs}
@@ -155,7 +190,11 @@ function SearchBox<T extends string>({
<DatePicker <DatePicker
label={c.label} label={c.label}
onChange={makeDateChangeHandler(c.paramName)} onChange={makeDateChangeHandler(c.paramName)}
value={inputs[c.paramName] ? dayjs(inputs[c.paramName]) : null}
value={
inputs[c.paramName]
? dayjs(inputs[c.paramName])
: null
}
/> />
</FormControl> </FormControl>
<Box <Box
@@ -170,7 +209,11 @@ function SearchBox<T extends string>({
<DatePicker <DatePicker
label={c.label2} label={c.label2}
onChange={makeDateToChangeHandler(c.paramName)} onChange={makeDateToChangeHandler(c.paramName)}
value={inputs[c.paramName.concat("To") as T] ? dayjs(inputs[c.paramName.concat("To") as T]) : null}
value={
inputs[c.paramName.concat("To") as T]
? dayjs(inputs[c.paramName.concat("To") as T])
: null
}
/> />
</FormControl> </FormControl>
</Box> </Box>
@@ -180,22 +223,22 @@ function SearchBox<T extends string>({
); );
})} })}
</Grid> </Grid>
<CardActions sx={{ justifyContent: "flex-end" }}>
<Button
variant="text"
startIcon={<RestartAlt />}
onClick={handleReset}
>
{t("Reset")}
</Button>
<Button
variant="outlined"
startIcon={<Search />}
onClick={handleSearch}
>
{t("Search")}
</Button>
</CardActions>
<CardActions sx={{ justifyContent: "flex-end" }}>
<Button
variant="text"
startIcon={<RestartAlt />}
onClick={handleReset}
>
{t("Reset")}
</Button>
<Button
variant="outlined"
startIcon={<Search />}
onClick={handleSearch}
>
{t("Search")}
</Button>
</CardActions>
</CardContent> </CardContent>
</Card> </Card>
); );


Notiek ielāde…
Atcelt
Saglabāt