شكرًا لمشاركتك شفرتك المصدرية لملفات steps.vue, step1.vue, convertor.ts, validateInputStep1.ts, و request.js. سأساعدك في تحسين دمج جميع هذه الأجزاء لضمان إدارة المراحل بشكل سلس، تخزين البيانات محليًا بفعالية، والتحقق من صحة البيانات قبل إرسالها إلى Frappe Doctype.
ملخص المشروع حتى الآن
steps.vue: المكون الرئيسي لإدارة التنقل بين المراحل المختلفة (Step1 إلى Step6) وعرض شريط التقدم والتنبيهات.
step1.vue: المكون الفرعي للمرحلة الأولى يحتوي على نماذج الإدخال والتحقق من الصحة.
convertor.ts: دالة لتحويل البيانات إلى هيكل مبسط لجعلها جاهزة للإرسال.
validateInputStep1.ts: دالة للتحقق من صحة بيانات المرحلة الأولى.
request.js: وظائف لإنشاء واسترجاع وتحديث سجلات Verification Instructions Request باستخدام frappe-ui.
الأهداف التي سنعمل على تحقيقها
تكامل بين step1.vue و steps.vue باستخدام Pinia.
تحسين دوال التحويل والتحقق لضمان نوعية البيانات والدقة.
ضمان تخزين البيانات محليًا بشكل موثوق من خلال Pinia و localStorage.
ضمان إرسال البيانات المجمعة إلى Frappe Doctype بشكل صحيح عند الضغط على زر "Accept".
إدارة التنبيهات لتحسين تجربة المستخدم.
1. تحسين convertor.ts
أ. تعريف الأنواع بدقة
التعامل مع TypeScript بفاعلية يتطلب تعريف أنواع البيانات بشكل دقيق. سأقترح تعريف واجهات (Interfaces) تعكس هيكلية البيانات التي نتعامل معها.
// types.ts
export interface ValidationField {
  value: string;
  isValid: boolean | null;
  validationMessage: string | null;
}


export interface Step1Data {
  employer_name: ValidationField;
  first_name: ValidationField;
  middle_name: ValidationField;
  last_name: ValidationField;
  suffix: ValidationField;
  alias_name: ValidationField;
  certify: boolean;
}


export interface PlainData {
  [key: string]: any;
}


ب. تحسين دالة objectConvertor
سنقوم بتحديث دالة objectConvertor لتعكس الأنواع بشكل دقيق ودعم الجداول الفرعية إذا لزم الحال.
// convertor.ts
import { Step1Data, PlainData } from './types';


function objectConvertor(data: Step1Data): PlainData {
  const result: PlainData = {};


  Object.keys(data).forEach((key) => {
    const field = data[key as keyof Step1Data];
    if (key === 'certify') {
      result[key] = field; // احتفظ بالقيمة البوليانية
    } else {
      result[key] = field.value;
    }
  });


  return result;
}


export default objectConvertor;


شرح:
نحافظ على قيمة certify كقيمة بوليانية، بينما نحول بقية الحقول إلى القيم فقط.
يمكنك توسيع الدالة لدعم جداول فرعية إذا كانت موجودة في مراحل أخرى.
2. تحسين validateInputStep1.ts
أ. استخدام Yup لتحسين التحقق من الصحة
استخدام مكتبة مثل Yup يمكن أن يحسن التحقق من الصحة بشكل كبير ويسهل إدارة قواعد التحقق.
أ. تثبيت Yup
npm install yup


ب. تحديث الدالة باستخدام Yup
// validateInputStep1.ts
import * as Yup from 'yup';
import { Step1Data } from './types';


const schema = Yup.object().shape({
  employer_name: Yup.string().required('Employer Name مطلوب'),
  first_name: Yup.string().required('First Name مطلوب'),
  middle_name: Yup.string().when('certify', {
    is: false,
    then: Yup.string().required('Middle Name مطلوب'),
    otherwise: Yup.string(),
  }),
  last_name: Yup.string().required('Last Name مطلوب'),
  suffix: Yup.string().required('Suffix مطلوب'),
  alias_name: Yup.string(), // غير إجباري
  certify: Yup.boolean(),
});


async function validateInputStep1(data: Step1Data): Promise<boolean> {
  try {
    await schema.validate(data, { abortEarly: false });
    // إذا كانت البيانات صحيحة، قم بتعيين الحقول كبرازش صحيحة
    Object.keys(data).forEach((key) => {
      if (key !== 'certify') {
        data[key as keyof Step1Data].isValid = true;
        data[key as keyof Step1Data].validationMessage = null;
      }
    });
    return false; // لا توجد أخطاء
  } catch (err: any) {
    // معالجة الأخطاء لكل حقل
    err.inner.forEach((error: any) => {
      const field = error.path;
      if (field && data[field as keyof Step1Data]) {
        data[field as keyof Step1Data].isValid = false;
        data[field as keyof Step1Data].validationMessage = error.message;
      }
    });
    return true; // توجد أخطاء
  }
}


export default validateInputStep1;


شرح:
استخدمنا Yup لتعريف مخطط التحقق من الصحة.
الدالة الآن تتحقق من صحة البيانات باستخدام Yup وتقوم بتحديث حالة التحقق من الصحة (isValid و validationMessage) لكل حقل.
الدالة تعيد true إذا كانت هناك أخطاء وfalse إذا كانت البيانات صحيحة.
3. تحسين request.js
أ. تحديث الاستخدام لدعم إنشاء سجل جديد عند الحاجة
// request.js
import { createListResource, createDocumentResource } from 'frappe-ui';
import { session } from './session';


// دالة لإنشاء أو جلب مورد الطلبات
export async function createOrFetchRequestList(fields) {
  const requestList = createListResource({
    doctype: 'Verification Instructions Request',
    fields,
    filters: { user_id: session.user },
    auto: true,
    pageLength: 1,
    onSuccess(data) {
      console.log("Data retrieved successfully:", data);
    },
    onError(error) {
      console.error("Error retrieving data:", error);
    }
  });


  try {
    const data = await requestList.fetch();
    if (!data.length) {
      // إذا لم يكن هناك سجل موجود، قم بإنشاء واحد جديد
      const documentResource = createDocumentResource({
        doctype: 'Verification Instructions Request',
      });
      const newDocument = await documentResource.save({
        user_id: session.user,
        // يمكن إضافة الحقول الأخرى هنا
      });
      console.log("New document created:", newDocument);
      return documentResource;
    }
    return createDocumentResource({
      doctype: 'Verification Instructions Request',
      name: data[0].name,
    });
  } catch (error) {
    console.error("Error fetching or creating request list:", error);
    throw error;
  }
}


// دالة لتحديث الحقول في السجل الحالي
export async function updateFieldsInRequestList(documentResource, updatedFields) {
  if (!documentResource) {
    console.error("Document resource is not available");
    return;
  }


  try {
    const response = await documentResource.update(updatedFields);
    console.log("Data updated successfully:", response);
    return response;
  } catch (error) {
    console.error("Error updating data:", error);
    throw error;
  }
}


// دالة لإنشاء مورد مستند
export function useDocumentResource(name = '') {
  if (!name) {
    console.warn("Document name is not provided.");
  }
  return createDocumentResource({
    doctype: "Verification Instructions Request",
    name: name,
  });
}


شرح التحسينات:
createOrFetchRequestList: تجمع بين جلب السجل الحالي وإنشاء سجل جديد إذا لم يكن موجودًا.
updateFieldsInRequestList: تستخدم الدالة update من documentResource لتحديث السجل بشكل أكثر فعالية.
useDocumentResource: تبقى كما هي، ويمكن استخدامها في أكثر من مكان.
4. دمج request.js مع stepsStore و steps.vue
أ. تحديث stepsStore لإدارة الطلبات
// stores/stepsStore.ts
import { defineStore } from 'pinia';
import { reactive, ref } from 'vue';
import { Step1Data, PlainData } from '../types';
import objectConvertor from '../utils/convertor';
import { createOrFetchRequestList, updateFieldsInRequestList } from '../utils/request';
import validateInputStep1 from '../utils/validateInputStep1';


export const useStepsStore = defineStore('steps', () => {
  const processed = ref(0); // مؤشر الخطوة الحالية


  // بيانات كل خطوة
  const step1Data: Step1Data = reactive({
    employer_name: {
      value: '',
      isValid: null,
      validationMessage: null,
    },
    first_name: {
      value: '',
      isValid: null,
      validationMessage: null,
    },
    middle_name: {
      value: '',
      isValid: null,
      validationMessage: null,
    },
    last_name: {
      value: '',
      isValid: null,
      validationMessage: null,
    },
    suffix: {
      value: '',
      isValid: null,
      validationMessage: null,
    },
    alias_name: {
      value: '',
      isValid: null,
      validationMessage: null,
    },
    certify: false,
  });


  // بيانات خطوات أخرى...
  // يمكن إضافة مثلاً step2Data, step3Data, وهكذا


  // قائمة التنبيهات
  const alerts = reactive<{ message: string; isDanger: boolean }[]>([]);


  // دالة لإضافة تنبيه
  const triggerAlert = (message: string, isDanger: boolean = true) => {
    alerts.push({ message, isDanger });
    setTimeout(() => {
      alerts.shift();
    }, 3000);
  };


  // دالة لإزالة تنبيه
  const removeAlert = (index: number) => {
    alerts.splice(index, 1);
  };


  // حفظ البيانات في localStorage
  const saveToLocalStorage = () => {
    const data = {
      processed: processed.value,
      step1Data,
      // خطوات أخرى...
    };
    localStorage.setItem('stepsData', JSON.stringify(data));
  };


  // تحميل البيانات من localStorage
  const loadFromLocalStorage = () => {
    const savedData = localStorage.getItem('stepsData');
    if (savedData) {
      const parsedData = JSON.parse(savedData);
      processed.value = parsedData.processed || 0;
      Object.assign(step1Data, parsedData.step1Data || {});
      // تحميل بيانات خطوات أخرى...
    }
  };


  // مسح البيانات بعد الإرسال الناجح أو الرفض
  const clearData = () => {
    processed.value = 0;
    Object.keys(step1Data).forEach((key) => {
      if (key !== 'certify') {
        step1Data[key].value = '';
        step1Data[key].isValid = null;
        step1Data[key].validationMessage = null;
      }
    });
    step1Data.certify = false;
    localStorage.removeItem('stepsData');
  };


  // دالة لإنشاء أو جلب قائمة الطلبات
  const getRequestList = async () => {
    const fields = ['name', 'user_id']; // أضف الحقول اللازمة
    try {
      const documentResource = await createOrFetchRequestList(fields);
      return documentResource;
    } catch (error) {
      triggerAlert('حدث خطأ عند جلب البيانات');
      throw error;
    }
  };


  // دالة لتحديث الحقول في قائمة الطلبات
  const updateRequestList = async (updatedFields: PlainData) => {
    try {
      const documentResource = await getRequestList();
      await updateFieldsInRequestList(documentResource, updatedFields);
      triggerAlert('تم تحديث البيانات بنجاح', false);
    } catch (error) {
      triggerAlert('حدث خطأ أثناء تحديث البيانات');
    }
  };


  // دالة للتحقق من صحة خطوة 1
  const validateStep1 = async (): Promise<boolean> => {
    const hasErrors = await validateInputStep1(step1Data);
    if (hasErrors) {
      triggerAlert('يرجى تصحيح الأخطاء في النموذج.');
    }
    return hasErrors;
  };


  return {
    processed,
    step1Data,
    alerts,
    triggerAlert,
    removeAlert,
    saveToLocalStorage,
    loadFromLocalStorage,
    clearData,
    getRequestList,
    updateRequestList,
    validateStep1,
  };
});


