Browse Source

update edits

tags/Baseline_30082024_FRONTEND_UAT
MSI\derek 1 year ago
parent
commit
9fe89f86cd
20 changed files with 677 additions and 138 deletions
  1. +28
    -0
      src/app/(main)/settings/skill/edit/page.tsx
  2. +2
    -2
      src/app/api/group/actions.ts
  3. +5
    -3
      src/app/api/user/actions.ts
  4. +3
    -2
      src/app/api/user/index.ts
  5. +151
    -0
      src/components/EditSkill/EditSkill.tsx
  6. +114
    -0
      src/components/EditSkill/EditSkillForm.tsx
  7. +40
    -0
      src/components/EditSkill/EditSkillLoading.tsx
  8. +21
    -0
      src/components/EditSkill/EditSkillWrapper.tsx
  9. +1
    -0
      src/components/EditSkill/index.ts
  10. +1
    -3
      src/components/EditTeam/Allocation.tsx
  11. +221
    -0
      src/components/EditUser/AuthAllocation.tsx
  12. +57
    -19
      src/components/EditUser/EditUser.tsx
  13. +3
    -3
      src/components/EditUser/EditUserWrapper.tsx
  14. +3
    -75
      src/components/EditUser/UserDetail.tsx
  15. +1
    -1
      src/components/EditUserGroup/EditUserGroup.tsx
  16. +0
    -2
      src/components/EditUserGroup/EditUserGroupWrapper.tsx
  17. +3
    -2
      src/components/SearchResults/SearchResults.tsx
  18. +17
    -15
      src/components/SkillSearch/SkillSearch.tsx
  19. +4
    -1
      src/components/StaffSearch/StaffSearch.tsx
  20. +2
    -10
      src/components/StaffSearch/StaffSearchWrapper.tsx

+ 28
- 0
src/app/(main)/settings/skill/edit/page.tsx View File

@@ -0,0 +1,28 @@
import { Edit } from "@mui/icons-material";
import { useSearchParams } from "next/navigation";
// import EditStaff from "@/components/EditStaff";
import { Suspense } from "react";
import { I18nProvider, getServerI18n } from "@/i18n";
// import EditStaffWrapper from "@/components/EditStaff/EditStaffWrapper";
import { Metadata } from "next";
import EditSkill from "@/components/EditSkill";
import { Typography } from "@mui/material";


const EditSkillPage: React.FC = async () => {
const { t } = await getServerI18n("staff");

return (
<>
<Typography variant="h4">{t("Edit Skill")}</Typography>
<I18nProvider namespaces={["team", "common"]}>
<Suspense fallback={<EditSkill.Loading />}>
<EditSkill />
</Suspense>
</I18nProvider>
{/* <EditStaff /> */}
</>
);
};

export default EditSkillPage;

+ 2
- 2
src/app/api/group/actions.ts View File

@@ -29,8 +29,8 @@ export interface record {
records: auth[];
}

