@hed-hog/contact 0.0.278 → 0.0.285

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 (70) hide show
  1. package/README.md +1 -4
  2. package/dist/person/dto/create-followup.dto.d.ts +5 -0
  3. package/dist/person/dto/create-followup.dto.d.ts.map +1 -0
  4. package/dist/person/dto/create-followup.dto.js +31 -0
  5. package/dist/person/dto/create-followup.dto.js.map +1 -0
  6. package/dist/person/dto/create-interaction.dto.d.ts +12 -0
  7. package/dist/person/dto/create-interaction.dto.d.ts.map +1 -0
  8. package/dist/person/dto/create-interaction.dto.js +39 -0
  9. package/dist/person/dto/create-interaction.dto.js.map +1 -0
  10. package/dist/person/dto/create.dto.d.ts +24 -0
  11. package/dist/person/dto/create.dto.d.ts.map +1 -1
  12. package/dist/person/dto/create.dto.js +56 -1
  13. package/dist/person/dto/create.dto.js.map +1 -1
  14. package/dist/person/dto/duplicates-query.dto.d.ts +8 -0
  15. package/dist/person/dto/duplicates-query.dto.d.ts.map +1 -0
  16. package/dist/person/dto/duplicates-query.dto.js +45 -0
  17. package/dist/person/dto/duplicates-query.dto.js.map +1 -0
  18. package/dist/person/dto/merge.dto.d.ts +6 -0
  19. package/dist/person/dto/merge.dto.d.ts.map +1 -0
  20. package/dist/person/dto/merge.dto.js +35 -0
  21. package/dist/person/dto/merge.dto.js.map +1 -0
  22. package/dist/person/dto/update-lifecycle-stage.dto.d.ts +13 -0
  23. package/dist/person/dto/update-lifecycle-stage.dto.d.ts.map +1 -0
  24. package/dist/person/dto/update-lifecycle-stage.dto.js +34 -0
  25. package/dist/person/dto/update-lifecycle-stage.dto.js.map +1 -0
  26. package/dist/person/dto/update.dto.d.ts +8 -1
  27. package/dist/person/dto/update.dto.d.ts.map +1 -1
  28. package/dist/person/dto/update.dto.js +36 -0
  29. package/dist/person/dto/update.dto.js.map +1 -1
  30. package/dist/person/person.controller.d.ts +57 -1
  31. package/dist/person/person.controller.d.ts.map +1 -1
  32. package/dist/person/person.controller.js +85 -3
  33. package/dist/person/person.controller.js.map +1 -1
  34. package/dist/person/person.service.d.ts +79 -0
  35. package/dist/person/person.service.d.ts.map +1 -1
  36. package/dist/person/person.service.js +730 -9
  37. package/dist/person/person.service.js.map +1 -1
  38. package/hedhog/data/route.yaml +18 -0
  39. package/hedhog/frontend/app/_components/crm-coming-soon.tsx.ejs +110 -110
  40. package/hedhog/frontend/app/_components/crm-nav.tsx.ejs +73 -73
  41. package/hedhog/frontend/app/_lib/crm-mocks.ts.ejs +498 -256
  42. package/hedhog/frontend/app/_lib/crm-sections.tsx.ejs +81 -81
  43. package/hedhog/frontend/app/accounts/_components/account-form-sheet.tsx.ejs +477 -0
  44. package/hedhog/frontend/app/accounts/_components/account-types.ts.ejs +62 -0
  45. package/hedhog/frontend/app/accounts/page.tsx.ejs +886 -15
  46. package/hedhog/frontend/app/activities/page.tsx.ejs +15 -15
  47. package/hedhog/frontend/app/contact-type/page.tsx.ejs +105 -91
  48. package/hedhog/frontend/app/dashboard/page.tsx.ejs +506 -573
  49. package/hedhog/frontend/app/document-type/page.tsx.ejs +105 -91
  50. package/hedhog/frontend/app/follow-ups/page.tsx.ejs +15 -15
  51. package/hedhog/frontend/app/page.tsx.ejs +5 -5
  52. package/hedhog/frontend/app/person/_components/person-form-sheet.tsx.ejs +1440 -1103
  53. package/hedhog/frontend/app/person/_components/person-interaction-dialog.tsx.ejs +4 -3
  54. package/hedhog/frontend/app/person/_components/person-types.ts.ejs +14 -0
  55. package/hedhog/frontend/app/person/page.tsx.ejs +108 -190
  56. package/hedhog/frontend/app/pipeline/_components/lead-detail-sheet.tsx.ejs +599 -0
  57. package/hedhog/frontend/app/pipeline/page.tsx.ejs +1074 -299
  58. package/hedhog/frontend/app/reports/page.tsx.ejs +15 -15
  59. package/hedhog/frontend/messages/en.json +107 -0
  60. package/hedhog/frontend/messages/pt.json +106 -0
  61. package/package.json +6 -6
  62. package/src/person/dto/create-followup.dto.ts +15 -0
  63. package/src/person/dto/create-interaction.dto.ts +23 -0
  64. package/src/person/dto/create.dto.ts +50 -0
  65. package/src/person/dto/duplicates-query.dto.ts +34 -0
  66. package/src/person/dto/merge.dto.ts +15 -0
  67. package/src/person/dto/update-lifecycle-stage.dto.ts +19 -0
  68. package/src/person/dto/update.dto.ts +31 -1
  69. package/src/person/person.controller.ts +63 -2
  70. package/src/person/person.service.ts +1096 -7
