i18n - docs translations (#16767)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com>
This commit is contained in:
committed by
GitHub
parent
c6ea7ae288
commit
99d5aa2589
+39
@@ -0,0 +1,39 @@
|
||||
---
|
||||
title: كائنات مخصصة
|
||||
---
|
||||
|
||||
الكائنات هي هياكل تسمح لك بتخزين البيانات (السجلات، السمات والقيم) الخاصة بالمؤسسة. توفر Twenty كلاً من الكائنات القياسية والمخصصة.
|
||||
|
||||
الكائنات القياسية هي كائنات مدمجة مع مجموعة من السمات المتاحة لجميع المستخدمين. تتضمن الأمثلة على الكائنات القياسية في Twenty الشركات والأشخاص. الكائنات القياسية تحتوي على حقول قياسية تتوفر أيضاً لجميع مستخدمي Twenty، مثل Company.displayName.
|
||||
|
||||
الكائنات المخصصة هي كائنات يمكنك إنشاؤها لتخزين المعلومات التي تخص مؤسستك. هذه الكائنات ليست مدمجة؛ يمكن لأعضاء مساحة العمل الخاصة بك إنشاء وتخصيص الكائنات المخصصة للاحتفاظ بالمعلومات التي لا تناسبها الكائنات القياسية.
|
||||
|
||||
## مخطط على مستوى عالي
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/server/custom-object-schema.png" alt="مخطط على مستوى عالي" />
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
## كيف يعمل
|
||||
|
||||
الكائنات المخصصة تأتي من جداول الميتاداتا التي تحدد شكل، اسم، ونوع الكائنات. كل هذه المعلومات موجودة في قاعدة بيانات المخطط البياني للبيانات الوصفية، والتي تتكون من جداول:
|
||||
|
||||
* **مصدر البيانات**: يشير إلى مكان وجود البيانات.
|
||||
* **الكائن**: يصف الكائن ويرتبط بمصدر البيانات.
|
||||
* **الحقل**: يحدد حقول الكائن ويرتبط بالكائن.
|
||||
|
||||
لإضافة كائن مخصص، سيقوم عضو مساحة العمل بالاستعلام عن واجهة برمجة التطبيقات /metadata. يقوم هذا بتحديث البيانات الوصفية وفقًا لذلك ويحسب مخطط GraphQL استنادًا إلى البيانات الوصفية، ويخزنها في ذاكرة GQL للاستخدام لاحقًا.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/server/add-custom-objects.jpeg" alt="استعلام واجهة برمجة التطبيقات /metadata لإضافة الكائنات المخصصة" />
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
لجلب البيانات، تتضمن العملية إجراء استعلامات من خلال نقطة النهاية /graphql وتمريرها من خلال محلل الاستعلام.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/server/custom-object-schema.png" alt="استعلام نقطة النهاية /graphql لجلب البيانات" />
|
||||
</div>
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
---
|
||||
title: تطبيق Zapier
|
||||
---
|
||||
|
||||
قم بمزامنة Twenty مع أكثر من 3000 تطبيق باستخدام [Zapier](https://zapier.com/) بسهولة. قم بأتمتة المهام، وزيادة الإنتاجية، وتعزيز علاقاتك مع العملاء!
|
||||
|
||||
## حول Zapier
|
||||
|
||||
Zapier هي أداة تتيح لك أتمتة سير العمل من خلال ربط التطبيقات التي يستخدمها فريقك يوميًا. المفهوم الأساسي ل Zapier هو سير العمل الآلي، المسمى Zaps، والذي يتضمن محفزات وإجراءات.
|
||||
|
||||
يمكنك معرفة المزيد حول كيفية عمل Zapier [هنا](https://zapier.com/how-it-works).
|
||||
|
||||
## إعداد
|
||||
|
||||
### الخطوة 1: تثبيت حزم Zapier
|
||||
|
||||
```bash
|
||||
cd packages/twenty-zapier
|
||||
|
||||
yarn
|
||||
```
|
||||
|
||||
### الخطوة 2: تسجيل الدخول باستخدام CLI
|
||||
|
||||
استخدم بيانات اعتماد Zapier الخاصة بك لتسجيل الدخول باستخدام CLI:
|
||||
|
||||
```bash
|
||||
zapier login
|
||||
```
|
||||
|
||||
### الخطوة 3: تعيين متغيرات البيئة
|
||||
|
||||
من مجلد `packages/twenty-zapier`، قم بتشغيل:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
شغل التطبيق محليًا، اذهب إلى [http://localhost:3000/settings/api-webhooks](http://localhost:3000/settings/api-webhooks)، وولد مفتاح API.
|
||||
|
||||
استبدل قيمة **YOUR_API_KEY** في ملف `.env` بمفتاح API الذي قمت بتوليده للتو.
|
||||
|
||||
## التطوير
|
||||
|
||||
<Warning>
|
||||
تأكد من تشغيل `yarn build` قبل أي أمر `zapier`.
|
||||
</Warning>
|
||||
|
||||
### تجربة
|
||||
|
||||
```bash
|
||||
yarn test
|
||||
```
|
||||
|
||||
### Lint
|
||||
|
||||
```bash
|
||||
yarn format
|
||||
```
|
||||
|
||||
### راقب وقم بالتحويل عند تحرير الكود
|
||||
|
||||
```bash
|
||||
yarn watch
|
||||
```
|
||||
|
||||
### تحقق من صحة تطبيق Zapier الخاص بك
|
||||
|
||||
```bash
|
||||
yarn validate
|
||||
```
|
||||
|
||||
### قم بنشر تطبيق Zapier الخاص بك
|
||||
|
||||
```bash
|
||||
yarn deploy
|
||||
```
|
||||
|
||||
### اعرض جميع أوامر Zapier CLI
|
||||
|
||||
```bash
|
||||
zapier
|
||||
```
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
---
|
||||
title: هيكلية المجلدات
|
||||
info: نظرة مفصلة على هيكل المجلدات الخاصة بنا
|
||||
---
|
||||
|
||||
في هذا الدليل، ستستكشف تفاصيل هيكل دليل المشروع وكيف يساهم في تنظيم Twenty وسهولة صيانتها.
|
||||
|
||||
باتباع اصطلاح هيكل المجلدات هذا، يسهل العثور على الملفات المتعلقة بميزات محددة وضمان أن يكون التطبيق قابلاً للتوسّع والصيانة.
|
||||
|
||||
```
|
||||
front
|
||||
└───modules
|
||||
│ └───module1
|
||||
│ │ └───submodule1
|
||||
│ └───module2
|
||||
│ └───ui
|
||||
│ │ └───display
|
||||
│ │ └───inputs
|
||||
│ │ │ └───buttons
|
||||
│ │ └───...
|
||||
└───pages
|
||||
└───...
|
||||
```
|
||||
|
||||
## الصفحات
|
||||
|
||||
تشمل المكونات عالية المستوى المحددة من قبل مسارات التطبيق. وتستورد مكوّنات منخفضة المستوى من مجلد الوحدات (تفاصيل إضافية أدناه).
|
||||
|
||||
## الوحدات
|
||||
|
||||
تمثّل كل وحدة ميزة أو مجموعة من الميزات، وتضم مكوّناتها الخاصة وحالاتها ومنطقها التشغيلي.
|
||||
يجب أن تتبع جميعها البنية المذكورة أدناه. يمكنك تعشيق الوحدات داخل الوحدات (تُعرف بالوحدات الفرعية) وستطبق نفس القواعد.
|
||||
|
||||
```
|
||||
module1
|
||||
└───components
|
||||
│ └───component1
|
||||
│ └───component2
|
||||
└───constants
|
||||
└───contexts
|
||||
└───graphql
|
||||
│ └───fragments
|
||||
│ └───queries
|
||||
│ └───mutations
|
||||
└───hooks
|
||||
│ └───internal
|
||||
└───states
|
||||
│ └───selectors
|
||||
└───types
|
||||
└───utils
|
||||
```
|
||||
|
||||
### السياقات
|
||||
|
||||
السياق هو وسيلة لتمرير البيانات عبر شجرة المكوّنات دون الحاجة إلى تمرير الخصائص يدويًا في كل مستوى.
|
||||
|
||||
انظر [React Context](https://react.dev/reference/react#context-hooks) لمزيد من التفاصيل.
|
||||
|
||||
### جراف كيو إل
|
||||
|
||||
تشمل الأجزاء، الاستعلامات والتحويرات.
|
||||
|
||||
انظر [GraphQL](https://graphql.org/learn/) لمزيد من التفاصيل.
|
||||
|
||||
* الأجزاء
|
||||
|
||||
الجزء هو جزء قابل لإعادة الاستخدام من استعلام، والذي يمكنك استخدامه في أماكن مختلفة. باستخدام الأجزاء، يصبح من الأسهل تجنب تكرار التعليمات البرمجية.
|
||||
|
||||
انظر [GraphQL Fragments](https://graphql.org/learn/queries/#fragments) لمزيد من التفاصيل.
|
||||
|
||||
* الاستعلامات
|
||||
|
||||
انظر [GraphQL Queries](https://graphql.org/learn/queries/) لمزيد من التفاصيل.
|
||||
|
||||
* التحويرات
|
||||
|
||||
انظر [GraphQL Mutations](https://graphql.org/learn/queries/#mutations) لمزيد من التفاصيل.
|
||||
|
||||
### الخطافات
|
||||
|
||||
انظر [Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) لمزيد من التفاصيل.
|
||||
|
||||
### الحالات
|
||||
|
||||
تشمل منطق إدارة الحالة. [RecoilJS](https://recoiljs.org) يتولّى ذلك.
|
||||
|
||||
* المحددات: انظر [RecoilJS Selectors](https://recoiljs.org/docs/basic-tutorial/selectors) لمزيد من التفاصيل.
|
||||
|
||||
لا تزال إدارة الحالة المدمجة في React تتولّى الحالة داخل المكوّن.
|
||||
|
||||
### الأدوات
|
||||
|
||||
يجب أن تحتوي فقط على دوال نقية قابلة لإعادة الاستخدام. بخلاف ذلك، أنشئ خطافات مخصصة في مجلد `hooks`.
|
||||
|
||||
## واجهة المستخدم
|
||||
|
||||
تحتوي على جميع مكونات واجهة المستخدم القابلة لإعادة الاستخدام المستخدمة في التطبيق.
|
||||
|
||||
يمكن أن يحتوي هذا المجلد على مجلّدات فرعية، مثل `data`، `display`، `feedback`، و `input` لأنواع معينة من المكوّنات. يجب أن يكون كل مكون مستقلًا وقابلًا لإعادة الاستخدام، حتى تتمكن من استخدامه في أجزاء مختلفة من التطبيق.
|
||||
|
||||
من خلال فصل مكونات واجهة المستخدم عن المكونات الأخرى في مجلد `modules`، يسهل الحفاظ على تصميم متسق وإجراء تغييرات على واجهة المستخدم دون التأثير على الأجزاء الأخرى (منطق الأعمال) من قاعدة الشيفرة.
|
||||
|
||||
## الواجهة والتبعيات
|
||||
|
||||
يمكنك استيراد شيفرة وحدة أخرى من أي وحدة باستثناء مجلد `ui`. سيجعل هذا شيفرته سهلة الاختبار.
|
||||
|
||||
### داخلي
|
||||
|
||||
كل جزء (خطافات، حالات، ...) من الممكن أن يحتوي جزء من الوحدة على مجلد `internal`، والذي يحتوي على الأجزاء التي تُستخدم فقط داخل الوحدة.
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
---
|
||||
title: أوامر الواجهة الأمامية
|
||||
---
|
||||
|
||||
## الأوامر المفيدة
|
||||
|
||||
### "بدء التطبيق"
|
||||
|
||||
```bash
|
||||
"npx nx start twenty-front"
|
||||
```
|
||||
|
||||
### "إعادة توليد مخطط graphql بناءً على مخطط API graphql"
|
||||
|
||||
```bash
|
||||
"npx nx run twenty-front:graphql:generate --configuration=metadata"
|
||||
```
|
||||
|
||||
"أو"
|
||||
|
||||
```bash
|
||||
"npx nx run twenty-front:graphql:generate"
|
||||
```
|
||||
|
||||
### Lint
|
||||
|
||||
```bash
|
||||
"npx nx run twenty-front:lint # مرر --fix لإصلاح أخطاء التدقيق"
|
||||
```
|
||||
|
||||
## "الترجمات"
|
||||
|
||||
```bash
|
||||
"npx nx run twenty-front:lingui:extract\nnpx nx run twenty-front:lingui:compile"
|
||||
```
|
||||
|
||||
### تجربة
|
||||
|
||||
```bash
|
||||
"npx nx run twenty-front:test # تشغيل اختبارات jest\nnpx nx run twenty-front:storybook:serve:dev # تشغيل storybook\nnpx nx run twenty-front:storybook:test # تشغيل الاختبارات # (يحتاج إلى تشغيل yarn storybook:serve:dev)\nnpx nx run twenty-front:storybook:coverage # (يحتاج إلى تشغيل yarn storybook:serve:dev)"
|
||||
```
|
||||
|
||||
## "التقنية المستخدمة"
|
||||
|
||||
"المشروع يحتوي على تقنية بسيطة ونظيفة، مع الحد الأدنى من كود البويلر بليت."
|
||||
|
||||
"**التطبيق**"
|
||||
|
||||
* "[React](https://react.dev/)"
|
||||
* "[Apollo](https://www.apollographql.com/docs/)"
|
||||
* "[GraphQL Codegen](https://the-guild.dev/graphql/codegen)"
|
||||
* "[Recoil](https://recoiljs.org/docs/introduction/core-concepts)"
|
||||
* "[TypeScript](https://www.typescriptlang.org/)"
|
||||
|
||||
**الاختبار**
|
||||
|
||||
* [Jest](https://jestjs.io/)
|
||||
* "[Storybook](https://storybook.js.org/)"
|
||||
|
||||
**الأدوات**
|
||||
|
||||
* [Yarn](https://yarnpkg.com/)
|
||||
* "[Craco](https://craco.js.org/docs/)"
|
||||
* [ESLint](https://eslint.org/)
|
||||
|
||||
## "الهيكلية"
|
||||
|
||||
### "التوجيه"
|
||||
|
||||
"[React Router](https://reactrouter.com/) يتعامل مع التوجيه."
|
||||
|
||||
"لتجنب [إعادة العرض](/l/ar/developers/contribute/capabilities/frontend-development/best-practices-front#managing-re-renders) غير الضرورية، يوجد منطق التوجيه في `useEffect` في `PageChangeEffect`."
|
||||
|
||||
### "إدارة الحالة"
|
||||
|
||||
"[Recoil](https://recoiljs.org/docs/introduction/core-concepts) يتعامل مع إدارة الحالة."
|
||||
|
||||
"راجع [أفضل الممارسات](/l/ar/developers/contribute/capabilities/frontend-development/best-practices-front#state-management) لمزيد من المعلومات حول إدارة الحالة."
|
||||
|
||||
## "الاختبار"
|
||||
|
||||
"[Jest](https://jestjs.io/) يعمل كأداة لاختبار الوحدات بينما يعمل [Storybook](https://storybook.js.org/) لاختبار المكونات."
|
||||
|
||||
"يستخدم Jest بشكل رئيسي لاختبار دوال الأدوات وليس المكونات نفسها."
|
||||
|
||||
"Storybook لاختبار سلوك المكونات المعزولة، وكذلك عرض نظام التصميم."
|
||||
@@ -4,5 +4,5 @@ image: /images/user-guide/tips/light-bulb.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/tips/light-bulb.png" alt="Header" />
|
||||
</Frame>
|
||||
<img src="/images/user-guide/tips/light-bulb.png" alt="رأس الصفحة" />
|
||||
</Frame>
|
||||
|
||||
@@ -4,49 +4,46 @@ image: /images/user-guide/table-views/table.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/table-views/table.png" alt="Header" />
|
||||
<img src="/images/user-guide/table-views/table.png" alt="رأس الصفحة" />
|
||||
</Frame>
|
||||
|
||||
يستعرض شريط التنقل الذي يحتوي على عدة مكونات `NavigationBarItem`.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="استخدام">
|
||||
```jsx
|
||||
استيراد { IconHome, IconUser, IconSettings } من '@tabler/icons-react';
|
||||
استيراد { NavigationBar } من "@/ui/navigation/navigation-bar/components/NavigationBar";
|
||||
|
||||
```jsx
|
||||
import { IconHome, IconUser, IconSettings } from '@tabler/icons-react';
|
||||
import { NavigationBar } from "@/ui/navigation/navigation-bar/components/NavigationBar";
|
||||
export const MyComponent = () => {
|
||||
|
||||
export const MyComponent = () => {
|
||||
const navigationItems = [
|
||||
{
|
||||
name: "الصفحة الرئيسية",
|
||||
Icon: IconHome,
|
||||
onClick: () => console.log("تم النقر على الصفحة الرئيسية"),
|
||||
},
|
||||
{
|
||||
name: "الملف الشخصي",
|
||||
Icon: IconUser,
|
||||
onClick: () => console.log("تم النقر على الملف الشخصي"),
|
||||
},
|
||||
{
|
||||
name: "الإعدادات",
|
||||
Icon: IconSettings,
|
||||
onClick: () => console.log("تم النقر على الإعدادات"),
|
||||
},
|
||||
];
|
||||
|
||||
const navigationItems = [
|
||||
{
|
||||
name: "Home",
|
||||
Icon: IconHome,
|
||||
onClick: () => console.log("Home clicked"),
|
||||
},
|
||||
{
|
||||
name: "Profile",
|
||||
Icon: IconUser,
|
||||
onClick: () => console.log("Profile clicked"),
|
||||
},
|
||||
{
|
||||
name: "Settings",
|
||||
Icon: IconSettings,
|
||||
onClick: () => console.log("Settings clicked"),
|
||||
},
|
||||
];
|
||||
return <NavigationBar activeItemName="الصفحة الرئيسية" items={navigationItems}/>;
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
return <NavigationBar activeItemName="Home" items={navigationItems}/>;
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| المحددات | النوع | الوصف |
|
||||
| ---------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| اسم العنصر النشط | string | اسم العنصر النشط حاليًا في قائمة التنقل |
|
||||
| العناصر | array | مصفوفة من الكائنات التي تمثل كل عنصر من عناصر التنقل. كل كائن يحتوي على `الاسم` للعنصر، ومكون `الأيقونة` للعرض، ودالة `onClick` التي تستدعي عند النقر على العنصر |
|
||||
|
||||
</Tab>
|
||||
<Tab title="المحددات">
|
||||
| المحددات | النوع | الوصف |
|
||||
| ---------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| اسم العنصر النشط | نص | اسم العنصر النشط حاليًا في قائمة التنقل |
|
||||
| العناصر | مصفوفة | مصفوفة من الكائنات التي تمثل كل عنصر من عناصر التنقل. كل كائن يحتوي على `الاسم` للعنصر، ومكون `الأيقونة` للعرض، ودالة `onClick` التي تستدعي عند النقر على العنصر |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
---
|
||||
title: Vlastní objekty
|
||||
---
|
||||
|
||||
Objekty jsou struktury, které vám umožňují ukládat data (záznamy, atributy a hodnoty) specifická pro organizaci. Twenty poskytuje jak standardní, tak vlastní objekty.
|
||||
|
||||
Standardní objekty jsou vestavěné objekty se sadou atributů dostupných pro všechny uživatele. Příklady standardních objektů v Twenty zahrnují Firma a Osoba. Standardní objekty mají standardní pole, která jsou také dostupná pro všechny uživatele Twenty, jako například Firma.displayName.
|
||||
|
||||
Vlastní objekty jsou objekty, které můžete vytvářet pro ukládání informací specifických pro vaši organizaci. Nejsou vestavěné; členové vaší pracovní skupiny mohou vytvářet a upravovat vlastní objekty pro ukládání informací, které nejsou vhodné pro standardní objekty.
|
||||
|
||||
## Schema na vysoké úrovni
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/server/custom-object-schema.png" alt="Schema na vysoké úrovni" />
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
## Jak to funguje
|
||||
|
||||
Vlastní objekty pocházejí z tabulek metadat, které určují tvar, název a typ objektů. Veškeré tyto informace jsou přítomny v databázi schémat metadat, která se skládá z následujících tabulek:
|
||||
|
||||
* **Zdroj dat**: Definuje, kde se data nacházejí.
|
||||
* **Objekt**: Popisuje objekt a odkazuje na DataSource.
|
||||
* **Pole**: Vymezuje pole objektu a připojuje je k objektu.
|
||||
|
||||
Pro přidání vlastního objektu vyhledá člen pracovní skupiny rozhraní /metadata API. To aktualizuje metadata odpovídajícím způsobem a generuje GraphQL schéma na základě metadat, které se ukládá v GQL cache pro pozdější použití.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/server/add-custom-objects.jpeg" alt="Vyhledejte /metadata API pro přidání vlastních objektů" />
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
Pro načtení dat proces zahrnuje provádění dotazů přes /graphql endpoint a jejich předání přes Query Resolver.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/server/custom-object-schema.png" alt="Dotazujte se na /graphql endpoint pro načtení dat." />
|
||||
</div>
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: Příznaky funkcí
|
||||
---
|
||||
|
||||
Příznaky funkcí slouží k skrytí zkušebních funkcí. Pro Twenty jsou nastaveny na úrovni pracovního prostoru, nikoli na úrovni uživatele.
|
||||
|
||||
## Přidání nového příznaku funkce
|
||||
|
||||
V `FeatureFlagKey.ts` přidat příznak funkce:
|
||||
|
||||
```ts
|
||||
type FeatureFlagKey =
|
||||
| 'IS_FEATURENAME_ENABLED'
|
||||
| ...;
|
||||
```
|
||||
|
||||
Také jej přidejte do výčtu v `feature-flag.entity.ts`:
|
||||
|
||||
```ts
|
||||
enum FeatureFlagKeys {
|
||||
IsFeatureNameEnabled = 'IS_FUNKCEPOVOLENA',
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Pro uplatnění příznaku funkce na **backendové** funkci použijte:
|
||||
|
||||
```ts
|
||||
@Gate({
|
||||
featureFlag: 'IS_FEATURENAME_ENABLED',
|
||||
})
|
||||
```
|
||||
|
||||
Pro uplatnění příznaku funkce na **frontendové** funkci použijte:
|
||||
|
||||
```ts
|
||||
const isFeatureNameEnabled = useIsFeatureEnabled('IS_FUNKCEPOVOLENA');
|
||||
```
|
||||
|
||||
## Nakonfigurujte příznaky funkcí pro nasazení
|
||||
|
||||
Změnit odpovídající záznam v tabulce `core.featureFlag`:
|
||||
|
||||
| id | klíč | workspaceId | hodnota |
|
||||
| ------- | ------------------- | ----------- | -------- |
|
||||
| Náhodný | `IS_FUNKCEPOVOLENA` | WorkspaceID | `pravda` |
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
---
|
||||
title: Fronta zpráv
|
||||
---
|
||||
|
||||
Fronty usnadňují provádění asynchronních operací. Mohou být použity pro provádění úkolů na pozadí, jako je například zaslání uvítacího e-mailu při registraci.
|
||||
Každý případ použití bude mít svou vlastní třídu fronty rozšířenou z `MessageQueueServiceBase`.
|
||||
|
||||
Aktuálně podporujeme pouze `bull-mq`[bull-mq](https://bullmq.io/) jako ovladač fronty.
|
||||
|
||||
## Kroky pro vytvoření a použití nové fronty
|
||||
|
||||
1. Přidejte název fronty pro vaši novou frontu pod výčtem `MESSAGE_QUEUES`.
|
||||
2. Poskytněte implementaci továrny fronty s názvem fronty jako token závislosti.
|
||||
3. Injektujte frontu, kterou jste vytvořili, do požadovaného modulu/služby s názvem fronty jako token závislosti.
|
||||
4. Přidejte třídu pracovníka s injekcí založenou na tokenu stejně jako producent.
|
||||
|
||||
### Ukázkové použití
|
||||
|
||||
```ts
|
||||
class Resolver {
|
||||
constructor(@Inject(MESSAGE_QUEUES.custom) private queue: MessageQueueService) {}
|
||||
|
||||
async onSomeAction() {
|
||||
//business logic
|
||||
await this.queue.add(someData);
|
||||
}
|
||||
}
|
||||
|
||||
//async worker
|
||||
class CustomWorker {
|
||||
constructor(@Inject(MESSAGE_QUEUES.custom) private queue: MessageQueueService) {
|
||||
this.initWorker();
|
||||
}
|
||||
|
||||
async initWorker() {
|
||||
await this.queue.work(async ({ id, data }) => {
|
||||
//worker logic
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
---
|
||||
title: Zapier App
|
||||
---
|
||||
|
||||
Snadno synchronizujte Twenty s více než 3000 aplikacemi pomocí [Zapier](https://zapier.com/). Automatizujte úkoly, zvyšte produktivitu a zlepšete vztahy se zákazníky!
|
||||
|
||||
## O Zapier
|
||||
|
||||
Zapier je nástroj, který umožňuje automatizovat pracovní postupy připojením aplikací, které váš tým používá každý den. Základním konceptem Zapier je automatizace pracovních postupů, nazývaných Zaps, které zahrnují spouštěče a akce.
|
||||
|
||||
Více o tom, jak Zapier funguje, se dozvíte [zde](https://zapier.com/how-it-works).
|
||||
|
||||
## Nastavení
|
||||
|
||||
### Krok 1: Nainstalujte balíčky Zapier
|
||||
|
||||
```bash
|
||||
cd packages/twenty-zapier\n\nyarn
|
||||
```
|
||||
|
||||
### Krok 2: Přihlaste se pomocí CLI
|
||||
|
||||
Použijte své Zapier přihlašovací údaje k přihlášení pomocí CLI:
|
||||
|
||||
```bash
|
||||
zapier login
|
||||
```
|
||||
|
||||
### Krok 3: Nastavte proměnné prostředí
|
||||
|
||||
Z adresáře `packages/twenty-zapier` spusťte:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Spusťte aplikaci lokálně, přejděte na [http://localhost:3000/settings/api-webhooks](http://localhost:3000/settings/api-webhooks), a vygenerujte klíč API.
|
||||
|
||||
Nahraďte hodnotu **YOUR_API_KEY** v souboru `.env` klíčem API, který jste právě vygenerovali.
|
||||
|
||||
## Vývoj
|
||||
|
||||
<Warning>
|
||||
Ujistěte se, že před jakýmkoli příkazem `zapier` spustíte `yarn build`.
|
||||
</Warning>
|
||||
|
||||
### Testovat
|
||||
|
||||
```bash
|
||||
yarn test
|
||||
```
|
||||
|
||||
### Linter
|
||||
|
||||
```bash
|
||||
yarn format
|
||||
```
|
||||
|
||||
### Sledování a kompilace při úpravě kódu
|
||||
|
||||
```bash
|
||||
yarn watch
|
||||
```
|
||||
|
||||
### Ověřte svou aplikaci Zapier
|
||||
|
||||
```bash
|
||||
yarn validate
|
||||
```
|
||||
|
||||
### Nasazení vaší aplikace Zapier
|
||||
|
||||
```bash
|
||||
yarn deploy
|
||||
```
|
||||
|
||||
### Seznam všech příkazů Zapier CLI
|
||||
|
||||
```bash
|
||||
zapier
|
||||
```
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
---
|
||||
title: Klávesové zkratky
|
||||
---
|
||||
|
||||
## Úvod
|
||||
|
||||
Pokud potřebujete poslouchat klávesovou zkratku, obvykle použijete posluchač událostí `onKeyDown`.
|
||||
|
||||
Ve `twenty-front` však můžete mít konflikty mezi stejnými klávesovými zkratkami, které jsou použity v různých komponentách, nasazených současně.
|
||||
|
||||
Například, pokud máte stránku, která poslouchá klávesu Enter, a modal, který poslouchá klávesu Enter, s komponentou Select uvnitř tohoto modalu, která poslouchá klávesu Enter, můžete mít konflikt, když jsou všechny nasazeny současně.
|
||||
|
||||
## Funkce hook `useScopedHotkeys`
|
||||
|
||||
K řešení tohoto problému máme vlastní hook, který umožňuje poslouchat klávesové zkratky bez jakéhokoliv konfliktu.
|
||||
|
||||
Umístíte ho do komponenty a bude poslouchat klávesové zkratky pouze když je komponenta nasazena A když je aktivní specifikovaný **hotkey scope**.
|
||||
|
||||
## Jak v praxi poslouchat klávesové zkratky?
|
||||
|
||||
Jsou potřebné dva kroky k nastavení poslouchání klávesových zkratek :
|
||||
|
||||
1. Nastavte [hotkey scope](#what-is-a-hotkey-scope-), který bude poslouchat klávesové zkratky
|
||||
2. Použijte hook `useScopedHotkeys` k poslouchání klávesových zkratek
|
||||
|
||||
Nastavení oblastí klávesových zkratek je potřebné i na jednoduchých stránkách, protože jiné UI prvky jako levé menu nebo příkazové menu mohou také poslouchat klávesové zkratky.
|
||||
|
||||
## Použití klávesových zkratek
|
||||
|
||||
Obecně budete mít dva případy použití, které vyžadují klávesové zkratky :
|
||||
|
||||
1. Na stránce nebo v komponentě nasazené na stránce
|
||||
2. V komponentě typu modal, která přebírá fokus kvůli akci uživatele
|
||||
|
||||
Druhý případ použití se může stát rekurzivně: například rozbalovací seznam v modalu.
|
||||
|
||||
### Poslouchání klávesových zkratek na stránce
|
||||
|
||||
Příklad :
|
||||
|
||||
```tsx
|
||||
const PageListeningEnter = () => {
|
||||
const {
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
goBackToPreviousHotkeyScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
// 1. Nastavte rozsah klávesových zkratek v useEffect
|
||||
useEffect(() => {
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
ExampleHotkeyScopes.ExampleEnterPage,
|
||||
);
|
||||
|
||||
// Vraťte se k předchozímu rozsahu klávesových zkratek, když se komponenta odpojí
|
||||
return () => {
|
||||
goBackToPreviousHotkeyScope();
|
||||
};
|
||||
}, [goBackToPreviousHotkeyScope, setHotkeyScopeAndMemorizePreviousScope]);
|
||||
|
||||
// 2. Použijte hook useScopedHotkeys
|
||||
useScopedHotkeys(
|
||||
Key.Enter,
|
||||
() => {
|
||||
// Nějaká logika vykonaná na této stránce, když uživatel stiskne Enter
|
||||
// ...
|
||||
},
|
||||
ExampleHotkeyScopes.ExampleEnterPage,
|
||||
);
|
||||
|
||||
return <div>Moje stránka, která poslouchá Enter</div>;
|
||||
};
|
||||
```
|
||||
|
||||
### Poslouchání klávesových zkratek v komponentě typu modal
|
||||
|
||||
V tomto příkladu použijeme komponentu typu modal, která poslouchá klávesu Escape, aby svému nadřazenému komponentu řekla, že se má zavřít.
|
||||
|
||||
Zde interakce uživatele mění rozsah.
|
||||
|
||||
```tsx
|
||||
const ExamplePageWithModal = () => {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
const {
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
goBackToPreviousHotkeyScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
const handleOpenModalClick = () => {
|
||||
// 1. Nastavte rozsah klávesových zkratek, když uživatel otevře modal
|
||||
setShowModal(true);
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
ExampleHotkeyScopes.ExampleModal,
|
||||
);
|
||||
};
|
||||
|
||||
const handleModalClose = () => {
|
||||
// 1. Vraťte se k předchozímu rozsahu klávesových zkratek, když se modal zavře
|
||||
setShowModal(false);
|
||||
goBackToPreviousHotkeyScope();
|
||||
};
|
||||
|
||||
return <div>
|
||||
<h1>Moje stránka s modálem</h1>
|
||||
<button onClick={handleOpenModalClick}>Otevřít modal</button>
|
||||
{showModal && <MyModalComponent onClose={handleModalClose} />}
|
||||
</div>;
|
||||
};
|
||||
```
|
||||
|
||||
Pak v komponentě modalu :
|
||||
|
||||
```tsx
|
||||
const MyDropdownComponent = ({ onClose }: { onClose: () => void }) => {
|
||||
// 2. Použijte hook useScopedHotkeys k poslouchání Escape.
|
||||
// Všimněte si, že escape je běžná klávesová zkratka, kterou může použít mnoho jiných komponent
|
||||
// Je tedy důležité používat rozsah klávesových zkratek, aby se předešlo konfliktům
|
||||
useScopedHotkeys(
|
||||
Key.Escape,
|
||||
() => {
|
||||
onClose()
|
||||
},
|
||||
ExampleHotkeyScopes.ExampleModal,
|
||||
);
|
||||
|
||||
return <div>Moje komponenta modalu</div>;
|
||||
};
|
||||
```
|
||||
|
||||
Je důležité používat tento vzor, když si nejste jisti, že samotné použití useEffect pro nastavení a odebrání bude dostatečné k zabránění konfliktům.
|
||||
|
||||
Tyto konflikty mohou být těžké ladit a mohou se vyskytovat častěji než ne u useEffects.
|
||||
|
||||
## Co je to hotkey scope?
|
||||
|
||||
Rozsah klávesových zkratek je řetězec, který představuje kontext, ve kterém jsou klávesové zkratky aktivní. Obecně je kódován jako enum.
|
||||
|
||||
Když změníte rozsah klávesových zkratek, klávesové zkratky, které poslouchají tento rozsah, budou povolené a klávesové zkratky, které poslouchají jiné rozsahy, budou zakázané.
|
||||
|
||||
Můžete nastavit pouze jeden rozsah najednou.
|
||||
|
||||
Například rozsahy klávesových zkratek pro každou stránku jsou definovány v `PageHotkeyScope` enumu:
|
||||
|
||||
```tsx
|
||||
export enum PageHotkeyScope {
|
||||
Nastavení = 'settings',
|
||||
VytvořitPracovníProstor = 'create-workspace',
|
||||
PřihlášeníRegistrace = 'sign-in-up',
|
||||
VytvořitProfil = 'create-profile',
|
||||
PožadovánoPlán = 'plan-required',
|
||||
ZobrazitStránku = 'show-page',
|
||||
OsobníZobrazitStránku = 'person-show-page',
|
||||
FiremníZobrazitStránku = 'company-show-page',
|
||||
FirmyStránka = 'companies-page',
|
||||
LidéStránka = 'people-page',
|
||||
PříležitostiStránka = 'opportunities-page',
|
||||
ProfilStránka = 'profile-page',
|
||||
ČlenPracovníhoProstoruStránka = 'workspace-member-page',
|
||||
ÚkolStránka = 'task-page',
|
||||
}
|
||||
```
|
||||
|
||||
Interně je aktuálně vybraný rozsah uložen v Recoil stavu, který je sdílen napříč aplikací :
|
||||
|
||||
```tsx
|
||||
export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||
key: 'currentHotkeyScopeState',
|
||||
defaultValue: INITIAL_HOTKEYS_SCOPE,
|
||||
});
|
||||
```
|
||||
|
||||
Ale tento Recoil stav by neměl být nikdy řízen ručně! Ukážeme si, jak jej používat v příští sekci.
|
||||
|
||||
## Jak to funguje interně?
|
||||
|
||||
Vytvořili jsme tenkou vrstvu nad [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro), která je výkonnější a vyhýbá se zbytečným překreslením.
|
||||
|
||||
Také jsme vytvořili Recoil stav, abychom mohli řídit stav rozsahu klávesových zkratek a učinit jej dostupným kdekoli v aplikaci.
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: Další metody
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Tento dokument je udržován komunitou. Může obsahovat problémy.
|
||||
</Warning>
|
||||
|
||||
## Kubernetes pomocí Terraform a Manifestů
|
||||
|
||||
Dokumentace vedená komunitou k nasazení Kubernetes je k dispozici [zde](https://github.com/twentyhq/twenty/tree/main/packages/twenty-docker/k8s)
|
||||
|
||||
### Coolify
|
||||
|
||||
Nasazení Twenty na serverech pomocí Coolify. (oficiální obrázek na Coolify bude brzy k dispozici)
|
||||
|
||||
[Dokumentace Coolify](https://coolify.io/docs/get-started/introduction)
|
||||
|
||||
### EasyPanel
|
||||
|
||||
Nasazení Twenty na EasyPanel s komunitně udržovanou šablonou níže.
|
||||
|
||||
[Nasadit na EasyPanel](https://easypanel.io/docs/templates/twenty)
|
||||
|
||||
### Elest.io
|
||||
|
||||
Nasazení Twenty na serverech pomocí Elest.io s odkazem níže.
|
||||
|
||||
[Nasadit na Elest.io](https://elest.io/open-source/twenty)
|
||||
|
||||
### Twenty na Railway
|
||||
|
||||
Nasazení Twenty na Railway s komunitně udržovanou šablonou níže.
|
||||
|
||||
[](https://railway.com/deploy/nAL3hA)
|
||||
|
||||
### Ostatní
|
||||
|
||||
Nasazení Twenty na Sealos s komunitně udržovanou šablonou níže.
|
||||
|
||||
[](https://sealos.io/products/app-store/twenty)
|
||||
|
||||
## Ostatní
|
||||
|
||||
Neváhejte otevřít PR pro přidání dalších možností poskytovatele cloudových služeb.
|
||||
@@ -4,5 +4,5 @@ image: /images/user-guide/tips/light-bulb.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/tips/light-bulb.png" alt="Header" />
|
||||
</Frame>
|
||||
<img src="/images/user-guide/tips/light-bulb.png" alt="Hlavička" />
|
||||
</Frame>
|
||||
|
||||
@@ -4,5 +4,5 @@ image: /images/user-guide/tasks/tasks_header.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/tasks/tasks_header.png" alt="Header" />
|
||||
</Frame>
|
||||
<img src="/images/user-guide/tasks/tasks_header.png" alt="Hlavička" />
|
||||
</Frame>
|
||||
|
||||
@@ -4,41 +4,38 @@ image: /images/user-guide/fields/field.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/fields/field.png" alt="Header" />
|
||||
<img src="/images/user-guide/fields/field.png" alt="Hlavička" />
|
||||
</Frame>
|
||||
|
||||
Zobrazuje drobečkovou navigační lištu.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="Použití">
|
||||
```jsx
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import { Breadcrumb } from "@/ui/navigation/bread-crumb/components/Breadcrumb";
|
||||
|
||||
```jsx
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import { Breadcrumb } from "@/ui/navigation/bread-crumb/components/Breadcrumb";
|
||||
export const MyComponent = () => {
|
||||
const breadcrumbLinks = [
|
||||
{ children: "Domů", href: "/" },
|
||||
{ children: "Kategorie", href: "/category" },
|
||||
{ children: "Podkategorie", href: "/category/subcategory" },
|
||||
{ children: "Aktuální stránka" },
|
||||
];
|
||||
|
||||
export const MyComponent = () => {
|
||||
const breadcrumbLinks = [
|
||||
{ children: "Home", href: "/" },
|
||||
{ children: "Category", href: "/category" },
|
||||
{ children: "Subcategory", href: "/category/subcategory" },
|
||||
{ children: "Current Page" },
|
||||
];
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Breadcrumb className links={breadcrumbLinks} />
|
||||
</BrowserRouter>
|
||||
)
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Breadcrumb className links={breadcrumbLinks} />
|
||||
</BrowserRouter>
|
||||
)
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| Vlastnosti | Typ | Popis |
|
||||
| ---------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| className | textový řetězec | Volitelný název třídy pro dodatečné stylování |
|
||||
| odkazy | pole | Pole objektů, z nichž každý představuje odkaz na drobečkovou navigaci. Každý objekt má vlastnost `children` (text obsahu odkazu) a nepovinnou vlastnost `href` (URL, na které se má navigovat po kliknutí na odkaz) |
|
||||
|
||||
</Tab>
|
||||
<Tab title="Vlastnosti">
|
||||
| Vlastnosti | Typ | Popis |
|
||||
| ---------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| className | řetězec | Volitelný název třídy pro dodatečné stylování |
|
||||
| odkazy | pole | Pole objektů, z nichž každý představuje odkaz na drobečkovou navigaci. Každý objekt má vlastnost `children` (text obsahu odkazu) a nepovinnou vlastnost `href` (URL, na které se má navigovat po kliknutí na odkaz) |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -4,7 +4,7 @@ image: /images/user-guide/what-is-twenty/20.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/what-is-twenty/20.png" alt="Header" />
|
||||
<img src="/images/user-guide/what-is-twenty/20.png" alt="Hlavička" />
|
||||
</Frame>
|
||||
|
||||
## Odkaz na kontakt
|
||||
@@ -12,44 +12,40 @@ image: /images/user-guide/what-is-twenty/20.png
|
||||
Stylizovaná komponenta odkazu pro zobrazení kontaktních informací.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="Použití">
|
||||
```jsx
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
|
||||
```jsx
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { ContactLink } from 'twenty-ui/navigation';
|
||||
|
||||
import { ContactLink } from 'twenty-ui/navigation';
|
||||
export const MyComponent = () => {
|
||||
const handleLinkClick = (event) => {
|
||||
console.log('Odkaz na kontakt kliknut!', event);
|
||||
};
|
||||
|
||||
export const MyComponent = () => {
|
||||
const handleLinkClick = (event) => {
|
||||
console.log('Contact link clicked!', event);
|
||||
};
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<ContactLink
|
||||
className
|
||||
href="mailto:example@example.com"
|
||||
onClick={handleLinkClick}
|
||||
>
|
||||
example@example.com
|
||||
</ContactLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| Vlastnosti | Typ | Popis |
|
||||
| ---------- | ----------------- | ----------------------------------------------------- |
|
||||
| className | textový řetězec | Volitelný název pro dodatečné stylování |
|
||||
| href | textový řetězec | Cílová URL adresa nebo cesta pro odkaz |
|
||||
| onClick | funkce | Funkce zpětného volání spuštěná při kliknutí na odkaz |
|
||||
| děti | `React.ReactNode` | Obsah, který se má zobrazovat uvnitř odkazu |
|
||||
|
||||
</Tab>
|
||||
return (
|
||||
<Router>
|
||||
<ContactLink
|
||||
className
|
||||
href="mailto:example@example.com"
|
||||
onClick={handleLinkClick}
|
||||
>
|
||||
example@example.com
|
||||
</ContactLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Vlastnosti">
|
||||
| Vlastnosti | Typ | Popis |
|
||||
| ---------- | ----------------- | ----------------------------------------------------- |
|
||||
| className | řetězec | Volitelný název pro další stylování |
|
||||
| href | řetězec | Cílová URL adresa nebo cesta pro odkaz |
|
||||
| onClick | funkce | Funkce zpětného volání spuštěná při kliknutí na odkaz |
|
||||
| děti | `React.ReactNode` | Obsah, který se má zobrazovat uvnitř odkazu |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Surový odkaz
|
||||
@@ -57,39 +53,35 @@ export const MyComponent = () => {
|
||||
Stylizovaná komponenta odkazu pro zobrazení odkazů.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="Použití">
|
||||
```jsx
|
||||
import { RawLink } from "/navigation";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
|
||||
```jsx
|
||||
import { RawLink } from "/navigation";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
export const MyComponent = () => {
|
||||
const handleLinkClick = (event) => {
|
||||
console.log("Odkaz na kontakt kliknut!", event);
|
||||
};
|
||||
|
||||
export const MyComponent = () => {
|
||||
const handleLinkClick = (event) => {
|
||||
console.log("Contact link clicked!", event);
|
||||
};
|
||||
return (
|
||||
<Router>
|
||||
<RawLink className href="/contact" onClick={handleLinkClick}>
|
||||
Kontaktujte nás
|
||||
</RawLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<RawLink className href="/contact" onClick={handleLinkClick}>
|
||||
Contact Us
|
||||
</RawLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| Vlastnosti | Typ | Popis |
|
||||
| ---------- | ----------------- | ----------------------------------------------------- |
|
||||
| className | textový řetězec | Volitelný název pro dodatečné stylování |
|
||||
| href | textový řetězec | Cílová URL adresa nebo cesta pro odkaz |
|
||||
| onClick | funkce | Funkce zpětného volání spuštěná při kliknutí na odkaz |
|
||||
| děti | `React.ReactNode` | Obsah, který se má zobrazovat uvnitř odkazu |
|
||||
|
||||
</Tab>
|
||||
<Tab title="Vlastnosti">
|
||||
| Vlastnosti | Typ | Popis |
|
||||
| ---------- | ----------------- | ----------------------------------------------------- |
|
||||
| className | řetězec | Volitelný název pro dodatečné stylování |
|
||||
| href | řetězec | Cílová URL adresa nebo cesta pro odkaz |
|
||||
| onClick | funkce | Funkce zpětného volání spuštěná při kliknutí na odkaz |
|
||||
| děti | `React.ReactNode` | Obsah, který se má zobrazovat uvnitř odkazu |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Zaoblený odkaz
|
||||
@@ -97,38 +89,34 @@ export const MyComponent = () => {
|
||||
Zaobleně stylizovaný odkaz s komponentou Chip pro odkazy.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="Použití">
|
||||
```jsx
|
||||
import { RoundedLink } from "/navigation";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
|
||||
```jsx
|
||||
import { RoundedLink } from "/navigation";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
export const MyComponent = () => {
|
||||
const handleLinkClick = (event) => {
|
||||
console.log("Odkaz na kontakt kliknut!", event);
|
||||
};
|
||||
|
||||
export const MyComponent = () => {
|
||||
const handleLinkClick = (event) => {
|
||||
console.log("Contact link clicked!", event);
|
||||
};
|
||||
return (
|
||||
<Router>
|
||||
<RoundedLink href="/contact" onClick={handleLinkClick}>
|
||||
Kontaktujte nás
|
||||
</RoundedLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<RoundedLink href="/contact" onClick={handleLinkClick}>
|
||||
Contact Us
|
||||
</RoundedLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Props">
|
||||
|
||||
| Vlastnosti | Typ | Popis |
|
||||
| ---------- | ----------------- | ----------------------------------------------------- |
|
||||
| href | textový řetězec | Cílová URL adresa nebo cesta pro odkaz |
|
||||
| děti | `React.ReactNode` | Obsah, který se má zobrazovat uvnitř odkazu |
|
||||
| onClick | funkce | Funkce zpětného volání spuštěná při kliknutí na odkaz |
|
||||
|
||||
</Tab>
|
||||
<Tab title="Vlastnosti">
|
||||
| Vlastnosti | Typ | Popis |
|
||||
| ---------- | ----------------- | ----------------------------------------------------- |
|
||||
| href | řetězec | Cílová URL adresa nebo cesta pro odkaz |
|
||||
| děti | `React.ReactNode` | Obsah, který se má zobrazovat uvnitř odkazu |
|
||||
| onClick | funkce | Funkce zpětného volání spuštěná při kliknutí na odkaz |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Sociální odkaz
|
||||
@@ -136,33 +124,30 @@ export const MyComponent = () => {
|
||||
Stylizované sociální odkazy s podporou různých typů sociálních odkazů, jako jsou URL, LinkedIn a X (nebo Twitter).
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="Použití">
|
||||
```jsx
|
||||
import { SocialLink } from "twenty-ui/navigation";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
|
||||
```jsx
|
||||
import { SocialLink } from "twenty-ui/navigation";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<Router>
|
||||
<SocialLink
|
||||
type="twitter"
|
||||
href="https://twitter.com/twentycrm"
|
||||
></SocialLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<Router>
|
||||
<SocialLink
|
||||
type="twitter"
|
||||
href="https://twitter.com/twentycrm"
|
||||
></SocialLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| Vlastnosti | Typ | Popis |
|
||||
| ---------- | ----------------- | ------------------------------------------------------------------------------------------------------- |
|
||||
| href | textový řetězec | Cílová URL adresa nebo cesta pro odkaz |
|
||||
| děti | `React.ReactNode` | Obsah, který se má zobrazovat uvnitř odkazu |
|
||||
| typ | textový řetězec | Typ sociálního odkazu. Možnosti zahrnují: `url`, `LinkedIn` a `Twitter` |
|
||||
| onClick | funkce | Funkce zpětného volání spuštěná při kliknutí na odkaz |
|
||||
|
||||
</Tab>
|
||||
<Tab title="Vlastnosti">
|
||||
| Vlastnosti | Typ | Popis |
|
||||
| ---------- | ----------------- | ----------------------------------------------------------------------- |
|
||||
| href | řetězec | Cílová URL adresa nebo cesta pro odkaz |
|
||||
| děti | `React.ReactNode` | Obsah, který se má zobrazovat uvnitř odkazu |
|
||||
| typ | řetězec | Typ sociálního odkazu. Možnosti zahrnují: `url`, `LinkedIn` a `Twitter` |
|
||||
| onClick | funkce | Funkce zpětného volání spuštěná při kliknutí na odkaz |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -4,49 +4,46 @@ image: /images/user-guide/table-views/table.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/table-views/table.png" alt="Header" />
|
||||
<img src="/images/user-guide/table-views/table.png" alt="Hlavička" />
|
||||
</Frame>
|
||||
|
||||
Zobrazuje navigační panel, který obsahuje více komponent `NavigationBarItem`.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="Použití">
|
||||
```jsx
|
||||
import { IconHome, IconUser, IconSettings } from '@tabler/icons-react';
|
||||
import { NavigationBar } from "@/ui/navigation/navigation-bar/components/NavigationBar";
|
||||
|
||||
```jsx
|
||||
import { IconHome, IconUser, IconSettings } from '@tabler/icons-react';
|
||||
import { NavigationBar } from "@/ui/navigation/navigation-bar/components/NavigationBar";
|
||||
export const MyComponent = () => {
|
||||
|
||||
export const MyComponent = () => {
|
||||
const navigationItems = [
|
||||
{
|
||||
name: "Home",
|
||||
Icon: IconHome,
|
||||
onClick: () => console.log("Domů kliknuto"),
|
||||
},
|
||||
{
|
||||
name: "Profile",
|
||||
Icon: IconUser,
|
||||
onClick: () => console.log("Profil kliknuto"),
|
||||
},
|
||||
{
|
||||
name: "Settings",
|
||||
Icon: IconSettings,
|
||||
onClick: () => console.log("Nastavení kliknuto"),
|
||||
},
|
||||
];
|
||||
|
||||
const navigationItems = [
|
||||
{
|
||||
name: "Home",
|
||||
Icon: IconHome,
|
||||
onClick: () => console.log("Home clicked"),
|
||||
},
|
||||
{
|
||||
name: "Profile",
|
||||
Icon: IconUser,
|
||||
onClick: () => console.log("Profile clicked"),
|
||||
},
|
||||
{
|
||||
name: "Settings",
|
||||
Icon: IconSettings,
|
||||
onClick: () => console.log("Settings clicked"),
|
||||
},
|
||||
];
|
||||
return <NavigationBar activeItemName="Home" items={navigationItems}/>;
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
return <NavigationBar activeItemName="Home" items={navigationItems}/>;
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| Vlastnosti | Typ | Popis |
|
||||
| -------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| activeItemName | textový řetězec | Název aktuálně aktivního navigačního prvku |
|
||||
| položky | pole | Pole objektů představujících jednotlivé navigační položky. Každý objekt obsahuje `název` položky, komponent `Ikona` ke zobrazení a funkce `onClick`, která se volá při kliknutí na položku. |
|
||||
|
||||
</Tab>
|
||||
<Tab title="Vlastnosti">
|
||||
| Vlastnosti | Typ | Popis |
|
||||
| -------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| activeItemName | řetězec | Název aktuálně aktivního navigačního prvku |
|
||||
| položky | pole | Pole objektů představujících jednotlivé navigační položky. Každý objekt obsahuje `název` položky, komponent `Ikona` ke zobrazení a funkce `onClick`, která se volá při kliknutí na položku. |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
---
|
||||
title: Profesionální služby
|
||||
description: Získejte profesionální pomoc s vytvářením složitých pracovních toků a automatizací od týmu Twenty a certifikovaných partnerů.
|
||||
---
|
||||
|
||||
## Kdy potřebujete profesionální pomoc?
|
||||
|
||||
Zvažte profesionální služby pro:
|
||||
|
||||
* Složitá více-systémová integrace
|
||||
* Pokročilá obchodní logika a pravidla automatizace
|
||||
* Velkoplošné pracovní postupy pro zpracování dat
|
||||
* Vývoj vlastních API
|
||||
* Školení týmu a optimalizace pracovního toku
|
||||
* Když nemáte interní zdroje
|
||||
|
||||
## Možnosti služeb
|
||||
|
||||
### Úvodní balíčky
|
||||
|
||||
Získejte pomoc od našeho hlavního týmu s našimi 4hodinovými [Úvodními balíčky](https://twenty.com/onboarding-packages):
|
||||
|
||||
* **Vytváření pracovních toků**: Vytvářejte vlastní pracovní toky pro Vaše podnikové procesy
|
||||
* **Návrh datového modelu**: Optimalizujte strukturu svých dat pro automatizaci pracovního toku
|
||||
* **Migrace dat**: Importujte stávající data s řádnou integrací pracovního toku
|
||||
|
||||
### Implementační partneři
|
||||
|
||||
Spolupracujte s certifikovanými partnery na pokročilých úpravách. Kontaktujte nás na contact@twenty.com pro spojení s našimi [implementačními partnery](https://twenty.com/partners).
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: Beste Praktiken
|
||||
---
|
||||
|
||||
Dieses Dokument beschreibt die Best Practices, die Sie bei der Arbeit am Backend befolgen sollten.
|
||||
|
||||
## Verfolgen Sie einen modularen Ansatz
|
||||
|
||||
Das Backend folgt einem modularen Ansatz, was ein grundlegendes Prinzip bei der Arbeit mit NestJS ist. Stellen Sie sicher, dass Sie Ihren Code in wiederverwendbare Module aufteilen, um eine saubere und organisierte Codebasis aufrechtzuerhalten.
|
||||
Jedes Modul sollte ein bestimmtes Feature oder eine Funktionalität kapseln und einen klar definierten Umfang haben. Dieser modulare Ansatz ermöglicht eine klare Trennung der Verantwortlichkeiten und beseitigt unnötige Komplexitäten.
|
||||
|
||||
## Dienste zur Verwendung in Modulen bereitstellen
|
||||
|
||||
Erstellen Sie immer Dienste mit einer klaren Einzelverantwortung, was die Lesbarkeit und Wartbarkeit des Codes verbessert. Benennen Sie die Dienste beschreibend und konsistent.
|
||||
|
||||
Sie sollten auch Dienste bereitstellen, die Sie in anderen Modulen verwenden möchten. Das Bereitstellen von Diensten für andere Module ist durch das leistungsstarke Abhängigkeitsinjektionssystem von NestJS möglich und fördert die lose Kopplung zwischen den Komponenten.
|
||||
|
||||
## Vermeiden Sie die Verwendung des Typs `any`
|
||||
|
||||
Wenn Sie eine Variable als `any` deklarieren, führt der TypeScript-Typenprüfer keine Typprüfung durch, was es möglich macht, der Variablen Werte beliebigen Typs zuzuweisen. TypeScript verwendet Typinferenz, um den Typ einer Variable basierend auf dem Wert zu bestimmen. Durch die Deklaration als `any` kann TypeScript den Typ nicht mehr ableiten. Dies erschwert es, typbezogene Fehler während der Entwicklung zu erkennen, was zu Laufzeitfehlern führt und den Code weniger wartbar, weniger zuverlässig und schwerer verständlich für andere macht.
|
||||
|
||||
Deshalb sollte alles einen Typ haben. Wenn Sie also ein neues Objekt mit Vor- und Nachnamen erstellen, sollten Sie eine Schnittstelle oder einen Typ definieren, der Vor- und Nachnamen enthält und die Struktur des Objekts definiert, mit dem Sie arbeiten.
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
---
|
||||
title: Nachrichtenwarteschlange
|
||||
---
|
||||
|
||||
Warteschlangen erleichtern die Durchführung asynchroner Vorgänge. Sie können für die Ausführung von Hintergrundaufgaben verwendet werden, wie z.B. das Versenden einer Willkommens-E-Mail bei der Registrierung.
|
||||
Jeder Anwendungsfall hat seine eigene Warteschlangenklasse, die von `MessageQueueServiceBase` erweitert wird.
|
||||
|
||||
Derzeit unterstützen wir nur `bull-mq`[bull-mq](https://bullmq.io/) als Warteschlangentreiber.
|
||||
|
||||
## Schritte zur Erstellung und Nutzung einer neuen Warteschlange
|
||||
|
||||
1. Fügen Sie einen Warteschlangennamen für Ihre neue Warteschlange unter dem Enum `MESSAGE_QUEUES` hinzu.
|
||||
2. Stellen Sie die Factory-Implementierung der Warteschlange mit dem Warteschlangennamen als Abhängigkeits-Token bereit.
|
||||
3. Injizieren Sie die von Ihnen erstellte Warteschlange im erforderlichen Modul/Service mit dem Warteschlangennamen als Abhängigkeits-Token.
|
||||
4. Fügen Sie eine Worker-Klasse mit tokenbasierter Injektion hinzu, genau wie der Producer.
|
||||
|
||||
### Beispielverwendung
|
||||
|
||||
```ts
|
||||
class Resolver {
|
||||
constructor(@Inject(MESSAGE_QUEUES.custom) private queue: MessageQueueService) {}
|
||||
|
||||
async onSomeAction() {
|
||||
//business logic
|
||||
await this.queue.add(someData);
|
||||
}
|
||||
}
|
||||
|
||||
// async-WorKer
|
||||
class CustomWorker {
|
||||
constructor(@Inject(MESSAGE_QUEUES.custom) private queue: MessageQueueService) {
|
||||
this.initWorker();
|
||||
}
|
||||
|
||||
async initWorker() {
|
||||
await this.queue.work(async ({ id, data }) => {
|
||||
//worker logic
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
---
|
||||
title: Frontend-Befehle
|
||||
---
|
||||
|
||||
## Nützliche Befehle
|
||||
|
||||
### Die App starten
|
||||
|
||||
```bash
|
||||
npx nx start twenty-front
|
||||
```
|
||||
|
||||
### GraphQL-Schema basierend auf API-GraphQL-Schema regenerieren
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:graphql:generate --configuration=metadata
|
||||
```
|
||||
|
||||
ODER
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:graphql:generate
|
||||
```
|
||||
|
||||
### Lint
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:lint # Füge --fix hinzu, um Lint-Fehler zu beheben
|
||||
```
|
||||
|
||||
## Übersetzungen
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:lingui:extract
|
||||
npx nx run twenty-front:lingui:compile
|
||||
```
|
||||
|
||||
### Test
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:test # führe JEST-Tests aus
|
||||
npx nx run twenty-front:storybook:serve:dev # führe Storybook aus
|
||||
npx nx run twenty-front:storybook:test # führe Tests aus # benötigt yarn storybook:serve:dev als laufenden Prozess
|
||||
npx nx run twenty-front:storybook:coverage # benötigt yarn storybook:serve:dev als laufenden Prozess
|
||||
```
|
||||
|
||||
## Technologie-Stack
|
||||
|
||||
Das Projekt hat einen sauberen und einfachen Stack mit minimalem Boilerplate-Code.
|
||||
|
||||
**App**
|
||||
|
||||
* [React](https://react.dev/)
|
||||
* [Apollo](https://www.apollographql.com/docs/)
|
||||
* [GraphQL Codegen](https://the-guild.dev/graphql/codegen)
|
||||
* [Recoil](https://recoiljs.org/docs/introduction/core-concepts)
|
||||
* [TypeScript](https://www.typescriptlang.org/)
|
||||
|
||||
**Tests**
|
||||
|
||||
* [Jest](https://jestjs.io/)
|
||||
* [Storybook](https://storybook.js.org/)
|
||||
|
||||
**Tools**
|
||||
|
||||
* [Yarn](https://yarnpkg.com/)
|
||||
* [Craco](https://craco.js.org/docs/)
|
||||
* [ESLint](https://eslint.org/)
|
||||
|
||||
## Architektur
|
||||
|
||||
### Routing
|
||||
|
||||
[React Router](https://reactrouter.com/) übernimmt das Routing.
|
||||
|
||||
Um unnötige [Re-Renders](/l/de/developers/contribute/capabilities/frontend-development/best-practices-front#managing-re-renders) zu vermeiden, befindet sich die gesamte Routing-Logik in einem `useEffect` in `PageChangeEffect`.
|
||||
|
||||
### Zustandsverwaltung
|
||||
|
||||
[Recoil](https://recoiljs.org/docs/introduction/core-concepts) übernimmt die Zustandsverwaltung.
|
||||
|
||||
Siehe [Best Practices](/l/de/developers/contribute/capabilities/frontend-development/best-practices-front#state-management) für mehr Informationen zur Zustandsverwaltung.
|
||||
|
||||
## Tests
|
||||
|
||||
[Jest](https://jestjs.io/) dient als Werkzeug für Unittests, während [Storybook](https://storybook.js.org/) für Komponententests verwendet wird.
|
||||
|
||||
Jest ist hauptsächlich für das Testen von Dienstprogrammfunktionen und nicht für die Komponenten selbst gedacht.
|
||||
|
||||
Storybook dient zum Testen des Verhaltens isolierter Komponenten sowie zur Darstellung des Designsystems.
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
---
|
||||
title: Tastenkombinationen
|
||||
---
|
||||
|
||||
## Einführung
|
||||
|
||||
Wenn Sie eine Tastenkombination abhören müssen, verwenden Sie normalerweise den `onKeyDown` Event-Listener.
|
||||
|
||||
In `twenty-front` jedoch könnten Sie Konflikte zwischen gleichen Tastenkombinationen haben, die in verschiedenen Komponenten, die gleichzeitig eingebunden sind, verwendet werden.
|
||||
|
||||
Zum Beispiel, wenn Sie eine Seite haben, die auf die Enter-Taste hört, und ein Modal das auf die Enter-Taste hört, und ein Select-Element innerhalb dieses Modals auf die Enter-Taste hört, könnte ein Konflikt entstehen, wenn alle gleichzeitig eingebunden sind.
|
||||
|
||||
## Der `useScopedHotkeys` Hook
|
||||
|
||||
Um dieses Problem zu lösen, verfügen wir über einen benutzerdefinierten Hook, der es ermöglicht, auf Tastenkombinationen zu hören, ohne Konflikte.
|
||||
|
||||
Sie platzieren ihn in einer Komponente, und er wird auf die Tastenkombinationen nur hören, wenn die Komponente eingebunden ist UND wenn der spezifizierte **Tastenkombinationsbereich** aktiv ist.
|
||||
|
||||
## Wie hört man in der Praxis auf Tastenkombinationen?
|
||||
|
||||
Es gibt zwei Schritte, um das Abhören von Tastenkombinationen einzurichten:
|
||||
|
||||
1. Setzen Sie den [Tastenkombinationsbereich](#what-is-a-hotkey-scope-), der auf Tastenkombinationen hören wird
|
||||
2. Verwenden Sie den `useScopedHotkeys` Hook, um auf Tastenkombinationen zu hören
|
||||
|
||||
Das Einrichten von Tastenkombinationsbereichen ist auch bei einfachen Seiten erforderlich, da andere UI-Elemente wie das linke Menü oder das Befehlsmenü ebenfalls auf Tastenkombinationen hören könnten.
|
||||
|
||||
## Anwendungsfälle für Tastenkombinationen
|
||||
|
||||
Im Allgemeinen haben Sie zwei Anwendungsfälle, die Tastenkombinationen erfordern:
|
||||
|
||||
1. Auf einer Seite oder einer Komponente, die auf einer Seite eingebunden ist
|
||||
2. In einer Modal-Komponente, die wegen einer Benutzeraktion den Fokus erhält
|
||||
|
||||
Der zweite Anwendungsfall kann rekursiv auftreten: ein Dropdown in einem Modal zum Beispiel.
|
||||
|
||||
### Auf Tastenkombinationen in einer Seite hören
|
||||
|
||||
Beispiel:
|
||||
|
||||
```tsx
|
||||
const PageListeningEnter = () => {
|
||||
const {
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
goBackToPreviousHotkeyScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
// 1. Setze den Tastenkombinationsbereich in einem useEffect
|
||||
useEffect(() => {
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
ExampleHotkeyScopes.ExampleEnterPage,
|
||||
);
|
||||
|
||||
// Zurückkehren zum vorherigen Tastenkombinationsbereich, wenn die Komponente entfernt wird
|
||||
return () => {
|
||||
goBackToPreviousHotkeyScope();
|
||||
};
|
||||
}, [goBackToPreviousHotkeyScope, setHotkeyScopeAndMemorizePreviousScope]);
|
||||
|
||||
// 2. Verwenden Sie den useScopedHotkeys Hook
|
||||
useScopedHotkeys(
|
||||
Key.Enter,
|
||||
() => {
|
||||
// Eine Logik, die auf dieser Seite ausgeführt wird, wenn der Benutzer Enter drückt
|
||||
// ...
|
||||
},
|
||||
ExampleHotkeyScopes.ExampleEnterPage,
|
||||
);
|
||||
|
||||
return <div>Meine Seite, die auf Enter hört</div>;
|
||||
};
|
||||
```
|
||||
|
||||
### Auf Tastenkombinationen in einer Modal-Komponente hören
|
||||
|
||||
Für dieses Beispiel verwenden wir eine Modalkomponente, die auf die Escape-Taste hört, um ihrem übergeordneten Element mitzuteilen, dass sie geschlossen werden soll.
|
||||
|
||||
Hier ändert die Benutzerinteraktion den Bereich.
|
||||
|
||||
```tsx
|
||||
const ExamplePageWithModal = () => {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
const {
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
goBackToPreviousHotkeyScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
const handleOpenModalClick = () => {
|
||||
// 1. Setze den Tastenkombinationsbereich, wenn der Benutzer das Modal öffnet
|
||||
setShowModal(true);
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
ExampleHotkeyScopes.ExampleModal,
|
||||
);
|
||||
};
|
||||
|
||||
const handleModalClose = () => {
|
||||
// 1. Zurückkehren zum vorherigen Tastenkombinationsbereich, wenn das Modal geschlossen wird
|
||||
setShowModal(false);
|
||||
goBackToPreviousHotkeyScope();
|
||||
};
|
||||
|
||||
return <div>
|
||||
<h1>Meine Seite mit einem Modal</h1>
|
||||
<button onClick={handleOpenModalClick}>Modal öffnen</button>
|
||||
{showModal && <MyModalComponent onClose={handleModalClose} />}
|
||||
</div>;
|
||||
};
|
||||
```
|
||||
|
||||
Dann in der Modalkomponente:
|
||||
|
||||
```tsx
|
||||
const MyDropdownComponent = ({ onClose }: { onClose: () => void }) => {
|
||||
// 2. Verwenden Sie den useScopedHotkeys Hook, um auf Escape zu hören.
|
||||
// Beachten Sie, dass Escape eine gängige Tastenkombination ist, die von vielen anderen Komponenten verwendet werden könnte
|
||||
// Daher ist es wichtig, einen Tastenkombinationsbereich zu verwenden, um Konflikte zu vermeiden
|
||||
useScopedHotkeys(
|
||||
Key.Escape,
|
||||
() => {
|
||||
onClose()
|
||||
},
|
||||
ExampleHotkeyScopes.ExampleModal,
|
||||
);
|
||||
|
||||
return <div>Meine Modalkomponente</div>;
|
||||
};
|
||||
```
|
||||
|
||||
Es ist wichtig, dieses Muster zu verwenden, wenn Sie sich nicht sicher sind, ob das einfache Benutzen eines useEffect bei Ein-/Ausbindung ausreicht, um Konflikte zu vermeiden.
|
||||
|
||||
Diese Konflikte können schwer zu debuggen sein und treten möglicherweise häufiger auf, als man denkt, mit useEffects.
|
||||
|
||||
## Was ist ein Tastenkombinationsbereich?
|
||||
|
||||
Ein Tastenkombinationsbereich ist ein String, der einen Kontext darstellt, in dem die Tastenkombinationen aktiv sind. Er wird im Allgemeinen als Enum kodiert.
|
||||
|
||||
Wenn Sie den Tastenkombinationsbereich ändern, werden die Tastenkombinationen aktiviert, die auf diesen Bereich hören, und die Tastenkombinationen, die auf andere Bereiche hören, werden deaktiviert.
|
||||
|
||||
Sie können immer nur einen Bereich gleichzeitig setzen.
|
||||
|
||||
Als Beispiel sind die Tastenkombinationsbereiche für jede Seite im `PageHotkeyScope` Enum definiert:
|
||||
|
||||
```tsx
|
||||
export enum PageHotkeyScope {
|
||||
Settings = 'settings',
|
||||
CreateWorkspace = 'create-workspace',
|
||||
SignInUp = 'sign-in-up',
|
||||
CreateProfile = 'create-profile',
|
||||
PlanRequired = 'plan-required',
|
||||
ShowPage = 'show-page',
|
||||
PersonShowPage = 'person-show-page',
|
||||
CompanyShowPage = 'company-show-page',
|
||||
CompaniesPage = 'companies-page',
|
||||
PeoplePage = 'people-page',
|
||||
OpportunitiesPage = 'opportunities-page',
|
||||
ProfilePage = 'profile-page',
|
||||
WorkspaceMemberPage = 'workspace-member-page',
|
||||
TaskPage = 'task-page',
|
||||
}
|
||||
```
|
||||
|
||||
Intern wird der aktuell ausgewählte Bereich in einem Recoil-State gespeichert, der in der gesamten Anwendung geteilt wird:
|
||||
|
||||
```tsx
|
||||
export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||
key: 'currentHotkeyScopeState',
|
||||
defaultValue: INITIAL_HOTKEYS_SCOPE,
|
||||
});
|
||||
```
|
||||
|
||||
Aber dieser Recoil-State sollte niemals manuell bearbeitet werden! Wir werden im nächsten Abschnitt sehen, wie man es verwendet.
|
||||
|
||||
## Wie funktioniert es intern?
|
||||
|
||||
Wir haben eine dünne Schicht über [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) erstellt, die es leistungsfähiger macht und unnötige Neu-Renderings vermeidet.
|
||||
|
||||
Wir haben auch einen Recoil-State erstellt, um den Tastenkombinationsbereich zu verwalten und in der gesamten Anwendung verfügbar zu machen.
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Storybook
|
||||
description: Durchsuchen Sie die UI-Komponentenbibliothek von Twenty
|
||||
---
|
||||
|
||||
Sehen Sie sich unsere vollständige Komponentenbibliothek und Dokumentation in Storybook an.
|
||||
|
||||
[Storybook öffnen →](https://storybook.twenty.com)
|
||||
@@ -4,5 +4,5 @@ image: /images/user-guide/tips/light-bulb.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/tips/light-bulb.png" alt="Header" />
|
||||
</Frame>
|
||||
<img src="/images/user-guide/tips/light-bulb.png" alt="Kopfzeile" />
|
||||
</Frame>
|
||||
|
||||
@@ -4,5 +4,5 @@ image: /images/user-guide/tasks/tasks_header.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/tasks/tasks_header.png" alt="Header" />
|
||||
</Frame>
|
||||
<img src="/images/user-guide/tasks/tasks_header.png" alt="Kopfzeile" />
|
||||
</Frame>
|
||||
|
||||
@@ -4,49 +4,46 @@ image: /images/user-guide/table-views/table.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/table-views/table.png" alt="Header" />
|
||||
<img src="/images/user-guide/table-views/table.png" alt="Kopfzeile" />
|
||||
</Frame>
|
||||
|
||||
Rendert eine Navigationsleiste, die mehrere `NavigationBarItem`-Komponenten enthält.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title=""Verwendung"">
|
||||
```jsx
|
||||
import { IconHome, IconUser, IconSettings } from '@tabler/icons-react';
|
||||
import { NavigationBar } from "@/ui/navigation/navigation-bar/components/NavigationBar";
|
||||
|
||||
```jsx
|
||||
import { IconHome, IconUser, IconSettings } from '@tabler/icons-react';
|
||||
import { NavigationBar } from "@/ui/navigation/navigation-bar/components/NavigationBar";
|
||||
export const MyComponent = () => {
|
||||
|
||||
export const MyComponent = () => {
|
||||
const navigationItems = [
|
||||
{
|
||||
name: "Home",
|
||||
Icon: IconHome,
|
||||
onClick: () => console.log("Home geklickt"),
|
||||
},
|
||||
{
|
||||
name: "Profil",
|
||||
Icon: IconUser,
|
||||
onClick: () => console.log("Profil geklickt"),
|
||||
},
|
||||
{
|
||||
name: "Einstellungen",
|
||||
Icon: IconSettings,
|
||||
onClick: () => console.log("Einstellungen geklickt"),
|
||||
},
|
||||
];
|
||||
|
||||
const navigationItems = [
|
||||
{
|
||||
name: "Home",
|
||||
Icon: IconHome,
|
||||
onClick: () => console.log("Home clicked"),
|
||||
},
|
||||
{
|
||||
name: "Profile",
|
||||
Icon: IconUser,
|
||||
onClick: () => console.log("Profile clicked"),
|
||||
},
|
||||
{
|
||||
name: "Settings",
|
||||
Icon: IconSettings,
|
||||
onClick: () => console.log("Settings clicked"),
|
||||
},
|
||||
];
|
||||
return <NavigationBar activeItemName="Home" items={navigationItems}/>;
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
return <NavigationBar activeItemName="Home" items={navigationItems}/>;
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| Props | Typ | Beschreibung |
|
||||
| -------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| activeItemName | Zeichenkette | Der Name des aktuell aktiven Navigationselements |
|
||||
| items | array | Ein Array von Objekten, die jeweils ein Navigationselement repräsentieren. Jedes Objekt enthält den `name` des Elements, die zugehörige `Icon`-Komponente und eine Funktion `onClick`, die ausgeführt wird, wenn das Element angeklickt wird |
|
||||
|
||||
</Tab>
|
||||
<Tab title=""Eigenschaften"">
|
||||
| "Eigenschaften" | Typ | Beschreibung |
|
||||
| --------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| activeItemName | Zeichenkette | Der Name des aktuell aktiven Navigationselements |
|
||||
| items | array | Ein Array von Objekten, die jeweils ein Navigationselement repräsentieren. Jedes Objekt enthält den `name` des Elements, die zugehörige `Icon`-Komponente und eine Funktion `onClick`, die ausgeführt wird, wenn das Element angeklickt wird |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -4,35 +4,31 @@ image: /images/user-guide/api/api.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/api/api.png" alt="Header" />
|
||||
<img src="/images/user-guide/api/api.png" alt="Kopfzeile" />
|
||||
</Frame>
|
||||
|
||||
Zeigt den Fortschritt durch eine Folge von nummerierten Schritten, indem der aktive Schritt hervorgehoben wird. Es rendert einen Container mit Schritten, die jeweils durch die `Step`-Komponente dargestellt werden.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title=""Verwendung"">
|
||||
```jsx
|
||||
import { StepBar } from "@/ui/navigation/step-bar/components/StepBar";
|
||||
|
||||
```jsx
|
||||
import { StepBar } from "@/ui/navigation/step-bar/components/StepBar";
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<StepBar activeStep={2}>
|
||||
<StepBar.Step>Schritt 1</StepBar.Step>
|
||||
<StepBar.Step>Schritt 2</StepBar.Step>
|
||||
<StepBar.Step>Schritt 3</StepBar.Step>
|
||||
</StepBar>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<StepBar activeStep={2}>
|
||||
<StepBar.Step>Step 1</StepBar.Step>
|
||||
<StepBar.Step>Step 2</StepBar.Step>
|
||||
<StepBar.Step>Step 3</StepBar.Step>
|
||||
</StepBar>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Props">
|
||||
|
||||
| Props | Typ | Beschreibung |
|
||||
| -------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| aktiverSchritt | nummer | Der Index des derzeit aktiven Schritts. Dies bestimmt, welcher Schritt visuell hervorgehoben werden soll. |
|
||||
|
||||
</Tab>
|
||||
<Tab title=""Eigenschaften"">
|
||||
| "Eigenschaften" | Typ | Beschreibung |
|
||||
| --------------- | ------ | --------------------------------------------------------------------------------------------------------- |
|
||||
| aktiverSchritt | nummer | Der Index des derzeit aktiven Schritts. Dies bestimmt, welcher Schritt visuell hervorgehoben werden soll. |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: Mejores prácticas
|
||||
---
|
||||
|
||||
Este documento describe las mejores prácticas que debe seguir al trabajar en el backend.
|
||||
|
||||
## Siga un enfoque modular
|
||||
|
||||
El backend sigue un enfoque modular, que es un principio fundamental al trabajar con NestJS. Asegúrese de descomponer su código en módulos reutilizables para mantener una base de código limpia y organizada.
|
||||
Cada módulo debe encapsular una característica o funcionalidad particular y tener un alcance bien definido. Este enfoque modular permite una clara separación de responsabilidades y elimina complejidades innecesarias.
|
||||
|
||||
## Exponer servicios para usar en módulos
|
||||
|
||||
Siempre cree servicios que tengan una responsabilidad clara y única, lo que mejora la legibilidad y mantenibilidad del código. Nombre los servicios de manera descriptiva y consistente.
|
||||
|
||||
También debe exponer servicios que desee usar en otros módulos. Exponer servicios a otros módulos es posible a través del poderoso sistema de inyección de dependencias de NestJS, y promueve un acoplamiento débil entre los componentes.
|
||||
|
||||
## Evitar usar el tipo `any`
|
||||
|
||||
Cuando declara una variable como `any`, el verificador de tipos de TypeScript no realiza ninguna comprobación de tipos, lo que hace posible asignar cualquier tipo de valores a la variable. TypeScript utiliza la inferencia de tipos para determinar el tipo de la variable a partir del valor. Al declararlo como `any`, TypeScript ya no puede inferir el tipo. Esto dificulta la captura de errores relacionados con el tipo durante el desarrollo, lo que lleva a errores en tiempo de ejecución y hace que el código sea menos mantenible, menos fiable y más difícil de entender para otros.
|
||||
|
||||
Por eso todo debe tener un tipo. Entonces, si crea un nuevo objeto con un nombre y apellido, debe crear una interfaz o tipo que contenga un nombre y apellido y defina la forma del objeto que está manipulando.
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
---
|
||||
title: Objetos personalizados
|
||||
---
|
||||
|
||||
Los objetos son estructuras que te permiten almacenar datos (registros, atributos y valores) específicos de una organización. Twenty proporciona tanto objetos estándar como personalizados.
|
||||
|
||||
Los objetos estándar son objetos incorporados con un conjunto de atributos disponibles para todos los usuarios. Ejemplos de objetos estándar en Twenty incluyen Empresa y Persona. Los objetos estándar tienen campos estándar que también están disponibles para todos los usuarios de Twenty, como Company.displayName.
|
||||
|
||||
Los objetos personalizados son objetos que puedes crear para almacenar información que es única para tu organización. No están incorporados; los miembros de tu espacio de trabajo pueden crear y personalizar objetos personalizados para albergar información para la cual los objetos estándar no son aptos.
|
||||
|
||||
## Esquema de alto nivel
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/server/custom-object-schema.png" alt="Esquema de alto nivel" />
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
## Cómo funciona
|
||||
|
||||
Los objetos personalizados provienen de tablas de metadatos que determinan la forma, el nombre y el tipo de los objetos. Toda esta información está presente en la base de datos del esquema de metadatos, que consta de tablas:
|
||||
|
||||
* **DataSource**: Detalles de dónde se encuentra la información.
|
||||
* **Object**: Describe el objeto y lo vincula a un DataSource.
|
||||
* **Field**: Describe los campos de un objeto y lo conecta al objeto.
|
||||
|
||||
Para añadir un objeto personalizado, el workspaceMember consultará la API de /metadata. Esto actualiza los metadatos de acuerdo y calcula un esquema GraphQL basado en los metadatos, almacenándolo en un caché de GQL para su uso posterior.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/server/add-custom-objects.jpeg" alt="Consultando la API de /metadata para añadir objetos personalizados" />
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
Para obtener datos, el proceso implica hacer consultas a través del endpoint /graphql y pasarlos a través del Query Resolver.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/server/custom-object-schema.png" alt="Consulta el endpoint /graphql para obtener datos" />
|
||||
</div>
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
---
|
||||
title: Cola de Mensajes
|
||||
---
|
||||
|
||||
Las colas facilitan la realización de operaciones asíncronas. Se pueden utilizar para realizar tareas en segundo plano, como enviar un correo de bienvenida al registrarse.
|
||||
Cada caso de uso tendrá su propia clase de cola extendida de `MessageQueueServiceBase`.
|
||||
|
||||
Actualmente, solo soportamos `bull-mq`[bull-mq](https://bullmq.io/) como el controlador de cola.
|
||||
|
||||
## Pasos para crear y usar una nueva cola
|
||||
|
||||
1. Agregue un nombre de cola para su nueva cola en la enumeración `MESSAGE_QUEUES`.
|
||||
2. Proporcione la implementación de fábrica de la cola con el nombre de la cola como el token de dependencia.
|
||||
3. Inyecte la cola que creó en el módulo/servicio requerido con el nombre de la cola como el token de dependencia.
|
||||
4. Agregue una clase de trabajador con inyección basada en token, al igual que el productor.
|
||||
|
||||
### Ejemplo de uso
|
||||
|
||||
```ts
|
||||
class Resolver {
|
||||
constructor(@Inject(MESSAGE_QUEUES.custom) private queue: MessageQueueService) {}
|
||||
|
||||
async onSomeAction() {
|
||||
//lógica de negocio
|
||||
await this.queue.add(someData);
|
||||
}
|
||||
}
|
||||
|
||||
//trabajador asíncrono
|
||||
class CustomWorker {
|
||||
constructor(@Inject(MESSAGE_QUEUES.custom) private queue: MessageQueueService) {
|
||||
this.initWorker();
|
||||
}
|
||||
|
||||
async initWorker() {
|
||||
await this.queue.work(async ({ id, data }) => {
|
||||
//lógica del trabajador
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
---
|
||||
title: Atajos de teclado
|
||||
---
|
||||
|
||||
## Introducción
|
||||
|
||||
Cuando necesitas escuchar una tecla de acceso rápido, normalmente usarías el evento `onKeyDown`.
|
||||
|
||||
En `twenty-front`, sin embargo, podrías tener conflictos entre los mismos atajos de teclado utilizados en diferentes componentes, montados al mismo tiempo.
|
||||
|
||||
Por ejemplo, si tienes una página que escucha la tecla Enter y un modal que escucha la tecla Enter, con un componente Select dentro de ese modal que también escucha la tecla Enter, podrías tener un conflicto cuando todos están montados al mismo tiempo.
|
||||
|
||||
## El gancho `useScopedHotkeys`
|
||||
|
||||
Para manejar este problema, tenemos un gancho personalizado que hace posible escuchar atajos de teclado sin ningún conflicto.
|
||||
|
||||
Lo colocas en un componente, y escuchará los atajos de teclado solo cuando el componente está montado Y cuando el **ámbito del atajo de teclado** especificado está activo.
|
||||
|
||||
## ¿Cómo escuchar atajos de teclado en la práctica?
|
||||
|
||||
Hay dos pasos involucrados en configurar la escucha de atajos de teclado:
|
||||
|
||||
1. Establece el [ámbito del atajo de teclado](#qué-es-un-ambito-de-atajo-de-teclado-) que escuchará los atajos de teclado
|
||||
2. Usa el gancho `useScopedHotkeys` para escuchar atajos de teclado
|
||||
|
||||
Configurar los ámbitos de atajos de teclado es necesario incluso en páginas simples, porque otros elementos de la interfaz de usuario como el menú lateral o el menú de comandos también podrían escuchar atajos de teclado.
|
||||
|
||||
## Casos de uso para atajos de teclado
|
||||
|
||||
En general, tendrás dos casos de uso que requieren atajos de teclado:
|
||||
|
||||
1. En una página o un componente montado en una página
|
||||
2. En un componente tipo modal que toma el enfoque debido a la acción del usuario
|
||||
|
||||
El segundo caso de uso puede ocurrir recursivamente: un desplegable en un modal, por ejemplo.
|
||||
|
||||
### Escuchando atajos de teclado en una página
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```tsx
|
||||
const EscuchaEnterEnPagina = () => {
|
||||
const {
|
||||
estableceAmbitoDeAtajoYMemorizaAmbitoAnterior,
|
||||
regresaAlAmbitoDeAtajoAnterior,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
// 1. Establece el ámbito de atajo en un useEffect
|
||||
useEffect(() => {
|
||||
estableceAmbitoDeAtajoYMemorizaAmbitoAnterior(
|
||||
EjemploAmbitosDeAtajo.EjemploEnterPagina,
|
||||
);
|
||||
|
||||
// Revertir al ámbito de atajo anterior cuando se desmonte el componente
|
||||
return () => {
|
||||
regresaAlAmbitoDeAtajoAnterior();
|
||||
};
|
||||
}, [regresaAlAmbitoDeAtajoAnterior, estableceAmbitoDeAtajoYMemorizaAmbitoAnterior]);
|
||||
|
||||
// 2. Usa el gancho useScopedHotkeys
|
||||
useScopedHotkeys(
|
||||
Key.Enter,
|
||||
() => {
|
||||
// Alguna lógica ejecutada en esta página cuando el usuario presiona Enter
|
||||
// ...
|
||||
},
|
||||
EjemploAmbitosDeAtajo.EjemploEnterPagina,
|
||||
);
|
||||
|
||||
return <div>Mi página que escucha Enter</div>;
|
||||
};
|
||||
```
|
||||
|
||||
### Escuchando atajos de teclado en un componente tipo modal
|
||||
|
||||
Para este ejemplo utilizaremos un componente modal que escucha la tecla Escape para indicar a su padre que lo cierre.
|
||||
|
||||
Aquí la interacción del usuario está cambiando el ámbito.
|
||||
|
||||
```tsx
|
||||
const PaginaEjemploConModal = () => {
|
||||
const [mostrarModal, establecerMostrarModal] = useState(false);
|
||||
|
||||
const {
|
||||
estableceAmbitoDeAtajoYMemorizaAmbitoAnterior,
|
||||
regresaAlAmbitoDeAtajoAnterior,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
const manejarClicAbrirModal = () => {
|
||||
// 1. Establece el ámbito de atajo cuando el usuario abre el modal
|
||||
establecerMostrarModal(true);
|
||||
estableceAmbitoDeAtajoYMemorizaAmbitoAnterior(
|
||||
EjemploAmbitosDeAtajo.EjemploModal,
|
||||
);
|
||||
};
|
||||
|
||||
const manejarCierreModal = () => {
|
||||
// 1. Revertir al ámbito de atajo anterior cuando se cierra el modal
|
||||
establecerMostrarModal(false);
|
||||
regresaAlAmbitoDeAtajoAnterior();
|
||||
};
|
||||
|
||||
return <div>
|
||||
<h1>Mi página con un modal</h1>
|
||||
<button onClick={manejarClicAbrirModal}>Abrir modal</button>
|
||||
{mostrarModal && <MiComponenteModal onClose={manejarCierreModal} />}
|
||||
</div>;
|
||||
};
|
||||
```
|
||||
|
||||
Luego, en el componente modal:
|
||||
|
||||
```tsx
|
||||
const MiComponenteDesplegable = ({ onClose }: { onClose: () => void }) => {
|
||||
// 2. Usa el gancho useScopedHotkeys para escuchar la tecla Escape.
|
||||
// Tenga en cuenta que Escape es una tecla de acceso rápido común que podría usarse en muchos otros componentes
|
||||
// Por lo tanto, es importante usar un ámbito de atajo para evitar conflictos
|
||||
useScopedHotkeys(
|
||||
Key.Escape,
|
||||
() => {
|
||||
onClose()
|
||||
},
|
||||
EjemploAmbitosDeAtajo.EjemploModal,
|
||||
);
|
||||
|
||||
return <div>Mi componente modal</div>;
|
||||
};
|
||||
```
|
||||
|
||||
Es importante usar este patrón cuando no estás seguro de que solo usar un useEffect con mount/unmount será suficiente para evitar conflictos.
|
||||
|
||||
Esos conflictos pueden ser difíciles de depurar, y puede suceder más a menudo de lo que crees con useEffects.
|
||||
|
||||
## ¿Qué es un ámbito de atajo de teclado?
|
||||
|
||||
Un ámbito de atajo de teclado es una cadena que representa un contexto en el que los atajos de teclado están activos. Por lo general, se codifica como un enum.
|
||||
|
||||
Cuando cambias el ámbito del atajo de teclado, se habilitarán los atajos que están escuchando este ámbito y se deshabilitarán los atajos que escuchan otros ámbitos.
|
||||
|
||||
Solo puedes establecer un ámbito a la vez.
|
||||
|
||||
Como ejemplo, los ámbitos de atajos de teclado para cada página se definen en el enum `PageHotkeyScope`:
|
||||
|
||||
```tsx
|
||||
export enum PageHotkeyScope {
|
||||
Configuración = 'configuración',
|
||||
CrearEspacioTrabajo = 'crear-espacio-trabajo',
|
||||
IniciarSesion = 'iniciar-sesión',
|
||||
CrearPerfil = 'crear-perfil',
|
||||
PlanRequerido = 'plan-requerido',
|
||||
MostrarPagina = 'mostrar-pagina',
|
||||
MostrarPersonaPagina = 'mostrar-persona-pagina',
|
||||
MostrarEmpresaPagina = 'mostrar-empresa-pagina',
|
||||
PaginaEmpresas = 'pagina-empresas',
|
||||
PaginaPersonas = 'pagina-personas',
|
||||
PaginaOportunidades = 'pagina-oportunidades',
|
||||
PaginaPerfil = 'pagina-perfil',
|
||||
PaginaMiembroEspacioTrabajo = 'pagina-miembro-espacio-trabajo',
|
||||
PaginaTareas = 'pagina-tareas',
|
||||
}
|
||||
```
|
||||
|
||||
Internamente, el ámbito seleccionado se almacena en un estado de Recoil que se comparte en toda la aplicación:
|
||||
|
||||
```tsx
|
||||
export const estadoActualDeAmbitoDeAtajo = createState<HotkeyScope>({
|
||||
key: 'estadoActualDeAmbitoDeAtajo',
|
||||
defaultValue: AMBITO_INICIAL_ATAJOS,
|
||||
});
|
||||
```
|
||||
|
||||
¡Pero este estado de Recoil nunca debe manejarse manualmente! Veremos cómo usarlo en la siguiente sección.
|
||||
|
||||
## ¿Cómo funciona internamente?
|
||||
|
||||
Hicimos un contenedor delgado sobre [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) que lo hace más eficiente y evita renders innecesarios.
|
||||
|
||||
También creamos un estado de Recoil para manejar el estado del ámbito de atajos de teclado y hacerlo disponible en toda la aplicación.
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Storybook
|
||||
description: Navegar por la biblioteca de componentes de interfaz de Twenty
|
||||
---
|
||||
|
||||
Ver nuestra biblioteca completa de componentes y documentación en Storybook.
|
||||
|
||||
[Abrir Storybook →](https://storybook.twenty.com)
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
---
|
||||
title: Trabajar con Figma
|
||||
info: Aprende cómo puedes colaborar con Twenty en Figma
|
||||
---
|
||||
|
||||
Figma es una herramienta de diseño de interfaces colaborativa que ayuda a superar la barrera de comunicación entre diseñadores y desarrolladores.
|
||||
Esta guía explica cómo puedes colaborar con Figma.
|
||||
|
||||
## Acceso
|
||||
|
||||
1. **Accede al enlace compartido:** Puedes acceder al archivo de Figma del proyecto [aquí](https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty).
|
||||
2. **Inicia sesión:** Si aún no has iniciado sesión, Figma te pedirá que lo hagas.
|
||||
Las características clave solo están disponibles para usuarios registrados, como el modo desarrollador y la capacidad de seleccionar un marco dedicado.
|
||||
|
||||
<Warning>
|
||||
No podrás colaborar efectivamente sin una cuenta.
|
||||
</Warning>
|
||||
|
||||
## Estructura de Figma
|
||||
|
||||
En la barra lateral izquierda, puedes acceder a las diferentes páginas del Figma de Twenty. Así es como están organizadas:
|
||||
|
||||
* **Página de componentes:** Esta es la primera página. El diseñador la utiliza para crear y organizar los elementos de diseño reutilizables que se usan en todo el archivo de diseño. Por ejemplo, botones, íconos, símbolos u otros componentes reutilizables. Sirve para mantener la consistencia a lo largo del diseño.
|
||||
* **Página principal:** La segunda página es la página principal, que muestra la interfaz de usuario completa del proyecto. Puedes presionar ***Play*** para usar el prototipo completo de la aplicación.
|
||||
* **Páginas de características:** Las otras páginas están típicamente dedicadas a características en progreso. Contienen el diseño de características específicas o módulos de la aplicación o sitio web. Normalmente, aún están en desarrollo.
|
||||
|
||||
## Consejos útiles
|
||||
|
||||
Con acceso de solo lectura, no puedes editar el diseño, pero puedes acceder a todas las características que serán útiles para convertir los diseños en código.
|
||||
|
||||
### Usa el modo Dev
|
||||
|
||||
El Modo Dev de Figma mejora la productividad de los desarrolladores al proporcionar una navegación fácil por el diseño, una gestión eficaz de assets, herramientas de comunicación eficientes, integraciones de caja de herramientas, fragmentos de código rápidos e información clave de capas, superando la brecha entre diseño y desarrollo. Puedes aprender más sobre el Modo Dev [aquí](https://www.figma.com/dev-mode/).
|
||||
|
||||
Cambia al modo "Desarrollador" en la parte derecha de la barra de herramientas para ver las especificaciones de diseño, copiar CSS y acceder a los assets.
|
||||
|
||||
### Usar el prototipo
|
||||
|
||||
Haz clic en cualquier elemento del lienzo y presiona el botón "Play" en el extremo superior derecho de la interfaz para acceder a la vista del prototipo. El modo de prototipo te permite interactuar con el diseño como si fuera el producto final. Demuestra el flujo entre pantallas y cómo los elementos de la interfaz, como botones, enlaces o menús, se comportan al interactuar con ellos.
|
||||
|
||||
1. **Entendiendo las transiciones y animaciones:** En el modo Prototipo, puedes ver cualquier transición o animación añadida por un diseñador entre pantallas o elementos de UI, proporcionando instrucciones visuales claras a los desarrolladores sobre la conducta y el estilo intencionados.
|
||||
2. **Aclaración de implementación:** Un prototipo también puede ayudar a reducir las ambigüedades. Los desarrolladores pueden interactuar con él para obtener una mejor comprensión de la funcionalidad o apariencia de elementos particulares.
|
||||
|
||||
Para más detalles y orientación sobre el aprendizaje de la plataforma Figma, puedes visitar la [Documentación Oficial de Figma](https://help.figma.com/hc/en-us).
|
||||
|
||||
### Medir distancias
|
||||
|
||||
Selecciona un elemento, mantén presionada la tecla `Option` (Mac) o `Alt` (Windows), luego pasa el cursor sobre otro elemento para ver la distancia entre ellos.
|
||||
|
||||
### Extensión Figma para VSCode (Recomendado)
|
||||
|
||||
[Figma para VS Code](https://marketplace.visualstudio.com/items?itemName=figma.figma-vscode-extension)
|
||||
te permite navegar e inspeccionar archivos de diseño, colaborar con diseñadores, rastrear cambios y acelerar la implementación, todo sin salir de tu editor de texto.
|
||||
Forma parte de nuestras extensiones recomendadas.
|
||||
|
||||
## Colaboración
|
||||
|
||||
1. **Uso de Comentarios:** Puedes utilizar la función de comentarios haciendo clic en el ícono de burbuja en la parte izquierda de la barra de herramientas.
|
||||
2. **Chat de Cursor:** Una característica agradable de Figma es el Chat de Cursor. Simplemente presiona `;` en Mac y `/` en Windows para enviar un mensaje si ves a alguien más usando Figma al mismo tiempo que tú.
|
||||
@@ -4,5 +4,5 @@ image: /images/user-guide/tips/light-bulb.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/tips/light-bulb.png" alt="Header" />
|
||||
</Frame>
|
||||
<img src="/images/user-guide/tips/light-bulb.png" alt="Encabezado" />
|
||||
</Frame>
|
||||
|
||||
@@ -4,44 +4,41 @@ image: /images/user-guide/tasks/tasks_header.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/tasks/tasks_header.png" alt="Header" />
|
||||
<img src="/images/user-guide/tasks/tasks_header.png" alt="Encabezado" />
|
||||
</Frame>
|
||||
|
||||
Usado cuando un usuario necesita seleccionar múltiples valores entre varias opciones.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="Uso">
|
||||
```jsx
|
||||
import { Checkbox } from "twenty-ui/display";
|
||||
|
||||
```jsx
|
||||
import { Checkbox } from "twenty-ui/display";
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={true}
|
||||
indeterminate={false}
|
||||
onChange={() => console.log("función onChange ejecutada")}
|
||||
onCheckedChange={() => console.log("función onCheckedChange ejecutada")}
|
||||
variant="primary"
|
||||
size="small"
|
||||
shape="squared"
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={true}
|
||||
indeterminate={false}
|
||||
onChange={() => console.log("onChange function fired")}
|
||||
onCheckedChange={() => console.log("onCheckedChange function fired")}
|
||||
variant="primary"
|
||||
size="small"
|
||||
shape="squared"
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| "Props" | Tipo | Descripción |
|
||||
| --------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| marcado | booleano | Indica si la caja de selección está marcada |
|
||||
| indeterminado | booleano | Indica si la caja de selección está en un estado indeterminado (ni marcada ni desmarcada) |
|
||||
| "onChange" | función | La función de devolución de llamada que desea activar cuando cambia el estado de la caja de selección |
|
||||
| onCheckedChange | "función" | La función de devolución de llamada que desea activar cuando cambia el estado `marcado` |
|
||||
| variante | "cadena" | La variante de estilo visual de la caja. Las opciones incluyen: `primario`, `secundario` y `terciario` |
|
||||
| tamaño | "cadena" | El tamaño de la caja de selección. Tiene dos opciones: `pequeño` y `grande` |
|
||||
| forma | cadena | La forma de la caja de selección. Tiene dos opciones: `cuadrado` y `redondeado` |
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
| Props | Tipo | Descripción |
|
||||
| --------------- | -------- | ------------------------------------------------------------------------------------------------------ |
|
||||
| marcado | booleano | Indica si la caja de selección está marcada |
|
||||
| indeterminado | booleano | Indica si la caja de selección está en un estado indeterminado (ni marcada ni desmarcada) |
|
||||
| onChange | función | La función de devolución de llamada que desea activar cuando cambia el estado de la caja de selección |
|
||||
| onCheckedChange | función | La función de devolución de llamada que desea activar cuando cambia el estado `marcado` |
|
||||
| variante | cadena | La variante de estilo visual de la caja. Las opciones incluyen: `primario`, `secundario` y `terciario` |
|
||||
| tamaño | cadena | El tamaño de la caja de selección. Tiene dos opciones: `pequeño` y `grande` |
|
||||
| forma | cadena | La forma de la caja de selección. Tiene dos opciones: `cuadrado` y `redondeado` |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -4,5 +4,5 @@ image: /images/user-guide/tasks/tasks_header.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/tasks/tasks_header.png" alt="Header" />
|
||||
</Frame>
|
||||
<img src="/images/user-guide/tasks/tasks_header.png" alt="Encabezado" />
|
||||
</Frame>
|
||||
|
||||
@@ -4,7 +4,7 @@ image: /images/user-guide/what-is-twenty/20.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/what-is-twenty/20.png" alt="Header" />
|
||||
<img src="/images/user-guide/what-is-twenty/20.png" alt="Encabezado" />
|
||||
</Frame>
|
||||
|
||||
## Enlace de Contacto
|
||||
@@ -12,44 +12,40 @@ image: /images/user-guide/what-is-twenty/20.png
|
||||
Un componente de enlace estilizado para mostrar información de contacto.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title=""Uso"">
|
||||
```jsx
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
|
||||
```jsx
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { ContactLink } from 'twenty-ui/navigation';
|
||||
|
||||
import { ContactLink } from 'twenty-ui/navigation';
|
||||
export const MyComponent = () => {
|
||||
const handleLinkClick = (event) => {
|
||||
console.log('¡Enlace de contacto pulsado!', event);
|
||||
};
|
||||
|
||||
export const MyComponent = () => {
|
||||
const handleLinkClick = (event) => {
|
||||
console.log('Contact link clicked!', event);
|
||||
};
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<ContactLink
|
||||
className
|
||||
href="mailto:example@example.com"
|
||||
onClick={handleLinkClick}
|
||||
>
|
||||
example@example.com
|
||||
</ContactLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| "Props" | Tipo | Descripción |
|
||||
| ----------- | ----------------- | ------------------------------------------------------------------------------- |
|
||||
| "className" | "cadena" | "Nombre opcional para estilización adicional" |
|
||||
| href | "cadena" | La URL o ruta objetivo del enlace |
|
||||
| alHacerClic | "función" | Función de devolución de llamada que se activa cuando se hace clic en el enlace |
|
||||
| hijos | `React.ReactNode` | El contenido que se mostrará dentro del enlace |
|
||||
|
||||
</Tab>
|
||||
return (
|
||||
<Router>
|
||||
<ContactLink
|
||||
className
|
||||
href="mailto:example@example.com"
|
||||
onClick={handleLinkClick}
|
||||
>
|
||||
example@example.com
|
||||
</ContactLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title=""Props"">
|
||||
| "Props" | Tipo | Descripción |
|
||||
| ----------- | ----------------- | ------------------------------------------------------------------------------- |
|
||||
| "className" | string | "Nombre opcional para estilización adicional" |
|
||||
| href | string | La URL o ruta objetivo del enlace |
|
||||
| onClick | "función" | Función de devolución de llamada que se activa cuando se hace clic en el enlace |
|
||||
| hijos | `React.ReactNode` | El contenido que se mostrará dentro del enlace |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Enlace Sin Estilo
|
||||
@@ -57,39 +53,35 @@ export const MyComponent = () => {
|
||||
Un componente de enlace estilizado para mostrar enlaces.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title=""Uso"">
|
||||
```jsx
|
||||
import { RawLink } from "/navigation";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
|
||||
```jsx
|
||||
import { RawLink } from "/navigation";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
export const MyComponent = () => {
|
||||
const handleLinkClick = (event) => {
|
||||
console.log("¡Enlace de contacto pulsado!", event);
|
||||
};
|
||||
|
||||
export const MyComponent = () => {
|
||||
const handleLinkClick = (event) => {
|
||||
console.log("Contact link clicked!", event);
|
||||
};
|
||||
return (
|
||||
<Router>
|
||||
<RawLink className href="/contact" onClick={handleLinkClick}>
|
||||
Contáctanos
|
||||
</RawLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<RawLink className href="/contact" onClick={handleLinkClick}>
|
||||
Contact Us
|
||||
</RawLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| "Props" | Tipo | Descripción |
|
||||
| ----------- | ----------------- | ------------------------------------------------------------------------------- |
|
||||
| "className" | cadena | "Nombre opcional para estilización adicional" |
|
||||
| href | "cadena" | La URL o ruta objetivo del enlace |
|
||||
| alHacerClic | función | Función de devolución de llamada que se activa cuando se hace clic en el enlace |
|
||||
| hijos | `React.ReactNode` | El contenido que se mostrará dentro del enlace |
|
||||
|
||||
</Tab>
|
||||
<Tab title=""Props"">
|
||||
| "Props" | Tipo | Descripción |
|
||||
| ------------- | ----------------- | ------------------------------------------------------------------------------- |
|
||||
| nombreDeClase | string | Nombre opcional para estilo adicional |
|
||||
| href | string | La URL o ruta objetivo del enlace |
|
||||
| onClick | función | Función de devolución de llamada que se activa cuando se hace clic en el enlace |
|
||||
| hijos | `React.ReactNode` | El contenido que se mostrará dentro del enlace |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Enlace Redondeado
|
||||
@@ -97,38 +89,34 @@ export const MyComponent = () => {
|
||||
Un enlace de estilo redondeado con un componente Chip para enlaces.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title=""Uso"">
|
||||
```jsx
|
||||
import { RoundedLink } from "/navigation";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
|
||||
```jsx
|
||||
import { RoundedLink } from "/navigation";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
export const MyComponent = () => {
|
||||
const handleLinkClick = (event) => {
|
||||
console.log("¡Enlace de contacto pulsado!", event);
|
||||
};
|
||||
|
||||
export const MyComponent = () => {
|
||||
const handleLinkClick = (event) => {
|
||||
console.log("Contact link clicked!", event);
|
||||
};
|
||||
return (
|
||||
<Router>
|
||||
<RoundedLink href="/contact" onClick={handleLinkClick}>
|
||||
Contáctanos
|
||||
</RoundedLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<RoundedLink href="/contact" onClick={handleLinkClick}>
|
||||
Contact Us
|
||||
</RoundedLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Props">
|
||||
|
||||
| "Props" | Tipo | Descripción |
|
||||
| ----------- | ----------------- | ------------------------------------------------------------------------------- |
|
||||
| href | "cadena" | La URL o ruta objetivo del enlace |
|
||||
| hijos | `React.ReactNode` | El contenido que se mostrará dentro del enlace |
|
||||
| alHacerClic | "función" | Función de devolución de llamada que se activa cuando se hace clic en el enlace |
|
||||
|
||||
</Tab>
|
||||
<Tab title=""Props"">
|
||||
| "Props" | Tipo | Descripción |
|
||||
| ------- | ----------------- | ------------------------------------------------------------------------------- |
|
||||
| href | cadena | La URL o ruta objetivo del enlace |
|
||||
| hijos | `React.ReactNode` | El contenido que se mostrará dentro del enlace |
|
||||
| onClick | "función" | Función de devolución de llamada que se activa cuando se hace clic en el enlace |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Enlace Social
|
||||
@@ -136,33 +124,30 @@ export const MyComponent = () => {
|
||||
Enlaces sociales estilizados, con soporte para varios tipos de enlaces sociales, como URLs, LinkedIn y X (o Twitter).
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="Uso">
|
||||
```jsx
|
||||
import { SocialLink } from "twenty-ui/navigation";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
|
||||
```jsx
|
||||
import { SocialLink } from "twenty-ui/navigation";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<Router>
|
||||
<SocialLink
|
||||
type="twitter"
|
||||
href="https://twitter.com/twentycrm"
|
||||
></SocialLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<Router>
|
||||
<SocialLink
|
||||
type="twitter"
|
||||
href="https://twitter.com/twentycrm"
|
||||
></SocialLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| "Props" | Tipo | Descripción |
|
||||
| ----------- | ----------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| href | "cadena" | La URL o ruta objetivo del enlace |
|
||||
| hijos | `React.ReactNode` | El contenido que se mostrará dentro del enlace |
|
||||
| tipo | "cadena" | El tipo de enlaces sociales. Las opciones incluyen: `url`, `LinkedIn`, y `Twitter` |
|
||||
| alHacerClic | "función" | Función de devolución de llamada que se activa cuando se hace clic en el enlace |
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
| Props | Tipo | Descripción |
|
||||
| ------- | ----------------- | ---------------------------------------------------------------------------------- |
|
||||
| href | string | La URL o ruta objetivo del enlace |
|
||||
| hijos | `React.ReactNode` | El contenido que se mostrará dentro del enlace |
|
||||
| tipo | string | El tipo de enlaces sociales. Las opciones incluyen: `url`, `LinkedIn`, y `Twitter` |
|
||||
| onClick | función | Función de devolución de llamada que se activa cuando se hace clic en el enlace |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -4,46 +4,41 @@ image: /images/user-guide/emails/emails_header.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/emails/emails_header.png" alt="Header" />
|
||||
<img src="/images/user-guide/emails/emails_header.png" alt="Encabezado" />
|
||||
</Frame>
|
||||
|
||||
Indica el progreso o la cuenta regresiva y se mueve de derecha a izquierda.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Uso">
|
||||
```jsx
|
||||
import { ProgressBar } from "twenty-ui/feedback";
|
||||
|
||||
<Tab title="Usage">
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<ProgressBar
|
||||
duration={6000}
|
||||
delay={0}
|
||||
easing="easeInOut"
|
||||
barHeight={10}
|
||||
barColor="#4bb543"
|
||||
autoStart={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
```jsx
|
||||
import { ProgressBar } from "twenty-ui/feedback";
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<ProgressBar
|
||||
duration={6000}
|
||||
delay={0}
|
||||
easing="easeInOut"
|
||||
barHeight={10}
|
||||
barColor="#4bb543"
|
||||
autoStart={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Props">
|
||||
|
||||
| "Props" | Tipo | Descripción | Predeterminado |
|
||||
| ---------------- | -------- | --------------------------------------------------------------------------------------------------------- | -------------- |
|
||||
| duración | número | La duración total de la animación de la barra de progreso en milisegundos | 3 |
|
||||
| retraso | número | El retraso en el inicio de la animación de la barra de progreso en milisegundos | 0 |
|
||||
| aceleración | "cadena" | Función de aceleración para la animación de la barra de progreso | easeInOut |
|
||||
| alturaDeBarra | número | La altura de la barra en píxeles | 24 |
|
||||
| colorDeBarra | "cadena" | El color de la barra | gray80 |
|
||||
| inicioAutomático | booleano | Si es `true`, la animación de la barra de progreso comienza automáticamente cuando el componente se monta | `verdadero` |
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
| Props | Tipo | Descripción | Predeterminado |
|
||||
| ---------------- | -------- | --------------------------------------------------------------------------------------------------------- | -------------- |
|
||||
| duración | número | La duración total de la animación de la barra de progreso en milisegundos | 3 |
|
||||
| retraso | número | El retraso en el inicio de la animación de la barra de progreso en milisegundos | 0 |
|
||||
| aceleración | cadena | Función de aceleración para la animación de la barra de progreso | easeInOut |
|
||||
| alturaDeBarra | número | La altura de la barra en píxeles | 24 |
|
||||
| colorDeBarra | cadena | El color de la barra | gray80 |
|
||||
| inicioAutomático | booleano | Si es `true`, la animación de la barra de progreso comienza automáticamente cuando el componente se monta | `verdadero` |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Barra de Progreso Circular
|
||||
@@ -51,27 +46,21 @@ export const MyComponent = () => {
|
||||
Indica el progreso de una tarea, a menudo se usa en pantallas de carga o áreas donde se desea comunicar procesos en curso al usuario.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Uso">
|
||||
```jsx
|
||||
import { CircularProgressBar } from "@/ui/feedback/progress-bar/components/CircularProgressBar";
|
||||
|
||||
<Tab title="Usage">
|
||||
|
||||
```jsx
|
||||
import { CircularProgressBar } from "@/ui/feedback/progress-bar/components/CircularProgressBar";
|
||||
|
||||
export const MyComponent = () => {
|
||||
return <CircularProgressBar size={80} barWidth={6} barColor="green" />;
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Props">
|
||||
|
||||
| "Props" | Tipo | Descripción | Predeterminado |
|
||||
| ------------ | ------ | -------------------------------------------- | -------------- |
|
||||
| tamaño | número | El tamaño de la barra de progreso circular | 50 |
|
||||
| anchoDeBarra | número | El ancho de la línea de la barra de progreso | 5 |
|
||||
| colorDeBarra | cadena | El color de la barra de progreso | currentColor |
|
||||
|
||||
</Tab>
|
||||
export const MyComponent = () => {
|
||||
return <CircularProgressBar size={80} barWidth={6} barColor="green" />;
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Props">
|
||||
| Props | Tipo | Descripción | Predeterminado |
|
||||
| ------------ | ------ | -------------------------------------------- | -------------- |
|
||||
| tamaño | número | El tamaño de la barra de progreso circular | 50 |
|
||||
| anchoDeBarra | número | El ancho de la línea de la barra de progreso | 5 |
|
||||
| colorDeBarra | cadena | El color de la barra de progreso | currentColor |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: Planes de precios},{
|
||||
description: Obtén información sobre los planes de precios de Twenty y cómo cambiar entre ellos.
|
||||
---
|
||||
|
||||
## Resumen
|
||||
|
||||
Twenty ofrece precios flexibles para adaptarse a equipos de todos los tamaños, tanto si prefieres alojamiento en la nube como autoalojamiento.
|
||||
|
||||
## Planes en la nube
|
||||
|
||||
### Pro (Nube)
|
||||
|
||||
Para equipos listos para escalar:
|
||||
|
||||
* Todas las funciones principales de CRM
|
||||
* Sincronización de correo electrónico y calendario
|
||||
* Flujos de trabajo y automatizaciones
|
||||
* Soporte estándar
|
||||
|
||||
<Note>
|
||||
Las funciones premium (SSO y permisos a nivel de fila) no están incluidas en el plan Pro.
|
||||
</Note>
|
||||
|
||||
### Organización (Nube)
|
||||
|
||||
Para equipos más grandes con necesidades avanzadas:
|
||||
|
||||
* Todo lo de Pro
|
||||
* **Funciones premium**: integración de SSO y permisos a nivel de fila
|
||||
* Soporte prioritario
|
||||
|
||||
## Planes de autoalojamiento
|
||||
|
||||
### Gratis (Autoalojado)
|
||||
|
||||
Aloja Twenty en tu propia infraestructura sin costo:
|
||||
|
||||
* Incluye todas las funciones de Pro
|
||||
* Soporte de la comunidad a través de Discord
|
||||
* Control total sobre tus datos
|
||||
|
||||
### Organización (Autoalojado)
|
||||
|
||||
Para equipos que necesitan funciones premium mientras se autoalojan:
|
||||
|
||||
* Todas las funciones de Pro
|
||||
* **Funciones premium**: integración de SSO y permisos a nivel de fila
|
||||
* Soporte del equipo de Twenty
|
||||
* No es obligatorio publicar el código personalizado como código abierto antes de distribuirlo
|
||||
|
||||
## Funciones premium
|
||||
|
||||
Las funciones premium solo están disponibles en los planes de Organización (Nube o Autoalojado):
|
||||
|
||||
* **Integración de SSO**: inicio de sesión único con tu proveedor de identidad
|
||||
* **Permisos a nivel de fila**: control de acceso granular a nivel de registro
|
||||
|
||||
## Cambiar de plan
|
||||
|
||||
### Actualizar a Organización
|
||||
|
||||
1. Ir a **Ajustes → Facturación**
|
||||
2. Haz clic en **Cambiar a Organización**
|
||||
3. Confirma tu actualización
|
||||
|
||||
### Reducir a Pro
|
||||
|
||||
Contacta al equipo de soporte para reducir tu plan.
|
||||
|
||||
### Cambiar a facturación anual
|
||||
|
||||
1. Ir a **Ajustes → Facturación**
|
||||
2. Haz clic en **Cambiar a anual**
|
||||
3. Ahorra con la facturación anual
|
||||
|
||||
### Cambiar a facturación mensual
|
||||
|
||||
Contacta al equipo de soporte para volver a la facturación mensual.
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
title: Migración de datos
|
||||
description: Importe y exporte sus datos de CRM mediante archivos CSV o la API.
|
||||
image: /images/user-guide/import-export-data/cloud.png
|
||||
---
|
||||
|
||||
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/import-export-data/cloud.png" alt="Migración de datos" />
|
||||
</Frame>
|
||||
|
||||
## Métodos de importación
|
||||
|
||||
Twenty admite dos métodos principales para importar datos:
|
||||
|
||||
| Método | Ideal para | Límite de volumen |
|
||||
| ---------------------------- | ------------------------------------------------ | ---------------------------- |
|
||||
| **Importación CSV** | Migraciones estándar, actualizaciones periódicas | 10.000 registros por archivo |
|
||||
| **Importación mediante API** | Migraciones a gran escala, automatización | Ilimitado |
|
||||
|
||||
<Note>Para conjuntos de datos muy grandes (cientos de miles de registros), use la API. Nuestros [socios de implementación](/l/es/user-guide/getting-started/capabilities/implementation-services) pueden ayudar a ejecutar estos scripts si es necesario.</Note>
|
||||
|
||||
## Conceptos básicos de la importación CSV
|
||||
|
||||
Puede importar datos de cualquier objeto mediante archivos CSV, XLSX o XLS. Cada archivo debe contener **solo un tipo de objeto** (por ejemplo, solo registros de Personas).
|
||||
|
||||
<Note>**Los campos deben existir antes de la importación.** Cargar un CSV crea registros, pero no crea campos. Si necesita campos personalizados, créelos primero en **Configuración → Modelo de datos**.</Note>
|
||||
|
||||
### Pasos
|
||||
|
||||
1. Vaya al objeto en el que desea importar datos
|
||||
2. Haga clic en el icono **⋮** en la esquina superior derecha (este es el Menú de comandos) y haga clic en **Importar registros**
|
||||
3. Descargue el archivo de plantilla para asegurarse de que sus datos estén en el formato esperado
|
||||
4. Cargue su archivo CSV con el formato correcto
|
||||
5. Asigne sus columnas a los campos de Twenty
|
||||
6. Revise los errores (resaltados en amarillo) y corríjalos, editando directamente en la interfaz de usuario
|
||||
7. Confirme la importación
|
||||
|
||||
### Importación de relaciones entre objetos
|
||||
|
||||
Puede importar relaciones entre objetos utilizando la función de importación CSV. Debe hacer referencia al objeto relacionado utilizando un campo único de ese objeto: el `id`, el `email` para Personas y Miembros del espacio de trabajo, el `domain` para empresas, cualquier otro campo establecido como único en el modelo de datos para cualquier otro objeto.
|
||||
|
||||
<Note>**Los registros eliminados cuentan para la unicidad.** Los registros eliminados lógicamente (visibles en Menú de comandos → Ver registros eliminados) se incluyen en las comprobaciones de unicidad. Si importa un registro con el mismo valor único que un registro eliminado, el registro eliminado se restaurará.</Note>
|
||||
|
||||
<Warning>
|
||||
**¡Importa el orden de importación!**
|
||||
|
||||
Al importar objetos relacionados, cargue los archivos en este orden:
|
||||
|
||||
1. **Empresas** primero (el lado "uno" de las relaciones)
|
||||
2. **Personas** segundo (vinculadas a empresas mediante companyId)
|
||||
3. **Oportunidades** tercero (vinculadas a empresas/personas)
|
||||
4. **Objetos personalizados** con relaciones al final
|
||||
|
||||
¿Por qué? El lado "uno" de una relación de uno a muchos debe existir antes de que pueda hacer referencia a él. Por ejemplo, el registro de Empresa debe existir antes de importar una Persona con el ID de esa empresa.
|
||||
</Warning>
|
||||
|
||||
Consulte [este artículo](/l/es/user-guide/data-migration/how-tos/import-relations-between-objects-via-csv) para ver una guía paso a paso sobre cómo proceder.
|
||||
|
||||
## Exportar Datos
|
||||
|
||||
Exporte los datos de su espacio de trabajo para copias de seguridad, informes o migración.
|
||||
|
||||
### Pasos
|
||||
|
||||
1. Vaya al objeto que desea exportar
|
||||
2. Configure la vista con las columnas que necesita
|
||||
3. Haga clic en **⋮** → **Exportar vista**
|
||||
4. Guarde el archivo CSV
|
||||
|
||||
<Note>**Solo se exportan las columnas visibles.** El archivo CSV contendrá únicamente las columnas que se muestran en su vista actual. Agregue u oculte columnas antes de exportar para controlar qué datos se incluyen.</Note>
|
||||
|
||||
<Note>**Límites de exportación**: hasta 20.000 registros por exportación.</Note>
|
||||
|
||||
## Permisos
|
||||
|
||||
La importación y exportación de datos requieren permisos específicos:
|
||||
|
||||
* **Importación**: requiere el permiso "Importar CSV"
|
||||
* **Exportación**: requiere el permiso "Exportar CSV"
|
||||
|
||||
Póngase en contacto con el administrador de su espacio de trabajo si no tiene estos permisos.
|
||||
|
||||
## Próximos Pasos
|
||||
|
||||
* [Prepare sus archivos CSV](/l/es/user-guide/data-migration/how-tos/prepare-your-csv-files)
|
||||
* [Importe relaciones entre objetos](/l/es/user-guide/data-migration/how-tos/import-relations-between-objects-via-csv)
|
||||
* [Importe mediante API para conjuntos de datos grandes](/l/es/user-guide/data-migration/how-tos/import-data-via-api)
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: Permisos y acceso
|
||||
description: Administra roles, permisos y el control de acceso en tu espacio de trabajo.
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/permissions/permissions.png" alt="Permisos y acceso" />
|
||||
</Frame>
|
||||
|
||||
El sistema de permisos de Twenty te permite controlar quién puede acceder y modificar los datos en tu espacio de trabajo. Crea roles, asigna permisos y configura SSO para un acceso seguro.
|
||||
|
||||
## Qué incluye esta sección
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Permisos" icon="lock" href="/l/es/user-guide/permissions-access/capabilities/permissions">
|
||||
Crea roles y configura permisos de objetos, campos y configuración.
|
||||
</Card>
|
||||
|
||||
<Card title="Configuración de SSO" icon="clave" href="/l/es/user-guide/permissions-access/capabilities/sso-configuration">
|
||||
Configura el inicio de sesión único (SSO) con tu proveedor de identidad.
|
||||
</Card>
|
||||
|
||||
<Card title="Preguntas frecuentes" icon="circle-question" href="/l/es/user-guide/permissions-access/how-tos/permissions-faq">
|
||||
Preguntas comunes sobre roles, permisos y SSO.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Características clave
|
||||
|
||||
* **Acceso basado en roles**: Crea roles personalizados con permisos específicos
|
||||
* **Permisos de objetos**: Controla quién puede ver, editar o eliminar registros
|
||||
* **Permisos de campos**: Restringe el acceso a campos sensibles
|
||||
* **Permisos de configuración**: Controla el acceso a la configuración del espacio de trabajo
|
||||
* **Integración con SSO**: Configura el inicio de sesión único para seguridad a nivel empresarial (plan Organization)
|
||||
|
||||
## Enlaces rápidos
|
||||
|
||||
* [Crear un rol](/l/es/user-guide/permissions-access/capabilities/permissions#create-a-role)
|
||||
* [Configurar SSO](/l/es/user-guide/permissions-access/capabilities/sso-configuration)
|
||||
* [Administrar miembros del equipo](/l/es/user-guide/settings/capabilities/member-management)
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
---
|
||||
title: Restringir el acceso a tu vista
|
||||
description: Controla quién puede ver tus vistas personalizadas.
|
||||
---
|
||||
|
||||
Cada vista (excepto las vistas predeterminadas "Todas las [Object Name]") tiene su propia configuración de visibilidad.
|
||||
|
||||
## Pasos
|
||||
|
||||
1. Abre la vista que deseas restringir
|
||||
2. Haz clic en **Opciones** en la esquina superior derecha
|
||||
3. Haz clic en **Visibilidad**
|
||||
4. Selecciona **No listada**
|
||||
|
||||
Tu vista ahora es visible solo para ti.
|
||||
|
||||
## Opciones de visibilidad
|
||||
|
||||
| Configuración | Quién puede ver |
|
||||
| ---------------------- | ----------------------------------------- |
|
||||
| **Espacio de trabajo** | Todos los miembros del espacio de trabajo |
|
||||
| **No listada** | Solo tú |
|
||||
|
||||
## Notas
|
||||
|
||||
* Las vistas predeterminadas "Todas las [Object Name]" no se pueden establecer como no listadas
|
||||
* Las vistas no listadas no aparecen en los menús desplegables de vistas de otros usuarios
|
||||
* Puedes cambiar la visibilidad de nuevo a Espacio de trabajo en cualquier momento
|
||||
|
||||
## Relacionado
|
||||
|
||||
* [Configuración de vistas](/l/es/user-guide/views-pipelines/capabilities/view-settings) — todas las opciones de configuración de vistas
|
||||
@@ -0,0 +1,231 @@
|
||||
---
|
||||
title: Realiza un seguimiento de cuánto tiempo permanecen las oportunidades en cada etapa.
|
||||
description: Supervisa la velocidad de las oportunidades registrando cuándo ingresan a cada etapa.
|
||||
---
|
||||
|
||||
<Tip>
|
||||
Este es un ejemplo de cómo crear [Campos de fórmula](/l/es/user-guide/workflows/how-tos/crm-automations/formula-fields) usando flujos de trabajo — específicamente cálculos de fechas.
|
||||
</Tip>
|
||||
|
||||
Registrar cuándo las oportunidades ingresan a cada etapa te ayuda a identificar cuellos de botella y medir la velocidad de las oportunidades.
|
||||
|
||||
Esta guía te muestra cómo configurar campos personalizados y un flujo de trabajo para registrar automáticamente cuándo una oportunidad pasa a cada etapa y calcular cuántos días pasó en la etapa anterior.
|
||||
|
||||
## Paso 1: Crear campos personalizados
|
||||
|
||||
Necesitas dos tipos de campos para cada etapa:
|
||||
|
||||
* **Campos de fecha y hora**: Registran cuándo la oportunidad ingresó a cada etapa
|
||||
* **Campos numéricos**: Almacenan cuántos días pasó la oportunidad en cada etapa
|
||||
|
||||
### Crea los campos "Última entrada"
|
||||
|
||||
1. Ve a **Ajustes → Modelo de datos → Oportunidades**
|
||||
2. Para cada etapa, haz clic en **+ Agregar campo** y configura:
|
||||
* **Nombre**: Última entrada en [Nombre de la etapa] (p. ej., "Última entrada en Nuevo", "Última entrada en Calificado")
|
||||
* **Tipo**: Fecha y hora
|
||||
* **Descripción**: Marca de tiempo cuando la oportunidad ingresó a esta etapa
|
||||
3. Haga clic en **Guardar**
|
||||
|
||||
Crea estos campos:
|
||||
|
||||
* Última entrada en Nuevo
|
||||
* Última entrada en Calificado
|
||||
* Última entrada en Reunión
|
||||
* Última entrada en Propuesta
|
||||
* Última entrada en Negociación
|
||||
* Última entrada en Cerrado ganado
|
||||
* Última entrada en Cerrado perdido
|
||||
|
||||
### Crea los campos "Días en la etapa"
|
||||
|
||||
1. Para cada etapa, haz clic en **+ Agregar campo** y configura:
|
||||
* **Nombre**: Días en [Nombre de la etapa] (p. ej., "Días en Nuevo", "Días en Calificado")
|
||||
* **Tipo**: Número
|
||||
* **Descripción**: Número de días que pasó en esta etapa
|
||||
2. Haga clic en **Guardar**
|
||||
|
||||
Crea estos campos:
|
||||
|
||||
* Días en Nuevo
|
||||
* Días en Calificado
|
||||
* Días en Reunión
|
||||
* Días en Propuesta
|
||||
* Días en Negociación
|
||||
|
||||
<Note>
|
||||
No necesitas campos "Días en" para Cerrado ganado y Cerrado perdido, ya que son etapas finales.
|
||||
</Note>
|
||||
|
||||
### Opcional: Hacer que los campos sean de solo lectura
|
||||
|
||||
Si no quieres que los usuarios editen manualmente estos campos calculados:
|
||||
|
||||
1. Ve a **Ajustes → Roles**
|
||||
2. Selecciona el rol que deseas configurar
|
||||
3. Busca el objeto Oportunidades
|
||||
4. Configura los campos "Última entrada" y "Días en" como de solo lectura
|
||||
|
||||
## Paso 2: Crear el flujo de trabajo
|
||||
|
||||
Este único flujo de trabajo gestiona ambas tareas:
|
||||
|
||||
* Registra la marca de tiempo al entrar en una nueva etapa
|
||||
* Calcula los días que se pasaron en la etapa anterior
|
||||
|
||||
### Crea el flujo de trabajo
|
||||
|
||||
1. Ve a **Flujos de trabajo**
|
||||
2. Haz clic en **+ Nuevo flujo de trabajo**
|
||||
3. Ponle el nombre "Seguimiento del tiempo por etapa"
|
||||
|
||||
### Configura el disparador
|
||||
|
||||
1. Agrega un disparador **Registro actualizado**
|
||||
2. Selecciona **Oportunidades** como objeto
|
||||
3. Filtrar por: el campo **Etapa** se actualiza
|
||||
|
||||
### Agrega ramas para cada etapa
|
||||
|
||||
<Note>
|
||||
Para crear una nueva rama, haz clic derecho en el lienzo del flujo de trabajo y haz clic en **Nueva acción**. Luego, vincula esta acción al nodo anterior arrastrando la flecha desde el nodo anterior hasta esta nueva acción.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
**Rama 1: Etapa = Nuevo (primera etapa)**
|
||||
|
||||
Como esta es la primera etapa, solo registramos la marca de tiempo de entrada; no hay una etapa anterior que calcular.
|
||||
|
||||
1. Agrega un nodo **Filtro**: Etapa = Nuevo
|
||||
2. Agrega una acción de **Código**:
|
||||
|
||||
```javascript
|
||||
export const main = async (): Promise<object> => {
|
||||
return { now: new Date().toISOString() };
|
||||
};
|
||||
```
|
||||
|
||||
3. Agrega una acción **Actualizar registro**:
|
||||
* Registro: la Oportunidad que activó el evento
|
||||
* Campo: Última entrada en Nuevo
|
||||
* Valor: `now` del nodo de Código
|
||||
|
||||
---
|
||||
|
||||
**Rama 2: Etapa = Calificado**
|
||||
|
||||
Al pasar a Calificado, registra la hora de entrada Y calcula los días que se pasaron en Nuevo.
|
||||
|
||||
1. Agrega un nodo **Filtro**: Etapa = Calificado
|
||||
2. Agrega una acción de **Código**:
|
||||
|
||||
```javascript
|
||||
export const main = async (params: {
|
||||
lastEnteredPreviousStage: Date;
|
||||
}): Promise<object> => {
|
||||
const { lastEnteredPreviousStage } = params;
|
||||
|
||||
const now = new Date();
|
||||
const entryDate = new Date(lastEnteredPreviousStage);
|
||||
const diffTime = Math.abs(now.getTime() - entryDate.getTime());
|
||||
const daysInPreviousStage = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||
|
||||
return {
|
||||
now: now.toISOString(),
|
||||
daysInPreviousStage: daysInPreviousStage
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
3. Configura la entrada del nodo de Código: mapea `lastEnteredPreviousStage` al campo **Última entrada en Nuevo**
|
||||
4. Agrega una acción **Actualizar registro**:
|
||||
* Registro: la Oportunidad que activó el evento
|
||||
* Campos a actualizar:
|
||||
* Última entrada en Calificado = `now`
|
||||
* Días en Nuevo = `daysInPreviousStage`
|
||||
|
||||
---
|
||||
|
||||
**Rama 3: Etapa = Reunión**
|
||||
|
||||
Al pasar a Reunión, registra la hora de entrada Y calcula los días que se pasaron en Calificado.
|
||||
|
||||
1. Agrega un nodo **Filtro**: Etapa = Reunión
|
||||
2. Agrega una acción de **Código**:
|
||||
|
||||
```javascript
|
||||
export const main = async (params: {
|
||||
lastEnteredPreviousStage: Date;
|
||||
}): Promise<object> => {
|
||||
const { lastEnteredPreviousStage } = params;
|
||||
|
||||
const now = new Date();
|
||||
const entryDate = new Date(lastEnteredPreviousStage);
|
||||
const diffTime = Math.abs(now.getTime() - entryDate.getTime());
|
||||
const daysInPreviousStage = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||
|
||||
return {
|
||||
now: now.toISOString(),
|
||||
daysInPreviousStage: daysInPreviousStage
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
3. Configura la entrada del nodo de Código: mapea `lastEnteredPreviousStage` al campo **Última entrada en Calificado**
|
||||
4. Agrega una acción **Actualizar registro**:
|
||||
* Registro: la Oportunidad que activó el evento
|
||||
* Campos a actualizar:
|
||||
* Última entrada en Reunión = `now`
|
||||
* Días en Calificado = `daysInPreviousStage`
|
||||
|
||||
---
|
||||
|
||||
**Continúa con las etapas restantes:**
|
||||
|
||||
| Etapa | Registros | Calcula |
|
||||
| --------------- | --------------------------------- | ------------------- |
|
||||
| Propuesta | Última entrada en Propuesta | Días en Reunión |
|
||||
| Negociación | Última entrada en Negociación | Días en Propuesta |
|
||||
| Cerrado ganado | Última entrada en Cerrado ganado | Días en Negociación |
|
||||
| Cerrado perdido | Última entrada en Cerrado perdido | Días en Negociación |
|
||||
|
||||
Las ramas no necesitan volver a unirse; cada una se ejecuta de forma independiente cuando se cumple su condición de etapa.
|
||||
|
||||
## Paso 3: Analizar el tiempo en la etapa
|
||||
|
||||
Con las marcas de tiempo y los recuentos de días registrados, ahora puedes analizar la velocidad de las oportunidades.
|
||||
|
||||
### Crea una vista "Oportunidades lentas"
|
||||
|
||||
1. Crea una vista de tabla de Oportunidades
|
||||
2. Agrega columnas: Nombre, Etapa, Días en [etapa anterior], Importe
|
||||
3. Ordena por el campo "Días en" (descendente)
|
||||
4. Filtra por Etapa para centrarte en una etapa a la vez
|
||||
|
||||
Las oportunidades en la parte superior pasaron más tiempo en la etapa anterior.
|
||||
|
||||
### Usa agregaciones
|
||||
|
||||
En tu vista Kanban del pipeline:
|
||||
|
||||
1. Haz clic en el número junto al nombre de una Etapa
|
||||
2. Selecciona **Promedio**
|
||||
3. Elige un campo "Días en"
|
||||
|
||||
Esto muestra el tiempo promedio que las oportunidades pasan en cada etapa.
|
||||
|
||||
## Resumen
|
||||
|
||||
| Componente | Propósito |
|
||||
| --------------------------- | ------------------------------------------------------------------------- |
|
||||
| **Campos "Última entrada"** | Almacenan cuándo la oportunidad ingresó a cada etapa |
|
||||
| **Campos "Días en"** | Almacenan cuántos días se pasaron en cada etapa |
|
||||
| **Flujo de trabajo** | Registra la marca de tiempo Y calcula los días en una sola pasada |
|
||||
| **Vistas y agregaciones** | Analiza la velocidad de las oportunidades e identifica cuellos de botella |
|
||||
|
||||
## Relacionado
|
||||
|
||||
* [Flujos de trabajo](/l/es/user-guide/workflows/overview) — conceptos básicos de automatización
|
||||
* [Cómo crear campos personalizados](/l/es/user-guide/data-model/how-tos/create-custom-fields) — configuración de campos
|
||||
* [Vistas Kanban](/l/es/user-guide/views-pipelines/capabilities/kanban-views) — agregaciones
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: Meilleures pratiques
|
||||
---
|
||||
|
||||
Ce document décrit les meilleures pratiques à suivre lors de travaux sur le backend.
|
||||
|
||||
## Suivez une approche modulaire
|
||||
|
||||
Le backend suit une approche modulaire, qui est un principe fondamental lors de l'utilisation de NestJS. Assurez-vous de décomposer votre code en modules réutilisables pour maintenir une base de code propre et organisée.
|
||||
Chaque module doit encapsuler une fonctionnalité particulière et avoir un périmètre bien défini. Cette approche modulaire permet une séparation claire des préoccupations et supprime les complexités inutiles.
|
||||
|
||||
## Exposez des services à utiliser dans les modules
|
||||
|
||||
Créez toujours des services avec une responsabilité claire et unique, ce qui améliore la lisibilité et la maintenabilité du code. Nommez les services de manière descriptive et cohérente.
|
||||
|
||||
Vous devez également exposer les services que vous souhaitez utiliser dans d'autres modules. Exposer des services à d'autres modules est possible grâce au puissant système d'injection de dépendance de NestJS, et favorise un couplage lâche entre les composants.
|
||||
|
||||
## Évitez d'utiliser le type `any`
|
||||
|
||||
Lorsque vous déclarez une variable comme `any`, le vérificateur de types de TypeScript ne procède à aucune vérification de type, ce qui permet d'affecter n'importe quel type de valeur à la variable. TypeScript utilise l'inférence de type pour déterminer le type d'une variable en fonction de sa valeur. En le déclarant comme `any`, TypeScript ne peut plus inférer le type. Cela rend difficile la détection des erreurs liées aux types pendant le développement, conduisant à des erreurs d'exécution et rendant le code moins maintenable, moins fiable et plus difficile à comprendre pour d'autres.
|
||||
|
||||
C'est pourquoi tout devrait avoir un type. Donc si vous créez un nouvel objet avec un prénom et un nom, vous devriez créer une interface ou un type contenant un prénom et un nom qui définit la forme de l'objet que vous manipulez.
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
---
|
||||
title: Architecture des Dossiers
|
||||
info: Un aperçu détaillé de notre architecture de dossiers
|
||||
---
|
||||
|
||||
Dans ce guide, vous explorerez les détails de la structure du répertoire de projet et comment elle contribue à l'organisation et à la maintenabilité de Twenty.
|
||||
|
||||
En suivant cette convention d'architecture de dossiers, il est plus facile de trouver les fichiers liés à des fonctionnalités spécifiques et de s'assurer que l'application est évolutive et maintenable.
|
||||
|
||||
```
|
||||
front
|
||||
└───modules
|
||||
│ └───module1
|
||||
│ │ └───sous-module1
|
||||
│ └───module2
|
||||
│ └───ui
|
||||
│ │ └───affichage
|
||||
│ │ └───entrées
|
||||
│ │ │ └───boutons
|
||||
│ │ └───...
|
||||
└───pages
|
||||
└───...
|
||||
```
|
||||
|
||||
## Pages
|
||||
|
||||
Comprend les composants de haut niveau définis par les routes de l'application. Ils importent des composants de plus bas niveau depuis le dossier `modules` (plus de détails ci-dessous).
|
||||
|
||||
## Modules
|
||||
|
||||
Chaque module représente une fonctionnalité ou un groupe de fonctionnalités, comprenant ses composants spécifiques, ses états, et sa logique opérationnelle.
|
||||
Ils doivent tous suivre la structure ci-dessous. Vous pouvez imbriquer des modules dans des modules (appelés sous-modules) et les mêmes règles s'appliqueront.
|
||||
|
||||
```
|
||||
module1
|
||||
└───composants
|
||||
│ └───composant1
|
||||
│ └───composant2
|
||||
└───constantes
|
||||
└───contextes
|
||||
└───graphql
|
||||
│ └───fragments
|
||||
│ └───requêtes
|
||||
│ └───mutations
|
||||
└───hooks
|
||||
│ └───interne
|
||||
└───états
|
||||
│ └───sélecteurs
|
||||
└───types
|
||||
└───utilitaires
|
||||
```
|
||||
|
||||
### Contextes
|
||||
|
||||
Un contexte est un moyen de transmettre des données à travers l'arborescence de composants sans avoir à transmettre les propriétés manuellement à chaque niveau.
|
||||
|
||||
Voir [React Context](https://react.dev/reference/react#context-hooks) pour plus de détails.
|
||||
|
||||
### GraphQL
|
||||
|
||||
Comprend des fragments, des requêtes et des mutations.
|
||||
|
||||
Voir [GraphQL](https://graphql.org/learn/) pour plus de détails.
|
||||
|
||||
* Fragments
|
||||
|
||||
Un fragment est une partie réutilisable d'une requête, que vous pouvez utiliser dans différents endroits. En utilisant des fragments, il est plus facile d'éviter de dupliquer du code.
|
||||
|
||||
Voir [GraphQL Fragments](https://graphql.org/learn/queries/#fragments) pour plus de détails.
|
||||
|
||||
* Requêtes
|
||||
|
||||
Voir [GraphQL Queries](https://graphql.org/learn/queries/) pour plus de détails.
|
||||
|
||||
* Mutations
|
||||
|
||||
Voir [GraphQL Mutations](https://graphql.org/learn/queries/#mutations) pour plus de détails.
|
||||
|
||||
### Hooks
|
||||
|
||||
Voir [Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) pour plus de détails.
|
||||
|
||||
### États
|
||||
|
||||
Contient la logique de gestion des états. [RecoilJS](https://recoiljs.org) gère cela.
|
||||
|
||||
* Sélecteurs : Voir [RecoilJS Selectors](https://recoiljs.org/docs/basic-tutorial/selectors) pour plus de détails.
|
||||
|
||||
La gestion de l'état intégrée de React gère toujours l'état au sein d'un composant.
|
||||
|
||||
### Utilitaires
|
||||
|
||||
Devrait juste contenir des fonctions pures réutilisables. Autrement, créez des hooks personnalisés dans le dossier `hooks`.
|
||||
|
||||
## UI
|
||||
|
||||
Contient tous les composants d'interface utilisateur réutilisables utilisés dans l'application.
|
||||
|
||||
Ce dossier peut contenir des sous-dossiers, comme `data`, `display`, `feedback`, et `input` pour des types de composants spécifiques. Chaque composant doit être autonome et réutilisable, de sorte que vous puissiez l'utiliser dans différentes parties de l'application.
|
||||
|
||||
En séparant les composants UI des autres composants dans le dossier `modules`, il est plus facile de maintenir un design cohérent et d'effectuer des changements de l'interface utilisateur sans affecter d'autres parties (logique métier) de la base de code.
|
||||
|
||||
## Interface et dépendances
|
||||
|
||||
Vous pouvez importer le code d'autres modules depuis n'importe quel module, sauf le dossier `ui`. Cela permettra de garder son code facile à tester.
|
||||
|
||||
### Interne
|
||||
|
||||
Chaque partie (hooks, états, ...) d'un module peut avoir un dossier `internal`, qui contient des parties utilisées uniquement au sein du module.
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
---
|
||||
title: Commandes Frontend
|
||||
---
|
||||
|
||||
## Commandes utiles
|
||||
|
||||
### Lancement de l'application
|
||||
|
||||
```bash
|
||||
npx nx start twenty-front
|
||||
```
|
||||
|
||||
### Régénérer le schéma GraphQL basé sur le schéma API GraphQL
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:graphql:generate --configuration=metadata
|
||||
```
|
||||
|
||||
OU
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:graphql:generate
|
||||
```
|
||||
|
||||
### Analyse
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:lint # passez --fix pour corriger les erreurs de lint
|
||||
```
|
||||
|
||||
## Traductions
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:lingui:extract\nnpx nx run twenty-front:lingui:compile
|
||||
```
|
||||
|
||||
### Test
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:test # exécuter les tests jest\nnpx nx run twenty-front:storybook:serve:dev # exécuter storybook\nnpx nx run twenty-front:storybook:test # exécuter les tests # (besoin de yarn storybook:serve:dev pour fonctionner)\nnpx nx run twenty-front:storybook:coverage # (besoin de yarn storybook:serve:dev pour fonctionner)
|
||||
```
|
||||
|
||||
## Écosystème Tech
|
||||
|
||||
Le projet a une stack simple et propre, avec un code boilerplate minimal.
|
||||
|
||||
**Application**
|
||||
|
||||
* [React](https://react.dev/)
|
||||
* [Apollo](https://www.apollographql.com/docs/)
|
||||
* [GraphQL Codegen](https://the-guild.dev/graphql/codegen)
|
||||
* [Recoil](https://recoiljs.org/docs/introduction/core-concepts)
|
||||
* [TypeScript](https://www.typescriptlang.org/)
|
||||
|
||||
**Tests**
|
||||
|
||||
* [Jest](https://jestjs.io/)
|
||||
* [Storybook](https://storybook.js.org/)
|
||||
|
||||
**Outils**
|
||||
|
||||
* [Yarn](https://yarnpkg.com/)
|
||||
* [Craco](https://craco.js.org/docs/)
|
||||
* [ESLint](https://eslint.org/)
|
||||
|
||||
## Architecture
|
||||
|
||||
### Routage
|
||||
|
||||
[React Router](https://reactrouter.com/) gère le routage.
|
||||
|
||||
Pour éviter les [re-renders](/l/fr/developers/contribute/capabilities/frontend-development/best-practices-front#managing-re-renders) inutiles, toute la logique de routage est dans un `useEffect` dans `PageChangeEffect`.
|
||||
|
||||
### Gestion de l'État
|
||||
|
||||
[Recoil](https://recoiljs.org/docs/introduction/core-concepts) gère la gestion de l'état.
|
||||
|
||||
Voir [les meilleures pratiques](/l/fr/developers/contribute/capabilities/frontend-development/best-practices-front#state-management) pour plus d'informations sur la gestion de l'état.
|
||||
|
||||
## Tests
|
||||
|
||||
[Jest](https://jestjs.io/) sert de guide pour les tests unitaires tandis que [Storybook](https://storybook.js.org/) est utilisé pour les tests de composants.
|
||||
|
||||
Jest est principalement utilisé pour tester les fonctions utilitaires, et non les composants eux-mêmes.
|
||||
|
||||
Storybook est utilisé pour tester le comportement des composants isolés, ainsi que pour afficher le système de design.
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Storybook
|
||||
description: Parcourir la bibliothèque de composants UI de Twenty
|
||||
---
|
||||
|
||||
Consultez notre bibliothèque de composants complète et la documentation dans Storybook.
|
||||
|
||||
[Ouvrir Storybook →](https://storybook.twenty.com)
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: Autres méthodes
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Ce document est maintenu par la communauté. Il pourrait contenir des problèmes.
|
||||
</Warning>
|
||||
|
||||
## Kubernetes via Terraform et Manifests
|
||||
|
||||
La documentation communautaire pour le déploiement de Kubernetes est disponible [ici](https://github.com/twentyhq/twenty/tree/main/packages/twenty-docker/k8s)
|
||||
|
||||
### Coolify
|
||||
|
||||
Déployer Twenty sur les serveurs avec Coolify. (l'image officielle sur Coolify sera bientôt disponible)
|
||||
|
||||
[Documentation Coolify](https://coolify.io/docs/get-started/introduction)
|
||||
|
||||
### EasyPanel
|
||||
|
||||
Déployez Twenty sur EasyPanel avec le modèle maintenu par la communauté ci-dessous.
|
||||
|
||||
[Déployer sur EasyPanel](https://easypanel.io/docs/templates/twenty)
|
||||
|
||||
### Elest.io
|
||||
|
||||
Déployez Twenty sur les serveurs avec Elest.io en utilisant le lien ci-dessous.
|
||||
|
||||
[Déployer sur Elest.io](https://elest.io/open-source/twenty)
|
||||
|
||||
### Twenty sur Railway
|
||||
|
||||
Déployez Twenty sur Railway avec le modèle maintenu par la communauté ci-dessous.
|
||||
|
||||
[](https://railway.com/deploy/nAL3hA)
|
||||
|
||||
### Twenty sur Sealos
|
||||
|
||||
Déployez Twenty sur Sealos avec le modèle maintenu par la communauté ci-dessous.
|
||||
|
||||
[](https://sealos.io/products/app-store/twenty)
|
||||
|
||||
## Autres
|
||||
|
||||
N'hésitez pas à ouvrir une PR pour ajouter d'autres options de fournisseur cloud.
|
||||
@@ -4,57 +4,52 @@ image: /images/user-guide/tips/light-bulb.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/tips/light-bulb.png" alt="Header" />
|
||||
<img src="/images/user-guide/tips/light-bulb.png" alt="En-tête" />
|
||||
</Frame>
|
||||
|
||||
Un message bref qui affiche des informations supplémentaires lorsqu'un utilisateur interagit avec un élément.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="Utilisation">
|
||||
```jsx
|
||||
import { AppTooltip } from "@/ui/display/tooltip/AppTooltip";
|
||||
|
||||
```jsx
|
||||
import { AppTooltip } from "@/ui/display/tooltip/AppTooltip";
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<>
|
||||
<p id="hoverText" style={{ display: "inline-block" }}>
|
||||
Customer Insights
|
||||
</p>
|
||||
<AppTooltip
|
||||
className
|
||||
anchorSelect="#hoverText"
|
||||
content="Explore customer behavior and preferences"
|
||||
delayHide={0}
|
||||
offset={6}
|
||||
noArrow={false}
|
||||
isOpen={true}
|
||||
place="bottom"
|
||||
positionStrategy="absolute"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Props">
|
||||
|
||||
| Propriétés | Type | Description |
|
||||
| ----------------------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| nomDeClasse | chaîne | Classe CSS facultative pour le style supplémentaire |
|
||||
| sélecteurAncre | Sélecteur CSS | Sélecteur pour l'ancre de l'info-bulle (l'élément qui déclenche l'info-bulle) |
|
||||
| contenu | chaîne | Le contenu que vous souhaitez afficher dans l'info-bulle |
|
||||
| délaiMasquer | nombre | Le délai avant de masquer l'info-bulle après que le curseur ait quitté l'ancre |
|
||||
| décalage | nombre | Le décalage en pixels pour positionner l'info-bulle |
|
||||
| pasDeFlèche | booléen | Si `vrai`, masque la flèche sur l'info-bulle |
|
||||
| estOuvert | booléen | Si `vrai`, l'info-bulle est ouverte par défaut |
|
||||
| emplacement | Chaîne `PlacesType` de `react-tooltip` | Spécifie le placement de l'info-bulle. Les valeurs incluent `bottom`, `left`, `right`, `top`, `top-start`, `top-end`, `right-start`, `right-end`, `bottom-start`, `bottom-end`, `left-start`, et `left-end` |
|
||||
| stratégiePositionnement | Chaîne `PositionStrategy` de `react-tooltip` | Stratégie de positionnement pour l'info-bulle. A deux valeurs : `absolute` et `fixed` |
|
||||
|
||||
</Tab>
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<>
|
||||
<p id="hoverText" style={{ display: "inline-block" }}>
|
||||
Aperçus client
|
||||
</p>
|
||||
<AppTooltip
|
||||
className
|
||||
anchorSelect="#hoverText"
|
||||
content="Explorez le comportement et les préférences des clients"
|
||||
delayHide={0}
|
||||
offset={6}
|
||||
noArrow={false}
|
||||
isOpen={true}
|
||||
place="bottom"
|
||||
positionStrategy="absolute"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Propriétés">
|
||||
| Propriétés | Type | Description |
|
||||
| ----------------------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| nomDeClasse | chaîne | Classe CSS facultative pour le style supplémentaire |
|
||||
| sélecteurAncre | Sélecteur CSS | Sélecteur pour l'ancre de l'info-bulle (l'élément qui déclenche l'info-bulle) |
|
||||
| contenu | chaîne | Le contenu que vous souhaitez afficher dans l'info-bulle |
|
||||
| délaiMasquer | nombre | Le délai avant de masquer l'info-bulle après que le curseur ait quitté l'ancre |
|
||||
| décalage | nombre | Le décalage en pixels pour positionner l'info-bulle |
|
||||
| pasDeFlèche | booléen | Si `vrai`, masque la flèche sur l'info-bulle |
|
||||
| estOuvert | booléen | Si `vrai`, l'info-bulle est ouverte par défaut |
|
||||
| emplacement | Chaîne `PlacesType` de `react-tooltip` | Spécifie le placement de l'info-bulle. Les valeurs incluent `bottom`, `left`, `right`, `top`, `top-start`, `top-end`, `right-start`, `right-end`, `bottom-start`, `bottom-end`, `left-start`, et `left-end` |
|
||||
| stratégiePositionnement | Chaîne `PositionStrategy` de `react-tooltip` | Stratégie de positionnement pour l'info-bulle. A deux valeurs : `absolute` et `fixed` |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Texte débordant avec info-bulle
|
||||
@@ -62,27 +57,22 @@ export const MyComponent = () => {
|
||||
Gère le texte débordant et affiche une info-bulle lorsque le texte déborde.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="Utilisation">
|
||||
```jsx
|
||||
import { OverflowingTextWithTooltip } from 'twenty-ui/display';
|
||||
|
||||
```jsx
|
||||
import { OverflowingTextWithTooltip } from 'twenty-ui/display';
|
||||
export const MyComponent = () => {
|
||||
const crmTaskDescription =
|
||||
'Suivre avec le client concernant son enquête récente sur le produit. Discuter des options de prix, répondre à toute préoccupation et fournir des informations additionnelles sur le produit. Enregistrer les détails de la conversation dans le CRM pour référence future.';
|
||||
|
||||
export const MyComponent = () => {
|
||||
const crmTaskDescription =
|
||||
'Follow up with client regarding their recent product inquiry. Discuss pricing options, address any concerns, and provide additional product information. Record the details of the conversation in the CRM for future reference.';
|
||||
|
||||
return <OverflowingTextWithTooltip text={crmTaskDescription} />;
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Props">
|
||||
|
||||
| Propriétés | Type | Description |
|
||||
| ---------- | ------ | ----------------------------------------------------------------------- |
|
||||
| texte | chaîne | Le contenu que vous souhaitez afficher dans la zone de texte débordante |
|
||||
|
||||
</Tab>
|
||||
return <OverflowingTextWithTooltip text={crmTaskDescription} />;
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Propriétés">
|
||||
| Propriétés | Type | Description |
|
||||
| ---------- | ------ | ----------------------------------------------------------------------- |
|
||||
| texte | chaîne | Le contenu que vous souhaitez afficher dans la zone de texte débordante |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -4,5 +4,5 @@ image: /images/user-guide/tips/light-bulb.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/tips/light-bulb.png" alt="Header" />
|
||||
</Frame>
|
||||
<img src="/images/user-guide/tips/light-bulb.png" alt="En-tête" />
|
||||
</Frame>
|
||||
|
||||
@@ -4,44 +4,41 @@ image: /images/user-guide/tasks/tasks_header.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/tasks/tasks_header.png" alt="Header" />
|
||||
<img src="/images/user-guide/tasks/tasks_header.png" alt="En-tête" />
|
||||
</Frame>
|
||||
|
||||
Utilisé lorsqu'un utilisateur doit sélectionner plusieurs valeurs parmi plusieurs options.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="Utilisation">
|
||||
```jsx
|
||||
import { Checkbox } from "twenty-ui/display";
|
||||
|
||||
```jsx
|
||||
import { Checkbox } from "twenty-ui/display";
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={true}
|
||||
indeterminate={false}
|
||||
onChange={() => console.log("la fonction onChange est exécutée")}
|
||||
onCheckedChange={() => console.log("la fonction onCheckedChange est exécutée")}
|
||||
variant="primary"
|
||||
size="small"
|
||||
shape="squared"
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={true}
|
||||
indeterminate={false}
|
||||
onChange={() => console.log("onChange function fired")}
|
||||
onCheckedChange={() => console.log("onCheckedChange function fired")}
|
||||
variant="primary"
|
||||
size="small"
|
||||
shape="squared"
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| Propriétés | Type | Description |
|
||||
| --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| coché | booléen | Indique si la case à cocher est cochée |
|
||||
| indéterminé | booléen | Indique si la case à cocher est dans un état indéterminé (ni cochée ni décochée) |
|
||||
| onChange | fonction | La fonction de rappel que vous souhaitez déclencher lorsque l'état de la case à cocher change |
|
||||
| onCheckedChange | fonction | La fonction de rappel que vous souhaitez déclencher lorsque l'état `coché` change |
|
||||
| variante | chaîne | Le style visuel de la variante de la boîte. Les options incluent : `primaire`, `secondaire` et `tertiaire` |
|
||||
| taille | chaîne | La taille de la case à cocher. Comporte deux options : `petit` et `grand` |
|
||||
| forme | chaîne | La forme de la case à cocher. Comporte deux options : `carrée` et `arrondie` |
|
||||
|
||||
</Tab>
|
||||
<Tab title="Propriétés">
|
||||
| Propriétés | Type | Description |
|
||||
| --------------- | -------- | ---------------------------------------------------------------------------------------------------------- |
|
||||
| coché | booléen | Indique si la case à cocher est cochée |
|
||||
| indéterminé | booléen | Indique si la case à cocher est dans un état indéterminé (ni cochée ni décochée) |
|
||||
| onChange | fonction | La fonction de rappel que vous souhaitez déclencher lorsque l'état de la case à cocher change |
|
||||
| onCheckedChange | fonction | La fonction de rappel que vous souhaitez déclencher lorsque l'état `coché` change |
|
||||
| variante | chaîne | Le style visuel de la variante de la boîte. Les options incluent : `primaire`, `secondaire` et `tertiaire` |
|
||||
| taille | chaîne | La taille de la case à cocher. Comporte deux options : `petit` et `grand` |
|
||||
| forme | chaîne | La forme de la case à cocher. Comporte deux options : `carrée` et `arrondie` |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -4,53 +4,49 @@ image: /images/user-guide/github/github-header.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/github/github-header.png" alt="Header" />
|
||||
<img src="/images/user-guide/github/github-header.png" alt="En-tête" />
|
||||
</Frame>
|
||||
|
||||
Un sélecteur d'icônes basé sur un menu déroulant qui permet aux utilisateurs de sélectionner une icône dans une liste.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Utilisation">
|
||||
```jsx
|
||||
import { RecoilRoot } de "recoil";
|
||||
import React, { useState } de "react";
|
||||
import { IconPicker } de "@/ui/input/components/IconPicker";
|
||||
|
||||
<Tab title="Usage">
|
||||
export const MonComposant = () => {
|
||||
|
||||
```jsx
|
||||
import { RecoilRoot } from "recoil";
|
||||
import React, { useState } from "react";
|
||||
import { IconPicker } from "@/ui/input/components/IconPicker";
|
||||
const [selectedIcon, setSelectedIcon] = useState("");
|
||||
const handleIconChange = ({ iconKey, Icon }) => {
|
||||
console.log("Icône sélectionnée:", iconKey);
|
||||
setSelectedIcon(iconKey);
|
||||
};
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<RecoilRoot>
|
||||
<IconPicker
|
||||
disabled={false}
|
||||
onChange={handleIconChange}
|
||||
selectedIconKey={selectedIcon}
|
||||
variant="primary"
|
||||
/>
|
||||
</RecoilRoot>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
const [selectedIcon, setSelectedIcon] = useState("");
|
||||
const handleIconChange = ({ iconKey, Icon }) => {
|
||||
console.log("Selected Icon:", iconKey);
|
||||
setSelectedIcon(iconKey);
|
||||
};
|
||||
|
||||
return (
|
||||
<RecoilRoot>
|
||||
<IconPicker
|
||||
disabled={false}
|
||||
onChange={handleIconChange}
|
||||
selectedIconKey={selectedIcon}
|
||||
variant="primary"
|
||||
/>
|
||||
</RecoilRoot>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| Propriétés | Type | Description |
|
||||
| --------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| désactivé | booléen | Désactive le sélecteur d'icônes si défini sur `true` |
|
||||
| onChange | fonction | La fonction de rappel déclenchée lorsque l'utilisateur sélectionne une icône. Elle reçoit un objet avec les propriétés `iconKey` et `Icon` |
|
||||
| selectedIconKey | chaîne | La clé de l'icône initialement sélectionnée |
|
||||
| onClickOutside | fonction | Fonction de rappel déclenchée lorsque l'utilisateur clique en dehors du menu déroulant |
|
||||
| onClose | fonction | Fonction de rappel déclenchée lorsque le menu déroulant est fermé |
|
||||
| onOpen | fonction | Fonction de rappel déclenchée lorsque le menu déroulant est ouvert |
|
||||
| variante | chaîne | La variante de style visuel de l'icône cliquable. Les options incluent : `primaire`, `secondaire` et `tertiaire` |
|
||||
|
||||
</Tab>
|
||||
<Tab title="Propriétés">
|
||||
| Propriétés | Type | Description |
|
||||
| --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| désactivé | booléen | Désactive le sélecteur d'icônes si défini sur `true` |
|
||||
| onChange | fonction | La fonction de rappel déclenchée lorsque l'utilisateur sélectionne une icône. Elle reçoit un objet avec les propriétés `iconKey` et `Icon` |
|
||||
| selectedIconKey | chaîne | La clé de l'icône initialement sélectionnée |
|
||||
| onClickOutside | fonction | Fonction de rappel déclenchée lorsque l'utilisateur clique en dehors du menu déroulant |
|
||||
| onClose | fonction | Fonction de rappel déclenchée lorsque le menu déroulant est fermé |
|
||||
| onOpen | fonction | Fonction de rappel déclenchée lorsque le menu déroulant est ouvert |
|
||||
| variante | chaîne | La variante de style visuel de l'icône cliquable. Les options incluent : `primaire`, `secondaire` et `tertiaire` |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -4,34 +4,31 @@ image: /images/user-guide/objects/objects.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/objects/objects.png" alt="Header" />
|
||||
<img src="/images/user-guide/objects/objects.png" alt="En-tête" />
|
||||
</Frame>
|
||||
|
||||
Permet aux utilisateurs de télécharger et de supprimer une image.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="Utilisation">
|
||||
```jsx
|
||||
import { ImageInput } from "@/ui/input/components/ImageInput";
|
||||
|
||||
```jsx
|
||||
import { ImageInput } from "@/ui/input/components/ImageInput";
|
||||
export const MyComponent = () => {
|
||||
return <ImageInput/>;
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
export const MyComponent = () => {
|
||||
return <ImageInput/>;
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| Propriétés | Type | Description |
|
||||
| ------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| photo | chaîne | L'URL source de l'image |
|
||||
| onUpload | fonction | La fonction appelée lors du téléchargement d'une nouvelle image par un utilisateur. Elle reçoit l'objet `File` comme paramètre. |
|
||||
| onRemove | fonction | La fonction appelée lorsque l'utilisateur clique sur le bouton supprimer. |
|
||||
| onAbort | fonction | La fonction appelée lorsque l'utilisateur clique sur le bouton annuler pendant le téléchargement de l'image. |
|
||||
| isUploading | booléen | Indique si une image est en cours de téléchargement. |
|
||||
| messageErreur | chaîne | Un message d'erreur facultatif à afficher sous l'entrée d'image. |
|
||||
| désactivé | booléen | Si `true`, l'entrée entière est désactivée et les boutons ne sont pas cliquables. |
|
||||
|
||||
</Tab>
|
||||
<Tab title="Propriétés">
|
||||
| Propriétés | Type | Description |
|
||||
| ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| photo | chaîne | L'URL source de l'image |
|
||||
| onUpload | fonction | La fonction appelée lors du téléchargement d'une nouvelle image par un utilisateur. Elle reçoit l'objet `File` comme paramètre. |
|
||||
| onRemove | fonction | La fonction appelée lorsque l'utilisateur clique sur le bouton supprimer. |
|
||||
| onAbort | fonction | La fonction appelée lorsque l'utilisateur clique sur le bouton annuler pendant le téléchargement de l'image. |
|
||||
| isUploading | booléen | Indique si une image est en cours de téléchargement. |
|
||||
| messageErreur | chaîne | Un message d'erreur facultatif à afficher sous l'entrée d'image. |
|
||||
| désactivé | booléen | Si `true`, l'entrée entière est désactivée et les boutons ne sont pas cliquables. |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -4,7 +4,7 @@ image: /images/user-guide/notes/notes_header.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/notes/notes_header.png" alt="Header" />
|
||||
<img src="/images/user-guide/notes/notes_header.png" alt="En-tête" />
|
||||
</Frame>
|
||||
|
||||
## Entrée de texte
|
||||
@@ -12,59 +12,52 @@ image: /images/user-guide/notes/notes_header.png
|
||||
Permet aux utilisateurs de saisir et de modifier du texte.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Utilisation">
|
||||
```jsx
|
||||
import { RecoilRoot } from "recoil";
|
||||
import { TextInput } from "@/ui/input/components/TextInput";
|
||||
|
||||
<Tab title="Usage">
|
||||
export const MyComponent = () => {
|
||||
const handleChange = (text) => {
|
||||
console.log("Changement d'entrée:", text);
|
||||
};
|
||||
|
||||
```jsx
|
||||
import { RecoilRoot } from "recoil";
|
||||
import { TextInput } from "@/ui/input/components/TextInput";
|
||||
const handleKeyDown = (event) => {
|
||||
console.log("Touche enfoncée:", event.key);
|
||||
};
|
||||
|
||||
export const MyComponent = () => {
|
||||
const handleChange = (text) => {
|
||||
console.log("Input changed:", text);
|
||||
};
|
||||
return (
|
||||
<RecoilRoot>
|
||||
<TextInput
|
||||
className
|
||||
label="Nom d'utilisateur"
|
||||
onChange={handleChange}
|
||||
fullWidth={false}
|
||||
disableHotkeys={false}
|
||||
error="Nom d'utilisateur invalide"
|
||||
onKeyDown={handleKeyDown}
|
||||
RightIcon={null}
|
||||
/>
|
||||
</RecoilRoot>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
console.log("Key pressed:", event.key);
|
||||
};
|
||||
|
||||
return (
|
||||
<RecoilRoot>
|
||||
<TextInput
|
||||
className
|
||||
label="Username"
|
||||
onChange={handleChange}
|
||||
fullWidth={false}
|
||||
disableHotkeys={false}
|
||||
error="Invalid username"
|
||||
onKeyDown={handleKeyDown}
|
||||
RightIcon={null}
|
||||
/>
|
||||
</RecoilRoot>
|
||||
);
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Props">
|
||||
|
||||
| Propriétés | Type | Description |
|
||||
| ----------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| nomDeClasse | chaîne | Nom facultatif pour un style supplémentaire |
|
||||
| étiquette | chaîne | Représente l'étiquette de l'entrée |
|
||||
| onChange | fonction | La fonction appelée lorsque la valeur de l'entrée change |
|
||||
| largeurTotale | booléen | Indique si l'entrée doit occuper 100% de la largeur |
|
||||
| désactiverLesRaccourcis | booléen | Indique si les raccourcis sont activés pour l'entrée |
|
||||
| erreur | chaîne | Représente le message d'erreur à afficher. Lorsqu'il est fourni, il ajoute également une icône d'erreur sur le côté droit de l'entrée |
|
||||
| surToucheEnfoncée | fonction | Appelée lorsqu'une touche est enfoncée alors que le champ de saisie est focalisé. Reçoit un `React.KeyboardEvent` en tant qu'argument |
|
||||
| IcôneDroite | ComposantIcône | Un composant icône facultatif affiché sur le côté droit de l'entrée |
|
||||
|
||||
Le composant accepte également d'autres propriétés d'éléments d'entrée HTML.
|
||||
|
||||
</Tab>
|
||||
<Tab title="Propriétés">
|
||||
| Propriétés | Type | Description |
|
||||
| ----------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| nomDeClasse | chaîne | Nom facultatif pour un style supplémentaire |
|
||||
| étiquette | chaîne | Représente l'étiquette de l'entrée |
|
||||
| onChange | fonction | La fonction appelée lorsque la valeur de l'entrée change |
|
||||
| largeurTotale | booléen | Indique si l'entrée doit occuper 100% de la largeur |
|
||||
| désactiverLesRaccourcis | booléen | Indique si les raccourcis sont activés pour l'entrée |
|
||||
| erreur | chaîne | Représente le message d'erreur à afficher. Lorsqu'il est fourni, il ajoute également une icône d'erreur sur le côté droit de l'entrée |
|
||||
| surToucheEnfoncée | fonction | Appelée lorsqu'une touche est enfoncée alors que le champ de saisie est focalisé. Reçoit un `React.KeyboardEvent` en tant qu'argument |
|
||||
| IcôneDroite | ComposantIcône | Un composant icône facultatif affiché sur le côté droit de l'entrée |
|
||||
|
||||
Le composant accepte également d'autres propriétés d'éléments d'entrée HTML.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Entrée de texte à taille automatique
|
||||
@@ -72,46 +65,40 @@ Le composant accepte également d'autres propriétés d'éléments d'entrée HTM
|
||||
Composant d'entrée de texte qui ajuste automatiquement sa hauteur en fonction du contenu.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Utilisation">
|
||||
```jsx
|
||||
import { RecoilRoot } from "recoil";
|
||||
import { AutosizeTextInput } from "@/ui/input/components/AutosizeTextInput";
|
||||
|
||||
<Tab title="Usage">
|
||||
|
||||
```jsx
|
||||
import { RecoilRoot } from "recoil";
|
||||
import { AutosizeTextInput } from "@/ui/input/components/AutosizeTextInput";
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<RecoilRoot>
|
||||
<AutosizeTextInput
|
||||
onValidate={() => console.log("onValidate function fired")}
|
||||
minRows={1}
|
||||
placeholder="Write a comment"
|
||||
onFocus={() => console.log("onFocus function fired")}
|
||||
variant="icon"
|
||||
buttonTitle
|
||||
value="Task: "
|
||||
/>
|
||||
</RecoilRoot>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Props">
|
||||
|
||||
| Propriétés | Type | Description |
|
||||
| -------------- | -------- | -------------------------------------------------------------------------------------------------------------- |
|
||||
| surValider | fonction | La fonction de rappel que vous souhaitez déclencher lorsque l'utilisateur valide l'entrée |
|
||||
| minLignes | nombre | Le nombre minimum de lignes pour la zone de texte |
|
||||
| espace réservé | chaîne | Le texte d'espace réservé que vous souhaitez afficher lorsque la zone de texte est vide |
|
||||
| surFocus | fonction | La fonction de rappel que vous souhaitez déclencher lorsque la zone de texte prend le focus |
|
||||
| variante | chaîne | La variante de l'entrée. Les options incluent : `défaut`, `icône`, et `bouton` |
|
||||
| titreBouton | chaîne | Le titre pour le bouton (applicable uniquement à la variante bouton) |
|
||||
| valeur | chaîne | La valeur initiale pour la zone de texte |
|
||||
|
||||
</Tab>
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<RecoilRoot>
|
||||
<AutosizeTextInput
|
||||
onValidate={() => console.log("Fonction onValidate déclenchée")}
|
||||
minRows={1}
|
||||
placeholder="Écrivez un commentaire"
|
||||
onFocus={() => console.log("Fonction onFocus déclenchée")}
|
||||
variant="icon"
|
||||
buttonTitle
|
||||
value="Tâche: "
|
||||
/>
|
||||
</RecoilRoot>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Propriétés">
|
||||
| Propriétés | Type | Description |
|
||||
| -------------- | -------- | ------------------------------------------------------------------------------------------- |
|
||||
| surValider | fonction | La fonction de rappel que vous souhaitez déclencher lorsque l'utilisateur valide l'entrée |
|
||||
| minLignes | nombre | Le nombre minimum de lignes pour la zone de texte |
|
||||
| espace réservé | chaîne | Le texte d'espace réservé que vous souhaitez afficher lorsque la zone de texte est vide |
|
||||
| surFocus | fonction | La fonction de rappel que vous souhaitez déclencher lorsque la zone de texte prend le focus |
|
||||
| variante | chaîne | La variante de l'entrée. Les options incluent : `défaut`, `icône`, et `bouton` |
|
||||
| titreBouton | chaîne | Le titre pour le bouton (applicable uniquement à la variante bouton) |
|
||||
| valeur | chaîne | La valeur initiale pour la zone de texte |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Zone de texte
|
||||
@@ -119,35 +106,31 @@ export const MyComponent = () => {
|
||||
Vous permet de créer des entrées de texte multiligne.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="Utilisation">
|
||||
```jsx
|
||||
import { TextArea } from "@/ui/input/components/TextArea";
|
||||
|
||||
```jsx
|
||||
import { TextArea } from "@/ui/input/components/TextArea";
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<TextArea
|
||||
disabled={false}
|
||||
minRows={4}
|
||||
onChange={()=>console.log('Fonction onChange déclenchée')}
|
||||
placeholder="Entrez le texte ici"
|
||||
value=""
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<TextArea
|
||||
disabled={false}
|
||||
minRows={4}
|
||||
onChange={()=>console.log('On change function fired')}
|
||||
placeholder="Enter text here"
|
||||
value=""
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Props">
|
||||
|
||||
| Propriétés | Type | Description |
|
||||
| -------------- | -------- | --------------------------------------------------------------------------- |
|
||||
| désactivé | booléen | Indique si la zone de texte est désactivée |
|
||||
| minLignes | nombre | Nombre minimum de lignes visibles pour la zone de texte. |
|
||||
| onChange | fonction | Fonction de rappel déclenchée lorsque le contenu de la zone de texte change |
|
||||
| espace réservé | chaîne | Texte de l'espace réservé affiché lorsque la zone de texte est vide |
|
||||
| valeur | chaîne | La valeur actuelle de la zone de texte |
|
||||
|
||||
</Tab>
|
||||
<Tab title="Propriétés">
|
||||
| Propriétés | Type | Description |
|
||||
| -------------- | -------- | --------------------------------------------------------------------------- |
|
||||
| désactivé | booléen | Indique si la zone de texte est désactivée |
|
||||
| minLignes | nombre | Nombre minimum de lignes visibles pour la zone de texte. |
|
||||
| surChangement | fonction | Fonction de rappel déclenchée lorsque le contenu de la zone de texte change |
|
||||
| espace réservé | chaîne | Texte de l'espace réservé affiché lorsque la zone de texte est vide |
|
||||
| valeur | chaîne | La valeur actuelle de la zone de texte |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -4,5 +4,5 @@ image: /images/user-guide/tasks/tasks_header.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/tasks/tasks_header.png" alt="Header" />
|
||||
</Frame>
|
||||
<img src="/images/user-guide/tasks/tasks_header.png" alt="En-tête" />
|
||||
</Frame>
|
||||
|
||||
@@ -4,41 +4,38 @@ image: /images/user-guide/fields/field.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/fields/field.png" alt="Header" />
|
||||
<img src="/images/user-guide/fields/field.png" alt="En-tête" />
|
||||
</Frame>
|
||||
|
||||
Affiche une barre de navigation en fil d'Ariane.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="Utilisation">
|
||||
```jsx
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import { Breadcrumb } from "@/ui/navigation/bread-crumb/components/Breadcrumb";
|
||||
|
||||
```jsx
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import { Breadcrumb } from "@/ui/navigation/bread-crumb/components/Breadcrumb";
|
||||
export const MyComponent = () => {
|
||||
const breadcrumbLinks = [
|
||||
{ children: "Accueil", href: "/" },
|
||||
{ children: "Catégorie", href: "/category" },
|
||||
{ children: "Sous-catégorie", href: "/category/subcategory" },
|
||||
{ children: "Page Actuelle" },
|
||||
];
|
||||
|
||||
export const MyComponent = () => {
|
||||
const breadcrumbLinks = [
|
||||
{ children: "Home", href: "/" },
|
||||
{ children: "Category", href: "/category" },
|
||||
{ children: "Subcategory", href: "/category/subcategory" },
|
||||
{ children: "Current Page" },
|
||||
];
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Breadcrumb className links={breadcrumbLinks} />
|
||||
</BrowserRouter>
|
||||
)
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Breadcrumb className links={breadcrumbLinks} />
|
||||
</BrowserRouter>
|
||||
)
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| Propriétés | Type | Description |
|
||||
| ----------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| nomDeClasse | chaîne | Nom de classe facultatif pour un style supplémentaire |
|
||||
| liens | tableau | Un tableau d'objets, chacun représentant un lien de fil d'Ariane. Chaque objet a une propriété `children` (le contenu textuel du lien) et une propriété `href` facultative (l'URL vers laquelle naviguer lorsque le lien est cliqué) |
|
||||
|
||||
</Tab>
|
||||
<Tab title="Propriétés">
|
||||
| Propriétés | Type | Description |
|
||||
| ----------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| nomDeClasse | chaîne | Nom de classe facultatif pour un style supplémentaire |
|
||||
| liens | tableau | Un tableau d'objets, chacun représentant un lien de fil d'Ariane. Chaque objet a une propriété `children` (le contenu textuel du lien) et une propriété `href` facultative (l'URL vers laquelle naviguer lorsque le lien est cliqué) |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -4,7 +4,7 @@ image: /images/user-guide/what-is-twenty/20.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/what-is-twenty/20.png" alt="Header" />
|
||||
<img src="/images/user-guide/what-is-twenty/20.png" alt="En-tête" />
|
||||
</Frame>
|
||||
|
||||
## Lien de contact
|
||||
@@ -12,44 +12,40 @@ image: /images/user-guide/what-is-twenty/20.png
|
||||
Un composant de lien stylisé pour afficher les informations de contact.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="Utilisation">
|
||||
```jsx
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
|
||||
```jsx
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { ContactLink } from 'twenty-ui/navigation';
|
||||
|
||||
import { ContactLink } from 'twenty-ui/navigation';
|
||||
export const MyComponent = () => {
|
||||
const handleLinkClick = (event) => {
|
||||
console.log('Lien de contact cliqué!', event);
|
||||
};
|
||||
|
||||
export const MyComponent = () => {
|
||||
const handleLinkClick = (event) => {
|
||||
console.log('Contact link clicked!', event);
|
||||
};
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<ContactLink
|
||||
className
|
||||
href="mailto:example@example.com"
|
||||
onClick={handleLinkClick}
|
||||
>
|
||||
example@example.com
|
||||
</ContactLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| Propriétés | Type | Description |
|
||||
| ----------- | ----------------- | -------------------------------------------------------- |
|
||||
| nomDeClasse | chaîne | Nom facultatif pour un style supplémentaire |
|
||||
| href | chaîne | L'URL cible ou le chemin du lien |
|
||||
| onClick | fonction | Fonction de rappel à déclencher lors du clic sur le lien |
|
||||
| enfants | `React.ReactNode` | Le contenu à afficher à l'intérieur du lien |
|
||||
|
||||
</Tab>
|
||||
return (
|
||||
<Router>
|
||||
<ContactLink
|
||||
className
|
||||
href="mailto:example@example.com"
|
||||
onClick={handleLinkClick}
|
||||
>
|
||||
example@example.com
|
||||
</ContactLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Propriétés">
|
||||
| Propriétés | Type | Description |
|
||||
| ----------- | ----------------- | -------------------------------------------------------- |
|
||||
| nomDeClasse | chaîne | Nom facultatif pour un style supplémentaire |
|
||||
| href | chaîne | L'URL cible ou le chemin du lien |
|
||||
| onClick | fonction | Fonction de rappel à déclencher lors du clic sur le lien |
|
||||
| enfants | `React.ReactNode` | Le contenu à afficher à l'intérieur du lien |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Lien brut
|
||||
@@ -57,39 +53,35 @@ export const MyComponent = () => {
|
||||
Un composant de lien stylisé pour afficher les liens.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="Utilisation">
|
||||
```jsx
|
||||
import { RawLink } from "/navigation";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
|
||||
```jsx
|
||||
import { RawLink } from "/navigation";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
export const MyComponent = () => {
|
||||
const handleLinkClick = (event) => {
|
||||
console.log("Lien de contact cliqué!", event);
|
||||
};
|
||||
|
||||
export const MyComponent = () => {
|
||||
const handleLinkClick = (event) => {
|
||||
console.log("Contact link clicked!", event);
|
||||
};
|
||||
return (
|
||||
<Router>
|
||||
<RawLink className href="/contact" onClick={handleLinkClick}>
|
||||
Contactez-nous
|
||||
</RawLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<RawLink className href="/contact" onClick={handleLinkClick}>
|
||||
Contact Us
|
||||
</RawLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| Propriétés | Type | Description |
|
||||
| ----------- | ----------------- | -------------------------------------------------------- |
|
||||
| nomDeClasse | chaîne | Nom facultatif pour un style supplémentaire |
|
||||
| href | chaîne | L'URL cible ou le chemin du lien |
|
||||
| auClique | fonction | Fonction de rappel à déclencher lors du clic sur le lien |
|
||||
| enfants | `React.ReactNode` | Le contenu à afficher à l'intérieur du lien |
|
||||
|
||||
</Tab>
|
||||
<Tab title="Propriétés">
|
||||
| Propriétés | Type | Description |
|
||||
| ----------- | ----------------- | -------------------------------------------------------- |
|
||||
| nomDeClasse | chaîne | Nom facultatif pour un style supplémentaire |
|
||||
| href | chaîne | L'URL cible ou le chemin du lien |
|
||||
| onClick | fonction | Fonction de rappel à déclencher lors du clic sur le lien |
|
||||
| enfants | `React.ReactNode` | Le contenu à afficher à l'intérieur du lien |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Lien arrondi
|
||||
@@ -97,38 +89,34 @@ export const MyComponent = () => {
|
||||
Un lien de style arrondi avec un composant Chip pour les liens.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="Utilisation">
|
||||
```jsx
|
||||
import { RoundedLink } from "/navigation";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
|
||||
```jsx
|
||||
import { RoundedLink } from "/navigation";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
export const MyComponent = () => {
|
||||
const handleLinkClick = (event) => {
|
||||
console.log("Lien de contact cliqué!", event);
|
||||
};
|
||||
|
||||
export const MyComponent = () => {
|
||||
const handleLinkClick = (event) => {
|
||||
console.log("Contact link clicked!", event);
|
||||
};
|
||||
return (
|
||||
<Router>
|
||||
<RoundedLink href="/contact" onClick={handleLinkClick}>
|
||||
Contactez-nous
|
||||
</RoundedLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<RoundedLink href="/contact" onClick={handleLinkClick}>
|
||||
Contact Us
|
||||
</RoundedLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Props">
|
||||
|
||||
| Propriétés | Type | Description |
|
||||
| ---------- | ----------------- | -------------------------------------------------------- |
|
||||
| href | chaîne | L'URL cible ou le chemin du lien |
|
||||
| enfants | `React.ReactNode` | Le contenu à afficher à l'intérieur du lien |
|
||||
| auClique | fonction | Fonction de rappel à déclencher lors du clic sur le lien |
|
||||
|
||||
</Tab>
|
||||
<Tab title="Propriétés">
|
||||
| Propriétés | Type | Description |
|
||||
| ---------- | ----------------- | -------------------------------------------------------- |
|
||||
| href | chaîne | L'URL cible ou le chemin du lien |
|
||||
| enfants | `React.ReactNode` | Le contenu à afficher à l'intérieur du lien |
|
||||
| onClick | fonction | Fonction de rappel à déclencher lors du clic sur le lien |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Lien social
|
||||
@@ -136,33 +124,30 @@ export const MyComponent = () => {
|
||||
Liens sociaux stylisés, avec support pour différents types de liens sociaux, tels que les URL, LinkedIn, et X (ou Twitter).
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="Utilisation">
|
||||
```jsx
|
||||
import { SocialLink } from "twenty-ui/navigation";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
|
||||
```jsx
|
||||
import { SocialLink } from "twenty-ui/navigation";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<Router>
|
||||
<SocialLink
|
||||
type="twitter"
|
||||
href="https://twitter.com/twentycrm"
|
||||
></SocialLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<Router>
|
||||
<SocialLink
|
||||
type="twitter"
|
||||
href="https://twitter.com/twentycrm"
|
||||
></SocialLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| Propriétés | Type | Description |
|
||||
| ---------- | ----------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| href | chaîne | L'URL cible ou le chemin du lien |
|
||||
| enfants | `React.ReactNode` | Le contenu à afficher à l'intérieur du lien |
|
||||
| type | chaîne | Le type de liens sociaux. Les options incluent: `url`, `LinkedIn`, et `Twitter` |
|
||||
| auClique | fonction | Fonction de rappel à déclencher lors du clic sur le lien |
|
||||
|
||||
</Tab>
|
||||
<Tab title="Propriétés">
|
||||
| Propriétés | Type | Description |
|
||||
| ---------- | ----------------- | ------------------------------------------------------------------------------- |
|
||||
| href | chaîne | L'URL cible ou le chemin du lien |
|
||||
| enfants | `React.ReactNode` | Le contenu à afficher à l'intérieur du lien |
|
||||
| type | chaîne | Le type de liens sociaux. Les options incluent: `url`, `LinkedIn`, et `Twitter` |
|
||||
| onClick | fonction | Fonction de rappel à déclencher lors du clic sur le lien |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -4,35 +4,31 @@ image: /images/user-guide/api/api.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/api/api.png" alt="Header" />
|
||||
<img src="/images/user-guide/api/api.png" alt="En-tête" />
|
||||
</Frame>
|
||||
|
||||
Affiche la progression à travers une séquence d'étapes numérotées en surlignant l'étape active. Il affiche un conteneur avec des étapes, chacune représentée par le composant `Step`.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="Utilisation">
|
||||
```jsx
|
||||
import { StepBar } from "@/ui/navigation/step-bar/components/StepBar";
|
||||
|
||||
```jsx
|
||||
import { StepBar } from "@/ui/navigation/step-bar/components/StepBar";
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<StepBar activeStep={2}>
|
||||
<StepBar.Step>Étape 1</StepBar.Step>
|
||||
<StepBar.Step>Étape 2</StepBar.Step>
|
||||
<StepBar.Step>Étape 3</StepBar.Step>
|
||||
</StepBar>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<StepBar activeStep={2}>
|
||||
<StepBar.Step>Step 1</StepBar.Step>
|
||||
<StepBar.Step>Step 2</StepBar.Step>
|
||||
<StepBar.Step>Step 3</StepBar.Step>
|
||||
</StepBar>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Props">
|
||||
|
||||
| Propriétés | Type | Description |
|
||||
| ----------- | ------ | ------------------------------------------------------------------------------------------------------------------------- |
|
||||
| étapeActive | nombre | L'index de l'étape actuellement active. Cela détermine quelle étape doit être visuellement mise en valeur |
|
||||
|
||||
</Tab>
|
||||
<Tab title="Propriétés">
|
||||
| Propriétés | Type | Description |
|
||||
| ----------- | ------ | --------------------------------------------------------------------------------------------------------- |
|
||||
| étapeActive | nombre | L'index de l'étape actuellement active. Cela détermine quelle étape doit être visuellement mise en valeur |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -4,46 +4,41 @@ image: /images/user-guide/emails/emails_header.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/emails/emails_header.png" alt="Header" />
|
||||
<img src="/images/user-guide/emails/emails_header.png" alt="En-tête" />
|
||||
</Frame>
|
||||
|
||||
Indique le progrès ou le compte à rebours et passe de droite à gauche.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Utilisation">
|
||||
```jsx
|
||||
import { ProgressBar } from "twenty-ui/feedback";
|
||||
|
||||
<Tab title="Usage">
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<ProgressBar
|
||||
duration={6000}
|
||||
delay={0}
|
||||
easing="easeInOut"
|
||||
barHeight={10}
|
||||
barColor="#4bb543"
|
||||
autoStart={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
```jsx
|
||||
import { ProgressBar } from "twenty-ui/feedback";
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<ProgressBar
|
||||
duration={6000}
|
||||
delay={0}
|
||||
easing="easeInOut"
|
||||
barHeight={10}
|
||||
barColor="#4bb543"
|
||||
autoStart={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Props">
|
||||
|
||||
| Propriétés | Type | Description | Par défaut |
|
||||
| -------------------- | ------- | --------------------------------------------------------------------------------------------------------- | ---------- |
|
||||
| durée | nombre | La durée totale de l'animation de la barre de progression en millisecondes | 3 |
|
||||
| délai | nombre | Le délai de démarrage de l'animation de la barre de progression en millisecondes | 0 |
|
||||
| adoucissement | chaîne | Fonction d'adoucissement pour l'animation de la barre de progression | easeInOut |
|
||||
| hauteurBarre | nombre | La hauteur de la barre en pixels | 24 |
|
||||
| couleurBarre | chaîne | La couleur de la barre | gray80 |
|
||||
| démarrageAutomatique | booléen | Si `true`, l'animation de la barre de progression commence automatiquement lorsque le composant est monté | `vrai` |
|
||||
|
||||
</Tab>
|
||||
<Tab title="Propriétés">
|
||||
| Propriétés | Type | Description | Par défaut |
|
||||
| -------------------- | ------- | --------------------------------------------------------------------------------------------------------- | ---------- |
|
||||
| durée | nombre | La durée totale de l'animation de la barre de progression en millisecondes | 3 |
|
||||
| délai | nombre | Le délai de démarrage de l'animation de la barre de progression en millisecondes | 0 |
|
||||
| adoucissement | chaîne | Fonction d'adoucissement pour l'animation de la barre de progression | easeInOut |
|
||||
| hauteurBarre | nombre | La hauteur de la barre en pixels | 24 |
|
||||
| couleurBarre | chaîne | La couleur de la barre | gray80 |
|
||||
| démarrageAutomatique | booléen | Si `true`, l'animation de la barre de progression commence automatiquement lorsque le composant est monté | `vrai` |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Barre de Progression Circulaire
|
||||
@@ -51,27 +46,21 @@ export const MyComponent = () => {
|
||||
Indique le progrès d'une tâche, souvent utilisé sur des écrans de chargement ou dans des zones où vous souhaitez communiquer des processus en cours à l'utilisateur.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Utilisation">
|
||||
```jsx
|
||||
import { CircularProgressBar } from "@/ui/feedback/progress-bar/components/CircularProgressBar";
|
||||
|
||||
<Tab title="Usage">
|
||||
|
||||
```jsx
|
||||
import { CircularProgressBar } from "@/ui/feedback/progress-bar/components/CircularProgressBar";
|
||||
|
||||
export const MyComponent = () => {
|
||||
return <CircularProgressBar size={80} barWidth={6} barColor="green" />;
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Props">
|
||||
|
||||
| Propriétés | Type | Description | Par défaut |
|
||||
| ------------ | ------ | ------------------------------------------------- | --------------- |
|
||||
| taille | nombre | La taille de la barre de progression circulaire | 50 |
|
||||
| largeurBarre | nombre | La largeur de la ligne de la barre de progression | 5 |
|
||||
| couleurBarre | chaîne | La couleur de la barre de progression | couleurCourante |
|
||||
|
||||
</Tab>
|
||||
export const MyComponent = () => {
|
||||
return <CircularProgressBar size={80} barWidth={6} barColor="green" />;
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Propriétés">
|
||||
| Propriétés | Type | Description | Par défaut |
|
||||
| ------------ | ------ | ------------------------------------------------- | --------------- |
|
||||
| taille | nombre | La taille de la barre de progression circulaire | 50 |
|
||||
| largeurBarre | nombre | La largeur de la ligne de la barre de progression | 5 |
|
||||
| couleurBarre | chaîne | La couleur de la barre de progression | couleurCourante |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: Autorisations et contrôle d’accès
|
||||
description: Contrôlez ce à quoi les agents IA peuvent accéder et ce qu’ils peuvent modifier dans votre espace de travail.
|
||||
---
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
Les agents IA respectent votre structure d’autorisations existante. C’est particulièrement important pour les équipes qui veulent contrôler précisément à quoi les processus d’IA automatisés peuvent accéder ou ce qu’ils peuvent modifier dans leur espace de travail.
|
||||
|
||||
## Attribuer un Rôle à un Agent IA
|
||||
|
||||
1. Allez dans **Paramètres → Rôles**
|
||||
2. Cliquez sur le rôle que vous souhaitez attribuer
|
||||
3. Ouvrez l'onglet **Attribution**
|
||||
4. Sous **Agents IA**, cliquez sur **+ Attribuer à un agent IA**
|
||||
5. Sélectionnez l’agent IA dans la liste
|
||||
6. Confirmez l'attribution
|
||||
|
||||
## Pourquoi Attribuer des Rôles aux Agents IA ?
|
||||
|
||||
| Avantage | Description |
|
||||
| ---------------- | --------------------------------------------------------------------------------------- |
|
||||
| **Sécurité** | Limitez les données auxquelles les agents IA peuvent accéder ou qu’ils peuvent modifier |
|
||||
| **Conformité** | Garantissez que l’IA ne traite que les données dont elle a besoin |
|
||||
| **Contrôle** | Empêchez les actions involontaires des automatisations d’IA |
|
||||
| **Auditabilité** | Suivez quelles actions ont été effectuées par chaque agent |
|
||||
|
||||
<Note>
|
||||
Pour les agents IA s’exécutant au sein de flux de travail, l’attribution d’un rôle garantit que l’agent ne peut pas accéder ni modifier des données en dehors de sa portée prévue—même si le flux de travail dispose d’autorisations plus étendues.
|
||||
</Note>
|
||||
|
||||
## Contenu connexe
|
||||
|
||||
* [Autorisations](/l/fr/user-guide/permissions-access/capabilities/permissions) — informations détaillées sur la création et la gestion des rôles
|
||||
* [Agents IA](/l/fr/user-guide/ai/capabilities/ai-agents) — fonctionnalités d’IA dans les flux de travail
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: Formules tarifaires},{
|
||||
description: En savoir plus sur les formules tarifaires de Twenty et comment passer de l’une à l’autre.
|
||||
---
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
Twenty propose une tarification flexible adaptée aux équipes de toute taille, que vous préfériez l’hébergement cloud ou l’auto-hébergement.
|
||||
|
||||
## Formules Cloud
|
||||
|
||||
### Pro (Cloud)
|
||||
|
||||
Pour les équipes prêtes à passer à l’échelle :
|
||||
|
||||
* Toutes les fonctionnalités CRM essentielles
|
||||
* Synchronisation des e-mails et du calendrier
|
||||
* Workflows et automatisations
|
||||
* Support standard
|
||||
|
||||
<Note>
|
||||
Les fonctionnalités premium (SSO et autorisations au niveau des lignes) ne sont pas incluses dans la formule Pro.
|
||||
</Note>
|
||||
|
||||
### Organisation (Cloud)
|
||||
|
||||
Pour les équipes plus importantes ayant des besoins avancés :
|
||||
|
||||
* Tout ce qui est inclus dans Pro
|
||||
* **Fonctionnalités premium** : intégration SSO et autorisations au niveau des lignes
|
||||
* Support prioritaire
|
||||
|
||||
## Formules auto-hébergées
|
||||
|
||||
### Gratuit (Auto-hébergé)
|
||||
|
||||
Hébergez Twenty sur votre propre infrastructure, sans frais :
|
||||
|
||||
* Toutes les fonctionnalités Pro incluses
|
||||
* Support communautaire via Discord
|
||||
* Contrôle total sur vos données
|
||||
|
||||
### Organisation (Auto-hébergé)
|
||||
|
||||
Pour les équipes qui ont besoin de fonctionnalités premium tout en s’auto-hébergeant :
|
||||
|
||||
* Toutes les fonctionnalités Pro
|
||||
* **Fonctionnalités premium** : intégration SSO et autorisations au niveau des lignes
|
||||
* Assistance de l’équipe Twenty
|
||||
* Aucune obligation de publier le code personnalisé en open source avant de le distribuer
|
||||
|
||||
## Fonctionnalités premium
|
||||
|
||||
Les fonctionnalités premium ne sont disponibles que sur les formules Organisation (Cloud ou auto-hébergé) :
|
||||
|
||||
* **Intégration SSO** : authentification unique avec votre fournisseur d’identité
|
||||
* **Autorisations au niveau des lignes** : contrôle d’accès granulaire au niveau de l’enregistrement
|
||||
|
||||
## Changer de formule
|
||||
|
||||
### Passer à l’Organisation
|
||||
|
||||
1. Allez dans **Paramètres → Facturation**
|
||||
2. Cliquez sur **Passer à l’Organisation**
|
||||
3. Confirmez la mise à niveau
|
||||
|
||||
### Rétrograder vers Pro
|
||||
|
||||
Contactez l’assistance pour rétrograder votre formule.
|
||||
|
||||
### Passer à la facturation annuelle
|
||||
|
||||
1. Allez dans **Paramètres → Facturation**
|
||||
2. Cliquez sur **Passer à l’annuel**
|
||||
3. Économisez avec la facturation annuelle
|
||||
|
||||
### Passer à la facturation mensuelle
|
||||
|
||||
Contactez l’assistance pour revenir à la facturation mensuelle.
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
---
|
||||
title: Limiter les e-mails importés.
|
||||
description: Contrôlez quels e-mails sont importés dans Twenty.
|
||||
---
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
Par défaut, Twenty synchronise tous les e-mails externes depuis votre boîte aux lettres connectée. Vous pouvez limiter ce qui est importé à l’aide de la **sélection de dossiers** et des **paramètres de visibilité**.
|
||||
|
||||
## Méthode 1 : Sélection de dossiers (recommandée)
|
||||
|
||||
Contrôlez quels dossiers e-mail sont synchronisés avec Twenty :
|
||||
|
||||
1. Allez dans **Paramètres → Versions → Lab**
|
||||
2. Activez **Dossier de messages**
|
||||
3. Retournez à **Paramètres → Comptes**
|
||||
4. Sélectionnez votre compte e-mail connecté
|
||||
5. Choisissez quels dossiers synchroniser :
|
||||
|
||||
| Dossier | Description |
|
||||
| -------------------------- | -------------------------------------------- |
|
||||
| **Boîte de réception** | E-mails entrants principaux |
|
||||
| **Envoyé** | E-mails sortants que vous avez envoyés |
|
||||
| **Archive** | Messages archivés |
|
||||
| **Dossiers personnalisés** | Tous les dossiers spécifiques de votre choix |
|
||||
|
||||
6. Excluez les dossiers que vous ne voulez pas synchroniser (Spam, Corbeille, dossiers personnels)
|
||||
|
||||
<Note>Cela vous donne un contrôle précis sur les e-mails qui apparaissent dans votre CRM sans tout synchroniser.</Note>
|
||||
|
||||
## Méthode 2 : Paramètres de création automatique de contacts
|
||||
|
||||
Contrôlez à quel moment les contacts sont créés à partir des e-mails :
|
||||
|
||||
1. Allez dans **Paramètres → Comptes**
|
||||
2. Sélectionnez votre boîte aux lettres connectée
|
||||
3. Choisissez une option :
|
||||
* **Désactivé** : Aucun contact n’est créé, mais les e-mails sont tout de même synchronisés avec les contacts existants.
|
||||
* **Envoyés et reçus** : Créer des contacts à partir de tous les e-mails externes
|
||||
* **Envoyés uniquement** : Créer des contacts uniquement à partir des e-mails que vous envoyez
|
||||
|
||||
## Ce qui est toujours exclu
|
||||
|
||||
Ces e-mails ne sont jamais synchronisés, quels que soient les paramètres :
|
||||
|
||||
* **E-mails internes** : Messages entre collègues (même domaine)
|
||||
* **E-mails de groupe** : Listes de distribution et messages de groupe
|
||||
* **Spam/Corbeille** : Les dossiers système sont généralement exclus
|
||||
|
||||
## Note importante
|
||||
|
||||
<Note>Nous ne fournissons pas d’adresse e-mail en CC pour une synchronisation sélective. Utilisez la fonctionnalité de sélection de dossiers ci-dessus pour obtenir le même niveau de contrôle.</Note>
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
title: Formats de fichiers pris en charge
|
||||
description: Formats de fichiers pris en charge pour l’importation de données dans Twenty.
|
||||
---
|
||||
|
||||
## Formats pris en charge
|
||||
|
||||
Twenty prend en charge trois formats de fichiers pour l’importation :
|
||||
|
||||
| Format | Extension | Notes |
|
||||
| ------------------ | --------- | ------------------------------ |
|
||||
| **CSV** | .csv | Recommandé, le plus compatible |
|
||||
| **Excel** | .xlsx | Format Excel moderne |
|
||||
| **Excel (hérité)** | .xls | Ancien format Excel |
|
||||
|
||||
## Exigences relatives aux fichiers
|
||||
|
||||
| Exigence | Valeur |
|
||||
| ---------------------------- | -------------------------------------------------------- |
|
||||
| **Encodage** | UTF-8 recommandé |
|
||||
| **Limite d’enregistrements** | 10 000 enregistrements par fichier |
|
||||
| **Structure** | La première ligne doit contenir les en-têtes de colonnes |
|
||||
| **Contenu** | Un seul type d’objet par fichier |
|
||||
|
||||
## Meilleures pratiques pour les fichiers CSV
|
||||
|
||||
* **Délimiteur** : Utilisez une virgule (`,`) ou un point-virgule (`;`)
|
||||
* **Délimiteur de texte** : Utilisez des guillemets doubles (`\"`) pour le texte contenant des virgules
|
||||
* **Fins de ligne** : Windows (CRLF) ou Unix (LF) sont tous deux pris en charge
|
||||
* **Valeurs vides** : Laissez les cellules vides, n’utilisez pas "NULL" ni "N/A"
|
||||
|
||||
## Meilleures pratiques pour Excel
|
||||
|
||||
Lors de l’exportation depuis Excel :
|
||||
|
||||
* Supprimez les formules (exportez uniquement les valeurs)
|
||||
* Supprimez les lignes vides à la fin
|
||||
* Assurez-vous qu’aucune cellule n’est fusionnée
|
||||
* Utilisez uniquement la première feuille
|
||||
|
||||
## Jeux de données volumineux
|
||||
|
||||
Pour les jeux de données de plus de 10 000 enregistrements :
|
||||
|
||||
* Divisez en plusieurs fichiers
|
||||
* Ou utilisez l’[importation via l’API](/l/fr/user-guide/data-migration/how-tos/import-data-via-api) pour un nombre illimité d’enregistrements
|
||||
|
||||
<Note>Pour des migrations très volumineuses (plus de 100 000 enregistrements), l’API est nettement plus rapide et plus fiable que les importations CSV.</Note>
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
title: Migration de données},{
|
||||
description: Importez et exportez vos données CRM via des fichiers CSV ou l’API.
|
||||
image: /images/user-guide/import-export-data/cloud.png
|
||||
---
|
||||
|
||||
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/import-export-data/cloud.png" alt="Migration de données" />
|
||||
</Frame>
|
||||
|
||||
## Méthodes d’import
|
||||
|
||||
Twenty prend en charge deux méthodes principales d’import de données :
|
||||
|
||||
| Méthode | Idéal pour | Limite de volume |
|
||||
| ----------------------- | -------------------------------------------- | ---------------------------------- |
|
||||
| **Importation CSV** | Migrations standard, mises à jour régulières | 10 000 enregistrements par fichier |
|
||||
| **Importation via API** | Migrations à grande échelle, automatisation | Illimité |
|
||||
|
||||
<Note>Pour des ensembles de données très volumineux (des centaines de milliers d’enregistrements), utilisez l’API. Nos [partenaires d’implémentation](/l/fr/user-guide/getting-started/capabilities/implementation-services) peuvent vous aider à exécuter ces scripts si nécessaire.</Note>
|
||||
|
||||
## Principes de base de l’import CSV
|
||||
|
||||
Vous pouvez importer des données pour n’importe quel objet à l’aide de fichiers CSV, XLSX ou XLS. Chaque fichier doit contenir **un seul type d’objet** (par exemple, uniquement des enregistrements Personnes).
|
||||
|
||||
<Note>**Les champs doivent exister avant l’import.** Le téléversement d’un CSV crée des enregistrements, mais ne crée pas de champs. Si vous avez besoin de champs personnalisés, créez-les d’abord dans **Paramètres → Modèle de données**.</Note>
|
||||
|
||||
### Étapes
|
||||
|
||||
1. Accédez à l’objet dans lequel vous souhaitez importer des données
|
||||
2. Cliquez sur l’icône **⋮** en haut à droite (le menu Commande), puis cliquez sur **Importer des enregistrements**
|
||||
3. Téléchargez le fichier modèle pour garantir que vos données sont au format attendu
|
||||
4. Téléversez votre fichier CSV formaté
|
||||
5. Faites correspondre vos colonnes aux champs Twenty
|
||||
6. Passez en revue les erreurs (surlignées en jaune) et corrigez-les en les modifiant directement dans l’interface
|
||||
7. Confirmez l’importation
|
||||
|
||||
### Importer des relations entre objets
|
||||
|
||||
Vous pouvez importer des relations entre objets à l’aide de la fonction d’import CSV. Vous devez référencer l’objet lié en utilisant un champ unique de cet objet : l’`id`, l’`email` pour les Personnes et les membres de l’espace de travail, le `domain` pour les entreprises, tout autre champ défini comme unique dans le modèle de données pour tout autre objet.
|
||||
|
||||
<Note>**Les enregistrements supprimés comptent pour l’unicité.** Les enregistrements supprimés de façon logique (visibles dans le menu Commande → Voir les enregistrements supprimés) sont inclus dans les vérifications d’unicité. Si vous importez un enregistrement ayant la même valeur unique qu’un enregistrement supprimé, l’enregistrement supprimé sera restauré.</Note>
|
||||
|
||||
<Warning>
|
||||
**L’ordre d’importation est important !**
|
||||
|
||||
Lors de l’import d’objets liés, téléversez les fichiers dans cet ordre :
|
||||
|
||||
1. **Entreprises** d’abord (le côté "un" des relations)
|
||||
2. **Personnes** en second (liées aux entreprises via companyId)
|
||||
3. **Opportunités** en troisième (liées aux entreprises/personnes)
|
||||
4. **Objets personnalisés** avec relations en dernier
|
||||
|
||||
Pourquoi ? Le côté "un" d’une relation un-à-plusieurs doit exister avant que vous puissiez y faire référence. Par exemple, l’enregistrement Entreprise doit exister avant d’importer une Personne avec l’ID de cette entreprise.
|
||||
</Warning>
|
||||
|
||||
Veuillez consulter [cet article](/l/fr/user-guide/data-migration/how-tos/import-relations-between-objects-via-csv) pour un guide étape par étape sur la procédure à suivre.
|
||||
|
||||
## Exporter des données
|
||||
|
||||
Exportez les données de votre espace de travail pour des sauvegardes, des rapports ou des migrations.
|
||||
|
||||
### Étapes
|
||||
|
||||
1. Accédez à l’objet que vous souhaitez exporter
|
||||
2. Configurez la vue avec les colonnes dont vous avez besoin
|
||||
3. Cliquez sur **⋮** → **Exporter la vue**
|
||||
4. Enregistrez le fichier CSV
|
||||
|
||||
<Note>**Seules les colonnes visibles sont exportées.** Le fichier CSV ne contiendra que les colonnes affichées dans votre vue actuelle. Ajoutez ou masquez des colonnes avant l’exportation pour contrôler les données incluses.</Note>
|
||||
|
||||
<Note>**Limites d’exportation** : jusqu’à 20 000 enregistrements par export.</Note>
|
||||
|
||||
## Autorisations
|
||||
|
||||
L’import et l’export de données nécessitent des autorisations spécifiques :
|
||||
|
||||
* **Importation** : nécessite l’autorisation "Import CSV"
|
||||
* **Exportation** : nécessite l’autorisation "Export CSV"
|
||||
|
||||
Contactez l’administrateur de votre espace de travail si vous n’avez pas ces autorisations.
|
||||
|
||||
## Prochaines étapes
|
||||
|
||||
* [Préparez vos fichiers CSV](/l/fr/user-guide/data-migration/how-tos/prepare-your-csv-files)
|
||||
* [Importer des relations entre objets](/l/fr/user-guide/data-migration/how-tos/import-relations-between-objects-via-csv)
|
||||
* [Importer via API pour de grands ensembles de données](/l/fr/user-guide/data-migration/how-tos/import-data-via-api)
|
||||
@@ -0,0 +1,122 @@
|
||||
---
|
||||
title: Champs
|
||||
description: Comprendre le rôle des champs et comment les gérer.
|
||||
---
|
||||
|
||||
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
|
||||
|
||||
## À propos des Champs
|
||||
|
||||
Les champs sont comme les colonnes d'une feuille de calcul. Ils stockent différents types de données comme du texte, des chiffres ou des dates. Les champs peuvent être standard (intégrés) ou personnalisés (ceux que vous créez).
|
||||
|
||||
### Champs Standards
|
||||
|
||||
Les champs standard sont intégrés à Twenty pour répondre aux besoins professionnels courants.
|
||||
|
||||
Par exemple, `Prénom` et `Nom de famille` sont des champs standards dans l'objet `Personnes`. Ils stockent des données textuelles pour les noms individuels.
|
||||
|
||||
Vous ne pouvez pas supprimer les champs standard, mais vous pouvez les désactiver si vous n'en avez pas besoin.
|
||||
|
||||
Vous pouvez également personnaliser les options des champs standard de type `SELECT`, par exemple les options pour l’`Étape` des Opportunités.
|
||||
|
||||
<img src="/images/user-guide/fields/standard-fields.png" style={{width:'100%'}} />
|
||||
|
||||
### Champs Personnalisés
|
||||
|
||||
Les champs personnalisés peuvent être ajoutés à n'importe quel objet. Vous pouvez stocker du texte, des chiffres, des dates, des sélections déroulantes, et plus encore. Utilisez les champs personnalisés pour suivre des informations spécifiques à votre entreprise.
|
||||
|
||||
Par exemple, un champ personnalisé pour SpaceX pourrait être `Statut Actif de la Fusée`, indiquant si une fusée est opérationnelle.
|
||||
|
||||
<img src="/images/user-guide/fields/custom-fields.png" style={{width:'100%'}} />
|
||||
|
||||
## Types de champs
|
||||
|
||||
Twenty prend en charge divers types de champs :
|
||||
|
||||
| Type | Description | Exemple |
|
||||
| ------------------ | ----------------------------------------------------------- | -------------------------- |
|
||||
| Adresse | Adresse structurée avec rue, ville, état, pays, code postal | Adresse du bureau |
|
||||
| Tableau | Liste de valeurs textuelles | Étiquettes |
|
||||
| Booléen | Case à cocher vrai/faux | Actif |
|
||||
| Devise | Valeur monétaire avec code de devise | Montant de l’affaire (USD) |
|
||||
| Date | Valeurs de date | Date de clôture |
|
||||
| Date et heure | Date avec heure | Heure de réunion |
|
||||
| Domaine | Domaine de site web (utilisé pour les Entreprises) | acme.com |
|
||||
| E-mail | Adresses e-mail (principale + supplémentaires) | Adresse e-mail du contact |
|
||||
| JSON | Données JSON structurées | Métadonnées personnalisées |
|
||||
| Liens | URL avec libellés (primaire + secondaire) | Site web, LinkedIn |
|
||||
| Texte long | Texte sur plusieurs lignes | Description, Notes |
|
||||
| Sélection multiple | Choix multiples à partir d’une liste prédéfinie | Étiquettes, Catégories |
|
||||
| Nombre | Valeurs numériques (entières ou décimales) | Quantité, Score |
|
||||
| Téléphone | Numéros de téléphone avec indicatif du pays | Téléphone professionnel |
|
||||
| Note | Notation par étoiles (1-5) | Priorité, Score |
|
||||
| Relation | Liens vers des enregistrements dans d’autres objets | Entreprise → Personnes |
|
||||
| Sélection | Choix unique à partir d’une liste prédéfinie | Étape, Statut |
|
||||
| Texte | Texte sur une seule ligne | Nom, Titre |
|
||||
|
||||
## Créer un Champ Personnalisé
|
||||
|
||||
Pour ajouter un champ personnalisé à n'importe quel objet, suivez ces étapes:
|
||||
|
||||
1. Allez dans les `Paramètres` dans la barre latérale gauche.
|
||||
2. Allez dans le `Modèle de Données`, puis sélectionnez l'objet que vous souhaitez personnaliser.
|
||||
3. Continuez en cliquant sur `Ajouter un champ`.
|
||||
4. Choisissez un nom de champ et un type qui répondent à vos exigences. Envisagez d'ajouter une description du champ pour une meilleure compréhension.
|
||||
|
||||
Votre nouveau champ est maintenant disponible dans les champs de l'application. Pour l'afficher sur une vue spécifique, cliquez sur le menu des options, puis sélectionnez `Champs`.
|
||||
|
||||
<VimeoEmbed videoId="927628219" title="Démonstration vidéo" />
|
||||
|
||||
**Méthode rapide:** Cliquez sur le bouton **+** en haut à droite de tout tableau d'objet, puis sélectionnez `Personnaliser les champs`. Cela vous amène directement aux paramètres du Modèle de Données.
|
||||
|
||||
<img src="/images/user-guide/fields/quick-new-field.png" style={{width:'100%'}} />
|
||||
|
||||
## Désactiver un champ
|
||||
|
||||
Vous pouvez désactiver un champ pour le masquer dans l'application sans perdre vos données. Considérez cela comme masquer le champ plutôt que de le supprimer.
|
||||
|
||||
Voici comment vous pouvez le faire:
|
||||
|
||||
1. Trouvez le champ que vous voulez désactiver dans vos paramètres d'objet.
|
||||
|
||||
2. Cliquez sur les trois points `⋮` à côté du champ pour ouvrir le menu.
|
||||
|
||||
3. Sélectionnez `Désactiver` dans le menu déroulant.
|
||||
|
||||
<img src="/images/user-guide/fields/deactivate-field.png" style={{width:'100%'}} />
|
||||
|
||||
Que se passe-t-il lorsque vous désactivez un champ?
|
||||
|
||||
1. **Dans l'application :** Le champ disparaît et vous ne pouvez plus ajouter de nouvelles valeurs.
|
||||
|
||||
2. **Relations existantes :** S'il s'agit d'un champ de relation, les connexions existantes restent mais vous ne pouvez pas en créer de nouvelles.
|
||||
|
||||
3. **Accès API :** Vous pouvez toujours accéder au champ et à ses données par l'API.
|
||||
|
||||
Vous pouvez réactiver les champs standards et personnalisés ou avoir l'option de les supprimer définitivement.
|
||||
|
||||
## Rendre les Champs Uniques
|
||||
|
||||
Rendez un champ unique pour garantir que des enregistrements distincts ne peuvent pas avoir la même valeur. Par exemple, les adresses e-mail sont uniques pour chaque personne.
|
||||
|
||||
Si vous obtenez une erreur lors de la définition de l'unicité, vérifiez les valeurs en double dans vos données (y compris les enregistrements supprimés).
|
||||
|
||||
## Bonnes pratiques de configuration des champs
|
||||
|
||||
### Conventions de nommage et limitations
|
||||
|
||||
* **Les noms au singulier et au pluriel doivent être distincts** : Notre API GraphQL nécessite des noms distincts pour les mutations
|
||||
* **Noms de champs protégés** : certains noms sont réservés à l’usage du système (par exemple, `Type`, `Application`)
|
||||
|
||||
### Champs de devise et de téléphone
|
||||
|
||||
* **Devise par défaut** : peut être configurée via le modèle de données
|
||||
* **Codes de pays par défaut** : peuvent être configurés pour les champs téléphoniques via le modèle de données
|
||||
|
||||
### Champs de sélection
|
||||
|
||||
* **Une option par défaut peut être sélectionnée** pour chaque champ de sélection
|
||||
|
||||
### Champs de texte d'enregistrement
|
||||
|
||||
* **Chaque objet a un champ d'affichage principal**: Ce champ apparaît dans la colonne la plus à gauche et représente l'enregistrement lorsqu'il est lié à d'autres objets. Il doit s'agir d'un champ de texte. Par exemple, le champ `Nom` est utilisé pour les Personnes comme champ principal, donc lorsque vous liez une personne à une entreprise, vous verrez son nom dans la vue de l'entreprise.
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: Personnalisez votre modèle de données},{
|
||||
description: Vue d’ensemble des options de personnalisation du modèle de données.
|
||||
---
|
||||
|
||||
Le modèle de données de Twenty est entièrement personnalisable. Créez des objets, des champs et des relations adaptés à votre entreprise.
|
||||
|
||||
## Liens rapides
|
||||
|
||||
| Je souhaite... | Guide |
|
||||
| ----------------------------- | ---------------------------------------------------------------------------------------------- |
|
||||
| Créer un nouvel objet | [Comment créer des objets personnalisés](/l/fr/user-guide/data-model/how-tos/create-custom-objects) |
|
||||
| Ajouter des champs à un objet | [Comment créer des champs personnalisés](/l/fr/user-guide/data-model/how-tos/create-custom-fields) |
|
||||
| Relier des objets entre eux | [Comment créer des champs de relation](/l/fr/user-guide/data-model/how-tos/create-relation-fields) |
|
||||
|
||||
## En savoir plus
|
||||
|
||||
* [Comprendre votre modèle de données](/l/fr/user-guide/data-model/overview) — concepts clés et conseils de planification
|
||||
* [Objets](/l/fr/user-guide/data-model/capabilities/objects) — objets standard et personnalisés
|
||||
* [Champs](/l/fr/user-guide/data-model/capabilities/fields) — tous les types de champs
|
||||
* [Champs de relation](/l/fr/user-guide/data-model/capabilities/relation-fields) — relier des objets
|
||||
* [FAQ sur le modèle de données](/l/fr/user-guide/data-model/how-tos/data-model-faq) — questions fréquentes
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: Autorisations et accès
|
||||
description: Gérez les rôles, les autorisations et le contrôle d’accès dans votre espace de travail.
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/permissions/permissions.png" alt="Autorisations et accès" />
|
||||
</Frame>
|
||||
|
||||
Le système d’autorisations de Twenty vous permet de contrôler qui peut accéder aux données de votre espace de travail et les modifier. Créez des rôles, attribuez des autorisations et configurez l’authentification unique (SSO) pour un accès sécurisé.
|
||||
|
||||
## Contenu de cette section
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Permissions" icon="lock" href="/l/fr/user-guide/permissions-access/capabilities/permissions">
|
||||
Créez des rôles et configurez les autorisations sur les objets, les champs et les paramètres.
|
||||
</Card>
|
||||
|
||||
<Card title="Configuration SSO" icon="clé" href="/l/fr/user-guide/permissions-access/capabilities/sso-configuration">
|
||||
Configurez l’authentification unique avec votre fournisseur d’identité.
|
||||
</Card>
|
||||
|
||||
<Card title="FAQ" icon="circle-question" href="/l/fr/user-guide/permissions-access/how-tos/permissions-faq">
|
||||
Questions fréquentes sur les rôles, les autorisations et l’authentification unique (SSO).
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Fonctionnalités clés
|
||||
|
||||
* **Accès basé sur les rôles** : Créez des rôles personnalisés avec des autorisations spécifiques
|
||||
* **Autorisations sur les objets** : Contrôlez qui peut afficher, modifier ou supprimer des enregistrements
|
||||
* **Autorisations sur les champs** : Restreignez l’accès aux champs sensibles
|
||||
* **Autorisations sur les paramètres** : Contrôlez l’accès à la configuration de l’espace de travail
|
||||
* **Intégration SSO** : Configurez l’authentification unique pour la sécurité d’entreprise (plan Organization)
|
||||
|
||||
## Liens rapides
|
||||
|
||||
* [Créer un rôle](/l/fr/user-guide/permissions-access/capabilities/permissions#create-a-role)
|
||||
* [Configurer le SSO](/l/fr/user-guide/permissions-access/capabilities/sso-configuration)
|
||||
* [Gérer les membres de l’équipe](/l/fr/user-guide/settings/capabilities/member-management)
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
---
|
||||
title: Restreindre l’accès à votre vue},{
|
||||
description: Contrôlez qui peut voir vos vues personnalisées.
|
||||
---
|
||||
|
||||
Chaque vue (sauf les vues par défaut "All [Object Name]") possède son propre paramètre de visibilité.
|
||||
|
||||
## Étapes
|
||||
|
||||
1. Ouvrez la vue que vous souhaitez restreindre
|
||||
2. Cliquez sur **Options** en haut à droite
|
||||
3. Cliquez sur **Visibilité**
|
||||
4. Sélectionnez **Non répertorié**
|
||||
|
||||
Votre vue est désormais visible uniquement par vous.
|
||||
|
||||
## Options de visibilité
|
||||
|
||||
| Paramètre | Qui peut voir |
|
||||
| --------------------- | --------------------------------------- |
|
||||
| **Espace de travail** | Tous les membres de l’espace de travail |
|
||||
| **Non répertorié** | Vous uniquement |
|
||||
|
||||
## Notes
|
||||
|
||||
* Les vues par défaut "All [Object Name]" ne peuvent pas être rendues non répertoriées
|
||||
* Les vues non répertoriées n’apparaissent pas dans les menus déroulants de vues des autres utilisateurs
|
||||
* Vous pouvez à tout moment rétablir la visibilité sur l’espace de travail
|
||||
|
||||
## Articles connexes
|
||||
|
||||
* [Paramètres des vues](/l/fr/user-guide/views-pipelines/capabilities/view-settings) — toutes les options de configuration des vues
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
---
|
||||
title: Afficher le Montant attendu dans votre pipeline},{
|
||||
description: Calculez et affichez les valeurs d'affaire pondérées en fonction de la probabilité de l'étape.
|
||||
---
|
||||
|
||||
Le Montant attendu est une valeur calculée : **Montant × Probabilité**. Cela vous aide à prévoir les revenus en pondérant les affaires selon leur probabilité de conclusion.
|
||||
|
||||
<Tip>
|
||||
Ceci est un exemple de création de [Champs de formule](/l/fr/user-guide/workflows/how-tos/crm-automations/formula-fields) à l'aide de workflows.
|
||||
</Tip>
|
||||
|
||||
Ce guide vous explique comment configurer les champs personnalisés et les workflows nécessaires pour calculer et afficher les montants attendus dans votre pipeline.
|
||||
|
||||
## Étape 1 : Créer des champs personnalisés
|
||||
|
||||
Vous avez besoin de deux champs personnalisés sur l'objet Opportunités.
|
||||
|
||||
### Créer le champ Probabilité
|
||||
|
||||
1. Allez dans **Paramètres → Modèle de données → Opportunités**
|
||||
2. Cliquez sur **+ Ajouter un champ**
|
||||
3. Configurer:
|
||||
* **Nom** : Probabilité
|
||||
* **Type** : Nombre
|
||||
* **Description** : Probabilité selon l'étape (0–100 %)
|
||||
4. Cliquez sur **Enregistrer**
|
||||
|
||||
### Créer le champ Montant attendu
|
||||
|
||||
1. Cliquez sur **+ Ajouter un champ**
|
||||
2. Configurer:
|
||||
* **Nom** : Montant attendu
|
||||
* **Type** : Devise
|
||||
* **Description** : Calculé : Montant × Probabilité
|
||||
3. Cliquez sur **Enregistrer**
|
||||
|
||||
### Facultatif : rendre les champs en lecture seule pour les utilisateurs
|
||||
|
||||
Si vous ne souhaitez pas que les utilisateurs modifient manuellement ces champs calculés :
|
||||
|
||||
1. Allez dans **Paramètres → Rôles**
|
||||
2. Sélectionnez le rôle à configurer
|
||||
3. Recherchez l'objet Opportunités
|
||||
4. Définissez les champs **Probabilité** et **Montant attendu** en lecture seule
|
||||
|
||||
Cela garantit que seuls les workflows peuvent mettre à jour ces valeurs.
|
||||
|
||||
## Étape 2 : Créer le workflow n° 1 — Mettre à jour la probabilité lors d'un changement d'étape
|
||||
|
||||
Ce workflow définit automatiquement la Probabilité lorsqu'une opportunité passe à une nouvelle étape.
|
||||
|
||||
### Créer le workflow
|
||||
|
||||
1. Allez dans **Workflows**
|
||||
2. Cliquez sur **+ Nouveau workflow**
|
||||
3. Nommez-le "Mettre à jour la probabilité lors d'un changement d'étape"
|
||||
|
||||
### Configurer le déclencheur
|
||||
|
||||
1. Ajoutez un déclencheur **Enregistrement créé ou mis à jour**
|
||||
2. Sélectionnez **Opportunités** comme objet
|
||||
3. Filtrer sur : le champ **Étape** est mis à jour
|
||||
|
||||
### Ajouter des branches pour chaque étape
|
||||
|
||||
Créez une branche pour chaque étape avec sa probabilité :
|
||||
|
||||
| Étape | Probabilité |
|
||||
| ------------- | ----------- |
|
||||
| Nouveau | 10 % |
|
||||
| Qualifié | 25 % |
|
||||
| Réunion | 40 % |
|
||||
| Proposition | 60 % |
|
||||
| Négociation | 80 % |
|
||||
| Fermée gagnée | 100 % |
|
||||
| Fermée perdue | 0% |
|
||||
|
||||
<Note>
|
||||
Pour créer une nouvelle branche, cliquez avec le bouton droit sur la zone de travail du workflow et cliquez sur **Nouvelle action**. Puis, reliez cette action au nœud précédent en faisant glisser la flèche du nœud précédent vers cette nouvelle action.
|
||||
</Note>
|
||||
|
||||
Pour chaque étape :
|
||||
|
||||
1. Ajoutez un nœud **Filtre** : Étape = [nom de l'étape]
|
||||
2. Ajoutez une action **Mettre à jour l'enregistrement** :
|
||||
* Enregistrement : l'opportunité déclenchante
|
||||
* Champ : Probabilité
|
||||
* Valeur : [probabilité pour cette étape]
|
||||
|
||||
### Calculer le montant attendu
|
||||
|
||||
Après la jonction des branches :
|
||||
|
||||
1. Ajoutez un nœud **Filtre** : le champ Montant n'est pas vide
|
||||
2. Ajoutez une action **Mettre à jour l'enregistrement** :
|
||||
* Enregistrement : l'opportunité déclenchante
|
||||
* Champ : Montant attendu
|
||||
* Valeur : Montant × Probabilité
|
||||
|
||||
## Étape 3 : Créer le workflow n° 2 — Recalculer lors d'une modification du montant
|
||||
|
||||
Ce workflow met à jour le Montant attendu lorsque le Montant de l'affaire change.
|
||||
|
||||
### Créer le workflow
|
||||
|
||||
1. Allez dans **Workflows**
|
||||
2. Cliquez sur **+ Nouveau workflow**
|
||||
3. Nommez-le "Recalculer le Montant attendu lors d'une modification du Montant"
|
||||
|
||||
### Configurer le déclencheur
|
||||
|
||||
1. Ajoutez un déclencheur **Enregistrement créé ou mis à jour**
|
||||
2. Sélectionnez **Opportunités** comme objet
|
||||
3. Filtrer sur : le champ **Montant** est mis à jour
|
||||
|
||||
### Ajouter la logique
|
||||
|
||||
1. Ajoutez un nœud **Filtre** : le champ Montant n'est pas vide
|
||||
2. Ajoutez une action **Mettre à jour l'enregistrement** :
|
||||
* Enregistrement : l'opportunité déclenchante
|
||||
* Champ : Montant attendu
|
||||
* Valeur : Montant × Probabilité
|
||||
|
||||
## Étape 4 : Afficher dans votre pipeline
|
||||
|
||||
Affichez maintenant les totaux de Montant attendu dans votre vue Kanban :
|
||||
|
||||
1. Ouvrez votre vue Kanban **Pipeline des ventes**
|
||||
2. Cliquez sur le **nombre** à côté de n'importe quel nom d'étape en haut d'une colonne
|
||||
3. Sélectionnez **Somme**
|
||||
4. Choisissez **Montant attendu**
|
||||
|
||||
Chaque colonne affiche maintenant la valeur totale pondérée du pipeline pour cette étape.
|
||||
|
||||
## Résumé
|
||||
|
||||
| Composant | Objectif |
|
||||
| ------------------------- | ----------------------------------------------------------------------------------- |
|
||||
| **Champ Probabilité** | Stocke la probabilité de gain selon l'étape |
|
||||
| **Champ Montant attendu** | Stocke Montant × Probabilité |
|
||||
| **Workflow n° 1** | Met à jour la Probabilité lorsque l'Étape change, puis recalcule le Montant attendu |
|
||||
| **Workflow n° 2** | Recalcule le Montant attendu lorsque le Montant change |
|
||||
| **Agrégation** | Affiche la somme du Montant attendu par étape |
|
||||
|
||||
## Connexes
|
||||
|
||||
* [Champs de formule](/l/fr/user-guide/workflows/how-tos/crm-automations/formula-fields) — créer des champs calculés à l'aide de workflows
|
||||
* [Vues Kanban](/l/fr/user-guide/views-pipelines/capabilities/kanban-views) — agrégations de colonnes
|
||||
* [Comment créer des champs personnalisés](/l/fr/user-guide/data-model/how-tos/create-custom-fields) — configuration des champs
|
||||
@@ -0,0 +1,231 @@
|
||||
---
|
||||
title: Suivre la durée pendant laquelle les opportunités restent à chaque étape
|
||||
description: Surveillez la vélocité des affaires en suivant le moment où les opportunités entrent dans chaque étape.
|
||||
---
|
||||
|
||||
<Tip>
|
||||
Voici un exemple de création de [champs de formule](/l/fr/user-guide/workflows/how-tos/crm-automations/formula-fields) à l'aide de Workflows — plus précisément des calculs de dates.
|
||||
</Tip>
|
||||
|
||||
Suivre le moment où les opportunités entrent dans chaque étape vous aide à identifier les goulots d'étranglement et à mesurer la vélocité des affaires.
|
||||
|
||||
Ce guide vous explique comment configurer des champs personnalisés et un workflow pour enregistrer automatiquement le moment où une opportunité passe à chaque étape, et calculer le nombre de jours passés dans l'étape précédente.
|
||||
|
||||
## Étape 1 : Créez des champs personnalisés
|
||||
|
||||
Vous avez besoin de deux types de champs pour chaque étape :
|
||||
|
||||
* **Champs Date et heure** : Enregistrer le moment où l'opportunité est entrée dans chaque étape
|
||||
* **Champs numériques** : Stocker le nombre de jours que l'opportunité a passés dans chaque étape
|
||||
|
||||
### Créez les champs "Dernière entrée"
|
||||
|
||||
1. Allez dans **Paramètres → Modèle de données → Opportunités**
|
||||
2. Pour chaque étape, cliquez sur **+ Ajouter un champ** et configurez :
|
||||
* **Nom** : Dernière entrée [Nom de l'étape] (par exemple, "Dernière entrée Nouveau", "Dernière entrée Qualifiée")
|
||||
* **Type** : Date et heure
|
||||
* **Description** : Horodatage du moment où l'opportunité est entrée dans cette étape
|
||||
3. Cliquez sur **Enregistrer**
|
||||
|
||||
Créez ces champs :
|
||||
|
||||
* Dernière entrée Nouveau
|
||||
* Dernière entrée Qualifiée
|
||||
* Dernière entrée Réunion
|
||||
* Dernière entrée Proposition
|
||||
* Dernière entrée Négociation
|
||||
* Dernière entrée Clôturée gagnée
|
||||
* Dernière entrée Clôturée perdue
|
||||
|
||||
### Créez les champs "Jours dans l'étape"
|
||||
|
||||
1. Pour chaque étape, cliquez sur **+ Ajouter un champ** et configurez :
|
||||
* **Nom** : Jours dans [Nom de l'étape] (par exemple, "Jours dans Nouveau", "Jours dans Qualifiée")
|
||||
* **Type** : Numérique
|
||||
* **Description** : Nombre de jours passés dans cette étape
|
||||
2. Cliquez sur **Enregistrer**
|
||||
|
||||
Créez ces champs :
|
||||
|
||||
* Jours dans Nouveau
|
||||
* Jours dans Qualifiée
|
||||
* Jours dans Réunion
|
||||
* Jours dans Proposition
|
||||
* Jours dans Négociation
|
||||
|
||||
<Note>
|
||||
Vous n'avez pas besoin de champs "Jours dans" pour Clôturée gagnée et Clôturée perdue, car ce sont des étapes finales.
|
||||
</Note>
|
||||
|
||||
### Facultatif : rendre les champs en lecture seule
|
||||
|
||||
Si vous ne souhaitez pas que les utilisateurs modifient manuellement ces champs calculés :
|
||||
|
||||
1. Allez dans **Paramètres → Rôles**
|
||||
2. Sélectionnez le rôle à configurer
|
||||
3. Trouvez l'objet Opportunités
|
||||
4. Définissez les champs "Dernière entrée" et "Jours dans" en lecture seule
|
||||
|
||||
## Étape 2 : Créez le workflow
|
||||
|
||||
Ce seul workflow gère les deux tâches :
|
||||
|
||||
* Enregistre l'horodatage lors de l'entrée dans une nouvelle étape
|
||||
* Calcule le nombre de jours passés dans l'étape précédente
|
||||
|
||||
### Créez le workflow
|
||||
|
||||
1. Allez dans **Workflows**
|
||||
2. Cliquez sur **+ Nouveau workflow**
|
||||
3. Nommez-le "Suivre le temps par étape"
|
||||
|
||||
### Configurez le déclencheur
|
||||
|
||||
1. Ajoutez un déclencheur **Enregistrement mis à jour**
|
||||
2. Sélectionnez **Opportunités** comme objet
|
||||
3. Filtrer sur : le champ **Étape** est mis à jour
|
||||
|
||||
### Ajoutez des branches pour chaque étape
|
||||
|
||||
<Note>
|
||||
Pour créer une nouvelle branche, faites un clic droit sur le canevas du workflow et cliquez sur **Nouvelle action**. Ensuite, reliez cette action au nœud précédent en faisant glisser la flèche du nœud précédent vers cette nouvelle action.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
**Branche 1 : Étape = Nouveau (première étape)**
|
||||
|
||||
Comme il s'agit de la première étape, nous enregistrons uniquement l'horodatage d'entrée — aucune étape précédente à calculer.
|
||||
|
||||
1. Ajoutez un nœud **Filtre** : Étape = Nouveau
|
||||
2. Ajoutez une action **Code** :
|
||||
|
||||
```javascript
|
||||
export const main = async (): Promise<object> => {
|
||||
return { now: new Date().toISOString() };
|
||||
};
|
||||
```
|
||||
|
||||
3. Ajoutez une action **Mettre à jour l'enregistrement** :
|
||||
* Enregistrement : l'opportunité déclenchante
|
||||
* Champ : Dernière entrée Nouveau
|
||||
* Valeur : `now` depuis le nœud Code
|
||||
|
||||
---
|
||||
|
||||
**Branche 2 : Étape = Qualifiée**
|
||||
|
||||
Lors du passage à Qualifiée, enregistrez l'heure d'entrée ET calculez les jours passés dans Nouveau.
|
||||
|
||||
1. Ajoutez un nœud **Filtre** : Étape = Qualifiée
|
||||
2. Ajoutez une action **Code** :
|
||||
|
||||
```javascript
|
||||
export const main = async (params: {
|
||||
lastEnteredPreviousStage: Date;
|
||||
}): Promise<object> => {
|
||||
const { lastEnteredPreviousStage } = params;
|
||||
|
||||
const now = new Date();
|
||||
const entryDate = new Date(lastEnteredPreviousStage);
|
||||
const diffTime = Math.abs(now.getTime() - entryDate.getTime());
|
||||
const daysInPreviousStage = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||
|
||||
return {
|
||||
now: now.toISOString(),
|
||||
daysInPreviousStage: daysInPreviousStage
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
3. Configurez l'entrée du nœud Code : associez `lastEnteredPreviousStage` au champ **Dernière entrée Nouveau**
|
||||
4. Ajoutez une action **Mettre à jour l'enregistrement** :
|
||||
* Enregistrement : l'opportunité déclenchante
|
||||
* Champs à mettre à jour:
|
||||
* Dernière entrée Qualifiée = `now`
|
||||
* Jours dans Nouveau = `daysInPreviousStage`
|
||||
|
||||
---
|
||||
|
||||
**Branche 3 : Étape = Réunion**
|
||||
|
||||
Lors du passage à Réunion, enregistrez l'heure d'entrée ET calculez les jours passés dans Qualifiée.
|
||||
|
||||
1. Ajoutez un nœud **Filtre** : Étape = Réunion
|
||||
2. Ajoutez une action **Code** :
|
||||
|
||||
```javascript
|
||||
export const main = async (params: {
|
||||
lastEnteredPreviousStage: Date;
|
||||
}): Promise<object> => {
|
||||
const { lastEnteredPreviousStage } = params;
|
||||
|
||||
const now = new Date();
|
||||
const entryDate = new Date(lastEnteredPreviousStage);
|
||||
const diffTime = Math.abs(now.getTime() - entryDate.getTime());
|
||||
const daysInPreviousStage = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||
|
||||
return {
|
||||
now: now.toISOString(),
|
||||
daysInPreviousStage: daysInPreviousStage
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
3. Configurez l'entrée du nœud Code : associez `lastEnteredPreviousStage` au champ **Dernière entrée Qualifiée**
|
||||
4. Ajoutez une action **Mettre à jour l'enregistrement** :
|
||||
* Enregistrement : l'opportunité déclenchante
|
||||
* Champs à mettre à jour:
|
||||
* Dernière entrée Réunion = `now`
|
||||
* Jours dans Qualifiée = `daysInPreviousStage`
|
||||
|
||||
---
|
||||
|
||||
**Continuez pour les étapes restantes :**
|
||||
|
||||
| Étape | Enregistrements | Calcule |
|
||||
| --------------- | ------------------------------- | ---------------------- |
|
||||
| Proposition | Dernière entrée Proposition | Jours dans Réunion |
|
||||
| Négociation | Dernière entrée Négociation | Jours dans Proposition |
|
||||
| Clôturée gagnée | Dernière entrée Clôturée gagnée | Jours dans Négociation |
|
||||
| Clôturée perdue | Dernière entrée Clôturée perdue | Jours dans Négociation |
|
||||
|
||||
Les branches n'ont pas besoin de se rejoindre — chacune s'exécute indépendamment lorsque sa condition d'étape est remplie.
|
||||
|
||||
## Étape 3 : Analyser le temps par étape
|
||||
|
||||
Avec les horodatages et les comptages de jours enregistrés, vous pouvez désormais analyser la vélocité des affaires.
|
||||
|
||||
### Créez une vue "Affaires lentes"
|
||||
|
||||
1. Créez une vue Tableau des Opportunités
|
||||
2. Ajoutez des colonnes : Nom, Étape, Jours dans [étape précédente], Montant
|
||||
3. Triez par le champ "Jours dans" (décroissant)
|
||||
4. Filtrez par Étape pour vous concentrer sur une étape à la fois
|
||||
|
||||
Les affaires en haut ont passé le plus de temps dans l'étape précédente.
|
||||
|
||||
### Utilisez les agrégations
|
||||
|
||||
Dans votre vue Kanban du pipeline :
|
||||
|
||||
1. Cliquez sur le nombre à côté du nom d'une étape
|
||||
2. Sélectionnez **Moyenne**
|
||||
3. Choisissez un champ "Jours dans"
|
||||
|
||||
Cela affiche le temps moyen que les affaires passent à chaque étape.
|
||||
|
||||
## Résumé
|
||||
|
||||
| Composant | Objectif |
|
||||
| ------------------------------ | -------------------------------------------------------------------------- |
|
||||
| **Champs « Dernière entrée »** | Stockent le moment où l'opportunité est entrée dans chaque étape |
|
||||
| **Champs « Jours dans »** | Stockent le nombre de jours passés dans chaque étape |
|
||||
| **Workflow** | Enregistre l'horodatage ET calcule les jours en un seul passage |
|
||||
| **Vues et agrégations** | Analysez la vélocité des affaires et identifiez les goulots d'étranglement |
|
||||
|
||||
## Connexes
|
||||
|
||||
* [Workflows](/l/fr/user-guide/workflows/overview) — principes de l'automatisation
|
||||
* [Comment créer des champs personnalisés](/l/fr/user-guide/data-model/how-tos/create-custom-fields) — configuration des champs
|
||||
* [Vues Kanban](/l/fr/user-guide/views-pipelines/capabilities/kanban-views) — agrégations
|
||||
@@ -0,0 +1,137 @@
|
||||
---
|
||||
title: Vues et Pipelines
|
||||
description: Découvrez comment créer et gérer des vues dans Twenty.
|
||||
image: /images/user-guide/table-views/table.png
|
||||
---
|
||||
|
||||
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/table-views/table.png" alt="Vues et Pipelines" />
|
||||
</Frame>
|
||||
|
||||
## Comprendre les vues
|
||||
|
||||
Les vues sont des configurations enregistrées qui déterminent la façon dont vos données sont affichées. Chaque vue peut avoir ses propres :
|
||||
|
||||
* **Disposition** : Tableau, Kanban ou Calendrier
|
||||
* **Filtres** : Quels enregistrements afficher
|
||||
* **Tri** : Comment les enregistrements sont classés
|
||||
* **Champs** : Quelles colonnes sont visibles
|
||||
|
||||
## Types de vue
|
||||
|
||||
### Vue Tableau
|
||||
|
||||
La vue par défaut de type tableur affichant les enregistrements en lignes avec des colonnes personnalisables.
|
||||
|
||||
### Vue Kanban
|
||||
|
||||
Une vue tableau visuelle où les enregistrements apparaissent sous forme de cartes organisées par étapes. Idéal pour :
|
||||
|
||||
* Pipelines de vente
|
||||
* Suivi de projets
|
||||
* Tout flux de travail avec des étapes définies
|
||||
|
||||
### Vue Calendrier
|
||||
|
||||
Affiche les enregistrements possédant des champs de date sur un calendrier. Parfait pour :
|
||||
|
||||
* Réunions et événements
|
||||
* Échéances et dates limites
|
||||
* Planification basée sur le temps
|
||||
|
||||
## Création d'une Vue
|
||||
|
||||
Il y a deux manières de créer une nouvelle vue.
|
||||
|
||||
### Utilisez le menu déroulant de la vue
|
||||
|
||||
1. Accédez à n'importe quel objet (Personnes, Entreprises, etc.)
|
||||
2. Cliquez sur le nom de la vue en haut à gauche (affiche la vue actuelle avec une flèche déroulante)
|
||||
3. Cliquez sur **+ Ajouter une vue**
|
||||
4. Nommez votre vue et cliquez sur **Créer**
|
||||
5. Choisissez une disposition (Tableau, Kanban ou Calendrier) sous **Options**
|
||||
6. Ajoutez des filtres et un tri selon vos besoins
|
||||
7. Sélectionnez les champs à afficher et réorganisez-les
|
||||
8. Cliquez sur **Enregistrer**
|
||||
|
||||
<VimeoEmbed videoId="1145648745" title="Démonstration vidéo" />
|
||||
|
||||
### Commencez par modifier une vue existante
|
||||
|
||||
1. Accédez à n'importe quel objet (Personnes, Entreprises, etc.)
|
||||
2. Choisissez une disposition (Tableau, Kanban ou Calendrier) sous **Options** ou ajoutez des filtres et un tri selon vos besoins
|
||||
3. Cliquez sur **Enregistrer comme nouvelle vue**
|
||||
4. Nommez votre vue et cliquez sur **Créer**
|
||||
5. Continuez à modifier votre nouvelle vue
|
||||
6. Cliquez sur **Mettre à jour la vue** pour enregistrer vos configurations supplémentaires
|
||||
|
||||
<VimeoEmbed videoId="1145654005" title="Démonstration vidéo" />
|
||||
|
||||
## Gérer les vues
|
||||
|
||||
### Modifier une vue
|
||||
|
||||
1. Sélectionnez la vue dans le menu déroulant
|
||||
2. Apportez vos modifications (filtres, tri, colonnes)
|
||||
3. Cliquez sur **Enregistrer** pour mettre à jour la vue
|
||||
|
||||
### Renommer une vue ou changer son icône
|
||||
|
||||
1. Ouvrez le menu déroulant de la vue
|
||||
2. Cliquez sur le menu **⋮** à côté du nom de la vue
|
||||
3. Sélectionnez **Modifier**
|
||||
4. Modifiez le nom ou l'icône
|
||||
5. Cliquez sur **Enregistrer**
|
||||
|
||||
### Réorganiser les vues
|
||||
|
||||
1. Ouvrez le menu déroulant de la vue
|
||||
2. Cliquez et faites glisser une vue par sa poignée
|
||||
3. Déposez-la à l'emplacement souhaité
|
||||
4. Le nouvel ordre est enregistré automatiquement
|
||||
|
||||
### Ajouter aux favoris
|
||||
|
||||
Épinglez les vues fréquemment utilisées pour un accès rapide :
|
||||
|
||||
1. Ouvrez le menu déroulant de la vue
|
||||
2. Cliquez sur le menu **⋮** à côté d'une vue
|
||||
3. Sélectionnez **Ajouter aux favoris**
|
||||
4. La vue apparaît dans votre section Favoris
|
||||
|
||||
### Supprimer une vue
|
||||
|
||||
1. Sélectionnez la vue à supprimer
|
||||
2. Cliquez sur le menu déroulant de la vue
|
||||
3. Cliquez sur le menu **⋮** à côté de la vue
|
||||
4. Sélectionnez **Supprimer**
|
||||
5. Confirmez la suppression
|
||||
|
||||
<Warning>
|
||||
Les vues supprimées ne peuvent pas être récupérées. Assurez-vous de vouloir la supprimer avant de confirmer.
|
||||
</Warning>
|
||||
|
||||
## Visibilité de la vue
|
||||
|
||||
Chaque vue (sauf les vues par défaut "Tous les [Nom de l'objet]") possède son propre paramètre de visibilité.
|
||||
|
||||
Pour modifier la visibilité :
|
||||
|
||||
1. Ouvrez la vue
|
||||
2. Cliquez sur **Options → Visibilité**
|
||||
3. Choisissez :
|
||||
* **Espace de travail** : Visible par tous les membres de l'espace de travail
|
||||
* **Non répertoriée** : Visible uniquement par vous
|
||||
|
||||
<Note>
|
||||
La visibilité des vues par défaut "Tous les [Nom de l'objet]" ne peut pas être modifiée.
|
||||
</Note>
|
||||
|
||||
## Prochaines étapes
|
||||
|
||||
* [Vues de Tableau](/l/fr/user-guide/views-pipelines/capabilities/table-views)
|
||||
* [Vues Kanban](/l/fr/user-guide/views-pipelines/capabilities/kanban-views)
|
||||
* [Filtres et tri](/l/fr/user-guide/views-pipelines/capabilities/filters-and-sorting)
|
||||
* [Paramètres de la vue](/l/fr/user-guide/views-pipelines/capabilities/view-settings)
|
||||
@@ -0,0 +1,66 @@
|
||||
---
|
||||
title: Branches de flux de travail},{
|
||||
description: Créez des chemins parallèles et une logique conditionnelle dans vos flux de travail.
|
||||
---
|
||||
|
||||
Les branches vous permettent de scinder votre flux de travail en plusieurs chemins pouvant s’exécuter simultanément ou de façon conditionnelle selon vos données.
|
||||
|
||||
<img src="/images/user-guide/workflows/branches.png" style={{width:'100%'}} />
|
||||
|
||||
## Fonctionnement des branches
|
||||
|
||||
Lorsque vous créez plusieurs connexions à partir d’un seul nœud, chaque chemin devient une branche. Par défaut, **toutes les branches s’exécutent en parallèle** — elles ne s’attendent pas les unes les autres.
|
||||
|
||||
## Création de branches
|
||||
|
||||
### Ajouter une nouvelle branche
|
||||
|
||||
1. **Cliquez avec le bouton droit sur la zone de travail principale** du flux de travail (et non sur un nœud existant)
|
||||
2. Cliquez sur **Ajouter un nœud**
|
||||
3. Choisissez le type de nœud pour votre nouvelle branche
|
||||
4. Faites glisser une flèche depuis le bas de l’étape précédente vers le haut de cette nouvelle action
|
||||
5. Répétez pour ajouter d’autres branches à partir du même nœud
|
||||
|
||||
<Note>
|
||||
Chaque branche est indépendante. L’ajout d’une branche n’affecte pas les autres chemins existants à partir de ce nœud.
|
||||
</Note>
|
||||
|
||||
### Disposition visuelle
|
||||
|
||||
Les branches apparaissent comme des chemins parallèles dans l’éditeur de flux de travail. Vous pouvez faire glisser les nœuds pour réorganiser la disposition visuelle sans affecter l’exécution.
|
||||
|
||||
## Branches conditionnelles
|
||||
|
||||
Puisque toutes les branches s’exécutent par défaut, utilisez des nœuds **Filtre** pour contrôler quels chemins s’exécutent réellement :
|
||||
|
||||
| Branche | Condition de filtre | Action |
|
||||
| ------- | --------------------- | ---------------------------------- |
|
||||
| A | Étape = "Gagnée" | Envoyer un e-mail de félicitations |
|
||||
| B | Étape = "Perdue" | Créer une tâche de suivi |
|
||||
| C | Étape = "Négociation" | Notifier le responsable |
|
||||
|
||||
1. Créez des branches à partir de votre déclencheur ou action
|
||||
2. Ajoutez un nœud **Filtre** comme première étape de chaque branche
|
||||
3. Configurez chaque filtre avec des conditions mutuellement exclusives
|
||||
4. Ajoutez vos actions après chaque filtre
|
||||
|
||||
Seules les branches pour lesquelles la condition du filtre est remplie poursuivront l’exécution.
|
||||
|
||||
## Fusion de branches
|
||||
|
||||
**Les branches ne fusionnent pas automatiquement.** Chaque branche s’exécute indépendamment jusqu’à sa fin. Vous avez toute latitude quant à la manière de gérer cela :
|
||||
|
||||
* **Option 1 : Garder les branches séparées**
|
||||
Chaque branche gère ses propres actions de suivi de manière indépendante. C’est l’approche la plus simple lorsque les branches n’ont pas besoin de converger.
|
||||
|
||||
* **Option 2 : Fusionner les branches manuellement**
|
||||
Lors de la création de votre flux de travail, vous pouvez connecter manuellement plusieurs branches à la même action en aval. Il suffit de faire glisser des flèches depuis la fin de chaque branche vers un nœud commun.
|
||||
|
||||
<Note>
|
||||
Bien que vous puissiez utiliser un nœud [Delay](/l/fr/user-guide/workflows/capabilities/workflow-actions#delay) pour mettre l’exécution en pause, il n’est pas actuellement configurable pour attendre "jusqu’à ce qu’une autre branche se termine".
|
||||
</Note>
|
||||
|
||||
## Contenus associés
|
||||
|
||||
* [Comment utiliser les branches dans les flux de travail](/l/fr/user-guide/workflows/capabilities/use-branches-in-workflows) - Guide étape par étape
|
||||
* [Actions de flux de travail](/l/fr/user-guide/workflows/capabilities/workflow-actions) - Actions disponibles, y compris Filtre
|
||||
@@ -0,0 +1,85 @@
|
||||
---
|
||||
title: Versions de Workflow
|
||||
description: Gérer les versions et les brouillons de workflows.
|
||||
image: /images/user-guide/workflows/workflow.png
|
||||
---
|
||||
|
||||
## À propos des versions
|
||||
|
||||
Chaque fois que vous activez un workflow, une nouvelle version est créée. Cela vous permet de suivre les modifications dans le temps et de revenir aux configurations précédentes si nécessaire.
|
||||
|
||||
## Statuts des versions
|
||||
|
||||
| Statut | Description |
|
||||
| ------------- | ------------------------------------------------- |
|
||||
| **Brouillon** | En cours d'édition, pas encore publié |
|
||||
| **Actif** | Version active répondant aux déclencheurs |
|
||||
| **Désactivé** | Auparavant actif, mais arrêté manuellement |
|
||||
| **Archivé** | Versions antérieures conservées pour l'historique |
|
||||
|
||||
## Travailler avec des brouillons
|
||||
|
||||
Lorsque vous modifiez un workflow actif, vos modifications sont enregistrées en tant que **brouillon**. La version active continue de s'exécuter pendant que vous travaillez sur des mises à jour.
|
||||
|
||||
Une fois vos modifications terminées, vous pouvez :
|
||||
|
||||
* **Activer** : Publier le brouillon comme nouvelle version active (la version précédente est archivée)
|
||||
* **Abandonner** : Supprimer le brouillon et conserver la version active actuelle
|
||||
|
||||
## Historique des versions
|
||||
|
||||
### Afficher les versions précédentes
|
||||
|
||||
1. Ouvrez le workflow
|
||||
2. Cliquez sur l'onglet **Versions**
|
||||
3. Affichez toutes les versions précédentes avec leurs horodatages
|
||||
|
||||
### Restaurer une version
|
||||
|
||||
1. Trouvez la version que vous souhaitez restaurer
|
||||
2. Cliquez sur **Utiliser comme brouillon**
|
||||
3. La version est copiée dans un nouveau brouillon
|
||||
4. Effectuez les mises à jour nécessaires
|
||||
5. Activez lorsque vous êtes prêt
|
||||
|
||||
## Meilleures pratiques
|
||||
|
||||
### Gestion des versions
|
||||
|
||||
* N'activez que lorsque vous êtes prêt pour la mise en production
|
||||
* Conservez des modifications significatives d'une version à l'autre.
|
||||
* Documentez les modifications majeures dans les noms ou les descriptions de workflows
|
||||
* Testez en mode brouillon avant d'activer
|
||||
|
||||
### Annulation des modifications
|
||||
|
||||
* Si une nouvelle version pose des problèmes, restaurez la version précédente
|
||||
* Utilisez l'historique des versions pour suivre ce qui a changé
|
||||
* Testez toujours les versions restaurées avant de les activer
|
||||
|
||||
## Workflows courants
|
||||
|
||||
### Modification rapide
|
||||
|
||||
1. Apportez des modifications mineures à un workflow actif
|
||||
2. Testez en mode brouillon
|
||||
3. Activez la nouvelle version
|
||||
|
||||
### Révision majeure
|
||||
|
||||
1. Utilisez la version précédente comme point de départ
|
||||
2. Apportez des modifications importantes dans un brouillon
|
||||
3. Testez minutieusement tous les scénarios
|
||||
4. Activez lorsque vous êtes confiant
|
||||
|
||||
### Retour en arrière
|
||||
|
||||
1. Identifiez le problème avec la version actuelle
|
||||
2. Trouvez la dernière version fonctionnelle dans l'historique
|
||||
3. Cliquez sur **Utiliser comme brouillon**
|
||||
4. Activez pour rétablir l'ancien comportement
|
||||
|
||||
## Articles connexes
|
||||
|
||||
* [Prise en main des workflows](/l/fr/user-guide/workflows/overview)
|
||||
* [Exécutions de workflows](/l/fr/user-guide/workflows/capabilities/workflow-runs)
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: Migliori Pratiche
|
||||
---
|
||||
|
||||
Questo documento descrive le migliori pratiche da seguire quando si lavora sul backend.
|
||||
|
||||
## Segui un approccio modulare
|
||||
|
||||
Il backend segue un approccio modulare, che è un principio fondamentale quando si lavora con NestJS. Assicurati di suddividere il tuo codice in moduli riutilizzabili per mantenere una codebase pulita e organizzata.
|
||||
Ogni modulo dovrebbe incapsulare una particolare caratteristica o funzionalità e avere un ambito ben definito. Questo approccio modulare consente una chiara separazione delle responsabilità e rimuove complessità non necessarie.
|
||||
|
||||
## Esporre servizi da usare nei moduli
|
||||
|
||||
Crea sempre servizi che abbiano una responsabilità chiara e singola, migliorando la leggibilità e la manutenzione del codice. Dai ai servizi nomi descrittivi e coerenti.
|
||||
|
||||
Dovresti anche esporre i servizi che vuoi utilizzare in altri moduli. Esporre servizi ad altri moduli è possibile tramite il potente sistema di iniezione delle dipendenze di NestJS e promuove un basso accoppiamento tra i componenti.
|
||||
|
||||
## Evitare di usare il tipo `any`
|
||||
|
||||
Quando dichiari una variabile come `any`, il sistema di controllo dei tipi di TypeScript non esegue alcun controllo dei tipi, rendendo possibile assegnare alla variabile valori di qualsiasi tipo. TypeScript utilizza l'inferenza dei tipi per determinare il tipo della variabile in base al valore. Dichiarandola come `any`, TypeScript non può più dedurre il tipo. Questo rende difficile individuare errori legati ai tipi durante lo sviluppo, portando a errori in fase di esecuzione e rendendo il codice meno manutenibile, meno affidabile e più difficile da comprendere per gli altri.
|
||||
|
||||
È per questo che tutto dovrebbe avere un tipo. Quindi, se crei un nuovo oggetto con un nome e un cognome, dovresti creare un'interfaccia o un tipo che contenga un nome e un cognome e che definisca la struttura dell'oggetto che stai manipolando.
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
---
|
||||
title: Coda di messaggi
|
||||
---
|
||||
|
||||
Le code facilitano l'esecuzione di operazioni asincrone. Possono essere usate per eseguire attività in background come l'invio di un'email di benvenuto alla registrazione.
|
||||
Ogni caso d'uso avrà la propria classe di coda estesa da `MessageQueueServiceBase`.
|
||||
|
||||
Al momento supportiamo solo `bull-mq`[bull-mq](https://bullmq.io/) come driver della coda.
|
||||
|
||||
## Passaggi per creare e utilizzare una nuova coda
|
||||
|
||||
1. Aggiungi un nome di coda per la tua nuova coda sotto l'enum `MESSAGE_QUEUES`.
|
||||
2. Fornire l'implementazione della fabbrica della coda con il nome della coda come token di dipendenza.
|
||||
3. Inserisci la coda che hai creato nel modulo/servizio richiesto con il nome della coda come token di dipendenza.
|
||||
4. Aggiungi una classe worker con iniezione basata su token, proprio come il produttore.
|
||||
|
||||
### Esempio di utilizzo
|
||||
|
||||
```ts
|
||||
class Resolver {
|
||||
constructor(@Inject(MESSAGE_QUEUES.custom) private queue: MessageQueueService) {}
|
||||
|
||||
async onSomeAction() {
|
||||
//business logic
|
||||
await this.queue.add(someData);
|
||||
}
|
||||
}
|
||||
|
||||
//async worker
|
||||
class CustomWorker {
|
||||
constructor(@Inject(MESSAGE_QUEUES.custom) private queue: MessageQueueService) {
|
||||
this.initWorker();
|
||||
}
|
||||
|
||||
async initWorker() {
|
||||
await this.queue.work(async ({ id, data }) => {
|
||||
//worker logic
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
---
|
||||
title: Comandi frontend
|
||||
---
|
||||
|
||||
## Comandi utili
|
||||
|
||||
### Avviare l'app
|
||||
|
||||
```bash
|
||||
npx nx start twenty-front
|
||||
```
|
||||
|
||||
### Rigenerare lo schema graphql sulla base dello schema API graphql
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:graphql:generate --configuration=metadata
|
||||
```
|
||||
|
||||
OPPURE
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:graphql:generate
|
||||
```
|
||||
|
||||
### Lint
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:lint # aggiungere --fix per correggere gli errori di lint
|
||||
```
|
||||
|
||||
## Traduzioni
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:lingui:extract
|
||||
npx nx run twenty-front:lingui:compile
|
||||
```
|
||||
|
||||
### Test
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:test # eseguire i test jest
|
||||
npx nx run twenty-front:storybook:serve:dev # eseguire storybook
|
||||
npx nx run twenty-front:storybook:test # eseguire i test # (è necessario che yarn storybook:serve:dev sia in esecuzione)
|
||||
npx nx run twenty-front:storybook:coverage # (è necessario che yarn storybook:serve:dev sia in esecuzione)
|
||||
```
|
||||
|
||||
## Tech Stack
|
||||
|
||||
Il progetto ha una struttura chiara e semplice, con un codice boilerplate minimo.
|
||||
|
||||
**App**
|
||||
|
||||
* [React](https://react.dev/)
|
||||
* [Apollo](https://www.apollographql.com/docs/)
|
||||
* [GraphQL Codegen](https://the-guild.dev/graphql/codegen)
|
||||
* [Recoil](https://recoiljs.org/docs/introduction/core-concepts)
|
||||
* [TypeScript](https://www.typescriptlang.org/)
|
||||
|
||||
**Testing**
|
||||
|
||||
* [Jest](https://jestjs.io/)
|
||||
* [Storybook](https://storybook.js.org/)
|
||||
|
||||
**Strumenti**
|
||||
|
||||
* [Yarn](https://yarnpkg.com/)
|
||||
* [Craco](https://craco.js.org/docs/)
|
||||
* [ESLint](https://eslint.org/)
|
||||
|
||||
## Architettura
|
||||
|
||||
### Routing
|
||||
|
||||
[React Router](https://reactrouter.com/) gestisce il routing.
|
||||
|
||||
Per evitare [re-render](/l/it/developers/contribute/capabilities/frontend-development/best-practices-front#managing-re-renders) inutili, tutta la logica del routing è in un `useEffect` in `PageChangeEffect`.
|
||||
|
||||
### Gestione dello stato
|
||||
|
||||
[Recoil](https://recoiljs.org/docs/introduction/core-concepts) gestisce lo stato.
|
||||
|
||||
Vedi [best practices](/l/it/developers/contribute/capabilities/frontend-development/best-practices-front#state-management) per ulteriori informazioni sulla gestione dello stato.
|
||||
|
||||
## Testing
|
||||
|
||||
[Jest](https://jestjs.io/) serve come strumento per i test unitari mentre [Storybook](https://storybook.js.org/) è per i test dei componenti.
|
||||
|
||||
Jest è principalmente per testare le funzioni utility, e non i componenti stessi.
|
||||
|
||||
Storybook è per testare il comportamento di componenti isolati, oltre che per mostrare il sistema di design.
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
---
|
||||
title: Tasti di scelta rapida
|
||||
---
|
||||
|
||||
## Introduzione
|
||||
|
||||
Quando hai bisogno di ascoltare un tasto di scelta rapida, normalmente useresti l'evento `onKeyDown`.
|
||||
|
||||
In `twenty-front`, tuttavia, potresti avere conflitti tra gli stessi tasti di scelta rapida che sono utilizzati in componenti diversi, montati nello stesso momento.
|
||||
|
||||
Ad esempio, se hai una pagina che ascolta il tasto Invio, e una modale che ascolta lo stesso tasto, con un componente Select all'interno della modale che ascolta il tasto Invio, potresti avere un conflitto quando tutti sono montati contemporaneamente.
|
||||
|
||||
## L'hook `useScopedHotkeys`
|
||||
|
||||
Per gestire questo problema, abbiamo un hook personalizzato che rende possibile ascoltare i tasti di scelta rapida senza alcun conflitto.
|
||||
|
||||
Lo posizioni in un componente, e ascolterà i tasti solo quando il componente è montato E quando l'**ambito del tasto di scelta rapida** specificato è attivo.
|
||||
|
||||
## Come ascoltare i tasti di scelta rapida nella pratica?
|
||||
|
||||
Sono coinvolti due passaggi nell'impostazione dell'ascolto dei tasti di scelta rapida:
|
||||
|
||||
1. Imposta l'[ambito del tasto di scelta rapida](#what-is-a-hotkey-scope-) che ascolterà i tasti di scelta rapida
|
||||
2. Usa l'hook `useScopedHotkeys` per ascoltare i tasti di scelta rapida
|
||||
|
||||
Configurare gli ambiti dei tasti di scelta rapida è necessario anche in pagine semplici, perché altri elementi dell'interfaccia utente come il menu a sinistra o il menu dei comandi potrebbero anch'essi ascoltare i tasti di scelta rapida.
|
||||
|
||||
## Casi d'uso per i tasti di scelta rapida
|
||||
|
||||
In generale, avrai due casi d'uso che richiedono tasti di scelta rapida:
|
||||
|
||||
1. In una pagina o un componente montato in una pagina
|
||||
2. In un componente di tipo modale che prende il focus a causa di un'azione dell'utente
|
||||
|
||||
Il secondo caso d'uso può verificarsi in modo ricorsivo: ad esempio, un menu a tendina in una modale.
|
||||
|
||||
### Ascoltare i tasti di scelta rapida in una pagina
|
||||
|
||||
Esempio:
|
||||
|
||||
```tsx
|
||||
const PageListeningEnter = () => {
|
||||
const {
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
goBackToPreviousHotkeyScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
// 1. Imposta l'ambito del tasto di scelta rapida in un useEffect
|
||||
useEffect(() => {
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
ExampleHotkeyScopes.ExampleEnterPage,
|
||||
);
|
||||
|
||||
// Torna all'ambito del tasto di scelta rapida precedente quando il componente viene smontato
|
||||
return () => {
|
||||
goBackToPreviousHotkeyScope();
|
||||
};
|
||||
}, [goBackToPreviousHotkeyScope, setHotkeyScopeAndMemorizePreviousScope]);
|
||||
|
||||
// 2. Usa l'hook useScopedHotkeys
|
||||
useScopedHotkeys(
|
||||
Key.Enter,
|
||||
() => {
|
||||
// Alcuna logica eseguita in questa pagina quando l'utente preme Invio
|
||||
// ...
|
||||
},
|
||||
ExampleHotkeyScopes.ExampleEnterPage,
|
||||
);
|
||||
|
||||
return <div>La mia pagina che ascolta per Invio</div>;
|
||||
};
|
||||
```
|
||||
|
||||
### Ascoltare i tasti di scelta rapida in un componente di tipo modale
|
||||
|
||||
Per questo esempio utilizzeremo un componente modale che ascolta il tasto Esc per segnalare al suo genitore di chiuderlo.
|
||||
|
||||
Qui l'interazione dell'utente sta cambiando l'ambito.
|
||||
|
||||
```tsx
|
||||
const ExamplePageWithModal = () => {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
const {
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
goBackToPreviousHotkeyScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
const handleOpenModalClick = () => {
|
||||
// 1. Imposta l'ambito del tasto di scelta rapida quando l'utente apre la modale
|
||||
setShowModal(true);
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
ExampleHotkeyScopes.ExampleModal,
|
||||
);
|
||||
};
|
||||
|
||||
const handleModalClose = () => {
|
||||
// 1. Torna all'ambito del tasto di scelta rapida precedente quando la modale viene chiusa
|
||||
setShowModal(false);
|
||||
goBackToPreviousHotkeyScope();
|
||||
};
|
||||
|
||||
return <div>
|
||||
<h1>La mia pagina con una modale</h1>
|
||||
<button onClick={handleOpenModalClick}>Apri modale</button>
|
||||
{showModal && <MyModalComponent onClose={handleModalClose} />}
|
||||
</div>;
|
||||
};
|
||||
```
|
||||
|
||||
Poi nel componente modale:
|
||||
|
||||
```tsx
|
||||
const MyDropdownComponent = ({ onClose }: { onClose: () => void }) => {
|
||||
// 2. Usa l'hook useScopedHotkeys per ascoltare per Esc.
|
||||
// Nota che esc è un tasto di scelta rapida comune che potrebbe essere utilizzato da molti altri componenti
|
||||
// Quindi è importante utilizzare un ambito di tasto di scelta rapida per evitare conflitti
|
||||
useScopedHotkeys(
|
||||
Key.Escape,
|
||||
() => {
|
||||
onClose()
|
||||
},
|
||||
ExampleHotkeyScopes.ExampleModal,
|
||||
);
|
||||
|
||||
return <div>Il mio componente modale</div>;
|
||||
};
|
||||
```
|
||||
|
||||
È importante utilizzare questo schema quando non sei sicuro che l'utilizzo di useEffect con mount/unmount sarà sufficiente per evitare conflitti.
|
||||
|
||||
Quei conflitti possono essere difficili da debug, e potrebbe accadere più spesso che no con useEffects.
|
||||
|
||||
## Che cos'è un ambito del tasto di scelta rapida?
|
||||
|
||||
Un ambito del tasto di scelta rapida è una stringa che rappresenta un contesto in cui i tasti di scelta rapida sono attivi. È generalmente codificato come un enum.
|
||||
|
||||
Quando cambi l'ambito del tasto di scelta rapida, i tasti di scelta rapida che ascoltano questo ambito saranno abilitati e i tasti che ascoltano altri ambiti saranno disabilitati.
|
||||
|
||||
Puoi impostare solo un ambito alla volta.
|
||||
|
||||
Ad esempio, gli ambiti dei tasti di scelta rapida per ogni pagina sono definiti nell'enum `PageHotkeyScope`:
|
||||
|
||||
```tsx
|
||||
export enum PageHotkeyScope {
|
||||
Settings = 'settings',
|
||||
CreateWorkspace = 'create-workspace',
|
||||
SignInUp = 'sign-in-up',
|
||||
CreateProfile = 'create-profile',
|
||||
PlanRequired = 'plan-required',
|
||||
ShowPage = 'show-page',
|
||||
PersonShowPage = 'person-show-page',
|
||||
CompanyShowPage = 'company-show-page',
|
||||
CompaniesPage = 'companies-page',
|
||||
PeoplePage = 'people-page',
|
||||
OpportunitiesPage = 'opportunities-page',
|
||||
ProfilePage = 'profile-page',
|
||||
WorkspaceMemberPage = 'workspace-member-page',
|
||||
TaskPage = 'task-page',
|
||||
}
|
||||
```
|
||||
|
||||
Internamente, l'ambito selezionato attualmente viene memorizzato in uno stato Recoil condiviso in tutta l'applicazione:
|
||||
|
||||
```tsx
|
||||
export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||
key: 'currentHotkeyScopeState',
|
||||
defaultValue: INITIAL_HOTKEYS_SCOPE,
|
||||
});
|
||||
```
|
||||
|
||||
Ma questo stato Recoil non dovrebbe mai essere gestito manualmente! Vedremo come usarlo nella sezione successiva.
|
||||
|
||||
## Come funziona internamente?
|
||||
|
||||
Abbiamo creato un sottile wrapper su [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) che lo rende più performante ed evita rendering non necessari.
|
||||
|
||||
Abbiamo anche creato uno stato Recoil per gestire lo stato dell'ambito del tasto di scelta rapida e renderlo disponibile ovunque nell'applicazione.
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Storybook
|
||||
description: Sfoglia la libreria dei componenti UI di Twenty
|
||||
---
|
||||
|
||||
Visualizza la nostra libreria completa di componenti e documentazione in Storybook.
|
||||
|
||||
[Apri Storybook →](https://storybook.twenty.com)
|
||||
@@ -4,5 +4,5 @@ image: /images/user-guide/tips/light-bulb.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/tips/light-bulb.png" alt="Header" />
|
||||
</Frame>
|
||||
<img src="/images/user-guide/tips/light-bulb.png" alt="Intestazione" />
|
||||
</Frame>
|
||||
|
||||
@@ -4,5 +4,5 @@ image: /images/user-guide/tasks/tasks_header.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/tasks/tasks_header.png" alt="Header" />
|
||||
</Frame>
|
||||
<img src="/images/user-guide/tasks/tasks_header.png" alt="Intestazione" />
|
||||
</Frame>
|
||||
|
||||
@@ -4,49 +4,46 @@ image: /images/user-guide/table-views/table.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/table-views/table.png" alt="Header" />
|
||||
<img src="/images/user-guide/table-views/table.png" alt="Intestazione" />
|
||||
</Frame>
|
||||
|
||||
Rende una barra di navigazione che contiene più componenti `NavigationBarItem`.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="Utilizzo">
|
||||
```jsx
|
||||
importa { IconHome, IconUser, IconSettings } da '@tabler/icons-react';
|
||||
importa { NavigationBar } da "@/ui/navigation/navigation-bar/components/NavigationBar";
|
||||
|
||||
```jsx
|
||||
import { IconHome, IconUser, IconSettings } from '@tabler/icons-react';
|
||||
import { NavigationBar } from "@/ui/navigation/navigation-bar/components/NavigationBar";
|
||||
export const MyComponent = () => {
|
||||
|
||||
export const MyComponent = () => {
|
||||
const navigationItems = [
|
||||
{
|
||||
name: "Home",
|
||||
Icon: IconHome,
|
||||
onClick: () => console.log("Home cliccato"),
|
||||
},
|
||||
{
|
||||
name: "Profilo",
|
||||
Icon: IconUser,
|
||||
onClick: () => console.log("Profilo cliccato"),
|
||||
},
|
||||
{
|
||||
name: "Impostazioni",
|
||||
Icon: IconSettings,
|
||||
onClick: () => console.log("Impostazioni cliccate"),
|
||||
},
|
||||
];
|
||||
|
||||
const navigationItems = [
|
||||
{
|
||||
name: "Home",
|
||||
Icon: IconHome,
|
||||
onClick: () => console.log("Home clicked"),
|
||||
},
|
||||
{
|
||||
name: "Profile",
|
||||
Icon: IconUser,
|
||||
onClick: () => console.log("Profile clicked"),
|
||||
},
|
||||
{
|
||||
name: "Settings",
|
||||
Icon: IconSettings,
|
||||
onClick: () => console.log("Settings clicked"),
|
||||
},
|
||||
];
|
||||
return <NavigationBar activeItemName="Home" items={navigationItems}/>;
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
return <NavigationBar activeItemName="Home" items={navigationItems}/>;
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| Props | Tipo | Descrizione |
|
||||
| -------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| activeItemName | string | Il nome dell'elemento di navigazione attualmente attivo |
|
||||
| elementi | array | Un array di oggetti che rappresentano ciascun elemento di navigazione. Ogni oggetto contiene il `name`, il componente `Icon` da visualizzare e una funzione `onClick` da chiamare quando l'elemento viene cliccato |
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
| Props | Tipo | Descrizione |
|
||||
| -------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| activeItemName | string | Il nome dell'elemento di navigazione attualmente attivo |
|
||||
| elementi | array | Un array di oggetti che rappresentano ciascun elemento di navigazione. Ogni oggetto contiene il `name`, il componente `Icon` da visualizzare e una funzione `onClick` da chiamare quando l'elemento viene cliccato |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -4,46 +4,41 @@ image: /images/user-guide/emails/emails_header.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/emails/emails_header.png" alt="Header" />
|
||||
<img src="/images/user-guide/emails/emails_header.png" alt="Intestazione" />
|
||||
</Frame>
|
||||
|
||||
Indica progresso o conto alla rovescia e si muove da destra a sinistra.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Utilizzo">
|
||||
```jsx
|
||||
import { ProgressBar } from "twenty-ui/feedback";
|
||||
|
||||
<Tab title="Usage">
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<ProgressBar
|
||||
duration={6000}
|
||||
delay={0}
|
||||
easing="easeInOut"
|
||||
barHeight={10}
|
||||
barColor="#4bb543"
|
||||
autoStart={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
```jsx
|
||||
import { ProgressBar } from "twenty-ui/feedback";
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<ProgressBar
|
||||
duration={6000}
|
||||
delay={0}
|
||||
easing="easeInOut"
|
||||
barHeight={10}
|
||||
barColor="#4bb543"
|
||||
autoStart={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Props">
|
||||
|
||||
| Props | Tipo | Descrizione | Predefinito |
|
||||
| --------------- | -------- | ---------------------------------------------------------------------------------------------------------- | ----------- |
|
||||
| durata | numero | La durata totale dell'animazione della barra di progresso in millisecondi | 3 |
|
||||
| ritardo | numero | Il ritardo nell'avvio dell'animazione della barra di progresso in millisecondi | 0 |
|
||||
| smorzamento | string | Funzione di smorzamento per l'animazione della barra di progresso | easeInOut |
|
||||
| altezzaBarra | numero | L'altezza della barra in pixel | 24 |
|
||||
| coloreBarra | string | Il colore della barra | gray80 |
|
||||
| avvioAutomatico | booleano | Se `true`, l'animazione della barra di progresso inizia automaticamente quando il componente viene montato | `vero` |
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
| Props | Tipo | Descrizione | Predefinito |
|
||||
| --------------- | -------- | ---------------------------------------------------------------------------------------------------------- | ----------- |
|
||||
| durata | numero | La durata totale dell'animazione della barra di progresso in millisecondi | 3 |
|
||||
| ritardo | numero | Il ritardo nell'avvio dell'animazione della barra di progresso in millisecondi | 0 |
|
||||
| smorzamento | stringa | Funzione di smorzamento per l'animazione della barra di progresso | easeInOut |
|
||||
| altezzaBarra | numero | L'altezza della barra in pixel | 24 |
|
||||
| coloreBarra | stringa | Il colore della barra | gray80 |
|
||||
| avvioAutomatico | booleano | Se `true`, l'animazione della barra di progresso inizia automaticamente quando il componente viene montato | `vero` |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Barra di progresso circolare
|
||||
@@ -51,27 +46,21 @@ export const MyComponent = () => {
|
||||
Indica il progresso di un'attività, spesso utilizzata in schermate di caricamento o aree in cui si desidera comunicare processi in corso all'utente.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Utilizzo">
|
||||
```jsx
|
||||
import { CircularProgressBar } from "@/ui/feedback/progress-bar/components/CircularProgressBar";
|
||||
|
||||
<Tab title="Usage">
|
||||
|
||||
```jsx
|
||||
import { CircularProgressBar } from "@/ui/feedback/progress-bar/components/CircularProgressBar";
|
||||
|
||||
export const MyComponent = () => {
|
||||
return <CircularProgressBar size={80} barWidth={6} barColor="green" />;
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Props">
|
||||
|
||||
| Props | Tipo | Descrizione | Predefinito |
|
||||
| -------------- | ------ | ------------------------------------------------- | ------------ |
|
||||
| dimensione | numero | La dimensione della barra di progresso circolare | 50 |
|
||||
| larghezzaBarra | numero | La larghezza della linea della barra di progresso | 5 |
|
||||
| coloreBarra | string | Il colore della barra di progresso | currentColor |
|
||||
|
||||
</Tab>
|
||||
export const MyComponent = () => {
|
||||
return <CircularProgressBar size={80} barWidth={6} barColor="green" />;
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Proprietà">
|
||||
| Proprietà | Tipo | Descrizione | Predefinito |
|
||||
| -------------- | ------- | ------------------------------------------------- | ------------ |
|
||||
| dimensione | numero | La dimensione della barra di progresso circolare | 50 |
|
||||
| larghezzaBarra | numero | La larghezza della linea della barra di progresso | 5 |
|
||||
| coloreBarra | stringa | Il colore della barra di progresso | currentColor |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: 機能フラグ
|
||||
---
|
||||
|
||||
機能フラグは試験的な機能を隠すために使用されます。 機能フラグは試験的な機能を隠すために使用されます。 Twentyでは、それらはワークスペースレベルで設定されており、ユーザーレベルではありません。 機能フラグは試験的な機能を隠すために使用されます。 Twentyでは、それらはワークスペースレベルで設定されており、ユーザーレベルではありません。
|
||||
|
||||
## 新しい機能フラグの追加
|
||||
|
||||
`FeatureFlagKey.ts`に機能フラグを追加します:
|
||||
|
||||
```ts
|
||||
type FeatureFlagKey =
|
||||
| 'IS_FEATURENAME_ENABLED'
|
||||
| ...;
|
||||
```
|
||||
|
||||
また、`feature-flag.entity.ts`の列挙型にも追加します:
|
||||
|
||||
```ts
|
||||
enum FeatureFlagKeys {
|
||||
IsFeatureNameEnabled = 'IS_FEATURENAME_ENABLED',
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**バックエンド**機能に機能フラグを適用するには、次を使用します:
|
||||
|
||||
```ts
|
||||
@Gate({
|
||||
featureFlag: 'IS_FEATURENAME_ENABLED',
|
||||
})
|
||||
```
|
||||
|
||||
**フロントエンド**機能に機能フラグを適用するには、次を使用します:
|
||||
|
||||
```ts
|
||||
const isFeatureNameEnabled = useIsFeatureEnabled('IS_FEATURENAME_ENABLED');
|
||||
```
|
||||
|
||||
## デプロイメント用の機能フラグを設定する
|
||||
|
||||
Table `core.featureFlag` 内の対応するレコードを変更します:
|
||||
|
||||
| id | キー | workspaceId | 値 |
|
||||
| ---- | ------------------------ | ----------- | --- |
|
||||
| ランダム | `IS_FEATURENAME_ENABLED` | WorkspaceID | `真` |
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
---
|
||||
title: バックエンドコマンド
|
||||
---
|
||||
|
||||
## 便利なコマンド
|
||||
|
||||
これらのコマンドは packages/twenty-server フォルダから実行してください。
|
||||
これらのコマンドは packages/twenty-server フォルダから実行してください。
|
||||
これらのコマンドは packages/twenty-server フォルダから実行してください。
|
||||
これらのコマンドは packages/twenty-server フォルダから実行してください。
|
||||
これらのコマンドは packages/twenty-server フォルダから実行してください。
|
||||
これらのコマンドは packages/twenty-server フォルダから実行してください。
|
||||
From any other folder you can run `npx nx {command} twenty-server` (or `npx nx run twenty-server:{command}`).
|
||||
|
||||
### 初期設定
|
||||
|
||||
```
|
||||
npx nx database:reset twenty-server # 開発用のシードでデータベースを設定する
|
||||
```
|
||||
|
||||
### サーバーの起動
|
||||
|
||||
```
|
||||
npx nx run twenty-server:start
|
||||
```
|
||||
|
||||
### Lint
|
||||
|
||||
```
|
||||
npx nx run twenty-server:lint # --fix を付けることでリントエラーを修正可能
|
||||
```
|
||||
|
||||
### テスト
|
||||
|
||||
```
|
||||
npx nx run twenty-server:test:unit # 単体テストを実行
|
||||
npx nx run twenty-server:test:integration # 統合テストを実行
|
||||
```
|
||||
|
||||
注: 統合テストを実行する前にデータベースをリセットする必要がある場合、 `npx nx run twenty-server:test:integration:with-db-reset` を実行できます。
|
||||
|
||||
### データベースのリセット
|
||||
|
||||
データベースをリセットおよびシードしたい場合は、次のコマンドを実行してください:
|
||||
|
||||
```bash
|
||||
npx nx run twenty-server:database:reset
|
||||
```
|
||||
|
||||
### マイグレーション
|
||||
|
||||
#### Core/Metadata スキーマ (TypeORM) のオブジェクトに対して
|
||||
|
||||
```bash
|
||||
npx nx run twenty-server:typeorm migration:generate src/database/typeorm/core/migrations/nameOfYourMigration -d src/database/typeorm/core/core.datasource.ts
|
||||
```
|
||||
|
||||
#### ワークスペースオブジェクトに対して
|
||||
|
||||
マイグレーションファイルはなく、それぞれのワークスペースに対して自動的に生成され、
|
||||
データベースに保存され、このコマンドで適用されます。
|
||||
|
||||
```bash
|
||||
npx nx run twenty-server:command workspace:sync-metadata -f
|
||||
```
|
||||
|
||||
<Warning>
|
||||
これによりデータベースが削除され、マイグレーションとシードが再実行されます。
|
||||
|
||||
このコマンドを実行する前に、保持したいデータをバックアップしてください。
|
||||
</Warning>
|
||||
|
||||
## 技術スタック
|
||||
|
||||
Twenty は主にバックエンドに NestJS を使用しています。
|
||||
|
||||
最初に使用した ORM は Prisma でした。 最初に使用した ORM は Prisma でした。 But in order to allow users to create custom fields and custom objects, a lower-level made more sense as we need to have fine-grained control. 最初に使用した ORM は Prisma でした。 But in order to allow users to create custom fields and custom objects, a lower-level made more sense as we need to have fine-grained control. 現在のプロジェクトでは TypeORM を使用しています。
|
||||
|
||||
現在の技術スタックは次のようになっています。
|
||||
|
||||
**コア**
|
||||
|
||||
* [NestJS](https://nestjs.com/)
|
||||
* [TypeORM](https://typeorm.io/)
|
||||
* [GraphQL Yoga](https://the-guild.dev/graphql/yoga-server)
|
||||
|
||||
**データベース**
|
||||
|
||||
* [Postgres](https://www.postgresql.org/)
|
||||
|
||||
**サードパーティ統合**
|
||||
|
||||
* [Sentry](https://sentry.io/welcome/) バグトラッキング用
|
||||
|
||||
**テスト**
|
||||
|
||||
* [Jest](https://jestjs.io/)
|
||||
|
||||
**ツール**
|
||||
|
||||
* [Yarn](https://yarnpkg.com/)
|
||||
* [ESLint](https://eslint.org/)
|
||||
|
||||
**開発**
|
||||
|
||||
* [AWS EKS](https://aws.amazon.com/eks/)
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
---
|
||||
title: フロントエンドコマンド
|
||||
---
|
||||
|
||||
## 便利なコマンド
|
||||
|
||||
### アプリの開始
|
||||
|
||||
```bash
|
||||
npx nx start twenty-front
|
||||
```
|
||||
|
||||
### APIのGraphQLスキーマに基づいてGraphQLスキーマを再生成する
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:graphql:generate --configuration=metadata
|
||||
```
|
||||
|
||||
または
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:graphql:generate
|
||||
```
|
||||
|
||||
### リント
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:lint # pass --fix to fix lint errors
|
||||
```
|
||||
|
||||
## 翻訳
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:lingui:extract
|
||||
npx nx run twenty-front:lingui:compile
|
||||
```
|
||||
|
||||
### テスト
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:test # run jest tests
|
||||
npx nx run twenty-front:storybook:serve:dev # run storybook
|
||||
npx nx run twenty-front:storybook:test # run tests # (needs yarn storybook:serve:dev to be running)
|
||||
npx nx run twenty-front:storybook:coverage # (needs yarn storybook:serve:dev to be running)
|
||||
```
|
||||
|
||||
## 技術スタック
|
||||
|
||||
プロジェクトは、最小限のボイラープレートコードを持つシンプルでクリーンなスタックを持っています。
|
||||
|
||||
**アプリ**
|
||||
|
||||
* [React](https://react.dev/)
|
||||
* [Apollo](https://www.apollographql.com/docs/)
|
||||
* [GraphQL Codegen](https://the-guild.dev/graphql/codegen)
|
||||
* [Recoil](https://recoiljs.org/docs/introduction/core-concepts)
|
||||
* [TypeScript](https://www.typescriptlang.org/)
|
||||
|
||||
**テスト**
|
||||
|
||||
* [Jest](https://jestjs.io/)
|
||||
* [Storybook](https://storybook.js.org/)
|
||||
|
||||
**ツール**
|
||||
|
||||
* [Yarn](https://yarnpkg.com/)
|
||||
* [Craco](https://craco.js.org/docs/)
|
||||
* [ESLint](https://eslint.org/)
|
||||
|
||||
## アーキテクチャ
|
||||
|
||||
### ルーティング
|
||||
|
||||
[React Router](https://reactrouter.com/)はルーティングを処理します。
|
||||
|
||||
不要な[再レンダリング](/l/ja/developers/contribute/capabilities/frontend-development/best-practices-front#managing-re-renders)を避けるために、すべてのルーティングロジックは`PageChangeEffect`内の`useEffect`にあります。
|
||||
|
||||
### 状態管理
|
||||
|
||||
[Recoil](https://recoiljs.org/docs/introduction/core-concepts)は状態管理を処理します。
|
||||
|
||||
状態管理に関する詳細な情報は[ベストプラクティス](/l/ja/developers/contribute/capabilities/frontend-development/best-practices-front#state-management)を参照してください。
|
||||
|
||||
## テスト
|
||||
|
||||
[Jest](https://jestjs.io/)はユニットテストのツールであり、[Storybook](https://storybook.js.org/)はコンポーネントテスト用です。
|
||||
|
||||
Jestは主にユーティリティ関数のテスト用で、コンポーネントのテスト自体ではありません。
|
||||
|
||||
Storybookは、独立したコンポーネントの挙動をテストし、デザインシステムを表示するためのものです。
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
---
|
||||
title: ホットキー
|
||||
---
|
||||
|
||||
## イントロダクション
|
||||
|
||||
ホットキーをリッスンする必要がある場合、通常は `onKeyDown` イベントリスナーを使用します。
|
||||
|
||||
しかし、`twenty-front` では、同時にマウントされている異なるコンポーネントで使用される同じホットキーの間で競合が生じることがあります。
|
||||
|
||||
例えば、Enterキーをリッスンするページと、Enterキーをリッスンするモーダル、さらにそのモーダル内のSelectコンポーネントもEnterキーをリッスンしている場合、全てが同時にマウントされると競合が生じる可能性があります。
|
||||
|
||||
## `useScopedHotkeys` フック
|
||||
|
||||
この問題を解決するために、どのような競合もなくホットキーをリッスンすることを可能にするカスタムフックがあります。
|
||||
|
||||
コンポーネント内に配置すると、コンポーネントがマウントされ、指定された**ホットキースコープ**がアクティブなときだけホットキーをリッスンします。
|
||||
|
||||
## 実際にホットキーをリッスンする方法は?
|
||||
|
||||
ホットキーをリッスンするための設定には2つのステップがあります:
|
||||
|
||||
1. ホットキーをリッスンする[ホットキースコープ](#what-is-a-hotkey-scope-)を設定します
|
||||
2. ホットキーをリッスンするために `useScopedHotkeys` フックを使用します
|
||||
|
||||
他のUI要素(例:左側のメニューやコマンドメニュー)もホットキーをリッスンする可能性があるため、ホットキースコープの設定は単純なページでも必要です。
|
||||
|
||||
## ホットキーのユースケース
|
||||
|
||||
一般的に、ホットキーが必要となる動作は2つあります:
|
||||
|
||||
1. ページにマウントされたコンポーネントで
|
||||
2. ユーザーのアクションでフォーカスをとるモーダルタイプのコンポーネントで
|
||||
|
||||
2番目のユースケースは再帰的に発生します:例えばモーダル内のドロップダウン。
|
||||
|
||||
### ページ内でホットキーをリッスン
|
||||
|
||||
例:
|
||||
|
||||
```tsx
|
||||
const PageListeningEnter = () => {
|
||||
const {
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
goBackToPreviousHotkeyScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
// 1. setHotkeyScopeAndMemorizePreviousScopeをuseEffect内で設定
|
||||
useEffect(() => {
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
ExampleHotkeyScopes.ExampleEnterPage,
|
||||
);
|
||||
|
||||
// コンポーネントがアンマウントされたときに前のホットキースコープに戻ります
|
||||
return () => {
|
||||
goBackToPreviousHotkeyScope();
|
||||
};
|
||||
}, [goBackToPreviousHotkeyScope, setHotkeyScopeAndMemorizePreviousScope]);
|
||||
|
||||
// 2. useScopedHotkeysフックを使用
|
||||
useScopedHotkeys(
|
||||
Key.Enter,
|
||||
() => {
|
||||
// ユーザーがEnterを押したときにこのページで実行されるロジック
|
||||
// ...
|
||||
},
|
||||
ExampleHotkeyScopes.ExampleEnterPage,
|
||||
);
|
||||
|
||||
return <div>Enterをリッスンする私のページ</div>;
|
||||
};
|
||||
```
|
||||
|
||||
### モーダルタイプのコンポーネントでホットキーをリッスン
|
||||
|
||||
この例では、親にモーダルを閉じるように指示するためにEscapeキーをリッスンするモーダルコンポーネントを使用します。
|
||||
|
||||
ここで、ユーザーの操作がスコープを変更します。
|
||||
|
||||
```tsx
|
||||
const ExamplePageWithModal = () => {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
const {
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
goBackToPreviousHotkeyScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
const handleOpenModalClick = () => {
|
||||
// 1. ユーザーがモーダルを開くときにホットキースコープを設定
|
||||
setShowModal(true);
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
ExampleHotkeyScopes.ExampleModal,
|
||||
);
|
||||
};
|
||||
|
||||
const handleModalClose = () => {
|
||||
// 1. モーダルが閉じられるときに前のホットキースコープに戻る
|
||||
setShowModal(false);
|
||||
goBackToPreviousHotkeyScope();
|
||||
};
|
||||
|
||||
return <div>
|
||||
<h1>私のモーダル付きページ</h1>
|
||||
<button onClick={handleOpenModalClick}>モーダルを開く</button>
|
||||
{showModal && <MyModalComponent onClose={handleModalClose} />}
|
||||
</div>;
|
||||
};
|
||||
```
|
||||
|
||||
その後、モーダルコンポーネント内で:
|
||||
|
||||
```tsx
|
||||
const MyDropdownComponent = ({ onClose }: { onClose: () => void }) => {
|
||||
// 2. EscapeをリッスンするためにuseScopedHotkeysフックを使用
|
||||
// Escapeは多くの他のコンポーネントでも使用される可能性がある一般的なホットキーです
|
||||
// スコープを使って競合を防ぐことが重要です
|
||||
useScopedHotkeys(
|
||||
Key.Escape,
|
||||
() => {
|
||||
onClose()
|
||||
},
|
||||
ExampleHotkeyScopes.ExampleModal,
|
||||
);
|
||||
|
||||
return <div>私のモーダルコンポーネント</div>;
|
||||
};
|
||||
```
|
||||
|
||||
モント/アンマウントでのuseEffectだけで競合を避けるには不十分なことがある場合にこのパターンを使用することが重要です。
|
||||
|
||||
これらの競合はデバッグが困難で、useEffectsでよく発生することがあります。
|
||||
|
||||
## ホットキースコープとは?
|
||||
|
||||
ホットキースコープは、ホットキーがアクティブなコンテキストを表す文字列です。 通常、enumとしてエンコードされます。 通常、enumとしてエンコードされます。 通常、enumとしてエンコードされます。
|
||||
|
||||
ホットキースコープを変更すると、このスコープをリッスンしているホットキーが有効になり、他のスコープをリッスンしているホットキーが無効になります。
|
||||
|
||||
一度に1つのスコープしか設定できません。
|
||||
|
||||
例として、各ページのホットキースコープは`PageHotkeyScope` enumで定義されています:
|
||||
|
||||
```tsx
|
||||
export enum PageHotkeyScope {
|
||||
Settings = '設定',
|
||||
CreateWorkspace = '作業スペースの作成',
|
||||
SignInUp = 'サインイン-アップ',
|
||||
CreateProfile = 'プロファイルの作成',
|
||||
PlanRequired = 'プランが必要',
|
||||
ShowPage = 'ページ表示',
|
||||
PersonShowPage = '人物表示ページ',
|
||||
CompanyShowPage = '会社表示ページ',
|
||||
CompaniesPage = '会社ページ',
|
||||
PeoplePage = '人物ページ',
|
||||
OpportunitiesPage = '機会ページ',
|
||||
ProfilePage = 'プロファイルページ',
|
||||
WorkspaceMemberPage = 'ワークスペースメンバーページ',
|
||||
TaskPage = 'タスクページ',
|
||||
}
|
||||
```
|
||||
|
||||
内部的には、現在選択されているスコープはアプリケーション全体で共有されるRecoilステートに格納されています:
|
||||
|
||||
```tsx
|
||||
export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||
key: '現在のホットキースコープステート',
|
||||
defaultValue: INITIAL_HOTKEYS_SCOPE,
|
||||
});
|
||||
```
|
||||
|
||||
しかし、このRecoilステートは手動で処理しないでください! 次のセクションでその使用方法を見ていきます。 次のセクションでその使用方法を見ていきます。 次のセクションでその使用方法を見ていきます。
|
||||
|
||||
## 内部的にはどう機能しているのか?
|
||||
|
||||
[react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro)の上に薄いラッパーを作成し、より効率的にし、不必要な再レンダリングを避けます。
|
||||
|
||||
また、ホットキースコープの状態を処理し、アプリケーション全体で利用できるRecoilステートを作成しました。
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: ストーリーブック
|
||||
description: TwentyのUIコンポーネントライブラリを閲覧
|
||||
---
|
||||
|
||||
ストーリーブックで私たちの完全なコンポーネントライブラリおよびドキュメントを参照してください。
|
||||
|
||||
[ストーリーブックを開く →](https://storybook.twenty.com)
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
---
|
||||
title: Figma で作業する
|
||||
info: Twenty の Figma を使用した共同作業方法を学ぶ
|
||||
---
|
||||
|
||||
Figma は、デザイナーと開発者の間のコミュニケーションの壁を埋めるのを助ける協働インターフェース設計ツールです。
|
||||
このガイドは、Figma を使って共同作業する方法を説明します。
|
||||
このガイドは、Figma を使って共同作業する方法を説明します。
|
||||
このガイドは、Figma を使って共同作業する方法を説明します。
|
||||
このガイドは、Figma を使って共同作業する方法を説明します。
|
||||
このガイドは、Figma を使って共同作業する方法を説明します。
|
||||
このガイドは、Figma を使って共同作業する方法を説明します。
|
||||
このガイドは、Figma を使って共同作業する方法を説明します。
|
||||
このガイドは、Figma を使って共同作業する方法を説明します。
|
||||
このガイドは、Figma を使って共同作業する方法を説明します。
|
||||
このガイドは、Figma を使って共同作業する方法を説明します。
|
||||
このガイドは、Figma を使って共同作業する方法を説明します。
|
||||
このガイドは、Figma を使って共同作業する方法を説明します。
|
||||
このガイドは、Figma を使って共同作業する方法を説明します。
|
||||
このガイドは、Figma を使って共同作業する方法を説明します。
|
||||
このガイドは、Figma を使って共同作業する方法を説明します。
|
||||
このガイドは、Figma を使って共同作業する方法を説明します。
|
||||
このガイドは、Figma を使って共同作業する方法を説明します。
|
||||
このガイドは、Figma を使って共同作業する方法を説明します。
|
||||
このガイドは、Figma を使って共同作業する方法を説明します。
|
||||
このガイドは、Figma を使って共同作業する方法を説明します。
|
||||
このガイドは、Figma を使って共同作業する方法を説明します。
|
||||
このガイドは、Figma を使って共同作業する方法を説明します。
|
||||
|
||||
## アクセス
|
||||
|
||||
1. **共有リンクにアクセスする:** プロジェクトの Figma ファイルは [ここ](https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty) からアクセスできます。
|
||||
2. **サインイン:** まだサインインしていない場合、Figma はサインインを促します。
|
||||
開発者モードや専用のフレーム選択など、キー機能はサインインしたユーザーのみが利用できます。
|
||||
|
||||
<Warning>
|
||||
アカウントがないと効果的に協力することはできません。
|
||||
</Warning>
|
||||
|
||||
## Figma 構造
|
||||
|
||||
左側のサイドバーで、Twenty の Figma の異なるページにアクセスできます。 それらは次のように整理されています: それらは次のように整理されています: それらは次のように整理されています: それらは次のように整理されています: それらは次のように整理されています:
|
||||
|
||||
* **コンポーネントページ:** これは最初のページです。 デザイナーはこれを使用して、デザインファイル全体で使用される再利用可能なデザイン要素を作成および整理します。 例えば、ボタン、アイコン、シンボル、またはその他の再利用可能なコンポーネントとして使用されます。 これにより、デザイン全体で一貫性を保つことができます。
|
||||
* **メインページ:** 2 番目のページはメインページで、プロジェクトの完全なユーザーインターフェースを示しています。 ***再生*** を押すことで、アプリプロトタイプを完全に使用できます。 **メインページ:** 2 番目のページはメインページで、プロジェクトの完全なユーザーインターフェースを示しています。 ***再生*** を押すことで、アプリプロトタイプを完全に使用できます。 ***再生*** を押すことで、アプリプロトタイプを完全に使用できます。
|
||||
* **特徴ページ:** その他のページは、通常、進行中の機能に専念しています。 それらはアプリケーションまたはウェブサイトの特定の機能またはモジュールのデザインを含みます。 通常、まだ進行中です。 それらはアプリケーションまたはウェブサイトの特定の機能またはモジュールのデザインを含みます。 通常、まだ進行中です。 それらはアプリケーションまたはウェブサイトの特定の機能またはモジュールのデザインを含みます。 通常、まだ進行中です。 それらはアプリケーションまたはウェブサイトの特定の機能またはモジュールのデザインを含みます。 通常、まだ進行中です。
|
||||
|
||||
## 役立つヒント
|
||||
|
||||
閲覧専用アクセスでは、デザインの編集はできませんが、デザインをコードに変換する際に役立つすべての機能にアクセスできます。
|
||||
|
||||
### 開発者モードを使用する
|
||||
|
||||
Figma の Dev モードは、デザインナビゲーションの容易化、効果的なアセット管理、効率的な通信ツール、ツールボックス統合、迅速なコードスニペット、主要レイヤー情報の提供により、開発者の生産性を向上させ、デザインと開発の間のギャップを埋めます。 Dev Mode についての詳細は [こちら](https://www.figma.com/dev-mode/) で確認できます。
|
||||
|
||||
ツールバーの右部分で「開発者」モードに切り替えることで、デザインスペックの確認、CSS のコピー、アセットへのアクセスが可能です。
|
||||
|
||||
### プロトタイプを使用する
|
||||
|
||||
キャンバス上の任意の要素をクリックし、インターフェースの右上隅にある「再生」ボタンを押してプロトタイプビューにアクセスします。 プロトタイプモードでは、あたかも最終製品であるかのようにデザインに触れることができます。 画面間のフローや、ボタン、リンク、メニューなどのインターフェース要素が操作時にどのように動作するかを示します。
|
||||
|
||||
1. **トランジションとアニメーションの理解:** プロトタイプモードでは、デザイナーによって画面や UI 要素間に追加されたトランジションやアニメーションを表示でき、開発者に意図された動作やスタイルを視覚的に明確に指示します。
|
||||
2. **実装の明確化:** プロトタイプは曖昧さを減らすのにも役立ちます。 開発者はそれを操作することで、特定の要素の機能や外観をより良く理解することができます。
|
||||
|
||||
Figma プラットフォームの学習に関するより包括的な詳細とガイダンスについては、公式の [Figma ドキュメント](https://help.figma.com/hc/en-us) を訪れることができます。
|
||||
|
||||
### 距離を測定する
|
||||
|
||||
要素を選択し、`Option` キー(Mac)または `Alt` キー(Windows)を押したまま、別の要素の上にカーソルを合わせると、それらの間の距離が表示されます。
|
||||
|
||||
### VSCode 用 Figma 拡張機能(推奨)
|
||||
|
||||
[Figma for VS Code](https://marketplace.visualstudio.com/items?itemName=figma.figma-vscode-extension) を使用すると、デザインファイルのナビゲート、検査、デザイナーとのコラボレーション、変更の追跡、実装の促進ができます - すべてをテキストエディターから離れることなく行えます。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
|
||||
## コラボレーション
|
||||
|
||||
1. **コメントの使用:** ツールバーの左側にあるバブルアイコンをクリックしてコメント機能を使用できます。
|
||||
2. **カーソルチャット:** Figma の魅力的な機能のひとつにカーソルチャットがあります。 他の Figma を同時に使用している誰かを見た場合、`/` キー(Windows)または `;` キー(Mac)を押してメッセージを送信します。 **カーソルチャット:** Figma の魅力的な機能のひとつにカーソルチャットがあります。 他の Figma を同時に使用している誰かを見た場合、`/` キー(Windows)または `;` キー(Mac)を押してメッセージを送信します。 他の Figma を同時に使用している誰かを見た場合、`/` キー(Windows)または `;` キー(Mac)を押してメッセージを送信します。 **カーソルチャット:** Figma の魅力的な機能のひとつにカーソルチャットがあります。 他の Figma を同時に使用している誰かを見た場合、`/` キー(Windows)または `;` キー(Mac)を押してメッセージを送信します。 **カーソルチャット:** Figma の魅力的な機能のひとつにカーソルチャットがあります。 他の Figma を同時に使用している誰かを見た場合、`/` キー(Windows)または `;` キー(Mac)を押してメッセージを送信します。 他の Figma を同時に使用している誰かを見た場合、`/` キー(Windows)または `;` キー(Mac)を押してメッセージを送信します。 **カーソルチャット:** Figma の魅力的な機能のひとつにカーソルチャットがあります。 他の Figma を同時に使用している誰かを見た場合、`/` キー(Windows)または `;` キー(Mac)を押してメッセージを送信します。 **カーソルチャット:** Figma の魅力的な機能のひとつにカーソルチャットがあります。 他の Figma を同時に使用している誰かを見た場合、`/` キー(Windows)または `;` キー(Mac)を押してメッセージを送信します。 他の Figma を同時に使用している誰かを見た場合、`/` キー(Windows)または `;` キー(Mac)を押してメッセージを送信します。 **カーソルチャット:** Figma の魅力的な機能のひとつにカーソルチャットがあります。 他の Figma を同時に使用している誰かを見た場合、`/` キー(Windows)または `;` キー(Mac)を押してメッセージを送信します。 他の Figma を同時に使用している誰かを見た場合、`/` キー(Windows)または `;` キー(Mac)を押してメッセージを送信します。
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: その他の方法
|
||||
---
|
||||
|
||||
<Warning>
|
||||
このドキュメントはコミュニティによって管理されています。 問題を含む可能性があります。
|
||||
問題を含む可能性があります。
|
||||
</Warning>
|
||||
|
||||
## Terraformとマニフェストを通したKubernetes
|
||||
|
||||
Kubernetesデプロイメントに関するコミュニティ主導のドキュメントは[こちら](https://github.com/twentyhq/twenty/tree/main/packages/twenty-docker/k8s)です。
|
||||
|
||||
### Coolify
|
||||
|
||||
Coolifyを使用してサーバーにTwentyをデプロイします。 (Coolify上の公式イメージは近日公開予定です) (Coolify上の公式イメージは近日公開予定です)
|
||||
|
||||
[Coolifyのドキュメント](https://coolify.io/docs/get-started/introduction)
|
||||
|
||||
### EasyPanel
|
||||
|
||||
以下のコミュニティ維持テンプレートを使用して、EasyPanelにTwentyをデプロイします。
|
||||
|
||||
以下のコミュニティ維持テンプレートを使用して、EasyPanelにTwentyをデプロイします。
|
||||
|
||||
### Elest.io
|
||||
|
||||
以下のリンクを使用して、Elest.ioにTwentyをサーバー上にデプロイします。
|
||||
|
||||
[Elest.ioにデプロイする](https://elest.io/open-source/twenty)
|
||||
|
||||
### Railway上のTwenty
|
||||
|
||||
以下のコミュニティ維持テンプレートを使用して、RailwayにTwentyをデプロイします。
|
||||
|
||||
[](https://railway.com/deploy/nAL3hA)
|
||||
|
||||
### Sealos上のTwenty
|
||||
|
||||
以下のコミュニティがメンテナンスしているテンプレートを使用して、SealosにTwentyをデプロイします。
|
||||
|
||||
[](https://sealos.io/products/app-store/twenty)
|
||||
|
||||
## その他
|
||||
|
||||
もっと多くのクラウドプロバイダーオプションを追加するために、PRを自由に作成してください。
|
||||
@@ -4,57 +4,52 @@ image: /images/user-guide/tips/light-bulb.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/tips/light-bulb.png" alt="Header" />
|
||||
<img src="/images/user-guide/tips/light-bulb.png" alt="ヘッダー" />
|
||||
</Frame>
|
||||
|
||||
ユーザーが要素とやり取りするときに追加情報を表示する短いメッセージ。
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="使用方法">
|
||||
```jsx
|
||||
import { AppTooltip } from "@/ui/display/tooltip/AppTooltip";
|
||||
|
||||
```jsx
|
||||
import { AppTooltip } from "@/ui/display/tooltip/AppTooltip";
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<>
|
||||
<p id="hoverText" style={{ display: "inline-block" }}>
|
||||
Customer Insights
|
||||
</p>
|
||||
<AppTooltip
|
||||
className
|
||||
anchorSelect="#hoverText"
|
||||
content="Explore customer behavior and preferences"
|
||||
delayHide={0}
|
||||
offset={6}
|
||||
noArrow={false}
|
||||
isOpen={true}
|
||||
place="bottom"
|
||||
positionStrategy="absolute"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Props">
|
||||
|
||||
| プロパティ | タイプ | 説明 |
|
||||
| ---------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| className | string | 追加のスタイリング用のオプションのCSSクラス |
|
||||
| anchorSelect | CSSセレクタ | ツールチップのアンカー(ツールチップをトリガーする要素)のセレクタ |
|
||||
| content | string | ツールチップ内に表示したいコンテンツ |
|
||||
| delayHide | 数 | アンカーからカーソルが離れた後、ツールチップを非表示にする前の遅延(秒) |
|
||||
| offset | 数 | ツールチップの位置を決めるためのオフセット(ピクセル) |
|
||||
| noArrow | ブール型 | `true`の場合、ツールチップの矢印を非表示にします |
|
||||
| isOpen | ブール型 | `true`の場合、ツールチップはデフォルトで開かれています |
|
||||
| place | `react-tooltip`からの`PlacesType`文字列 | ツールチップの配置を指定します。 値は`bottom`、`left`、`right`、`top`、`top-start`、`top-end`、`right-start`、`right-end`、`bottom-start`、`bottom-end`、`left-start`、`left-end`などがあります |
|
||||
| positionStrategy | `react-tooltip`からの`PositionStrategy`文字列 | ツールチップの位置戦略。 ツールチップの位置戦略。 2つの値があります: `absolute` と `fixed` |
|
||||
|
||||
</Tab>
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<>
|
||||
<p id="hoverText" style={{ display: "inline-block" }}>
|
||||
カスタマーインサイト
|
||||
</p>
|
||||
<AppTooltip
|
||||
className
|
||||
anchorSelect="#hoverText"
|
||||
content="顧客の行動や好みを調査する"
|
||||
delayHide={0}
|
||||
offset={6}
|
||||
noArrow={false}
|
||||
isOpen={true}
|
||||
place="bottom"
|
||||
positionStrategy="absolute"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="プロパティ">
|
||||
| プロパティ | タイプ | 説明 |
|
||||
| ---------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| className | string | 追加のスタイリング用のオプションのCSSクラス |
|
||||
| anchorSelect | CSSセレクタ | ツールチップのアンカー(ツールチップをトリガーする要素)のセレクタ |
|
||||
| content | string | ツールチップ内に表示したいコンテンツ |
|
||||
| delayHide | 数 | アンカーからカーソルが離れた後、ツールチップを非表示にする前の遅延(秒) |
|
||||
| offset | 数 | ツールチップの位置を決めるためのオフセット(ピクセル) |
|
||||
| noArrow | ブール型 | `true`の場合、ツールチップの矢印を非表示にします |
|
||||
| isOpen | ブール型 | `true`の場合、ツールチップはデフォルトで開かれています |
|
||||
| place | `react-tooltip`からの`PlacesType`文字列 | ツールチップの配置を指定します。 値は`bottom`、`left`、`right`、`top`、`top-start`、`top-end`、`right-start`、`right-end`、`bottom-start`、`bottom-end`、`left-start`、`left-end`などがあります |
|
||||
| positionStrategy | `react-tooltip`からの`PositionStrategy`文字列 | ツールチップの位置戦略。 2つの値があります: `absolute` と `fixed` |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## ツールチップを伴うオーバーフローテキスト
|
||||
@@ -62,27 +57,22 @@ export const MyComponent = () => {
|
||||
オーバーフローテキストを処理し、テキストがオーバーフローしたときにツールチップを表示します。
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="使用方法">
|
||||
```jsx
|
||||
import { OverflowingTextWithTooltip } from 'twenty-ui/display';
|
||||
|
||||
```jsx
|
||||
import { OverflowingTextWithTooltip } from 'twenty-ui/display';
|
||||
export const MyComponent = () => {
|
||||
const crmTaskDescription =
|
||||
'最近の製品照会に関してクライアントをフォローアップします。 価格オプションについて議論し、懸念事項に対応し、追加の製品情報を提供します。 将来の参照のために、会話の詳細をCRMに記録します。';
|
||||
|
||||
export const MyComponent = () => {
|
||||
const crmTaskDescription =
|
||||
'Follow up with client regarding their recent product inquiry. Discuss pricing options, address any concerns, and provide additional product information. Record the details of the conversation in the CRM for future reference.';
|
||||
|
||||
return <OverflowingTextWithTooltip text={crmTaskDescription} />;
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Props">
|
||||
|
||||
| プロパティ | タイプ | 説明 |
|
||||
| ----- | ------ | -------------------------- |
|
||||
| テキスト | string | オーバーフローテキストエリア内に表示したいコンテンツ |
|
||||
|
||||
</Tab>
|
||||
return <OverflowingTextWithTooltip text={crmTaskDescription} />;
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="プロパティ">
|
||||
| プロパティ | タイプ | 説明 |
|
||||
| ----- | ------ | -------------------------- |
|
||||
| テキスト | string | オーバーフローテキストエリア内に表示したいコンテンツ |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -4,5 +4,5 @@ image: /images/user-guide/tips/light-bulb.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/tips/light-bulb.png" alt="Header" />
|
||||
</Frame>
|
||||
<img src="/images/user-guide/tips/light-bulb.png" alt="ヘッダー" />
|
||||
</Frame>
|
||||
|
||||
@@ -4,53 +4,49 @@ image: /images/user-guide/github/github-header.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/github/github-header.png" alt="Header" />
|
||||
<img src="/images/user-guide/github/github-header.png" alt="ヘッダー" />
|
||||
</Frame>
|
||||
|
||||
ドロップダウンベースのアイコンピッカーで、ユーザーがリストからアイコンを選択できます。
|
||||
|
||||
<Tabs>
|
||||
<Tab title="使用方法">
|
||||
```jsx
|
||||
import { RecoilRoot } from "recoil";
|
||||
import React, { useState } from "react";
|
||||
import { IconPicker } from "@/ui/input/components/IconPicker";
|
||||
|
||||
<Tab title="Usage">
|
||||
export const MyComponent = () => {
|
||||
|
||||
```jsx
|
||||
import { RecoilRoot } from "recoil";
|
||||
import React, { useState } from "react";
|
||||
import { IconPicker } from "@/ui/input/components/IconPicker";
|
||||
const [selectedIcon, setSelectedIcon] = useState("");
|
||||
const handleIconChange = ({ iconKey, Icon }) => {
|
||||
console.log("選択されたアイコン:", iconKey);
|
||||
setSelectedIcon(iconKey);
|
||||
};
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<RecoilRoot>
|
||||
<IconPicker
|
||||
disabled={false}
|
||||
onChange={handleIconChange}
|
||||
selectedIconKey={selectedIcon}
|
||||
variant="primary"
|
||||
/>
|
||||
</RecoilRoot>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
const [selectedIcon, setSelectedIcon] = useState("");
|
||||
const handleIconChange = ({ iconKey, Icon }) => {
|
||||
console.log("Selected Icon:", iconKey);
|
||||
setSelectedIcon(iconKey);
|
||||
};
|
||||
|
||||
return (
|
||||
<RecoilRoot>
|
||||
<IconPicker
|
||||
disabled={false}
|
||||
onChange={handleIconChange}
|
||||
selectedIconKey={selectedIcon}
|
||||
variant="primary"
|
||||
/>
|
||||
</RecoilRoot>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| プロパティ | タイプ | 説明 |
|
||||
| --------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| disabled | ブール型 | `true` に設定されるとアイコンピッカーは無効になります |
|
||||
| onChange | 機能 | ユーザーがアイコンを選択したときにトリガーされるコールバック関数。 ユーザーがアイコンを選択したときにトリガーされるコールバック関数。 それは `iconKey` と `Icon` プロパティを持つオブジェクトを受け取ります ユーザーがアイコンを選択したときにトリガーされるコールバック関数。 それは `iconKey` と `Icon` プロパティを持つオブジェクトを受け取ります ユーザーがアイコンを選択したときにトリガーされるコールバック関数。 それは `iconKey` と `Icon` プロパティを持つオブジェクトを受け取ります ユーザーがアイコンを選択したときにトリガーされるコールバック関数。 それは `iconKey` と `Icon` プロパティを持つオブジェクトを受け取ります ユーザーがアイコンを選択したときにトリガーされるコールバック関数。 それは `iconKey` と `Icon` プロパティを持つオブジェクトを受け取ります ユーザーがアイコンを選択したときにトリガーされるコールバック関数。 それは `iconKey` と `Icon` プロパティを持つオブジェクトを受け取ります ユーザーがアイコンを選択したときにトリガーされるコールバック関数。 それは `iconKey` と `Icon` プロパティを持つオブジェクトを受け取ります ユーザーがアイコンを選択したときにトリガーされるコールバック関数。 それは `iconKey` と `Icon` プロパティを持つオブジェクトを受け取ります |
|
||||
| selectedIconKey | string | 最初に選択されたアイコンのキー |
|
||||
| onClickOutside | 機能 | ユーザーがドロップダウン外をクリックしたときにトリガーされるコールバック関数 |
|
||||
| onClose | 機能 | ドロップダウンが閉じられたときにトリガーされるコールバック関数 |
|
||||
| onOpen | 機能 | ドロップダウンが開かれたときにトリガーされるコールバック関数 |
|
||||
| バリアント | string | クリック可能なアイコンのビジュアルスタイルバリアント。 オプションには `primary`、 `secondary`、 `tertiary` が含まれます |
|
||||
|
||||
</Tab>
|
||||
<Tab title="プロパティ">
|
||||
| プロパティ | タイプ | 説明 |
|
||||
| --------------- | ------ | ------------------------------------------------------------------------------ |
|
||||
| 無効 | ブール型 | `true` に設定されるとアイコンピッカーは無効になります |
|
||||
| onChange | 関数 | ユーザーがアイコンを選択したときにトリガーされるコールバック関数。 それは `iconKey` と `Icon` プロパティを持つオブジェクトを受け取ります |
|
||||
| selectedIconKey | string | 最初に選択されたアイコンのキー |
|
||||
| onClickOutside | 関数 | ユーザーがドロップダウン外をクリックしたときにトリガーされるコールバック関数 |
|
||||
| onClose | 関数 | ドロップダウンが閉じられたときにトリガーされるコールバック関数 |
|
||||
| onOpen | 関数 | ドロップダウンが開かれたときにトリガーされるコールバック関数 |
|
||||
| バリアント | string | クリック可能なアイコンのビジュアルスタイルバリアント。 オプションには `primary`、 `secondary`、 `tertiary` が含まれます |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -4,5 +4,5 @@ image: '""'
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/tasks/tasks_header.png" alt="Header" />
|
||||
</Frame>
|
||||
<img src="""" alt="ヘッダー" />
|
||||
</Frame>
|
||||
|
||||
@@ -4,41 +4,38 @@ image: /images/user-guide/fields/field.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/fields/field.png" alt="Header" />
|
||||
<img src="/images/user-guide/fields/field.png" alt="ヘッダー" />
|
||||
</Frame>
|
||||
|
||||
ブレッドクラムナビゲーションバーをレンダリングします。
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="使用方法">
|
||||
```jsx
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import { Breadcrumb } from "@/ui/navigation/bread-crumb/components/Breadcrumb";
|
||||
|
||||
```jsx
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import { Breadcrumb } from "@/ui/navigation/bread-crumb/components/Breadcrumb";
|
||||
export const MyComponent = () => {
|
||||
const breadcrumbLinks = [
|
||||
{ children: "ホーム", href: "/" },
|
||||
{ children: "カテゴリ", href: "/category" },
|
||||
{ children: "サブカテゴリ", href: "/category/subcategory" },
|
||||
{ children: "現在のページ" },
|
||||
];
|
||||
|
||||
export const MyComponent = () => {
|
||||
const breadcrumbLinks = [
|
||||
{ children: "Home", href: "/" },
|
||||
{ children: "Category", href: "/category" },
|
||||
{ children: "Subcategory", href: "/category/subcategory" },
|
||||
{ children: "Current Page" },
|
||||
];
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Breadcrumb className links={breadcrumbLinks} />
|
||||
</BrowserRouter>
|
||||
)
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Breadcrumb className links={breadcrumbLinks} />
|
||||
</BrowserRouter>
|
||||
)
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| プロパティ | タイプ | 説明 |
|
||||
| --------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| className | string | 追加のスタイリングのためのオプションクラス名 |
|
||||
| リンク | array | 各オブジェクトはブレッドクラムリンクを表します。 各オブジェクトはブレッドクラムリンクを表します。 各オブジェクトはブレッドクラムリンクを表します。 各オブジェクトはブレッドクラムリンクを表します。 各オブジェクトはブレッドクラムリンクを表します。 各オブジェクトはブレッドクラムリンクを表します。 各オブジェクトには `children` プロパティ(リンクのテキストコンテンツ)とオプションの `href` プロパティ(リンクをクリックしたときに移動するURL)が含まれています。 |
|
||||
|
||||
</Tab>
|
||||
<Tab title="プロパティ">
|
||||
| プロパティ | タイプ | 説明 |
|
||||
| --------- | ------ | --------------------------------------------------------------------------------------------------------------------- |
|
||||
| className | string | 追加のスタイリングのためのオプションクラス名 |
|
||||
| リンク | 配列 | 各オブジェクトはブレッドクラムリンクを表します。 各オブジェクトには `children` プロパティ(リンクのテキストコンテンツ)とオプションの `href` プロパティ(リンクをクリックしたときに移動するURL)が含まれています。 |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -4,7 +4,7 @@ image: /images/user-guide/what-is-twenty/20.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/what-is-twenty/20.png" alt="Header" />
|
||||
<img src="/images/user-guide/what-is-twenty/20.png" alt="ヘッダー" />
|
||||
</Frame>
|
||||
|
||||
## 連絡先リンク
|
||||
@@ -12,44 +12,40 @@ image: /images/user-guide/what-is-twenty/20.png
|
||||
連絡先情報を表示するためのスタイライズされたリンクコンポーネントです。
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="使用方法">
|
||||
```jsx
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
|
||||
```jsx
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { ContactLink } from 'twenty-ui/navigation';
|
||||
|
||||
import { ContactLink } from 'twenty-ui/navigation';
|
||||
export const MyComponent = () => {
|
||||
const handleLinkClick = (event) => {
|
||||
console.log('連絡先リンクがクリックされました!', event);
|
||||
};
|
||||
|
||||
export const MyComponent = () => {
|
||||
const handleLinkClick = (event) => {
|
||||
console.log('Contact link clicked!', event);
|
||||
};
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<ContactLink
|
||||
className
|
||||
href="mailto:example@example.com"
|
||||
onClick={handleLinkClick}
|
||||
>
|
||||
example@example.com
|
||||
</ContactLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| プロパティ | タイプ | 説明 |
|
||||
| --------- | ----------------- | --------------------------- |
|
||||
| className | string | 追加スタイル用のオプション名 |
|
||||
| href | string | リンクのターゲットURLまたはパス |
|
||||
| onClick | 機能 | リンクがクリックされるとトリガーされるコールバック関数 |
|
||||
| children | `React.ReactNode` | リンク内に表示されるコンテンツ |
|
||||
|
||||
</Tab>
|
||||
return (
|
||||
<Router>
|
||||
<ContactLink
|
||||
className
|
||||
href="mailto:example@example.com"
|
||||
onClick={handleLinkClick}
|
||||
>
|
||||
example@example.com
|
||||
</ContactLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="プロパティ">
|
||||
| プロパティ | タイプ | 説明 |
|
||||
| --------- | ----------------- | ---------------------------- |
|
||||
| className | string | 追加のスタイリングのためのオプション名 |
|
||||
| href | string | リンクのターゲットURLまたはパス |
|
||||
| onClick | function | リンクがクリックされた際にトリガーされるコールバック関数 |
|
||||
| children | `React.ReactNode` | リンク内に表示されるコンテンツ |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## 生リンク
|
||||
@@ -57,39 +53,35 @@ export const MyComponent = () => {
|
||||
リンクを表示するためのスタイライズされたリンクコンポーネントです。
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="使用方法">
|
||||
```jsx
|
||||
import { RawLink } from "/navigation";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
|
||||
```jsx
|
||||
import { RawLink } from "/navigation";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
export const MyComponent = () => {
|
||||
const handleLinkClick = (event) => {
|
||||
console.log("連絡先リンクがクリックされました!", event);
|
||||
};
|
||||
|
||||
export const MyComponent = () => {
|
||||
const handleLinkClick = (event) => {
|
||||
console.log("Contact link clicked!", event);
|
||||
};
|
||||
return (
|
||||
<Router>
|
||||
<RawLink className href="/contact" onClick={handleLinkClick}>
|
||||
お問い合わせ
|
||||
</RawLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<RawLink className href="/contact" onClick={handleLinkClick}>
|
||||
Contact Us
|
||||
</RawLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| プロパティ | タイプ | 説明 |
|
||||
| --------- | ----------------- | --------------------------- |
|
||||
| className | string | 追加のスタイリングのためのオプション名 |
|
||||
| href | string | リンクのターゲットURLまたはパス |
|
||||
| onClick | 機能 | リンクがクリックされるとトリガーされるコールバック関数 |
|
||||
| children | `React.ReactNode` | リンク内に表示されるコンテンツ |
|
||||
|
||||
</Tab>
|
||||
<Tab title="プロパティ">
|
||||
| プロパティ | タイプ | 説明 |
|
||||
| --------- | ----------------- | ---------------------------- |
|
||||
| className | string | 追加のスタイリングのためのオプション名 |
|
||||
| href | string | リンクのターゲットURLまたはパス |
|
||||
| onClick | 機能 | リンクがクリックされた際にトリガーされるコールバック関数 |
|
||||
| children | `React.ReactNode` | リンク内に表示されるコンテンツ |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## 丸リンク
|
||||
@@ -97,38 +89,34 @@ export const MyComponent = () => {
|
||||
リンクのためのチップコンポーネントを備えた丸いスタイルのリンクです。
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="使用方法">
|
||||
```jsx
|
||||
import { RoundedLink } from "/navigation";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
|
||||
```jsx
|
||||
import { RoundedLink } from "/navigation";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
export const MyComponent = () => {
|
||||
const handleLinkClick = (event) => {
|
||||
console.log("連絡先リンクがクリックされました!", event);
|
||||
};
|
||||
|
||||
export const MyComponent = () => {
|
||||
const handleLinkClick = (event) => {
|
||||
console.log("Contact link clicked!", event);
|
||||
};
|
||||
return (
|
||||
<Router>
|
||||
<RoundedLink href="/contact" onClick={handleLinkClick}>
|
||||
お問い合わせ
|
||||
</RoundedLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<RoundedLink href="/contact" onClick={handleLinkClick}>
|
||||
Contact Us
|
||||
</RoundedLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Props">
|
||||
|
||||
| プロパティ | タイプ | 説明 |
|
||||
| -------- | ----------------- | --------------------------- |
|
||||
| href | string | リンクのターゲットURLまたはパス |
|
||||
| children | `React.ReactNode` | リンク内に表示されるコンテンツ |
|
||||
| onClick | 機能 | リンクがクリックされるとトリガーされるコールバック関数 |
|
||||
|
||||
</Tab>
|
||||
<Tab title="プロパティ">
|
||||
| プロパティ | タイプ | 説明 |
|
||||
| -------- | ----------------- | ---------------------------- |
|
||||
| href | string | リンクのターゲットURLまたはパス |
|
||||
| children | `React.ReactNode` | リンク内に表示されるコンテンツ |
|
||||
| onClick | 機能 | リンクがクリックされた際にトリガーされるコールバック関数 |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## ソーシャルリンク
|
||||
@@ -136,33 +124,30 @@ export const MyComponent = () => {
|
||||
URL、LinkedIn、X(またはTwitter)など、さまざまなソーシャルリンクタイプに対応したスタイライズされたソーシャルリンクです。
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="使用方法">
|
||||
```jsx
|
||||
import { SocialLink } from "twenty-ui/navigation";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
|
||||
```jsx
|
||||
import { SocialLink } from "twenty-ui/navigation";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<Router>
|
||||
<SocialLink
|
||||
type="twitter"
|
||||
href="https://twitter.com/twentycrm"
|
||||
></SocialLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<Router>
|
||||
<SocialLink
|
||||
type="twitter"
|
||||
href="https://twitter.com/twentycrm"
|
||||
></SocialLink>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| プロパティ | タイプ | 説明 |
|
||||
| -------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| href | string | リンクのターゲットURLまたはパス |
|
||||
| children | `React.ReactNode` | リンク内に表示されるコンテンツ |
|
||||
| タイプ | string | ソーシャルリンクの種類です。 ソーシャルリンクの種類です。 オプションには、`url`、`LinkedIn`、`Twitter`があります。 ソーシャルリンクの種類です。 オプションには、`url`、`LinkedIn`、`Twitter`があります。 |
|
||||
| onClick | 機能 | リンクがクリックされるとトリガーされるコールバック関数 |
|
||||
|
||||
</Tab>
|
||||
<Tab title="プロパティ">
|
||||
| プロパティ | タイプ | 説明 |
|
||||
| -------- | ----------------- | ------------------------------------------------------- |
|
||||
| href | string | リンクのターゲットURLまたはパス |
|
||||
| children | `React.ReactNode` | リンク内に表示されるコンテンツ |
|
||||
| タイプ | string | ソーシャルリンクの種類です。 オプションには、`url`、`LinkedIn`、`Twitter`があります。 |
|
||||
| onClick | function | リンクがクリックされた際にトリガーされるコールバック関数 |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -4,49 +4,46 @@ image: /images/user-guide/table-views/table.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/table-views/table.png" alt="Header" />
|
||||
<img src="/images/user-guide/table-views/table.png" alt="ヘッダー" />
|
||||
</Frame>
|
||||
|
||||
複数の`NavigationBarItem`コンポーネントを含むナビゲーションバーをレンダリングします。
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="使用方法">
|
||||
```jsx
|
||||
import { IconHome, IconUser, IconSettings } from '@tabler/icons-react';
|
||||
import { NavigationBar } from "@/ui/navigation/navigation-bar/components/NavigationBar";
|
||||
|
||||
```jsx
|
||||
import { IconHome, IconUser, IconSettings } from '@tabler/icons-react';
|
||||
import { NavigationBar } from "@/ui/navigation/navigation-bar/components/NavigationBar";
|
||||
export const MyComponent = () => {
|
||||
|
||||
export const MyComponent = () => {
|
||||
const navigationItems = [
|
||||
{
|
||||
name: "ホーム",
|
||||
Icon: IconHome,
|
||||
onClick: () => console.log("ホームがクリックされました"),
|
||||
},
|
||||
{
|
||||
name: "プロフィール",
|
||||
Icon: IconUser,
|
||||
onClick: () => console.log("プロフィールがクリックされました"),
|
||||
},
|
||||
{
|
||||
name: "設定",
|
||||
Icon: IconSettings,
|
||||
onClick: () => console.log("設定がクリックされました"),
|
||||
},
|
||||
];
|
||||
|
||||
const navigationItems = [
|
||||
{
|
||||
name: "Home",
|
||||
Icon: IconHome,
|
||||
onClick: () => console.log("Home clicked"),
|
||||
},
|
||||
{
|
||||
name: "Profile",
|
||||
Icon: IconUser,
|
||||
onClick: () => console.log("Profile clicked"),
|
||||
},
|
||||
{
|
||||
name: "Settings",
|
||||
Icon: IconSettings,
|
||||
onClick: () => console.log("Settings clicked"),
|
||||
},
|
||||
];
|
||||
return <NavigationBar activeItemName="ホーム" items={navigationItems}/>;
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
return <NavigationBar activeItemName="Home" items={navigationItems}/>;
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Props">
|
||||
|
||||
| プロパティ | タイプ | 説明 |
|
||||
| -------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| activeItemName | string | 現在アクティブなナビゲーション項目の名前 |
|
||||
| アイテム | array | 各ナビゲーションアイテムを表すオブジェクトの配列。 各ナビゲーションアイテムを表すオブジェクトの配列。 各ナビゲーションアイテムを表すオブジェクトの配列。 各ナビゲーションアイテムを表すオブジェクトの配列。 各ナビゲーションアイテムを表すオブジェクトの配列。 各オブジェクトには、アイテムの `name`、表示する `Icon` コンポーネント、およびアイテムがクリックされたときに呼び出される `onClick` 関数が含まれています。 |
|
||||
|
||||
</Tab>
|
||||
<Tab title="プロパティ">
|
||||
| プロパティ | タイプ | 説明 |
|
||||
| -------------- | ------ | ------------------------------------------------------------------------------------------------------------------- |
|
||||
| activeItemName | string | 現在アクティブなナビゲーション項目の名前 |
|
||||
| アイテム | 配列 | 各ナビゲーションアイテムを表すオブジェクトの配列。 各オブジェクトには、アイテムの `name`、表示する `Icon` コンポーネント、およびアイテムがクリックされたときに呼び出される `onClick` 関数が含まれています。 |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -4,35 +4,31 @@ image: /images/user-guide/api/api.png
|
||||
---
|
||||
|
||||
<Frame>
|
||||
<img src="/images/user-guide/api/api.png" alt="Header" />
|
||||
<img src="/images/user-guide/api/api.png" alt="ヘッダー" />
|
||||
</Frame>
|
||||
|
||||
アクティブなステップをハイライトして、一連の番号付きステップの進行状況を表示します。 各 `Step` コンポーネントによって表されるステップを含むコンテナをレンダリングします。 各 `Step` コンポーネントによって表されるステップを含むコンテナをレンダリングします。 アクティブなステップをハイライトして、一連の番号付きステップの進行状況を表示します。 各 `Step` コンポーネントによって表されるステップを含むコンテナをレンダリングします。 各 `Step` コンポーネントによって表されるステップを含むコンテナをレンダリングします。 各 `Step` コンポーネントによって表されるステップを含むコンテナをレンダリングします。
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Tab title="使用方法">
|
||||
```jsx
|
||||
import { StepBar } from "@/ui/navigation/step-bar/components/StepBar";
|
||||
|
||||
```jsx
|
||||
import { StepBar } from "@/ui/navigation/step-bar/components/StepBar";
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<StepBar activeStep={2}>
|
||||
<StepBar.Step>Step 1</StepBar.Step>
|
||||
<StepBar.Step>Step 2</StepBar.Step>
|
||||
<StepBar.Step>Step 3</StepBar.Step>
|
||||
</StepBar>
|
||||
);
|
||||
};
|
||||
```
|
||||
</Tab>
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<StepBar activeStep={2}>
|
||||
<StepBar.Step>Step 1</StepBar.Step>
|
||||
<StepBar.Step>Step 2</StepBar.Step>
|
||||
<StepBar.Step>Step 3</StepBar.Step>
|
||||
</StepBar>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Props">
|
||||
|
||||
| プロパティ | タイプ | 説明 |
|
||||
| --------- | --- | -------------------------------------------------------------------------------------- |
|
||||
| アクティブステップ | 数 | 現在アクティブなステップのインデックス。 これにより、どのステップを視覚的にハイライトするかが決まります。 これにより、どのステップを視覚的にハイライトするかが決まります。 |
|
||||
|
||||
</Tab>
|
||||
<Tab title="プロパティ">
|
||||
| プロパティ | タイプ | 説明 |
|
||||
| --------- | --- | ----------------------------------------------------- |
|
||||
| アクティブステップ | 数 | 現在アクティブなステップのインデックス。 これにより、どのステップを視覚的にハイライトするかが決まります。 |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: 모범 사례
|
||||
---
|
||||
|
||||
이 문서는 백엔드 작업 시 따를 모범 사례를 설명합니다.
|
||||
|
||||
## 모듈형 접근법 따르기
|
||||
|
||||
백엔드는 NestJS로 작업할 때 기본 원칙 중 하나인 모듈형 접근법을 따릅니다. 코드베이스를 깔끔하고 체계적으로 유지하려면 코드를 재사용 가능한 모듈로 분리하십시오.
|
||||
각 모듈은 특정 기능을 캡슐화하고 명확하게 정의된 범위를 가져야 합니다. 이 모듈형 접근법은 관심사의 명확한 분리를 가능하게 하고 불필요한 복잡성을 제거합니다.
|
||||
|
||||
## 모듈에서 사용할 서비스를 노출시키십시오.
|
||||
|
||||
항상 명확하고 단일한 책임을 가진 서비스를 생성하여 코드 가독성과 유지보수성을 향상시킵니다. 서비스의 이름을 일관되게 설명적으로 지정하십시오.
|
||||
|
||||
다른 모듈에서 사용하고자 하는 서비스를 노출해야 합니다. 다른 모듈에 서비스를 노출하는 것은 NestJS의 강력한 의존성 주입 시스템을 통해 가능하며, 구성 요소 간의 느슨한 결합을 촉진합니다.
|
||||
|
||||
## `any` 타입 사용 피하기
|
||||
|
||||
변수를 `any`로 선언하면 TypeScript의 타입 검사자가 타입 검사를 수행하지 않으므로 변수에 모든 유형의 값을 할당할 수 있게 됩니다. TypeScript는 값에 따라 변수의 타입을 추론하기 위해 타입 추론을 사용합니다. 이를 `any`로 선언하면 TypeScript는 더 이상 타입을 추론할 수 없습니다. 이는 개발 중 타입 관련 오류를 잡기 어렵게 만들어 런타임 오류로 이어지고, 코드의 유지보수성과 신뢰성이 떨어지며, 다른 사람이 이해하기도 어려워집니다.
|
||||
|
||||
이것이 모든 것이 타입을 가져야 하는 이유입니다. 따라서 이름(first name)과 성(last name)을 가진 새 개체를 만든다면, 다루는 개체의 구조를 정의하는 이름과 성을 포함한 인터페이스나 타입을 만들어야 합니다.
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
---
|
||||
title: 폴더 아키텍처
|
||||
info: 우리의 폴더 아키텍처를 자세히 살펴보기
|
||||
---
|
||||
|
||||
이 가이드에서는 프로젝트 디렉토리 구조의 세부 사항과 그것이 Twenty의 구조화와 유지관리성에 어떻게 기여하는지 살펴봅니다.
|
||||
|
||||
이 폴더 아키텍처 관례를 따르면 특정 기능과 관련된 파일을 더 쉽게 찾을 수 있고, 애플리케이션의 확장성과 유지관리성을 보장하기가 쉬워집니다.
|
||||
|
||||
```
|
||||
front
|
||||
└───modules
|
||||
│ └───module1
|
||||
│ │ └───submodule1
|
||||
│ └───module2
|
||||
│ └───ui
|
||||
│ │ └───display
|
||||
│ │ └───inputs
|
||||
│ │ │ └───buttons
|
||||
│ │ └───...
|
||||
└───pages
|
||||
└───...
|
||||
```
|
||||
|
||||
## 페이지
|
||||
|
||||
애플리케이션 라우트로 정의된 최상위 컴포넌트를 포함합니다. 모듈 폴더에서 더 하위 수준의 컴포넌트를 가져옵니다(자세한 내용은 아래를 참조하세요).
|
||||
|
||||
## 모듈
|
||||
|
||||
각 모듈은 특정 컴포넌트, 상태 및 운영 로직을 포함하는 기능 또는 기능 그룹을 나타냅니다.
|
||||
모두 아래 구조를 따라야 합니다. 모듈 안에 모듈을 중첩할 수 있으며(이를 서브모듈이라고 하며), 동일한 규칙이 적용됩니다.
|
||||
|
||||
```
|
||||
module1
|
||||
└───components
|
||||
│ └───component1
|
||||
│ └───component2
|
||||
└───constants
|
||||
└───contexts
|
||||
└───graphql
|
||||
│ └───fragments
|
||||
│ └───queries
|
||||
│ └───mutations
|
||||
└───hooks
|
||||
│ └───internal
|
||||
└───states
|
||||
│ └───selectors
|
||||
└───types
|
||||
└───utils
|
||||
```
|
||||
|
||||
### 컨텍스트
|
||||
|
||||
컨텍스트는 각 레벨에서 수동으로 props를 전달하지 않고, 컴포넌트 트리를 통해 데이터를 전달할 수 있는 방법입니다.
|
||||
|
||||
자세한 내용은 [React Context](https://react.dev/reference/react#context-hooks)를 참조하세요.
|
||||
|
||||
### GraphQL
|
||||
|
||||
프래그먼트, 쿼리 및 뮤테이션이 포함됩니다.
|
||||
|
||||
자세한 내용은 [GraphQL](https://graphql.org/learn/)을 참조하세요.
|
||||
|
||||
* 프래그먼트
|
||||
|
||||
프래그먼트는 쿼리의 재사용 가능한 조각으로, 여러 장소에서 사용할 수 있습니다. 프래그먼트를 사용하면 코드 중복을 피하기가 더 쉬워집니다.
|
||||
|
||||
자세한 내용은 [GraphQL 프래그먼트](https://graphql.org/learn/queries/#fragments)를 참조하세요.
|
||||
|
||||
* 쿼리
|
||||
|
||||
자세한 내용은 [GraphQL 쿼리](https://graphql.org/learn/queries/)를 참조하세요.
|
||||
|
||||
* 뮤테이션
|
||||
|
||||
자세한 내용은 [GraphQL 뮤테이션](https://graphql.org/learn/queries/#mutations)을 참조하세요.
|
||||
|
||||
### 훅
|
||||
|
||||
자세한 내용은 [훅](https://react.dev/learn/reusing-logic-with-custom-hooks)을 참조하세요.
|
||||
|
||||
### 상태
|
||||
|
||||
상태 관리 로직이 포함되어 있습니다. [RecoilJS](https://recoiljs.org)가 이것을 처리합니다.
|
||||
|
||||
* 셀렉터: 자세한 내용은 [RecoilJS 셀렉터](https://recoiljs.org/docs/basic-tutorial/selectors)를 참조하세요.
|
||||
|
||||
React의 내장 상태 관리는 구성 요소 내에서의 상태를 여전히 처리합니다.
|
||||
|
||||
### 유틸
|
||||
|
||||
재사용 가능한 순수 함수를 포함해야 합니다. 그렇지 않으면 `hooks` 폴더에 커스텀 훅을 만들어야 합니다.
|
||||
|
||||
## UI
|
||||
|
||||
애플리케이션에서 사용되는 모든 재사용 가능한 UI 컴포넌트를 포함합니다.
|
||||
|
||||
이 폴더는 `data`, `display`, `feedback`, `input`과 같은 특정 유형의 컴포넌트에 대한 하위 폴더를 포함할 수 있습니다. 각 컴포넌트는 자기 완결적이고 재사용 가능해야 하며, 애플리케이션의 다른 부분에서 사용할 수 있어야 합니다.
|
||||
|
||||
`modules` 폴더의 다른 컴포넌트에서 UI 컴포넌트를 분리함으로써 일관된 디자인을 유지하고, 코드베이스의 다른 부분(비즈니스 로직)에 영향을 주지 않고 UI를 변경하기가 더 쉬워집니다.
|
||||
|
||||
## 인터페이스 및 종속성
|
||||
|
||||
`ui` 폴더를 제외한 모든 모듈에서 다른 모듈 코드를 가져올 수 있습니다. 이렇게 하면 코드를 쉽게 테스트할 수 있습니다.
|
||||
|
||||
### 내부
|
||||
|
||||
각 부분(훅, 상태, ...) 모듈의 각 부분은 모듈 내에서만 사용하는 `internal` 폴더를 가질 수 있습니다.
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
---
|
||||
title: 프론트엔드 명령어
|
||||
---
|
||||
|
||||
## 유용한 명령어
|
||||
|
||||
### 앱 시작하기
|
||||
|
||||
```bash
|
||||
npx nx start twenty-front
|
||||
```
|
||||
|
||||
### API GraphQL 스키마를 기반으로 GraphQL 스키마 재생성
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:graphql:generate --configuration=metadata
|
||||
```
|
||||
|
||||
또는
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:graphql:generate
|
||||
```
|
||||
|
||||
### 린트
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:lint # lint 오류를 수정하려면 --fix 전달
|
||||
```
|
||||
|
||||
## 번역
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:lingui:extract
|
||||
npx nx run twenty-front:lingui:compile
|
||||
```
|
||||
|
||||
### 테스트
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:test # jest 테스트 실행
|
||||
npx nx run twenty-front:storybook:serve:dev # storybook 실행
|
||||
npx nx run twenty-front:storybook:test # 테스트 실행 # (yarn storybook:serve:dev 실행 필요)
|
||||
npx nx run twenty-front:storybook:coverage # (yarn storybook:serve:dev 실행 필요)
|
||||
```
|
||||
|
||||
## 기술 스택
|
||||
|
||||
프로젝트는 최소한의 보일러플레이트 코드로 깔끔하고 단순한 스택을 가지고 있습니다.
|
||||
|
||||
**앱**
|
||||
|
||||
* [React](https://react.dev/)
|
||||
* [Apollo](https://www.apollographql.com/docs/)
|
||||
* [GraphQL Codegen](https://the-guild.dev/graphql/codegen)
|
||||
* [Recoil](https://recoiljs.org/docs/introduction/core-concepts)
|
||||
* [TypeScript](https://www.typescriptlang.org/)
|
||||
|
||||
**테스트**
|
||||
|
||||
* [Jest](https://jestjs.io/)
|
||||
* [Storybook](https://storybook.js.org/)
|
||||
|
||||
**도구**
|
||||
|
||||
* [Yarn](https://yarnpkg.com/)
|
||||
* [Craco](https://craco.js.org/docs/)
|
||||
* [ESLint](https://eslint.org/)
|
||||
|
||||
## 아키텍처
|
||||
|
||||
### 라우팅
|
||||
|
||||
[React Router](https://reactrouter.com/)가 라우팅을 처리합니다.
|
||||
|
||||
불필요한 [재렌더링](/l/ko/developers/contribute/capabilities/frontend-development/best-practices-front#managing-re-renders)을 피하기 위해 모든 라우팅 로직은 `PageChangeEffect`의 `useEffect`에 있습니다.
|
||||
|
||||
### 상태 관리
|
||||
|
||||
[Recoil](https://recoiljs.org/docs/introduction/core-concepts)이 상태 관리를 처리합니다.
|
||||
|
||||
상태 관리에 대한 자세한 정보는 [최고의 관례](/l/ko/developers/contribute/capabilities/frontend-development/best-practices-front#state-management)를 참조하십시오.
|
||||
|
||||
## 테스트
|
||||
|
||||
[Jest](https://jestjs.io/)는 유닛 테스트 도구로 사용되고 [Storybook](https://storybook.js.org/)은 컴포넌트 테스트에 사용됩니다.
|
||||
|
||||
Jest는 주로 유틸리티 함수 테스트에 사용되며, 직접 컴포넌트를 테스트하지는 않습니다.
|
||||
|
||||
Storybook은 개별 컴포넌트의 동작을 테스트하고 디자인 시스템을 표시하는 데 사용됩니다.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user