> ## Documentation Index
> Fetch the complete documentation index at: https://docs.twenty.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 4. بناء واجهة المستخدم

> المشاهدة، الملاحة الجانبية، الأوامر، والمكونات الأمامية.

الآن يمكن الوصول إلى الكائنات فقط من خلال الإعدادات. لنمنح التطبيق حضورًا حقيقيًا في واجهة المستخدم: طرق عرض القوائم، وعناصر في الشريط الجانبي، وأمر **إنشاء مستند** بنقرة واحدة، ومكوّن واجهة أمامية في صفحة السجل من أجل **معاينة** مستند، وعلامة تبويب **محرر** نص منسق أصلية للقوالب.

## وجهات النظر والملاحة

A [view](/l/ar/developers/extend/apps/layout/views) هي قائمة محفوظة لكائن معين.
ويضع [عنصر قائمة التنقل](/l/ar/developers/extend/apps/layout/navigation-menu-items)
هذا العرض في الشريط الجانبي.

```ts filename="src/views/documents.view.ts" theme={null}
import { defineView, ViewKey } from 'twenty-sdk/define';

export default defineView({
  universalIdentifier: DOCUMENTS_VIEW_UNIVERSAL_IDENTIFIER,
  name: 'All documents',
  objectUniversalIdentifier: DOCUMENT_OBJECT_UNIVERSAL_IDENTIFIER,
  icon: 'IconFile',
  key: ViewKey.INDEX,
  position: 0,
  fields: [
    { universalIdentifier: DOCUMENTS_VIEW_NAME_FIELD_UNIVERSAL_IDENTIFIER,
      fieldMetadataUniversalIdentifier: DOCUMENT_NAME_FIELD_UNIVERSAL_IDENTIFIER,
      position: 0, isVisible: true, size: 280 },
    { universalIdentifier: DOCUMENTS_VIEW_STATUS_FIELD_UNIVERSAL_IDENTIFIER,
      fieldMetadataUniversalIdentifier: DOCUMENT_STATUS_FIELD_UNIVERSAL_IDENTIFIER,
      position: 1, isVisible: true, size: 120 },
    { universalIdentifier: DOCUMENTS_VIEW_TEMPLATE_FIELD_UNIVERSAL_IDENTIFIER,
      fieldMetadataUniversalIdentifier: DOCUMENT_TEMPLATE_FIELD_UNIVERSAL_IDENTIFIER,
      position: 2, isVisible: true, size: 200 },
  ],
});
```

```ts filename="src/navigation-menu-items/documents.navigation-menu-item.ts" theme={null}
import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define';

export default defineNavigationMenuItem({
  universalIdentifier: DOCUMENTS_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER,
  name: 'Documents',
  icon: 'IconFile',
  color: 'green',
  position: 1,
  type: NavigationMenuItemType.VIEW,
  viewUniversalIdentifier: DOCUMENTS_VIEW_UNIVERSAL_IDENTIFIER,
});
```

أضف نفس زوج للنماذج. ويظهر كلاهما الآن في الشريط الجانبي:

<Frame caption="المستندات والقوالب في الشريط الجانبي، مع إدراج الوثيقة التي تم إنشاؤها.">
  <img src="https://mintlify.s3.us-west-1.amazonaws.com/twenty/images/docs/developers/extends/apps/document-Generator/04-documents-view.png" alt="المستندات المعروضة مع مستند تم إنشاؤه" />
</Frame>

## مكون أمامي

[مكون أمامي](/l/ar/developers/extend/apps/layout/front-components) هو مكون React
مربع رملي داخل عشرين. يقرأ السجل المحدد، يحمّل قوالب الشخص
عبر `CoreApiClient`، و POSTs إلى المسار من الفصل
الأخير.