شرح:
getRequestList: تجمع بين جلب السجل الحالي وإنشاء سجل جديد إذا لم يكن موجودًا.
updateRequestList: تقوم بتحويل بيانات الخطوات وتحديث السجل في Frappe.
validateStep1: تقوم بالتحقق من صحة بيانات المرحلة الأولى وتطلق تنبيهات إذا كانت هناك أخطاء.
ب. تحديث steps.vue
<!-- steps.vue -->
<template>
  <Header />
  <div class="lg:grid lg:grid-cols-12 lg:container py-8 bg-bg">
    <div class="container col-span-4 lg:order-2 relative">
      <div class="sticky top-4">
        <!-- شريط تقدم -->
        <div class="pb-3 font-medium text-primary text-[1rem]">
          <span class="m-2">←</span>
          Model steps instructions of Job Request ({{ Math.round((100 / totalSteps) * stepsStore.processed) }}% Completed)
        </div>
        <div class="py-3 grid grid-cols-6 lg:flex lg:flex-col lg:gap-4 w-full">
          <StepIcon
            v-for="step in steps"
            :key="step.number"
            :process="step.process"
            :complete="step.complete"
            :label="step.label"
            :desc="step.desc"
            @click="goToStep(step.number - 1)"
          />
        </div>
        <div class="py-3 lg:hidden flex justify-end text-secondary text-[1rem]">
          <button @click="showAllSteps">
            All Steps ({{ totalSteps }}) <span class="inline-block mx-1">→</span>
          </button>
        </div>
      </div>
    </div>
    <div class="pb-20 bg-bg leading-[1.6rem] col-span-8">
      <component
        :is="currentStepComponent"
        @save="handleSave"
      />
    </div>
  </div>
  <div class="bg-[#F0F0F0] fixed lg:static bottom-0 w-screen h-fit">
    <div class="container py-4 flex w-full justify-between items-center">
      <div class="text-xs text-[#1D1B20]">Auto Saving</div>


      <div class="flex gap-2">
        <button
          class="text-secondary"
          @click="previousStep"
          v-if="stepsStore.processed > 0"
        >
          ←
        </button>
        <Button
          v-if="stepsStore.processed < totalSteps - 1"
          level="primary"
          @clicked="emitSave"
        >
          Step {{ stepsStore.processed + 1 }} →
        </Button>
      </div>


      <div v-if="stepsStore.processed === totalSteps - 1" class="lg:flex justify-end grow lg:gap-2">
        <Button level="other" @clicked="accept"> Accept </Button>
        <Button level="other" @clicked="reject"> Reject </Button>
      </div>
    </div>
  </div>
  <div class="fixed z-10 bottom-0 py-6 px-4 flex flex-col gap-2">
    <div v-for="(alert, index) in stepsStore.alerts" :key="index">
      <SnackBar :isDanger="alert.isDanger" :message="alert.message" @close="stepsStore.removeAlert(index)" />
    </div>
  </div>
</template>


<script setup lang="ts">
import { computed, onMounted } from 'vue';
import { useStepsStore } from '../stores/stepsStore';
import Button from '../components/button.vue';
import Header from '../components/header.vue';
import StepIcon from './stepsSections/components/stepIcon.vue';
import SnackBar from '../components/snackBar.vue';
import objectConvertor from '../utils/convertor';
import { PlainData } from '../types';


// استيراد مكونات الخطوات الفرعية
import Step1 from './stepsSections/step1.vue';
import Step2 from './stepsSections/step2.vue';
import Step3 from './stepsSections/step3.vue';
import Step4 from './stepsSections/step4.vue';
import Step5 from './stepsSections/step5.vue';
import Step6 from './stepsSections/step6.vue';


// استخدام المتجر
const stepsStore = useStepsStore();


// تحميل البيانات المحفوظة في localStorage عند تحميل المكون
onMounted(() => {
  stepsStore.loadFromLocalStorage();
});


// تعريف المراحل
const totalSteps = 6;


// تعريف المراحل لوضع تعريفات StepIcon
const steps = [
  { number: 1, label: 'Step 1', desc: 'Personal Information', process: stepsStore.processed === 0, complete: stepsStore.processed >= 1 },
  { number: 2, label: 'Step 2', desc: '...', process: stepsStore.processed === 1, complete: stepsStore.processed >= 2 },
  { number: 3, label: 'Step 3', desc: '...', process: stepsStore.processed === 2, complete: stepsStore.processed >= 3 },
  { number: 4, label: 'Step 4', desc: '...', process: stepsStore.processed === 3, complete: stepsStore.processed >= 4 },
  { number: 5, label: 'Step 5', desc: '...', process: stepsStore.processed === 4, complete: stepsStore.processed >= 5 },
  { number: 6, label: 'Step 6', desc: '...', process: stepsStore.processed === 5, complete: stepsStore.processed >= 6 },
];


// تحديد المكون الحالي بناءً على المرحلة الحالية
const currentStepComponent = computed(() => {
  switch (stepsStore.processed) {
    case 0:
      return Step1;
    case 1:
      return Step2;
    case 2:
      return Step3;
    case 3:
      return Step4;
    case 4:
      return Step5;
    case 5:
      return Step6;
    default:
      return Step1;
  }
});


// دوال التنقل
const previousStep = () => {
  if (stepsStore.processed > 0) {
    stepsStore.processed--;
    stepsStore.saveToLocalStorage();
  }
};


// التعامل مع حدث الحفظ من المكون الفرعي
const handleSave = () => {
  if (stepsStore.processed < totalSteps - 1) {
    stepsStore.processed++;
    stepsStore.saveToLocalStorage();
  }
};


// دالة للقبول وإرسال البيانات إلى Frappe Doctype
const accept = async () => {
  // الحصول على السجل الحالي أو إنشاء uno جديد
  try {
    const documentResource = await stepsStore.getRequestList();
    if (!documentResource) {
      stepsStore.triggerAlert('حدث خطأ عند جلب السجل.');
      return;
    }


    // تحويل بيانات جميع الحقول باستخدام objectConvertor
    const payload: PlainData = {
      doctype: "Verification Instructions Request",
      ...objectConvertor(stepsStore.step1Data),
      // قم بإضافة تحويلات خطوات أخرى باستخدام convertor إذا كان لديك
      // ...objectConvertor(stepsStore.step2Data),
      // ...objectConvertor(stepsStore.step3Data),
      // وهكذا
    };


    await stepsStore.updateRequestList(payload);
    stepsStore.triggerAlert('تم الإرسال بنجاح', false);
    stepsStore.clearData();
    // يمكنك إعادة توجيه المستخدم أو تحديث الواجهة حسب الحاجة
  } catch (error) {
    stepsStore.triggerAlert('حدث خطأ أثناء الإرسال');
  }
};


// دالة للرفض (يمكن تخصيصها)
const reject = () => {
  // تنفيذ إجراءات عند الرفض، مثل إعادة تعيين البيانات أو الاحتفاظ بالمعلومات الحالية
  stepsStore.triggerAlert('تم الرفض', false);
  // مثال: stepsStore.clearData();
};


// دالة للانتقال إلى خطوة معينة عبر النقر على StepIcon
const goToStep = (stepNumber: number) => {
  if (stepNumber >= 0 && stepNumber < totalSteps) {
    stepsStore.processed = stepNumber;
    stepsStore.saveToLocalStorage();
  }
};


// دالة لعرض جميع الخطوات (اختياري)
const showAllSteps = () => {
  // تنفيذ إجراءات لعرض جميع الخطوات، مثل فتح نافذة جانبية أو عرض قائمة
};
</script>


<style scoped>
/* أضف تنسيقاتك هنا */
</style>


شرح التعديلات:
دمج stepsStore مع request.js:
استخدمنا دوال createOrFetchRequestList و updateFieldsInRequestList لجلب أو إنشاء السجل وتحديثه.
دالة accept تقوم بتحويل بيانات الخطوات باستخدام objectConvertor ثم تحديث السجل في Frappe.
معالجة الأخطاء والتأكيد على النجاح:
عند نجاح العملية، تعرض تنبيهًا بنجاح الإرسال وتقوم بمسح البيانات.
عند حدوث خطأ، تعرض تنبيهًا بفشل العملية.
تحديث بيانات المراحل وتحفيز التنقل:
عند حفظ النموذج بنجاح، يتم الانتقال إلى الخطوة التالية من خلال حدث save.
أضفنا وظيفة goToStep للسماح للمستخدم بالانتقال إلى أي خطوة بالنقر على أيقونة الخطوة.
5. تحسين step1.vue
أ. تحديث step1.vue لاستخدام Pinia والتحقق من الصحة
<!-- step1.vue -->
<template>
  <div class="pt-3 container">
    <h3 class="text-lg font-medium mb-3 text-black">1. Basic Information</h3>
    <div class="lg:grid grid-cols-2 lg:gap-2">
      <FieldContainer>
        <StyledInput
          labelText="Employer Name"
          :isMandatory="true"
          infoText="Employer Name"
          inputType="text"
          v-model="formData.employer_name"
          name="employer_name"
          id="EmployerName"
          :isValid="validation.employer_name.isValid"
          :validationMessage="validation.employer_name.validationMessage"
        />
        <SupportingText>Supporting text</SupportingText>
      </FieldContainer>
      <FieldContainer>
        <StyledInput
          labelText="First Name"
          :isMandatory="true"
          infoText="First Name"
          inputType="text"
          v-model="formData.first_name"
          name="first_name"
          id="FirstName"
          :isValid="validation.first_name.isValid"
          :validationMessage="validation.first_name.validationMessage"
        />
      </FieldContainer>
    </div>
    <FieldContainer>
      <CheckBox
        name="certify"
        id="Certify"
        :checked="formData.certify"
        @input-change="handleCheckbox"
      >
        I certify that I do not have a Middle Name on my official identification document
      </CheckBox>
    </FieldContainer>
    <div class="lg:grid grid-cols-2 lg:gap-2">
      <FieldContainer>
        <StyledInput
          labelText="Middle Name"
          :isMandatory="!formData.certify"
          inputType="text"
          v-model="formData.middle_name"
          name="middle_name"
          id="MiddleName"
          :isValid="validation.middle_name.isValid"
          :validationMessage="validation.middle_name.validationMessage"
          :disabled="formData.certify"
        />
      </FieldContainer>
    </div>


    <div class="lg:grid grid-cols-2 lg:gap-2">
      <FieldContainer>
        <StyledInput
          labelText="Last Name"
          :isMandatory="true"
          infoText="Last Name"
          inputType="text"
          v-model="formData.last_name"
          name="last_name"
          id="LastName"
          :isValid="validation.last_name.isValid"
          :validationMessage="validation.last_name.validationMessage"
        />
      </FieldContainer>
      <FieldContainer>
        <StyledInput
          labelText="Suffix"
          :isMandatory="false" <!-- إذا كان مقبولاً يكون غير إجباري -->
          infoText="Suffix"
          inputType="text"
          v-model="formData.suffix"
          name="suffix"
          id="Suffix"
          :isValid="validation.suffix.isValid"
          :validationMessage="validation.suffix.validationMessage"
        />
        <SupportingText>Supporting text</SupportingText>
      </FieldContainer>
    </div>


    <FieldContainer>
      <StyledInput
        labelText="Alias Name"
        :isMandatory="false" <!-- إذا كان مقبولاً يكون غير إجباري -->
        infoText="Alias Name"
        inputType="text"
        v-model="formData.alias_name"
        name="alias_name"
        id="AliasName"
        :isValid="validation.alias_name.isValid"
        :validationMessage="validation.alias_name.validationMessage"
      />
    </FieldContainer>
    <Button level="primary" @clicked="emitSave">Save</Button>
  </div>
