From afe7cac746466dbcd2484f1aa6d3bad331e72354 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Mon, 29 Apr 2024 12:19:37 +0800 Subject: [PATCH] master pages --- src/app/(main)/settings/skill/create/page.tsx | 48 +++++ src/app/(main)/settings/skill/page.tsx | 50 +++++ src/app/(main)/settings/user/page.tsx | 54 ++++++ src/app/api/skill/actions.ts | 18 +- src/app/api/skill/index.ts | 22 +++ src/app/api/staff/actions.ts | 6 +- src/app/api/team/actions.ts | 7 +- src/app/api/user/actions.ts | 27 +++ src/app/api/user/index.ts | 43 +++++ src/components/CreateSkill/CreateSkill.tsx | 122 ++++++++++++ .../CreateSkill/CreateSkillLoading.tsx | 40 ++++ .../CreateSkill/CreateSkillWrapper.tsx | 19 ++ src/components/CreateSkill/SkillInfo.tsx | 90 +++++++++ src/components/CreateSkill/index.ts | 1 + src/components/CreateTeam/CreateTeam.tsx | 2 +- src/components/CreateTeam/StaffAllocation.tsx | 174 ++++++++++-------- .../NavigationContent/NavigationContent.tsx | 4 + src/components/SkillSearch/SkillSearch.tsx | 96 ++++++++++ .../SkillSearch/SkillSearchLoading.tsx | 40 ++++ .../SkillSearch/SkillSearchWrapper.tsx | 27 +++ src/components/SkillSearch/index.ts | 1 + .../StaffSearch/ConfirmDeleteModal.tsx | 106 ----------- src/components/StaffSearch/StaffSearch.tsx | 79 +++----- .../TeamSearch/ConfirmDeleteModal.tsx | 105 ----------- src/components/TeamSearch/TeamSearch.tsx | 169 ++++++++--------- src/components/UserSearch/UserSearch.tsx | 98 ++++++++++ .../UserSearch/UserSearchLoading.tsx | 40 ++++ .../UserSearch/UserSearchWrapper.tsx | 19 ++ src/components/UserSearch/index.ts | 1 + 29 files changed, 1062 insertions(+), 446 deletions(-) create mode 100644 src/app/(main)/settings/skill/create/page.tsx create mode 100644 src/app/(main)/settings/skill/page.tsx create mode 100644 src/app/(main)/settings/user/page.tsx create mode 100644 src/app/api/skill/index.ts create mode 100644 src/app/api/user/actions.ts create mode 100644 src/app/api/user/index.ts create mode 100644 src/components/CreateSkill/CreateSkill.tsx create mode 100644 src/components/CreateSkill/CreateSkillLoading.tsx create mode 100644 src/components/CreateSkill/CreateSkillWrapper.tsx create mode 100644 src/components/CreateSkill/SkillInfo.tsx create mode 100644 src/components/CreateSkill/index.ts create mode 100644 src/components/SkillSearch/SkillSearch.tsx create mode 100644 src/components/SkillSearch/SkillSearchLoading.tsx create mode 100644 src/components/SkillSearch/SkillSearchWrapper.tsx create mode 100644 src/components/SkillSearch/index.ts delete mode 100644 src/components/StaffSearch/ConfirmDeleteModal.tsx delete mode 100644 src/components/TeamSearch/ConfirmDeleteModal.tsx create mode 100644 src/components/UserSearch/UserSearch.tsx create mode 100644 src/components/UserSearch/UserSearchLoading.tsx create mode 100644 src/components/UserSearch/UserSearchWrapper.tsx create mode 100644 src/components/UserSearch/index.ts diff --git a/src/app/(main)/settings/skill/create/page.tsx b/src/app/(main)/settings/skill/create/page.tsx new file mode 100644 index 0000000..c98f993 --- /dev/null +++ b/src/app/(main)/settings/skill/create/page.tsx @@ -0,0 +1,48 @@ +// 'use client'; +import { I18nProvider, getServerI18n } from "@/i18n"; +import CustomInputForm from "@/components/CustomInputForm"; +import Check from "@mui/icons-material/Check"; +import Close from "@mui/icons-material/Close"; +import Button from "@mui/material/Button"; +import Stack from "@mui/material/Stack"; +import Tab from "@mui/material/Tab"; +import Tabs, { TabsProps } from "@mui/material/Tabs"; +import { useRouter } from "next/navigation"; +import React, { useCallback, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Task, TaskTemplate } from "@/app/api/tasks"; +import { + FieldErrors, + FormProvider, + SubmitErrorHandler, + SubmitHandler, + useForm, +} from "react-hook-form"; +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 CreateSkill from "@/components/CreateSkill"; + +// const Title = ["title1", "title2"]; + +const CreateStaff: 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 Skill")} + + + + + ); +}; + +export default CreateStaff; diff --git a/src/app/(main)/settings/skill/page.tsx b/src/app/(main)/settings/skill/page.tsx new file mode 100644 index 0000000..f263c87 --- /dev/null +++ b/src/app/(main)/settings/skill/page.tsx @@ -0,0 +1,50 @@ +import { preloadClaims } from "@/app/api/claims"; +// import { preloadSkill, preloadTeamLeads } from "@/app/api/staff"; +import SkillSearch from "@/components/SkillSearch"; +import { I18nProvider, getServerI18n } from "@/i18n"; +import Add from "@mui/icons-material/Add"; +import Button from "@mui/material/Button"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import { Metadata } from "next"; +import Link from "next/link"; +import { Suspense } from "react"; + +export const metadata: Metadata = { + title: "Skill", +}; + +const Skill: React.FC = async () => { + const { t } = await getServerI18n("skill"); +// preloadTeamLeads(); +// preloadSkill(); + return ( + <> + + + {t("Skill")} + + + + + }> + + + + + ); +}; + +export default Skill; diff --git a/src/app/(main)/settings/user/page.tsx b/src/app/(main)/settings/user/page.tsx new file mode 100644 index 0000000..95973ab --- /dev/null +++ b/src/app/(main)/settings/user/page.tsx @@ -0,0 +1,54 @@ +import { preloadClaims } from "@/app/api/claims"; +import { preloadStaff, preloadTeamLeads } from "@/app/api/staff"; +import StaffSearch from "@/components/StaffSearch"; +import TeamSearch from "@/components/TeamSearch"; +import UserSearch from "@/components/UserSearch"; +import { I18nProvider, getServerI18n } from "@/i18n"; +import Add from "@mui/icons-material/Add"; +import Button from "@mui/material/Button"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import { Metadata } from "next"; +import Link from "next/link"; +import { Suspense } from "react"; + + +export const metadata: Metadata = { + title: "User", + }; + + + const User: React.FC = async () => { + const { t } = await getServerI18n("User"); + // preloadTeamLeads(); + // preloadStaff(); + return ( + <> + + + {t("User")} + + + + + }> + + + + + ); + }; + + export default User; \ No newline at end of file diff --git a/src/app/api/skill/actions.ts b/src/app/api/skill/actions.ts index eda7f39..6a0deca 100644 --- a/src/app/api/skill/actions.ts +++ b/src/app/api/skill/actions.ts @@ -5,6 +5,13 @@ import { serverFetchJson } from "@/app/utils/fetchUtil"; import { BASE_API_URL } from "@/config/api"; import { cache } from "react"; +export interface CreateSkillInputs { + id?: number; + name: String; + code: String; + description: String; +} + export interface comboProp { id: any; label: string; @@ -18,4 +25,13 @@ export const fetchSkillCombo = cache(async () => { return serverFetchJson(`${BASE_API_URL}/skill/combo`, { next: { tags: ["skill"] }, }); - }); \ No newline at end of file + }); + + +export const saveSkill = async (data: CreateSkillInputs) => { + return serverFetchJson(`${BASE_API_URL}/skill/save`, { + method: "POST", + body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + }); + }; \ No newline at end of file diff --git a/src/app/api/skill/index.ts b/src/app/api/skill/index.ts new file mode 100644 index 0000000..cf6ebec --- /dev/null +++ b/src/app/api/skill/index.ts @@ -0,0 +1,22 @@ +import { serverFetchJson } from "@/app/utils/fetchUtil"; +import { BASE_API_URL } from "@/config/api"; +import { cache } from "react"; +import "server-only"; + +export interface SkillResult { + action: any; + id: number; + name: string; + description: string; + code: string; + } + + export const preloadSkill = () => { + fetchSkill(); + }; + + export const fetchSkill = cache(async () => { + return serverFetchJson(`${BASE_API_URL}/skill`, { + next: { tags: ["sill"] }, + }); + }); \ No newline at end of file diff --git a/src/app/api/staff/actions.ts b/src/app/api/staff/actions.ts index 9416d2d..a2235d6 100644 --- a/src/app/api/staff/actions.ts +++ b/src/app/api/staff/actions.ts @@ -1,5 +1,5 @@ "use server"; -import { serverFetchJson } from "@/app/utils/fetchUtil"; +import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; import { BASE_API_URL } from "@/config/api"; import { StaffResult, data } from "."; import { cache } from "react"; @@ -59,8 +59,8 @@ export const testing = async (data: CreateStaffInputs) => { }); }; -export const deleteStaff = async (data: StaffResult) => { - return serverFetchJson(`${BASE_API_URL}/staffs/delete/${data.id}`, { +export const deleteStaff = async (id: number) => { + return serverFetchWithNoContent(`${BASE_API_URL}/staffs/delete/${id}`, { method: "DELETE", // body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, diff --git a/src/app/api/team/actions.ts b/src/app/api/team/actions.ts index 28496d0..47e1a82 100644 --- a/src/app/api/team/actions.ts +++ b/src/app/api/team/actions.ts @@ -1,5 +1,5 @@ "use server"; -import { serverFetchJson } from "@/app/utils/fetchUtil"; +import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; import { BASE_API_URL } from "@/config/api"; import { cache } from "react"; import { TeamResult } from "."; @@ -53,10 +53,9 @@ export const saveTeam = async (data: CreateTeamInputs) => { }; -export const deleteTeam = async (data: TeamResult) => { - return serverFetchJson(`${BASE_API_URL}/team/delete/${data.id}`, { +export const deleteTeam = async (id: number) => { + return serverFetchWithNoContent(`${BASE_API_URL}/team/delete/${id}`, { method: "DELETE", - // body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, }); }; diff --git a/src/app/api/user/actions.ts b/src/app/api/user/actions.ts new file mode 100644 index 0000000..5df734a --- /dev/null +++ b/src/app/api/user/actions.ts @@ -0,0 +1,27 @@ +"use server"; + +import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; +import { BASE_API_URL } from "@/config/api"; +import { revalidateTag } from "next/cache"; +import { UserDetail, UserResult } from "."; +import { cache } from "react"; + +export interface UserInputs { + username: string; + firstname: string; + lastname: string; +} + + +export const fetchUserDetails = cache(async (id: number) => { + return serverFetchJson(`${BASE_API_URL}/user/${id}`, { + next: { tags: ["user"] }, + }); + }); + +export const deleteUser = async (id: number) => { + return serverFetchWithNoContent(`${BASE_API_URL}/user/${id}`, { + method: "DELETE", + headers: { "Content-Type": "application/json" }, + }); + }; \ No newline at end of file diff --git a/src/app/api/user/index.ts b/src/app/api/user/index.ts new file mode 100644 index 0000000..9a6065b --- /dev/null +++ b/src/app/api/user/index.ts @@ -0,0 +1,43 @@ +import { serverFetchJson } from "@/app/utils/fetchUtil"; +import { BASE_API_URL } from "@/config/api"; +import { cache } from "react"; +import "server-only"; + + +export interface UserResult { + action: any; + id: number; + name: string; + locale: string; + username: string; + fullName: string; + firstname: string; + lastname: string; + title: string; + department: string; + email: string; + phone1: string; + phone2: string; + remarks: string; + } + +// export interface DetailedUser extends UserResult { +// username: string; +// password: string +// } + +export interface UserDetail { + authIds: number[]; + data: UserResult; + groupIds: number[]; + } + + export const preloadUser = () => { + fetchUser(); + }; + + export const fetchUser = cache(async () => { + return serverFetchJson(`${BASE_API_URL}/user`, { + next: { tags: ["user"] }, + }); + }); \ No newline at end of file diff --git a/src/components/CreateSkill/CreateSkill.tsx b/src/components/CreateSkill/CreateSkill.tsx new file mode 100644 index 0000000..d264b34 --- /dev/null +++ b/src/components/CreateSkill/CreateSkill.tsx @@ -0,0 +1,122 @@ +"use client"; + +import { + FieldErrors, + FormProvider, + SubmitErrorHandler, + SubmitHandler, + useForm, +} from "react-hook-form"; +import { Button, Stack, Tab, Tabs, TabsProps, Typography } from "@mui/material"; +import { Check, Close, RestartAlt } from "@mui/icons-material"; +import { useCallback, useState } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useTranslation } from "react-i18next"; +import { CreateSkillInputs, saveSkill } from "@/app/api/skill/actions"; +import { Error } from "@mui/icons-material"; +import SkillInfo from "./SkillInfo"; + +interface Props {} + +const CreateSkill: React.FC = () => { + const formProps = useForm(); + const [serverError, setServerError] = useState(""); + const router = useRouter(); + const { t } = useTranslation(); + const [tabIndex, setTabIndex] = useState(0); + const errors = formProps.formState.errors; + + const onSubmit = useCallback>( + async (data) => { + try { + console.log(data); + await saveSkill(data) + 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 handleReset = useCallback(() => { +// console.log(defaultValues) +// }, [defaultValues]) + + const handleTabChange = useCallback>( + (_e, newValue) => { + setTabIndex(newValue); + }, + [] + ); + + const hasErrorsInTab = ( + tabIndex: number, + errors: FieldErrors + ) => { + switch (tabIndex) { + case 0: + return Object.keys(errors).length > 0; + default: + false; + } + }; + return ( + <> + + + + + ) : undefined + } + iconPosition="end" + /> + {/* */} + + {serverError && ( + + {serverError} + + )} + {tabIndex === 0 && } + + + + + + + + ); +}; + +export default CreateSkill; diff --git a/src/components/CreateSkill/CreateSkillLoading.tsx b/src/components/CreateSkill/CreateSkillLoading.tsx new file mode 100644 index 0000000..f7d17bf --- /dev/null +++ b/src/components/CreateSkill/CreateSkillLoading.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 CreateSkillLoading: React.FC = () => { + return ( + <> + + + + + + + + + + + CreateSkill + + + + + + + + + + + ); +}; + +export default CreateSkillLoading; diff --git a/src/components/CreateSkill/CreateSkillWrapper.tsx b/src/components/CreateSkill/CreateSkillWrapper.tsx new file mode 100644 index 0000000..f2f667a --- /dev/null +++ b/src/components/CreateSkill/CreateSkillWrapper.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import CreateSkill from "./CreateSkill"; +import CreateSkillLoading from "./CreateSkillLoading"; +import { fetchStaff, fetchTeamLeads } from "@/app/api/staff"; +import { useSearchParams } from "next/navigation"; + +interface SubComponents { + Loading: typeof CreateSkillLoading; +} + +const CreateSkillWrapper: React.FC & SubComponents = async () => { + + + return ; +}; + +CreateSkillWrapper.Loading = CreateSkillLoading; + +export default CreateSkillWrapper; diff --git a/src/components/CreateSkill/SkillInfo.tsx b/src/components/CreateSkill/SkillInfo.tsx new file mode 100644 index 0000000..be9724d --- /dev/null +++ b/src/components/CreateSkill/SkillInfo.tsx @@ -0,0 +1,90 @@ +"use client"; +import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import Grid from "@mui/material/Grid"; +import TextField from "@mui/material/TextField"; +import Typography from "@mui/material/Typography"; +import { useTranslation } from "react-i18next"; +import CardActions from "@mui/material/CardActions"; +import RestartAlt from "@mui/icons-material/RestartAlt"; +import Button from "@mui/material/Button"; +import { Controller, useFormContext } from "react-hook-form"; +import { FormControl, InputLabel, MenuItem, Select } from "@mui/material"; +import { useCallback } from "react"; +import { CreateSkillInputs } from "@/app/api/skill/actions"; + +const SkillInfo: React.FC = ( +) => { + const { t } = useTranslation(); + const { + register, + formState: { errors, defaultValues }, + control, + reset, + resetField, + setValue, + } = useFormContext(); + + const resetSkill = useCallback(() => { + console.log(defaultValues); + if (defaultValues !== undefined) { + resetField("name"); + } + }, [defaultValues]); + + return ( + <> + + + + + {t("Skill Info")} + + + + + + + + + + + + + + + + + ); +}; +export default SkillInfo; diff --git a/src/components/CreateSkill/index.ts b/src/components/CreateSkill/index.ts new file mode 100644 index 0000000..044c4cf --- /dev/null +++ b/src/components/CreateSkill/index.ts @@ -0,0 +1 @@ +export { default } from "./CreateSkillWrapper"; diff --git a/src/components/CreateTeam/CreateTeam.tsx b/src/components/CreateTeam/CreateTeam.tsx index 93b585e..64159c0 100644 --- a/src/components/CreateTeam/CreateTeam.tsx +++ b/src/components/CreateTeam/CreateTeam.tsx @@ -89,7 +89,7 @@ const hasErrorsInTab = ( } iconPosition="end" /> - + {serverError && ( diff --git a/src/components/CreateTeam/StaffAllocation.tsx b/src/components/CreateTeam/StaffAllocation.tsx index c51b839..bbd768c 100644 --- a/src/components/CreateTeam/StaffAllocation.tsx +++ b/src/components/CreateTeam/StaffAllocation.tsx @@ -18,9 +18,21 @@ import { StaffResult } from "@/app/api/staff"; import SearchResults, { Column } from "../SearchResults"; import { Clear, PersonAdd, PersonRemove, Search } from "@mui/icons-material"; import { Card } from "reactstrap"; -import { Box, CardContent, Grid, IconButton, InputAdornment, Stack, Tab, Tabs, TabsProps, TextField, Typography } from "@mui/material"; +import { + Box, + CardContent, + Grid, + IconButton, + InputAdornment, + Stack, + Tab, + Tabs, + TabsProps, + TextField, + Typography, +} from "@mui/material"; import { differenceBy } from "lodash"; -import StarsIcon from '@mui/icons-material/Stars'; +import StarsIcon from "@mui/icons-material/Stars"; export interface Props { allStaffs: StaffResult[]; @@ -35,16 +47,15 @@ const StaffAllocation: React.FC = ({ allStaffs: staff }) => { reset, resetField, } = useFormContext(); - + const initialStaffs = staff.map((s) => ({ ...s })); -// console.log(initialStaffs) + // console.log(initialStaffs) const [filteredStaff, setFilteredStaff] = useState(initialStaffs); const [selectedStaff, setSelectedStaff] = useState( initialStaffs.filter((s) => getValues("addStaffIds")?.includes(s.id)) ); - const [seletedTeamLead, setSeletedTeamLead] = useState() - // Adding / Removing staff + // Adding / Removing staff const addStaff = useCallback((staff: StaffResult) => { setSelectedStaff((s) => [...s, staff]); }, []); @@ -53,27 +64,31 @@ const StaffAllocation: React.FC = ({ allStaffs: staff }) => { setSelectedStaff((s) => s.filter((s) => s.id !== staff.id)); }, []); - const setTeamLead = useCallback((staff: StaffResult) => { - - setSeletedTeamLead(staff.id) - const rearrangedList = getValues("addStaffIds").reduce((acc, num, index) => { - if (num === staff.id && index !== 0) { + const setTeamLead = useCallback( + (staff: StaffResult) => { + const rearrangedList = getValues("addStaffIds").reduce( + (acc, num, index) => { + if (num === staff.id && index !== 0) { acc.splice(index, 1); - acc.unshift(num) - } - return acc; - }, getValues("addStaffIds")); - console.log(rearrangedList) - console.log(selectedStaff) - - const rearrangedStaff = rearrangedList.map((id) => { + acc.unshift(num); + } + return acc; + }, + getValues("addStaffIds") + ); + console.log(rearrangedList); + console.log(selectedStaff); + + const rearrangedStaff = rearrangedList.map((id) => { return selectedStaff.find((staff) => staff.id === id); }); - console.log(rearrangedStaff) - setSelectedStaff(rearrangedStaff as StaffResult[]); + console.log(rearrangedStaff); + setSelectedStaff(rearrangedStaff as StaffResult[]); - setValue("addStaffIds", rearrangedList) - }, [addStaff, selectedStaff]); + setValue("addStaffIds", rearrangedList); + }, + [addStaff, selectedStaff] + ); const clearSubsidiary = useCallback(() => { if (defaultValues !== undefined) { @@ -86,7 +101,7 @@ const StaffAllocation: React.FC = ({ allStaffs: staff }) => { // Sync with form useEffect(() => { - console.log(selectedStaff) + console.log(selectedStaff); setValue( "addStaffIds", selectedStaff.map((s) => s.id) @@ -94,7 +109,7 @@ const StaffAllocation: React.FC = ({ allStaffs: staff }) => { }, [selectedStaff, setValue]); useEffect(() => { - console.log(selectedStaff) + console.log(selectedStaff); }, [selectedStaff]); const StaffPoolColumns = useMemo[]>( @@ -107,7 +122,7 @@ const StaffAllocation: React.FC = ({ allStaffs: staff }) => { }, { label: t("Staff Id"), name: "staffId" }, { label: t("Staff Name"), name: "name" }, - { label: t("Current Position"), name: "currentPosition" }, + { label: t("Position"), name: "currentPosition" }, ], [addStaff, t] ); @@ -122,7 +137,7 @@ const StaffAllocation: React.FC = ({ allStaffs: staff }) => { }, { label: t("Staff Id"), name: "staffId" }, { label: t("Staff Name"), name: "name" }, - { label: t("Current Position"), name: "currentPosition" }, + { label: t("Position"), name: "currentPosition" }, { label: t("Team Lead"), name: "action", @@ -144,16 +159,16 @@ const StaffAllocation: React.FC = ({ allStaffs: staff }) => { }, []); 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)) - // }) - // ); + setFilteredStaff( + initialStaffs.filter((i) => { + const q = query.toLowerCase(); + return ( + i.staffId.toLowerCase().includes(q) || + i.name.toLowerCase().includes(q) || + i.currentPosition.toLowerCase().includes(q) + ); + }) + ); }, [staff, query]); const resetStaff = React.useCallback(() => { @@ -161,8 +176,7 @@ const StaffAllocation: React.FC = ({ allStaffs: staff }) => { clearSubsidiary(); }, [clearQueryInput, clearSubsidiary]); - const formProps = useForm({ - }); + const formProps = useForm({}); // Tab related const [tabIndex, setTabIndex] = React.useState(0); @@ -170,7 +184,7 @@ const StaffAllocation: React.FC = ({ allStaffs: staff }) => { (_e, newValue) => { setTabIndex(newValue); }, - [], + [] ); return ( @@ -185,48 +199,48 @@ const StaffAllocation: React.FC = ({ allStaffs: staff }) => { {t("staff")} - - - - - - - - ), - }} - /> + + + + + + + + ), + }} + /> + - - - - - - - {tabIndex === 0 && ( - - )} - {tabIndex === 1 && ( - + + - )} - + + + {tabIndex === 0 && ( + + )} + {tabIndex === 1 && ( + + )} + diff --git a/src/components/NavigationContent/NavigationContent.tsx b/src/components/NavigationContent/NavigationContent.tsx index ad68823..9016052 100644 --- a/src/components/NavigationContent/NavigationContent.tsx +++ b/src/components/NavigationContent/NavigationContent.tsx @@ -31,6 +31,8 @@ import { NAVIGATION_CONTENT_WIDTH } from "@/config/uiConfig"; import Logo from "../Logo"; import GroupIcon from '@mui/icons-material/Group'; import BusinessIcon from '@mui/icons-material/Business'; +import ManageAccountsIcon from '@mui/icons-material/ManageAccounts'; +import EmojiEventsIcon from '@mui/icons-material/EmojiEvents'; interface NavigationItem { icon: React.ReactNode; @@ -117,10 +119,12 @@ const navigationItems: NavigationItem[] = [ { icon: , label: "Subsidiary", path: "/settings/subsidiary" }, { icon: , label: "Staff", path: "/settings/staff" }, { icon: , label: "Company", path: "/settings/company" }, + { icon: , label: "Skill", path: "/settings/skill" }, { icon: , label: "Department", path: "/settings/department" }, { icon: , label: "Position", path: "/settings/position" }, { icon: , label: "Salary", path: "/settings/salary" }, { icon: , label: "Team", path: "/settings/team" }, + { icon: , label: "User", path: "/settings/user" }, ], }, ]; diff --git a/src/components/SkillSearch/SkillSearch.tsx b/src/components/SkillSearch/SkillSearch.tsx new file mode 100644 index 0000000..01db336 --- /dev/null +++ b/src/components/SkillSearch/SkillSearch.tsx @@ -0,0 +1,96 @@ +"use client"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import SearchBox, { Criterion } from "../SearchBox/index"; +import { useTranslation } from "react-i18next"; +import SearchResults, { Column } from "../SearchResults/index"; +import EditNote from "@mui/icons-material/EditNote"; +import DeleteIcon from "@mui/icons-material/Delete"; +import { useRouter } from "next/navigation"; +import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; +import { SkillResult } from "@/app/api/skill"; + +interface Props { + skill: SkillResult[]; +} + +type SearchQuery = Partial>; +type SearchParamNames = keyof SearchQuery; + +const SkillSearch: React.FC = ({ skill }) => { + const { t } = useTranslation(); + const [filteredStaff, setFilteredStaff] = useState(skill); + const router = useRouter(); + + const searchCriteria: Criterion[] = useMemo( + () => [ + { + label: t("Staff Name"), + paramName: "name", + type: "text", + }, + ], + [t] + ); + + const onSkillClick = useCallback( + (skill: SkillResult) => { + console.log(skill); + const id = skill.id; + // router.push(`/settings/skill/edit?id=${id}`); + }, + [router, t] + ); + + const deleteClick = useCallback((skill: SkillResult) => { + // deleteDialog(async () => { + // await deleteStaff(skill.id); + // successDialog("Delete Success", t); + // setFilteredStaff((prev) => prev.filter((obj) => obj.id !== skill.id)); + // }, t); + }, []); + + const columns = useMemo[]>( + () => [ + { + name: "action", + label: t("Actions"), + onClick: onSkillClick, + buttonIcon: , + }, + { name: "name", label: t("Name") }, + { name: "code", label: t("Code") }, + { name: "description", label: t("Description") }, + { + name: "action", + label: t("Actions"), + onClick: deleteClick, + buttonIcon: , + color: "error", + }, + ], + [t, onSkillClick, deleteClick] + ); + + return ( + <> + { + // 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), + // ) + // ); + }} + /> + items={filteredStaff} columns={columns} /> + + ); +}; + +export default SkillSearch; diff --git a/src/components/SkillSearch/SkillSearchLoading.tsx b/src/components/SkillSearch/SkillSearchLoading.tsx new file mode 100644 index 0000000..a5959e9 --- /dev/null +++ b/src/components/SkillSearch/SkillSearchLoading.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 SkillSearchLoading: React.FC = () => { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default SkillSearchLoading; diff --git a/src/components/SkillSearch/SkillSearchWrapper.tsx b/src/components/SkillSearch/SkillSearchWrapper.tsx new file mode 100644 index 0000000..33d0547 --- /dev/null +++ b/src/components/SkillSearch/SkillSearchWrapper.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import SkillSearch from "./SkillSearch"; +import SkillSearchLoading from "./SkillSearchLoading"; +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 { SkillResult, fetchSkill } from "@/app/api/skill"; +// import { preloadStaff } from "@/app/api/staff"; + +interface SubComponents { + Loading: typeof SkillSearchLoading; +} + +const SkillSearchWrapper: React.FC & SubComponents = async () => { + const skill = await fetchSkill() + console.log(skill); + + return ; +}; + +SkillSearchWrapper.Loading = SkillSearchLoading; + +export default SkillSearchWrapper; diff --git a/src/components/SkillSearch/index.ts b/src/components/SkillSearch/index.ts new file mode 100644 index 0000000..5833a58 --- /dev/null +++ b/src/components/SkillSearch/index.ts @@ -0,0 +1 @@ +export { default } from "./SkillSearchWrapper"; diff --git a/src/components/StaffSearch/ConfirmDeleteModal.tsx b/src/components/StaffSearch/ConfirmDeleteModal.tsx deleted file mode 100644 index abeb962..0000000 --- a/src/components/StaffSearch/ConfirmDeleteModal.tsx +++ /dev/null @@ -1,106 +0,0 @@ -"use client"; -import React, { useCallback, useMemo, useState } from "react"; -import Button from "@mui/material/Button"; -import { Card, Modal, Stack, Typography } from "@mui/material"; -import { useTranslation } from "react-i18next"; -import { Add } from "@mui/icons-material"; -import Check from "@mui/icons-material/Check"; -import Close from "@mui/icons-material/Close"; -import { TSMS_BUTTON_THEME } from "@/theme/colorConst"; -import { ThemeProvider } from "@emotion/react"; - -interface Props { - isOpen: boolean; - onConfirm: (data: any) => void; - onCancel: (data: any | null) => void; - // staff: StaffResult[]; -} - -const ConfirmModal: React.FC = ({ ...props }) => { - const { t } = useTranslation(); - return ( - <> - - - <> - - {t("Confirm")} - - <> - - {t("Are You Sure")} - - - {/* */} - - - - - {/* */} - - - - - ); -}; - -export default ConfirmModal; diff --git a/src/components/StaffSearch/StaffSearch.tsx b/src/components/StaffSearch/StaffSearch.tsx index e65cfe7..fc6204d 100644 --- a/src/components/StaffSearch/StaffSearch.tsx +++ b/src/components/StaffSearch/StaffSearch.tsx @@ -5,15 +5,11 @@ import SearchBox, { Criterion } from "../SearchBox/index"; import { useTranslation } from "react-i18next"; import SearchResults, { Column } from "../SearchResults/index"; import EditNote from "@mui/icons-material/EditNote"; -import DeleteIcon from '@mui/icons-material/Delete'; -import ConfirmModal from "./ConfirmDeleteModal"; +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"; -interface combo { - id: any; - label: string; -} interface Props { staff: StaffResult[]; } @@ -24,8 +20,6 @@ type SearchParamNames = keyof SearchQuery; const StaffSearch: React.FC = ({ staff }) => { const { t } = useTranslation(); const [filteredStaff, setFilteredStaff] = useState(staff); - const [data, setData] = useState(); - const [isOpen, setIsOpen] = useState(false); const router = useRouter(); const searchCriteria: Criterion[] = useMemo( @@ -41,10 +35,10 @@ const StaffSearch: React.FC = ({ staff }) => { paramName: "name", type: "text", }, - { - label: t("Staff ID"), - paramName: "staffId", - type: "text" + { + label: t("Staff ID"), + paramName: "staffId", + type: "text", }, { label: t("Grade"), @@ -59,39 +53,26 @@ const StaffSearch: React.FC = ({ staff }) => { options: ["pos1", "CEO"], }, ], - [t], + [t] ); - const onStaffClick = useCallback((staff: StaffResult) => { - console.log(staff); - const id = staff.id - router.push(`/settings/staff/edit?id=${id}`); - }, [router, t]); - - const deleteClick = (staff: StaffResult) => { - console.log(staff); - setData(staff) - setIsOpen(!isOpen) - }; - - const onConfirm = useCallback(async (staff: StaffResult) => { - console.log(staff); - if (data) - await deleteStaff(data) - setIsOpen(false) - window.location.reload; - }, [deleteStaff, data]); + const onStaffClick = useCallback( + (staff: StaffResult) => { + console.log(staff); + const id = staff.id; + router.push(`/settings/staff/edit?id=${id}`); + }, + [router, t] + ); - const onCancel = useCallback((staff: StaffResult) => { - console.log(staff); - setIsOpen(false) + const deleteClick = useCallback((staff: StaffResult) => { + deleteDialog(async () => { + await deleteStaff(staff.id); + successDialog("Delete Success", t); + setFilteredStaff((prev) => prev.filter((obj) => obj.id !== staff.id)); + }, t); }, []); - // useEffect(() => { - // console.log("id"); - // console.log(id); - // }, [id]); - const columns = useMemo[]>( () => [ { @@ -110,34 +91,30 @@ const StaffSearch: React.FC = ({ staff }) => { label: t("Actions"), onClick: deleteClick, buttonIcon: , + color: "error", }, ], - [t, onStaffClick, deleteClick], + [t, onStaffClick, deleteClick] ); return ( <> { + onSearch={(query) => { setFilteredStaff( staff.filter( - (s) => - s.staffId.toLowerCase().includes(query.staffId.toLowerCase()) && - s.name.toLowerCase().includes(query.name.toLowerCase()) + (s) => + s.staffId.toLowerCase().includes(query.staffId.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), ) - ) + ); }} /> items={filteredStaff} columns={columns} /> - ); }; diff --git a/src/components/TeamSearch/ConfirmDeleteModal.tsx b/src/components/TeamSearch/ConfirmDeleteModal.tsx deleted file mode 100644 index a5e7ed0..0000000 --- a/src/components/TeamSearch/ConfirmDeleteModal.tsx +++ /dev/null @@ -1,105 +0,0 @@ -"use client"; -import React, { useCallback, useMemo, useState } from "react"; -import Button from "@mui/material/Button"; -import { Card, Modal, Stack, Typography } from "@mui/material"; -import { useTranslation } from "react-i18next"; -import { Add } from "@mui/icons-material"; -import Check from "@mui/icons-material/Check"; -import Close from "@mui/icons-material/Close"; -import { TSMS_BUTTON_THEME } from "@/theme/colorConst"; -import { ThemeProvider } from "@emotion/react"; - -interface Props { - isOpen: boolean; - onConfirm: (data: any) => void; - onCancel: (data: any | null) => void; -} - -const ConfirmModal: React.FC = ({ ...props }) => { - const { t } = useTranslation(); - return ( - <> - - - <> - - {t("Confirm")} - - <> - - {t("Are You Sure")} - - - {/* */} - - - - - {/* */} - - - - - ); -}; - -export default ConfirmModal; diff --git a/src/components/TeamSearch/TeamSearch.tsx b/src/components/TeamSearch/TeamSearch.tsx index b2cc9e8..a1db872 100644 --- a/src/components/TeamSearch/TeamSearch.tsx +++ b/src/components/TeamSearch/TeamSearch.tsx @@ -6,12 +6,10 @@ import { useCallback, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import SearchResults, { Column } from "../SearchResults/index"; import EditNote from "@mui/icons-material/EditNote"; -import DeleteIcon from '@mui/icons-material/Delete'; -import { deleteStaff } from "@/app/api/staff/actions"; +import DeleteIcon from "@mui/icons-material/Delete"; import { useRouter } from "next/navigation"; -import ConfirmModal from "./ConfirmDeleteModal"; import { deleteTeam } from "@/app/api/team/actions"; - +import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; interface Props { team: TeamResult[]; @@ -20,109 +18,90 @@ type SearchQuery = Partial>; type SearchParamNames = keyof SearchQuery; const TeamSearch: React.FC = ({ team }) => { - const { t } = useTranslation(); - const [filteredTeam, setFilteredTeam] = useState(team); - const [data, setData] = useState(); - const [isOpen, setIsOpen] = useState(false); - const router = useRouter(); - - const searchCriteria: Criterion[] = useMemo( - () => [ - { - label: t("Team Name"), - paramName: "name", - type: "text", - }, - { - label: t("Team Code"), - paramName: "code", - type: "text", - }, - { - label: t("Team Description"), - paramName: "description", - type: "text", - }, - ], - [t], - ); + const { t } = useTranslation(); + const [filteredTeam, setFilteredTeam] = useState(team); + const router = useRouter(); - const onTeamClick = useCallback((team: TeamResult) => { - console.log(team); - const id = team.id - router.push(`/settings/team/edit?id=${id}`); - }, [router, t]); - - // const onDeleteClick = useCallback((team: TeamResult) => { - // console.log(team); - // deleteTeam + const searchCriteria: Criterion[] = useMemo( + () => [ + { + label: t("Team Name"), + paramName: "name", + type: "text", + }, + { + label: t("Team Code"), + paramName: "code", + type: "text", + }, + { + label: t("Team Description"), + paramName: "description", + type: "text", + }, + ], + [t] + ); - // }, [router, t]); + const onTeamClick = useCallback( + (team: TeamResult) => { + console.log(team); + const id = team.id; + router.push(`/settings/team/edit?id=${id}`); + }, + [router, t] + ); - const onDeleteClick = (team: TeamResult) => { - console.log(team); - setData(team) - setIsOpen(!isOpen) - }; + const onDeleteClick = useCallback((team: TeamResult) => { + deleteDialog(async () => { + await deleteTeam(team.id); - const onConfirm = useCallback(async (team: TeamResult) => { - console.log(team); - if (data) - await deleteTeam(data) - setIsOpen(false) - window.location.reload; - }, [deleteTeam, data]); + successDialog("Delete Success", t); - const onCancel = useCallback(() => { - setIsOpen(false) - }, []); + setFilteredTeam((prev) => prev.filter((obj) => obj.id !== team.id)); + }, t); + }, []); - const columns = useMemo[]>( - () => [ - { - name: "action", - label: t("Edit"), - onClick: onTeamClick, - buttonIcon: , - }, - { name: "name", label: t("Name") }, - { name: "code", label: t("Code") }, - { name: "description", label: t("description") }, - { - name: "action", - label: t("Delete"), - onClick: onDeleteClick, - buttonIcon: , - }, - ], - [t], - ); + const columns = useMemo[]>( + () => [ + { + name: "action", + label: t("Edit"), + onClick: onTeamClick, + buttonIcon: , + }, + { name: "name", label: t("Name") }, + { name: "code", label: t("Code") }, + { name: "description", label: t("description") }, + { name: "staffName", label: t("TeamLead") }, + { + name: "action", + label: t("Delete"), + onClick: onDeleteClick, + buttonIcon: , + color: "error" + }, + ], + [t] + ); return ( - <> - + { - // setFilteredStaff( - // staff.filter( - // (s) => - // s.staffId.toLowerCase().includes(query.staffId.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), - // ) - // ) + onSearch={(query) => { + setFilteredTeam( + team.filter( + (t) => + t.name.toLowerCase().includes(query.name.toLowerCase()) && + t.code.toLowerCase().includes(query.code.toLowerCase()) && + t.description.toLowerCase().includes(query.description.toLowerCase()) + ) + ) }} /> items={filteredTeam} columns={columns} /> - - - + ); }; export default TeamSearch; diff --git a/src/components/UserSearch/UserSearch.tsx b/src/components/UserSearch/UserSearch.tsx new file mode 100644 index 0000000..095c544 --- /dev/null +++ b/src/components/UserSearch/UserSearch.tsx @@ -0,0 +1,98 @@ +"use client"; + +import SearchBox, { Criterion } from "../SearchBox"; +import { useCallback, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import SearchResults, { Column } from "../SearchResults/index"; +import EditNote from "@mui/icons-material/EditNote"; +import DeleteIcon from "@mui/icons-material/Delete"; +import { useRouter } from "next/navigation"; +import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; +import { UserResult } from "@/app/api/user"; +import { deleteUser } from "@/app/api/user/actions"; + +interface Props { + users: UserResult[]; +} +type SearchQuery = Partial>; +type SearchParamNames = keyof SearchQuery; + +const UserSearch: React.FC = ({ users }) => { + const { t } = useTranslation(); + const [filteredUser, setFilteredUser] = useState(users); + const router = useRouter(); + + const searchCriteria: Criterion[] = useMemo( + () => [ + { + label: t("User Name"), + paramName: "title", + type: "text", + }, + ], + [t] + ); + + const onUserClick = useCallback( + (users: UserResult) => { + console.log(users); + router.push(`/settings/user/edit?id=${users.id}`) + }, + [router, t] + ); + + const onDeleteClick = useCallback((users: UserResult) => { + deleteDialog(async () => { + await deleteUser(users.id); + + successDialog("Delete Success", t); + + setFilteredUser((prev) => prev.filter((obj) => obj.id !== users.id)); + }, t); + }, []); + + const columns = useMemo[]>( + () => [ + { + name: "action", + label: t("Edit"), + onClick: onUserClick, + buttonIcon: , + }, + { name: "name", label: t("UserName") }, + { name: "fullName", label: t("FullName") }, + { name: "title", label: t("Title") }, + { name: "department", label: t("Department") }, + { name: "email", label: t("Email") }, + { name: "phone1", label: t("Phone") }, + { + name: "action", + label: t("Delete"), + onClick: onDeleteClick, + buttonIcon: , + color: "error" + }, + ], + [t] + ); + + return ( + <> + { + // setFilteredUser( + // users.filter( + // (t) => + // t.name.toLowerCase().includes(query.name.toLowerCase()) && + // t.code.toLowerCase().includes(query.code.toLowerCase()) && + // t.description.toLowerCase().includes(query.description.toLowerCase()) + // ) + // ) + }} + /> + items={filteredUser} columns={columns} /> + + ); +}; +export default UserSearch; diff --git a/src/components/UserSearch/UserSearchLoading.tsx b/src/components/UserSearch/UserSearchLoading.tsx new file mode 100644 index 0000000..535a751 --- /dev/null +++ b/src/components/UserSearch/UserSearchLoading.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 UserSearchLoading: React.FC = () => { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default UserSearchLoading; diff --git a/src/components/UserSearch/UserSearchWrapper.tsx b/src/components/UserSearch/UserSearchWrapper.tsx new file mode 100644 index 0000000..beaef92 --- /dev/null +++ b/src/components/UserSearch/UserSearchWrapper.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import UserSearch from "./UserSearch"; +import UserSearchLoading from "./UserSearchLoading"; +import { UserResult, fetchUser } from "@/app/api/user"; + +interface SubComponents { + Loading: typeof UserSearchLoading; +} + +const UserSearchWrapper: React.FC & SubComponents = async () => { +const users = await fetchUser() + console.log(users); + + return ; +}; + +UserSearchWrapper.Loading = UserSearchLoading; + +export default UserSearchWrapper; diff --git a/src/components/UserSearch/index.ts b/src/components/UserSearch/index.ts new file mode 100644 index 0000000..c2e98d7 --- /dev/null +++ b/src/components/UserSearch/index.ts @@ -0,0 +1 @@ +export { default } from "./UserSearchWrapper";