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