فاتورتك تصل: 48 دولار شهرياً مقابل Monday.com. ثلاثة مقاعد، خطة Pro. وفي كل مراجعة sprint، يتمتم شخص ما بنفس الجملة — "أكره هذا الشيء". ليس لأن البرنامج معطل. Monday.com مثير للإعجاب حقاً. إنه يفعل 200 شيء لا يحتاجها خط الأنابيب الخاص بك ويتعثر في الـ 4 سير عمل التي تشغلها فعلاً. لن تقوم الأعمدة بالفرز بالطريقة التي تتحرك بها عملية البيع. تحدث الأتمتة مرتين. طريقة العرض على الهاتف المحمول تجعل مسؤول الحساب الخاص بنا تريد رمي هاتفها. فقررنا ما يفعله أي متجر dev لديه أرصدة Supabase وعدد كبير جداً من الآراء: أعدنا بناءه. احدى عشرة يوماً، 1200 سطر من Astro، بدون أسف. هنا schema، متحكم السحب، والفاتورة التي جعلت مدير الشؤون المالية الخاص بنا يبتسم.

هذا ليس منشوراً "نحن أذكى من Monday.com". إنه منشور "كان لدينا احتياجات محددة جداً، وstack كنا نعرفه بالفعل، ونهاية أسبوع". إليك القصة الكاملة لبناء لوحة kanban CRM مخصصة باستخدام Astro و Supabase وجرعة صحية من الكراهية تجاه SaaS bloat.

جدول المحتويات

داخل CRM Kanban الخاص بنا: لماذا أعدنا بناء Monday.com في Astro + Supabase

لماذا توقف Monday.com عن العمل معنا

دعني أكون محدداً بشأن إحباطاتنا، لأن الشكاوى الغامضة حول أدوات SaaS غير مفيدة.

المشكلة 1: كان نموذج البيانات يحاربنا. يفكر Monday.com في "لوحات" و "عناصر" مع "أعمدة". تفكر وكالتنا في الصفقات والجهات الاتصال والمشاريع — ثلاث كيانات مميزة مع علاقات بينها. احتجنا إلى أن تشير الصفقة إلى جهة اتصال، والمشروع إلى صفقة. يمكن لـ Monday.com أن يفعل هذا نوعاً ما مع الأعمدة المرتبطة، لكنها محرجة. في كل مرة ينشئ شخص صفقة جديدة، كان يتعين عليه ربطها يدوياً بجهة الاتصال الصحيحة. نسي الناس. أصبحت البيانات فوضوية.

المشكلة 2: لم تتمكن طريقة العرض kanban من فعل ما أردناه. احتجنا إلى رؤية الصفقات مجمعة حسب المرحلة ولونية مرمزة حسب المصدر (إحالة، عضوي، outbound). تتيح طريقة عرض kanban في Monday.com لك تجميع حسب عمود حالة واحد. هذا كل شيء. لا يمكنك وضع طبقة مرئية ثانية دون اختراقها بأسماء.

المشكلة 3: السرعة. هذا أمر شخصي، لكن Monday.com شعر بأنه بطيء لما كنا نفعله. انقر على صفقة، انتظر تحميل اللوحة الجانبية، قم بالتمرير عبر الحقول التي لا نستخدمها، ابحث عن الملاحظة التي نحتاجها. كانت كل تفاعل لديه قدر كافٍ من الكمون للشعور به كاحتكاك.

المشكلة 4: توجه التكلفة. بـ 48 دولار شهرياً لثلاثة أشخاص، إنه ليس مكلفاً. لكننا كنا نراقب عضواً رابعاً في الفريق، وقفزات تسعير Monday.com إلى 60 دولار شهرياً للخطة Pro مع 5 مقاعد (لا يمكنك شراء 4). هذا 720 دولار سنوياً لأداة كنا نشتكي منها بنشاط.

نقطة التحول

كان المحفز الفعلي محرجاً بشكل عادي. أرسل لنا عميل محتمل بريداً إلكترونياً، ورد عليه عضوان في الفريق لأن أياً منهما لم يستطع معرفة من "طلب" الفرصة من Monday.com. لم تسطح نظام الإخطارات بوضوح، وحلنا hacky لإضافة نفسك إلى عمود "الأشخاص" لم يكن موثوقاً. عندها فتحت VS Code بدلاً من Monday.com.

ما الذي احتجنا إليه فعلاً

