realtimex-crm 0.13.8 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/assets/DealList-CRBXwi1I.js +59 -0
  2. package/dist/assets/DealList-CRBXwi1I.js.map +1 -0
  3. package/dist/assets/index-5AmasdLr.css +1 -0
  4. package/dist/assets/index-D5uatqhL.js +166 -0
  5. package/dist/assets/{index-BrW7DPxi.js.map → index-D5uatqhL.js.map} +1 -1
  6. package/dist/index.html +1 -1
  7. package/dist/stats.html +1 -1
  8. package/package.json +1 -1
  9. package/src/components/atomic-crm/deals/DealCreate.tsx +5 -1
  10. package/src/components/atomic-crm/deals/DealShow.tsx +5 -1
  11. package/src/components/atomic-crm/integrations/WebhooksTab.tsx +10 -0
  12. package/src/components/atomic-crm/layout/Header.tsx +6 -0
  13. package/src/components/atomic-crm/notes/NoteCreate.tsx +3 -2
  14. package/src/components/atomic-crm/notes/NotesIterator.tsx +1 -1
  15. package/src/components/atomic-crm/providers/fakerest/dataGenerator/index.ts +4 -0
  16. package/src/components/atomic-crm/providers/fakerest/dataGenerator/taskActivity.ts +62 -0
  17. package/src/components/atomic-crm/providers/fakerest/dataGenerator/taskNotes.ts +29 -0
  18. package/src/components/atomic-crm/providers/fakerest/dataGenerator/tasks.ts +33 -8
  19. package/src/components/atomic-crm/providers/fakerest/dataGenerator/types.ts +4 -0
  20. package/src/components/atomic-crm/providers/supabase/dataProvider.ts +37 -0
  21. package/src/components/atomic-crm/root/CRM.tsx +12 -1
  22. package/src/components/atomic-crm/root/ConfigurationContext.tsx +10 -0
  23. package/src/components/atomic-crm/root/defaultConfiguration.ts +15 -0
  24. package/src/components/atomic-crm/tasks/MyTasksInput.tsx +30 -0
  25. package/src/components/atomic-crm/tasks/Task.tsx +20 -9
  26. package/src/components/atomic-crm/tasks/TaskActivityTimeline.tsx +91 -0
  27. package/src/components/atomic-crm/tasks/TaskAside.tsx +122 -0
  28. package/src/components/atomic-crm/tasks/TaskCreate.tsx +112 -0
  29. package/src/components/atomic-crm/tasks/TaskEdit.tsx +20 -1
  30. package/src/components/atomic-crm/tasks/TaskList.tsx +52 -0
  31. package/src/components/atomic-crm/tasks/TaskListTable.tsx +60 -0
  32. package/src/components/atomic-crm/tasks/TaskPriorityBadge.tsx +20 -0
  33. package/src/components/atomic-crm/tasks/TaskShow.tsx +71 -0
  34. package/src/components/atomic-crm/tasks/TaskStatusBadge.tsx +21 -0
  35. package/src/components/atomic-crm/tasks/index.ts +9 -0
  36. package/src/components/atomic-crm/types.ts +50 -0
  37. package/src/components/ui/visually-hidden.tsx +10 -0
  38. package/supabase/migrations/20251225085142_enhance_companies.sql +3 -3
  39. package/supabase/migrations/{20251226120000_fix_ambiguous_column.sql → 20251225110000_fix_ambiguous_column.sql} +2 -0
  40. package/supabase/migrations/20251225120000_enhance_tasks_schema.sql +111 -0
  41. package/supabase/migrations/20251225120001_enhance_tasks_logic.sql +109 -0
  42. package/supabase/migrations/20251225120002_enhance_tasks_webhooks.sql +72 -0
  43. package/supabase/migrations/20251225150000_add_taskNotes_attachments.sql +6 -0
  44. package/dist/assets/DealList-CyjZCmZS.js +0 -59
  45. package/dist/assets/DealList-CyjZCmZS.js.map +0 -1
  46. package/dist/assets/index-BrW7DPxi.js +0 -166
  47. package/dist/assets/index-u4GyWWrL.css +0 -1
