تخطي إلى المحتوى
Architecture Decision Record

نظام الأوضاع

بنية تدعم وضعين (خاص وعام) تُحدد في وقت التشغيل — مع Keyed Remount للتبديل الآمن، وهيلبر useModeVariant لعزل الفروقات الحقيقية بس.
الفهرس

نظرة عامة

المبدأ الأساسي

النظام بيخدم نوعين من الجهات — خاص وعام — بفورمات وحقول مختلفة. الـ mode بيتحدد runtime بعد تسجيل الدخول، وكل فرق حقيقي بين الوضعين بيتعزل في ملف "variant" نظيف بدل شروط متناثرة في الكود.

هوية الشجرة
الـ mode بيتحدد مرة واحدة، وأي تغيير فيه يعني إعادة بناء الشجرة من الصفر — مش تحديث تفاعلي.
Lazy Creation
ملف الـ variant يتعمل بس لو فيه فرق فعلي — موديول من غير فروقات = ملفات عادية بالكامل.
أداة واحدة لكل حجم فرق
نص → مفتاح ترجمة. حقل واحد → BaseModeShow. هيكل مختلف → ملف variant مستقل.

حل الـ Mode — useMode

يقرأ الـ mode من بيانات الـ session ويوحّد الصيغة لقيمة واحدة
قواعد التحويل
المصدرالقيمةالنتيجة
privateCompanyModetrueprivate
privateCompanyModefalsepublic
mode1private
mode2public
شكل غير متعارف عليه—public (مع تحذير)
useMode.js — الشكل العامjs
export const useMode = () => {
  const { data: session } = useAuth()

  const mode = computed(() => resolveMode(session.value))

  const isMode = (...modes) => modes.includes(mode.value)

  return { mode, isMode }
}

لو شكل بيانات الـ session جه بصيغة مش متعارف عليها، النظام بيرجع لـ public كـ fallback آمن بدل ما يفشل، مع تحذير واضح في الكونسول يساعد في التشخيص السريع.

آلية التبديل — Keyed Remount

الـ mode ثابت طوال حياة شجرة المكونات — التبديل بينهم هدم وإعادة بناء كاملة، مش تحديث تفاعلي
تغيّر الـ mode
مصدر التغيير (مثلاً: حفظ إعدادات) يحدّث بيانات الـ session
المراقب المركزي
watch(mode) في app.vue بيلتقط التغيير
تنظيف وفحص
مسح الكاش القديم + التأكد إن الصفحة الحالية مسموحة في الوضع الجديد
Remount كامل
الـ key بتاع NuxtLayout بيتغيّر → هدم وبناء الشجرة من الصفر
app.vue — الآليةvue
<script setup>
const { mode } = useMode()
const route = useRoute()

watch(mode, async () => {
  clearNuxtData()
  const allowed = route.meta.modes
  if (allowed && !allowed.includes(mode.value)) {
    await navigateTo('/')
  }
})
  </script>

<template>
  <NuxtLayout :key="mode">
    <NuxtPage />
  </NuxtLayout>
</template>
القاعدة المعمارية

اقرأ الـ mode في أي وقت، لكن لا تخزّن نسخة منه (أو من أي قيمة محسوبة منه) في مكان بيعيش برّه شجرة المكونات — أي نسخة زي كده بتنجو من عملية الهدم وبتفضل شايلة قيمة الوضع القديم بصمت.

الـ Helper الرسمي — useModeVariant

يختار الـ variant المناسب للوضع الحالي، مرة واحدة وقت بناء الـ composable
useModeVariant.jsjs
export const useModeVariant = (variants) => {
  const { mode } = useMode()
  return variants[mode.value]?.() ?? {}
}
موديول فيه فروقات حقيقية — مثال: التوظيف (Employment)js
export const useEmployeeEmploymentSchema = () => {
  const modeSpecific = useModeVariant({
    private: useEmployeeEmploymentSchemaPrivate,
    public: useEmployeeEmploymentSchemaPublic
  })

  const baseSchema = z.object({ /* الحقول المشتركة */ })

  const schema = computed(() =>
    modeSpecific.extension
      ? baseSchema.extend(modeSpecific.extension.shape)
      : baseSchema
  )

  return { schema }
}
موديول من غير فروقات — ملف عادي بالكاملjs
export const useQualificationsSchema = () => {
  const schema = z.object({ /* ... */ })
  return { schema }
}

فلسفة الدمج — مش قاعدة واحدة شاملة

المبدأ الحاكم: هل الترتيب/التكوين الكامل جزء من العقد المرئي؟
شكل كل Artifact
الـ Artifactالفلسفةالشكل
Schema / Form Statebase + extensionملف الـ variant يصدّر الفرق (delta) بس — المشترك مُعرَّف مرة واحدة
Columnsتعريف كامل لكل وضعترتيب الأعمدة عقد مرئي — كل variant بيعرّف الأعمدة كاملة
Sidebarتعريف كامل عند ظهور فرقنفس منطق الـ Columns — الترتيب والتجميع مرئيين
Componentsفصل كامل بالملفاتهيكل مختلف يستحق ملفات مستقلة عبر componentsMap