قبل كتابة أي كود، قضينا حوالي ساعة في سرد بالضبط ما كان يحتاجه CRM الخاص بنا. ليس ما سيكون جميلاً. ما كان ضروري فعلاً.

إليك القائمة:

  1. لوحة kanban مع أعمدة لمراحل الصفقات: Lead → Contacted → Proposal → Negotiation → Won → Lost
  2. بطاقات الصفقات تعرض: اسم الاتصال، قيمة الصفقة، علامة المصدر (مرمزة بالألوان)، عضو الفريق المعين، أيام في المرحلة الحالية
  3. السحب والإفلات بين الأعمدة مع الثبات الفوري
  4. عرض تفاصيل الصفقة مع ملاحظات (markdown)، معلومات الاتصال، وسجل نشاط بسيط
  5. مزامنة في الوقت الفعلي حتى يرى شخصان ينظران إلى اللوحة نفس الحالة
  6. قاعدة بيانات الجهات الاتصال مع معلومات أساسية (الاسم، البريد الإلكتروني، الشركة، الملاحظات)
  7. مصادقة بسيطة — فقط فريقنا، بدون وصول عام

هذا كل شيء. لا توجد مخططات Gantt. لا تتبع الوقت. لا محرك أتمتة. لا 47 نوعاً مختلفاً من الأعمدة. فقط لوحة kanban مدعومة بقاعدة بيانات حقيقية مع علاقات حقيقية.

اختيار Stack: Astro + Supabase

نحن متجر تطوير Astro، لذا كان Astro هو الخيار الواضح. لكن يستحق الشرح لماذا يعقل فعلاً هنا، لأن سمعة Astro كـ "مولد موقع ثابت" تقلل من شأنها بشكل كبير.

منذ Astro 4.x (والآن 5.x في 2026)، يعد العرض من جانب الخادم مع المسارات حسب الطلب ميزة من الدرجة الأولى. يمكنك بناء تطبيقات ديناميكية كاملة. نستخدم وضع العرض الهجين في Astro: يتم عرض معظم الصفحات من جانب الخادم عند الطلب، لكن يمكننا الاستمرار في الجعل المسبق لأشياء مثل صفحة تسجيل الدخول.

للوحة kanban التفاعلية نفسها، نستخدم جزيرة React. هذه ميزة Astro القاتلة للتطبيقات مثل هذه — قشرة التطبيق (nav، layout، auth checks) يتم عرضها من جانب الخادم بدون JS، ولوحة kanban تحمل كجزيرة تفاعلية واحدة مع client:load.

كان Supabase هو اختيار قاعدة البيانات لعدة أسباب:

الميزة لماذا كان مهماً
Postgres تحت الغطاء قاعدة بيانات العلاقات الحقيقية، المفاتيح الخارجية الحقيقية، الاستعلامات الحقيقية
اشتراكات Realtime دعم WebSocket مدمج للتحديثات المباشرة
أمان مستوى الصف (RLS) قواعد المصادقة على مستوى قاعدة البيانات، ليس فقط مستوى التطبيق
مكتبة عميل JS واجهة برمجية نظيفة، دعم TypeScript جيد
الطبقة المجانية يناسب الاستخدام الخاص بنا بشكل مريح في خطة Supabase المجانية
خيار الاستضافة الذاتية إذا تجاوزنا الطبقة المجانية في أي وقت، يمكننا تشغيله بأنفسنا

لقد أخذنا في الاعتبار خيارات أخرى بإيجاز:

الخيار لماذا مررنا عليه
Firebase / Firestore NoSQL يجعل البيانات العلائقية محرجة. تعرضنا للحروق من قبل.
PlanetScale كبير، لكن بدون realtime مدمج. سيحتاج إلى حل WebSocket منفصل.
Neon + Prisma مجموعة صلبة، لكن Supabase يعطينا auth + realtime + DB في واحد.
البناء على Next.js نحن نعرف Next.js جيداً (نبنيها بانتظام)، لكن لأداة داخلية، معمارية جزيرة Astro تعني أقل JS من جانب العميل للأجزاء غير التفاعلية.

داخل CRM Kanban الخاص بنا: لماذا أعدنا بناء Monday.com في Astro + Supabase - architecture

تصميم قاعدة البيانات: الحفاظ على البساطة

لديها المخطط أربع جداول. هذا كل شيء.