```tsx filename="src/front-components/generate-document-form.front-component.tsx" theme={null}
import { useEffect, useState } from 'react';
import { CoreApiClient } from 'twenty-client-sdk/core';
import { defineFrontComponent } from 'twenty-sdk/define';
import { enqueueSnackbar, useSelectedRecordIds } from 'twenty-sdk/front-component';

const GenerateDocumentForm = () => {
  const selectedRecordIds = useSelectedRecordIds();
  const recordId = selectedRecordIds.length === 1 ? selectedRecordIds[0] : null;
  const [templates, setTemplates] = useState<{ id: string; name: string }[]>([]);
  const [templateId, setTemplateId] = useState('');

  useEffect(() => {
    new CoreApiClient()
      .query({ documentTemplates: {
        __args: { filter: { target: { eq: 'PERSON' } }, first: 100 },
        edges: { node: { id: true, name: true } } } })
      .then(({ documentTemplates }) => {
        const list = documentTemplates?.edges?.map((e) => e.node) ?? [];
        setTemplates(list);
        if (list[0]) setTemplateId(list[0].id);
      });
  }, []);

  const generate = async () => {
    const apiBaseUrl = process.env.TWENTY_API_URL;
    const token = process.env.TWENTY_APP_ACCESS_TOKEN ?? process.env.TWENTY_API_KEY;
    const res = await fetch(`${apiBaseUrl}/s/documents/generate`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
      body: JSON.stringify({ templateId, recordId }),
    }).then((r) => r.json());
    await enqueueSnackbar({
      message: res.success ? 'Document generated.' : 'Generation failed.',
      variant: res.success ? 'success' : 'error',
    });
  };

  // ...render a <select> of templates and a Generate button
};

export default defineFrontComponent({
  universalIdentifier: GENERATE_DOCUMENT_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
  name: 'generate-document-form',
  component: GenerateDocumentForm,
});
```

<Warning>
  النمط مع متغيرات CSS المضمنة (`var(--t-color-blue)`)، ليس القيم المستوردة من
  `XXui`. تمزق SDK تلك الحزمة أثناء البناء، لذا فإن واردات مستوى الوحدة النمطية من ثوابت السمة
  ستكون 'غير محددة\`. انظر
  [الكامل المكون](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/front-components/generate-document-form.front-component.tsx).
</Warning>

## أمر لفتحه