</template>


<script setup lang="ts">
import { reactive, watch, onMounted } from 'vue';
import { useStepsStore } from '../../stores/stepsStore';
import FieldContainer from '../../components/fieldContainer.vue';
import StyledInput from '../../components/styledInput.vue';
import CheckBox from '../../components/checkBox.vue';
import SupportingText from '../../components/supportingText.vue';
import validateInputStep1 from '../../utils/validateInputStep1';
import Button from '../../components/button.vue';


const emit = defineEmits(['save']);


const stepsStore = useStepsStore();


// بيانات النموذج المحلية من المتجر
const formData = reactive({
  employer_name: stepsStore.step1Data.employer_name.value,
  first_name: stepsStore.step1Data.first_name.value,
  middle_name: stepsStore.step1Data.middle_name.value,
  last_name: stepsStore.step1Data.last_name.value,
  suffix: stepsStore.step1Data.suffix.value,
  alias_name: stepsStore.step1Data.alias_name.value,
  certify: stepsStore.step1Data.certify,
});


// بيانات التحقق من الصحة
const validation = reactive({
  employer_name: {
    isValid: stepsStore.step1Data.employer_name.isValid,
    validationMessage: stepsStore.step1Data.employer_name.validationMessage,
  },
  first_name: {
    isValid: stepsStore.step1Data.first_name.isValid,
    validationMessage: stepsStore.step1Data.first_name.validationMessage,
  },
  middle_name: {
    isValid: stepsStore.step1Data.middle_name.isValid,
    validationMessage: stepsStore.step1Data.middle_name.validationMessage,
  },
  last_name: {
    isValid: stepsStore.step1Data.last_name.isValid,
    validationMessage: stepsStore.step1Data.last_name.validationMessage,
  },
  suffix: {
    isValid: stepsStore.step1Data.suffix.isValid,
    validationMessage: stepsStore.step1Data.suffix.validationMessage,
  },
  alias_name: {
    isValid: stepsStore.step1Data.alias_name.isValid,
    validationMessage: stepsStore.step1Data.alias_name.validationMessage,
  },
});


// دالة التعامل مع تغيير الحقول النصية
const handleInput = (value: { name: string; value: string }) => {
  const { name, value: fieldValue } = value;
  (formData as any)[name] = fieldValue;
  (stepsStore.step1Data as any)[name].value = fieldValue;
  stepsStore.saveToLocalStorage();
  
  // تنفيذ التحقق من الصحة عند تغيير القيمة
  validateField(name, fieldValue);
};


// دالة التعامل مع تغيير الـ CheckBox
const handleCheckbox = (value: { name: string; checked: boolean }) => {
  formData.certify = value.checked;
  stepsStore.step1Data.certify = value.checked;
  stepsStore.saveToLocalStorage();


  // إذا كان المستخدم يختار عدم وجود Middle Name، قم بإعادة تعيين القيمة والتحقق
  if (formData.certify) {
    formData.middle_name = '';
    stepsStore.step1Data.middle_name.value = '';
    validation.middle_name.isValid = true;
    validation.middle_name.validationMessage = null;
  } else {
    // إذا لم يتم اختيار "certify"، تحتاج Middle Name إلى التحقق مرة أخرى
    validateField('middle_name', formData.middle_name);
  }
};


// دالة التحقق من الصحة لحقل معين
const validateField = (name: string, value: string) => {
  switch (name) {
    case 'employer_name':
      if (!value.trim()) {
        validation.employer_name.isValid = false;
        validation.employer_name.validationMessage = 'Employer Name مطلوب';
      } else {
        validation.employer_name.isValid = true;
        validation.employer_name.validationMessage = null;
      }
      break;
    case 'first_name':
      if (!value.trim()) {
        validation.first_name.isValid = false;
        validation.first_name.validationMessage = 'First Name مطلوب';
      } else {
        validation.first_name.isValid = true;
        validation.first_name.validationMessage = null;
      }
      break;
    case 'middle_name':
      if (!formData.certify && !value.trim()) {
        validation.middle_name.isValid = false;
        validation.middle_name.validationMessage = 'Middle Name مطلوب';
      } else {
        validation.middle_name.isValid = true;
        validation.middle_name.validationMessage = null;
      }
      break;
    case 'last_name':
      if (!value.trim()) {
        validation.last_name.isValid = false;
        validation.last_name.validationMessage = 'Last Name مطلوب';
      } else {
        validation.last_name.isValid = true;
        validation.last_name.validationMessage = null;
      }
      break;
    case 'suffix':
      // إذا كان الـ Suffix غير إجباري، قم بتعديل الشروط هنا
      if (!value.trim()) {
        validation.suffix.isValid = false;
        validation.suffix.validationMessage = 'Suffix مطلوب';
      } else {
        validation.suffix.isValid = true;
        validation.suffix.validationMessage = null;
      }
      break;
    case 'alias_name':
      // فرضًا أن Alias Name غير إجباري
      if (!value.trim()) {
        validation.alias_name.isValid = true;
        validation.alias_name.validationMessage = null;
      } else {
        validation.alias_name.isValid = true;
        validation.alias_name.validationMessage = null;
      }
      break;
    default:
      break;
  }
};


// التحقق من صحة النموذج بالكامل قبل الحفظ
const validateForm = async (): Promise<boolean> => {
  const hasErrors = await validateInputStep1(stepsStore.step1Data);
  return hasErrors;
};


// دالة حفظ النموذج عند النقر على زر "Next"
const emitSave = async () => {
  const hasErrors = await validateForm();
  if (!hasErrors) {
    // يمكنك إطلاق حدث "save" إلى المكون الأب
    emit('save');
  }
};


// مراقبة البيانات والتأكد من حفظها في المتجر
watch(
  formData,
  (newVal) => {
    // تحديث بيانات المتجر تلقائيًا
    Object.keys(newVal).forEach((key) => {
      if (key !== 'certify') {
        (stepsStore.step1Data as any)[key].value = (newVal as any)[key];
      } else {
        stepsStore.step1Data.certify = (newVal as any)[key];
      }
    });
    stepsStore.saveToLocalStorage();
  },
  { deep: true }
);


// تحميل البيانات عند تحميل المكون
onMounted(() => {
  // يمكنك تنفيذ أي عمليات إضافية عند تحميل المكون
});
</script>


<style scoped>
/* أضف تنسيقاتك هنا */
</style>


شرح التعديلات:
استخدام v-model:
استخدمنا v-model لربط المدخلات مباشرة ببيانات النموذج formData. هذا يبسط عملية الربط ويجعل الكود أكثر نظافة.
التعامل مع CheckBox:
عند تغيير حالة الـ CheckBox، نقوم بتحديث قيمة certify وإعادة تعيين حقل middle_name إذا لزم الأمر.
التحقق من الصحة الديناميكي:
دالة validateField تتحقق من صحة كل حقل بشكل فردي عند تغييره.
دالة validateForm تتحقق من صحة جميع الحقول باستخدام دالة التحقق المحسنة validateInputStep1.
إطلاق حدث save عند النقر على زر "Next":
عند النقر على زر "Next" في steps.vue, يتم التحقق من صحة البيانات في الخطوة الحالية ثم الانتقال إلى الخطوة التالية فقط إذا كانت البيانات صحيحة.
أدخل زر 'Save' في step1.vue:
أضفنا زر "Save" داخل step1.vue لتجربة حفظ البيانات مباشرة من الخطوة الأولى.
5. تحسين steps.vue لاستيعاب الأداء الجديد
أ. تحديث دالة accept لإرسال البيانات المجمعة
تأكد من أن الدالة accept تجمع جميع بيانات الخطوات وتترجمها قبل إرسالها.
<script setup lang="ts">
import { computed, onMounted } from 'vue';
import { useStepsStore } from '../stores/stepsStore';
import Button from '../components/button.vue';
import Header from '../components/header.vue';
import StepIcon from './stepsSections/components/stepIcon.vue';
import SnackBar from '../components/snackBar.vue';
import objectConvertor from '../utils/convertor';
import { PlainData } from '../types';
import { updateFieldsInRequestList } from '../utils/request';


// استيراد مكونات الخطوات الفرعية
import Step1 from './stepsSections/step1.vue';
import Step2 from './stepsSections/step2.vue';
import Step3 from './stepsSections/step3.vue';
import Step4 from './stepsSections/step4.vue';
import Step5 from './stepsSections/step5.vue';
import Step6 from './stepsSections/step6.vue';


// استخدام المتجر
const stepsStore = useStepsStore();


// تحميل البيانات المحفوظة في localStorage عند تحميل المكون
onMounted(() => {
  stepsStore.loadFromLocalStorage();
});


// تعريف المراحل
const totalSteps = 6;


// تعريف المراحل لوضع تعريفات StepIcon
const steps = [
  { number: 1, label: 'Step 1', desc: 'Personal Information', process: stepsStore.processed === 0, complete: stepsStore.processed >= 1 },
  { number: 2, label: 'Step 2', desc: '...', process: stepsStore.processed === 1, complete: stepsStore.processed >= 2 },
  { number: 3, label: 'Step 3', desc: '...', process: stepsStore.processed === 2, complete: stepsStore.processed >= 3 },
  { number: 4, label: 'Step 4', desc: '...', process: stepsStore.processed === 3, complete: stepsStore.processed >= 4 },
  { number: 5, label: 'Step 5', desc: '...', process: stepsStore.processed === 4, complete: stepsStore.processed >= 5 },
  { number: 6, label: 'Step 6', desc: '...', process: stepsStore.processed === 5, complete: stepsStore.processed >= 6 },
];