-- جهات الاتصال: الأشخاص والشركات التي نتحدث معها
create table contacts (
  id uuid default gen_random_uuid() primary key,
  name text not null,
  email text,
  company text,
  phone text,
  notes text,
  created_at timestamptz default now()
);

-- الصفقات: عناصر خط أنابيب لوحة kanban الخاصة بنا
create table deals (
  id uuid default gen_random_uuid() primary key,
  contact_id uuid references contacts(id) on delete set null,
  title text not null,
  value integer, -- مخزنة بالسنتات
  stage text not null default 'lead'
    check (stage in ('lead', 'contacted', 'proposal', 'negotiation', 'won', 'lost')),
  source text
    check (source in ('referral', 'organic', 'outbound', 'repeat', 'other')),
  assigned_to uuid references auth.users(id),
  position integer not null default 0, -- للترتيب داخل العمود
  stage_entered_at timestamptz default now(),
  created_at timestamptz default now(),
  updated_at timestamptz default now()
);

-- سجل النشاط: سجل append-only بسيط
create table activities (
  id uuid default gen_random_uuid() primary key,
  deal_id uuid references deals(id) on delete cascade,
  user_id uuid references auth.users(id),
  action text not null, -- 'stage_change', 'note', 'created', إلخ.
  details jsonb,
  created_at timestamptz default now()
);

-- ملاحظات الصفقات: ملاحظات markdown مرفقة بالصفقات
create table deal_notes (
  id uuid default gen_random_uuid() primary key,
  deal_id uuid references deals(id) on delete cascade,
  user_id uuid references auth.users(id),
  content text not null,
  created_at timestamptz default now(),
  updated_at timestamptz default now()
);

حقل stage_entered_at على الصفقات هو أحد القرارات الصغيرة المفضلة لدي. في كل مرة تنتقل الصفقة إلى مرحلة جديدة، نقوم بتحديث طابع زمني هذا. يتيح لنا ذلك حساب "أيام في المرحلة الحالية" دون الاستعلام عن سجل النشاط. بسيط وسريع ومفيد.

يتعامل حقل position مع الترتيب ضمن أعمدة kanban. عندما تسحب بطاقة بين شخصين آخرين، نحسب قيمة موضع جديدة. نستخدم فاصل عدد صحيح (يزيد الموضع بمقدار 1000) حتى نادراً ما نحتاج إلى إعادة موازنة.

بناء لوحة Kanban

lوحة kanban هي مكون React مركب كجزيرة Astro. استخدمنا @dnd-kit/core للسحب والإفلات لأنها الأكثر سهولة في الوصول ومكتبة DnD المدعومة بشكل جيد في النظام البيئي React اعتباراً من 2026.

إليك الهيكل المبسط:

// src/components/KanbanBoard.tsx
import { DndContext, DragOverlay } from '@dnd-kit/core';
import { useState } from 'react';
import { KanbanColumn } from './KanbanColumn';
import { DealCard } from './DealCard';
import { useDeals } from '../hooks/useDeals';
import { useRealtimeDeals } from '../hooks/useRealtimeDeals';

const STAGES = ['lead', 'contacted', 'proposal', 'negotiation', 'won', 'lost'];

const SOURCE_COLORS: Record<string, string> = {
  referral: '#10b981',
  organic: '#3b82f6',
  outbound: '#f59e0b',
  repeat: '#8b5cf6',
  other: '#6b7280',
};

export function KanbanBoard() {
  const { deals, moveDeal } = useDeals();
  const [activeId, setActiveId] = useState<string | null>(null);

  // الاشتراك في التغييرات في الوقت الفعلي
  useRealtimeDeals();

  const handleDragEnd = async (event) => {
    const { active, over } = event;
    if (!over) return;

    const dealId = active.id;
    const newStage = over.data.current?.stage;
    const newPosition = over.data.current?.position;

    if (newStage) {
      await moveDeal(dealId, newStage, newPosition);
    }
    setActiveId(null);
  };

  return (
    <DndContext onDragStart={({ active }) => setActiveId(active.id)} onDragEnd={handleDragEnd}>
      <div className="kanban-grid">
        {STAGES.map((stage) => (
          <KanbanColumn
            key={stage}
            stage={stage}
            deals={deals.filter((d) => d.stage === stage)}
            sourceColors={SOURCE_COLORS}
          />
        ))}
      </div>
      <DragOverlay>
        {activeId ? <DealCard deal={deals.find((d) => d.id === activeId)} overlay /> : null}
      </DragOverlay>
    </DndContext>
  );
}