[أمر قائمة](/l/ar/developers/extend/apps/layout/command-menu-items) مع
\`available ityType: 'RECORD\_SELECTION'' يظهر عند اختيار شخص، و
يفتح المكون في اللوحة الجانبية.

```ts filename="src/command-menu-items/generate-document.command-menu-item.ts" theme={null}
import { defineCommandMenuItem, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define';

export default defineCommandMenuItem({
  universalIdentifier: GENERATE_DOCUMENT_COMMAND_UNIVERSAL_IDENTIFIER,
  label: 'Generate document',
  availabilityType: 'RECORD_SELECTION',
  availabilityObjectUniversalIdentifier:
    STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier,
  frontComponentUniversalIdentifier:
    GENERATE_DOCUMENT_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
});
```

## جرب التدفق بأكمله

افتح **People**، حدِّد شخصًا، ثم اضغط <kbd>⌘K</kbd> / <kbd>Ctrl K</kbd>.
يظهر "إنشاء مستند"، وسم مع التطبيق الخاص بك:

<Frame caption="يظهر الأمر عندما يتم اختيار شخص.">
  <img src="https://mintcdn.com/twenty/sqJBeTZq-W-RDBPU/images/docs/developers/extends/apps/document-generator/06-command-menu.png?fit=max&auto=format&n=sqJBeTZq-W-RDBPU&q=85&s=7f565368b8e4f21b0899b89505cc4f34" alt="قائمة الأوامر مع توليد المستند" width="2880" height="1800" data-path="images/docs/developers/extends/apps/document-generator/06-command-menu.png" />
</Frame>

قم بتشغيله – يفتح مكونك في اللوحة الجانبية. اختيار قالب، انقر
**إنشاء**، وسجل جديد في **الوثائق**.

<Frame caption="المكون الأمامي، تحميل القوالب والتوليد على النقرة.">
  <img src="https://mintlify.s3.us-west-1.amazonaws.com/twenty/images/docs/developers/extends/apps/document-Generator/06b-front-component.png" alt="إنشاء لوحة وثيقة جانبية" />
</Frame>

يسجل كل مستند تم إنشاؤه التطبيق الخاص بك كمؤلف:

<Frame caption="تم إنشاؤها من قبل مولد المستندات، الحالة التي تم إنشاؤها.">
  <img src="https://mintcdn.com/twenty/sqJBeTZq-W-RDBPU/images/docs/developers/extends/apps/document-generator/05-document-record.png?fit=max&auto=format&n=sqJBeTZq-W-RDBPU&q=85&s=735261a59834a51277284303a9d6f845" alt="سجل مستند تم إنشاؤه" width="2880" height="1800" data-path="images/docs/developers/extends/apps/document-generator/05-document-record.png" />
</Frame>

## معاينة مستند في صفحة السجل الخاصة به

المكون الأمامي ليس فقط لقوائم الأوامر - يمكنك تحميل واحد كعلامة تبويب \*\*على صفحة تسجيل
\*\*. دعونا نضيف علامة تبويب *Preview* إلى سجل المستند الذي يجعل جسم
Markdown كصفحة مصقولة قابلة للطباعة.

ويقرأ المكون معرف السجل الحالي من سياق التنفيذ، ويحمل المستند
ويضعه. تعمل المكوّنات الأمامية ضمن **sandbox** لا يسمح إلا بقائمة محددة من علامات HTML — يتم حظر حقن HTML الخام (`dangerouslySetInnerHTML`) وعنصر `\<style>` — لذلك نقوم بعرض Markdown كعناصر React مع أنماط مضمنة عبر مساعد [`Markdown`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/utils/markdown-to-react.tsx) صغير.

```tsx filename="src/front-components/document-viewer.front-component.tsx" theme={null}
import { CoreApiClient } from 'twenty-client-sdk/core';
import { defineFrontComponent } from 'twenty-sdk/define';
import { useFrontComponentExecutionContext } from 'twenty-sdk/front-component';
import { Markdown } from 'src/utils/markdown-to-react';

const DocumentViewer = () => {
  const recordId = useFrontComponentExecutionContext((c) => c.recordId ?? null);
  // ...load { content, file } for recordId, then derive the links:
  const pdfUrl = document.file?.[0]?.url;
  const webUrl = `${process.env.TWENTY_API_URL ?? ''}/s/documents/view?id=${recordId}`;

  // Render the template body, plus quick links to the web page and the PDF.
  // Links open in a new tab so they don't navigate the embedded component.
  return (
    <div style={styles.scroll}>
      <div style={styles.actions}>
        <a style={styles.actionLink} href={webUrl} target="_blank" rel="noopener noreferrer">
          Open web page
        </a>
        {pdfUrl ? (
          <a style={styles.actionLink} href={pdfUrl} target="_blank" rel="noopener noreferrer">
            Download PDF
          </a>
        ) : null}
      </div>
      <div style={styles.paper}>
        <div style={styles.body}>
          <Markdown content={document.content} />
        </div>
      </div>
    </div>
  );
};

export default defineFrontComponent({
  universalIdentifier: DOCUMENT_VIEWER_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
  name: 'document-viewer',
  component: DocumentViewer,
});
```

قم بتحميلها مع [تخطيط الصفحة](/l/ar/developers/extend/apps/layout/page-layouts). يضيف تخطيط
`RECORD_PAGE` علامات تبويب إلى طريقة عرض تسجيل الكائن؛ ويستضيف عنصر التحكم 'FRONT\_COMPONENT`
في علامة تبويب 'CANVAS` المكون:

```ts filename="src/page-layouts/document-record.page-layout.ts" theme={null}
import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define';

export default definePageLayout({
  universalIdentifier: DOCUMENT_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER,
  name: 'Document record page',
  type: 'RECORD_PAGE',
  objectUniversalIdentifier: DOCUMENT_OBJECT_UNIVERSAL_IDENTIFIER,
  tabs: [{
    universalIdentifier: DOCUMENT_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER,
    title: 'Preview',
    icon: 'IconEye',
    position: 50,
    layoutMode: PageLayoutTabLayoutMode.CANVAS,
    widgets: [{
      universalIdentifier: DOCUMENT_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIER,
      title: 'Document preview',
      type: 'FRONT_COMPONENT',
      configuration: {
        configurationType: 'FRONT_COMPONENT',
        frontComponentUniversalIdentifier: DOCUMENT_VIEWER_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
      },
    }],
  }],
});
```

افتح أي مستند – علامة تبويب **المعاينة** تجعله جميلا، مع روابط إلى صفحة ويب
القابلة للمشاركة و PDF:

<Frame caption="علامة التبويب المعاينة تجعل المستند بأنماط مضمنة، بالإضافة إلى روابط سريعة.">
  <img src="https://mintcdn.com/twenty/sqJBeTZq-W-RDBPU/images/docs/developers/extends/apps/document-generator/09-document-viewer.png?fit=max&auto=format&n=sqJBeTZq-W-RDBPU&q=85&s=e56c0d51f60f1597b55f497c35245e7e" alt="عنصر واجهة عارض المستند في علامة تبويب صفحة التسجيل" width="2880" height="1800" data-path="images/docs/developers/extends/apps/document-generator/09-document-viewer.png" />
</Frame>

## تحرير قالب مع محرر النص الغني

القوالب لا تحتاج إلى عنصر مخصص على الإطلاق. لأن `body' هو حقل
`RICH\_TEXT' ، يقدم عشرون محرر نص ثري كامل له -
نفس المحرر القياسي للمذكرة وعناصر المهمة. نحن فقط نسطحه على صفحة سجل القالب

إضافة علامة تبويب مع عنصر واجهة المستخدم 'FIELD' في وضع العرض 'EDITOR' ، مع الإشارة إلى حقل 'body'
عبر 'field MetadataId':

```ts filename="src/page-layouts/template-record.page-layout.ts" theme={null}
{
  universalIdentifier: TEMPLATE_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER,
  title: 'Template',
  position: 1,
  layoutMode: PageLayoutTabLayoutMode.GRID,
  widgets: [{
    universalIdentifier: TEMPLATE_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIER,
    title: 'Template',
    type: 'FIELD',
    gridPosition: { row: 0, column: 0, rowSpan: 6, columnSpan: 12 },
    configuration: {
      configurationType: 'FIELD',
      fieldMetadataId: TEMPLATE_BODY_FIELD_UNIVERSAL_IDENTIFIER,
      fieldDisplayMode: 'EDITOR',
    },
  }],
}
```

يخزن حقل "RICH\_TEXT" كتلة المحرر JSON وإسقاط Markdown
على حد سواء. خط أنابيب التوليد يقرأ إسقاط الـ Markdown هذا، لذا فإن العناصر النائبة وملف PDF وصفحة الويب القابلة للمشاركة تستمر جميعها في العمل دون تغيير — راجع الملف الكامل [`template-record.page-layout.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/page-layouts/template-record.page-layout.ts).
الآن المحررون يكتبون قوالب في محرر نص غني مناسب:

<Frame caption="علامة التبويب النموذج: محرر النص الغني الأصلي 20 مرتبط بحقل الجسم.">
  <img src="https://mintlify.s3.us-west-1.amazonaws.com/twenty/images/docs/developers/extends/apps/document-Generator/10-template-editor.png" alt="سجل القالب مع علامة تبويب محرر النص الغني الأصلي" />
</Frame>

**بعد هذه الخطوة:** معاينة الوثائق بشكل جميل والقوالب قابلة للتحرير
في التطبيق. ثم دعنا وكيل الذكاء الاصطناعي يولدها من محادثة.

<Card title="التالي : وكيل AI →" icon="robot" href="/l/ar/developers/extend/apps/tutorials/document-Generator/ai-agent">
  إضافة وكيل ومهارة يتصلان بأداتك.
</Card>