// تحديد المكون الحالي بناءً على المرحلة الحالية
const currentStepComponent = computed(() => {
  switch (stepsStore.processed) {
    case 0:
      return Step1;
    case 1:
      return Step2;
    case 2:
      return Step3;
    case 3:
      return Step4;
    case 4:
      return Step5;
    case 5:
      return Step6;
    default:
      return Step1;
  }
});


// دوال التنقل
const previousStep = () => {
  if (stepsStore.processed > 0) {
    stepsStore.processed--;
    stepsStore.saveToLocalStorage();
  }
};


// التعامل مع حدث الحفظ من المكون الفرعي
const handleSave = () => {
  if (stepsStore.processed < totalSteps - 1) {
    stepsStore.processed++;
    stepsStore.saveToLocalStorage();
  }
};


// دالة للقبول وإرسال البيانات إلى Frappe Doctype
const accept = async () => {
  try {
    const documentResource = await stepsStore.getRequestList();
    if (!documentResource) {
      stepsStore.triggerAlert('حدث خطأ عند جلب السجل.');
      return;
    }


    // تحويل بيانات جميع الحقول باستخدام objectConvertor
    const payload: PlainData = {
      doctype: "Verification Instructions Request",
      ...objectConvertor(stepsStore.step1Data),
      // قم بإضافة تحويلات خطوات أخرى باستخدام convertor إذا كان لديك
      // ...objectConvertor(stepsStore.step2Data),
      // ...objectConvertor(stepsStore.step3Data),
      // وهكذا
    };


    await stepsStore.updateRequestList(payload);
    stepsStore.triggerAlert('تم الإرسال بنجاح', false);
    stepsStore.clearData();
    // يمكنك إعادة توجيه المستخدم أو تحديث الواجهة حسب الحاجة
  } catch (error) {
    stepsStore.triggerAlert('حدث خطأ أثناء الإرسال');
  }
};


// دالة للرفض (يمكن تخصيصها)
const reject = () => {
  // تنفيذ إجراءات عند الرفض، مثل إعادة تعيين البيانات أو الاحتفاظ بالمعلومات الحالية
  stepsStore.triggerAlert('تم الرفض', false);
  // مثال: stepsStore.clearData();
};


// دالة للانتقال إلى خطوة معينة عبر النقر على StepIcon
const goToStep = (stepNumber: number) => {
  if (stepNumber >= 0 && stepNumber < totalSteps) {
    stepsStore.processed = stepNumber;
    stepsStore.saveToLocalStorage();
  }
};


// دالة لعرض جميع الخطوات (اختياري)
const showAllSteps = () => {
  // تنفيذ إجراءات لعرض جميع الخطوات، مثل فتح نافذة جانبية أو عرض قائمة
};
</script>


<style scoped>
/* أضف تنسيقاتك هنا */
</style>


شرح التعديلات:
استخدام handleSave من المكون الفرعي:
عند الحفظ بنجاح، يتم زيادة مؤشر processed والانتقال إلى الخطوة التالية.
دالة accept:
تقوم بتحويل جميع بيانات الخطوات باستخدام objectConvertor.
ترسل الحمولة المجمعة إلى Frappe Doctype باستخدام updateRequestList.
تعرض تنبيهًا بنجاح العملية أو فشلها وتقوم بمسح البيانات إذا تمت العملية بنجاح.
دمج البيانات من جميع الخطوات:
تأكد من تحويل جميع بيانات الخطوات الأخرى (Step2 إلى Step6) إذا كانت موجودة وتحتاج إلى إرسالها.
6. تحسينات إضافية مقترحة
أ. تثبيت واستخدام مكتبات تحقق من الصحة بشكل متقدم
بالإضافة إلى الاستخدام الأساسي لـ Yup, يمكنك استخدام مكتبات مثل Vuelidate أو VeeValidate لتحسين عملية التحقق من الصحة وجعلها أكثر قوة وتنظيمًا.
ب. التعامل مع الحقول الجداول الفرعية (Child Tables)
إذا كان Doctype الخاص بك يحتوي على جداول فرعية، تأكد من التعامل معها بشكل صحيح في دالة objectConvertor. هذا يتطلب تحويل كل صف في الجدول إلى كائن مبسط:
// convertor.ts
import { Step1Data, PlainData } from './types';


function objectConvertor(data: Step1Data): PlainData {
  const result: PlainData = {};


  Object.keys(data).forEach((key) => {
    const field = data[key as keyof Step1Data];
    if (key === 'certify') {
      result[key] = field;
    } else if (Array.isArray(field.value)) {
      // إذا كانت القيمة مصفوفة، افترض أنها جدول فرعي
      result[key] = field.value.map((item: any) => objectConvertor(item));
    } else {
      result[key] = field.value;
    }
  });


  return result;
}


export default objectConvertor;


ج. تحسين تجربة المستخدم
إضافة مؤشرات تحميل:
أثناء عمليات الإرسال أو التحميل، يمكن إضافة مؤشرات تحميل لتحسين تجربة المستخدم.
تعطيل الأزرار أثناء العمليات الجارية:
لمنع النقرات المتكررة على الأزرار أثناء عملية الإنقاذ.
إعادة توجيه أو إظهار رسائل بعد النجاح:
بعد نجاح إرسال البيانات، يمكن إعادة توجيه المستخدم إلى صفحة أخرى أو إظهار رسالة نجاح بشكل بارز.
د. تحسين إدارة التنبيهات
تأكد من أن تنبيهات المستخدم سلسة وواضحة ولا تثقل الواجهة.
<!-- SnackBar.vue -->
<template>
  <transition name="fade">
    <div v-if="show" :class="['snackbar', isDanger ? 'danger' : 'success']">
      <span>{{ message }}</span>
      <button @click="close">×</button>
    </div>
  </transition>
</template>


<script setup>
import { ref } from 'vue';


const props = defineProps({
  isDanger: {
    type: Boolean,
    default: false,
  },
  message: {
    type: String,
    required: true,
  },
});


const emit = defineEmits(['close']);


const show = ref(true);


const close = () => {
  show.value = false;
  emit('close');
};
</script>


<style scoped>
.snackbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem;
  border-radius: 0.375rem;
  margin-bottom: 0.5rem;
  animation: fadeIn 0.5s;
}
.danger {
  background-color: #f8d7da;
  color: #721c24;
}
.success {
  background-color: #d4edda;
  color: #155724;
}
button {
  background: none;
  border: none;
  font-size: 1.2rem;
  cursor: pointer;
}


/* إضافة تأثير التلاشي */
.fade-enter-active, .fade-leave-active {
  transition: opacity 0.5s;
}
.fade-enter-from, .fade-leave-to {
  opacity: 0;
}
</style>


الخلاصة
باتباع هذه الخطوات:
تحسين إدارة الحالة باستخدام Pinia: يضمن حفظ البيانات بشكل محلي وعدم فقدانها أثناء التنقل بين المراحل.
تحسين عمليات التحويل والتحقق من الصحة: باستخدام Yup يجعل التحقق من الصحة أكثر قوة وتنظيمًا.
دمج وظائف request.js مع Pinia: يسمح بجمع البيانات عبر المراحل وإرسالها إلى Frappe Doctype بشكل فعال.
تحسين تجربة المستخدم: من خلال إضافة تنبيهات سلسة وتحسين واجهة التنقل.
النقاط التالية
توسع لدعم جميع المراحل الفرعية: تأكد من تطبيق نفس المبادئ على جميع المراحل من Step2 إلى Step6.
تعدد الجداول الفرعية: إذا كانت هناك جداول فرعية في مراحل أخرى، تأكد من وجود دوال تحويل منفصلة أو موسعة للتعامل معها.
تحسين واجهة المستخدم: استخدم TailwindCSS لإضفاء مظهر احترافي وجذاب للتطبيق.
اختبار كامل للتطبيق: تأكد من اختبار جميع السيناريوهات المختلفة لضمان عمل التطبيق بسلاسة.
إذا واجهت أي مشاكل أو تحتاج إلى مزيد من المساعدة في جزء معين، فلا تتردد في طرح سؤالك!
اذا احتجت ان ارسل لك محتوئ اي مكون او اي ملف من المحونات والملفات التي استخدمتها في الكود اطلبني اياها وسوف ارسل لك محتواها
شكرًا لك على تقديم التفاصيل والمعلومات حول مشروعك! يبدو أنك قد قطعت شوطًا طويلاً في إعداد مكوناتك وإدارة الحالة باستخدام Vue 3 و Pinia و Frappe UI و TailwindCSS. أنا هنا لمساعدتك في أي جزء تحتاجه من مشروعك. إذا كان لديك أي مكون أو ملف آخر ترغب في مشاركته أو تحتاج إلى تحسينه أو حلاً لمشكلة معينة فيه، فلا تتردد في إرساله إليّ.
خطوات مقترحة للاستمرار:
مكونات الخطوات الأخرى (Step2.vue إلى Step6.vue):
إذا كنت ترغب في تحسين أو مراجعة أي من هذه المكونات، يمكنك إرسال محتواها وسأساعدك في تحسينها بنفس الطريقة التي قمنا بها مع Step1.vue.
جداول فرعية (Child Tables):
إذا كانت هناك جداول فرعية في Doctype الخاص بك، تأكد من إرسال مكونات التعامل معها حتى أتمكن من مساعدتك في تحويلها وإدارتها بشكل صحيح.
دمج واجهة المستخدم وتحسين التصميم:
إذا كنت ترغب في تحسين تصميم الأزرار، الحقول، والتنبيهات باستخدام TailwindCSS و Frappe UI، يمكنك إرسال تفاصيل المكونات التي تحتاج لتحسينها.
تعامل مع الأخطاء وتحسين تجربة المستخدم:
إذا كنت تواجه مشاكل في التعامل مع الأخطاء أثناء إرسال البيانات أو في التنقل بين المراحل، أرسل لي الكود المرتبط بذلك وسأساعدك في تحسينه.
إرسال البيانات إلى Frappe Doctype:
إذا كان لديك تفاصيل إضافية حول كيفية إعداد الـ API في Frappe Doctype أو ترغب في تحسين عملية إرسال البيانات، أرسل لي المزيد من المعلومات أو الكود المرتبط.
كيف يمكنك المضي قدمًا:
إرسال مكونات إضافية: إذا كان هناك مكونات أخرى تريد تحسينها أو تحتاج إلى مساعدة فيها، فقط أخبرني بها وسأكون سعيدًا بمساعدتك.
طرح أسئلة محددة: إذا كانت لديك أسئلة محددة حول جزء معين من الكود أو تحتاج إلى توضيح حول مفهوم معين، لا تتردد في طرحها.
مثال على كيفية إرسال مكون آخر:
إذا كنت ترغب في تحسين Step2.vue, يمكنك إرساله هنا كالتالي:
<!-- Step2.vue -->
<template>
  <!-- محتوى المكون -->