تقوم دالة moveDeal بتحديث متفائل — تحدث الحالة المحلية على الفور، ثم ترسل التحديث إلى Supabase. إذا فشل تحديث قاعدة البيانات، فإنها تتراجع. هذا يجعل اللوحة تشعر بأنها فورية.

const moveDeal = async (dealId: string, newStage: string, newPosition: number) => {
  // تحديث متفائل
  setDeals((prev) =>
    prev.map((d) =>
      d.id === dealId
        ? { ...d, stage: newStage, position: newPosition, stage_entered_at: new Date().toISOString() }
        : d
    )
  );

  const { error } = await supabase
    .from('deals')
    .update({
      stage: newStage,
      position: newPosition,
      stage_entered_at: new Date().toISOString(),
      updated_at: new Date().toISOString(),
    })
    .eq('id', dealId);

  if (error) {
    // التراجع — إعادة جلب من الخادم
    await refreshDeals();
    toast.error('Failed to move deal');
  }

  // تسجيل النشاط
  await supabase.from('activities').insert({
    deal_id: dealId,
    user_id: currentUser.id,
    action: 'stage_change',
    details: { from: previousStage, to: newStage },
  });
};

صفحة Astro التي تستضيف هذا أنيقة:

---
// src/pages/board.astro
import Layout from '../layouts/App.astro';
import { KanbanBoard } from '../components/KanbanBoard';
import { getSession } from '../lib/auth';

const session = await getSession(Astro.request);
if (!session) return Astro.redirect('/login');
---

<Layout title="Deal Board">
  <KanbanBoard client:load />
</Layout>

هذا التوجيه client:load يفعل الرفع الثقيل. تم عرض التخطيط والملاح والقشرة الصفحة كلها من جانب الخادم كـ HTML. لوحة kanban نفسها تتجمع على العميل. هذا يعني أن تحميل الصفحة الأول سريع — يحصل المتصفح على HTML على الفور، ولوحة kanban التفاعلية تبدأ على الفور بعد ذلك.

التحديثات في الوقت الفعلي باستخدام Supabase Realtime

هذه كانت الميزة التي جعلت Supabase الفائز الواضح لهذا المشروع. عندما ينقل أحد أعضاء الفريق الصفقة، يرى أعضاء الفريق الآخرون تحركها في الوقت الفعلي. لا حاجة للتحديث.

// src/hooks/useRealtimeDeals.ts
import { useEffect } from 'react';
import { supabase } from '../lib/supabase';
import { useDealsStore } from '../stores/deals';

export function useRealtimeDeals() {
  const { updateDealLocally, addDealLocally, removeDealLocally } = useDealsStore();

  useEffect(() => {
    const channel = supabase
      .channel('deals-changes')
      .on(
        'postgres_changes',
        { event: '*', schema: 'public', table: 'deals' },
        (payload) => {
          switch (payload.eventType) {
            case 'UPDATE':
              updateDealLocally(payload.new);
              break;
            case 'INSERT':
              addDealLocally(payload.new);
              break;
            case 'DELETE':
              removeDealLocally(payload.old.id);
              break;
          }
        }
      )
      .subscribe();

    return () => {
      supabase.removeChannel(channel);
    };
  }, []);
}

مشكلة واحدة: عندما تنقل الصفقة، تستعيد تغييراتك الخاصة عبر اشتراك realtime. إذا لم تكن حذراً، فهذا يسبب عيب مرئي حيث تقفز البطاقة. نتعامل مع هذا عن طريق وسم التحديثات المتفائلة بطابع زمني وتجاهل أحداث realtime التي تطابق التغييرات المحلية الحديثة. إنها بضعة أسطر إضافية من الكود لكنها تجعل تجربة المستخدم تشعر بأنها صلبة.

المصادقة وأمان مستوى الصف

بما أن هذه أداة داخلية، فإن المصادقة بسيطة. نستخدم Supabase Auth مع البريد الإلكتروني / كلمة المرور. ثلاث حسابات. لا توجد تدفق sign-up — أنشأنا الحسابات يدوياً في لوحة تحكم Supabase.

أمان مستوى الصف هو حيث يصبح مثيراً للاهتمام. على الرغم من أن هذه أداة داخلية، فإن RLS يعني أن قاعدة البيانات الخاصة بنا لن تسرب البيانات بالصدفة حتى لو أفسدنا كود التطبيق.

