change some files

This commit is contained in:
2026-06-02 17:08:52 +03:30
parent b8dc1d0e1b
commit cfb48c5bb0
76 changed files with 5204 additions and 2555 deletions

View 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>
);
}

View 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;

View 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: "",
},
};

View 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;
}

View 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(),
}),
});