i18n - docs translations (#22281)

Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22281?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

Co-authored-by: github-actions <github-actions@twenty.com>
This commit is contained in:
github-actions[bot]
2026-06-29 07:22:03 +02:00
committed by GitHub
parent 655b788e8d
commit 68f4cf269d
196 changed files with 4412 additions and 1235 deletions
@@ -7,18 +7,18 @@ info: نظرة تفصيلية داخل هيكلية مجلدات الخادم
```
server
└───قدرات
└───ثوابت
└───نواة
└───قاعدة البيانات
└───زخارف
└───فلاتر
└───حمايات
└───الصحة
└───تكاملات
└───بيانات وصفية
└───مساحة العمل
└───أدوات
└───ability
└───constants
└───core
└───database
└───decorators
└───filters
└───guards
└───health
└───integrations
└───metadata
└───workspace
└───utils},{
```
## قدرات
@@ -56,28 +56,28 @@ server
```
workspace
└───منشئ مساحة العمل
└───مصانع
└───أنواع GraphQL
└───قاعدة البيانات
└───واجهات
└───تعريفات الكائنات
└───خدمات
└───التخزين
└───أدوات
└───منشئ مستعرض مساحة العمل
└───مصانع
└───واجهات
└───منشئ الاستعلامات مساحة العمل
└───مصانع
└───واجهات
└───مشغل استعلامات مساحة العمل
└───واجهات
└───أدوات
└───مصدر بيانات مساحة العمل
└───مدير مساحة العمل
└───مشغل الانتقالات مساحة العمل
└───أدوات
└───workspace-schema-builder
└───factories
└───graphql-types
└───database
└───interfaces
└───object-definitions
└───services
└───storage
└───utils
└───workspace-resolver-builder
└───factories
└───interfaces
└───workspace-query-builder
└───factories
└───interfaces
└───workspace-query-runner
└───interfaces
└───utils
└───workspace-datasource
└───workspace-manager
└───workspace-migration-runner
└───utils
└───workspace.module.ts
└───workspace.factory.spec.ts
└───workspace.factory.ts
@@ -11,7 +11,7 @@ icon: terminal
### إعداد المرة الأولى
```
npx nx database:reset twenty-server # إعداد قاعدة البيانات مع بذور التطوير
npx nx database:reset twenty-server # setup the database with dev seeds
```
### بدء الخادم
@@ -23,14 +23,14 @@ npx nx run twenty-server:start
### Lint
```
npx nx run twenty-server:lint # مرر --fix لإصلاح أخطاء التدقيق
npx nx run twenty-server:lint # pass --fix to fix lint errors},{
```
### تجربة
```
npx nx run twenty-server:test:unit # تشغيل اختبارات الوحدة
npx nx run twenty-server:test:integration # تشغيل اختبارات التكامل
npx nx run twenty-server:test:unit # run unit tests
npx nx run twenty-server:test:integration # run integration tests
```
ملاحظة: يمكنك تشغيل `npx nx run twenty-server:test:integration:with-db-reset` في حالة احتياجك لإعادة تعيين قاعدة البيانات قبل تشغيل اختبارات التكامل.
@@ -83,8 +83,8 @@ If you feel like you need to add a `useEffect` in your root component, you shoul
يمكنك تطبيق نفس الشيء على منطق جلب البيانات، مع الخُطافات Apollo.
```tsx
// ❌ سيئ، سيتسبب في إعادة التصيير حتى إذا لم تتغير البيانات،
// لأن useEffect يحتاج إلى إعادة التقييم
// ❌ Bad, will cause re-renders even if data is not changing,
// because useEffect needs to be re-evaluated
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
@@ -104,8 +104,8 @@ export const App = () => (
```
```tsx
// ✅ جيّد، لن يتسبب في إعادة التصيير إذا لم تتغير البيانات،
// لأن useEffect يُعاد تقييمه في مكوّن شقيق آخر
// ✅ Good, will not cause re-renders if data is not changing,
// because useEffect is re-evaluated in another sibling component
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
@@ -172,13 +172,13 @@ export const App = () => (
الأسماء العامة في البرمجة ليست مثالية لأنها تفتقر إلى التحديد، مما يؤدي إلى الغموض وتقليل قابلية قراءة التعليمات البرمجية. مثل هذه الأسماء تفشل في التعبير عن الغرض من المتغير أو الوظيفة، مما يجعل من الصعب على المطورين فهم نية التعليمات البرمجية دون تحقيق أعمق. يمكن أن يؤدي ذلك إلى زيادة وقت إزالة الأخطاء، وزيادة قابلية التعرض للأخطاء، وصعوبات في الصيانة والتعاون. في الوقت نفسه، تجعل التسمية الوصفية التعليمات البرمجية تفسيرية بذاتها وأسهل في التنقل، مما يعزز جودة التعليمات البرمجية وإنتاجية المطور.
```tsx
// ❌ سيّئ، يستخدم اسمًا عامًا لا يوضح
// الغرض أو المحتوى بوضوح
// ❌ Bad, uses a generic name that doesn't communicate its
// purpose or content clearly
const [value, setValue] = useState('');
```
```tsx
// ✅ جيّد، يستخدم اسمًا وصفيًا
// ✅ Good, uses a descriptive name
const [email, setEmail] = useState('');
```
@@ -191,14 +191,14 @@ const [email, setEmail] = useState('');
يجب أن تبدأ أسماء معالجات الأحداث بكلمة `handle`، بينما يعتبر `on` بادئة تستخدم لتسمية الأحداث في خصائص المكونات.
```tsx
// ❌ سيّئ
// ❌ Bad
const onEmailChange = (val: string) => {
// ...
};
```
```tsx
// ✅ جيّد
// ✅ Good
const handleEmailChange = (val: string) => {
// ...
};
@@ -226,12 +226,12 @@ const EmailField = ({ value, disabled = false }: EmailFieldProps) => (
**الاستخدام**
```tsx
// ❌ سيّئ، تمرير نفس القيمة كقيمة افتراضية لا يضيف أي فائدة
// ❌ Bad, passing in the same value as the default value adds no value
const Form = () => <EmailField value="username@email.com" disabled={false} />;
```
```tsx
// ✅ جيّد، يفترض القيمة الافتراضية
// ✅ Good, assumes the default value
const Form = () => <EmailField value="username@email.com" />;
```
@@ -244,7 +244,7 @@ const Form = () => <EmailField value="username@email.com" />;
```tsx
const SomeParentComponent = () => <MyComponent Icon={MyIcon} />;
// داخل MyComponent
// In MyComponent
const MyComponent = ({ MyIcon }: { MyIcon: IconComponent }) => {
const theme = useTheme();
@@ -289,7 +289,7 @@ const MyComponent = ({ MyIcon }: { MyIcon: IconComponent }) => {
**الاستخدام**
```tsx
// ❌ سيّئ، يحدد المسار النسبي بالكامل
// ❌ Bad, specifies the entire relative path
import {
CatalogDecorator
} from '../../../../../testing/decorators/CatalogDecorator';
@@ -299,7 +299,7 @@ import {
```
```tsx
// ✅ جيّد، يستخدم الأسماء المستعارة المحددة
// ✅ Good, utilises the designated aliases
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from 'twenty-ui/testing';
```
@@ -314,10 +314,10 @@ const validationSchema = z
exist: z.boolean(),
email: z
.string()
.email('يجب أن يكون البريد الإلكتروني صالحًا'),
.email('Email must be a valid email'),
password: z
.string()
.regex(PASSWORD_REGEX, 'يجب أن تحتوي كلمة المرور على 8 أحرف على الأقل'),
.regex(PASSWORD_REGEX, 'Password must contain at least 8 characters'),
})
.required();
@@ -8,37 +8,41 @@ icon: terminal
### "بدء التطبيق"
```bash
"npx nx start twenty-front"
npx nx start twenty-front
```
### "إعادة توليد مخطط graphql بناءً على مخطط API graphql"
```bash
"npx nx run twenty-front:graphql:generate --configuration=metadata"
npx nx run twenty-front:graphql:generate --configuration=metadata
```
"أو"
```bash
"npx nx run twenty-front:graphql:generate"
npx nx run twenty-front:graphql:generate
```
### Lint
```bash
"npx nx run twenty-front:lint # مرر --fix لإصلاح أخطاء التدقيق"
npx nx run twenty-front:lint # pass --fix to fix lint errors
```
## "الترجمات"
```bash
"npx nx run twenty-front:lingui:extract\nnpx nx run twenty-front:lingui:compile"
npx nx run twenty-front:lingui:extract
npx 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)"
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)
```
## "التقنية المستخدمة"
@@ -68,10 +68,10 @@ const EmailField: React.FC<{
```
```tsx
/* ✅ - جيد، نوع منفصل (OwnProps) يُعرَّف صراحةً لخصائص
* المكون
* - هذه الطريقة لا تتضمن تلقائيًا خاصية الأطفال. إذا
* كنت تريد تضمينها، يجب أن تحددها في OwnProps.
/* ✅ - Good, a separate type (OwnProps) is explicitly defined for the
* component's props
* - This method doesn't automatically include the children prop. If
* you want to include it, you have to specify it in OwnProps.
*/
type EmailFieldProps = {
value: string;
@@ -95,8 +95,8 @@ const MyComponent = (props: OwnProps) => {
```
```tsx
/* ✅ - جيد، يسرد جميع الخصائص بوضوح
* - يعزز من قابلية القراءة والصيانة
/* ✅ - Good, Explicitly lists all props
* - Enhances readability and maintainability
*/
const MyComponent = ({ prop1, prop2, prop3 }: MyComponentProps) => {
return <OtherComponent {...{ prop1, prop2, prop3 }} />;
@@ -114,20 +114,20 @@ const MyComponent = ({ prop1, prop2, prop3 }: MyComponentProps) => {
### استخدم معامل دمج القيم الفارغة `??`
```tsx
// ❌ سيء، قد يعيد 'default' حتى إذا كانت القيمة 0 أو ''
// ❌ Bad, can return 'default' even if value is 0 or ''
const value = process.env.MY_VALUE || 'default';
// ✅ جيد، سيعيد 'default' فقط إذا كانت القيمة null أو غير معرّفة
// ✅ Good, will return 'default' only if value is null or undefined
const value = process.env.MY_VALUE ?? 'default';
```
### استخدم معامل الربط الاختياري `?.`
```tsx
// ❌ سيء
// ❌ Bad
onClick && onClick();
// ✅ جيد
// ✅ Good
onClick?.();
```
@@ -167,7 +167,7 @@ let color = Color.Red;
```
```tsx
// ✅ جيد، يستخدم حرفا مشفوعا
// ✅ Good, utilizes a string literal
let color: "red" | "green" | "blue" = "red";
```
@@ -198,12 +198,12 @@ setHotkeyScopeAndMemorizePreviousScope(
قم بتنسيق المكونات باستخدام [Linaria styled](https://github.com/callstack/linaria).
```tsx
// ❌ سيء
// ❌ Bad
<div className="my-class">Hello World</div>
```
```tsx
// ✅ جيد
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
@@ -212,14 +212,14 @@ const StyledTitle = styled.div`
قم بإضافة بادئة للمكونات المنسقة بـ "Styled" لتمييزها عن المكونات "الحقيقية".
```tsx
// ❌ سيء
// ❌ Bad
const Title = styled.div`
color: red;
`;
```
```tsx
// ✅ جيد
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
@@ -238,7 +238,7 @@ const StyledTitle = styled.div`
امتنع عن تقديم ألوان جديدة، بدلاً من ذلك، استخدم اللوحة الموجودة في السمة. إذا كانت هناك حالة لا تتطابق فيها اللوحة، يرجى ترك تعليق لكي تتمكن الفريق من تصحيحها.
```tsx
// ❌ سيء، يحدد القيم المشفوعة للأسلوب دون استخدام السمة
// ❌ Bad, directly specifies style values without utilizing the theme
const StyledButton = styled.button`
color: #333333;
font-size: 1rem;
@@ -249,7 +249,7 @@ const StyledButton = styled.button`
```
```tsx
// ✅ جيد، يستعمل السمة
// ✅ Good, utilizes the theme
const StyledButton = styled.button`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
@@ -59,9 +59,9 @@ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
```bash
nvm install # يثبت إصدار node الموصى به
nvm install # installs recommended node version
nvm use # استخدم إصدار node الموصى به
nvm use # use recommended node version
corepack enable
```
@@ -119,15 +119,15 @@ cd twenty
**الخيار 1 (المفضل):** لتوفير قاعدة بياناتك محليًا مع `brew`:
```bash
brew install postgresql@16
export PATH="/opt/homebrew/opt/postgresql@16/bin:$PATH"
brew services start postgresql@16
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
brew install postgresql@16
export PATH="/opt/homebrew/opt/postgresql@16/bin:$PATH"
brew services start postgresql@16
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
يمكنك التحقق مما إذا كان خادم PostgreSQL يعمل بتنفيذ:
```bash
brew services list
brew services list
```
قد لا يقوم المُثبِّت بإنشاء المستخدم `postgres` افتراضيًا عند التثبيت
@@ -135,35 +135,35 @@ cd twenty
اسم المستخدم الخاص بك في MacOS (مثل "john").
للتحقق وإنشاء المستخدم `postgres` إذا لزم الأمر، اتبع هذه الخطوات:
```bash
# قم بالاتصال بPostgreSQL
psql postgres
أو
psql -U $(whoami) -d postgres
# Connect to PostgreSQL
psql postgres
or
psql -U $(whoami) -d postgres
```
بمجرد أن تكون عند مطالبة psql (postgres=#)، قم بتشغيل:
```bash
# قائمة الأدوار الموجودة في PostgreSQL
\du
# List existing PostgreSQL roles
\du
```
سترى مخرجات مشابهة ل:
```bash
اسم الأدوار | الخصائص | عضو في
-----------+-------------+-----------
john | مشرف نظام | {}
Role name | Attributes | Member of
-----------+-------------+-----------
john | Superuser | {}
```
إذا لم ترَ دور `postgres` مدرجًا، انتقل إلى الخطوة التالية.
قم بإنشاء دور `postgres` يدويًا:
```bash
CREATE ROLE postgres WITH SUPERUSER LOGIN;
CREATE ROLE postgres WITH SUPERUSER LOGIN;
```
يقوم هذا بإنشاء دور مشرف نظام باسم `postgres` مع إمكانية تسجيل الدخول.
```bash
اسم الدور | الخصائص | عضو في
Role name | Attributes | Member of
-----------+-------------+-----------
postgres | مشرف نظام | {}
john | مشرف نظام | {}
postgres | Superuser | {}
john | Superuser | {}
```
**الخيار 2:** إذا كنت قد قمت بتثبيت docker:
@@ -178,7 +178,8 @@ SSL (HTTPS) مطلوب لعمل ميزات معينة في المتصفح بشك
لتطبيق التغييرات، أعد تشغيل حاويات Docker:
```bash
docker compose down\ndocker compose up -d
docker compose down
docker compose up -d
```
#### اعتبارات
@@ -16,7 +16,7 @@ icon: gear
## 1. إعداد لوحة الإدارة (افتراضي)
```bash
IS_CONFIG_VARIABLES_IN_DB_ENABLED=true # افتراضي
IS_CONFIG_VARIABLES_IN_DB_ENABLED=true # default
```
**يحدث أغلب التكوين عبر واجهة المستخدم** بعد التثبيت:
@@ -23,9 +23,9 @@ icon: wrench
لتحديث `PG_DATABASE_PASSWORD` عليك القيام بما يلي:
```sh
# تحديث PG_DATABASE_PASSWORD في .env
إيقاف تشغيل docker باستخدام –volumes
تشغيل docker مرة أخرى باستخدام -d
# Update the PG_DATABASE_PASSWORD in .env
docker compose down --volumes
docker compose up -d
```
#### تم العثور على فواصل الخط CR [نظام Windows]
@@ -180,11 +180,12 @@ plugins: [
1. قم بتشغيل الأوامر التالية:
```bash
docker exec -it twenty-server-1 yarn
docker exec -it twenty-server-1 npx nx database:reset --configuration=no-seed
docker exec -it twenty-server-1 npx nx database:reset --configuration=no-seed
```
2. إعادة تشغيل حاويات Docker:
```bash
docker compose down\ndocker compose up -d
docker compose down
docker compose up -d
```
لاحظ أن الأمر database:reset سيقوم بمسح قاعدة البيانات الخاصة بك بالكامل وإعادة إنشائها من جديد.
@@ -19,12 +19,12 @@ export const MyComponent = () => {
return (
<>
<p id="hoverText" style={{ display: "inline-block" }}>
رؤى العملاء
Customer Insights
</p>
<AppTooltip
className
anchorSelect="#hoverText"
content="استكشاف سلوك العملاء وتفضيلاتهم"
content="Explore customer behavior and preferences"
delayHide={0}
offset={6}
noArrow={false}
@@ -72,7 +72,7 @@ import { OverflowingTextWithTooltip } from 'twenty-ui/display';
export const MyComponent = () => {
const crmTaskDescription =
'المتابعة مع العميل بشأن استفساره الأخير عن المنتج. مناقشة خيارات التسعير، ومعالجة أي مخاوف، وتقديم معلومات إضافية عن المنتج. تسجيل تفاصيل المحادثة في نظام إدارة علاقات العملاء (CRM) للرجوع إليها لاحقاً.';
'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} />;
};
@@ -132,8 +132,8 @@ export const MyComponent = () => {
<TextArea
disabled={false}
minRows={4}
onChange={()=>console.log('تم تشغيل الدالة onChange')}
placeholder="أدخل النص هنا"
onChange={()=>console.log('On change function fired')}
placeholder="Enter text here"
value=""
/>
);
@@ -13,12 +13,12 @@ export const MyComponent = () => {
return (
<Toggle
value = {true}
onChange = {()=>console.log('تم تشغيل حدث onChange')}
onChange = {()=>console.log('On Change event')}
color="green"
toggleSize = "medium"
/>
);
};
};},{
```
</Tab>
@@ -21,7 +21,7 @@ import { ContactLink } from 'twenty-ui/navigation';
export const MyComponent = () => {
const handleLinkClick = (event) => {
console.log('تم النقر على رابط الاتصال!', event);
console.log('Contact link clicked!', event);
};
return (
@@ -357,16 +357,16 @@ import { MenuItemSelectAvatar } from "twenty-ui/display";
export const MyComponent = () => {
const imageUrl =
"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAYABgAAD/4QCMRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAABgAAAAAQAAAGAAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAABSgAwAEAAAAAQAAABQAAAAA/8AAEQgAFAAUAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6OnqvLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMACwgICggHCwoJCg0MCw0RHBIRDw8RIhkaFBwpJCsqKCQnJy0yQDctMD0wJyc4TDk9Q0VISUgrNk9VTkZUQEdIRf/bAEMBDA0NEQ8RIRISIUUuJy5FRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRf/dAAQAAv/aAAwDAQACEQMRAD8Ava1q728otYY98joSCTgZrnbXWdTtrhrfVZXWLafmcAEkdgR/hVltQku9Q8+OIEBcGOT+ID0PY1ka1KH2u8ToqnPLbmIqG7u6LtbQ7RXBRec4Uck9eKXcPWsKDWVnhWSL5kYcFelSf2m3901POh8jP//QoyIAnTuKpXsY82NsksUyWPU5q/L9z8RVK++/F/uCsVsaEURwgA4HtT9x9TUcf3KfUGh//9k=";
"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAYABgAAD/4QCMRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAABgAAAAAQAAAGAAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAABSgAwAEAAAAAQAAABQAAAAA/8AAEQgAFAAUAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMACwgICggHCwoJCg0MCw0RHBIRDw8RIhkaFBwpJCsqKCQnJy0yQDctMD0wJyc4TDk9Q0VISUgrNk9VTkZUQEdIRf/bAEMBDA0NEQ8RIRISIUUuJy5FRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRf/dAAQAAv/aAAwDAQACEQMRAD8Ava1q728otYY98joSCTgZrnbXWdTtrhrfVZXWLafmcAEkdgR/hVltQku9Q8+OIEBcGOT+ID0PY1ka1KH2u8ToqnPLbmIqG7u6LtbQ7RXBRec4Uck9eKXcPWsKDWVnhWSL5kYcFelSf2m3901POh8jP//QoyIAnTuKpXsY82NsksUyWPU5q/L9z8RVK++/F/uCsVsaEURwgA4HtT9x9TUcf3KfUGh//9k=";
const handleSelection = () => {
console.log("تم تحديد عنصر القائمة");
console.log("Menu item selected");
};
return (
<MenuItemSelectAvatar
avatar={<img src={imageUrl} alt="الصورة الرمزية" />}
text="الخيار الأول"
avatar={<img src={imageUrl} alt="Avatar" />}
text="First Option"
selected={true}
disabled={false}
hovered={false}
@@ -42,14 +42,14 @@ description: أرسل رسائل بريد إلكتروني مُخصّصة تلق
استدعِ البيانات من الخطوات السابقة باستخدام صيغة `{{variable}}`:
```text
مرحباً {{trigger.object.firstName}},
Hi {{trigger.object.firstName}},
شكراً لتواصلك معنا!
Thank you for connecting with us!
تم الآن إدراج شركتك، {{trigger.object.company.name}}، في نظامنا.
Your company, {{trigger.object.company.name}}, is now in our system.
أطيب التحيات،
الفريق
Best regards,
The Team
```
### المتغيرات المتاحة من المشغّلات
@@ -84,13 +84,13 @@ description: قم بالتكرار عبر مصفوفات السجلات لتنف
**الهدف**: وضع علامة "متأخر" على جميع المهام المتأخرة عن موعدها
```
1. Search Records (المهام، تاريخ الاستحقاق < اليوم، الحالة ≠ مكتمل)
2. عامل تصفية (length > 0)
1. Search Records (Tasks, Due Date < Today, Status ≠ Completed)
2. Filter (length > 0)
3. Iterator (searchRecords)
└── تحديث سجل
- الكائن: المهام
- السجل: {{iterator.currentItem.id}}
- الحالة: متأخر
└── Update Record
- Object: Tasks
- Record: {{iterator.currentItem.id}}
- Status: Late
```
### إنشاء سجلات من مصفوفة
@@ -98,14 +98,14 @@ description: قم بالتكرار عبر مصفوفات السجلات لتنف
**الهدف**: يتلقى الويبهوك طلباً يتضمن عدة عناصر، وإنشاء سجل لكل عنصر
```
1. مشغّل الويبهوك (يتلقى مصفوفة العناصر)
2. عامل تصفية (items.length > 0)
1. Webhook Trigger (receives items array)
2. Filter (items.length > 0)
3. Iterator (trigger.body.items)
└── إنشاء سجل
- الكائن: عناصر الطلب
- الاسم: {{iterator.currentItem.name}}
- الكمية: {{iterator.currentItem.qty}}
- الطلب المرتبط: {{trigger.body.orderId}}
└── Create Record
- Object: Order Items
- Name: {{iterator.currentItem.name}}
- Quantity: {{iterator.currentItem.qty}}
- Related Order: {{trigger.body.orderId}}
```
### معالجة مشروطة داخل الحلقة
@@ -113,11 +113,11 @@ description: قم بالتكرار عبر مصفوفات السجلات لتنف
**الهدف**: إرسال بريد إلكتروني فقط لجهات الاتصال ذات العناوين الصالحة
```
1. Search Records (الأشخاص)
1. Search Records (People)
2. Iterator (searchRecords)
└── عامل تصفية (currentItem.email غير فارغ)
└── إرسال بريد إلكتروني
- إلى: {{iterator.currentItem.email}}
└── Filter (currentItem.email is not empty)
└── Send Email
- To: {{iterator.currentItem.email}}
```
## استكشاف الأخطاء وإصلاحها
@@ -82,19 +82,19 @@ description: أتمتة الأنشطة بعد الفوز عند إغلاق ال
**مثال على محتوى البريد الإلكتروني**:
```
مرحبًا فريق نجاح العملاء،
Hi CS Team,
لدينا عميل جديد!
We have a new customer!
الشركة: {{trigger.object.company.name}}
الصفقة: {{trigger.object.name}}
القيمة: {{trigger.object.amount}}
مندوب المبيعات: {{trigger.object.owner.name}}
تاريخ الإغلاق: {{trigger.object.closedAt}}
Company: {{trigger.object.company.name}}
Deal: {{trigger.object.name}}
Value: {{trigger.object.amount}}
Sales Rep: {{trigger.object.owner.name}}
Close Date: {{trigger.object.closedAt}}
تم إنشاء مهمة الإعداد تلقائيًا.
An onboarding task has been created automatically.
لنمنحهم بداية رائعة!
Let's give them a great start!
```
### الخطوة 7: تأكيد لمندوب المبيعات
@@ -34,7 +34,7 @@ Pro uplatnění příznaku funkce na **backendové** funkci použijte:
Pro uplatnění příznaku funkce na **frontendové** funkci použijte:
```ts
const isFeatureNameEnabled = useIsFeatureEnabled('IS_FUNKCEPOVOLENA');
const isFeatureNameEnabled = useIsFeatureEnabled('IS_FEATURENAME_ENABLED');
```
## Nakonfigurujte příznaky funkcí pro nasazení
@@ -23,14 +23,14 @@ npx nx run twenty-server:start
### Linter
```
npx nx run twenty-server:lint # přidejte --fix pro opravu chyb ve formátování
npx nx run twenty-server:lint # pass --fix to fix lint errors
```
### Testovat
```
npx nx run twenty-server:test:unit # spuštění jednotkových testů
npx nx run twenty-server:test:integration # spuštění integračních testů
npx nx run twenty-server:test:unit # run unit tests
npx nx run twenty-server:test:integration # run integration tests
```
Poznámka: můžete spustit `npx nx run twenty-server:test:integration:with-db-reset`, pokud potřebujete před spuštěním integračních testů obnovit databázi.
@@ -15,7 +15,9 @@ Více o tom, jak Zapier funguje, se dozvíte [zde](https://zapier.com/how-it-wor
### Krok 1: Nainstalujte balíčky Zapier
```bash
cd packages/twenty-zapier\n\nyarn
cd packages/twenty-zapier
yarn
```
### Krok 2: Přihlaste se pomocí CLI
@@ -83,8 +83,8 @@ If you feel like you need to add a `useEffect` in your root component, you shoul
Stejný postup můžete aplikovat na logiku získávání dat pomocí Apollo hooks.
```tsx
// ❌ Špatně, způsobí překreslení, i když se data nemění,
// protože useEffect je třeba znovu vyhodnotit
// ❌ Bad, will cause re-renders even if data is not changing,
// because useEffect needs to be re-evaluated
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
@@ -104,8 +104,8 @@ export const App = () => (
```
```tsx
// ✅ Dobře, nezpůsobí překreslení, pokud se data nemění,
// protože useEffect je znovu vyhodnocen v jiné sourozenecké komponentě
// ✅ Good, will not cause re-renders if data is not changing,
// because useEffect is re-evaluated in another sibling component
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
@@ -244,7 +244,7 @@ Nejčastější příklad je komponenta ikon:
```tsx
const SomeParentComponent = () => <MyComponent Icon={MyIcon} />;
// V komponentě MyComponent
// In MyComponent
const MyComponent = ({ MyIcon }: { MyIcon: IconComponent }) => {
const theme = useTheme();
@@ -299,7 +299,7 @@ import {
```
```tsx
// ✅ Dobře, využívá určené aliasy
// ✅ Good, utilises the designated aliases
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from 'twenty-ui/testing';
```
@@ -26,7 +26,7 @@ npx nx run twenty-front:graphql:generate
### Linter
```bash
npx nx run twenty-front:lint # přidejte --fix pro opravu chyb ve formátování
npx nx run twenty-front:lint # pass --fix to fix lint errors
```
## Překlady
@@ -39,10 +39,10 @@ npx nx run twenty-front:lingui:compile
### Testovat
```bash
npx nx run twenty-front:test # spusťte jest testy
npx nx run twenty-front:storybook:serve:dev # spusťte storybook
npx nx run twenty-front:storybook:test # spusťte testy # (vyžaduje, aby byl spuštěn yarn storybook:serve:dev)
npx nx run twenty-front:storybook:coverage # (vyžaduje, aby byl spuštěn yarn storybook:serve:dev)
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)
```
## Technologický stack
@@ -68,11 +68,11 @@ const EmailField: React.FC<{
```
```tsx
/* ✅ - Dobré, explicitně definován samostatný typ (OwnProps) pro
* rekvizity komponenty
* - Tato metoda automaticky nezahrnuje rekvizitu children. Pokud
* ji chcete zahrnout, musíte ji specifikovat v OwnProps.
*/
/* ✅ - Good, a separate type (OwnProps) is explicitly defined for the
* component's props
* - This method doesn't automatically include the children prop. If
* you want to include it, you have to specify it in OwnProps.
*/
type EmailFieldProps = {
value: string;
};
@@ -95,8 +95,8 @@ const MyComponent = (props: OwnProps) => {
```
```tsx
/* ✅ - Dobré, explicitně uvádí všechny rekvizity
* - Zvyšuje čitelnost a udržovatelnost
/* ✅ - Dobré, Explicitně uvádí všechny prop
* - Zvyšuje čitelnost a možnost údržby
*/
const MyComponent = ({ prop1, prop2, prop3 }: MyComponentProps) => {
return <OtherComponent {...{ prop1, prop2, prop3 }} />;
@@ -114,20 +114,20 @@ Odůvodnění:
### Používejte operátor nullish-coalescing `??`
```tsx
// ❌ Špatné, může vrátit 'default' i když je hodnota 0 nebo ''
// ❌ Bad, can return 'default' even if value is 0 or ''
const value = process.env.MY_VALUE || 'default';
// ✅ Dobré, vrací 'default' pouze pokud je hodnota null nebo undefined
// ✅ Good, will return 'default' only if value is null or undefined
const value = process.env.MY_VALUE ?? 'default';
```
### Používejte volitelné zřetězení `?.`
```tsx
// ❌ Špatné
// ❌ Bad
onClick && onClick();
// ✅ Dobré
// ✅ Good
onClick?.();
```
@@ -167,7 +167,7 @@ let color = Color.Red;
```
```tsx
// ✅ Dobré, používá textový literál
// ✅ Good, utilizes a string literal
let color: "red" | "green" | "blue" = "red";
```
@@ -198,12 +198,12 @@ setHotkeyScopeAndMemorizePreviousScope(
Stylujte komponenty pomocí [Linaria styled](https://github.com/callstack/linaria).
```tsx
// ❌ Špatné
<div className="my-class">Ahoj světe</div>
// ❌ Bad
<div className="my-class">Hello World</div>
```
```tsx
// ✅ Dobré
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
@@ -212,14 +212,14 @@ const StyledTitle = styled.div`
Prefixujte stylizované komponenty "Styled", abyste je odlišili od "skutečných" komponent.
```tsx
// ❌ Špatné
// ❌ Bad
const Title = styled.div`
color: red;
`;
```
```tsx
// ✅ Dobré
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
@@ -238,7 +238,7 @@ Vyhýbejte se používání hodnot `px` nebo `rem` přímo ve stylizovaných kom
Zdržte se zavádění nových barev; místo toho použijte existující paletu z tématu. Pokud by došlo k tomu, že paleta neodpovídá, prosím nechte komentář, aby to tým mohl napravit.
```tsx
// ❌ Špatné, přímo specifikuje hodnoty stylu bez využití tématu
// ❌ Bad, directly specifies style values without utilizing the theme
const StyledButton = styled.button`
color: #333333;
font-size: 1rem;
@@ -249,7 +249,7 @@ const StyledButton = styled.button`
```
```tsx
// ✅ Dobré, využívá téma
// ✅ Good, utilizes the theme
const StyledButton = styled.button`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
@@ -39,9 +39,9 @@ Zobrazí se výzva k vytvoření uživatelského jména a hesla pro vaši instal
```bash
sudo apt-get install git
git config --global user.name "Vaše Jméno"
git config --global user.name "Your Name"
git config --global user.email "vasemail@domena.com"
git config --global user.email "youremail@domain.com"
```
3. Nainstalujte nvm, node.js a yarn
@@ -59,9 +59,9 @@ Zavřete a znovu otevřete terminál, abyste použili nvm. Potom spusťte násle
```bash
nvm install # instaluje doporučenou verzi node
nvm install # installs recommended node version
nvm use # používá doporučenou verzi node
nvm use # use recommended node version
corepack enable
```
@@ -134,22 +134,22 @@ Všechny příkazy v následujících krocích byste měli provádět z kořene
přes Homebrew na macOS. Místo toho vytvoří PostgreSQL roli, která odpovídá vašemu uživatelskému jménu v MacOS (např. "john").
Pro zkontrolování a vytvoření uživatele `postgres`, pokud je to nutné, postupujte takto:
```bash
# Připojit se k PostgreSQL
# Connect to PostgreSQL
psql postgres
nebo
or
psql -U $(whoami) -d postgres
```
Po zobrazení výzvy psql (postgres=#) spusťte:
```bash
# Seznam existujících PostgreSQL rolí
# List existing PostgreSQL roles
\du
```
Zobrazí se výstup podobný tomuto:
```bash
Jméno role | Vlastnosti | Členem
Role name | Attributes | Member of
-----------+-------------+-----------
john | Superuživatel | {}
john | Superuser | {}
```
Pokud nevidíte roli `postgres`, pokračujte na další krok.
@@ -159,10 +159,10 @@ Všechny příkazy v následujících krocích byste měli provádět z kořene
```
Tím vytvoříte superuživatelskou roli pojmenovanou `postgres` s přístupovými právy.
```bash
Jméno role | Vlastnosti | Členem
Role name | Attributes | Member of
-----------+-------------+-----------
postgres | Superuživatel | {}
john | Superuživatel | {}
postgres | Superuser | {}
john | Superuser | {}
```
**Možnost 2:** Pokud máte nainstalován docker:
@@ -76,7 +76,7 @@ Postupujte podle těchto kroků pro ruční nastavení.
Aktualizujte hodnotu `PG_DATABASE_PASSWORD` ve vašem .env souboru silným heslem bez speciálních znaků.
```ini
PG_DATABASE_PASSWORD=moje_silné_heslo
PG_DATABASE_PASSWORD=my_strong_password
```
### Krok 2: Získání souboru Docker Compose
@@ -135,7 +135,7 @@ Důrazně doporučujeme nastavit Twenty za reverzní proxy se SSL ukončením pr
Pokud přistupujete k aplikaci přímo bez reverzní proxy:
```ini
SERVER_URL=http://vaše-doména-nebo-ip:3000
SERVER_URL=http://your-domain-or-ip:3000
```
* **S Reverzní Proxy (Standardní Porty):**
@@ -143,7 +143,7 @@ Důrazně doporučujeme nastavit Twenty za reverzní proxy se SSL ukončením pr
Pokud používáte reverzní proxy jako Nginx nebo Traefik a máte SSL nakonfigurované:
```ini
SERVER_URL=https://vaše-doména-nebo-ip
SERVER_URL=https://your-domain-or-ip
```
* **S Reverzní Proxy (Vlastní Porty):**
@@ -151,7 +151,7 @@ Důrazně doporučujeme nastavit Twenty za reverzní proxy se SSL ukončením pr
Pokud používáte nestandardní porty:
```ini
SERVER_URL=https://vaše-doména-nebo-ip:vlastní-port
SERVER_URL=https://your-domain-or-ip:custom-port
```
2. **Aktualizujte `.env` Soubor**
@@ -159,7 +159,7 @@ Důrazně doporučujeme nastavit Twenty za reverzní proxy se SSL ukončením pr
Otevřete svůj `.env` soubor a aktualizujte `SERVER_URL`:
```ini
SERVER_URL=http(s)://vaše-doména-nebo-ip:váš-port
SERVER_URL=http(s)://your-domain-or-ip:your-port
```
**Příklady:**
@@ -170,7 +170,7 @@ Důrazně doporučujeme nastavit Twenty za reverzní proxy se SSL ukončením pr
```
* Přístup přes doménu s SSL:
```ini
SERVER_URL=https://mojeaplikace.com
SERVER_URL=https://mytwentyapp.com
```
3. **Restartujte Aplikaci**
@@ -16,7 +16,7 @@ Twenty nabízí **dvě konfigurační režimy** pro různé potřeby nasazení:
## 1. Konfigurace Admin panelu (výchozí)
```bash
IS_CONFIG_VARIABLES_IN_DB_ENABLED=true # výchozí
IS_CONFIG_VARIABLES_IN_DB_ENABLED=true # default
```
**Většina konfigurace probíhá prostřednictvím uživatelského rozhraní** po instalaci:
@@ -255,7 +255,7 @@ Po konfiguraci integrací Gmail, Google Kalendář nebo Microsoft 365 je třeba
Zaregistrujte následující opakující se úlohy ve vašem pracovním kontejneru:
```bash
# z vašeho pracovního kontejneru
# from your worker container
yarn command:prod cron:messaging:messages-import
yarn command:prod cron:messaging:message-list-fetch
yarn command:prod cron:calendar:calendar-event-list-fetch
@@ -24,7 +24,7 @@ Pokračujte pouze v případě, že se jedná o novou instalaci bez důležitýc
K aktualizaci `PG_DATABASE_PASSWORD` musíte:
```sh
# Aktualizovat PG_DATABASE_PASSWORD ve .env
# Update the PG_DATABASE_PASSWORD in .env
docker compose down --volumes
docker compose up -d
```
@@ -181,7 +181,7 @@ Pokud se nemůžete přihlásit po nastavení:
1. Spusťte následující příkazy:
```bash
docker exec -it twenty-server-1 yarn
docker exec -it twenty-server-1 npx nx database:reset --configuration=no-seed
docker exec -it twenty-server-1 npx nx database:reset --configuration=no-seed
```
2. Restartujte Docker kontejnery:
```bash
@@ -19,12 +19,12 @@ export const MyComponent = () => {
return (
<>
<p id="hoverText" style={{ display: "inline-block" }}>
Zákaznické přehledy
Customer Insights
</p>
<AppTooltip
className
anchorSelect="#hoverText"
content="Prozkoumejte chování a preference zákazníků"
content="Explore customer behavior and preferences"
delayHide={0}
offset={6}
noArrow={false}
@@ -34,7 +34,7 @@ export const MyComponent = () => {
/>
</>
);
};
};},{
```
</Tab>
@@ -72,7 +72,7 @@ import { OverflowingTextWithTooltip } from 'twenty-ui/display';
export const MyComponent = () => {
const crmTaskDescription =
'Kontaktujte klienta ohledně jeho nedávného dotazu na produkt. Proberte cenové možnosti, vyřešte případné obavy a poskytněte další informace o produktu. Zaznamenejte podrobnosti rozhovoru do systému CRM pro budoucí potřebu.';
'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} />;
};
@@ -19,7 +19,7 @@ export const MyComponent = () => {
return (
<Chip
size="large"
label="Klikatelný štítek"
label="Clickable Chip"
clickable={true}
variant="highlighted"
accent="text-primary"
@@ -30,7 +30,7 @@ export const MyComponent = () => {
/>
);
};
},{
```
</Tab>
@@ -64,7 +64,7 @@ export const MyComponent = () => {
return (
<Chip
size="large"
label="Průhledný neaktivní štítek"
label="Transparent Disabled Chip"
clickable={false}
variant="rounded"
accent="text-secondary"
@@ -89,7 +89,7 @@ export const MyComponent = () => {
return (
<Chip
size="large"
label="Neaktivní štítek, který při přetečení zobrazí popisek."
label="Disabled chip that triggers a tooltip when overflowing."
clickable={false}
variant="regular"
accent="text-primary"
@@ -121,7 +121,7 @@ export const MyComponent = () => {
<Chip
linkToEntity="/entity-link"
entityId="entityTest"
name="Název entity"
name="Entity name"
pictureUrl=""
avatarType="rounded"
variant="regular"
@@ -17,7 +17,7 @@ export const MyComponent = () => {
<Tag
className
color="red"
text="Urgent"
text="Urgent"
onClick={() => console.log("click")}
/>
);
@@ -22,10 +22,10 @@ export const MyComponent = () => {
<Select
className
disabled={false}
label="Vyberte možnost"
label="Select an option"
options={[
{ value: 'option1', label: 'Možnost A', Icon: IconTwentyStar },
{ value: 'option2', label: 'Možnost B', Icon: IconTwentyStar },
{ value: 'option1', label: 'Option A', Icon: IconTwentyStar },
{ value: 'option2', label: 'Option B', Icon: IconTwentyStar },
]}
value="option1"
/>
@@ -19,21 +19,21 @@ import { TextInput } from "@/ui/input/components/TextInput";
export const MyComponent = () => {
const handleChange = (text) => {
console.log("Změněn vstup:", text);
console.log("Input changed:", text);
};
const handleKeyDown = (event) => {
console.log("Stisknutá klávesa:", event.key);
console.log("Key pressed:", event.key);
};
return (
<TextInput
className
label="Uživatelské jméno"
label="Username"
onChange={handleChange}
fullWidth={false}
disableHotkeys={false}
error="Neplatné uživatelské jméno"
error="Invalid username"
onKeyDown={handleKeyDown}
RightIcon={null}
/>
@@ -82,13 +82,13 @@ import { AutosizeTextInput } from "@/ui/input/components/AutosizeTextInput";
export const MyComponent = () => {
return (
<AutosizeTextInput
onValidate={() => console.log("Funkce onValidate spuštěna")}
onValidate={() => console.log("onValidate function fired")}
minRows={1}
placeholder="Napište komentář"
onFocus={() => console.log("Funkce onFocus spuštěna")}
placeholder="Write a comment"
onFocus={() => console.log("onFocus function fired")}
variant="icon"
buttonTitle
value="Úkol: "
value="Task: "
/>
);
};
@@ -42,14 +42,14 @@ Než budete moci odesílat e-maily z pracovních postupů:
Odkazujte na data z předchozích kroků pomocí syntaxe `{{variable}}`:
```text
Dobrý den {{trigger.object.firstName}},
Hi {{trigger.object.firstName}},
Děkujeme, že jste se s námi spojili!
Thank you for connecting with us!
Vaše společnost, {{trigger.object.company.name}}, je nyní v našem systému.
Your company, {{trigger.object.company.name}}, is now in our system.
S pozdravem,
Tým
Best regards,
The Team
```
### Dostupné proměnné ze spouštěčů
@@ -63,15 +63,15 @@ Po dokončení práce paralelních větví je můžete sloučit zpět do jedné
### Příklad: Zpracovat a poté upozornit
```
Spouštěč
Trigger
├── Větev A: Aktualizovat záznam zákazníka
├── Branch A: Update Customer Record
└── Větev B: Vytvořit tiket podpory
└── Branch B: Create Support Ticket
↘ ↙
Sloučený krok: Odeslat potvrzovací e-mail
Merged Step: Send Confirmation Email
```
Potvrzovací e-mail se odešle až po dokončení aktualizace zákazníka i vytvoření tiketu.
@@ -129,8 +129,8 @@ Uvnitř iterátoru použijte `{{iterator.currentItem}}` pro přístup k aktuáln
**Řešení**: Ujistěte se, že předáváte výsledek akce Search Records nebo pole, nikoli jeden záznam.
```
Správně: {{searchRecords}}
Špatně: {{searchRecords[0]}}
Correct: {{searchRecords}}
Wrong: {{searchRecords[0]}}
```
### Iterátor se nespouští
@@ -140,7 +140,7 @@ Uvnitř iterátoru použijte `{{iterator.currentItem}}` pro přístup k aktuáln
**Řešení**: Před iterátor přidejte filtr pro kontrolu délky pole:
```
Filtr: {{searchRecords.length}} > 0
Filter: {{searchRecords.length}} > 0
```
### Akce se spouštějí příliš mnohokrát
@@ -82,19 +82,19 @@ Vytvořte pracovní postup, který automaticky zajistí všechny činnosti po v
**Příklad textu e-mailu**:
```
Ahoj týme CS,
Hi CS Team,
Máme nového zákazníka!
We have a new customer!
Společnost: {{trigger.object.company.name}}
Obchod: {{trigger.object.name}}
Hodnota: {{trigger.object.amount}}
Obchodní zástupce: {{trigger.object.owner.name}}
Datum uzavření: {{trigger.object.closedAt}}
Company: {{trigger.object.company.name}}
Deal: {{trigger.object.name}}
Value: {{trigger.object.amount}}
Sales Rep: {{trigger.object.owner.name}}
Close Date: {{trigger.object.closedAt}}
Onboardingový úkol byl automaticky vytvořen.
An onboarding task has been created automatically.
Dejme jim skvělý start!
Let's give them a great start!
```
### Krok 7: Potvrďte obchodnímu zástupci
@@ -23,14 +23,14 @@ npx nx run twenty-server:start
### Lint
```
npx nx run twenty-server:lint # --fix übergeben, um Lint-Fehler zu beheben
npx nx run twenty-server:lint # pass --fix to fix lint errors
```
### Test
```
npx nx run twenty-server:test:unit # Unit-Tests ausführen
npx nx run twenty-server:test:integration # Integrationstests ausführen
npx nx run twenty-server:test:unit # run unit tests
npx nx run twenty-server:test:integration # run integration tests
```
Hinweis: Sie können `npx nx run twenty-server:test:integration:with-db-reset` ausführen, falls Sie die Datenbank zurücksetzen müssen, bevor Sie die Integrationstests durchführen.
@@ -83,8 +83,8 @@ If you feel like you need to add a `useEffect` in your root component, you shoul
Dasselbe können Sie auch für die Datenabruflogik mit Apollo-Hooks anwenden.
```tsx
// ❌ Schlecht, verursacht Re-Renders, auch wenn sich die Daten nicht ändern,
// weil useEffect neu ausgewertet werden muss
// ❌ Bad, will cause re-renders even if data is not changing,
// because useEffect needs to be re-evaluated
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
@@ -104,8 +104,8 @@ export const App = () => (
```
```tsx
// ✅ Gut, verursacht keine Re-Renders, wenn sich die Daten nicht ändern,
// weil useEffect in einer anderen Geschwisterkomponente neu ausgewertet wird
// ✅ Good, will not cause re-renders if data is not changing,
// because useEffect is re-evaluated in another sibling component
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
@@ -172,13 +172,13 @@ Variablennamen sollten den Zweck oder die Funktion der Variable genau beschreibe
Generische Namen in der Programmierung sind nicht ideal, weil ihnen die Spezifität fehlt, was zu Mehrdeutigkeit und verminderter Lesbarkeit des Codes führt. Solche Namen vermitteln nicht den Zweck der Variablen oder Funktion, was es Entwicklern erschwert, die Absicht des Codes ohne tiefere Untersuchung zu verstehen. Dies kann zu erhöhten Debugging-Zeiten, höherer Fehleranfälligkeit und Schwierigkeiten bei der Wartung und Zusammenarbeit führen. In der Zwischenzeit macht eine beschreibende Namensgebung den Code selbsterklärend und einfacher zu navigieren, was die Codequalität und die Produktivität der Entwickler verbessert.
```tsx
// ❌ Schlecht, verwendet einen generischen Namen, der seinen
// Zweck oder Inhalt nicht klar kommuniziert
// ❌ Bad, uses a generic name that doesn't communicate its
// purpose or content clearly
const [value, setValue] = useState('');
```
```tsx
// ✅ Gut, verwendet einen beschreibenden Namen
// ✅ Good, uses a descriptive name
const [email, setEmail] = useState('');
```
@@ -26,7 +26,7 @@ npx nx run twenty-front:graphql:generate
### Lint
```bash
npx nx run twenty-front:lint # Füge --fix hinzu, um Lint-Fehler zu beheben
npx nx run twenty-front:lint # pass --fix to fix lint errors
```
## Übersetzungen
@@ -39,10 +39,10 @@ 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
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)
```
## Technologie-Stack
@@ -68,10 +68,10 @@ const EmailField: React.FC<{
```
```tsx
/* ✅ - Gut, ein separater Typ (EigeneProps) ist explizit für die
* Eigenschaften der Komponente definiert
* - Diese Methode schließt das Kinder-Prop nicht automatisch ein. Wenn
* Sie es einbeziehen möchten, müssen Sie es in EigeneProps angeben.
/* ✅ - Gut, ein separater Typ (OwnProps) ist explizit für die
* Props der Komponente definiert
* - Diese Methode schließt das children-Prop nicht automatisch ein. Wenn
* Sie es einbeziehen möchten, müssen Sie es in OwnProps angeben.
*/
type EmailFieldProps = {
value: string;
@@ -95,8 +95,8 @@ const MyComponent = (props: OwnProps) => {
```
```tsx
/* ✅ - Gut, listet alle Props explizit auf
* - Erhöht die Lesbarkeit und Wartbarkeit
/* ✅ - Good, Explicitly lists all props
* - Enhances readability and maintainability
*/
const MyComponent = ({ prop1, prop2, prop3 }: MyComponentProps) => {
return <OtherComponent {...{ prop1, prop2, prop3 }} />;
@@ -114,20 +114,20 @@ Rational
### Verwenden Sie den Nullish-Coalescing-Operator `??`
```tsx
// ❌ Schlecht, kann 'default' zurückgeben, selbst wenn der Wert 0 oder '' ist
// ❌ Bad, can return 'default' even if value is 0 or ''
const value = process.env.MY_VALUE || 'default';
// ✅ Gut, wird `default` nur zurückgeben, wenn der Wert null oder undefiniert ist
// ✅ Good, will return 'default' only if value is null or undefined
const value = process.env.MY_VALUE ?? 'default';
```
### Verwenden Sie optionales Chaining `?.`
```tsx
// ❌ Schlecht
// ❌ Bad
onClick && onClick();
// ✅ Gut
// ✅ Good
onClick?.();
```
@@ -167,9 +167,9 @@ let color = Color.Red;
```
```tsx
// ✅ Gut, verwendet ein String-Literal
// ✅ Good, utilizes a string literal
let farbe: "red" | "green" | "blue" = "red";
let color: "red" | "green" | "blue" = "red";
```
#### GraphQL und interne Bibliotheken
@@ -198,12 +198,12 @@ setHotkeyScopeAndMemorizePreviousScope(
Stylen Sie die Komponenten mit [Linaria styled](https://github.com/callstack/linaria).
```tsx
// ❌ Schlecht
<div className="my-class">Hallo Welt</div>
// ❌ Bad
<div className="my-class">Hello World</div>
```
```tsx
// ✅ Gut
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
@@ -212,14 +212,14 @@ const StyledTitle = styled.div`
Prefixen Sie stilisierte Komponenten mit "Styled", um sie von "echten" Komponenten zu unterscheiden.
```tsx
// ❌ Schlecht
// ❌ Bad
const Title = styled.div`
color: red;
`;
```
```tsx
// ✅ Gut
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
@@ -238,7 +238,7 @@ Vermeiden Sie die Verwendung direkter `px`- oder `rem`-Werte innerhalb der gesty
Vermeiden Sie es, neue Farben einzuführen; verwenden Sie stattdessen die vorhandene Palette aus dem Thema. Sollte es eine Situation geben, in der die Palette nicht übereinstimmt, hinterlassen Sie bitte einen Kommentar, damit das Team dies korrigieren kann.
```tsx
// ❌ Schlecht, gibt direkt Stilwerte an, ohne das Thema zu nutzen
// ❌ Bad, directly specifies style values without utilizing the theme
const StyledButton = styled.button`
color: #333333;
font-size: 1rem;
@@ -249,7 +249,7 @@ const StyledButton = styled.button`
```
```tsx
// ✅ Gut, nutzt das Thema
// ✅ Good, utilizes the theme
const StyledButton = styled.button`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
@@ -59,9 +59,9 @@ Schließen und öffnen Sie Ihr Terminal erneut, um nvm zu verwenden. Führen Sie
```bash
nvm install # installiert empfohlene node-Version
nvm install # installs recommended node version
nvm use # verwendet empfohlene node-Version
nvm use # use recommended node version
corepack enable
```
@@ -119,15 +119,15 @@ Alle folgenden Befehle innerhalb des Projekts sind vom Stammverzeichnis aus ausz
**Option 1 (bevorzugt):** Um Ihre Datenbank lokal mit `brew` bereitzustellen:
```bash
brew install postgresql@16
export PATH="/opt/homebrew/opt/postgresql@16/bin:$PATH"
brew services start postgresql@16
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
brew install postgresql@16
export PATH="/opt/homebrew/opt/postgresql@16/bin:$PATH"
brew services start postgresql@16
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
Sie können überprüfen, ob der PostgreSQL-Server läuft, indem Sie folgendes ausführen:
```bash
brew services list
brew services list
```
Das Installationsprogramm erstellt den Benutzer `postgres` möglicherweise nicht standardmäßig bei der Installation
@@ -135,20 +135,20 @@ Alle folgenden Befehle innerhalb des Projekts sind vom Stammverzeichnis aus ausz
Benutzernamen (z. B. "john") entspricht.
Um zu überprüfen und, falls erforderlich, den Benutzer `postgres` zu erstellen, führen Sie folgende Schritte aus:
```bash
# Verbinden Sie sich mit PostgreSQL
# Connect to PostgreSQL
psql postgres
oder
or
psql -U $(whoami) -d postgres
```
Sobald Sie sich an der psql-Eingabeaufforderung (postgres=#) befinden, führen Sie aus:
```bash
# Vorhandene PostgreSQL-Rollen auflisten
# List existing PostgreSQL roles
\du
```
Sie werden eine Ausgabe ähnlich der folgenden sehen:
```bash
Rolle Name | Attribute | Mitglied von
Role name | Attributes | Member of
-----------+-------------+-----------
john | Superuser | {}
```
@@ -160,7 +160,7 @@ Alle folgenden Befehle innerhalb des Projekts sind vom Stammverzeichnis aus ausz
```
Dadurch wird eine Superuser-Rolle namens `postgres` mit Anmeldezugriff erstellt.
```bash
Rollenname | Attribute | Mitglied von
Role name | Attributes | Member of
-----------+-------------+-----------
postgres | Superuser | {}
john | Superuser | {}
@@ -76,7 +76,7 @@ Befolgen Sie diese Schritte für eine manuelle Einrichtung.
Aktualisieren Sie den Wert `PG_DATABASE_PASSWORD` in der .env-Datei mit einem starken Passwort ohne Sonderzeichen.
```ini
PG_DATABASE_PASSWORD=mein_starkes_passwort
PG_DATABASE_PASSWORD=my_strong_password
```
### Schritt 2: Beschaffen Sie die Docker Compose-Datei
@@ -16,7 +16,7 @@ Twenty bietet **zwei Konfigurationsmodi**, um unterschiedlichen Implementierungs
## 1. Admin-Panel-Konfiguration (Standard)
```bash
IS_CONFIG_VARIABLES_IN_DB_ENABLED=true # Standard
IS_CONFIG_VARIABLES_IN_DB_ENABLED=true # default
```
**Die meiste Konfiguration erfolgt über die Benutzeroberfläche** nach der Installation:
@@ -255,7 +255,7 @@ Nachdem Sie Gmail-, Google Kalender- oder Microsoft 365-Integrationen konfigurie
Registrieren Sie die folgenden wiederkehrenden Aufgaben in Ihrem Worker-Container:
```bash
# von Ihrem Worker-Container
# from your worker container
yarn command:prod cron:messaging:messages-import
yarn command:prod cron:messaging:message-list-fetch
yarn command:prod cron:calendar:calendar-event-list-fetch
@@ -23,7 +23,7 @@ Fahren Sie nur fort, wenn dies eine Neuinstallation ohne wichtige Daten ist.
Um das `PG_DATABASE_PASSWORD` zu aktualisieren, müssen Sie:
```sh
# Aktualisieren Sie das PG_DATABASE_PASSWORD in .env
# Update the PG_DATABASE_PASSWORD in .env
docker compose down --volumes
docker compose up -d
```
@@ -24,7 +24,7 @@ export const MyComponent = () => {
<AppTooltip
className
anchorSelect="#hoverText"
content="Erkunden Sie das Kundenverhalten und die Präferenzen"
content="Explore customer behavior and preferences"
delayHide={0}
offset={6}
noArrow={false}
@@ -34,7 +34,7 @@ export const MyComponent = () => {
/>
</>
);
};
};},{
```
</Tab>
@@ -72,7 +72,7 @@ import { OverflowingTextWithTooltip } from 'twenty-ui/display';
export const MyComponent = () => {
const crmTaskDescription =
'Fassen Sie beim Kunden aufgrund seiner jüngsten Produktanfrage nach. Besprechen Sie Preisoptionen, gehen Sie auf eventuelle Bedenken ein und stellen Sie zusätzliche Produktinformationen bereit. Protokollieren Sie die Details des Gesprächs im CRM zur späteren Referenz.';
'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} />;
};
@@ -30,7 +30,7 @@ export const MyComponent = () => {
/>
);
};
%
```
</Tab>
@@ -64,7 +64,7 @@ export const MyComponent = () => {
return (
<Chip
size="large"
label="Transparenter deaktivierter Chip"
label="Transparent Disabled Chip"
clickable={false}
variant="rounded"
accent="text-secondary"
@@ -89,7 +89,7 @@ export const MyComponent = () => {
return (
<Chip
size="large"
label="Deaktivierter Chip, der bei Überlauf einen Tooltip auslöst."
label="Disabled chip that triggers a tooltip when overflowing."
clickable={false}
variant="regular"
accent="text-primary"
@@ -121,7 +121,7 @@ export const MyComponent = () => {
<Chip
linkToEntity="/entity-link"
entityId="entityTest"
name="Entitätsname"
name="Entity name"
pictureUrl=""
avatarType="rounded"
variant="regular"
@@ -22,7 +22,7 @@ export const MyComponent = () => {
<Select
className
disabled={false}
label="Option auswählen"
label="Select an option"
options={[
{ value: 'option1', label: 'Option A', Icon: IconTwentyStar },
{ value: 'option2', label: 'Option B', Icon: IconTwentyStar },
@@ -19,21 +19,21 @@ import { TextInput } from "@/ui/input/components/TextInput";
export const MyComponent = () => {
const handleChange = (text) => {
console.log("Eingabe geändert:", text);
console.log("Input changed:", text);
};
const handleKeyDown = (event) => {
console.log("Taste gedrückt:", event.key);
console.log("Key pressed:", event.key);
};
return (
<TextInput
className
label="Benutzername"
label="Username"
onChange={handleChange}
fullWidth={false}
disableHotkeys={false}
error="Ungültiger Benutzername"
error="Invalid username"
onKeyDown={handleKeyDown}
RightIcon={null}
/>
@@ -82,13 +82,13 @@ import { AutosizeTextInput } from "@/ui/input/components/AutosizeTextInput";
export const MyComponent = () => {
return (
<AutosizeTextInput
onValidate={() => console.log("onValidate-Funktion ausgelöst")}
onValidate={() => console.log("onValidate function fired")}
minRows={1}
placeholder="Kommentar schreiben"
onFocus={() => console.log("onFocus-Funktion ausgelöst")}
placeholder="Write a comment"
onFocus={() => console.log("onFocus function fired")}
variant="icon"
buttonTitle
value="Aufgabe: "
value="Task: "
/>
);
};
@@ -130,9 +130,9 @@ Die Daten entsprechen nicht dem erwarteten Format für diesen Feldtyp.
**Lösung:** Verwenden Sie das Format `https://domain.com` (empfohlen)
```
⚠️ acme.com (gültig, aber nicht empfohlen)
⚠️ www.acme.com (gültig, aber nicht empfohlen)
✅ https://acme.com (empfohlen)
⚠️ acme.com (valid, but not not recommended)
⚠️ www.acme.com (valid, but not recommended)
✅ https://acme.com (recommended)
```
<Note>Alle Formate sind gültig, aber `https://domain.com` wird empfohlen, da es dem von der E-Mail-/Kalendersynchronisierung verwendeten Format entspricht. Die Verwendung anderer Formate kann doppelte Unternehmen erzeugen.</Note>
@@ -42,14 +42,14 @@ Bevor Sie E-Mails aus Workflows senden können:
Verweisen Sie mit der Syntax `{{variable}}` auf Daten aus vorherigen Schritten:
```text
Hallo {{trigger.object.firstName}},
Hi {{trigger.object.firstName}},
Vielen Dank, dass Sie mit uns Kontakt aufgenommen haben!
Thank you for connecting with us!
Ihr Unternehmen, {{trigger.object.company.name}}, ist jetzt in unserem System.
Your company, {{trigger.object.company.name}}, is now in our system.
Mit freundlichen Grüßen,
Das Team
Best regards,
The Team
```
### Verfügbare Variablen aus Auslösern
@@ -63,15 +63,15 @@ Nachdem parallele Verzweigungen ihre Arbeit abgeschlossen haben, können Sie sie
### Beispiel: Erst verarbeiten, dann benachrichtigen
```
Auslöser
Trigger
├── Verzweigung A: Kundendatensatz aktualisieren
├── Branch A: Update Customer Record
└── Verzweigung B: Support-Ticket erstellen
└── Branch B: Create Support Ticket
↘ ↙
Zusammengeführter Schritt: Bestätigungs-E-Mail senden
Merged Step: Send Confirmation Email
```
Die Bestätigungs-E-Mail wird erst gesendet, nachdem sowohl die Aktualisierung des Kundendatensatzes als auch die Erstellung des Tickets abgeschlossen sind.
@@ -84,12 +84,12 @@ Im Iterator verwenden Sie `{{iterator.currentItem}}`, um auf den aktuellen Daten
**Ziel**: Alle überfälligen Aufgaben als "Late" markieren
```
1. Datensätze suchen (Aufgaben, Fälligkeitsdatum < Heute, Status ≠ Abgeschlossen)
1. Search Records (Tasks, Due Date < Today, Status ≠ Completed)
2. Filter (length > 0)
3. Iterator (searchRecords)
└── Datensatz aktualisieren
- Objekt: Aufgaben
- Datensatz: {{iterator.currentItem.id}}
└── Update Record
- Object: Tasks
- Record: {{iterator.currentItem.id}}
- Status: Late
```
@@ -98,14 +98,14 @@ Im Iterator verwenden Sie `{{iterator.currentItem}}`, um auf den aktuellen Daten
**Ziel**: Ein Webhook erhält eine Bestellung mit mehreren Positionen; für jede Position einen Datensatz erstellen
```
1. Webhook-Trigger (empfängt ein Array von Positionen)
1. Webhook Trigger (receives items array)
2. Filter (items.length > 0)
3. Iterator (trigger.body.items)
└── Datensatz erstellen
- Objekt: Bestellpositionen
└── Create Record
- Object: Order Items
- Name: {{iterator.currentItem.name}}
- Menge: {{iterator.currentItem.qty}}
- Zugehörige Bestellung: {{trigger.body.orderId}}
- Quantity: {{iterator.currentItem.qty}}
- Related Order: {{trigger.body.orderId}}
```
### Bedingte Verarbeitung innerhalb der Schleife
@@ -113,11 +113,11 @@ Im Iterator verwenden Sie `{{iterator.currentItem}}`, um auf den aktuellen Daten
**Ziel**: Nur E-Mails an Kontakte mit gültigen E-Mail-Adressen senden
```
1. Datensätze suchen (Personen)
1. Search Records (People)
2. Iterator (searchRecords)
└── Filter (currentItem.email ist nicht leer)
└── E-Mail senden
- An: {{iterator.currentItem.email}}
└── Filter (currentItem.email is not empty)
└── Send Email
- To: {{iterator.currentItem.email}}
```
## Fehlerbehebung
@@ -129,8 +129,8 @@ Im Iterator verwenden Sie `{{iterator.currentItem}}`, um auf den aktuellen Daten
**Lösung**: Stellen Sie sicher, dass Sie das Ergebnis von Datensätze suchen oder ein Array-Feld übergeben, nicht einen einzelnen Datensatz.
```
Richtig: {{searchRecords}}
Falsch: {{searchRecords[0]}}
Correct: {{searchRecords}}
Wrong: {{searchRecords[0]}}
```
### Iterator wird nicht ausgeführt
@@ -82,19 +82,19 @@ Erstellen Sie einen Workflow, der alle Post-Win-Aktivitäten automatisch erledig
**Beispiel für den E-Mail-Text**:
```
Hi CS-Team,
Hi CS Team,
Wir haben einen neuen Kunden!
We have a new customer!
Unternehmen: {{trigger.object.company.name}}
Company: {{trigger.object.company.name}}
Deal: {{trigger.object.name}}
Wert: {{trigger.object.amount}}
Vertriebsmitarbeiter: {{trigger.object.owner.name}}
Abschlussdatum: {{trigger.object.closedAt}}
Value: {{trigger.object.amount}}
Sales Rep: {{trigger.object.owner.name}}
Close Date: {{trigger.object.closedAt}}
Eine Onboarding-Aufgabe wurde automatisch erstellt.
An onboarding task has been created automatically.
Sorgen wir für einen großartigen Start!
Let's give them a great start!
```
### Schritt 7: Bestätigung an Vertriebsmitarbeiter senden
@@ -45,11 +45,11 @@ description: Häufig gestellte Fragen zu Workflows in Twenty.
**Aktuelle Problemumgehung**: Erstellen Sie mehrere Verzweigungen von Ihrem Schritt aus, die jeweils mit einer **Filter**-Aktion beginnen:
```
Schritt 1
Step 1
├── Verzweigung A: Filter (Bedingung = true) → Aktionen...
├── Branch A: Filter (condition = true) → Actions...
└── Verzweigung B: Filter (Bedingung = false) → Aktionen...
└── Branch B: Filter (condition = false) → Actions...
```
Nur die Verzweigung, deren Filterbedingung erfüllt ist, führt die nachfolgenden Aktionen aus.
@@ -6,7 +6,7 @@ info: Una mirada detallada a la arquitectura de carpetas de nuestro servidor
La estructura del directorio backend es la siguiente:
```
servidor
server
└───ability
└───constants
└───core
@@ -18,7 +18,7 @@ servidor
└───integrations
└───metadata
└───workspace
└───utils
└───utils},{
```
## Habilidad
@@ -56,31 +56,31 @@ Genera y sirve un esquema GraphQL personalizado basado en los metadatos.
```
workspace
└───construcción-esquema-espacio-de-trabajo
└───fábricas
└───tipos-graphql
└───bases-datos
└───workspace-schema-builder
└───factories
└───graphql-types
└───database
└───interfaces
└───definiciones-objetos
└───servicios
└───almacenamiento
└───utilidades
└───constructor-resolver-espacio-de-trabajo
└───fábricas
└───object-definitions
└───services
└───storage
└───utils
└───workspace-resolver-builder
└───factories
└───interfaces
└───constructor-consultas-espacio-de-trabajo
└───fábricas
└───workspace-query-builder
└───factories
└───interfaces
└───ejecutor-consultas-espacio-de-trabajo
└───workspace-query-runner
└───interfaces
└───utilidades
└───fuente-datos-espacio-de-trabajo
└───gestor-espacio-de-trabajo
└───ejecutor-migraciones-espacio-de-trabajo
└───utilidades
└───espacio.trabajo.module.ts
└───espacio.trabajo.factory.spec.ts
└───espacio.trabajo.factory.ts
└───utils
└───workspace-datasource
└───workspace-manager
└───workspace-migration-runner
└───utils
└───workspace.module.ts
└───workspace.factory.spec.ts
└───workspace.factory.ts
```
La raíz del directorio de espacio de trabajo incluye el `espacio.trabajo.factory.ts`, un archivo que contiene la función `createGraphQLSchema`. Esta función genera un esquema específico para el espacio de trabajo utilizando los metadatos para adaptar un esquema para espacios de trabajo individuales. Al separar la construcción del esquema y del resolver, usamos la función `makeExecutableSchema`, que combina estos elementos discretos.
@@ -83,8 +83,8 @@ If you feel like you need to add a `useEffect` in your root component, you shoul
Puedes aplicar lo mismo para la lógica de obtención de datos, con hooks de Apollo.
```tsx
// ❌ Malo, provocará re-renderizados incluso si los datos no están cambiando,
// porque useEffect necesita volver a evaluarse
// ❌ Bad, will cause re-renders even if data is not changing,
// because useEffect needs to be re-evaluated
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
@@ -104,8 +104,8 @@ export const App = () => (
```
```tsx
// ✅ Bueno, no provocará re-renderizados si los datos no están cambiando,
// porque useEffect se vuelve a evaluar en otro componente hermano
// ✅ Good, will not cause re-renders if data is not changing,
// because useEffect is re-evaluated in another sibling component
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
@@ -172,13 +172,13 @@ Los nombres de variables deben describir con precisión el propósito o función
Los nombres genéricos en programación no son ideales porque carecen de especificidad, lo que lleva a la ambigüedad y reduce la legibilidad del código. Tales nombres no transmiten el propósito de la variable o función, lo que dificulta a los desarrolladores entender la intención del código sin una investigación más profunda. Esto puede resultar en un aumento del tiempo de depuración, una mayor susceptibilidad a errores y dificultades en el mantenimiento y colaboración. Mientras tanto, la nomenclatura descriptiva hace que el código sea autoexplicativo y más fácil de navegar, mejorando la calidad del código y la productividad del desarrollador.
```tsx
// ❌ Malo, usa un nombre genérico que no comunica claramente su
// propósito o contenido
// ❌ Bad, uses a generic name that doesn't communicate its
// purpose or content clearly
const [value, setValue] = useState('');
```
```tsx
// ✅ Bueno, usa un nombre descriptivo
// ✅ Good, uses a descriptive name
const [email, setEmail] = useState('');
```
@@ -191,14 +191,14 @@ const [email, setEmail] = useState('');
Los nombres de manejadores de eventos deben comenzar con `handle`, mientras que `on` es un prefijo usado para nombrar eventos en las props de los componentes.
```tsx
// ❌ Malo
// ❌ Bad
const onEmailChange = (val: string) => {
// ...
};
```
```tsx
// ✅ Bueno
// ✅ Good
const handleEmailChange = (val: string) => {
// ...
};
@@ -226,13 +226,13 @@ const EmailField = ({ value, disabled = false }: EmailFieldProps) => (
**Uso**
```tsx
// ❌ Malo, pasar el mismo valor que el valor predeterminado no aporta nada
const Form = () => <EmailField value="username@email.com" disabled={false} />;
// ❌ Bad, passing in the same value as the default value adds no value
const Form = () => <EmailField value=\"username@email.com\" disabled={false} />;
```
```tsx
// ✅ Bueno, asume el valor predeterminado
const Form = () => <EmailField value="username@email.com" />;
// ✅ Good, assumes the default value
const Form = () => <EmailField value=\"username@email.com\" />;
```
## Componente como props
@@ -244,7 +244,7 @@ El ejemplo más común de esto son los componentes de icono:
```tsx
const SomeParentComponent = () => <MyComponent Icon={MyIcon} />;
// En MyComponent
// In MyComponent
const MyComponent = ({ MyIcon }: { MyIcon: IconComponent }) => {
const theme = useTheme();
@@ -289,7 +289,7 @@ Al importar, opta por los alias designados en lugar de especificar rutas complet
**Uso**
```tsx
// ❌ Malo, especifica toda la ruta relativa
// ❌ Bad, specifies the entire relative path
import {
CatalogDecorator
} from '../../../../../testing/decorators/CatalogDecorator';
@@ -299,7 +299,7 @@ import {
```
```tsx
// ✅ Bueno, utiliza los alias designados
// ✅ Good, utilises the designated aliases
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from 'twenty-ui/testing';
```
@@ -314,10 +314,10 @@ const validationSchema = z
exist: z.boolean(),
email: z
.string()
.email('El correo electrónico debe ser válido'),
.email('Email must be a valid email'),
password: z
.string()
.regex(PASSWORD_REGEX, 'La contraseña debe contener al menos 8 caracteres'),
.regex(PASSWORD_REGEX, 'Password must contain at least 8 characters'),
})
.required();
@@ -26,7 +26,7 @@ npx nx run twenty-front:graphql:generate
### Lint
```bash
npx nx run twenty-front:lint # pasar --fix para corregir errores de lint
npx nx run twenty-front:lint # pass --fix to fix lint errors
```
## Traducciones
@@ -39,10 +39,10 @@ npx nx run twenty-front:lingui:compile
### Prueba
```bash
npx nx run twenty-front:test # ejecutar pruebas con Jest
npx nx run twenty-front:storybook:serve:dev # ejecutar Storybook
npx nx run twenty-front:storybook:test # ejecutar pruebas # (requiere que yarn storybook:serve:dev esté en ejecución)
npx nx run twenty-front:storybook:coverage # (requiere que yarn storybook:serve:dev esté en ejecución)
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)
```
## Stack Tecnológico
@@ -68,10 +68,10 @@ const EmailField: React.FC<{
```
```tsx
/* ✅ - Bueno, se define explícitamente un tipo (OwnProps) separado para las
* props del componente
* - Este método no incluye automáticamente la prop children. Si
* quieres incluirla, debes especificarla en OwnProps.
/* ✅ - Good, a separate type (OwnProps) is explicitly defined for the
* component's props
* - This method doesn't automatically include the children prop. If
* you want to include it, you have to specify it in OwnProps.
*/
type EmailFieldProps = {
value: string;
@@ -95,8 +95,8 @@ const MyComponent = (props: OwnProps) => {
```
```tsx
/* ✅ - Bueno, enumera explícitamente todas las props
* - Mejora la legibilidad y la mantenibilidad
/* ✅ - Good, Explicitly lists all props
* - Enhances readability and maintainability
*/
const MyComponent = ({ prop1, prop2, prop3 }: MyComponentProps) => {
return <OtherComponent {...{ prop1, prop2, prop3 }} />;
@@ -124,10 +124,10 @@ const value = process.env.MY_VALUE ?? 'default';
### Usar encadenamiento opcional `?.`
```tsx
// ❌ Malo
// ❌ Bad
onClick && onClick();
// ✅ Bueno
// ✅ Good
onClick?.();
```
@@ -160,7 +160,7 @@ Debes ejecutar todos los comandos de los siguientes pasos desde la raíz del pro
```
Esto crea un rol de superusuario llamado `postgres` con acceso de inicio de sesión.
```bash
Nombre del rol | Atributos | Miembro de
Role name | Attributes | Member of
-----------+-------------+-----------
postgres | Superuser | {}
john | Superuser | {}
@@ -19,7 +19,7 @@ export const MyComponent = () => {
return (
<Chip
size="large"
label="Chip clicable"
label="Clickable Chip"
clickable={true}
variant="highlighted"
accent="text-primary"
@@ -64,7 +64,7 @@ export const MyComponent = () => {
return (
<Chip
size="large"
label="Chip transparente deshabilitado"
label="Transparent Disabled Chip"
clickable={false}
variant="rounded"
accent="text-secondary"
@@ -89,7 +89,7 @@ export const MyComponent = () => {
return (
<Chip
size="large"
label="Chip deshabilitado que muestra un tooltip al desbordarse."
label="Disabled chip that triggers a tooltip when overflowing."
clickable={false}
variant="regular"
accent="text-primary"
@@ -121,7 +121,7 @@ export const MyComponent = () => {
<Chip
linkToEntity="/entity-link"
entityId="entityTest"
name="Nombre de la entidad"
name="Entity name"
pictureUrl=""
avatarType="rounded"
variant="regular"
@@ -20,7 +20,7 @@ export const MyComponent = () => {
const [selectedIcon, setSelectedIcon] = useState("");
const handleIconChange = ({ iconKey, Icon }) => {
console.log("Ícono Seleccionado:", iconKey);
console.log("Selected Icon:", iconKey);
setSelectedIcon(iconKey);
};
@@ -22,10 +22,10 @@ export const MyComponent = () => {
<Select
className
disabled={false}
label="Selecciona una opción"
label="Select an option"
options={[
{ value: 'option1', label: 'Opción A', Icon: IconTwentyStar },
{ value: 'option2', label: 'Opción B', Icon: IconTwentyStar },
{ value: 'option1', label: 'Option A', Icon: IconTwentyStar },
{ value: 'option2', label: 'Option B', Icon: IconTwentyStar },
]}
value="option1"
/>
@@ -132,8 +132,8 @@ export const MyComponent = () => {
<TextArea
disabled={false}
minRows={4}
onChange={()=>console.log('Función onChange ejecutada')}
placeholder="Introduce texto aquí"
onChange={()=>console.log('On change function fired')}
placeholder="Enter text here"
value=""
/>
);
@@ -13,12 +13,12 @@ export const MyComponent = () => {
return (
<Toggle
value = {true}
onChange = {()=>console.log('Evento onChange')}
onChange = {()=>console.log('On Change event')}
color="green"
toggleSize = "medium"
/>
);
};
};},{
```
</Tab>
File diff suppressed because one or more lines are too long
@@ -130,9 +130,9 @@ Los datos no coinciden con el formato esperado para ese tipo de campo.
**Solución:** Usa el formato `https://domain.com` (recomendado)
```
⚠️ acme.com (válido, pero no recomendado)
⚠️ www.acme.com (válido, pero no recomendado)
✅ https://acme.com (recomendado)
⚠️ acme.com (valid, but not recommended)
⚠️ www.acme.com (valid, but not recommended)
✅ https://acme.com (recommended)
```
<Note>Todos los formatos son válidos, pero se recomienda `https://domain.com` porque coincide con el formato utilizado por la sincronización de correo/calendario. Usar otros formatos puede crear empresas duplicadas.</Note>
@@ -140,8 +140,8 @@ Puede actualizar registros existentes Y crear otros nuevos en la misma importaci
```csv
email,firstName,lastName,jobTitle
john@acme.com,John,Smith,Senior Manager ← Actualiza existente (el email coincide)
newperson@acme.com,New,Person,Analyst ← Crea nuevo (el email no coincide)
john@acme.com,John,Smith,Senior Manager ← Updates existing (email matches)
newperson@acme.com,New,Person,Analyst ← Creates new (email doesn't match)
```
## Errores comunes que se deben evitar
@@ -63,15 +63,15 @@ Después de que las ramas en paralelo completen su trabajo, puedes volver a unir
### Ejemplo: procesar y luego notificar
```
Disparador
Trigger
├── Rama A: Actualizar el registro del cliente
├── Branch A: Update Customer Record
└── Rama B: Crear ticket de soporte
└── Branch B: Create Support Ticket
↘ ↙
Paso fusionado: Enviar correo de confirmación
Merged Step: Send Confirmation Email
```
El correo de confirmación se envía solo después de que se completen tanto la actualización del cliente como la creación del ticket.
@@ -82,19 +82,19 @@ Crea un flujo de trabajo que gestione automáticamente todas las actividades pos
**Ejemplo de cuerpo del correo**:
```
Hola equipo de CS,
Hi CS Team,
¡Tenemos un nuevo cliente!
We have a new customer!
Empresa: {{trigger.object.company.name}}
Negocio: {{trigger.object.name}}
Valor: {{trigger.object.amount}}
Representante de ventas: {{trigger.object.owner.name}}
Fecha de cierre: {{trigger.object.closedAt}}
Company: {{trigger.object.company.name}}
Deal: {{trigger.object.name}}
Value: {{trigger.object.amount}}
Sales Rep: {{trigger.object.owner.name}}
Close Date: {{trigger.object.closedAt}}
Se ha creado automáticamente una tarea de incorporación.
An onboarding task has been created automatically.
¡Démosles un gran comienzo!
Let's give them a great start!
```
### Paso 7: Confirmar al representante de ventas
@@ -23,7 +23,7 @@ npx nx run twenty-server:start
### Analyse
```
npx nx run twenty-server:lint # passez --fix pour corriger les erreurs de lint
npx nx run twenty-server:lint # pass --fix to fix lint errors
```
### Test
@@ -83,8 +83,8 @@ If you feel like you need to add a `useEffect` in your root component, you shoul
Vous pouvez appliquer la même chose à la logique de récupération de données, avec les hooks Apollo.
```tsx
// ❌ Mauvais, provoquera de nouveaux rendus même si les données ne changent pas,
// car useEffect doit être réévalué
// ❌ Bad, will cause re-renders even if data is not changing,
// because useEffect needs to be re-evaluated
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
@@ -104,8 +104,8 @@ export const App = () => (
```
```tsx
// ✅ Bon, ne provoquera pas de nouveaux rendus si les données ne changent pas,
// car useEffect est réévalué dans un autre composant frère
// ✅ Good, will not cause re-renders if data is not changing,
// because useEffect is re-evaluated in another sibling component
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
@@ -172,13 +172,13 @@ Les noms de variables doivent décrire précisément l'objectif ou la fonction d
Les noms génériques en programmation ne sont pas idéaux car ils manquent de spécificité, ce qui conduit à une ambiguïté et réduit la lisibilité du code. De tels noms ne parviennent pas à transmettre l'objectif de la variable ou de la fonction, rendant difficile pour les développeurs de comprendre l'intention du code sans une enquête plus approfondie. Cela peut entraîner un temps de débogage accru, une plus grande vulnérabilité aux erreurs et des difficultés de maintenance et de collaboration. Pendant ce temps, des noms descriptifs rendent le code explicite et plus facile à naviguer, améliorant la qualité du code et la productivité des développeurs.
```tsx
// ❌ Mauvais, utilise un nom générique qui ne communique pas clairement son
// objectif ou son contenu
// ❌ Bad, uses a generic name that doesn't communicate its
// purpose or content clearly
const [value, setValue] = useState('');
```
```tsx
// ✅ Bon, utilise un nom descriptif
// ✅ Good, uses a descriptive name
const [email, setEmail] = useState('');
```
@@ -191,14 +191,14 @@ const [email, setEmail] = useState('');
Les noms des gestionnaires d'événements doivent commencer par `handle`, tandis que `on` est un préfixe utilisé pour nommer les événements dans les propriétés des composants.
```tsx
// ❌ Mauvais
// ❌ Bad
const onEmailChange = (val: string) => {
// ...
};
```
```tsx
// ✅ Bon
// ✅ Good
const handleEmailChange = (val: string) => {
// ...
};
@@ -226,12 +226,12 @@ const EmailField = ({ value, disabled = false }: EmailFieldProps) => (
**Utilisation**
```tsx
// ❌ Mauvais, passer la même valeur que la valeur par défaut n'apporte rien
// ❌ Bad, passing in the same value as the default value adds no value
const Form = () => <EmailField value="username@email.com" disabled={false} />;
```
```tsx
// ✅ Bon, s'appuie sur la valeur par défaut
// ✅ Good, assumes the default value
const Form = () => <EmailField value="username@email.com" />;
```
@@ -244,7 +244,7 @@ L'exemple le plus courant pour cela est les composants icône :
```tsx
const SomeParentComponent = () => <MyComponent Icon={MyIcon} />;
// Dans MyComponent
// In MyComponent
const MyComponent = ({ MyIcon }: { MyIcon: IconComponent }) => {
const theme = useTheme();
@@ -289,7 +289,7 @@ Lors de l'importation, optez pour les alias désignés plutôt que de spécifier
**Utilisation**
```tsx
// ❌ Mauvais, spécifie l'intégralité du chemin relatif
// ❌ Bad, specifies the entire relative path
import {
CatalogDecorator
} from '../../../../../testing/decorators/CatalogDecorator';
@@ -299,7 +299,7 @@ import {
```
```tsx
// ✅ Bon, utilise les alias désignés
// ✅ Good, utilises the designated aliases
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from 'twenty-ui/testing';
```
@@ -314,10 +314,10 @@ const validationSchema = z
exist: z.boolean(),
email: z
.string()
.email('L\'adresse e-mail doit être valide'),
.email('Email must be a valid email'),
password: z
.string()
.regex(PASSWORD_REGEX, 'Le mot de passe doit contenir au moins 8 caractères'),
.regex(PASSWORD_REGEX, 'Password must contain at least 8 characters'),
})
.required();
@@ -112,9 +112,9 @@ Ensuite, dans le composant modal :
```tsx
const MyDropdownComponent = ({ onClose }: { onClose: () => void }) => {
// 2. Utilisez le hook useScopedHotkeys pour écouter la touche Échap.
// Notez que la touche Échap est un raccourci courant qui peut être utilisé par de nombreux autres composants
// Il est donc important d'utiliser une portée de raccourci clavier pour éviter les conflits
// 2. Use the useScopedHotkeys hook to listen for Escape.
// Note that escape is a common hotkey that could be used by many other components
// So it's important to use a hotkey scope to avoid conflicts
useScopedHotkeys(
Key.Escape,
() => {
@@ -123,7 +123,7 @@ const MyDropdownComponent = ({ onClose }: { onClose: () => void }) => {
ExampleHotkeyScopes.ExampleModal,
);
return <div>Mon composant modal</div>;
return <div>My modal component</div>;
};
```
@@ -79,7 +79,7 @@ type EmailFieldProps = {
const EmailField = ({ value }: EmailFieldProps) => (
<TextInput value={value} disabled fullWidth />
);
);},{
```
#### Pas de propagation de props à variable unique dans les éléments JSX
@@ -95,8 +95,8 @@ const MyComponent = (props: OwnProps) => {
```
```tsx
/* ✅ - Bon, liste explicitement tous les props
* - Améliore la lisibilité et la maintenabilité
/* ✅ - Good, Explicitly lists all props
* - Enhances readability and maintainability
*/
const MyComponent = ({ prop1, prop2, prop3 }: MyComponentProps) => {
return <OtherComponent {...{ prop1, prop2, prop3 }} />;
@@ -124,10 +124,10 @@ const value = process.env.MY_VALUE ?? 'default';
### Utilisez la chaîne facultative `?.`
```tsx
// ❌ Mauvais
// ❌ Bad
onClick && onClick();
// ✅ Bon
// ✅ Good
onClick?.();
```
@@ -160,10 +160,10 @@ Vous devez exécuter toutes les commandes des étapes suivantes depuis la racine
```
Cela crée un rôle superutilisateur nommé `postgres` avec un accès de connexion.
```bash
Nom du rôle | Attributs | Membre de
Role name | Attributes | Member of
-----------+-------------+-----------
postgres | Superutilisateur | {}
john | Superutilisateur | {}
postgres | Superuser | {}
john | Superuser | {}
```
**Option 2 :** Si vous avez installé Docker :
@@ -19,7 +19,7 @@ export const MyComponent = () => {
return (
<Chip
size="large"
label="Puce cliquable"
label="Clickable Chip"
clickable={true}
variant="highlighted"
accent="text-primary"
@@ -30,7 +30,7 @@ export const MyComponent = () => {
/>
);
};
},{
```
</Tab>
@@ -64,7 +64,7 @@ export const MyComponent = () => {
return (
<Chip
size="large"
label="Puce transparente désactivée"
label="Transparent Disabled Chip"
clickable={false}
variant="rounded"
accent="text-secondary"
@@ -89,7 +89,7 @@ export const MyComponent = () => {
return (
<Chip
size="large"
label="Puce désactivée qui déclenche une info-bulle en cas de dépassement."
label="Disabled chip that triggers a tooltip when overflowing."
clickable={false}
variant="regular"
accent="text-primary"
@@ -121,7 +121,7 @@ export const MyComponent = () => {
<Chip
linkToEntity="/entity-link"
entityId="entityTest"
name="Nom de l'entité"
name="Entity name"
pictureUrl=""
avatarType="rounded"
variant="regular"
@@ -32,7 +32,7 @@ Vous pouvez importer chaque icône en tant que composant. Voici un exemple :
```jsx
import { IconArrowLeft } from "@tabler/icons-react";
export const MonComposant = () => {
export const MyComponent = () => {
return <IconArrowLeft color="red" size={48} />;
};
```
@@ -69,7 +69,7 @@ Affiche une icône de carnet d'adresses.
```jsx
import { IconAddressBook } from 'twenty-ui/display';
export const MonComposant = () => {
export const MyComponent = () => {
return <IconAddressBook size={24} stroke={2} />;
};
```
@@ -20,7 +20,7 @@ export const MyComponent = () => {
const [selectedIcon, setSelectedIcon] = useState("");
const handleIconChange = ({ iconKey, Icon }) => {
console.log("Icône sélectionnée:", iconKey);
console.log("Selected Icon:", iconKey);
setSelectedIcon(iconKey);
};
@@ -42,14 +42,14 @@ Avant de pouvoir envoyer des e-mails depuis des workflows :
Faites référence aux données des étapes précédentes en utilisant la syntaxe `{{variable}}` :
```text
Bonjour {{trigger.object.firstName}},
Hi {{trigger.object.firstName}},
Merci d'avoir pris contact avec nous !
Thank you for connecting with us!
Votre entreprise, {{trigger.object.company.name}}, est maintenant dans notre système.
Your company, {{trigger.object.company.name}}, is now in our system.
Cordialement,
L'équipe
Best regards,
The Team
```
### Variables disponibles à partir des déclencheurs
@@ -63,15 +63,15 @@ Une fois que les branches parallèles ont terminé leur travail, vous pouvez les
### Exemple : Traiter puis notifier
```
Déclencheur
Trigger
├── Branche A : Mettre à jour l'enregistrement client
├── Branch A: Update Customer Record
└── Branche B : Créer un ticket d'assistance
└── Branch B: Create Support Ticket
↘ ↙
Étape fusionnée : Envoyer un e-mail de confirmation
Merged Step: Send Confirmation Email
```
L'e-mail de confirmation n'est envoyé qu'après la mise à jour du client et la création du ticket.
@@ -84,13 +84,13 @@ Dans l'itérateur, utilisez `{{iterator.currentItem}}` pour accéder à l'enregi
**Objectif** : Marquer toutes les tâches en retard comme "En retard"
```
1. Rechercher des enregistrements (Tâches, Date d'échéance < Aujourd'hui, StatutTerminé)
2. Filtre (longueur > 0)
3. Itérateur (searchRecords)
└── Mettre à jour l'enregistrement
- Objet : Tâches
- Enregistrement : {{iterator.currentItem.id}}
- Statut : En retard
1. Search Records (Tasks, Due Date < Today, StatusCompleted)
2. Filter (length > 0)
3. Iterator (searchRecords)
└── Update Record
- Object: Tasks
- Record: {{iterator.currentItem.id}}
- Status: Late
```
### Créer des enregistrements à partir d'un tableau
@@ -98,14 +98,14 @@ Dans l'itérateur, utilisez `{{iterator.currentItem}}` pour accéder à l'enregi
**Objectif** : Le webhook reçoit une commande avec plusieurs éléments, créer un enregistrement pour chacun
```
1. Déclencheur Webhook (reçoit un tableau d'éléments)
2. Filtre (items.length > 0)
3. Itérateur (trigger.body.items)
└── Créer un enregistrement
- Objet : Articles de commande
- Nom : {{iterator.currentItem.name}}
- Quantité : {{iterator.currentItem.qty}}
- Commande associée : {{trigger.body.orderId}}
1. Webhook Trigger (receives items array)
2. Filter (items.length > 0)
3. Iterator (trigger.body.items)
└── Create Record
- Object: Order Items
- Name: {{iterator.currentItem.name}}
- Quantity: {{iterator.currentItem.qty}}
- Related Order: {{trigger.body.orderId}}
```
### Traitement conditionnel dans la boucle
@@ -113,11 +113,11 @@ Dans l'itérateur, utilisez `{{iterator.currentItem}}` pour accéder à l'enregi
**Objectif** : N'envoyer un e-mail qu'aux contacts avec des adresses valides
```
1. Rechercher des enregistrements (Personnes)
2. Itérateur (searchRecords)
└── Filtre (currentItem.email n'est pas vide)
└── Envoyer un e-mail
- À : {{iterator.currentItem.email}}
1. Search Records (People)
2. Iterator (searchRecords)
└── Filter (currentItem.email is not empty)
└── Send Email
- To: {{iterator.currentItem.email}}
```
## Résolution des problèmes
@@ -129,8 +129,8 @@ Dans l'itérateur, utilisez `{{iterator.currentItem}}` pour accéder à l'enregi
**Correctif** : Assurez-vous de passer le résultat de Rechercher des enregistrements ou un champ de type tableau, pas un seul enregistrement.
```
✅ Correct : {{searchRecords}}
Incorrect : {{searchRecords[0]}}
✅ Correct: {{searchRecords}}
Wrong: {{searchRecords[0]}}
```
### L'itérateur ne s'exécute pas
@@ -140,7 +140,7 @@ Dans l'itérateur, utilisez `{{iterator.currentItem}}` pour accéder à l'enregi
**Correctif** : Ajoutez un filtre avant l'itérateur pour vérifier la longueur du tableau :
```
Filtre : {{searchRecords.length}} > 0
Filter: {{searchRecords.length}} > 0
```
### Les actions s'exécutent trop de fois
@@ -11,7 +11,7 @@ Da qualsiasi altra cartella puoi eseguire `npx nx {command} twenty-server` (oppu
### Impostazione iniziale
```
npx nx database:reset twenty-server # configura il database con dati di esempio per lo sviluppo
npx nx database:reset twenty-server # setup the database with dev seeds
```
### Avvio del server
@@ -23,14 +23,14 @@ npx nx run twenty-server:start
### Lint
```
npx nx run twenty-server:lint # passa --fix per correggere gli errori di lint
npx nx run twenty-server:lint # pass --fix to fix lint errors
```
### Test
```
npx nx run twenty-server:test:unit # esegui test unitari
npx nx run twenty-server:test:integration # esegui test di integrazione
npx nx run twenty-server:test:unit # run unit tests
npx nx run twenty-server:test:integration # run integration tests
```
Nota: puoi eseguire `npx nx run twenty-server:test:integration:with-db-reset` nel caso in cui sia necessario resettare il database prima di eseguire i test di integrazione.
@@ -15,7 +15,9 @@ Puoi saperne di più su come funziona Zapier [qui](https://zapier.com/how-it-wor
### Passo 1: Installa i pacchetti di Zapier
```bash
cd packages/twenty-zapier\n\nyarn
cd packages/twenty-zapier
yarn
```
### Passaggio 2: Accedi con la CLI
@@ -83,8 +83,8 @@ Se senti di dover aggiungere un `useEffect` nel tuo componente radice, dovresti
Puoi applicare lo stesso per la logica di recupero dati, con i hook di Apollo.
```tsx
// ❌ Sconsigliato, cause re-render anche se i dati non cambiano,
// perché useEffect deve essere ri-eseguito
// ❌ Bad, will cause re-renders even if data is not changing,
// because useEffect needs to be re-evaluated
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
@@ -104,8 +104,8 @@ export const App = () => (
```
```tsx
// ✅ Consigliato, non cause re-render se i dati non cambiano,
// perché useEffect viene ri-eseguito in un altro componente fratello
// ✅ Good, will not cause re-renders if data is not changing,
// because useEffect is re-evaluated in another sibling component
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
@@ -172,13 +172,13 @@ I nomi delle variabili dovrebbero descrivere precisamente lo scopo o la funzione
I nomi generici nella programmazione non sono ideali perché mancano di specificità, portando all'ambiguità e riducendo la leggibilità del codice. Tali nomi non riescono a trasmettere lo scopo della variabile o della funzione, rendendo difficile per gli sviluppatori comprendere l'intento del codice senza un'indagine più approfondita. Questo può risultare in tempi di debug più lunghi, maggiore suscettibilità agli errori e difficoltà nella manutenzione e nella collaborazione. Nel frattempo, una denominazione descrittiva rende il codice autoesplicativo e più facile da navigare, migliorando la qualità del codice e la produttività dello sviluppatore.
```tsx
// ❌ Sconsigliato, usa un nome generico che non comunica chiaramente
// scopo o contenuto
// ❌ Bad, uses a generic name that doesn't communicate its
// purpose or content clearly
const [value, setValue] = useState('');
```
```tsx
// ✅ Consigliato, usa un nome descrittivo
// ✅ Good, uses a descriptive name
const [email, setEmail] = useState('');
```
@@ -191,14 +191,14 @@ const [email, setEmail] = useState('');
I nomi dei gestori degli eventi dovrebbero iniziare con `handle`, mentre `on` è un prefisso usato per nominare gli eventi nelle props dei componenti.
```tsx
// ❌ Sconsigliato
// ❌ Bad
const onEmailChange = (val: string) => {
// ...
};
```
```tsx
// ✅ Consigliato
// ✅ Good
const handleEmailChange = (val: string) => {
// ...
};
@@ -226,12 +226,12 @@ const EmailField = ({ value, disabled = false }: EmailFieldProps) => (
**Utilizzo**
```tsx
// ❌ Sconsigliato, passare lo stesso valore del valore predefinito non apporta alcun beneficio
// ❌ Bad, passing in the same value as the default value adds no value
const Form = () => <EmailField value="username@email.com" disabled={false} />;
```
```tsx
// ✅ Consigliato, presume il valore predefinito
// ✅ Good, assumes the default value
const Form = () => <EmailField value="username@email.com" />;
```
@@ -244,7 +244,7 @@ L'esempio più comune per questo sono i componenti icona:
```tsx
const SomeParentComponent = () => <MyComponent Icon={MyIcon} />;
// Nel componente MyComponent
// In MyComponent
const MyComponent = ({ MyIcon }: { MyIcon: IconComponent }) => {
const theme = useTheme();
@@ -289,7 +289,7 @@ Quando importi, opta per gli alias designati anziché specificare percorsi compl
**Utilizzo**
```tsx
// ❌ Sconsigliato, specifica l'intero percorso relativo
// ❌ Bad, specifies the entire relative path
import {
CatalogDecorator
} from '../../../../../testing/decorators/CatalogDecorator';
@@ -299,7 +299,7 @@ import {
```
```tsx
// ✅ Consigliato, utilizza gli alias designati
// ✅ Good, utilises the designated aliases
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from 'twenty-ui/testing';
```
@@ -314,10 +314,10 @@ const validationSchema = z
exist: z.boolean(),
email: z
.string()
.email('L\'indirizzo email deve essere valido'),
.email('Email must be a valid email'),
password: z
.string()
.regex(PASSWORD_REGEX, 'La password deve contenere almeno 8 caratteri'),
.regex(PASSWORD_REGEX, 'Password must contain at least 8 characters'),
})
.required();
@@ -9,17 +9,17 @@ In questa guida, esplorerai i dettagli della struttura delle directory del proge
Seguendo questa convenzione di architettura delle cartelle, è più facile trovare i file relativi a funzionalità specifiche e garantire che l'applicazione sia scalabile e manutenibile.
```
fronte
└───moduli
│ └───modulo1
└───sottomodulo1
│ └───modulo2
│ └───ui
│ └───schermo
└───ingressi
│ │ └───bottoni
└───...
└───pagine
front
└───modules
└───module1
└───submodule1
└───module2
└───ui
│ └───display
└───inputs
│ │ └───buttons
└───...
└───pages
└───...
```
@@ -33,22 +33,22 @@ Ogni modulo rappresenta una funzionalità o un gruppo di funzionalità, comprend
Dovrebbero tutti seguire la struttura sottostante. Puoi nidificare moduli all'interno di moduli (indicati come sottomoduli) e le stesse regole si applicano.
```
modulo1
└───componenti
│ └───componente1
│ └───componente2
└───costanti
└───contesti
└───graphql
│ └───frammenti
│ └───query
│ └───mutazioni
└───hook
│ └───interni
└───stati
│ └───selettori
└───tipi
└───utilità
module1
└───components
└───component1
└───component2
└───constants
└───contexts
└───graphql
└───fragments
└───queries
└───mutations
└───hooks
└───internal
└───states
└───selectors
└───types
└───utils
```
### Contesti
@@ -26,7 +26,7 @@ npx nx run twenty-front:graphql:generate
### Lint
```bash
npx nx run twenty-front:lint # aggiungere --fix per correggere gli errori di lint
npx nx run twenty-front:lint # pass --fix to fix lint errors
```
## Traduzioni
@@ -39,10 +39,10 @@ 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)
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)
```
## Tech Stack
@@ -68,10 +68,10 @@ const EmailField: React.FC<{
```
```tsx
/* ✅ - Bene, un tipo separato (OwnProps) è definito esplicitamente per le
* props del componente
* - Questo metodo non include automaticamente la proprietà children. Se
* si vuole includerla, occorre specificarla in OwnProps.
/* ✅ - Good, a separate type (OwnProps) is explicitly defined for the
* component's props
* - This method doesn't automatically include the children prop. If
* you want to include it, you have to specify it in OwnProps.
*/
type EmailFieldProps = {
value: string;
@@ -95,8 +95,8 @@ const MyComponent = (props: OwnProps) => {
```
```tsx
/* ✅ - Bene, elenca esplicitamente tutte le props
* - Migliora la leggibilità e la manutenibilità
/* ✅ - Good, Explicitly lists all props
* - Enhances readability and maintainability
*/
const MyComponent = ({ prop1, prop2, prop3 }: MyComponentProps) => {
return <OtherComponent {...{ prop1, prop2, prop3 }} />;
@@ -114,20 +114,20 @@ Ragionamento:
### Usa l'operatore di coalescenza dei valori null `??`
```tsx
// ❌ Male, può restituire 'default' anche se il valore è 0 o ''
// ❌ Bad, can return 'default' even if value is 0 or ''
const value = process.env.MY_VALUE || 'default';
// ✅ Bene, restituirà 'default' solo se il valore è null o undefined
// ✅ Good, will return 'default' only if value is null or undefined
const value = process.env.MY_VALUE ?? 'default';
```
### Usa il collegamento delle opzioni `?.`
```tsx
// ❌ Male
// ❌ Bad
onClick && onClick();
// ✅ Bene
// ✅ Good
onClick?.();
```
@@ -167,7 +167,7 @@ let color = Color.Red;
```
```tsx
// ✅ Bene, utilizza un letterale di stringa
// ✅ Good, utilizes a string literal
let color: "red" | "green" | "blue" = "red";
```
@@ -198,12 +198,12 @@ setHotkeyScopeAndMemorizePreviousScope(
Stilizza i componenti con [Linaria styled](https://github.com/callstack/linaria).
```tsx
// ❌ Male
<div className="my-class">Ciao Mondo</div>
// ❌ Bad
<div className="my-class">Hello World</div>
```
```tsx
// ✅ Bene
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
@@ -212,14 +212,14 @@ const StyledTitle = styled.div`
Prefissi i componenti stilizzati con "Styled" per differenziarli dai componenti "reali".
```tsx
// ❌ Male
// ❌ Bad
const Title = styled.div`
color: red;
`;
```
```tsx
// ✅ Bene
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
@@ -238,7 +238,7 @@ Evita di usare `px` o valori `rem` direttamente nei componenti stilizzati. I val
Evita di introdurre nuovi colori; usa invece la palette esistente nel tema. Nel caso in cui la palette non sia adeguata, procedi lasciando un commento affinché il team possa risolvere.
```tsx
// ❌ Male, specifica direttamente i valori di stile senza utilizzare il tema
// ❌ Bad, directly specifies style values without utilizing the theme
const StyledButton = styled.button`
color: #333333;
font-size: 1rem;
@@ -249,7 +249,7 @@ const StyledButton = styled.button`
```
```tsx
// ✅ Bene, sfrutta il tema
// ✅ Good, utilizes the theme
const StyledButton = styled.button`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
@@ -264,13 +264,13 @@ const StyledButton = styled.button`
Evita le importazioni di tipo. Per far rispettare questo standard, una regola di Oxlint verifica e segnala qualsiasi import di tipo. Questo aiuta a mantenere la coerenza e la leggibilità del codice TypeScript.
```tsx
// ❌ Male
// ❌ Bad
import { type Meta, type StoryObj } from '@storybook/react';
// ❌ Male
// ❌ Bad
import type { Meta, StoryObj } from '@storybook/react';
// ✅ Bene
// ✅ Good
import { Meta, StoryObj } from '@storybook/react';
```
@@ -59,9 +59,9 @@ Chiudi e riapri il tuo terminale per usare nvm. Poi esegui i seguenti comandi.
```bash
nvm install # installa la versione di node raccomandata
nvm install # installs recommended node version
nvm use # usa la versione di node raccomandata
nvm use # use recommended node version
corepack enable
```
@@ -135,20 +135,20 @@ Dovresti eseguire tutti i comandi nei passaggi successivi dalla radice del proge
(es., "john").
Per controllare e creare l'utente `postgres` se necessario, segui questi passaggi:
```bash
# Connetti a PostgreSQL
# Connect to PostgreSQL
psql postgres
o
or
psql -U $(whoami) -d postgres
```
Una volta nel prompt di psql (postgres=#), esegui:
```bash
# Elenca i ruoli di PostgreSQL esistenti
# List existing PostgreSQL roles
\du
```
Vedrai un output simile a:
```bash
Nome ruolo | Attributi | Membro di
Role name | Attributes | Member of
-----------+-------------+-----------
john | Superuser | {}
```
@@ -160,7 +160,7 @@ Dovresti eseguire tutti i comandi nei passaggi successivi dalla radice del proge
```
Questo crea un ruolo superuser chiamato `postgres` con accesso di login.
```bash
Nome ruolo | Attributi | Membro di
Role name | Attributes | Member of
-----------+-------------+-----------
postgres | Superuser | {}
john | Superuser | {}
@@ -31,7 +31,7 @@ bash <(curl -sL https://raw.githubusercontent.com/twentyhq/twenty/main/packages/
Per installare una versione o un ramo specifico:
```bash
VERSION=vx.y.z BRANCH=nome-ramo bash <(curl -sL https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/scripts/install.sh)
VERSION=vx.y.z BRANCH=branch-name bash <(curl -sL https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/scripts/install.sh)
```
* Sostituisci x.y.z con il numero di versione desiderato.
@@ -76,7 +76,7 @@ Segui questi passaggi per un setup manuale.
Aggiorna il valore `PG_DATABASE_PASSWORD` nel file .env con una password forte senza caratteri speciali.
```ini
PG_DATABASE_PASSWORD=mia_password_forte
PG_DATABASE_PASSWORD=my_strong_password
```
### Passo 2: Ottieni il File Docker Compose
@@ -135,7 +135,7 @@ Raccomandiamo fortemente di configurare Twenty dietro un proxy inverso con termi
Se accedi all'applicazione direttamente senza un proxy inverso:
```ini
SERVER_URL=http://tuo-dominio-o-ip:3000
SERVER_URL=http://your-domain-or-ip:3000
```
* **Con Proxy Inverso (Porte Standard):**
@@ -143,7 +143,7 @@ Raccomandiamo fortemente di configurare Twenty dietro un proxy inverso con termi
Se usi un proxy inverso come Nginx o Traefik e hai configurato SSL:
```ini
SERVER_URL=https://tuo-dominio-o-ip
SERVER_URL=https://your-domain-or-ip
```
* **Con Proxy Inverso (Porte Personalizzate):**
@@ -151,7 +151,7 @@ Raccomandiamo fortemente di configurare Twenty dietro un proxy inverso con termi
Se usi porte non standard:
```ini
SERVER_URL=https://tuo-dominio-o-ip:porta-personalizzata
SERVER_URL=https://your-domain-or-ip:custom-port
```
2. **Aggiorna il File `.env`**
@@ -159,7 +159,7 @@ Raccomandiamo fortemente di configurare Twenty dietro un proxy inverso con termi
Apri il tuo file `.env` e aggiorna il `SERVER_URL`:
```ini
SERVER_URL=http(s)://tuo-dominio-o-ip:tuaporta
SERVER_URL=http(s)://your-domain-or-ip:your-port
```
**Esempi:**
@@ -16,7 +16,7 @@ Twenty offre **due modalità di configurazione** per soddisfare diverse esigenze
## 1. Configurazione del Pannello di Amministrazione (Predefinito)
```bash
IS_CONFIG_VARIABLES_IN_DB_ENABLED=true # predefinito
IS_CONFIG_VARIABLES_IN_DB_ENABLED=true # default
```
**La maggior parte della configurazione avviene tramite l'interfaccia utente** dopo l'installazione:
@@ -254,8 +254,8 @@ Dopo aver configurato le integrazioni di Gmail, Google Calendar o Microsoft 365,
Registrare i seguenti lavori ricorrenti nel tuo container worker:
```bash
# dal tuo container worker
yarn command:prod cron:messaging:messages-import
# from your worker container
yarn command:prod cron:messaging:messages-import
yarn command:prod cron:messaging:message-list-fetch
yarn command:prod cron:calendar:calendar-event-list-fetch
yarn command:prod cron:calendar:calendar-events-import
@@ -24,7 +24,7 @@ Procedi solo se si tratta di una nuova installazione senza dati importanti.
Per aggiornare il `PG_DATABASE_PASSWORD` devi:
```sh
# Aggiorna il PG_DATABASE_PASSWORD in .env
# Update the PG_DATABASE_PASSWORD in .env
docker compose down --volumes
docker compose up -d
```
@@ -19,12 +19,12 @@ export const MyComponent = () => {
return (
<>
<p id="hoverText" style={{ display: "inline-block" }}>
Intuizioni del cliente
Customer Insights
</p>
<AppTooltip
className
anchorSelect="#hoverText"
content="Esplora il comportamento e le preferenze dei clienti"
content="Explore customer behavior and preferences"
delayHide={0}
offset={6}
noArrow={false}
@@ -72,7 +72,7 @@ import { OverflowingTextWithTooltip } from 'twenty-ui/display';
export const MyComponent = () => {
const crmTaskDescription =
'Contatta nuovamente il cliente in merito alla sua recente richiesta di informazioni sul prodotto. Discuti le opzioni di prezzo, rispondi a eventuali dubbi e fornisci ulteriori informazioni sul prodotto. Registra i dettagli della conversazione nel CRM per riferimenti futuri.';
'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} />;
};
@@ -19,7 +19,7 @@ export const MyComponent = () => {
return (
<Chip
size="large"
label="Chip cliccabile"
label="Clickable Chip"
clickable={true}
variant="highlighted"
accent="text-primary"
@@ -30,7 +30,7 @@ export const MyComponent = () => {
/>
);
};
},{
```
</Tab>
@@ -64,7 +64,7 @@ export const MyComponent = () => {
return (
<Chip
size="large"
label="Chip trasparente disabilitato"
label="Transparent Disabled Chip"
clickable={false}
variant="rounded"
accent="text-secondary"
@@ -89,7 +89,7 @@ export const MyComponent = () => {
return (
<Chip
size="large"
label="Chip disabilitato che attiva un tooltip quando va in overflow."
label="Disabled chip that triggers a tooltip when overflowing."
clickable={false}
variant="regular"
accent="text-primary"
@@ -121,7 +121,7 @@ export const MyComponent = () => {
<Chip
linkToEntity="/entity-link"
entityId="entityTest"
name="Nome dell'entità"
name="Entity name"
pictureUrl=""
avatarType="rounded"
variant="regular"
@@ -17,7 +17,7 @@ export const MyComponent = () => {
<Tag
className
color="red"
text="Urgente"
text="Urgent"
onClick={() => console.log("click")}
/>
);
File diff suppressed because it is too large Load Diff
@@ -22,10 +22,10 @@ export const MyComponent = () => {
<Select
className
disabled={false}
label="Seleziona un'opzione"
label="Select an option"
options={[
{ value: 'option1', label: 'Opzione A', Icon: IconTwentyStar },
{ value: 'option2', label: 'Opzione B', Icon: IconTwentyStar },
{ value: 'option1', label: 'Option A', Icon: IconTwentyStar },
{ value: 'option2', label: 'Option B', Icon: IconTwentyStar },
]}
value="option1"
/>
@@ -140,8 +140,8 @@ Puoi aggiornare i record esistenti E creare nuovi record nella stessa importazio
```csv
email,firstName,lastName,jobTitle
john@acme.com,John,Smith,Senior Manager ← Aggiorna esistente (l'email corrisponde)
newperson@acme.com,New,Person,Analyst ← Crea nuovo (l'email non corrisponde)
john@acme.com,John,Smith,Senior Manager ← Updates existing (email matches)
newperson@acme.com,New,Person,Analyst ← Creates new (email doesn't match)
```
## Errori comuni da evitare
@@ -42,14 +42,14 @@ Prima di poter inviare email dai flussi di lavoro:
Richiama i dati dai passaggi precedenti usando la sintassi `{{variable}}`:
```text
Ciao {{trigger.object.firstName}},
Hi {{trigger.object.firstName}},
Grazie per esserti messo in contatto con noi!
Thank you for connecting with us!
La tua azienda, {{trigger.object.company.name}}, è ora nel nostro sistema.
Your company, {{trigger.object.company.name}}, is now in our system.
Cordiali saluti,
Il Team
Best regards,
The Team
```
### Variabili disponibili dai trigger
@@ -65,13 +65,13 @@ Dopo che le diramazioni parallele hanno completato il loro lavoro, puoi riunirle
```
Trigger
├── Diramazione A: Aggiorna il record cliente
├── Branch A: Update Customer Record
└── Diramazione B: Crea ticket di assistenza
└── Branch B: Create Support Ticket
↘ ↙
Passaggio riunito: Invia email di conferma
Merged Step: Send Confirmation Email
```
L'email di conferma viene inviata solo dopo che sia l'aggiornamento del cliente sia la creazione del ticket sono stati completati.
@@ -82,19 +82,19 @@ Crea un flusso di lavoro che gestisca automaticamente tutte le attività post-vi
**Esempio di corpo dell'email**:
```
Ciao team di Customer Success,
Hi CS Team,
Abbiamo un nuovo cliente!
We have a new customer!
Azienda: {{trigger.object.company.name}}
Trattativa: {{trigger.object.name}}
Valore: {{trigger.object.amount}}
Rappresentante commerciale: {{trigger.object.owner.name}}
Data di chiusura: {{trigger.object.closedAt}}
Company: {{trigger.object.company.name}}
Deal: {{trigger.object.name}}
Value: {{trigger.object.amount}}
Sales Rep: {{trigger.object.owner.name}}
Close Date: {{trigger.object.closedAt}}
È stata creata automaticamente un'attività di onboarding.
An onboarding task has been created automatically.
Diamo loro un ottimo inizio!
Let's give them a great start!
```
### Passaggio 7: Conferma al rappresentante commerciale
@@ -45,11 +45,11 @@ description: Domande frequenti sui flussi di lavoro in Twenty.
**Soluzione temporanea**: Crea più diramazioni dal tuo passaggio, ciascuna iniziando con un'azione **Filtro**:
```
Passaggio 1
Step 1
├── Diramazione A: Filtro (condizione = true) → Azioni...
├── Branch A: Filter (condition = true) → Actions...
└── Diramazione B: Filtro (condizione = false) → Azioni...
└── Branch B: Filter (condition = false) → Actions...
```
Solo la diramazione in cui la condizione del filtro è soddisfatta eseguirà le azioni successive.

Some files were not shown because too many files have changed in this diff Show More