-- يمكن فقط للمستخدمين المصرح لهم رؤية الصفقات
alter table deals enable row level security;

create policy "Authenticated users can read all deals"
  on deals for select
  to authenticated
  using (true);

create policy "Authenticated users can insert deals"
  on deals for insert
  to authenticated
  with check (true);

create policy "Authenticated users can update deals"
  on deals for update
  to authenticated
  using (true);

create policy "Authenticated users can delete deals"
  on deals for delete
  to authenticated
  using (true);

نعم، هذه السياسات متساهلة — يمكن لأي مستخدم مصرح له أن يفعل أي شيء. بالنسبة لفريق مكون من ثلاثة أشخاص، هذا جيد. إذا نمونا يوماً ما إلى حجم حيث نحتاج إلى أذونات بناءً على الأدوار، فإن البنية الأساسية لـ RLS موجودة بالفعل. سنقوم فقط بتشديد السياسات.

النشر وتكاليف الاستضافة

هنا الجزء الممتع. دعنا نتحدث عن المال.

الخدمة الخطة التكلفة الشهرية
Supabase الطبقة المجانية 0 دولار
Vercel (استضافة Astro SSR) خطة Pro (كنا نملكها بالفعل) 0 دولار إضافي
النطاق نطاق فرعي من النطاق الموجود 0 دولار
الإجمالي 0 دولار/شهر

كنا بالفعل على خطة Vercel Pro لمشاريع العملاء، لذا فإن نشر تطبيق SSR إضافي لا يكلفنا شيئاً إضافياً. تعطيك طبقة Supabase المجانية 500 ميجابايت من تخزين قاعدة البيانات، و 50000 مستخدم نشط شهرياً (لدينا 3)، واتصالات realtime. نستخدم ربما 1٪ من سعة الطبقة المجانية.

قارن ذلك بـ Monday.com:

Monday.com CRM المخصص الخاص بنا
التكلفة الشهرية 48 دولار (3 مقاعد، Pro) 0 دولار
التكلفة السنوية 576 دولار 0 دولار
وقت البناء 0 ساعة حوالي 20 ساعة
الصيانة 0 ساعة/شهر حوالي 1 ساعة/شهر

بسعرنا الداخلي كل ساعة، تبلغ قيمة 20 ساعة من وقت dev أكثر بكثير من 576 دولار سنوياً. لكن هذا الحساب يفتقد النقطة. بنينا هذا جزئياً لأننا أردنا، جزئياً لأنها أداة أفضل لسير العمل المحدد الخاص بنا، وجزئياً لأن تلك الساعات العشرين من dev علمتنا أشياء استخدمناها منذ ذلك الحين في مشاريع العملاء. لقد طبقنا منذ ذلك الحين معماريات Astro + Supabase مماثلة للتطبيقات المدعومة بـ headless CMS التي نبنيها لعملاء.

ما الذي كنا سنفعله بشكل مختلف

منذ حوالي أربعة أشهر قمنا بشحن v1. إليك ما كنت سأغيره:

استخدم Zustand منذ اليوم الأول

بدأنا مع useState و useContext المدمج في React لإدارة الحالة. بحلول الوقت الذي أضفنا فيه مزامنة realtime وتحديثات متفائلة ومنطق التراجع، كان كود إدارة الحالة متشابكاً. انتقلنا إلى Zustand بعد أسبوعين. كان يجب أن نبدأ هناك.

أضف البحث مبكراً

لم نبني البحث حتى الأسبوع الثالث، وتلك الأسابيع الثلاثة من المسح يدوياً عبر الأعمدة بحثاً عن صفقة محددة كانت محبطة. كان استعلام ilike بسيط على Supabase سيأخذ 30 دقيقة لتنفيذه.

اختصارات لوحة المفاتيح

لا تزال لم تضفها، لكننا نريدها. اضغط على N لإنشاء صفقة جديدة، / للبحث، 1-6 للتصفية حسب المرحلة. أشياء صغيرة تضيف عندما تكون في الأداة عدة مرات يومياً.

عرض أفضل على الهاتف المحمول

تعمل لوحة kanban على الهاتف المحمول، من الناحية الفنية. لكن ستة أعمدة لا تناسب شاشة الهاتف. نحتاج إلى عرض قائمة للجوال. لم نحدد أولويات له لأننا نادراً ما نتحقق من CRM على هواتفنا، لكن سيكون من الجيد أن يكون لديه.

