| @@ -8,9 +8,9 @@ import { cache } from "react"; | |||
| export interface CreateSkillInputs { | |||
| id?: number; | |||
| name: String; | |||
| code: String; | |||
| description: String; | |||
| name: string; | |||
| code: string; | |||
| description: string; | |||
| } | |||
| export interface comboProp { | |||
| @@ -15,10 +15,16 @@ import { useTranslation } from "react-i18next"; | |||
| import { CreateSkillInputs, saveSkill } from "@/app/api/skill/actions"; | |||
| import { Error } from "@mui/icons-material"; | |||
| import SkillInfo from "./SkillInfo"; | |||
| import { SkillResult } from "@/app/api/skill"; | |||
| interface Props {} | |||
| interface Props { | |||
| skill: SkillResult[] | |||
| } | |||
| const CreateSkill: React.FC<Props> = () => { | |||
| const CreateSkill: React.FC<Props> = ({ | |||
| skill | |||
| }) => { | |||
| const codeList = skill.map(s => s.code.toLowerCase().trim()) | |||
| const formProps = useForm<CreateSkillInputs>(); | |||
| const [serverError, setServerError] = useState(""); | |||
| const router = useRouter(); | |||
| @@ -29,7 +35,15 @@ const CreateSkill: React.FC<Props> = () => { | |||
| const onSubmit = useCallback<SubmitHandler<CreateSkillInputs>>( | |||
| async (data) => { | |||
| try { | |||
| console.log(data); | |||
| let haveError = false | |||
| haveError = codeList.includes(data.code.toLowerCase().trim()) | |||
| if (haveError) { | |||
| formProps.setError("code", { message: t("Duplicated Code"), type: "required" }) | |||
| return | |||
| } | |||
| data.name = data.name.trim() | |||
| data.code = data.code.trim() | |||
| data.description = data.description.trim() | |||
| await saveSkill(data) | |||
| router.replace(`/settings/skill`) | |||
| } catch (e) { | |||
| @@ -3,15 +3,16 @@ import CreateSkill from "./CreateSkill"; | |||
| import CreateSkillLoading from "./CreateSkillLoading"; | |||
| import { fetchStaff, fetchTeamLeads } from "@/app/api/staff"; | |||
| import { useSearchParams } from "next/navigation"; | |||
| import { fetchSkill } from "@/app/api/skill"; | |||
| interface SubComponents { | |||
| Loading: typeof CreateSkillLoading; | |||
| } | |||
| const CreateSkillWrapper: React.FC & SubComponents = async () => { | |||
| const skill = await fetchSkill() | |||
| return <CreateSkill/>; | |||
| return <CreateSkill skill={skill}/>; | |||
| }; | |||
| CreateSkillWrapper.Loading = CreateSkillLoading; | |||
| @@ -69,8 +69,8 @@ const CreateStaff: React.FC<formProps> = ({ combos }) => { | |||
| try { | |||
| console.log(data); | |||
| let haveError = false; | |||
| let regex_email = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/ | |||
| let regex_phone = /^\d{8}$/ | |||
| const regex_email = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/ | |||
| const regex_phone = /^\d{8}$/ | |||
| if (!regex_email.test(data.email)) { | |||
| haveError = true | |||
| @@ -11,27 +11,38 @@ import { useRouter } from "next/navigation"; | |||
| import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; | |||
| import Person from '@mui/icons-material/Person'; | |||
| import { MAINTAIN_USER, VIEW_USER } from "@/middleware"; | |||
| import { TeamResult } from "@/app/api/team"; | |||
| import { Grade } from "@/app/api/grades"; | |||
| import { PositionResult } from "@/app/api/positions"; | |||
| interface Props { | |||
| staff: StaffResult[]; | |||
| abilities: string[] | |||
| teams: TeamResult[] | |||
| grades: Grade[] | |||
| positions: PositionResult[] | |||
| isAuthed: boolean | |||
| } | |||
| type SearchQuery = Partial<Omit<StaffResult, "id">>; | |||
| type SearchParamNames = keyof SearchQuery; | |||
| const StaffSearch: React.FC<Props> = ({ staff, abilities }) => { | |||
| const StaffSearch: React.FC<Props> = ({ staff, teams, grades, positions, isAuthed }) => { | |||
| console.log(staff) | |||
| const { t } = useTranslation(); | |||
| const [filteredStaff, setFilteredStaff] = useState(staff); | |||
| const router = useRouter(); | |||
| const teamCombo = teams.map(t => `${t.name} - ${t.code}`) | |||
| const gradeCombo = grades.map(g => `${g.name}`) | |||
| const positionCombo = positions.map(p => `${p.name}`) | |||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | |||
| () => [ | |||
| { | |||
| label: t("Team"), | |||
| paramName: "team", | |||
| type: "select", | |||
| options: ["1", "2"], | |||
| options: teamCombo, | |||
| }, | |||
| { | |||
| label: t("Staff Name"), | |||
| @@ -47,13 +58,13 @@ const StaffSearch: React.FC<Props> = ({ staff, abilities }) => { | |||
| label: t("Grade"), | |||
| paramName: "grade", | |||
| type: "select", | |||
| options: ["pos1", "CEO"], | |||
| options: gradeCombo, | |||
| }, | |||
| { | |||
| label: t("Current Position"), | |||
| paramName: "currentPosition", | |||
| type: "select", | |||
| options: ["pos1", "CEO"], | |||
| options: positionCombo, | |||
| }, | |||
| ], | |||
| [t] | |||
| @@ -97,7 +108,7 @@ const StaffSearch: React.FC<Props> = ({ staff, abilities }) => { | |||
| label: t("Users"), | |||
| onClick: onUserClick, | |||
| buttonIcon: <Person />, | |||
| isHidden: ![MAINTAIN_USER, VIEW_USER].some((ability) => abilities.includes(ability)), | |||
| isHidden: !isAuthed, | |||
| }, | |||
| { name: "team", label: t("Team") }, | |||
| { name: "name", label: t("Staff Name") }, | |||
| @@ -114,20 +125,22 @@ const StaffSearch: React.FC<Props> = ({ staff, abilities }) => { | |||
| ], | |||
| [t, onStaffClick, deleteClick] | |||
| ); | |||
| // postData.teamId = team[index].id | |||
| return ( | |||
| <> | |||
| <SearchBox | |||
| criteria={searchCriteria} | |||
| onSearch={(query) => { | |||
| // console.log(teamCombo.findIndex(team => team === query.team)) | |||
| 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), | |||
| s.staffId.toLowerCase().includes(query.staffId.toLowerCase()) | |||
| && s.name.toLowerCase().includes(query.name.toLowerCase()) | |||
| && (query.team === "All" || s.teamId === teams[teamCombo.findIndex(team => team === query.team)].id) | |||
| && (query.grade === "All" || s.grade === query.grade) | |||
| && (query.currentPosition === "All" || s.currentPosition === query.currentPosition) | |||
| ) | |||
| ); | |||
| }} | |||
| @@ -4,6 +4,10 @@ import StaffSearch from "./StaffSearch"; | |||
| import StaffSearchLoading from "./StaffSearchLoading"; | |||
| import { Session, getServerSession } from "next-auth"; | |||
| import { authOptions } from "@/config/authConfig"; | |||
| import { fetchTeam } from "@/app/api/team"; | |||
| import { fetchPositions } from "@/app/api/positions"; | |||
| import { fetchGrades } from "@/app/api/grades"; | |||
| import { MAINTAIN_USER, VIEW_USER } from "@/middleware"; | |||
| interface SubComponents { | |||
| Loading: typeof StaffSearchLoading; | |||
| @@ -14,11 +18,15 @@ interface SessionWithAbilities extends Session { | |||
| } | |||
| const StaffSearchWrapper: React.FC & SubComponents = async () => { | |||
| const staff = await fetchStaff(); | |||
| const session = await getServerSession(authOptions) as SessionWithAbilities; | |||
| const abilities: string[] = session.abilities!! | |||
| const isAuthed = ![MAINTAIN_USER, VIEW_USER].some((ability) => session.abilities!.includes(ability)) | |||
| const staff = await fetchStaff(); | |||
| const teams = await fetchTeam(); | |||
| const grades = await fetchGrades(); | |||
| const positions = await fetchPositions(); | |||
| return <StaffSearch staff={staff} abilities={abilities}/>; | |||
| return <StaffSearch staff={staff} teams={teams} grades={grades} positions={positions} isAuthed={isAuthed}/>; | |||
| }; | |||
| StaffSearchWrapper.Loading = StaffSearchLoading; | |||
| @@ -25,6 +25,7 @@ | |||
| "Depart Date": "離職日期", | |||
| "Depart Reason": "離職原因", | |||
| "Remark": "備註", | |||
| "Reset": "重設", | |||
| "Confirm": "確認", | |||
| "Cancel": "取消" | |||
| } | |||