| @@ -8,7 +8,7 @@ export const metadata: Metadata = { | |||||
| title: "Edit User Group", | title: "Edit User Group", | ||||
| }; | }; | ||||
| const Positions: React.FC = async () => { | |||||
| const Group: React.FC = async () => { | |||||
| const { t } = await getServerI18n("group"); | const { t } = await getServerI18n("group"); | ||||
| // Preload necessary dependencies | // Preload necessary dependencies | ||||
| @@ -23,4 +23,4 @@ const Positions: React.FC = async () => { | |||||
| ); | ); | ||||
| }; | }; | ||||
| export default Positions; | |||||
| export default Group; | |||||
| @@ -22,7 +22,7 @@ import { CreateProjectInputs, saveProject } from "@/app/api/projects/actions"; | |||||
| import { Error } from "@mui/icons-material"; | import { Error } from "@mui/icons-material"; | ||||
| import { ProjectCategory } from "@/app/api/projects"; | import { ProjectCategory } from "@/app/api/projects"; | ||||
| import { Grid, Typography } from "@mui/material"; | import { Grid, Typography } from "@mui/material"; | ||||
| import CreateStaffForm from "@/components/CreateStaff/CreateStaff"; | |||||
| import CreateStaff from "@/components/CreateStaff"; | |||||
| interface CreateCustomInputs { | interface CreateCustomInputs { | ||||
| projectCode: string; | projectCode: string; | ||||
| @@ -31,23 +31,17 @@ interface CreateCustomInputs { | |||||
| // const Title = ["title1", "title2"]; | // const Title = ["title1", "title2"]; | ||||
| const CreateStaff: React.FC = async () => { | |||||
| const CreateStaffPage: React.FC = async () => { | |||||
| const { t } = await getServerI18n("staff"); | const { t } = await getServerI18n("staff"); | ||||
| const title = ['', t('Additional Info')] | |||||
| // const regex = new RegExp("^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$") | |||||
| // console.log(regex) | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <Typography variant="h4">{t("Create Staff")}</Typography> | <Typography variant="h4">{t("Create Staff")}</Typography> | ||||
| <I18nProvider namespaces={["staff"]}> | <I18nProvider namespaces={["staff"]}> | ||||
| <CreateStaffForm | |||||
| Title={title} | |||||
| /> | |||||
| <CreateStaff/> | |||||
| </I18nProvider> | </I18nProvider> | ||||
| </> | </> | ||||
| ); | ); | ||||
| }; | }; | ||||
| export default CreateStaff; | |||||
| export default CreateStaffPage; | |||||
| @@ -15,6 +15,8 @@ export interface combo { | |||||
| } | } | ||||
| export interface CreatePositionInputs { | export interface CreatePositionInputs { | ||||
| positionCode: string; | |||||
| positionName: string; | |||||
| code: string; | code: string; | ||||
| name: string; | name: string; | ||||
| description: string; | description: string; | ||||
| @@ -11,6 +11,12 @@ export interface UserInputs { | |||||
| email: string; | email: string; | ||||
| } | } | ||||
| export interface PasswordInputs { | |||||
| password: string; | |||||
| newPassword: string; | |||||
| newPasswordCheck: string; | |||||
| } | |||||
| export const fetchUserDetails = cache(async (id: number) => { | export const fetchUserDetails = cache(async (id: number) => { | ||||
| return serverFetchJson<UserDetail>(`${BASE_API_URL}/user/${id}`, { | return serverFetchJson<UserDetail>(`${BASE_API_URL}/user/${id}`, { | ||||
| @@ -31,4 +37,12 @@ export const deleteUser = async (id: number) => { | |||||
| method: "DELETE", | method: "DELETE", | ||||
| headers: { "Content-Type": "application/json" }, | headers: { "Content-Type": "application/json" }, | ||||
| }); | }); | ||||
| }; | |||||
| export const changePassword = async (data: any) => { | |||||
| return serverFetchJson(`${BASE_API_URL}/user/change-password`, { | |||||
| method: "PATCH", | |||||
| body: JSON.stringify(data), | |||||
| headers: { "Content-Type": "application/json" }, | |||||
| }); | |||||
| }; | }; | ||||
| @@ -10,6 +10,7 @@ import Divider from "@mui/material/Divider"; | |||||
| import Typography from "@mui/material/Typography"; | import Typography from "@mui/material/Typography"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import { signOut } from "next-auth/react"; | import { signOut } from "next-auth/react"; | ||||
| import { useRouter } from "next/navigation"; | |||||
| type Props = Pick<AppBarProps, "avatarImageSrc" | "profileName">; | type Props = Pick<AppBarProps, "avatarImageSrc" | "profileName">; | ||||
| @@ -26,6 +27,7 @@ const Profile: React.FC<Props> = ({ avatarImageSrc, profileName }) => { | |||||
| }; | }; | ||||
| const { t } = useTranslation("login"); | const { t } = useTranslation("login"); | ||||
| const router = useRouter(); | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| @@ -52,6 +54,7 @@ const Profile: React.FC<Props> = ({ avatarImageSrc, profileName }) => { | |||||
| {profileName} | {profileName} | ||||
| </Typography> | </Typography> | ||||
| <Divider /> | <Divider /> | ||||
| <MenuItem onClick={() => {router.replace("/settings/changepassword")}}>{t("Change Password")}</MenuItem> | |||||
| <MenuItem onClick={() => signOut()}>{t("Sign out")}</MenuItem> | <MenuItem onClick={() => signOut()}>{t("Sign out")}</MenuItem> | ||||
| </Menu> | </Menu> | ||||
| </> | </> | ||||
| @@ -22,7 +22,6 @@ import { fetchSkillCombo } from "@/app/api/skill/actions"; | |||||
| import { fetchSalaryCombo } from "@/app/api/salarys/actions"; | import { fetchSalaryCombo } from "@/app/api/salarys/actions"; | ||||
| interface Field { | interface Field { | ||||
| // subtitle: string; | |||||
| id: string; | id: string; | ||||
| label: string; | label: string; | ||||
| type: string; | type: string; | ||||
| @@ -33,12 +32,6 @@ interface Field { | |||||
| options?: any[]; | options?: any[]; | ||||
| readOnly?: boolean; | readOnly?: boolean; | ||||
| } | } | ||||
| interface formProps { | |||||
| Title?: string[]; | |||||
| // fieldLists: Field[][]; | |||||
| } | |||||
| export interface comboItem { | export interface comboItem { | ||||
| company: comboProp[]; | company: comboProp[]; | ||||
| team: comboProp[]; | team: comboProp[]; | ||||
| @@ -49,101 +42,14 @@ export interface comboItem { | |||||
| salary: comboProp[]; | salary: comboProp[]; | ||||
| } | } | ||||
| const CreateStaff: React.FC<formProps> = ({ Title }) => { | |||||
| // const router = useRouter(); | |||||
| const { t } = useTranslation(); | |||||
| const [companyCombo, setCompanyCombo] = useState<comboProp[]>(); | |||||
| const [teamCombo, setTeamCombo] = useState<comboProp[]>(); | |||||
| const [departmentCombo, setDepartmentCombo] = useState<comboProp[]>(); | |||||
| const [positionCombo, setPositionCombo] = useState<comboProp[]>(); | |||||
| const [gradeCombo, setGradeCombo] = useState<comboProp[]>(); | |||||
| const [skillCombo, setSkillCombo] = useState<comboProp[]>(); | |||||
| const [salaryCombo, setSalaryCombo] = useState<comboProp[]>(); | |||||
| // const [serverError, setServerError] = useState(""); | |||||
| let comboItem: comboItem = { | |||||
| company: [], | |||||
| team: [], | |||||
| department: [], | |||||
| position: [], | |||||
| grade: [], | |||||
| skill: [], | |||||
| salary: [], | |||||
| }; | |||||
| const fetchCompany = async () => { | |||||
| await fetchCompanyCombo().then((data) => { | |||||
| if (data) setCompanyCombo(data.records); | |||||
| }); | |||||
| } | |||||
| const fetchTeam = async () => { | |||||
| await fetchTeamCombo().then((data) => { | |||||
| if (data) setTeamCombo(data.records); | |||||
| }); | |||||
| } | |||||
| const fetchDepartment = async () => { | |||||
| await fetchDepartmentCombo().then((data) => { | |||||
| if (data) setDepartmentCombo(data.records); | |||||
| }); | |||||
| } | |||||
| const fetchPosition = async () => { | |||||
| await fetchPositionCombo().then((data) => { | |||||
| if (data) setPositionCombo(data.records); | |||||
| }); | |||||
| } | |||||
| const fetchGrade = async () => { | |||||
| await fetchGradeCombo().then((data) => { | |||||
| if (data) setGradeCombo(data.records); | |||||
| }); | |||||
| } | |||||
| const fetchSkill = async () => { | |||||
| await fetchSkillCombo().then((data) => { | |||||
| if (data) setSkillCombo(data.records); | |||||
| }); | |||||
| } | |||||
| const fetchSalary = async () => { | |||||
| await fetchSalaryCombo().then((data) => { | |||||
| if (data) setSalaryCombo(data.records); | |||||
| }); | |||||
| } | |||||
| useEffect(() => { | |||||
| fetchCompany() | |||||
| fetchTeam() | |||||
| fetchDepartment() | |||||
| fetchPosition() | |||||
| fetchGrade() | |||||
| fetchSkill() | |||||
| fetchSalary() | |||||
| }, []); | |||||
| useEffect(() => { | |||||
| if(!companyCombo) | |||||
| fetchCompany() | |||||
| if(!teamCombo) | |||||
| fetchTeam() | |||||
| if(!departmentCombo) | |||||
| fetchDepartment() | |||||
| if(!positionCombo) | |||||
| fetchPosition() | |||||
| if(!gradeCombo) | |||||
| fetchGrade() | |||||
| if(!skillCombo) | |||||
| fetchSkill() | |||||
| if(!salaryCombo) | |||||
| fetchSalary() | |||||
| interface formProps { | |||||
| Title?: string[]; | |||||
| combos: comboItem; | |||||
| } | |||||
| }, [companyCombo, teamCombo, departmentCombo, positionCombo, gradeCombo, skillCombo, salaryCombo]); | |||||
| // useEffect(() => { | |||||
| // console.log(companyCombo) | |||||
| // }, [companyCombo]); | |||||
| const CreateStaff: React.FC<formProps> = ({ Title, combos }) => { | |||||
| const { t } = useTranslation(); | |||||
| const fieldLists: Field[][] = [ | const fieldLists: Field[][] = [ | ||||
| [ | [ | ||||
| @@ -163,49 +69,49 @@ const CreateStaff: React.FC<formProps> = ({ Title }) => { | |||||
| id: "companyId", | id: "companyId", | ||||
| label: t("Company"), | label: t("Company"), | ||||
| type: "combo-Obj", | type: "combo-Obj", | ||||
| options: companyCombo || [], | |||||
| options: combos.company || [], | |||||
| required: true, | required: true, | ||||
| }, | }, | ||||
| { | { | ||||
| id: "teamId", | id: "teamId", | ||||
| label: t("Team"), | label: t("Team"), | ||||
| type: "combo-Obj", | type: "combo-Obj", | ||||
| options: teamCombo || [], | |||||
| options: combos.team || [], | |||||
| required: false, | required: false, | ||||
| }, | }, | ||||
| { | { | ||||
| id: "departmentId", | id: "departmentId", | ||||
| label: t("Department"), | label: t("Department"), | ||||
| type: "combo-Obj", | type: "combo-Obj", | ||||
| options: departmentCombo || [], | |||||
| options: combos.department || [], | |||||
| required: true, | required: true, | ||||
| }, | }, | ||||
| { | { | ||||
| id: "gradeId", | id: "gradeId", | ||||
| label: t("Grade"), | label: t("Grade"), | ||||
| type: "combo-Obj", | type: "combo-Obj", | ||||
| options: gradeCombo || [], | |||||
| options: combos.grade || [], | |||||
| required: false, | required: false, | ||||
| }, | }, | ||||
| { | { | ||||
| id: "skillSetId", | id: "skillSetId", | ||||
| label: t("Skillset"), | label: t("Skillset"), | ||||
| type: "multiSelect-Obj", | type: "multiSelect-Obj", | ||||
| options: skillCombo || [], | |||||
| options: combos.skill || [], | |||||
| required: false, | required: false, | ||||
| }, | }, | ||||
| { | { | ||||
| id: "currentPositionId", | id: "currentPositionId", | ||||
| label: t("Current Position"), | label: t("Current Position"), | ||||
| type: "combo-Obj", | type: "combo-Obj", | ||||
| options: positionCombo || [], | |||||
| options: combos.position || [], | |||||
| required: true, | required: true, | ||||
| }, | }, | ||||
| { | { | ||||
| id: "salaryId", | id: "salaryId", | ||||
| label: t("Salary Point"), | label: t("Salary Point"), | ||||
| type: "combo-Obj", | type: "combo-Obj", | ||||
| options: salaryCombo || [], | |||||
| options: combos.salary || [], | |||||
| required: true, | required: true, | ||||
| }, | }, | ||||
| // { | // { | ||||
| @@ -279,7 +185,7 @@ const CreateStaff: React.FC<formProps> = ({ Title }) => { | |||||
| id: "joinPositionId", | id: "joinPositionId", | ||||
| label: t("Join Position"), | label: t("Join Position"), | ||||
| type: "combo-Obj", | type: "combo-Obj", | ||||
| options: positionCombo || [], | |||||
| options: combos.position || [], | |||||
| required: true, | required: true, | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -1,17 +1,48 @@ | |||||
| import React from "react"; | import React from "react"; | ||||
| import CreateStaff from "./CreateStaff"; | |||||
| import CreateStaff, { comboItem } from "./CreateStaff"; | |||||
| import CreateStaffLoading from "./CreateStaffLoading"; | import CreateStaffLoading from "./CreateStaffLoading"; | ||||
| import { fetchStaff, fetchTeamLeads } from "@/app/api/staff"; | import { fetchStaff, fetchTeamLeads } from "@/app/api/staff"; | ||||
| import { useSearchParams } from "next/navigation"; | import { useSearchParams } from "next/navigation"; | ||||
| 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 { fetchCompanyCombo } from "@/app/api/companys/actions"; | |||||
| interface SubComponents { | interface SubComponents { | ||||
| Loading: typeof CreateStaffLoading; | Loading: typeof CreateStaffLoading; | ||||
| } | } | ||||
| const CreateStaffWrapper: React.FC & SubComponents = async () => { | const CreateStaffWrapper: React.FC & SubComponents = async () => { | ||||
| const [ | |||||
| CompanyCombo, | |||||
| TeamCombo, | |||||
| DepartmentCombo, | |||||
| PositionCombo, | |||||
| GradeCombo, | |||||
| SkillCombo, | |||||
| SalaryCombo, | |||||
| ] = await Promise.all([ | |||||
| fetchCompanyCombo(), | |||||
| fetchTeamCombo(), | |||||
| fetchDepartmentCombo(), | |||||
| fetchPositionCombo(), | |||||
| fetchGradeCombo(), | |||||
| fetchSkillCombo(), | |||||
| fetchSalaryCombo(), | |||||
| ]); | |||||
| const combos: comboItem = { | |||||
| company: CompanyCombo.records, | |||||
| team: TeamCombo.records, | |||||
| department: DepartmentCombo.records, | |||||
| position: PositionCombo.records, | |||||
| grade: GradeCombo.records, | |||||
| skill: SkillCombo.records, | |||||
| salary: SalaryCombo.records, | |||||
| } | |||||
| return <CreateStaff/>; | |||||
| return <CreateStaff combos={combos}/>; | |||||
| }; | }; | ||||
| CreateStaffWrapper.Loading = CreateStaffLoading; | CreateStaffWrapper.Loading = CreateStaffLoading; | ||||
| @@ -83,7 +83,7 @@ const EditStaffForm: React.FC<formProps> = ({ Title, fieldLists }) => { | |||||
| }; | }; | ||||
| return ( | return ( | ||||
| <> | <> | ||||
| {serverError && ( | |||||
| {serverError && ( | |||||
| <Typography variant="body2" color="error" alignSelf="flex-end"> | <Typography variant="body2" color="error" alignSelf="flex-end"> | ||||
| {serverError} | {serverError} | ||||
| </Typography> | </Typography> | ||||
| @@ -0,0 +1,164 @@ | |||||
| "use client"; | |||||
| import { useRouter, useSearchParams } from "next/navigation"; | |||||
| import { useCallback, useEffect, useMemo, useState } from "react"; | |||||
| import SearchResults, { Column } from "../SearchResults"; | |||||
| // import { TeamResult } from "@/app/api/team"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| 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 { Check, Close, Error, RestartAlt } from "@mui/icons-material"; | |||||
| import { StaffResult } from "@/app/api/staff"; | |||||
| import { editUser, fetchUserDetails } from "@/app/api/user/actions"; | |||||
| import UserDetail from "./UserDetail"; | |||||
| import { UserResult } from "@/app/api/user"; | |||||
| interface Props { | |||||
| // users: UserResult[] | |||||
| } | |||||
| const EditUser: React.FC<Props> = async ({ }) => { | |||||
| const { t } = useTranslation(); | |||||
| const formProps = useForm<UserResult>(); | |||||
| const searchParams = useSearchParams(); | |||||
| const id = parseInt(searchParams.get("id") || "0"); | |||||
| const [tabIndex, setTabIndex] = useState(0); | |||||
| const router = useRouter(); | |||||
| const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | |||||
| (_e, newValue) => { | |||||
| setTabIndex(newValue); | |||||
| }, | |||||
| [] | |||||
| ); | |||||
| const [serverError, setServerError] = useState(""); | |||||
| const [data, setData] = useState<UserResult>(); | |||||
| const fetchUserDetail = async () => { | |||||
| console.log(id); | |||||
| try { | |||||
| const userDetail = await fetchUserDetails(id); | |||||
| console.log(userDetail); | |||||
| const _data = userDetail.data as UserResult; | |||||
| console.log(_data); | |||||
| setData(_data); | |||||
| formProps.reset({ | |||||
| username: _data.username, | |||||
| firstname: _data.firstname, | |||||
| lastname: _data.lastname, | |||||
| title: _data.title, | |||||
| department: _data.department, | |||||
| email: _data.email, | |||||
| phone1: _data.phone1, | |||||
| phone2: _data.phone2, | |||||
| remarks: _data.remarks, | |||||
| }); | |||||
| } catch (error) { | |||||
| console.log(error); | |||||
| setServerError(t("An error has occurred. Please try again later.")); | |||||
| } | |||||
| }; | |||||
| useEffect(() => { | |||||
| fetchUserDetail(); | |||||
| }, []); | |||||
| const hasErrorsInTab = ( | |||||
| tabIndex: number, | |||||
| errors: FieldErrors<UserResult> | |||||
| ) => { | |||||
| switch (tabIndex) { | |||||
| case 0: | |||||
| return Object.keys(errors).length > 0; | |||||
| default: | |||||
| false; | |||||
| } | |||||
| }; | |||||
| const handleCancel = () => { | |||||
| router.back(); | |||||
| }; | |||||
| const onSubmit = useCallback<SubmitHandler<UserResult>>( | |||||
| async (data) => { | |||||
| try { | |||||
| console.log(data); | |||||
| const tempData = { | |||||
| username: data.username, | |||||
| email: data.email, | |||||
| locked: false | |||||
| } | |||||
| console.log(tempData); | |||||
| await editUser(id, tempData); | |||||
| router.replace("/settings/staff"); | |||||
| } catch (e) { | |||||
| console.log(e); | |||||
| setServerError(t("An error has occurred. Please try again later.")); | |||||
| } | |||||
| }, | |||||
| [router] | |||||
| ); | |||||
| const onSubmitError = useCallback<SubmitErrorHandler<UserResult>>( | |||||
| (errors) => { | |||||
| console.log(errors); | |||||
| }, | |||||
| [] | |||||
| ); | |||||
| return ( | |||||
| <> | |||||
| {serverError && ( | |||||
| <Typography variant="body2" color="error" alignSelf="flex-end"> | |||||
| {serverError} | |||||
| </Typography> | |||||
| )} | |||||
| <FormProvider {...formProps}> | |||||
| <Stack | |||||
| spacing={2} | |||||
| component="form" | |||||
| onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} | |||||
| > | |||||
| <UserDetail data={data!!} /> | |||||
| <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 EditUser; | |||||
| @@ -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 EditUserLoading: 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>EditUser | |||||
| <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 EditUserLoading; | |||||
| @@ -0,0 +1,23 @@ | |||||
| import React from "react"; | |||||
| import EditUser from "./EditUser"; | |||||
| import EditUserLoading from "./EditUserLoading"; | |||||
| // import { fetchTeam, fetchTeamLeads } from "@/app/api/Team"; | |||||
| import { useSearchParams } from "next/navigation"; | |||||
| import { fetchTeam, fetchTeamDetail } from "@/app/api/team"; | |||||
| import { fetchStaff } from "@/app/api/staff"; | |||||
| import { fetchUser } from "@/app/api/user"; | |||||
| interface SubComponents { | |||||
| Loading: typeof EditUserLoading; | |||||
| } | |||||
| const EditUserWrapper: React.FC & SubComponents = async () => { | |||||
| // const users = await fetchUser() | |||||
| // console.log(users) | |||||
| return <EditUser />; | |||||
| }; | |||||
| EditUserWrapper.Loading = EditUserLoading; | |||||
| export default EditUserWrapper; | |||||
| @@ -0,0 +1,136 @@ | |||||
| "use client"; | |||||
| import { UserResult } from "@/app/api/user"; | |||||
| import { | |||||
| Card, | |||||
| CardContent, | |||||
| Grid, | |||||
| Stack, | |||||
| TextField, | |||||
| Typography, | |||||
| } from "@mui/material"; | |||||
| import { useFormContext } from "react-hook-form"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| interface Props { | |||||
| data: UserResult | |||||
| } | |||||
| const UserDetail: React.FC<Props> = ({ | |||||
| data | |||||
| }) => { | |||||
| const { t } = useTranslation(); | |||||
| const { | |||||
| register, | |||||
| formState: { errors }, | |||||
| control, | |||||
| } = useFormContext<UserResult>(); | |||||
| return ( | |||||
| <Card> | |||||
| <CardContent component={Stack} spacing={4}> | |||||
| <Typography variant="overline" display="block" marginBlockEnd={1}> | |||||
| {t("User Detail")} | |||||
| </Typography> | |||||
| <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("username")} | |||||
| fullWidth | |||||
| {...register("username", { | |||||
| 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")} | |||||
| fullWidth | |||||
| {...register("email", { | |||||
| required: "email required!", | |||||
| })} | |||||
| 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> | |||||
| ); | |||||
| }; | |||||
| export default UserDetail; | |||||
| @@ -0,0 +1 @@ | |||||
| export { default } from "./EditUserWrapper"; | |||||
| @@ -14,14 +14,11 @@ const EditUserGroupWrapper: React.FC & SubComponents = async () => { | |||||
| const [ | const [ | ||||
| groups, | groups, | ||||
| // auths, | |||||
| users, | users, | ||||
| ] = await Promise.all([ | ] = await Promise.all([ | ||||
| fetchGroup(), | fetchGroup(), | ||||
| // fetchAuth(), | |||||
| fetchUser(), | fetchUser(), | ||||
| ]); | ]); | ||||
| console.log(users) | |||||
| return <EditUserGroup groups={groups.records} users={users}/>; | return <EditUserGroup groups={groups.records} users={users}/>; | ||||
| }; | }; | ||||
| @@ -9,6 +9,7 @@ import DeleteIcon from "@mui/icons-material/Delete"; | |||||
| import { deleteStaff } from "@/app/api/staff/actions"; | import { deleteStaff } from "@/app/api/staff/actions"; | ||||
| import { useRouter } from "next/navigation"; | import { useRouter } from "next/navigation"; | ||||
| import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; | import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; | ||||
| import Person from '@mui/icons-material/Person'; | |||||
| interface Props { | interface Props { | ||||
| staff: StaffResult[]; | staff: StaffResult[]; | ||||
| @@ -65,6 +66,14 @@ const StaffSearch: React.FC<Props> = ({ staff }) => { | |||||
| [router, t] | [router, t] | ||||
| ); | ); | ||||
| const onUserClick = useCallback( | |||||
| (staff: StaffResult) => { | |||||
| console.log(staff); | |||||
| router.push(`/settings/staff/user?id=${staff.id}`); | |||||
| }, | |||||
| [router, t] | |||||
| ); | |||||
| const deleteClick = useCallback((staff: StaffResult) => { | const deleteClick = useCallback((staff: StaffResult) => { | ||||
| deleteDialog(async () => { | deleteDialog(async () => { | ||||
| await deleteStaff(staff.id); | await deleteStaff(staff.id); | ||||
| @@ -81,6 +90,12 @@ const StaffSearch: React.FC<Props> = ({ staff }) => { | |||||
| onClick: onStaffClick, | onClick: onStaffClick, | ||||
| buttonIcon: <EditNote />, | buttonIcon: <EditNote />, | ||||
| }, | }, | ||||
| { | |||||
| name: "id", | |||||
| label: t("Actions"), | |||||
| onClick: onUserClick, | |||||
| buttonIcon: <Person />, | |||||
| }, | |||||
| { name: "team", label: t("Team") }, | { name: "team", label: t("Team") }, | ||||
| { name: "name", label: t("Staff Name") }, | { name: "name", label: t("Staff Name") }, | ||||
| { name: "staffId", label: t("Staff ID") }, | { name: "staffId", label: t("Staff ID") }, | ||||
| @@ -71,9 +71,6 @@ export default async function middleware( | |||||
| return response; | return response; | ||||
| } | } | ||||
| // const session = await getServerSession(authOptions); | |||||
| // console.log(session); | |||||
| let abilities: string[] = [] | let abilities: string[] = [] | ||||
| if (token) { | if (token) { | ||||
| abilities = (token.abilities as ability[]).map((item: ability) => item.actionSubjectCombo); | abilities = (token.abilities as ability[]).map((item: ability) => item.actionSubjectCombo); | ||||
| @@ -93,6 +90,9 @@ export default async function middleware( | |||||
| if (req.nextUrl.pathname.startsWith('/settings/user')) { | if (req.nextUrl.pathname.startsWith('/settings/user')) { | ||||
| isAuth = [MAINTAIN_USER, VIEW_USER].some((ability) => abilities.includes(ability)); | isAuth = [MAINTAIN_USER, VIEW_USER].some((ability) => abilities.includes(ability)); | ||||
| } | } | ||||
| if (req.nextUrl.pathname.startsWith('/settings/staff/user')) { | |||||
| isAuth = [MAINTAIN_USER, VIEW_USER].some((ability) => abilities.includes(ability)); | |||||
| } | |||||
| if (req.nextUrl.pathname.startsWith('/analytics')) { | if (req.nextUrl.pathname.startsWith('/analytics')) { | ||||
| isAuth = [GENERATE_REPORTS].some((ability) => abilities.includes(ability)); | isAuth = [GENERATE_REPORTS].some((ability) => abilities.includes(ability)); | ||||
| } | } | ||||
| @@ -104,40 +104,6 @@ export default async function middleware( | |||||
| } | } | ||||
| }); | }); | ||||
| // for (const obj of abilities) { | |||||
| // switch (obj.actionSubjectCombo.toLowerCase()) { | |||||
| // case "maintain_user": | |||||
| // // appendRoutes(settings) | |||||
| // break; | |||||
| // case "maintain_group": | |||||
| // // appendRoutes("/testing-maintain_user") | |||||
| // break; | |||||
| // case "view_user": | |||||
| // // appendRoutes("/testing-maintain_user") | |||||
| // break; | |||||
| // case "view_group": | |||||
| // // appendRoutes("/testing-maintain_user") | |||||
| // break; | |||||
| // } | |||||
| // } | |||||
| // console.log("TESTING_ROUTES: ") | |||||
| // console.log(TESTING_ROUTES) | |||||
| // TESTING_ROUTES.some((route) => { | |||||
| // if (req.nextUrl.pathname.startsWith(route)) { | |||||
| // console.log("////////////////start//////////////// ") | |||||
| // console.log("TESTING_ROUTES:") | |||||
| // console.log("route:") | |||||
| // console.log(route) | |||||
| // console.log("pathname:") | |||||
| // console.log(req.nextUrl.pathname) | |||||
| // console.log("////////////////end////////////////") | |||||
| // } | |||||
| // return (req.nextUrl.pathname.startsWith(route)) | |||||
| // }) | |||||
| // Matcher for using the auth middleware | // Matcher for using the auth middleware | ||||
| return PRIVATE_ROUTES.some((route) => req.nextUrl.pathname.startsWith(route)) | return PRIVATE_ROUTES.some((route) => req.nextUrl.pathname.startsWith(route)) | ||||
| ? await authMiddleware(req, event) // Let auth middleware handle response | ? await authMiddleware(req, event) // Let auth middleware handle response | ||||