الأسئلة الشائعة

كم من الوقت استغرق بناء لوحة kanban CRM؟ أخذت النسخة القابلة للاستخدام الأولى حوالي 20 ساعة موزعة على نهاية أسبوع وبضع ليال. حصلنا على لوحة kanban وتفاصيل الصفقات والسحب والإفلات والمصادقة الأساسية. ربما أمضينا ساعات أخرى 10 منذ ذلك الحين على تحسينات مثل البحث وتصميم أفضل للجوال وإصلاحات الأخطاء.

لماذا Astro بدلاً من Next.js لتطبيق ديناميكي؟ معمارية جزيرة Astro تعني أن الأجزاء غير التفاعلية من التطبيق الخاص بنا (layout، nav، صفحات ثابتة) تشحن بدون JavaScript. لوحة kanban نفسها هي جزيرة React تتجمع عند التحميل. بالنسبة لأداة داخلية حيث سطح التفاعل مركز على مكون واحد، هذا مناسب كبير. نستخدم Next.js لمشاريع العملاء حيث التفاعل أكثر توزيعاً عبر الصفحات.

هل طبقة Supabase المجانية حقاً كافية لـ CRM؟ لفريق صغير، بالتأكيد. لدينا ربما 200 صفقة، 150 جهة اتصال، وبضعة آلاف من إدخالات سجل النشاط. هذا كيلوبايت من البيانات. تعطيك طبقة Supabase المجانية 500 ميجابايت من التخزين، الذي لن نصل إليه لسنوات. غطاء اتصالات realtime كريم أيضاً — تحصل على ما يصل إلى 200 اتصال متزامن في الخطة المجانية.

ماذا عن النسخ الاحتياطية؟ تشمل Supabase نسخ احتياطية يومية في خطة Pro (25 دولار / شهر)، لكننا على الطبقة المجانية. نقوم بتشغيل pg_dump أسبوعي عبر وظيفة cron على VPS بسعر 5 دولار أشهر كنا نملكها بالفعل. إنها ليست براقة، لكنها تعمل. لدينا أيضاً مشروع مستنسخ Supabase يمكننا استعادة إليه إذا حدث أي شيء خاطئ.

هل يمكن لهذا النهج العمل لفريق أكبر من 3 أشخاص؟ حتى ربما 10-15 شخصاً، أعتقد أن هذا سيعمل بشكل جيد مع سياسات RLS أكثر صرامة وبعض منطق واجهة المستخدم بناءً على الأدوار. بعد ذلك، ستبدأ في الرغبة في ميزات مثل الأتمتة وسير العمل المخصص والتقارير التي ستتطلب جهداً هندسياً جاداً. في هذه المرحلة، يكون أداة CRM مخصصة أكثر منطقية — فقط ربما ليس Monday.com.

كيف يعمل مزامنة realtime؟ يستخدم Supabase Realtime WebSockets تحت الغطاء، وللحالة الخاصة بنا (3 مستخدمين متزامنين، تحديثات متكررة منخفضة)، إنه فوري بشكل أساسي. قياس زمن الكمون من البداية إلى النهاية من مستخدم واحد يسحب بطاقة إلى رؤية مستخدم آخر التحديث: عادة 80-150ms. هذا أسرع مما يمكننا إدراكه.

هل نظرت في بدائل CRM مفتوحة المصدر مثل Twenty أو Folk؟ نظرنا إلى Twenty (CRM مفتوح المصدر الذي أطلق في 2024) وهو مثير للإعجاب. لكنها CRM كاملة مع ميزات أكثر بكثير مما احتجنا، واستضافة ذاتية تتطلب المزيد من البنية الأساسية. كان هدفنا بناء بالضبط ما احتجنا ولا شيء أكثر. إذا كان Twenty موجوداً عندما بدأنا وكان يحتوي على وضع kanban مركز أبسط، فربما كنا قد ذهبنا بهذا الطريق.

هل ستبني أدوات داخلية مخصصة للعملاء أيضاً؟ لقد فعلنا بالفعل. جاء عدة عملاء إلينا بعد تجاوز أدوات مثل Monday.com أو Notion أو Airtable لسير عمل محدد. نبني عادة هذه باستخدام Astro أو Next.js على الواجهة الأمامية و Supabase أو headless CMS على الخلف. إذا بدا ذلك وكأنه شيء تحتاجه، يجب أن نتحدث.