diff --git a/src/app/(main)/settings/group/edit/page.tsx b/src/app/(main)/settings/group/edit/page.tsx index ae51d16..9055358 100644 --- a/src/app/(main)/settings/group/edit/page.tsx +++ b/src/app/(main)/settings/group/edit/page.tsx @@ -8,7 +8,7 @@ export const metadata: Metadata = { title: "Edit User Group", }; -const Positions: React.FC = async () => { +const Group: React.FC = async () => { const { t } = await getServerI18n("group"); // Preload necessary dependencies @@ -23,4 +23,4 @@ const Positions: React.FC = async () => { ); }; -export default Positions; \ No newline at end of file +export default Group; \ No newline at end of file diff --git a/src/app/(main)/settings/staff/create/page.tsx b/src/app/(main)/settings/staff/create/page.tsx index bc40f6a..24f3589 100644 --- a/src/app/(main)/settings/staff/create/page.tsx +++ b/src/app/(main)/settings/staff/create/page.tsx @@ -22,7 +22,7 @@ import { CreateProjectInputs, saveProject } from "@/app/api/projects/actions"; import { Error } from "@mui/icons-material"; import { ProjectCategory } from "@/app/api/projects"; import { Grid, Typography } from "@mui/material"; -import CreateStaffForm from "@/components/CreateStaff/CreateStaff"; +import CreateStaff from "@/components/CreateStaff"; interface CreateCustomInputs { projectCode: string; @@ -31,23 +31,17 @@ interface CreateCustomInputs { // const Title = ["title1", "title2"]; -const CreateStaff: React.FC = async () => { +const CreateStaffPage: React.FC = async () => { 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 ( <> {t("Create Staff")} - + ); }; -export default CreateStaff; +export default CreateStaffPage; diff --git a/src/app/api/positions/actions.ts b/src/app/api/positions/actions.ts index 8826cc7..3caf1ab 100644 --- a/src/app/api/positions/actions.ts +++ b/src/app/api/positions/actions.ts @@ -15,6 +15,8 @@ export interface combo { } export interface CreatePositionInputs { + positionCode: string; + positionName: string; code: string; name: string; description: string; diff --git a/src/app/api/user/actions.ts b/src/app/api/user/actions.ts index 4d353c3..fff3da4 100644 --- a/src/app/api/user/actions.ts +++ b/src/app/api/user/actions.ts @@ -11,6 +11,12 @@ export interface UserInputs { email: string; } +export interface PasswordInputs { + password: string; + newPassword: string; + newPasswordCheck: string; +} + export const fetchUserDetails = cache(async (id: number) => { return serverFetchJson(`${BASE_API_URL}/user/${id}`, { @@ -31,4 +37,12 @@ export const deleteUser = async (id: number) => { method: "DELETE", 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" }, + }); }; \ No newline at end of file diff --git a/src/components/AppBar/Profile.tsx b/src/components/AppBar/Profile.tsx index 8cb49cf..7b48190 100644 --- a/src/components/AppBar/Profile.tsx +++ b/src/components/AppBar/Profile.tsx @@ -10,6 +10,7 @@ import Divider from "@mui/material/Divider"; import Typography from "@mui/material/Typography"; import { useTranslation } from "react-i18next"; import { signOut } from "next-auth/react"; +import { useRouter } from "next/navigation"; type Props = Pick; @@ -26,6 +27,7 @@ const Profile: React.FC = ({ avatarImageSrc, profileName }) => { }; const { t } = useTranslation("login"); + const router = useRouter(); return ( <> @@ -52,6 +54,7 @@ const Profile: React.FC = ({ avatarImageSrc, profileName }) => { {profileName} + {router.replace("/settings/changepassword")}}>{t("Change Password")} signOut()}>{t("Sign out")} diff --git a/src/components/CreateStaff/CreateStaff.tsx b/src/components/CreateStaff/CreateStaff.tsx index 94e7f71..00fa507 100644 --- a/src/components/CreateStaff/CreateStaff.tsx +++ b/src/components/CreateStaff/CreateStaff.tsx @@ -22,7 +22,6 @@ import { fetchSkillCombo } from "@/app/api/skill/actions"; import { fetchSalaryCombo } from "@/app/api/salarys/actions"; interface Field { - // subtitle: string; id: string; label: string; type: string; @@ -33,12 +32,6 @@ interface Field { options?: any[]; readOnly?: boolean; } - -interface formProps { - Title?: string[]; - // fieldLists: Field[][]; -} - export interface comboItem { company: comboProp[]; team: comboProp[]; @@ -49,101 +42,14 @@ export interface comboItem { salary: comboProp[]; } -const CreateStaff: React.FC = ({ Title }) => { - // const router = useRouter(); - const { t } = useTranslation(); - const [companyCombo, setCompanyCombo] = useState(); - const [teamCombo, setTeamCombo] = useState(); - const [departmentCombo, setDepartmentCombo] = useState(); - const [positionCombo, setPositionCombo] = useState(); - const [gradeCombo, setGradeCombo] = useState(); - const [skillCombo, setSkillCombo] = useState(); - const [salaryCombo, setSalaryCombo] = useState(); - // 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 = ({ Title, combos }) => { + const { t } = useTranslation(); const fieldLists: Field[][] = [ [ @@ -163,49 +69,49 @@ const CreateStaff: React.FC = ({ Title }) => { id: "companyId", label: t("Company"), type: "combo-Obj", - options: companyCombo || [], + options: combos.company || [], required: true, }, { id: "teamId", label: t("Team"), type: "combo-Obj", - options: teamCombo || [], + options: combos.team || [], required: false, }, { id: "departmentId", label: t("Department"), type: "combo-Obj", - options: departmentCombo || [], + options: combos.department || [], required: true, }, { id: "gradeId", label: t("Grade"), type: "combo-Obj", - options: gradeCombo || [], + options: combos.grade || [], required: false, }, { id: "skillSetId", label: t("Skillset"), type: "multiSelect-Obj", - options: skillCombo || [], + options: combos.skill || [], required: false, }, { id: "currentPositionId", label: t("Current Position"), type: "combo-Obj", - options: positionCombo || [], + options: combos.position || [], required: true, }, { id: "salaryId", label: t("Salary Point"), type: "combo-Obj", - options: salaryCombo || [], + options: combos.salary || [], required: true, }, // { @@ -279,7 +185,7 @@ const CreateStaff: React.FC = ({ Title }) => { id: "joinPositionId", label: t("Join Position"), type: "combo-Obj", - options: positionCombo || [], + options: combos.position || [], required: true, }, { diff --git a/src/components/CreateStaff/CreateStaffWrapper.tsx b/src/components/CreateStaff/CreateStaffWrapper.tsx index c54ad87..825fc35 100644 --- a/src/components/CreateStaff/CreateStaffWrapper.tsx +++ b/src/components/CreateStaff/CreateStaffWrapper.tsx @@ -1,17 +1,48 @@ import React from "react"; -import CreateStaff from "./CreateStaff"; +import CreateStaff, { comboItem } from "./CreateStaff"; import CreateStaffLoading from "./CreateStaffLoading"; import { fetchStaff, fetchTeamLeads } from "@/app/api/staff"; 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 { Loading: typeof CreateStaffLoading; } 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 ; + return ; }; CreateStaffWrapper.Loading = CreateStaffLoading; diff --git a/src/components/EditStaffForm/EditStaffForm.tsx b/src/components/EditStaffForm/EditStaffForm.tsx index f15be32..96f8b1d 100644 --- a/src/components/EditStaffForm/EditStaffForm.tsx +++ b/src/components/EditStaffForm/EditStaffForm.tsx @@ -83,7 +83,7 @@ const EditStaffForm: React.FC = ({ Title, fieldLists }) => { }; return ( <> - {serverError && ( + {serverError && ( {serverError} diff --git a/src/components/EditUser/EditUser.tsx b/src/components/EditUser/EditUser.tsx new file mode 100644 index 0000000..db52fac --- /dev/null +++ b/src/components/EditUser/EditUser.tsx @@ -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 = async ({ }) => { + const { t } = useTranslation(); + const formProps = useForm(); + const searchParams = useSearchParams(); + const id = parseInt(searchParams.get("id") || "0"); + const [tabIndex, setTabIndex] = useState(0); + const router = useRouter(); + + const handleTabChange = useCallback>( + (_e, newValue) => { + setTabIndex(newValue); + }, + [] + ); + + const [serverError, setServerError] = useState(""); + const [data, setData] = useState(); + + 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 + ) => { + switch (tabIndex) { + case 0: + return Object.keys(errors).length > 0; + default: + false; + } + }; + + const handleCancel = () => { + router.back(); + }; + + const onSubmit = useCallback>( + 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>( + (errors) => { + console.log(errors); + }, + [] + ); + + return ( + <> + {serverError && ( + + {serverError} + + )} + + + + + + + + + + + + ); +}; +export default EditUser; diff --git a/src/components/EditUser/EditUserLoading.tsx b/src/components/EditUser/EditUserLoading.tsx new file mode 100644 index 0000000..971c9e4 --- /dev/null +++ b/src/components/EditUser/EditUserLoading.tsx @@ -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 ( + <> + + + + + + + + + + + EditUser + + + + + + + + + + + ); +}; + +export default EditUserLoading; diff --git a/src/components/EditUser/EditUserWrapper.tsx b/src/components/EditUser/EditUserWrapper.tsx new file mode 100644 index 0000000..eaf7aa5 --- /dev/null +++ b/src/components/EditUser/EditUserWrapper.tsx @@ -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 ; +}; + +EditUserWrapper.Loading = EditUserLoading; + +export default EditUserWrapper; diff --git a/src/components/EditUser/UserDetail.tsx b/src/components/EditUser/UserDetail.tsx new file mode 100644 index 0000000..ed7ad2a --- /dev/null +++ b/src/components/EditUser/UserDetail.tsx @@ -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 = ({ + data +}) => { + const { t } = useTranslation(); + const { + register, + formState: { errors }, + control, + } = useFormContext(); + + return ( + + + + {t("User Detail")} + + + + + + {/* + + + + + + + + + + + + + + + + + + + + + + + */} + + + + ); +}; + +export default UserDetail; diff --git a/src/components/EditUser/index.ts b/src/components/EditUser/index.ts new file mode 100644 index 0000000..c12dc8e --- /dev/null +++ b/src/components/EditUser/index.ts @@ -0,0 +1 @@ +export { default } from "./EditUserWrapper"; diff --git a/src/components/EditUserGroup/EditUserGroupWrapper.tsx b/src/components/EditUserGroup/EditUserGroupWrapper.tsx index 84f7501..dffd5e7 100644 --- a/src/components/EditUserGroup/EditUserGroupWrapper.tsx +++ b/src/components/EditUserGroup/EditUserGroupWrapper.tsx @@ -14,14 +14,11 @@ const EditUserGroupWrapper: React.FC & SubComponents = async () => { const [ groups, - // auths, users, ] = await Promise.all([ fetchGroup(), - // fetchAuth(), fetchUser(), ]); - console.log(users) return ; }; diff --git a/src/components/StaffSearch/StaffSearch.tsx b/src/components/StaffSearch/StaffSearch.tsx index 4111d14..5bfb017 100644 --- a/src/components/StaffSearch/StaffSearch.tsx +++ b/src/components/StaffSearch/StaffSearch.tsx @@ -9,6 +9,7 @@ import DeleteIcon from "@mui/icons-material/Delete"; 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'; interface Props { staff: StaffResult[]; @@ -65,6 +66,14 @@ const StaffSearch: React.FC = ({ staff }) => { [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) => { deleteDialog(async () => { await deleteStaff(staff.id); @@ -81,6 +90,12 @@ const StaffSearch: React.FC = ({ staff }) => { onClick: onStaffClick, buttonIcon: , }, + { + name: "id", + label: t("Actions"), + onClick: onUserClick, + buttonIcon: , + }, { name: "team", label: t("Team") }, { name: "name", label: t("Staff Name") }, { name: "staffId", label: t("Staff ID") }, diff --git a/src/middleware.ts b/src/middleware.ts index 078ace6..a793a79 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -71,9 +71,6 @@ export default async function middleware( return response; } - // const session = await getServerSession(authOptions); - // console.log(session); - let abilities: string[] = [] if (token) { 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')) { 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')) { 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 return PRIVATE_ROUTES.some((route) => req.nextUrl.pathname.startsWith(route)) ? await authMiddleware(req, event) // Let auth middleware handle response