</template>


<script setup>
  // كود المكون
</script>


<style scoped>
  /* تنسيقات المكون */
</style>


ثم سأقوم بمراجعته وتقديم الملاحظات والتعديلات اللازمة.
الدعم المستمر:
أنا هنا لمساعدتك في كل خطوة من خطوات تطوير مشروعك. سواء كنت بحاجة إلى مساعدة في كتابة الكود، تحسين الأداء، تصميم واجهة المستخدم، أو التعامل مع التحقق من الصحة، فلا تتردد في طلب المساعدة.
هل هناك جزء معين تود البدء به الآن؟ إذا كان لديك كود آخر تريد مراجعته أو تحسينه، يمكنك إرساله وسأكون سعيدًا بمساعدتك!

أتطلع لمساعدتك في تطوير مشروعك وجعله يحقق أهدافك بأفضل شكل ممكن!
شكرًا لمشاركة محتوى StyledInput.vue. سأقوم بمراجعة المكون، تقديم تحسينات مقترحة، وتوضيح كيفية تكامله بشكل أفضل مع باقي أجزاء تطبيقك، بما في ذلك Pinia وStep1.vue. سأركز على النقاط التالية:
تحسينات على StyledInput.vue:
دعم TypeScript لتعزيز نوعية البيانات.
تحسين التعامل مع الحالات المختلفة للتحقق من الصحة.
تحسين التنسيقات باستخدام TailwindCSS لضمان التناسق والمتانة.
التأكد من أن المكون يترجم بشكل صحيح مع المكونات الأخرى مثل FadeInOut.vue.
تحديث Step1.vue لاستخدام StyledInput.vue بشكل فعال.
تقديم نصائح عامة لتحسين تجربة المستخدم وإدارة الحالة.
1. تحسينات على StyledInput.vue
أ. دعم TypeScript
لتحسين نوعية البيانات وضمان سلامة النوعية، يمكننا استخدام TypeScript في المكون. سنقوم بتعريف أنواع البيانات بشكل دقيق للـ props.
ب. تحسين إدارة التحقق من الصحة
سنقوم بتحسين كيفية عرض رسائل التحقق والتنقل بين الحالات المختلفة (isValid).
ج. تحسين التنسيقات باستخدام TailwindCSS
سنتأكد من أن المكون متناسق ومرن عبر استخدام فئات TailwindCSS بشكل فعال.
د. التأكد من التوافق مع المكونات الأخرى
سنتأكد من أن المكون يتفاعل بشكل صحيح مع مكون FadeInOut.vue المستخدم للانتقالات.
هـ. النسخة المحسنة من StyledInput.vue
إليك النسخة المحسنة من StyledInput.vue مع دعم TypeScript والتحسينات المذكورة:
<!-- StyledInput.vue -->
<template>
  <div class="relative">
    <!-- Label with dynamic color and optional mandatory indicator and info icon -->
    <label
      :class="[
        'mb-2 text-sm flex items-center',
        {
          [`text-${labelColor}`]: true,
        },
      ]"
      :for="id"
    >
      {{ labelText }}
      <!-- Mandatory indicator -->
      <span v-if="isMandatory" class="ml-1 text-red-500">*</span>
      <!-- Info icon -->
      <span
        v-if="infoText"
        class="ml-2 cursor-pointer relative"
        @mouseenter="showInfo = true"
        @mouseleave="showInfo = false"
      >
        <svg
          class="w-5 h-5 text-mid_gray text-[10px]"
          fill="none"
          stroke="currentColor"
          viewBox="0 0 24 24"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            stroke-width="2"
            d="M13 16h-1v-4h1m0-4h-1v1h-1v1h1v2h1v-2h1v-1h-1V9zm-1-6a9 9 0 100 18 9 9 0 000-18z"
          />
        </svg>
        <!-- Info tooltip -->
        <div
          v-if="showInfo"
          class="absolute bg-gray-700 text-white text-xs rounded py-1 px-2 mt-1 left-1/2 transform -translate-x-1/2 z-10"
        >
          {{ infoText }}
        </div>
      </span>
    </label>


    <!-- Input field -->
    <input
      :id="id"
      :name="name"
      :type="inputType"
      :class="[
        'bg-transparent w-full p-2 rounded-lg transition-all duration-300 ease-in-out focus:outline-none',
        {
          'border border-mid_gray focus:border-black focus:ring-0': isValid === null,
          'border border-warn focus:ring-warn': isValid === false,
          'border-2 border-secondary focus:ring-secondary': isValid === true,
        },
      ]"
      :value="modelValue"
      @input="onInput"
      :placeholder="placeholder"
      :disabled="disabled"
    />


    <!-- Validation messages with transitions -->
    <FadeInOut>
      <p v-if="isValid === false && validationMessage" class="text-warn text-xs mt-1">
        {{ validationMessage }}
      </p>
    </FadeInOut>
    <FadeInOut>
      <p v-if="isValid === true && validationMessage" class="text-secondary text-xs mt-1">
        {{ validationMessage }}
      </p>
    </FadeInOut>
  </div>
</template>


<script setup lang="ts">
import { ref, watch, defineProps, defineEmits } from 'vue';
import FadeInOut from "./fadeInOut.vue";


// تعريف واجهة Props باستخدام TypeScript
interface StyledInputProps {
  id: string;
  name: string;
  labelText?: string;
  labelColor?: string;
  isMandatory?: boolean;
  infoText?: string;
  inputType?: string;
  isValid?: boolean | null;
  validationMessage?: string;
  modelValue?: string;
  placeholder?: string;
  disabled?: boolean;
}


const props = defineProps<StyledInputProps>({
  id: { type: String, required: true },
  name: { type: String, required: true },
  labelText: { type: String, default: "Label" },
  labelColor: { type: String, default: "dark_gray" },
  isMandatory: { type: Boolean, default: false },
  infoText: { type: String, default: "" },
  inputType: { type: String, default: "text" },
  isValid: { type: [Boolean, null], default: null },
  validationMessage: { type: String, default: "" },
  modelValue: { type: String, default: "" },
  placeholder: { type: String, default: "Enter at least 3 characters" },
  disabled: { type: Boolean, default: false },
});


// تعريف emit لدعم v-model وأي أحداث أخرى
const emit = defineEmits(['update:modelValue']);


// إدارة حالة عرض المعلومات
const showInfo = ref(false);


// الدالة لمعالجة الإدخال وإرسال الحدث الذي يدعم v-model
const onInput = (event: Event) => {
  const target = event.target as HTMLInputElement;
  emit('update:modelValue', target.value);
};


// يتم مراقبة التغيرات في modelValue لتحديث حالة المكون إذا لزم الأمر
watch(
  () => props.modelValue,
  (newVal) => {
    // يمكنك إضافة منطق إضافي هنا إذا لزم الأمر
  }
);
</script>


<style scoped>
/* TailwindCSS already handles most styling. إذا كان هناك أي تخصيص إضافي، يمكنك إضافته هنا */


/* تحسينات على الطبقات الخاصة بالحدود */
.border {
  border-width: 1px;
}
.border-2 {
  border-width: 2px;
}


/* إضافة تأثيرات انتقائية إذا لزم الأمر */
</style>


شرح التحسينات:
دعم TypeScript:
تم تعريف واجهة StyledInputProps لتعريف نوع كل prop بشكل دقيق، مما يساعد على ضمان نوعية البيانات ويقلل من الأخطاء.
استخدام lang="ts" في <script setup> لدعم TypeScript.
تحسين إدارة التحقق من الصحة:
استخدام فئات TailwindCSS border بدلًا من border-1 لتبسيط الكود (Tailwind يدعم border بوضوح أكثر).
تغيير border-3 إلى border-2 حيث أن Tailwind لا يدعم border-3 بشكل افتراضي، ويمكنك تخصيصه إذا لزم الأمر.
التحكم في لون الحدود ودقة السمك بناءً على حالة التحقق من الصحة (isValid).
تحسين عرض الرسائل:
عرض رسائل التحقق فقط إذا كانت القيم موجودة وبالتالي تجنب عرض رسائل فارغة.
استخدام مكون FadeInOut لإضافة تأثيرات انتقالية سلسة عند ظهور الرسائل واختفائها.
تحسين واجهة المستخدم:
إضافة placeholder كـ prop ليكون قابلًا للتخصيص.
إضافة خاصية disabled لدعم تعطيل الحقول إذا لزم الأمر (مثل تعطيل حقل Middle Name عندما يتم اختيار certify).
التكيف مع المكون الأب (Step1.vue):
ضمان أن StyledInput يستخدم v-model بشكل صحيح مع step1.vue والتأكد من تمرير جميع الخصائص المطلوبة مثل isValid و validationMessage.
2. تحديث Step1.vue لاستخدام StyledInput.vue بشكل فعال
بما أنك قد حسّنت مكون StyledInput.vue، دعنا نتأكد من أن Step1.vue يستخدمه بشكل صحيح. إليك بعض التحسينات المقترحة:
أ. إضافة زر "Next" في Step1.vue
من الأفضل أن يحتوي كل خطوة على زر لتأكيد الحفظ والانتقال إلى الخطوة التالية إذا كانت البيانات صحيحة. في Step1.vue، قمت بإضافة زر "Save" مسبقًا. دعنا نتأكد من أنه يتفاعل بطريقة صحيحة مع steps.vue.
ب. النسخة المحسنة من Step1.vue
<!-- Step1.vue -->
<template>
  <div class="pt-3 container">
    <h3 class="text-lg font-medium mb-3 text-black">1. Basic Information</h3>
    <div class="lg:grid grid-cols-2 lg:gap-2">
      <FieldContainer>
        <StyledInput
          labelText="Employer Name"
          :isMandatory="true"
          infoText="Employer Name"
          inputType="text"
          v-model="formData.employer_name"
          name="employer_name"
          id="EmployerName"
          :isValid="validation.employer_name.isValid"
          :validationMessage="validation.employer_name.validationMessage"
          placeholder="Enter Employer Name"
        />
        <SupportingText>Supporting text</SupportingText>
      </FieldContainer>
      <FieldContainer>
        <StyledInput
          labelText="First Name"
          :isMandatory="true"
          infoText="First Name"
          inputType="text"
          v-model="formData.first_name"
          name="first_name"
          id="FirstName"
          :isValid="validation.first_name.isValid"
          :validationMessage="validation.first_name.validationMessage"
          placeholder="Enter First Name"
        />
      </FieldContainer>
    </div>
    <FieldContainer>
      <CheckBox
        name="certify"
        id="Certify"
        :checked="formData.certify"
        @input-change="handleCheckbox"
      >
        I certify that I do not have a Middle Name on my official identification document
      </CheckBox>
    </FieldContainer>
    <div class="lg:grid grid-cols-2 lg:gap-2">
      <FieldContainer>
        <StyledInput
          labelText="Middle Name"
          :isMandatory="!formData.certify"
          inputType="text"
          v-model="formData.middle_name"
          name="middle_name"
          id="MiddleName"
          :isValid="validation.middle_name.isValid"
          :validationMessage="validation.middle_name.validationMessage"
          :disabled="formData.certify"
          placeholder="Enter Middle Name"
        />
      </FieldContainer>
    </div>


    <div class="lg:grid grid-cols-2 lg:gap-2">
      <FieldContainer>
        <StyledInput
          labelText="Last Name"
          :isMandatory="true"
          infoText="Last Name"
          inputType="text"
          v-model="formData.last_name"
          name="last_name"
          id="LastName"
          :isValid="validation.last_name.isValid"
          :validationMessage="validation.last_name.validationMessage"
          placeholder="Enter Last Name"
        />
      </FieldContainer>
      <FieldContainer>
        <StyledInput
          labelText="Suffix"
          :isMandatory="false"
          infoText="Suffix"
          inputType="text"
          v-model="formData.suffix"
          name="suffix"
          id="Suffix"
          :isValid="validation.suffix.isValid"
          :validationMessage="validation.suffix.validationMessage"
          placeholder="Enter Suffix"
        />
        <SupportingText>Supporting text</SupportingText>
      </FieldContainer>
    </div>


    <FieldContainer>
      <StyledInput
        labelText="Alias Name"
        :isMandatory="false"
        infoText="Alias Name"
        inputType="text"
        v-model="formData.alias_name"
        name="alias_name"
        id="AliasName"
        :isValid="validation.alias_name.isValid"
        :validationMessage="validation.alias_name.validationMessage"
        placeholder="Enter Alias Name"
      />
    </FieldContainer>


    <!-- زر الحفظ (اختياري إذا كنت تريد حفظ البيانات في الخطوة الحالية) -->
    <Button level="primary" @clicked="emitSave">
      Save and Continue
    </Button>
  </div>