المكوّن الرسمي للفروقات الصغيرة — BaseModeShow

إظهار/إخفاء عنصر أو حقل واحد حسب الوضع، بدون فصل ملف كامل
BaseModeShow — Usagevue
<!-- إظهار للوضع الخاص فقط -->
<BaseModeShow :modes="['private']">
  <BaseInput v-model="state.someField" label="حقل خاص بالوضع الخاص" />
</BaseModeShow>
سلّم الفروقات
حجم الفرقالأداة
نص فقط (عنوان، label)مفتاح ترجمة بلاحقة الوضع
حقل أو عنصر واحدBaseModeShow
هيكل مختلف بالكاململف variant مستقل عبر componentsMap

حماية الصفحات — Middleware

middleware عام بيحمي الصفحات المقيدة بوضع معين
mode.global.jsjs
export default defineNuxtRouteMiddleware((to) => {
  const allowedModes = to.meta?.modes
  if (!allowedModes || !Array.isArray(allowedModes) || allowedModes.length === 0) return
  const { isMode } = useMode()
  if (!isMode(...allowedModes)) return navigateTo('/')
})
definePageMeta — أمثلةjs
// صفحة متاحة لكل الأوضاع
definePageMeta({ title: 'pages.qualifications' })

// صفحة خاصة بالوضع الخاص فقط
definePageMeta({ title: 'pages.employment', modes: ['private'] })

الـ mode في الواجهة أداة عرض (UX) بس — الحماية دي بتنظّم التنقل، مش بديل عن أي تحقق أمني حقيقي على مستوى الـ backend.

نمط الموديول — مثال حقيقي (Employment)

موديول التوظيف فيه فروقات حقيقية بين الوضعين، فبيستخدم نمط الـ variant الكامل
composables/modules/administration/employees/_employment/
├── schema/
│   ├── index.js                    ← الأوركسترا (useModeVariant)
│   ├── schema.private.js           ← extension الوضع الخاص
│   └── schema.public.js            ← extension الوضع العام
├── form/
│   ├── index.js
│   ├── form.private.js
│   └── form.public.js
└── lists/
    └── index.js                    ← مشترك، بدون فروقات

موديولات تانية بلا فروقات حقيقية (زي المؤهلات) — ملفات عادية بلا أي variant، وكأن نظام الـ mode مش موجود خالص.

القرارات التصميمية

لماذا اخترنا هذا النمط وليس البدائل الأخرى
البدائل التي درسناها
البديلالمشكلة
Config-Driven (حقول كـ array)يفقد مرونة الـ template — لا slots مخصصة، لا layouts معقدة، كل نوع حقل جديد يحتاج case جديد في الـ renderer
Nuxt Layers (طبقة منفصلة لكل وضع)الـ mode بييجي runtime من بيانات الـ session — Layers بتشتغل build-time بس، مش مناسبة للسيناريو ده

ما تغيّر أثناء البناء

حاجتان اتبنوا بالكود فعلاً، واتراجعوا أثناء المراجعة الداخلية للبناء — قبل أي استخدام إنتاج حقيقي
التغييرات
التغييركان مصمَّماتضح في المراجعةالتعديل وليه
من 3 أوضاع لوضعيننظام بتلات أوضاع منفصلة — خاص، حكومي، ومختلط — كل واحد بملفات ومنطق مستقل خاص بيه.الوضع "المختلط" ما كانش له تمايز فعلي كافي يستاهل مسار كامل مستقل، والفرق الحقيقي بين "الحكومي" وباقي الحالات كان بيتلخص عملياً في نفس منطق الوضع "العام".تقليص النظام لوضعين بس. تبسيط حقيقي في عدد المسارات المطلوب صيانتها، مش فقدان قدرة فعلية كانت مستخدَمة.
حذف ModeSwitch وfilterByModeمكوّن ModeSwitch (Dynamic component يختار الشكل المناسب حسب الوضع تلقائياً) ودالة filterByMode (تصفية عناصر قائمة حسب الوضع) — كأدوات مساعدة عامة.ModeSwitch كان بيكرر بالظبط نفس وظيفة نمط componentsMap المستخدَم أصلاً في كل أوركسترا. filterByMode طلعت dead code فعلياً — صفر استخدامات حقيقية في الكود وقت المراجعة.الاتنين اتشالوا بالكامل. ModeSwitch استُبدل بـ componentsMap مباشرة، وfilterByMode اتشالت من غير أي بديل — مكنش ليها استخدام حقيقي أصلاً.