@@ -0,0 +1,111 @@
1
+ -- 1.1 Task Notes Table
2
+ CREATE TABLE IF NOT EXISTS public."taskNotes" (
3
+ id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
4
+ task_id bigint NOT NULL REFERENCES public.tasks(id) ON DELETE CASCADE,
5
+ text text NOT NULL,
6
+ date timestamp with time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
7
+ sales_id bigint NOT NULL REFERENCES public.sales(id),
8
+ status text DEFAULT 'cold'::text,
9
+ created_at timestamp with time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
10
+ updated_at timestamp with time zone DEFAULT timezone('utc'::text, now()) NOT NULL
11
+ );
12
+
13
+ CREATE INDEX IF NOT EXISTS idx_taskNotes_task_id ON public."taskNotes"(task_id);
14
+ CREATE INDEX IF NOT EXISTS idx_taskNotes_sales_id ON public."taskNotes"(sales_id);
15
+ CREATE INDEX IF NOT EXISTS idx_taskNotes_date ON public."taskNotes"(date DESC);
16
+ CREATE INDEX IF NOT EXISTS idx_taskNotes_task_id_date ON public."taskNotes"(task_id, date DESC);
17
+
18
+ ALTER TABLE public."taskNotes" ENABLE ROW LEVEL SECURITY;
19
+
20
+ CREATE POLICY "Enable read access for all authenticated users" ON public."taskNotes"
21
+ FOR SELECT USING (auth.role() = 'authenticated');
22
+
23
+ CREATE POLICY "Enable insert for authenticated users" ON public."taskNotes"
24
+ FOR INSERT WITH CHECK (auth.role() = 'authenticated');
25
+
26
+ CREATE POLICY "Enable update for authenticated users" ON public."taskNotes"
27
+ FOR UPDATE USING (auth.role() = 'authenticated');
28
+
29
+ CREATE POLICY "Enable delete for authenticated users" ON public."taskNotes"
30
+ FOR DELETE USING (auth.role() = 'authenticated');
31
+
32
+ -- Ensure update_updated_at_column function exists
33
+ CREATE OR REPLACE FUNCTION update_updated_at_column()
34
+ RETURNS TRIGGER AS $$
35
+ BEGIN
36
+ NEW.updated_at = timezone('utc'::text, now());
37
+ RETURN NEW;
38
+ END;
39
+ $$ language 'plpgsql';
40
+
41
+ CREATE TRIGGER update_taskNotes_updated_at
42
+ BEFORE UPDATE ON public."taskNotes"
43
+ FOR EACH ROW
44
+ EXECUTE FUNCTION update_updated_at_column();
45
+
46
+ -- 1.2 Enhanced Tasks Table
47
+ ALTER TABLE public.tasks
48
+ ADD COLUMN IF NOT EXISTS priority text DEFAULT 'medium',
49
+ ADD COLUMN IF NOT EXISTS assigned_to bigint REFERENCES public.sales(id),
50
+ ADD COLUMN IF NOT EXISTS status text DEFAULT 'todo',
51
+ ADD COLUMN IF NOT EXISTS created_at timestamp with time zone DEFAULT timezone('utc'::text, now()),
52
+ ADD COLUMN IF NOT EXISTS updated_at timestamp with time zone DEFAULT timezone('utc'::text, now());
53
+
54
+ COMMENT ON COLUMN public.tasks.priority IS 'Task priority: low, medium, high, urgent';
55
+ COMMENT ON COLUMN public.tasks.assigned_to IS 'Sales person assigned to task (nullable for unassigned tasks)';
56
+ COMMENT ON COLUMN public.tasks.status IS 'Task status: todo, in_progress, blocked, done, cancelled';
57
+
58
+ CREATE INDEX IF NOT EXISTS idx_tasks_assigned_to ON public.tasks(assigned_to);
59
+ CREATE INDEX IF NOT EXISTS idx_tasks_status ON public.tasks(status);
60
+ CREATE INDEX IF NOT EXISTS idx_tasks_priority ON public.tasks(priority);
61
+
62
+ CREATE TRIGGER update_tasks_updated_at
63
+ BEFORE UPDATE ON public.tasks
64
+ FOR EACH ROW
65
+ EXECUTE FUNCTION update_updated_at_column();
66
+
67
+ -- Update existing tasks
68
+ UPDATE public.tasks
69
+ SET status = CASE
70
+ WHEN done_date IS NOT NULL THEN 'done'
71
+ ELSE 'todo'
72
+ END
73
+ WHERE status IS NULL OR status = 'todo';
74
+
75
+ UPDATE public.tasks
76
+ SET assigned_to = sales_id
77
+ WHERE assigned_to IS NULL AND sales_id IS NOT NULL;
78
+
79
+ -- 1.4 Task Activity Log
80
+ CREATE TABLE IF NOT EXISTS public.task_activity (
81
+ id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
82
+ task_id bigint NOT NULL REFERENCES public.tasks(id) ON DELETE CASCADE,
83
+ sales_id bigint NOT NULL REFERENCES public.sales(id),
84
+ action text NOT NULL,
85
+ field_name text,
86
+ old_value text,
87
+ new_value text,
88
+ created_at timestamp with time zone DEFAULT timezone('utc'::text, now()) NOT NULL
89
+ );
90
+
91
+ CREATE INDEX IF NOT EXISTS idx_task_activity_task_id ON public.task_activity(task_id);
92
+ CREATE INDEX IF NOT EXISTS idx_task_activity_sales_id ON public.task_activity(sales_id);
93
+ CREATE INDEX IF NOT EXISTS idx_task_activity_created_at ON public.task_activity(created_at DESC);
94
+ CREATE INDEX IF NOT EXISTS idx_task_activity_task_created ON public.task_activity(task_id, created_at DESC);
95
+
96
+ COMMENT ON COLUMN public.task_activity.action IS 'Action performed: created, updated, assigned, completed, deleted, duplicated, archived';
97
+
98
+ ALTER TABLE public.task_activity ENABLE ROW LEVEL SECURITY;
99
+
100
+ CREATE POLICY "Enable read access for all authenticated users" ON public.task_activity
101
+ FOR SELECT USING (auth.role() = 'authenticated');
102
+
103
+ CREATE POLICY "Enable insert for authenticated users" ON public.task_activity
104
+ FOR INSERT WITH CHECK (auth.role() = 'authenticated');
105
+
106
+ -- 1.5 Task Archiving Columns
107
+ ALTER TABLE public.tasks
108
+ ADD COLUMN IF NOT EXISTS archived boolean DEFAULT false,
109
+ ADD COLUMN IF NOT EXISTS archived_at timestamp with time zone;
110
+
111
+ CREATE INDEX IF NOT EXISTS idx_tasks_archived ON public.tasks(archived, archived_at);
@@ -0,0 +1,109 @@
1
+ -- 1.3 Tasks Summary View
2
+ CREATE OR REPLACE VIEW public.tasks_summary AS
3
+ SELECT
4
+ t.id,
5
+ t.contact_id,
6
+ t.type,
7
+ t.text,
8
+ t.due_date,
9
+ t.done_date,
10
+ t.sales_id,
11
+ t.priority,
12
+ t.assigned_to,
13
+ t.status,
14
+ t.created_at,
15
+ t.updated_at,
16
+ t.archived,
17
+ t.archived_at,
18
+ -- Contact information
19
+ c.first_name as contact_first_name,
20
+ c.last_name as contact_last_name,
21
+ c.email_jsonb->0->>'email' as contact_email,
22
+ c.company_id,
23
+ comp.name as company_name,
24
+ -- Assigned sales person
25
+ s_assigned.first_name as assigned_first_name,
26
+ s_assigned.last_name as assigned_last_name,
27
+ -- Creator sales person
28
+ s_creator.first_name as creator_first_name,
29
+ s_creator.last_name as creator_last_name,
30
+ -- Task note count
31
+ count(DISTINCT tn.id) as nb_notes,
32
+ -- Most recent note
33
+ max(tn.date) as last_note_date
34
+ FROM public.tasks t
35
+ LEFT JOIN public.contacts c ON t.contact_id = c.id
36
+ LEFT JOIN public.companies comp ON c.company_id = comp.id
37
+ LEFT JOIN public.sales s_assigned ON t.assigned_to = s_assigned.id
38
+ LEFT JOIN public.sales s_creator ON t.sales_id = s_creator.id
39
+ LEFT JOIN public."taskNotes" tn ON t.id = tn.task_id
40
+ GROUP BY
41
+ t.id,
42
+ c.first_name, c.last_name, c.email_jsonb, c.company_id,
43
+ comp.name,
44
+ s_assigned.first_name, s_assigned.last_name,
45
+ s_creator.first_name, s_creator.last_name;
46
+
47
+ GRANT SELECT ON public.tasks_summary TO authenticated;
48
+
49
+ -- 1.5 Archiving Function
50
+ CREATE OR REPLACE FUNCTION archive_completed_tasks(days_threshold integer DEFAULT 30)
51
+ RETURNS integer AS $$
52
+ DECLARE
53
+ archived_count integer;
54
+ BEGIN
55
+ WITH archived AS (
56
+ UPDATE public.tasks
57
+ SET archived = true,
58
+ archived_at = NOW()
59
+ WHERE status = 'done'
60
+ AND done_date IS NOT NULL
61
+ AND done_date < NOW() - (days_threshold || ' days')::interval
62
+ AND archived = false
63
+ RETURNING id
64
+ )
65
+ SELECT COUNT(*) INTO archived_count FROM archived;
66
+
67
+ RETURN archived_count;
68
+ END;
69
+ $$ LANGUAGE plpgsql SECURITY DEFINER;
70
+
71
+ GRANT EXECUTE ON FUNCTION archive_completed_tasks TO authenticated;
72
+
73
+ -- 1.6 Real-Time
74
+ -- tasks table is already enabled for realtime
75
+ ALTER PUBLICATION supabase_realtime ADD TABLE public."taskNotes";
76
+ ALTER PUBLICATION supabase_realtime ADD TABLE public.task_activity;
77
+
78
+ -- Sync Logic for Status and Done Date (Section 7)
79
+ CREATE OR REPLACE FUNCTION sync_task_status_and_done_date()
80
+ RETURNS TRIGGER AS $$
81
+ BEGIN
82
+ -- If status changed to 'done', set done_date
83
+ IF NEW.status = 'done' AND (OLD.status IS DISTINCT FROM 'done') THEN
84
+ NEW.done_date = NOW();
85
+ END IF;
86
+
87
+ -- If status changed from 'done', clear done_date
88
+ IF NEW.status != 'done' AND OLD.status = 'done' THEN
89
+ NEW.done_date = NULL;
90
+ END IF;
91
+
92
+ -- If done_date set, update status to 'done'
93
+ IF NEW.done_date IS NOT NULL AND OLD.done_date IS NULL THEN
94
+ NEW.status = 'done';
95
+ END IF;
96
+
97
+ -- If done_date cleared, update status away from 'done'
98
+ IF NEW.done_date IS NULL AND OLD.done_date IS NOT NULL THEN
99
+ NEW.status = 'todo';
100
+ END IF;
101
+
102
+ RETURN NEW;
103
+ END;
104
+ $$ LANGUAGE plpgsql;
105
+
106
+ CREATE TRIGGER sync_task_status_trigger
107
+ BEFORE UPDATE ON public.tasks
108
+ FOR EACH ROW
109
+ EXECUTE FUNCTION sync_task_status_and_done_date();
@@ -0,0 +1,72 @@
1
+ -- 1.7 Extend Task Webhooks
2
+
3
+ CREATE OR REPLACE FUNCTION trigger_task_webhooks()
4
+ RETURNS TRIGGER AS $$
5
+ DECLARE
6
+ specific_event_fired boolean := false;
7
+ BEGIN
8
+ -- Task created
9
+ IF TG_OP = 'INSERT' THEN
10
+ PERFORM enqueue_webhook_event('task.created', to_jsonb(NEW));
11
+ RETURN NEW;
12
+ END IF;
13
+
14
+ -- Task updated
15
+ IF TG_OP = 'UPDATE' THEN
16
+ -- Task completed (existing logic - keep for backward compatibility)
17
+ IF OLD.done_date IS NULL AND NEW.done_date IS NOT NULL THEN
18
+ PERFORM enqueue_webhook_event('task.completed', to_jsonb(NEW));
19
+ specific_event_fired := true;
20
+ END IF;
21
+
22
+ -- Task assigned/reassigned
23
+ IF OLD.assigned_to IS DISTINCT FROM NEW.assigned_to THEN
24
+ PERFORM enqueue_webhook_event('task.assigned', jsonb_build_object(
25
+ 'task', to_jsonb(NEW),
26
+ 'old_assignee', OLD.assigned_to,
27
+ 'new_assignee', NEW.assigned_to
28
+ ));
29
+ specific_event_fired := true;
30
+ END IF;
31
+
32
+ -- Task archived
33
+ IF OLD.archived IS DISTINCT FROM NEW.archived AND NEW.archived = true THEN
34
+ PERFORM enqueue_webhook_event('task.archived', to_jsonb(NEW));
35
+ specific_event_fired := true;
36
+ END IF;
37
+
38
+ -- Task priority changed
39
+ IF OLD.priority IS DISTINCT FROM NEW.priority THEN
40
+ PERFORM enqueue_webhook_event('task.priority_changed', jsonb_build_object(
41
+ 'task', to_jsonb(NEW),
42
+ 'old_priority', OLD.priority,
43
+ 'new_priority', NEW.priority
44
+ ));
45
+ specific_event_fired := true;
46
+ END IF;
47
+
48
+ -- Generic task.updated (only fires if no specific event matched)
49
+ -- This reduces webhook noise while still catching other field changes
50
+ IF NOT specific_event_fired THEN
51
+ PERFORM enqueue_webhook_event('task.updated', jsonb_build_object(
52
+ 'old', to_jsonb(OLD),
53
+ 'new', to_jsonb(NEW)
54
+ ));
55
+ END IF;
56
+ END IF;
57
+
58
+ -- Task deleted
59
+ IF TG_OP = 'DELETE' THEN
60
+ PERFORM enqueue_webhook_event('task.deleted', to_jsonb(OLD));
61
+ RETURN OLD;
62
+ END IF;
63
+
64
+ RETURN NEW;
65
+ END;
66
+ $$ LANGUAGE plpgsql;
67
+
68
+ -- Trigger already exists in previous migrations, but we can recreate it to be safe or ensure it covers INSERT/DELETE
69
+ DROP TRIGGER IF EXISTS tasks_webhook_trigger ON public.tasks;
70
+ CREATE TRIGGER tasks_webhook_trigger
71
+ AFTER INSERT OR UPDATE OR DELETE ON public.tasks
72
+ FOR EACH ROW EXECUTE FUNCTION trigger_task_webhooks();
@@ -0,0 +1,6 @@
1
+ -- Add attachments column to taskNotes table to match contactNotes and dealNotes
2
+
3
+ ALTER TABLE public."taskNotes"
4
+ ADD COLUMN IF NOT EXISTS attachments jsonb[];
5
+
6
+ COMMENT ON COLUMN public."taskNotes".attachments IS 'Array of attachment metadata (file URLs, names, types)';