</template>


<script setup lang="ts">
import { reactive, watch, onMounted } from 'vue';
import { useStepsStore } from '../../stores/stepsStore';
import FieldContainer from '../../components/fieldContainer.vue';
import StyledInput from '../../components/styledInput.vue';
import CheckBox from '../../components/checkBox.vue';
import SupportingText from '../../components/supportingText.vue';
import validateInputStep1 from '../../utils/validateInputStep1';
import Button from '../../components/button.vue';


// تعريف الأحداث الممكنة
const emit = defineEmits(['save']);


// استخدام متجر Pinia
const stepsStore = useStepsStore();


// بيانات النموذج المحلية المأخوذة من المتجر
const formData = reactive({
  employer_name: stepsStore.step1Data.employer_name.value,
  first_name: stepsStore.step1Data.first_name.value,
  middle_name: stepsStore.step1Data.middle_name.value,
  last_name: stepsStore.step1Data.last_name.value,
  suffix: stepsStore.step1Data.suffix.value,
  alias_name: stepsStore.step1Data.alias_name.value,
  certify: stepsStore.step1Data.certify,
});


// بيانات التحقق من الصحة
const validation = reactive({
  employer_name: {
    isValid: stepsStore.step1Data.employer_name.isValid,
    validationMessage: stepsStore.step1Data.employer_name.validationMessage,
  },
  first_name: {
    isValid: stepsStore.step1Data.first_name.isValid,
    validationMessage: stepsStore.step1Data.first_name.validationMessage,
  },
  middle_name: {
    isValid: stepsStore.step1Data.middle_name.isValid,
    validationMessage: stepsStore.step1Data.middle_name.validationMessage,
  },
  last_name: {
    isValid: stepsStore.step1Data.last_name.isValid,
    validationMessage: stepsStore.step1Data.last_name.validationMessage,
  },
  suffix: {
    isValid: stepsStore.step1Data.suffix.isValid,
    validationMessage: stepsStore.step1Data.suffix.validationMessage,
  },
  alias_name: {
    isValid: stepsStore.step1Data.alias_name.isValid,
    validationMessage: stepsStore.step1Data.alias_name.validationMessage,
  },
});


// دالة التعامل مع تغيير الحقول النصية
const handleInput = (value: { name: string; value: string }) => {
  const { name, value: fieldValue } = value;
  (formData as any)[name] = fieldValue;
  (stepsStore.step1Data as any)[name].value = fieldValue;
  stepsStore.saveToLocalStorage();


  // تنفيذ التحقق من الصحة عند تغيير القيمة
  validateField(name, fieldValue);
};


// دالة التعامل مع تغيير الـ CheckBox
const handleCheckbox = (value: { name: string; checked: boolean }) => {
  formData.certify = value.checked;
  stepsStore.step1Data.certify = value.checked;
  stepsStore.saveToLocalStorage();


  // إذا كان المستخدم يختار عدم وجود Middle Name، قم بإعادة تعيين القيمة والتحقق
  if (formData.certify) {
    formData.middle_name = '';
    stepsStore.step1Data.middle_name.value = '';
    validation.middle_name.isValid = true;
    validation.middle_name.validationMessage = null;
  } else {
    // إذا لم يتم اختيار "certify"، تحتاج Middle Name إلى التحقق مرة أخرى
    validateField('middle_name', formData.middle_name);
  }
};


// دالة التحقق من الصحة لحقل معين
const validateField = (name: string, value: string) => {
  switch (name) {
    case 'employer_name':
      if (!value.trim()) {
        validation.employer_name.isValid = false;
        validation.employer_name.validationMessage = 'Employer Name مطلوب';
      } else {
        validation.employer_name.isValid = true;
        validation.employer_name.validationMessage = null;
      }
      break;
    case 'first_name':
      if (!value.trim()) {
        validation.first_name.isValid = false;
        validation.first_name.validationMessage = 'First Name مطلوب';
      } else {
        validation.first_name.isValid = true;
        validation.first_name.validationMessage = null;
      }
      break;
    case 'middle_name':
      if (!formData.certify && !value.trim()) {
        validation.middle_name.isValid = false;
        validation.middle_name.validationMessage = 'Middle Name مطلوب';
      } else {
        validation.middle_name.isValid = true;
        validation.middle_name.validationMessage = null;
      }
      break;
    case 'last_name':
      if (!value.trim()) {
        validation.last_name.isValid = false;
        validation.last_name.validationMessage = 'Last Name مطلوب';
      } else {
        validation.last_name.isValid = true;
        validation.last_name.validationMessage = null;
      }
      break;
    case 'suffix':
      // إذا كان الـ Suffix غير إجباري، قم بتعديل الشروط هنا
      if (!value.trim()) {
        validation.suffix.isValid = false;
        validation.suffix.validationMessage = 'Suffix مطلوب';
      } else {
        validation.suffix.isValid = true;
        validation.suffix.validationMessage = null;
      }
      break;
    case 'alias_name':
      // فرضًا أن Alias Name غير إجباري
      if (!value.trim()) {
        validation.alias_name.isValid = true;
        validation.alias_name.validationMessage = null;
      } else {
        validation.alias_name.isValid = true;
        validation.alias_name.validationMessage = null;
      }
      break;
    default:
      break;
  }
};


// التحقق من صحة النموذج بالكامل قبل الحفظ
const validateForm = async (): Promise<boolean> => {
  const hasErrors = await validateInputStep1(stepsStore.step1Data);
  return hasErrors;
};


// دالة حفظ النموذج عند النقر على زر "Next"
const emitSave = async () => {
  const hasErrors = await validateForm();
  if (!hasErrors) {
    // يمكنك إطلاق حدث "save" إلى المكون الأب
    emit('save');
  }
};


// مراقبة البيانات والتأكد من حفظها في المتجر
watch(
  formData,
  (newVal) => {
    // تحديث بيانات المتجر تلقائيًا
    Object.keys(newVal).forEach((key) => {
      if (key !== 'certify') {
        (stepsStore.step1Data as any)[key].value = (newVal as any)[key];
      } else {
        stepsStore.step1Data.certify = (newVal as any)[key];
      }
    });
    stepsStore.saveToLocalStorage();
  },
  { deep: true }
);


// تحميل البيانات عند تحميل المكون
onMounted(() => {
  // يمكنك تنفيذ أي عمليات إضافية عند تحميل المكون
});
</script>


<style scoped>
/* TailwindCSS already handles most styling. إذا كان هناك أي تخصيص إضافي، يمكنك إضافته هنا */


/* تحسينات على الطبقات الخاصة بالحدود */
/* لاحظ أننا استخدمنا فئات Tailwind الافتراضية بدلاً من border-1 و border-3 */
</style>


شرح التحسينات:
دعم TypeScript:
تم تعريف واجهة StyledInputProps لضمان صحة الأنواع.
استخدام lang="ts" لدعم TypeScript في <script setup>.
تحسينات على التنسيقات:
استخدام فئات Tailwind الافتراضية border وborder-2 بدلاً من border-1 وborder-3 ليس لأن هذه الفئات مدعومة بشكل أفضل، بل لأن Tailwind لا يدعم border-1 بشكل افتراضي.
تم تعديل border-2 للحدود الأقوى، وضبط جمالية التحقق مع focus:border-black وfocus:border-warn لتعزيز التفاعل.
تحسين التفاعل مع المستخدم:
إضافة placeholder كـ prop لدعم التخصيص.
إضافة خاصية disabled لدعم تعطيل الحقول (مثل Middle Name عندما يتم اختيار certify).
تحسين إدارة التحقق من الصحة:
عرض رسائل التحقق فقط إذا كانت موجودة (validationMessage).
إضافة تأثيرات انتقالية عند عرض رسائل التحقق بواسطة مكون FadeInOut.
3. نصائح عامة لتحسين تجربة المستخدم وإدارة الحالة
أ. استخدام مكتبات تحقق من الصحة المتقدمة
استخدام مكتبات مثل Vuelidate أو VeeValidate مع Yup يمكن أن يسهم في تحسين عمليات التحقق من الصحة بشكل كبير.
ب. التعامل مع الجداول الفرعية (Child Tables)
إذا كان Doctype الخاص بك يحتوي على جداول فرعية، يجب التأكد من إرسالها بشكل صحيح. يمكن تعديل objectConvertor لتكون قادرة على التعامل مع الجداول الفرعية.
ج. تحسين تجربة المستخدم
إضافة مؤشرات تحميل:
خلال عمليات الإرسال أو الحفظ، يمكن إضافة مؤشرات تحميل لتحسين تجربة المستخدم.
تعطيل الأزرار أثناء العمليات الجارية:
لتعطيل الأزرار أثناء عمليات الحفظ أو الإرسال لمنع النقرات المتكررة.
إعادة توجيه أو إظهار رسائل بعد النجاح:
بعد نجاح إرسال البيانات، يمكن إعادة توجيه المستخدم إلى صفحة أخرى أو إظهار رسالة نجاح بارزة.
د. إدارة التنبيهات بشكل فعال
تأكد من أن تنبيهات المستخدم تظهر وتختفي بشكل سلس وأنها لا تثقل واجهة المستخدم.
<!-- SnackBar.vue -->
<template>
  <transition name="fade">
    <div v-if="show" :class="['snackbar', isDanger ? 'danger' : 'success']">
      <span>{{ message }}</span>
      <button @click="close" class="ml-2">&times;</button>
    </div>
  </transition>