export const fetchAuth = cache(async (id?: number) => {
return serverFetchJson<record>(`${BASE_API_URL}/group/auth/combo/${id ?? 0}`, {
export const fetchAuth = cache(async (target: string, id?: number) => {
return serverFetchJson<record>(`${BASE_API_URL}/group/auth/${target}/${id ?? 0}`, {
next: { tags: ["auth"] },
});
});


+ 5
- 3
src/app/api/user/actions.ts View File

@@ -7,8 +7,10 @@ import { UserDetail, UserResult } from ".";
import { cache } from "react";

export interface UserInputs {
username: string;
email: string;
name: string;
email?: string;
addAuthIds?: number[];
removeAuthIds?: number[];
}

export interface PasswordInputs {
@@ -40,7 +42,7 @@ export const deleteUser = async (id: number) => {
};

export const changePassword = async (data: any) => {
return serverFetchJson(`${BASE_API_URL}/user/change-password`, {
return serverFetchWithNoContent(`${BASE_API_URL}/user/change-password`, {
method: "PATCH",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },


+ 3
- 2
src/app/api/user/index.ts View File

@@ -3,7 +3,6 @@ import { BASE_API_URL } from "@/config/api";
import { cache } from "react";
import "server-only";


export interface UserResult {
action: any;
id: number;
@@ -20,6 +19,7 @@ export interface UserResult {
phone2: string;
remarks: string;
groupId: number;
auths: any
}

// export interface DetailedUser extends UserResult {
@@ -28,9 +28,10 @@ export interface UserResult {
// }

export interface UserDetail {
authIds: number[];
data: UserResult;
authIds: number[];
groupIds: number[];
auths: any[]
}

export const preloadUser = () => {


+ 151
- 0
src/components/EditSkill/EditSkill.tsx View File

@@ -0,0 +1,151 @@
"use client";
import { SkillResult } from "@/app/api/skill";
import {
Button,
Card,
CardContent,
Grid,
Stack,
Tab,
Tabs,
TabsProps,
TextField,
Typography,
} from "@mui/material";
import {
FieldErrors,
FormProvider,
SubmitErrorHandler,
SubmitHandler,
useForm,
useFormContext,
} from "react-hook-form";
import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useRouter, useSearchParams } from "next/navigation";
import { Check, Close, Error, RestartAlt } from "@mui/icons-material";
import EditSkillForm from "./EditSkillForm";
import { CreateSkillInputs, saveSkill } from "@/app/api/skill/actions";
import AuthAllocation from "../EditUser/AuthAllocation";

interface Props {
skills: SkillResult[];
}

const EditSkill: React.FC<Props> = async ({ skills }) => {
const { t } = useTranslation();
const formProps = useForm<CreateSkillInputs>();
const [serverError, setServerError] = useState("");
const router = useRouter();
const searchParams = useSearchParams();
const id = parseInt(searchParams.get("id") || "0");
const [tabIndex, setTabIndex] = useState(0);
const [filteredSkill, setFilteredSkill] = useState<SkillResult>(() =>
skills.filter((s) => s.id === id)[0] as SkillResult
);
const errors = formProps.formState.errors;

const onSubmit = useCallback<SubmitHandler<CreateSkillInputs>>(
async (data) => {
try {
console.log(data);
const postData = {
...data,
id: id
}
await saveSkill(postData)
router.replace(`/settings/skill`)
} catch (e) {
console.log(e);
setServerError(t("An error has occurred. Please try again later."));
}
},
[router]
);

const handleCancel = () => {
router.back();
};

const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
(_e, newValue) => {
setTabIndex(newValue);
},
[]
);

useEffect(() => {
formProps.reset({
name: filteredSkill.name,
code: filteredSkill.code,
description: filteredSkill.description
});
}, [skills]);

const hasErrorsInTab = (
tabIndex: number,
errors: FieldErrors<CreateSkillInputs>
) => {
switch (tabIndex) {
case 0:
return Object.keys(errors).length > 0;
default:
false;
}
};

return (
<>
{serverError && (
<Typography variant="body2" color="error" alignSelf="flex-end">
{serverError}
</Typography>
)}
<FormProvider {...formProps}>
<Stack
spacing={2}
component="form"
onSubmit={formProps.handleSubmit(onSubmit)}
>
<Tabs
value={tabIndex}
onChange={handleTabChange}
variant="scrollable"
>
<Tab
label={t("Skill Info")}
icon={
hasErrorsInTab(0, errors) ? (
<Error sx={{ marginInlineEnd: 1 }} color="error" />
) : undefined
}
iconPosition="end"
/>
{/* <Tab label={t("Certification")} iconPosition="end" /> */}
</Tabs>
{tabIndex === 0 && <EditSkillForm />}
<Stack direction="row" justifyContent="flex-end" gap={1}>
<Button
variant="text"
startIcon={<RestartAlt />}
// onClick={() => console.log("asdasd")}
>
{t("Reset")}
</Button>
<Button
variant="outlined"
startIcon={<Close />}
onClick={handleCancel}
>
{t("Cancel")}
</Button>
<Button variant="contained" startIcon={<Check />} type="submit">
{t("Confirm")}
</Button>
</Stack>
</Stack>
</FormProvider>
</>
);
};
export default EditSkill;

+ 114
- 0
src/components/EditSkill/EditSkillForm.tsx View File

@@ -0,0 +1,114 @@
"use client";

import { CreateSkillInputs } from "@/app/api/skill/actions";
import {
Box,
Button,
Card,
CardContent,
Grid,
Stack,
Tab,
Tabs,
TabsProps,
TextField,
Typography,
} from "@mui/material";
import { useSearchParams } from "next/navigation";
import {
FieldErrors,
FormProvider,
SubmitErrorHandler,
SubmitHandler,
useForm,
useFormContext,
} from "react-hook-form";
import { useTranslation } from "react-i18next";

interface Props {
// users: UserResult[]
}

const EditSkillForm: React.FC<Props> = async ({}) => {
const { t } = useTranslation();
const searchParams = useSearchParams();
const idString = searchParams.get("id");
const {
register,
setValue,
getValues,
formState: { errors, defaultValues },
reset,
resetField,
} = useFormContext<CreateSkillInputs>();
// const formProps = useForm({});

return (
<>
<Card sx={{ display: "block" }}>
<CardContent component={Stack} spacing={4}>
<Box>
<Typography variant="overline" display="block" marginBlockEnd={1}>
{t("Skill Info")}
</Typography>
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
<Grid item xs={6}>
<TextField
label={t("Skill Name")}
fullWidth
rows={4}
{...register("name", {
required: true,
})}
error={Boolean(errors.name)}
helperText={
Boolean(errors.name) &&
(errors.name?.message
? t(errors.name.message)
: t("Please input correct name"))
}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Skill Code")}
fullWidth
rows={4}
{...register("code", {
required: true,
})}
error={Boolean(errors.code)}
helperText={
Boolean(errors.code) &&
(errors.code?.message
? t(errors.code.message)
: t("Please input correct name"))
}
/>
</Grid>
<Grid item xs={12}>
<TextField
label={t("Skill Description")}
fullWidth
multiline
rows={4}
{...register("description", {
required: true,
})}
error={Boolean(errors.description)}
helperText={
Boolean(errors.description) &&
(errors.description?.message
? t(errors.description.message)
: t("Please input correct name"))
}
/>
</Grid>
</Grid>
</Box>
</CardContent>
</Card>
</>
);
};
export default EditSkillForm;

+ 40
- 0
src/components/EditSkill/EditSkillLoading.tsx View File

@@ -0,0 +1,40 @@
import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent";
import Skeleton from "@mui/material/Skeleton";
import Stack from "@mui/material/Stack";
import React from "react";

// Can make this nicer
export const EditSkillLoading: React.FC = () => {
return (
<>
<Card>
<CardContent>
<Stack spacing={2}>
<Skeleton variant="rounded" height={60} />
<Skeleton variant="rounded" height={60} />
<Skeleton variant="rounded" height={60} />
<Skeleton
variant="rounded"
height={50}
width={100}
sx={{ alignSelf: "flex-end" }}
/>
</Stack>
</CardContent>
</Card>
<Card>Edit Skill
<CardContent>
<Stack spacing={2}>
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
</Stack>
</CardContent>
</Card>
</>
);
};

export default EditSkillLoading;

+ 21
- 0
src/components/EditSkill/EditSkillWrapper.tsx View File

@@ -0,0 +1,21 @@
import React from "react";
import EditSkill from "./EditSkill";
import EditSkillLoading from "./EditSkillLoading";
import { fetchStaff, fetchTeamLeads } from "@/app/api/staff";
import { useSearchParams } from "next/navigation";
import { fetchSkill } from "@/app/api/skill";

interface SubComponents {
Loading: typeof EditSkillLoading;
}

const EditSkillWrapper: React.FC & SubComponents = async () => {
const skills = await fetchSkill()
console.log(skills)

return <EditSkill skills={skills}/>;
};

EditSkillWrapper.Loading = EditSkillLoading;

export default EditSkillWrapper;

+ 1
- 0
src/components/EditSkill/index.ts View File

@@ -0,0 +1 @@
export { default } from "./EditSkillWrapper";

+ 1
- 3
src/components/EditTeam/Allocation.tsx View File

@@ -16,8 +16,8 @@ import { Staff4TransferList, fetchStaffCombo } from "@/app/api/staff/actions";
import { StaffResult, StaffTeamTable } from "@/app/api/staff";
import SearchResults, { Column } from "../SearchResults";
import { Clear, PersonAdd, PersonRemove, Search } from "@mui/icons-material";
import { Card } from "reactstrap";
import {
Card,
Box,
CardContent,
Grid,
@@ -49,8 +49,6 @@ const Allocation: React.FC<Props> = ({ allStaffs: staff, teamLead }) => {
reset,
resetField,
} = useFormContext<CreateTeamInputs>();
// let firstFilter: StaffResult[] = []

const initialStaffs = staff.map((s) => ({ ...s }));
const [filteredStaff, setFilteredStaff] = useState(initialStaffs);


+ 221
- 0
src/components/EditUser/AuthAllocation.tsx View File

@@ -0,0 +1,221 @@
"use client";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { Add, Clear, PersonAdd, PersonRemove, Remove, Search } from "@mui/icons-material";
import { useTranslation } from "react-i18next";
import {
FieldErrors,
FormProvider,
SubmitErrorHandler,
SubmitHandler,
useForm,
useFormContext,
} from "react-hook-form";
import {
Box,
Card,
CardContent,
Grid,
IconButton,
InputAdornment,
Stack,
Tab,
Tabs,
TabsProps,
TextField,
Typography,
} from "@mui/material";
import { differenceBy } from "lodash";
import { UserInputs } from "@/app/api/user/actions";
import { auth } from "@/app/api/group/actions";
import SearchResults, { Column } from "../SearchResults";

export interface Props {
auths: auth[]
}

const AuthAllocation: React.FC<Props> = ({ auths }) => {
const { t } = useTranslation();
const searchParams = useSearchParams();
const id = parseInt(searchParams.get("id") || "0");
const {
setValue,
getValues,
formState: { defaultValues },
reset,
resetField,
} = useFormContext<UserInputs>();
const initialAuths = auths.map((u) => ({ ...u })).sort((a, b) => a.id - b.id);
const [filteredAuths, setFilteredAuths] = useState(initialAuths);
const [selectedAuths, setSelectedAuths] = useState<typeof filteredAuths>(
() => {
return filteredAuths.filter(
(s) => getValues("addAuthIds")?.includes(s.id)
);
}
);
const [removeAuthIds, setRemoveAuthIds] = useState<number[]>([]);

// Adding / Removing Auth
const addAuth = useCallback((auth: auth) => {
setSelectedAuths((a) => [...a, auth]);
}, []);
const removeAuth = useCallback((auth: auth) => {
setSelectedAuths((a) => a.filter((a) => a.id !== auth.id));
setRemoveAuthIds((prevIds) => [...prevIds, auth.id]);
}, []);

const clearAuth = useCallback(() => {
if (defaultValues !== undefined) {
resetField("addAuthIds");
setSelectedAuths(
initialAuths.filter((auth) => defaultValues.addAuthIds?.includes(auth.id))
);
}
}, [defaultValues]);

// Sync with form
useEffect(() => {
setValue(
"addAuthIds",
selectedAuths.map((a) => a.id)
);
setValue(
"removeAuthIds",
removeAuthIds
);
}, [selectedAuths, removeAuthIds, setValue]);

const AuthPoolColumns = useMemo<Column<auth>[]>(
() => [
{
label: t("Add"),
name: "id",
onClick: addAuth,
buttonIcon: <Add />,
},
{ label: t("authority"), name: "authority" },
{ label: t("Auth Name"), name: "name" },
// { label: t("Current Position"), name: "currentPosition" },
],
[addAuth, t]
);

const allocatedAuthColumns = useMemo<Column<auth>[]>(
() => [
{
label: t("Remove"),
name: "id",
onClick: removeAuth,
buttonIcon: <Remove color="warning"/>,
},
{ label: t("authority"), name: "authority" },
{ label: t("Auth Name"), name: "name" },
],
[removeAuth, selectedAuths, t]
);

const [query, setQuery] = React.useState("");
const onQueryInputChange = React.useCallback<
React.ChangeEventHandler<HTMLInputElement>
>((e) => {
setQuery(e.target.value);
}, []);
const clearQueryInput = React.useCallback(() => {
setQuery("");
}, []);

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))
// })
// );
}, [auths, query]);

const resetAuth = React.useCallback(() => {
clearQueryInput();
clearAuth();
}, [clearQueryInput, clearAuth]);

const formProps = useForm({});

// Tab related
const [tabIndex, setTabIndex] = React.useState(0);
const handleTabChange = React.useCallback<NonNullable<TabsProps["onChange"]>>(
(_e, newValue) => {
setTabIndex(newValue);
},
[]
);

return (
<>
<FormProvider {...formProps}>
<Card sx={{ display: "block" }}>
<CardContent
sx={{ display: "flex", flexDirection: "column", gap: 1 }}
>
<Stack gap={2}>
<Typography variant="overline" display="block">
{t("Authority")}
</Typography>
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
<Grid item xs={6} display="flex" alignItems="center">
<Search sx={{ marginInlineEnd: 1 }} />
<TextField
variant="standard"
fullWidth
onChange={onQueryInputChange}
value={query}
placeholder={t("Search by staff ID, name or position.")}
InputProps={{
endAdornment: query && (
<InputAdornment position="end">
<IconButton onClick={clearQueryInput}>
<Clear />
</IconButton>
</InputAdornment>
),
}}
/>
</Grid>
</Grid>
<Tabs value={tabIndex} onChange={handleTabChange}>
<Tab label={t("Authority Pool")} />
<Tab
label={`${t("Allocated Authority")} (${selectedAuths.length})`}
/>
</Tabs>
<Box sx={{ marginInline: -3 }}>
{tabIndex === 0 && (
<SearchResults
noWrapper
items={differenceBy(filteredAuths, selectedAuths, "id")}
columns={AuthPoolColumns}
/>
)}
{tabIndex === 1 && (
<SearchResults
noWrapper
items={selectedAuths}
columns={allocatedAuthColumns}
/>
)}
</Box>
</Stack>
</CardContent>
</Card>
</FormProvider>
</>
);

}
export default AuthAllocation

+ 57
- 19
src/components/EditUser/EditUser.tsx View File

@@ -1,6 +1,6 @@
"use client";
import { useRouter, useSearchParams } from "next/navigation";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from "react";
import SearchResults, { Column } from "../SearchResults";
// import { TeamResult } from "@/app/api/team";
import { useTranslation } from "react-i18next";
@@ -26,9 +26,11 @@ import {
} from "react-hook-form";
import { Check, Close, Error, RestartAlt } from "@mui/icons-material";
import { StaffResult } from "@/app/api/staff";
import { editUser, fetchUserDetails } from "@/app/api/user/actions";
import { UserInputs, editUser, fetchUserDetails } from "@/app/api/user/actions";
import UserDetail from "./UserDetail";
import { UserResult } from "@/app/api/user";
import { auth, fetchAuth } from "@/app/api/group/actions";
import AuthAllocation from "./AuthAllocation";

interface Props {
// users: UserResult[]
@@ -36,11 +38,14 @@ interface Props {

const EditUser: React.FC<Props> = async ({ }) => {
const { t } = useTranslation();
const formProps = useForm<UserResult>();
const formProps = useForm<UserInputs>();
const searchParams = useSearchParams();
const id = parseInt(searchParams.get("id") || "0");
const [tabIndex, setTabIndex] = useState(0);
const router = useRouter();
const [serverError, setServerError] = useState("");
const [data, setData] = useState<UserResult>();
const [auths, setAuths] = useState<auth[]>();

const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
(_e, newValue) => {
@@ -49,38 +54,45 @@ const EditUser: React.FC<Props> = async ({ }) => {
[]
);

const [serverError, setServerError] = useState("");
const [data, setData] = useState<UserResult>();
const errors = formProps.formState.errors;

const fetchUserDetail = async () => {
console.log(id);
try {
// fetch user info
const userDetail = await fetchUserDetails(id);
console.log(userDetail);
const _data = userDetail.data as UserResult;
console.log(_data);
setData(_data);
//fetch user auths
const authDetail = await fetchAuth("user", id);
setAuths(authDetail.records)
const addAuthIds = authDetail.records.filter((item) => item.v === 1).map((item) => item.id).sort((a, b) => a - b);

formProps.reset({
username: _data.username,
firstname: _data.firstname,
lastname: _data.lastname,
title: _data.title,
department: _data.department,
name: _data.username,
email: _data.email,
phone1: _data.phone1,
phone2: _data.phone2,
remarks: _data.remarks,
addAuthIds: addAuthIds || []
});
} catch (error) {
console.log(error);
setServerError(t("An error has occurred. Please try again later."));
}
};
}

useEffect(() => {
fetchUserDetail();
}, []);

// useEffect(() => {
// const thisUser = users.filter((item) => item.id === id)
// formProps.reset({
// username: thisUser[0].username,
// email: thisUser[0].email,
// });
// }, []);

const hasErrorsInTab = (
tabIndex: number,
errors: FieldErrors<UserResult>
@@ -97,14 +109,16 @@ const EditUser: React.FC<Props> = async ({ }) => {
router.back();
};

const onSubmit = useCallback<SubmitHandler<UserResult>>(
const onSubmit = useCallback<SubmitHandler<UserInputs>>(
async (data) => {
try {
console.log(data);
const tempData = {
username: data.username,
name: data.name,
email: data.email,
locked: false
locked: false,
addAuthIds: data.addAuthIds || [],
removeAuthIds: data.removeAuthIds || [],
}
console.log(tempData);
await editUser(id, tempData);
@@ -116,7 +130,7 @@ const EditUser: React.FC<Props> = async ({ }) => {
},
[router]
);
const onSubmitError = useCallback<SubmitErrorHandler<UserResult>>(
const onSubmitError = useCallback<SubmitErrorHandler<UserInputs>>(
(errors) => {
console.log(errors);
},
@@ -136,7 +150,31 @@ const EditUser: React.FC<Props> = async ({ }) => {
component="form"
onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)}
>
<UserDetail data={data!!} />
<Stack
direction="row"
justifyContent="space-between"
flexWrap="wrap"
rowGap={2}
>
<Tabs
value={tabIndex}
onChange={handleTabChange}
variant="scrollable"
>
<Tab
label={t("User Detail")}
icon={
hasErrorsInTab(0, errors) ? (
<Error sx={{ marginInlineEnd: 1 }} color="error" />
) : undefined
}
iconPosition="end"
/>
<Tab label={t("User Authority")} iconPosition="end" />
</Tabs>
</Stack>
{tabIndex == 0 && <UserDetail data={data!} />}
{tabIndex === 1 && <AuthAllocation auths={auths!}/>}
<Stack direction="row" justifyContent="flex-end" gap={1}>
<Button
variant="text"


+ 3
- 3
src/components/EditUser/EditUserWrapper.tsx View File

@@ -12,10 +12,10 @@ interface SubComponents {
}

const EditUserWrapper: React.FC & SubComponents = async () => {
// const users = await fetchUser()
// console.log(users)
const users = await fetchUser()
console.log(users)

return <EditUser />;
return <EditUser />
};

EditUserWrapper.Loading = EditUserLoading;


+ 3
- 75
src/components/EditUser/UserDetail.tsx View File

@@ -1,6 +1,7 @@
"use client";

import { UserResult } from "@/app/api/user";
import { UserInputs } from "@/app/api/user/actions";
import {
Card,
CardContent,
@@ -25,7 +26,7 @@ const UserDetail: React.FC<Props> = ({
register,
formState: { errors },
control,
} = useFormContext<UserResult>();
} = useFormContext<UserInputs>();

return (
<Card>
@@ -38,52 +39,12 @@ const UserDetail: React.FC<Props> = ({
<TextField
label={t("username")}
fullWidth
{...register("username", {
{...register("name", {
required: "username required!",
})}
error={Boolean(errors.name)}
/>
</Grid>
{/* <Grid item xs={6}>
<TextField
label={t("First Name")}
fullWidth
{...register("firstname", {
required: "Name required!",
})}
error={Boolean(errors.firstname)}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Last Name")}
fullWidth
{...register("lastname", {
required: "Name required!",
})}
error={Boolean(errors.lastname)}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("title")}
fullWidth
{...register("title", {
required: "title required!",
})}
error={Boolean(errors.title)}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("department")}
fullWidth
{...register("department", {
required: "department required!",
})}
error={Boolean(errors.department)}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("email")}
@@ -94,39 +55,6 @@ const UserDetail: React.FC<Props> = ({
error={Boolean(errors.email)}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("phone1")}
fullWidth
{...register("phone1", {
required: "phone1 required!",
})}
error={Boolean(errors.phone1)}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("phone2")}
fullWidth
{...register("phone2", {
required: "phone2 required!",
})}
error={Boolean(errors.phone2)}
/>
</Grid>
<Grid item xs={12}>
<TextField
label={t("remarks")}
fullWidth
multiline
rows={4}
variant="filled"
{...register("remarks", {
required: "remarks required!",
})}
error={Boolean(errors.remarks)}
/>
</Grid> */}
</Grid>
</CardContent>
</Card>


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

@@ -83,7 +83,7 @@ const EditUserGroup: React.FC<Props> = ({ groups, users }) => {
const thisGroup = groups.filter((item) => item.id === id)[0];
const addUserIds = users.filter((item) => item.groupId === id).map((data) => data.id)
let addAuthIds: number[] = []
fetchAuth(id).then((data) => {
fetchAuth("group", id).then((data) => {
setAuths(data.records)
addAuthIds = data.records.filter((data) => data.v === 1).map((data) => data.id).sort((a, b) => a - b);
formProps.reset({


+ 0
- 2
src/components/EditUserGroup/EditUserGroupWrapper.tsx View File

@@ -2,9 +2,7 @@ import React from "react";
import EditUserGroup from "./EditUserGroup";
import EditUserGroupLoading from "./EditUserGroupLoading";
import { fetchGroup } from "@/app/api/group";
import { fetchAuth } from "@/app/api/group/actions";
import { fetchUser } from "@/app/api/user";
import { useSearchParams } from "next/navigation";

interface SubComponents {
Loading: typeof EditUserGroupLoading;


+ 3
- 2
src/components/SearchResults/SearchResults.tsx View File

@@ -26,6 +26,7 @@ interface BaseColumn<T extends ResultWithId> {
color?: IconButtonOwnProps["color"];
needTranslation?: boolean;
type?: string;
isHidden?: boolean;
}

interface ColumnWithAction<T extends ResultWithId> extends BaseColumn<T> {
@@ -78,7 +79,7 @@ function SearchResults<T extends ResultWithId>({
<Table stickyHeader>
<TableHead>
<TableRow>
{columns.map((column, idx) => (
{columns.filter(item => item.isHidden !== true).map((column, idx) => (
<TableCell key={`${column.name.toString()}${idx}`}>
{column?.type === "money" ? <div style={{display: "flex", justifyContent: "flex-end"}}>{column.label}</div> : column.label}
</TableCell>
@@ -91,7 +92,7 @@ function SearchResults<T extends ResultWithId>({
.map((item) => {
return (
<TableRow hover tabIndex={-1} key={item.id}>
{columns.map((column, idx) => {
{columns.filter(item => item.isHidden !== true).map((column, idx) => {
const columnName = column.name;

return (


+ 17
- 15
src/components/SkillSearch/SkillSearch.tsx View File

@@ -18,16 +18,21 @@ type SearchParamNames = keyof SearchQuery;

const SkillSearch: React.FC<Props> = ({ skill }) => {
const { t } = useTranslation();
const [filteredStaff, setFilteredStaff] = useState(skill);
const [filteredSkill, setFilteredSkill] = useState(skill);
const router = useRouter();

const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [
{
label: t("Staff Name"),
label: t("Skill Name"),
paramName: "name",
type: "text",
},
{
label: t("Skill code"),
paramName: "code",
type: "text",
},
],
[t]
);
@@ -36,7 +41,7 @@ const SkillSearch: React.FC<Props> = ({ skill }) => {
(skill: SkillResult) => {
console.log(skill);
const id = skill.id;
// router.push(`/settings/skill/edit?id=${id}`);
router.push(`/settings/skill/edit?id=${id}`);
},
[router, t]
);
@@ -45,7 +50,7 @@ const SkillSearch: React.FC<Props> = ({ skill }) => {
// deleteDialog(async () => {
// await deleteStaff(skill.id);
// successDialog("Delete Success", t);
// setFilteredStaff((prev) => prev.filter((obj) => obj.id !== skill.id));
// setFilteredSkill((prev) => prev.filter((obj) => obj.id !== skill.id));
// }, t);
}, []);

@@ -76,19 +81,16 @@ const SkillSearch: React.FC<Props> = ({ skill }) => {
<SearchBox
criteria={searchCriteria}
onSearch={(query) => {
// setFilteredStaff(
// skill.filter(
// (s) =>
// s.skillId.toLowerCase().includes(query.skillId.toLowerCase()) &&
// s.name.toLowerCase().includes(query.name.toLowerCase())
// // (query.team === "All" || s.team === query.team) &&
// // (query.category === "All" || s.category === query.category) &&
// // (query.team === "All" || s.team === query.team),
// )
// );
setFilteredSkill(
skill.filter(
(s) =>
s.name.toLowerCase().includes(query.name.toLowerCase()) &&
s.code.toLowerCase().includes(query.code.toLowerCase())
)
);
}}
/>
<SearchResults<SkillResult> items={filteredStaff} columns={columns} />
<SearchResults<SkillResult> items={filteredSkill} columns={columns} />
</>
);
};


+ 4
- 1
src/components/StaffSearch/StaffSearch.tsx View File

@@ -10,15 +10,17 @@ import { deleteStaff } from "@/app/api/staff/actions";
import { useRouter } from "next/navigation";
import { deleteDialog, successDialog } from "../Swal/CustomAlerts";
import Person from '@mui/icons-material/Person';
import { MAINTAIN_USER, VIEW_USER } from "@/middleware";

interface Props {
staff: StaffResult[];
abilities: string[]
}

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

const StaffSearch: React.FC<Props> = ({ staff }) => {
const StaffSearch: React.FC<Props> = ({ staff, abilities }) => {
const { t } = useTranslation();
const [filteredStaff, setFilteredStaff] = useState(staff);
const router = useRouter();
@@ -95,6 +97,7 @@ const StaffSearch: React.FC<Props> = ({ staff }) => {
label: t("Actions"),
onClick: onUserClick,
buttonIcon: <Person />,
isHidden: ![MAINTAIN_USER, VIEW_USER].some((ability) => abilities.includes(ability)),
},
{ name: "team", label: t("Team") },
{ name: "name", label: t("Staff Name") },


+ 2
- 10
src/components/StaffSearch/StaffSearchWrapper.tsx View File

@@ -2,16 +2,8 @@ import { fetchStaff, fetchTeamLeads } from "@/app/api/staff";
import React from "react";
import StaffSearch from "./StaffSearch";
import StaffSearchLoading from "./StaffSearchLoading";
import { comboProp, fetchCompanyCombo } from "@/app/api/companys/actions";
import { fetchTeamCombo } from "@/app/api/team/actions";
import { fetchDepartmentCombo } from "@/app/api/departments/actions";
import { fetchPositionCombo } from "@/app/api/positions/actions";
import { fetchGradeCombo } from "@/app/api/grades/actions";
import { fetchSkillCombo } from "@/app/api/skill/actions";
import { fetchSalaryCombo } from "@/app/api/salarys/actions";
import { Session, getServerSession } from "next-auth";
import { authOptions } from "@/config/authConfig";
// import { preloadStaff } from "@/app/api/staff";

interface SubComponents {
Loading: typeof StaffSearchLoading;
@@ -24,9 +16,9 @@ interface SessionWithAbilities extends Session {
const StaffSearchWrapper: React.FC & SubComponents = async () => {
const staff = await fetchStaff();
const session = await getServerSession(authOptions) as SessionWithAbilities;
console.log(session.abilities);
const abilities: string[] = session.abilities!!

return <StaffSearch staff={staff} />;
return <StaffSearch staff={staff} abilities={abilities}/>;
};

StaffSearchWrapper.Loading = StaffSearchLoading;


Loading…
Cancel
Save