change some files
This commit is contained in:
349
ui/forms/skillsForm/InnerSkillsForm.tsx
Normal file
349
ui/forms/skillsForm/InnerSkillsForm.tsx
Normal file
@@ -0,0 +1,349 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import {
|
||||
Box,
|
||||
MenuItem,
|
||||
Paper,
|
||||
TextField,
|
||||
Typography,
|
||||
Divider,
|
||||
Switch,
|
||||
FormControlLabel,
|
||||
Button,
|
||||
} from "@mui/material";
|
||||
import { Form, type FormikProps } from "formik";
|
||||
import type { SkillsFormProps, SkillsFormValues } from "./types";
|
||||
import { certTypes, proficiencyOptions } from "./constant";
|
||||
|
||||
type Props = FormikProps<SkillsFormValues> & SkillsFormProps;
|
||||
|
||||
export default function InnerSkillsForm(props: Props) {
|
||||
const { values, errors, touched, handleChange, setFieldValue, isSubmitting } = props;
|
||||
|
||||
const handleBack = () => {
|
||||
props.update({
|
||||
computerSkill: values.computerSkill,
|
||||
languageSkill: values.languageSkill,
|
||||
});
|
||||
props.setStep(props.step - 1);
|
||||
};
|
||||
|
||||
const compFields = [
|
||||
"pcUsage",
|
||||
"word",
|
||||
"excel",
|
||||
"powerPoint",
|
||||
"rahkaran",
|
||||
"kasra",
|
||||
"didgah",
|
||||
"his",
|
||||
] as const;
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<Box sx={{ display: "grid", gap: 3 }}>
|
||||
|
||||
{/* 1. Computer Skills Section */}
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: { xs: 2, md: 3 },
|
||||
borderRadius: "24px",
|
||||
border: "1px solid #e2e8f0",
|
||||
backgroundColor: "#ffffff",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" sx={{ mb: 3, fontWeight: "bold", color: "#0f172a" }}>
|
||||
مهارتهای کامپیوتری
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", md: "repeat(4, 1fr)" }, gap: 2 }}>
|
||||
{compFields.map((field) => {
|
||||
const hasError = !!errors.computerSkill?.[field] && !!touched.computerSkill?.[field];
|
||||
return (
|
||||
<TextField
|
||||
key={field}
|
||||
select
|
||||
label={field.toUpperCase()}
|
||||
name={`computerSkill.${field}`}
|
||||
value={values.computerSkill[field]}
|
||||
onChange={handleChange}
|
||||
error={hasError}
|
||||
helperText={hasError ? errors.computerSkill?.[field] : ""}
|
||||
fullWidth
|
||||
>
|
||||
{proficiencyOptions.map((o) => (
|
||||
<MenuItem key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
);
|
||||
})}
|
||||
|
||||
<TextField
|
||||
label="سایر نرمافزارها"
|
||||
name="computerSkill.otherSoftware"
|
||||
value={values.computerSkill.otherSoftware || ""}
|
||||
onChange={handleChange}
|
||||
error={!!errors.computerSkill?.otherSoftware && !!touched.computerSkill?.otherSoftware}
|
||||
helperText={
|
||||
touched.computerSkill?.otherSoftware ? errors.computerSkill?.otherSoftware : ""
|
||||
}
|
||||
fullWidth
|
||||
multiline
|
||||
minRows={2}
|
||||
sx={{ gridColumn: { xs: "1", md: "1 / -1" } }}
|
||||
/>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{/* 2. Language Skills Section */}
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: { xs: 2, md: 3 },
|
||||
borderRadius: "24px",
|
||||
border: "1px solid #e2e8f0",
|
||||
backgroundColor: "#ffffff",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" sx={{ mb: 3, fontWeight: "bold", color: "#0f172a" }}>
|
||||
آشنایی با زبانهای خارجه
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" }, gap: 2 }}>
|
||||
|
||||
{/* English */}
|
||||
<TextField
|
||||
select
|
||||
label="زبان انگلیسی*"
|
||||
name="languageSkill.englishLevel"
|
||||
value={values.languageSkill.englishLevel}
|
||||
onChange={handleChange}
|
||||
error={!!errors.languageSkill?.englishLevel && !!touched.languageSkill?.englishLevel}
|
||||
helperText={
|
||||
touched.languageSkill?.englishLevel ? errors.languageSkill?.englishLevel : " "
|
||||
}
|
||||
fullWidth
|
||||
>
|
||||
{proficiencyOptions.map((o) => (
|
||||
<MenuItem key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={values.languageSkill.hasEnglishCertificate}
|
||||
onChange={(e) => {
|
||||
const checked = e.target.checked;
|
||||
setFieldValue("languageSkill.hasEnglishCertificate", checked);
|
||||
if (!checked) {
|
||||
// پاکسازی فیلد نوع مدرک در صورت غیرفعال شدن سوئیچ
|
||||
setFieldValue("languageSkill.englishCertificateType", "");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="مدرک معتبر زبان انگلیسی دارد"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{values.languageSkill.hasEnglishCertificate && (
|
||||
<TextField
|
||||
select
|
||||
label="نوع مدرک*"
|
||||
name="languageSkill.englishCertificateType"
|
||||
value={values.languageSkill.englishCertificateType}
|
||||
onChange={handleChange}
|
||||
error={
|
||||
!!errors.languageSkill?.englishCertificateType &&
|
||||
!!touched.languageSkill?.englishCertificateType
|
||||
}
|
||||
helperText={
|
||||
touched.languageSkill?.englishCertificateType
|
||||
? errors.languageSkill?.englishCertificateType
|
||||
: " "
|
||||
}
|
||||
fullWidth
|
||||
sx={{ gridColumn: { xs: "1", md: "1 / -1" } }}
|
||||
>
|
||||
<MenuItem value="">انتخاب...</MenuItem>
|
||||
{certTypes.map((t) => (
|
||||
<MenuItem key={t} value={t}>
|
||||
{t}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
)}
|
||||
|
||||
<TextField
|
||||
label="توضیحات زبان انگلیسی"
|
||||
name="languageSkill.englishDescription"
|
||||
value={values.languageSkill.englishDescription}
|
||||
onChange={handleChange}
|
||||
error={
|
||||
!!errors.languageSkill?.englishDescription &&
|
||||
!!touched.languageSkill?.englishDescription
|
||||
}
|
||||
helperText={
|
||||
touched.languageSkill?.englishDescription
|
||||
? errors.languageSkill?.englishDescription
|
||||
: ""
|
||||
}
|
||||
fullWidth
|
||||
multiline
|
||||
minRows={2}
|
||||
sx={{ gridColumn: { xs: "1", md: "1 / -1" } }}
|
||||
/>
|
||||
|
||||
<Divider sx={{ gridColumn: { xs: "1", md: "1 / -1" }, my: 1 }} />
|
||||
|
||||
{/* Arabic */}
|
||||
<TextField
|
||||
select
|
||||
label="زبان عربی*"
|
||||
name="languageSkill.arabicLevel"
|
||||
value={values.languageSkill.arabicLevel}
|
||||
onChange={handleChange}
|
||||
error={!!errors.languageSkill?.arabicLevel && !!touched.languageSkill?.arabicLevel}
|
||||
helperText={
|
||||
touched.languageSkill?.arabicLevel ? errors.languageSkill?.arabicLevel : " "
|
||||
}
|
||||
fullWidth
|
||||
>
|
||||
{proficiencyOptions.map((o) => (
|
||||
<MenuItem key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<Box />
|
||||
|
||||
<TextField
|
||||
label="توضیحات زبان عربی"
|
||||
name="languageSkill.arabicDescription"
|
||||
value={values.languageSkill.arabicDescription}
|
||||
onChange={handleChange}
|
||||
error={
|
||||
!!errors.languageSkill?.arabicDescription &&
|
||||
!!touched.languageSkill?.arabicDescription
|
||||
}
|
||||
helperText={
|
||||
touched.languageSkill?.arabicDescription
|
||||
? errors.languageSkill?.arabicDescription
|
||||
: ""
|
||||
}
|
||||
fullWidth
|
||||
multiline
|
||||
minRows={2}
|
||||
sx={{ gridColumn: { xs: "1", md: "1 / -1" } }}
|
||||
/>
|
||||
|
||||
<Divider sx={{ gridColumn: { xs: "1", md: "1 / -1" }, my: 1 }} />
|
||||
|
||||
{/* Other Skills */}
|
||||
<TextField
|
||||
label="سایر زبان ها (توضیحات در مورد میزان تسلط)"
|
||||
name="languageSkill.otherLanguagesDescription"
|
||||
value={values.languageSkill.otherLanguagesDescription}
|
||||
onChange={handleChange}
|
||||
error={
|
||||
!!errors.languageSkill?.otherLanguagesDescription &&
|
||||
!!touched.languageSkill?.otherLanguagesDescription
|
||||
}
|
||||
helperText={
|
||||
touched.languageSkill?.otherLanguagesDescription
|
||||
? errors.languageSkill?.otherLanguagesDescription
|
||||
: ""
|
||||
}
|
||||
fullWidth
|
||||
multiline
|
||||
minRows={2}
|
||||
sx={{ gridColumn: { xs: "1", md: "1 / -1" } }}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
label="آشنایی با گویش ها و لهجه های کشور (توضیحات در مورد میزان تسلط)"
|
||||
name="languageSkill.dialectsDescription"
|
||||
value={values.languageSkill.dialectsDescription}
|
||||
onChange={handleChange}
|
||||
error={
|
||||
!!errors.languageSkill?.dialectsDescription &&
|
||||
!!touched.languageSkill?.dialectsDescription
|
||||
}
|
||||
helperText={
|
||||
touched.languageSkill?.dialectsDescription
|
||||
? errors.languageSkill?.dialectsDescription
|
||||
: ""
|
||||
}
|
||||
fullWidth
|
||||
multiline
|
||||
minRows={2}
|
||||
sx={{ gridColumn: { xs: "1", md: "1 / -1" } }}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
label="سایر مهارت ها (اعم از ورزشی، هنری، فرهنگی، اجتماعی و ...)"
|
||||
name="languageSkill.otherSkills"
|
||||
value={values.languageSkill.otherSkills}
|
||||
onChange={handleChange}
|
||||
error={!!errors.languageSkill?.otherSkills && !!touched.languageSkill?.otherSkills}
|
||||
helperText={
|
||||
touched.languageSkill?.otherSkills ? errors.languageSkill?.otherSkills : ""
|
||||
}
|
||||
fullWidth
|
||||
multiline
|
||||
minRows={3}
|
||||
sx={{ gridColumn: { xs: "1", md: "1 / -1" } }}
|
||||
/>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{/* Wizard Navigation Buttons */}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
mt: 2,
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
disabled={props.step === 1 || isSubmitting}
|
||||
type="button"
|
||||
onClick={handleBack}
|
||||
sx={{
|
||||
borderRadius: "12px",
|
||||
color: "#64748b",
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
بازگشت
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
sx={{
|
||||
borderRadius: "12px",
|
||||
px: 4,
|
||||
py: 1.5,
|
||||
bgcolor: props.step === 12 ? "green" : "#2563eb",
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
{props.step === 12 ? "اتمام و ثبت نهایی" : "گام بعدی"}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
41
ui/forms/skillsForm/SkillsForm.tsx
Normal file
41
ui/forms/skillsForm/SkillsForm.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
"use client";
|
||||
|
||||
import { withFormik, type FormikBag } from "formik";
|
||||
import type { SkillsFormProps, SkillsFormValues } from "./types";
|
||||
import { INITIAL_SKILLS_VALUES } from "./constant";
|
||||
import { SkillsValidationSchema } from "./validation";
|
||||
import InnerSkillsForm from "./InnerSkillsForm";
|
||||
|
||||
const SkillsForm = withFormik<SkillsFormProps, SkillsFormValues>({
|
||||
displayName: "SkillsForm",
|
||||
|
||||
enableReinitialize: true,
|
||||
|
||||
mapPropsToValues: (props) => {
|
||||
return {
|
||||
computerSkill: props.data?.computerSkill || INITIAL_SKILLS_VALUES.computerSkill,
|
||||
languageSkill: props.data?.languageSkill || INITIAL_SKILLS_VALUES.languageSkill,
|
||||
};
|
||||
},
|
||||
|
||||
validationSchema: SkillsValidationSchema,
|
||||
|
||||
handleSubmit: async (
|
||||
values,
|
||||
bag: FormikBag<SkillsFormProps, SkillsFormValues>
|
||||
) => {
|
||||
const { props, setSubmitting } = bag;
|
||||
|
||||
// ثبت مقادیر در استیت اصلی Wizard کامپوننت مادر
|
||||
props.update({
|
||||
computerSkill: values.computerSkill,
|
||||
languageSkill: values.languageSkill,
|
||||
});
|
||||
|
||||
// رفتن به گام بعدی
|
||||
props.setStep((prev) => prev + 1);
|
||||
setSubmitting(false);
|
||||
},
|
||||
})(InnerSkillsForm);
|
||||
|
||||
export default SkillsForm;
|
||||
39
ui/forms/skillsForm/constant/index.ts
Normal file
39
ui/forms/skillsForm/constant/index.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { ProficiencyLevel, SkillsFormValues } from "../types";
|
||||
|
||||
export const proficiencyOptions: { value: ProficiencyLevel; label: string }[] = [
|
||||
{ value: "", label: "انتخاب ..." },
|
||||
{ value: "NONE", label: "ندارد" },
|
||||
{ value: "VERY_WEAK", label: "خیلی ضعیف" },
|
||||
{ value: "WEAK", label: "ضعیف" },
|
||||
{ value: "AVERAGE", label: "متوسط" },
|
||||
{ value: "GOOD", label: "خوب" },
|
||||
{ value: "VERY_GOOD", label: "خیلی خوب" },
|
||||
{ value: "EXCELLENT", label: "عالی" },
|
||||
];
|
||||
|
||||
export const certTypes = ["IELTS", "TOFEL", "TOLIMO", "MCHE"] as const;
|
||||
|
||||
export const INITIAL_SKILLS_VALUES: SkillsFormValues = {
|
||||
computerSkill: {
|
||||
pcUsage: "",
|
||||
word: "",
|
||||
excel: "",
|
||||
powerPoint: "",
|
||||
rahkaran: "",
|
||||
kasra: "",
|
||||
didgah: "",
|
||||
his: "",
|
||||
otherSoftware: "",
|
||||
},
|
||||
languageSkill: {
|
||||
englishLevel: "",
|
||||
englishDescription: "",
|
||||
hasEnglishCertificate: false,
|
||||
englishCertificateType: "",
|
||||
arabicLevel: "",
|
||||
arabicDescription: "",
|
||||
otherLanguagesDescription: "",
|
||||
dialectsDescription: "",
|
||||
otherSkills: "",
|
||||
},
|
||||
};
|
||||
46
ui/forms/skillsForm/types/index.ts
Normal file
46
ui/forms/skillsForm/types/index.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import type React from "react";
|
||||
|
||||
export type ProficiencyLevel = "" | "NONE" | "VERY_WEAK" | "WEAK" | "AVERAGE" | "GOOD" | "VERY_GOOD" | "EXCELLENT";
|
||||
|
||||
export interface ComputerSkillFormData {
|
||||
pcUsage: ProficiencyLevel;
|
||||
word: ProficiencyLevel;
|
||||
excel: ProficiencyLevel;
|
||||
powerPoint: ProficiencyLevel;
|
||||
rahkaran: ProficiencyLevel;
|
||||
kasra: ProficiencyLevel;
|
||||
didgah: ProficiencyLevel;
|
||||
his: ProficiencyLevel;
|
||||
otherSoftware?: string;
|
||||
}
|
||||
|
||||
export interface LanguageSkillsFormData {
|
||||
englishLevel: ProficiencyLevel;
|
||||
englishDescription: string;
|
||||
hasEnglishCertificate: boolean;
|
||||
englishCertificateType: string;
|
||||
arabicLevel: ProficiencyLevel;
|
||||
arabicDescription: string;
|
||||
otherLanguagesDescription: string;
|
||||
dialectsDescription: string;
|
||||
otherSkills: string;
|
||||
}
|
||||
|
||||
export interface SkillsFormValues {
|
||||
computerSkill: ComputerSkillFormData;
|
||||
languageSkill: LanguageSkillsFormData;
|
||||
}
|
||||
|
||||
/** این ساختار را با دادههای استپهای دیگر Wizard خود تطابق دهید */
|
||||
export interface WizardFormData {
|
||||
computerSkill: ComputerSkillFormData;
|
||||
languageSkill: LanguageSkillsFormData;
|
||||
// ... rest of wizard data
|
||||
}
|
||||
|
||||
export interface SkillsFormProps {
|
||||
step: number;
|
||||
setStep: React.Dispatch<React.SetStateAction<number>>;
|
||||
data: WizardFormData;
|
||||
update: (patch: Partial<WizardFormData>) => void;
|
||||
}
|
||||
30
ui/forms/skillsForm/validation/index.ts
Normal file
30
ui/forms/skillsForm/validation/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import * as Yup from "yup";
|
||||
|
||||
export const SkillsValidationSchema = Yup.object().shape({
|
||||
computerSkill: Yup.object().shape({
|
||||
pcUsage: Yup.string().optional(),
|
||||
word: Yup.string().optional(),
|
||||
excel: Yup.string().optional(),
|
||||
powerPoint: Yup.string().optional(),
|
||||
rahkaran: Yup.string().optional(),
|
||||
kasra: Yup.string().optional(),
|
||||
didgah: Yup.string().optional(),
|
||||
his: Yup.string().optional(),
|
||||
otherSoftware: Yup.string().max(1000, "توضیحات نمیتواند بیش از ۱۰۰۰ کاراکتر باشد").optional(),
|
||||
}),
|
||||
languageSkill: Yup.object().shape({
|
||||
englishLevel: Yup.string().required("سطح زبان انگلیسی الزامی است"),
|
||||
englishDescription: Yup.string().optional(),
|
||||
hasEnglishCertificate: Yup.boolean(),
|
||||
englishCertificateType: Yup.string().when("hasEnglishCertificate", {
|
||||
is: true,
|
||||
then: (schema) => schema.required("انتخاب نوع مدرک زبان الزامی است"),
|
||||
otherwise: (schema) => schema.optional(),
|
||||
}),
|
||||
arabicLevel: Yup.string().required("سطح زبان عربی الزامی است"),
|
||||
arabicDescription: Yup.string().optional(),
|
||||
otherLanguagesDescription: Yup.string().optional(),
|
||||
dialectsDescription: Yup.string().optional(),
|
||||
otherSkills: Yup.string().optional(),
|
||||
}),
|
||||
});
|
||||
Reference in New Issue
Block a user