</template>


<script setup lang="ts">
import { ref } from 'vue';


const props = defineProps({
  isDanger: {
    type: Boolean,
    default: false,
  },
  message: {
    type: String,
    required: true,
  },
});


const emit = defineEmits(['close']);


const show = ref(true);


const close = () => {
  show.value = false;
  emit('close');
};
</script>


<style scoped>
.snackbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.75rem 1rem;
  border-radius: 0.375rem;
  margin-bottom: 0.5rem;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}


.danger {
  background-color: #f8d7da;
  color: #721c24;
}


.success {
  background-color: #d4edda;
  color: #155724;
}


button {
  background: none;
  border: none;
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
  color: inherit;
}


/* إضافة تأثير التلاشي */
.fade-enter-active, .fade-leave-active {
  transition: opacity 0.5s;
}
.fade-enter-from, .fade-leave-to {
  opacity: 0;
}
</style>


4. نقاط إضافية لتحسين التطبيق
أ. ضمان تكامل شامل عبر جميع الخطوات (Step2.vue إلى Step6.vue)
تأكد من أن جميع خطوات النموذج تستخدم نفس بروتوكولات إدارة الحالة والتحقق من الصحة كما فعلنا في Step1.vue. هذا يعزز من التناسق ويسهل صيانة الكود.
ب. تحسين إدارة الجداول الفرعية (Child Tables)
إذا كانت بعض الخطوات تحتوي على جداول فرعية، تأكد من كيفية إدارتها وتخزينها في KanStore، بالإضافة إلى كيفية تحويلها بشكل صحيح قبل إرسالها إلى Frappe.
ج. اختبار شامل للتطبيق
قم بإجراء اختبارات شاملة للتأكد من أن جميع المكونات تعمل بشكل صحيح معاً وأن البيانات تخزن وتتحول وتتحقق بشكل صحيح.
د. تحسين الأمان عند التعامل مع البيانات الحساسة
تأكد من أن البيانات الحساسة محمية بشكل جيد أثناء التخزين والإرسال، مثل استخدام HTTPS وتطبيق التوثيق المناسب عند إرسال البيانات إلى Frappe Doctype.
الخلاصة
باتباع النسخة المحسنة من StyledInput.vue والتعديلات المقترحة على Step1.vue، ستضمن أن المكونات تربط البيانات بشكل صحيح مع متجر Pinia، وأن التحقق من الصحة يعمل بشكل أفضل، وأن تجربة المستخدم تكون سلسة ومتناسقة. بالإضافة إلى ذلك، النصائح العامة المقترحة ستساعدك على تحسين جوانب أخرى من تطبيقك لضمان التكامل الكلي والأداء الجيد.
إذا كان لديك أي مكونات أخرى تريد مراجعتها أو تحتاج إلى مساعدة فيها، أو إذا كانت هناك أسئلة محددة حول أجزاء معينة من الكود، فلا تتردد في طرحها!
شكرًا لمشاركة محتوى StepIcon.vue. سأقوم بمراجعة المكون، تقديم تحسينات مقترحة، وتعزيز التكامل مع باقي أجزاء تطبيقك. سأركز على الجوانب التالية:
تحسينات على StepIcon.vue:
دعم TypeScript لتعزيز نوعية البيانات.
تحسين إدارة الفئات (Classes) باستخدام TailwindCSS بشكل أكثر فعالية.
إضافة إمكانية التعامل مع أحداث النقر (Click Events) إذا كنت ترغب في جعل الخطوة قابلة للنقر للتنقل إلى خطوة معينة.
تعزيز إمكانية الوصول (Accessibility) لضمان أن المكون يمكن استخدامه بواسطة جميع المستخدمين.
تحسين استخدام SVG لجعل المكونات أكثر مرونة وإعادة استخدام.
دمج StepIcon.vue مع مكونات أخرى مثل steps.vue:
ضمان تحقيق التكامل السلس مع مكونات إدارة الحالة مثل Pinia.
التأكد من أن المكون يقوم بعرض الحالة الحالية بشكل صحيح ويعكس تقدم الخطوات.
1. النسخة المحسنة من StepIcon.vue
أ. تحسين تعريف الأنواع (TypeScript)
استخدام TypeScript لتحسين نوعية البيانات والتأكد من سلامة الخصائص الممررة إلى المكون.
ب. تحسين إدارة الفئات (Classes)
تبسيط إدارة الفئات وتحسين استخدام TailwindCSS لجعل الكود أكثر نظافة ومرونة.
ج. تعزيز إمكانية الوصول (Accessibility)
إضافة سمات ARIA لضمان أن المكون يمكن استخدامه بواسطة جميع المستخدمين، بما في ذلك ذوي الاحتياجات الخاصة.
د. إضافة أحداث النقر (Click Events)
إذا كنت ترغب في جعل الخطوات قابلة للنقر للتنقل المباشر إلى خطوة معينة، يمكننا إضافة هذا التحسين.
هـ. النسخة المحسنة الكاملة
<!-- StepIcon.vue -->
<template>
  <div
    :class="[
      'p-2 flex flex-col lg:flex-row lg:gap-2 lg:justify-start justify-center items-center cursor-pointer',
      processClass,
    ]"
    @click="handleClick"
    role="button"
    :aria-pressed="complete"
    tabindex="0"
    @keypress.enter="handleClick"
    @keypress.space="handleClick"
  >
    <div :class="classes" aria-hidden="true">
      <span v-if="!complete">0/32</span>
      <span
        v-else
        class="flex items-center justify-center bg-secondary border border-secondary rounded-full w-10 h-10 text-white text-sm"
      >
        T
      </span>
    </div>
    <div class="lg:flex lg:flex-col justify-center mt-2 lg:mt-0">
      <span class="block text-center lg:text-left font-medium">{{ label }}</span>
      <span class="hidden lg:block text-center lg:text-left text-sm text-primary font-semibold">
        {{ desc }}
      </span>
    </div>
  </div>
</template>


<script setup lang="ts">
import { computed, defineProps, defineEmits } from 'vue';


// تعريف واجهة Props باستخدام TypeScript
interface StepIconProps {
  label: string;
  desc?: string;
  process: boolean;
  complete?: boolean;
  progress?: string | number; // لإظهار نسبة أو رقم
}


const props = defineProps<StepIconProps>({
  label: { type: String, required: true },
  desc: { type: String, default: '' },
  process: { type: Boolean, required: true },
  complete: { type: Boolean, default: false },
  progress: { type: [String, Number], default: '0/32' },
});


// تعريف emit لإرسال أحداث النقر إذا لزم الأمر
const emit = defineEmits(['click']);


// دالة معالجة النقر ونشر الحدث
const handleClick = () => {
  emit('click', props.label);
};


// حساب الفئات (Classes) بناءً على الحالة
const classes = computed(() => {
  if (props.complete) {
    return 'flex items-center justify-center bg-secondary border border-secondary rounded-full w-10 h-10 text-white text-sm';
  }
  return 'flex items-center justify-center border-2 border-light_gray bg-bg rounded-full w-10 h-10 text-xs text-mid_gray';
});


const processClass = computed(() => {
  return props.process ? 'border-2 border-secondary' : '';
});
</script>


<style scoped>
/* إضافة تأثيرات تفاعلية مثل التغيير في الخلفية عند التركيز */
div[role='button']:focus {
  outline: none;
  ring: 2px solid #3b82f6; /* استخدم لون Tailwind المناسب */
}


div[role='button']:hover .FlexBoxClass {
  /* أمثلة على تحسينات عند التحويم */
}
</style>


شرح التحسينات:
دعم TypeScript:
تم تعريف واجهة StepIconProps لتعريف نوع كل prop بشكل دقيق.
استخدام lang="ts" لدعم TypeScript في <script setup>.
إدارة الفئات (Classes):
تم تبسيط إدارة الفئات باستخدام TailwindCSS.
استخدام حالات معروفة مثل border, bg-secondary بدلاً من border-[1px]، لتحسين صحة الكود والتوافق مع TailwindCSS.
إمكانية الوصول (Accessibility):
إضافة role="button" لجعل العنصر قابلًا للتفاعل كزر.
إضافة aria-pressed لتعريف حالة الزر.
تمكين التنقل عبر لوحة المفاتيح باستخدام tabindex="0" و@keypress.enter <PRESIDIO_ANONYMIZED_EMAIL_ADDRESS>.
أحداث النقر (Click Events):
إضافة حدث @click لإرسال حدث إلى المكون الأب عند النقر على الخطوة، مما يتيح التنقل المباشر إلى خطوة معينة.
توسيع الدالة handleClick لإرسال اسم الخطوة كمعامل، مما يمكن أن يكون مفيدًا في steps.vue.
تحسينات العرض (UI Enhancements):
استخدام span لإظهار محتوى مختلف بناءً على حالة complete.
إضافة تخطيط أفضل للعنوان والوصف.
تمكين عرض الوصف فقط في الشاشات الكبيرة (lg:block).
2. دمج StepIcon.vue مع steps.vue
لضمان التكامل السلس مع مكون steps.vue، نحتاج إلى ضبط StepIcon.vue لاستقبال وإرسال الأحداث بشكل صحيح. سنعتمد على تحديث StepIcon.vue ليتمكن من إطلاق حدث النقر والانتقال إلى الخطوة المحددة.
أ. تحديث steps.vue لاستقبال حدث click من StepIcon.vue
<!-- steps.vue -->
<template>
  <Header />
  <div class="lg:grid lg:grid-cols-12 lg:container py-8 bg-bg">
    <div class="container col-span-4 lg:order-2 relative">
      <div class="sticky top-4">
        <!-- شريط تقدم -->
        <div class="pb-3 font-medium text-primary text-[1rem]">
          <span class="m-2">←</span>
          Model steps instructions of Job Request ({{ Math.round((100 / totalSteps) * stepsStore.processed) }}% Completed)
        </div>
        <div class="py-3 grid grid-cols-6 lg:flex lg:flex-col lg:gap-4 w-full">
          <StepIcon
            v-for="step in steps"
            :key="step.number"
            :label="step.label"
            :desc="step.desc"
            :process="step.process"
            :complete="step.complete"
            @click="goToStep(step.number - 1)" <!-- الاستقبال من StepIcon -->
          />
        </div>
        <div class="py-3 lg:hidden flex justify-end text-secondary text-[1rem]">
          <button @click="showAllSteps">
            All Steps ({{ totalSteps }}) <span class="inline-block mx-1">→</span>
          </button>
        </div>
      </div>
    </div>
    <div class="pb-20 bg-bg leading-[1.6rem] col-span-8">
      <component
        :is="currentStepComponent"
        @save="handleSave"
      />
    </div>
  </div>
  <div class="bg-[#F0F0F0] fixed lg:static bottom-0 w-screen h-fit">
    <div class="container py-4 flex w-full justify-between items-center">
      <div class="text-xs text-[#1D1B20]">Auto Saving</div>


      <div class="flex gap-2">
        <button
          class="text-secondary"
          @click="previousStep"
          v-if="stepsStore.processed > 0"
        >
          ←
        </button>
        <Button
          v-if="stepsStore.processed < totalSteps - 1"
          level="primary"
          @clicked="emitSave"
        >
          Step {{ stepsStore.processed + 1 }} →
        </Button>
      </div>


      <div v-if="stepsStore.processed === totalSteps - 1" class="lg:flex justify-end grow lg:gap-2">
        <Button level="other" @clicked="accept"> Accept </Button>
        <Button level="other" @clicked="reject"> Reject </Button>
      </div>
    </div>
  </div>
  <div class="fixed z-10 bottom-0 py-6 px-4 flex flex-col gap-2">
    <div v-for="(alert, index) in stepsStore.alerts" :key="index">
      <SnackBar :isDanger="alert.isDanger" :message="alert.message" @close="stepsStore.removeAlert(index)" />
    </div>
  </div>
