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

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

A view هي قائمة محفوظة لكائن معين. ويضع عنصر قائمة التنقل هذا العرض في الشريط الجانبي.
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 },
  ],
});
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,
});
أضف نفس زوج للنماذج. ويظهر كلاهما الآن في الشريط الجانبي:
المستندات المعروضة مع مستند تم إنشاؤه

مكون أمامي

مكون أمامي هو مكون React مربع رملي داخل عشرين. يقرأ السجل المحدد، يحمّل قوالب الشخص عبر CoreApiClient، و POSTs إلى المسار من الفصل الأخير.
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,
});
النمط مع متغيرات CSS المضمنة (var(--t-color-blue))، ليس القيم المستوردة من XXui. تمزق SDK تلك الحزمة أثناء البناء، لذا فإن واردات مستوى الوحدة النمطية من ثوابت السمة ستكون ‘غير محددة`. انظر الكامل المكون.

أمر لفتحه

أمر قائمة مع `available ityType: ‘RECORD_SELECTION” يظهر عند اختيار شخص، و يفتح المكون في اللوحة الجانبية.
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، حدِّد شخصًا، ثم اضغط ⌘K / Ctrl K. يظهر “إنشاء مستند”، وسم مع التطبيق الخاص بك:
قائمة الأوامر مع توليد المستند
قم بتشغيله – يفتح مكونك في اللوحة الجانبية. اختيار قالب، انقر إنشاء، وسجل جديد في الوثائق.
إنشاء لوحة وثيقة جانبية
يسجل كل مستند تم إنشاؤه التطبيق الخاص بك كمؤلف:
سجل مستند تم إنشاؤه

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

المكون الأمامي ليس فقط لقوائم الأوامر - يمكنك تحميل واحد كعلامة تبويب **على صفحة تسجيل **. دعونا نضيف علامة تبويب Preview إلى سجل المستند الذي يجعل جسم Markdown كصفحة مصقولة قابلة للطباعة. ويقرأ المكون معرف السجل الحالي من سياق التنفيذ، ويحمل المستند ويضعه. تعمل المكوّنات الأمامية ضمن sandbox لا يسمح إلا بقائمة محددة من علامات HTML — يتم حظر حقن HTML الخام (dangerouslySetInnerHTML) وعنصر \<style> — لذلك نقوم بعرض Markdown كعناصر React مع أنماط مضمنة عبر مساعد Markdown صغير.
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,
});
قم بتحميلها مع تخطيط الصفحة. يضيف تخطيط RECORD_PAGE علامات تبويب إلى طريقة عرض تسجيل الكائن؛ ويستضيف عنصر التحكم ‘FRONT_COMPONENT في علامة تبويب 'CANVAS المكون:
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:
عنصر واجهة عارض المستند في علامة تبويب صفحة التسجيل

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

القوالب لا تحتاج إلى عنصر مخصص على الإطلاق. لأن body' هو حقل RICH_TEXT’ ، يقدم عشرون محرر نص ثري كامل له - نفس المحرر القياسي للمذكرة وعناصر المهمة. نحن فقط نسطحه على صفحة سجل القالب إضافة علامة تبويب مع عنصر واجهة المستخدم ‘FIELD’ في وضع العرض ‘EDITOR’ ، مع الإشارة إلى حقل ‘body’ عبر ‘field MetadataId’:
{
  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. الآن المحررون يكتبون قوالب في محرر نص غني مناسب:
سجل القالب مع علامة تبويب محرر النص الغني الأصلي
بعد هذه الخطوة: معاينة الوثائق بشكل جميل والقوالب قابلة للتحرير في التطبيق. ثم دعنا وكيل الذكاء الاصطناعي يولدها من محادثة.

التالي : وكيل AI →

إضافة وكيل ومهارة يتصلان بأداتك.