@@ -1,81 +1,81 @@
1
- import type { LucideIcon } from 'lucide-react';
2
- import {
3
- Activity,
4
- BarChart3,
5
- Building2,
6
- LayoutDashboard,
7
- Rows3,
8
- Users,
9
- } from 'lucide-react';
10
-
11
- export type CrmSection = {
12
- href: string;
13
- translationKey: string;
14
- icon: LucideIcon;
15
- implemented: boolean;
16
- colorClass: string;
17
- glowClass: string;
18
- };
19
-
20
- export const crmSections: CrmSection[] = [
21
- {
22
- href: '/contact/dashboard',
23
- translationKey: 'dashboard',
24
- icon: LayoutDashboard,
25
- implemented: true,
26
- colorClass: 'from-orange-500/20 via-amber-500/10 to-transparent',
27
- glowClass: 'bg-orange-500/10 text-orange-700',
28
- },
29
- {
30
- href: '/contact/pipeline',
31
- translationKey: 'pipeline',
32
- icon: Rows3,
33
- implemented: true,
34
- colorClass: 'from-sky-500/20 via-cyan-500/10 to-transparent',
35
- glowClass: 'bg-sky-500/10 text-sky-700',
36
- },
37
- {
38
- href: '/contact/person',
39
- translationKey: 'records',
40
- icon: Users,
41
- implemented: true,
42
- colorClass: 'from-emerald-500/20 via-green-500/10 to-transparent',
43
- glowClass: 'bg-emerald-500/10 text-emerald-700',
44
- },
45
- {
46
- href: '/contact/activities',
47
- translationKey: 'activities',
48
- icon: Activity,
49
- implemented: false,
50
- colorClass: 'from-violet-500/20 via-fuchsia-500/10 to-transparent',
51
- glowClass: 'bg-violet-500/10 text-violet-700',
52
- },
53
- {
54
- href: '/contact/follow-ups',
55
- translationKey: 'followups',
56
- icon: Activity,
57
- implemented: false,
58
- colorClass: 'from-pink-500/20 via-rose-500/10 to-transparent',
59
- glowClass: 'bg-pink-500/10 text-pink-700',
60
- },
61
- {
62
- href: '/contact/accounts',
63
- translationKey: 'accounts',
64
- icon: Building2,
65
- implemented: false,
66
- colorClass: 'from-lime-500/20 via-green-500/10 to-transparent',
67
- glowClass: 'bg-lime-500/10 text-lime-700',
68
- },
69
- {
70
- href: '/contact/reports',
71
- translationKey: 'reports',
72
- icon: BarChart3,
73
- implemented: false,
74
- colorClass: 'from-slate-500/20 via-zinc-500/10 to-transparent',
75
- glowClass: 'bg-slate-500/10 text-slate-700',
76
- },
77
- ];
78
-
79
- export const crmImplementedSections = crmSections.filter(
80
- (section) => section.implemented
81
- );
1
+ import type { LucideIcon } from 'lucide-react';
2
+ import {
3
+ Activity,
4
+ BarChart3,
5
+ Building2,
6
+ LayoutDashboard,
7
+ Rows3,
8
+ Users,
9
+ } from 'lucide-react';
10
+
11
+ export type CrmSection = {
12
+ href: string;
13
+ translationKey: string;
14
+ icon: LucideIcon;
15
+ implemented: boolean;
16
+ colorClass: string;
17
+ glowClass: string;
18
+ };
19
+
20
+ export const crmSections: CrmSection[] = [
21
+ {
22
+ href: '/contact/dashboard',
23
+ translationKey: 'dashboard',
24
+ icon: LayoutDashboard,
25
+ implemented: true,
26
+ colorClass: 'from-orange-500/20 via-amber-500/10 to-transparent',
27
+ glowClass: 'bg-orange-500/10 text-orange-700',
28
+ },
29
+ {
30
+ href: '/contact/pipeline',
31
+ translationKey: 'pipeline',
32
+ icon: Rows3,
33
+ implemented: true,
34
+ colorClass: 'from-sky-500/20 via-cyan-500/10 to-transparent',
35
+ glowClass: 'bg-sky-500/10 text-sky-700',
36
+ },
37
+ {
38
+ href: '/contact/person',
39
+ translationKey: 'records',
40
+ icon: Users,
41
+ implemented: true,
42
+ colorClass: 'from-emerald-500/20 via-green-500/10 to-transparent',
43
+ glowClass: 'bg-emerald-500/10 text-emerald-700',
44
+ },
45
+ {
46
+ href: '/contact/activities',
47
+ translationKey: 'activities',
48
+ icon: Activity,
49
+ implemented: false,
50
+ colorClass: 'from-violet-500/20 via-fuchsia-500/10 to-transparent',
51
+ glowClass: 'bg-violet-500/10 text-violet-700',
52
+ },
53
+ {
54
+ href: '/contact/follow-ups',
55
+ translationKey: 'followups',
56
+ icon: Activity,
57
+ implemented: false,
58
+ colorClass: 'from-pink-500/20 via-rose-500/10 to-transparent',
59
+ glowClass: 'bg-pink-500/10 text-pink-700',
60
+ },
61
+ {
62
+ href: '/contact/accounts',
63
+ translationKey: 'accounts',
64
+ icon: Building2,
65
+ implemented: true,
66
+ colorClass: 'from-lime-500/20 via-green-500/10 to-transparent',
67
+ glowClass: 'bg-lime-500/10 text-lime-700',
68
+ },
69
+ {
70
+ href: '/contact/reports',
71
+ translationKey: 'reports',
72
+ icon: BarChart3,
73
+ implemented: false,
74
+ colorClass: 'from-slate-500/20 via-zinc-500/10 to-transparent',
75
+ glowClass: 'bg-slate-500/10 text-slate-700',
76
+ },
77
+ ];
78
+
79
+ export const crmImplementedSections = crmSections.filter(
80
+ (section) => section.implemented
81
+ );
@@ -0,0 +1,477 @@
1
+ 'use client';
2
+
3
+ import { Button } from '@/components/ui/button';
4
+ import {
5
+ Form,
6
+ FormControl,
7
+ FormField,
8
+ FormItem,
9
+ FormLabel,
10
+ FormMessage,
11
+ } from '@/components/ui/form';
12
+ import { Input } from '@/components/ui/input';
13
+ import {
14
+ Select,
15
+ SelectContent,
16
+ SelectItem,
17
+ SelectTrigger,
18
+ SelectValue,
19
+ } from '@/components/ui/select';
20
+ import {
21
+ Sheet,
22
+ SheetContent,
23
+ SheetDescription,
24
+ SheetHeader,
25
+ SheetTitle,
26
+ } from '@/components/ui/sheet';
27
+ import { zodResolver } from '@hookform/resolvers/zod';
28
+ import { useEffect } from 'react';
29
+ import { useForm } from 'react-hook-form';
30
+ import { toast } from 'sonner';
31
+ import { z } from 'zod';
32
+ import type { Account, AccountFormValues, UserOption } from './account-types';
33
+
34
+ const accountFormSchema = z.object({
35
+ name: z.string().min(2, 'Name must be at least 2 characters'),
36
+ trade_name: z.string().optional().nullable(),
37
+ status: z.enum(['active', 'inactive']),
38
+ industry: z.string().optional().nullable(),
39
+ website: z.string().optional().nullable(),
40
+ email: z
41
+ .string()
42
+ .email('Invalid email')
43
+ .optional()
44
+ .or(z.literal(''))
45
+ .nullable(),
46
+ phone: z.string().optional().nullable(),
47
+ owner_user_id: z.number().optional().nullable(),
48
+ annual_revenue: z.number().optional().nullable(),
49
+ employee_count: z.number().optional().nullable(),
50
+ lifecycle_stage: z
51
+ .enum(['prospect', 'customer', 'churned', 'inactive'])
52
+ .optional()
53
+ .nullable(),
54
+ city: z.string().optional().nullable(),
55
+ state: z.string().optional().nullable(),
56
+ });
57
+
58
+ type AccountFormData = z.infer<typeof accountFormSchema>;
59
+
60
+ interface AccountFormSheetProps {
61
+ open: boolean;
62
+ onOpenChange: (open: boolean) => void;
63
+ account?: Account | null;
64
+ owners: UserOption[];
65
+ onSubmit: (data: AccountFormValues) => void;
66
+ isLoading?: boolean;
67
+ }
68
+
69
+ export function AccountFormSheet({
70
+ open,
71
+ onOpenChange,
72
+ account,
73
+ owners,
74
+ onSubmit,
75
+ isLoading = false,
76
+ }: AccountFormSheetProps) {
77
+ const form = useForm<AccountFormData>({
78
+ resolver: zodResolver(accountFormSchema),
79
+ defaultValues: {
80
+ name: '',
81
+ trade_name: null,
82
+ status: 'active',
83
+ industry: null,
84
+ website: null,
85
+ email: null,
86
+ phone: null,
87
+ owner_user_id: null,
88
+ annual_revenue: null,
89
+ employee_count: null,
90
+ lifecycle_stage: 'prospect' as const,
91
+ city: null,
92
+ state: null,
93
+ },
94
+ });
95
+
96
+ useEffect(() => {
97
+ if (account) {
98
+ form.reset({
99
+ name: account.name,
100
+ trade_name: account.trade_name ?? null,
101
+ status: account.status,
102
+ industry: account.industry ?? null,
103
+ website: account.website ?? null,
104
+ email: account.email ?? null,
105
+ phone: account.phone ?? null,
106
+ owner_user_id: account.owner_user_id ?? null,
107
+ annual_revenue: account.annual_revenue ?? null,
108
+ employee_count: account.employee_count ?? null,
109
+ lifecycle_stage: account.lifecycle_stage ?? 'prospect',
110
+ city: account.city ?? null,
111
+ state: account.state ?? null,
112
+ });
113
+ } else {
114
+ form.reset({
115
+ name: '',
116
+ trade_name: null,
117
+ status: 'active',
118
+ industry: null,
119
+ website: null,
120
+ email: null,
121
+ phone: null,
122
+ owner_user_id: null,
123
+ annual_revenue: null,
124
+ employee_count: null,
125
+ lifecycle_stage: 'prospect',
126
+ city: null,
127
+ state: null,
128
+ });
129
+ }
130
+ }, [account, form, open]);
131
+
132
+ const handleSubmit = (data: AccountFormData) => {
133
+ try {
134
+ onSubmit(data);
135
+ form.reset();
136
+ onOpenChange(false);
137
+ } catch (error) {
138
+ toast.error('Failed to save account');
139
+ }
140
+ };
141
+
142
+ return (
143
+ <Sheet open={open} onOpenChange={onOpenChange}>
144
+ <SheetContent className="max-h-screen overflow-y-auto">
145
+ <SheetHeader>
146
+ <SheetTitle>{account ? 'Edit Account' : 'New Account'}</SheetTitle>
147
+ <SheetDescription>
148
+ {account
149
+ ? 'Update account information and details'
150
+ : 'Create a new account record in the CRM'}
151
+ </SheetDescription>
152
+ </SheetHeader>
153
+
154
+ <Form {...form}>
155
+ <form
156
+ onSubmit={form.handleSubmit(handleSubmit)}
157
+ className="space-y-4 py-4"
158
+ >
159
+ {/* Name and Trade Name */}
160
+ <FormField
161
+ control={form.control}
162
+ name="name"
163
+ render={({ field }) => (
164
+ <FormItem>
165
+ <FormLabel>Company Name</FormLabel>
166
+ <FormControl>
167
+ <Input
168
+ placeholder="Company name"
169
+ {...field}
170
+ disabled={isLoading}
171
+ />
172
+ </FormControl>
173
+ <FormMessage />
174
+ </FormItem>
175
+ )}
176
+ />
177
+
178
+ <FormField
179
+ control={form.control}
180
+ name="trade_name"
181
+ render={({ field }) => (
182
+ <FormItem>
183
+ <FormLabel>Trade Name (optional)</FormLabel>
184
+ <FormControl>
185
+ <Input
186
+ placeholder="Trade name or brand"
187
+ value={field.value ?? ''}
188
+ onChange={(e) => field.onChange(e.target.value || null)}
189
+ disabled={isLoading}
190
+ />
191
+ </FormControl>
192
+ <FormMessage />
193
+ </FormItem>
194
+ )}
195
+ />
196
+
197
+ {/* Status and Lifecycle Stage */}
198
+ <div className="grid gap-4 grid-cols-2">
199
+ <FormField
200
+ control={form.control}
201
+ name="status"
202
+ render={({ field }) => (
203
+ <FormItem>
204
+ <FormLabel>Status</FormLabel>
205
+ <Select
206
+ value={field.value}
207
+ onValueChange={field.onChange}
208
+ disabled={isLoading}
209
+ >
210
+ <FormControl>
211
+ <SelectTrigger>
212
+ <SelectValue />
213
+ </SelectTrigger>
214
+ </FormControl>
215
+ <SelectContent>
216
+ <SelectItem value="active">Active</SelectItem>
217
+ <SelectItem value="inactive">Inactive</SelectItem>
218
+ </SelectContent>
219
+ </Select>
220
+ <FormMessage />
221
+ </FormItem>
222
+ )}
223
+ />
224
+
225
+ <FormField
226
+ control={form.control}
227
+ name="lifecycle_stage"
228
+ render={({ field }) => (
229
+ <FormItem>
230
+ <FormLabel>Stage</FormLabel>
231
+ <Select
232
+ value={field.value ?? 'prospect'}
233
+ onValueChange={(value) => field.onChange(value || null)}
234
+ disabled={isLoading}
235
+ >
236
+ <FormControl>
237
+ <SelectTrigger>
238
+ <SelectValue />
239
+ </SelectTrigger>
240
+ </FormControl>
241
+ <SelectContent>
242
+ <SelectItem value="prospect">Prospect</SelectItem>
243
+ <SelectItem value="customer">Customer</SelectItem>
244
+ <SelectItem value="churned">Churned</SelectItem>
245
+ <SelectItem value="inactive">Inactive</SelectItem>
246
+ </SelectContent>
247
+ </Select>
248
+ <FormMessage />
249
+ </FormItem>
250
+ )}
251
+ />
252
+ </div>
253
+
254
+ {/* Contact Information */}
255
+ <FormField
256
+ control={form.control}
257
+ name="email"
258
+ render={({ field }) => (
259
+ <FormItem>
260
+ <FormLabel>Email (optional)</FormLabel>
261
+ <FormControl>
262
+ <Input
263
+ type="email"
264
+ placeholder="contact@company.com"
265
+ value={field.value ?? ''}
266
+ onChange={(e) => field.onChange(e.target.value || null)}
267
+ disabled={isLoading}
268
+ />
269
+ </FormControl>
270
+ <FormMessage />
271
+ </FormItem>
272
+ )}
273
+ />
274
+
275
+ <FormField
276
+ control={form.control}
277
+ name="phone"
278
+ render={({ field }) => (
279
+ <FormItem>
280
+ <FormLabel>Phone (optional)</FormLabel>
281
+ <FormControl>
282
+ <Input
283
+ placeholder="(11) 99999-9999"
284
+ value={field.value ?? ''}
285
+ onChange={(e) => field.onChange(e.target.value || null)}
286
+ disabled={isLoading}
287
+ />
288
+ </FormControl>
289
+ <FormMessage />
290
+ </FormItem>
291
+ )}
292
+ />
293
+
294
+ <FormField
295
+ control={form.control}
296
+ name="website"
297
+ render={({ field }) => (
298
+ <FormItem>
299
+ <FormLabel>Website (optional)</FormLabel>
300
+ <FormControl>
301
+ <Input
302
+ placeholder="www.company.com"
303
+ value={field.value ?? ''}
304
+ onChange={(e) => field.onChange(e.target.value || null)}
305
+ disabled={isLoading}
306
+ />
307
+ </FormControl>
308
+ <FormMessage />
309
+ </FormItem>
310
+ )}
311
+ />
312
+
313
+ {/* Business Information */}
314
+ <FormField
315
+ control={form.control}
316
+ name="industry"
317
+ render={({ field }) => (
318
+ <FormItem>
319
+ <FormLabel>Industry (optional)</FormLabel>
320
+ <FormControl>
321
+ <Input
322
+ placeholder="e.g., Technology, Healthcare"
323
+ value={field.value ?? ''}
324
+ onChange={(e) => field.onChange(e.target.value || null)}
325
+ disabled={isLoading}
326
+ />
327
+ </FormControl>
328
+ <FormMessage />
329
+ </FormItem>
330
+ )}
331
+ />
332
+
333
+ <div className="grid gap-4 grid-cols-2">
334
+ <FormField
335
+ control={form.control}
336
+ name="annual_revenue"
337
+ render={({ field }) => (
338
+ <FormItem>
339
+ <FormLabel>Annual Revenue (optional)</FormLabel>
340
+ <FormControl>
341
+ <Input
342
+ type="number"
343
+ placeholder="0"
344
+ value={field.value ?? ''}
345
+ onChange={(e) =>
346
+ field.onChange(
347
+ e.target.value ? Number(e.target.value) : null
348
+ )
349
+ }
350
+ disabled={isLoading}
351
+ />
352
+ </FormControl>
353
+ <FormMessage />
354
+ </FormItem>
355
+ )}
356
+ />
357
+
358
+ <FormField
359
+ control={form.control}
360
+ name="employee_count"
361
+ render={({ field }) => (
362
+ <FormItem>
363
+ <FormLabel>Employees (optional)</FormLabel>
364
+ <FormControl>
365
+ <Input
366
+ type="number"
367
+ placeholder="0"
368
+ value={field.value ?? ''}
369
+ onChange={(e) =>
370
+ field.onChange(
371
+ e.target.value ? Number(e.target.value) : null
372
+ )
373
+ }
374
+ disabled={isLoading}
375
+ />
376
+ </FormControl>
377
+ <FormMessage />
378
+ </FormItem>
379
+ )}
380
+ />
381
+ </div>
382
+
383
+ {/* Address Information */}
384
+ <div className="grid gap-4 grid-cols-2">
385
+ <FormField
386
+ control={form.control}
387
+ name="city"
388
+ render={({ field }) => (
389
+ <FormItem>
390
+ <FormLabel>City (optional)</FormLabel>
391
+ <FormControl>
392
+ <Input
393
+ placeholder="São Paulo"
394
+ value={field.value ?? ''}
395
+ onChange={(e) => field.onChange(e.target.value || null)}
396
+ disabled={isLoading}
397
+ />
398
+ </FormControl>
399
+ <FormMessage />
400
+ </FormItem>
401
+ )}
402
+ />
403
+
404
+ <FormField
405
+ control={form.control}
406
+ name="state"
407
+ render={({ field }) => (
408
+ <FormItem>
409
+ <FormLabel>State (optional)</FormLabel>
410
+ <FormControl>
411
+ <Input
412
+ placeholder="SP"
413
+ maxLength={2}
414
+ value={field.value ?? ''}
415
+ onChange={(e) => field.onChange(e.target.value || null)}
416
+ disabled={isLoading}
417
+ />
418
+ </FormControl>
419
+ <FormMessage />
420
+ </FormItem>
421
+ )}
422
+ />
423
+ </div>
424
+
425
+ {/* Owner */}
426
+ <FormField
427
+ control={form.control}
428
+ name="owner_user_id"
429
+ render={({ field }) => (
430
+ <FormItem>
431
+ <FormLabel>Owner (optional)</FormLabel>
432
+ <Select
433
+ value={field.value ? String(field.value) : ''}
434
+ onValueChange={(value) =>
435
+ field.onChange(value ? Number(value) : null)
436
+ }
437
+ disabled={isLoading}
438
+ >
439
+ <FormControl>
440
+ <SelectTrigger>
441
+ <SelectValue placeholder="Unassigned" />
442
+ </SelectTrigger>
443
+ </FormControl>
444
+ <SelectContent>
445
+ <SelectItem value="">Unassigned</SelectItem>
446
+ {owners.map((owner) => (
447
+ <SelectItem key={owner.id} value={String(owner.id)}>
448
+ {owner.name}
449
+ </SelectItem>
450
+ ))}
451
+ </SelectContent>
452
+ </Select>
453
+ <FormMessage />
454
+ </FormItem>
455
+ )}
456
+ />
457
+
458
+ {/* Submit Button */}
459
+ <div className="flex justify-end gap-2 pt-4 border-t">
460
+ <Button
461
+ type="button"
462
+ variant="outline"
463
+ onClick={() => onOpenChange(false)}
464
+ disabled={isLoading}
465
+ >
466
+ Cancel
467
+ </Button>
468
+ <Button type="submit" disabled={isLoading}>
469
+ {isLoading ? 'Saving...' : 'Save Account'}
470
+ </Button>
471
+ </div>
472
+ </form>
473
+ </Form>
474
+ </SheetContent>
475
+ </Sheet>
476
+ );
477
+ }
@@ -0,0 +1,62 @@
1
+ 'use client';
2
+
3
+ export type AccountLifecycleStage =
4
+ | 'prospect'
5
+ | 'customer'
6
+ | 'churned'
7
+ | 'inactive';
8
+
9
+ export type UserOption = {
10
+ id: number;
11
+ name: string;
12
+ };
13
+
14
+ export type AccountStats = {
15
+ total: number;
16
+ active: number;
17
+ customers: number;
18
+ prospects: number;
19
+ };
20
+
21
+ export type Account = {
22
+ id: number;
23
+ name: string;
24
+ trade_name?: string | null;
25
+ status: 'active' | 'inactive';
26
+ industry?: string | null;
27
+ website?: string | null;
28
+ email?: string | null;
29
+ phone?: string | null;
30
+ owner_user_id?: number | null;
31
+ owner_user?: UserOption | null;
32
+ annual_revenue?: number | null;
33
+ employee_count?: number | null;
34
+ lifecycle_stage?: AccountLifecycleStage | null;
35
+ city?: string | null;
36
+ state?: string | null;
37
+ created_at: string;
38
+ last_interaction_at?: string | null;
39
+ };
40
+
41
+ export type PaginatedResult<T> = {
42
+ data: T[];
43
+ total: number;
44
+ page: number;
45
+ pageSize: number;
46
+ };
47
+
48
+ export type AccountFormValues = {
49
+ name: string;
50
+ trade_name?: string | null;
51
+ status: 'active' | 'inactive';
52
+ industry?: string | null;
53
+ website?: string | null;
54
+ email?: string | null;
55
+ phone?: string | null;
56
+ owner_user_id?: number | null;
57
+ annual_revenue?: number | null;
58
+ employee_count?: number | null;
59
+ lifecycle_stage?: AccountLifecycleStage | null;
60
+ city?: string | null;
61
+ state?: string | null;
62
+ };