</template>


<script setup lang="ts">
import { computed, onMounted } from 'vue';
import { useStepsStore } from '../stores/stepsStore';
import Button from '../components/button.vue';
import Header from '../components/header.vue';
import StepIcon from './stepsSections/components/stepIcon.vue';
import SnackBar from '../components/snackBar.vue';
import objectConvertor from '../utils/convertor';
import { PlainData } from '../types';
import { updateFieldsInRequestList } from '../utils/request';


// استيراد مكونات الخطوات الفرعية
import Step1 from './stepsSections/step1.vue';
import Step2 from './stepsSections/step2.vue';
import Step3 from './stepsSections/step3.vue';
import Step4 from './stepsSections/step4.vue';
import Step5 from './stepsSections/step5.vue';
import Step6 from './stepsSections/step6.vue';


// استخدام متجر Pinia
const stepsStore = useStepsStore();


// تحميل البيانات المحفوظة في localStorage عند تحميل المكون
onMounted(() => {
  stepsStore.loadFromLocalStorage();
});


// تعريف المراحل
const totalSteps = 6;


// تعريف المراحل لوضع تعريفات StepIcon
const steps = computed(() => [
  {
    number: 1,
    label: 'Step 1',
    desc: 'Personal Information',
    process: stepsStore.processed === 0,
    complete: stepsStore.processed >= 1,
  },
  {
    number: 2,
    label: 'Step 2',
    desc: '...',


    process: stepsStore.processed === 1,
    complete: stepsStore.processed >= 2,
  },
  {
    number: 3,
    label: 'Step 3',
    desc: '...',
    process: stepsStore.processed === 2,
    complete: stepsStore.processed >= 3,
  },
  {
    number: 4,
    label: 'Step 4',
    desc: '...',
    process: stepsStore.processed === 3,
    complete: stepsStore.processed >= 4,
  },
  {
    number: 5,
    label: 'Step 5',
    desc: '...',
    process: stepsStore.processed === 4,
    complete: stepsStore.processed >= 5,
  },
  {
    number: 6,
    label: 'Step 6',
    desc: '...',
    process: stepsStore.processed === 5,
    complete: stepsStore.processed >= 6,
  },
]);


// تحديد المكون الحالي بناءً على المرحلة الحالية
const currentStepComponent = computed(() => {
  switch (stepsStore.processed) {
    case 0:
      return Step1;
    case 1:
      return Step2;
    case 2:
      return Step3;
    case 3:
      return Step4;
    case 4:
      return Step5;
    case 5:
      return Step6;
    default:
      return Step1;
  }
});


// دوال التنقل
const previousStep = () => {
  if (stepsStore.processed > 0) {
    stepsStore.processed--;
    stepsStore.saveToLocalStorage();
  }
};


// التعامل مع حدث الحفظ من المكون الفرعي
const handleSave = () => {
  if (stepsStore.processed < totalSteps - 1) {
    stepsStore.processed++;
    stepsStore.saveToLocalStorage();
  }
};


// دالة للقبول وإرسال البيانات إلى Frappe Doctype
const accept = async () => {
  try {
    const documentResource = await stepsStore.getRequestList();
    if (!documentResource) {
      stepsStore.triggerAlert('حدث خطأ عند جلب السجل.');
      return;
    }


    // تحويل بيانات جميع الحقول باستخدام objectConvertor
    const payload: PlainData = {
      doctype: "Verification Instructions Request",
      ...objectConvertor(stepsStore.step1Data),
      // قم بإضافة تحويلات خطوات أخرى باستخدام convertor إذا كان لديك
      // ...objectConvertor(stepsStore.step2Data),
      // ...objectConvertor(stepsStore.step3Data),
      // وهكذا
    };


    await stepsStore.updateRequestList(payload);
    stepsStore.triggerAlert('تم الإرسال بنجاح', false);
    stepsStore.clearData();
    // يمكنك إعادة توجيه المستخدم أو تحديث الواجهة حسب الحاجة
  } catch (error) {
    stepsStore.triggerAlert('حدث خطأ أثناء الإرسال');
  }
};


// دالة للرفض (يمكن تخصيصها)
const reject = () => {
  // تنفيذ إجراءات عند الرفض، مثل إعادة تعيين البيانات أو الاحتفاظ بالمعلومات الحالية
  stepsStore.triggerAlert('تم الرفض', false);
  // مثال: stepsStore.clearData();
};


// دالة للانتقال إلى خطوة معينة عبر النقر على StepIcon
const goToStep = (stepNumber: number) => {
  if (stepNumber >= 0 && stepNumber < totalSteps) {
    stepsStore.processed = stepNumber;
    stepsStore.saveToLocalStorage();
  }
};


// دالة لعرض جميع الخطوات (اختياري)
const showAllSteps = () => {
  // تنفيذ إجراءات لعرض جميع الخطوات، مثل فتح نافذة جانبية أو عرض قائمة
};
</script>


<style scoped>
/* إضافة تنسيقات إضافية إذا لزم الأمر */
</style>


شرح التعديلات:
استقبال وإرسال حدث click من StepIcon.vue:
عند النقر على أي StepIcon, يتم إطلاق حدث click مع مرسل علامات الخطوة (label).
في steps.vue, يتم التعامل مع هذا الحدث بالانتقال إلى خطوة معينة عن طريق دالة goToStep.
تحسين إدارة الفئات (Classes) في steps.vue:
تعريف المراحل كدائنات في steps باستخدام computed لتحديث الفئات (process وcomplete) بشكل ديناميكي بناءً على حالة stepsStore.processed.
هذا يسهل من إدارة المراحل وتعديلها في المستقبل.
دالة accept:
تحويل بيانات جميع الخطوات باستخدام objectConvertor وإرسالها إلى Frappe Doctype.
تأكد من إضافة تحويلات الخطوات الأخرى (Step2 إلى Step6) إذا كانت تحتوي على بيانات تحتاج للإرسال.
دالة reject:
يمكن تخصيصها حسب متطلباتك، مثل مسح البيانات أو الاحتفاظ بها مع إظهار رسالة تنبيه.
تحسين تجربة المستخدم:
إضافة تأثيرات تفاعلية عند التركيز (focus) على StepIcon.
استخدام ARIA Attributes لضمان إمكانية الوصول.
تحسين إدارة التنقل:
السماح للمستخدم بالانتقال إلى خطوة معينة عبر النقر على أي StepIcon.
يمكن ضبط هذا السلوك حسب متطلباتك، مثل التأكد من اكتمال الخطوات السابقة قبل السماح بالانتقال.
3. نصائح إضافية لتحسين StepIcon.vue وتجربة المستخدم
أ. تحسين عرض النسبة عند !complete
لجعل المكون أكثر ديناميكية، يمكنك جعل 0/32 قيمة قابلة للتخصيص عبر props.
// تحديث واجهة StepIconProps
export interface StepIconProps {
  label: string;
  desc?: string;
  process: boolean;
  complete?: boolean;
  progress?: string | number; // فئة جديدة لإظهار النسبة أو الرقم
}


ثم في template, استخدم progress:
<span v-if="!complete">{{ progress }}</span>


وفي props, قم بتعريفها:
progress: { type: [String, Number], default: '0/32' },


ب. إضافة إشارة لخطوات مكتملة
يمكنك استخدام الأيقونات أو الألوان لتوضيح الخطوات المكتملة بشكل أفضل.
ج. تحسين استخدام SVG
لتصبح الأيقونات أكثر مرونة، يمكنك استخدام مكونات SVG مخصصة لجعل الرموز قابلة للتخصيص بالكامل.
د. استخدام Transition لعناصر التفاعل
يمكنك إضافة تأثيرات انتقالية للمكون لجعل التجربة أكثر سلاسة.
<!-- Example: إضافة transition للمكون -->
<transition name="fade">
  <div
    :class="[
      'p-2 flex flex-col lg:flex-row lg:gap-2 lg:justify-start justify-center items-center cursor-pointer',
      processClass,
    ]"
    @click="handleClick"
    role="button"
    :aria-pressed="complete"
    tabindex="0"
    @keypress.enter="handleClick"
    @keypress.space="handleClick"
  >
    <!-- Content here -->
  </div>
</transition>


ثم قم بتعريف التأثير في style:
.fade-enter-active, .fade-leave-active {
  transition: opacity 0.5s;
}
.fade-enter-from, .fade-leave-to {
  opacity: 0;
}


هـ. إضافة دعم النقر إلى مكون غير قابل للنقر
إذا كانت بعض المراحل غير قابلة للنقر، يمكنك ضبط المكون ليكون غير قابل للتفاعل بناءً على خاصية معينة.
الخلاصة
من خلال تحسين StepIcon.vue كما هو موضح أعلاه، ستتمكن من تحقيق مكون متكامل ومرن يعكس حالة الخطوات بشكل فعّال ويعزز من تجربة المستخدم. هذه التحسينات تشمل دعم TypeScript، تحسين إدارة الفئات باستخدام TailwindCSS، تعزيز إمكانية الوصول، وإضافة إمكانية التعامل مع أحداث النقر والتنقل مباشرة إلى خطوات معينة.
النقاط التالية:
تطبيق نفس التحسينات على مكونات الخطوات الأخرى (Step2.vue إلى Step6.vue).
إضافة دعم للتحقق من الصحة الديناميكي عبر الخطوات باستخدام Pinia و Yup.
تحسين إدارة الجداول الفرعية (Child Tables) إذا كانت موجودة في أي من الخطوات الأخرى.
الاستمرار في اختبار التطبيق لضمان عمل جميع المكونات معًا بشكل صحيح وسلس.
إذا كان لديك أي مكونات أخرى تحتاج إلى تحسين أو تعديل، أو إذا واجهت أي مشاكل في دمج هذه التحسينات، فلا تتردد في مشاركتي بالكود وسأكون سعيدًا بمساعدتك