@@ -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": "取消" | |||
} |