@hed-hog/contact 0.0.293 → 0.0.295

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 (46) hide show
  1. package/dist/person/dto/account.dto.d.ts +28 -0
  2. package/dist/person/dto/account.dto.d.ts.map +1 -0
  3. package/dist/person/dto/account.dto.js +123 -0
  4. package/dist/person/dto/account.dto.js.map +1 -0
  5. package/dist/person/dto/activity.dto.d.ts +15 -0
  6. package/dist/person/dto/activity.dto.d.ts.map +1 -0
  7. package/dist/person/dto/activity.dto.js +65 -0
  8. package/dist/person/dto/activity.dto.js.map +1 -0
  9. package/dist/person/dto/dashboard-query.dto.d.ts +9 -0
  10. package/dist/person/dto/dashboard-query.dto.d.ts.map +1 -0
  11. package/dist/person/dto/dashboard-query.dto.js +40 -0
  12. package/dist/person/dto/dashboard-query.dto.js.map +1 -0
  13. package/dist/person/dto/followup-query.dto.d.ts +10 -0
  14. package/dist/person/dto/followup-query.dto.d.ts.map +1 -0
  15. package/dist/person/dto/followup-query.dto.js +45 -0
  16. package/dist/person/dto/followup-query.dto.js.map +1 -0
  17. package/dist/person/person.controller.d.ts +204 -0
  18. package/dist/person/person.controller.d.ts.map +1 -1
  19. package/dist/person/person.controller.js +138 -0
  20. package/dist/person/person.controller.js.map +1 -1
  21. package/dist/person/person.service.d.ts +234 -0
  22. package/dist/person/person.service.d.ts.map +1 -1
  23. package/dist/person/person.service.js +1367 -0
  24. package/dist/person/person.service.js.map +1 -1
  25. package/hedhog/data/menu.yaml +163 -163
  26. package/hedhog/data/route.yaml +41 -0
  27. package/hedhog/frontend/app/accounts/_components/account-form-sheet.tsx.ejs +210 -114
  28. package/hedhog/frontend/app/accounts/_components/account-types.ts.ejs +3 -0
  29. package/hedhog/frontend/app/accounts/page.tsx.ejs +323 -245
  30. package/hedhog/frontend/app/activities/_components/activity-detail-sheet.tsx.ejs +240 -0
  31. package/hedhog/frontend/app/activities/_components/activity-types.ts.ejs +66 -0
  32. package/hedhog/frontend/app/activities/page.tsx.ejs +165 -517
  33. package/hedhog/frontend/app/dashboard/_components/dashboard-types.ts.ejs +70 -0
  34. package/hedhog/frontend/app/dashboard/page.tsx.ejs +504 -356
  35. package/hedhog/frontend/app/follow-ups/page.tsx.ejs +242 -153
  36. package/hedhog/frontend/messages/en.json +91 -6
  37. package/hedhog/frontend/messages/pt.json +91 -6
  38. package/hedhog/table/crm_activity.yaml +68 -0
  39. package/hedhog/table/person_company.yaml +22 -0
  40. package/package.json +5 -5
  41. package/src/person/dto/account.dto.ts +100 -0
  42. package/src/person/dto/activity.dto.ts +54 -0
  43. package/src/person/dto/dashboard-query.dto.ts +25 -0
  44. package/src/person/dto/followup-query.dto.ts +25 -0
  45. package/src/person/person.controller.ts +116 -0
  46. package/src/person/person.service.ts +2139 -77
@@ -0,0 +1,240 @@
1
+ 'use client';
2
+
3
+ import { Badge } from '@/components/ui/badge';
4
+ import { Button } from '@/components/ui/button';
5
+ import { Separator } from '@/components/ui/separator';
6
+ import {
7
+ Sheet,
8
+ SheetContent,
9
+ SheetDescription,
10
+ SheetFooter,
11
+ SheetHeader,
12
+ SheetTitle,
13
+ } from '@/components/ui/sheet';
14
+ import { Skeleton } from '@/components/ui/skeleton';
15
+ import { formatDateTime } from '@/lib/format-date';
16
+ import { cn } from '@/lib/utils';
17
+ import { useApp, useQuery } from '@hed-hog/next-app-provider';
18
+ import { CalendarClock, CheckCircle2, Loader2 } from 'lucide-react';
19
+ import { useTranslations } from 'next-intl';
20
+ import type { ReactNode } from 'react';
21
+ import type { ActivityDetail, ActivityPriority, ActivityStatus } from './activity-types';
22
+
23
+ function getStatusBadgeClass(status: ActivityStatus) {
24
+ if (status === 'completed') {
25
+ return 'border-emerald-500/25 bg-emerald-500/10 text-emerald-700';
26
+ }
27
+
28
+ if (status === 'overdue') {
29
+ return 'border-red-500/25 bg-red-500/10 text-red-700';
30
+ }
31
+
32
+ return 'border-amber-500/25 bg-amber-500/10 text-amber-700';
33
+ }
34
+
35
+ function getPriorityBadgeClass(priority: ActivityPriority) {
36
+ if (priority === 'high') {
37
+ return 'border-red-500/25 bg-red-500/10 text-red-700';
38
+ }
39
+
40
+ if (priority === 'medium') {
41
+ return 'border-sky-500/25 bg-sky-500/10 text-sky-700';
42
+ }
43
+
44
+ return 'border-slate-500/25 bg-slate-500/10 text-slate-700';
45
+ }
46
+
47
+ function DetailRow({
48
+ label,
49
+ value,
50
+ }: {
51
+ label: string;
52
+ value: ReactNode;
53
+ }) {
54
+ return (
55
+ <div className="grid gap-1 sm:grid-cols-[140px_1fr] sm:items-start sm:gap-3">
56
+ <span className="text-xs font-medium uppercase tracking-[0.12em] text-muted-foreground">
57
+ {label}
58
+ </span>
59
+ <div className="text-sm text-foreground">{value}</div>
60
+ </div>
61
+ );
62
+ }
63
+
64
+ export function ActivityDetailSheet({
65
+ activityId,
66
+ open,
67
+ refreshKey,
68
+ isCompleting,
69
+ onOpenChange,
70
+ onComplete,
71
+ }: {
72
+ activityId: number | null;
73
+ open: boolean;
74
+ refreshKey: number;
75
+ isCompleting: boolean;
76
+ onOpenChange: (open: boolean) => void;
77
+ onComplete: (activityId: number) => Promise<void>;
78
+ }) {
79
+ const t = useTranslations('contact.CrmActivities');
80
+ const { request, currentLocaleCode, getSettingValue } = useApp();
81
+
82
+ const { data: activity, isLoading, isFetching } = useQuery<ActivityDetail | null>({
83
+ queryKey: ['contact-activity-detail', activityId, refreshKey, currentLocaleCode],
84
+ enabled: open && !!activityId,
85
+ queryFn: async () => {
86
+ if (!activityId) {
87
+ return null;
88
+ }
89
+
90
+ const response = await request<ActivityDetail>({
91
+ url: `/person/activities/${activityId}`,
92
+ method: 'GET',
93
+ });
94
+
95
+ return response.data;
96
+ },
97
+ });
98
+
99
+ const ownerName = activity?.owner_user?.name || t('unassignedOwner');
100
+ const createdByName = activity?.created_by_user?.name || t('unassignedOwner');
101
+ const completedByName =
102
+ activity?.completed_by_user?.name || t('unassignedOwner');
103
+ const isCompleted = activity?.status === 'completed';
104
+
105
+ return (
106
+ <Sheet open={open} onOpenChange={onOpenChange}>
107
+ <SheetContent className="flex w-full flex-col sm:max-w-xl">
108
+ <SheetHeader className="text-left">
109
+ <SheetTitle>{t('detail.title')}</SheetTitle>
110
+ <SheetDescription>{t('detail.description')}</SheetDescription>
111
+ </SheetHeader>
112
+
113
+ <div className="flex-1 space-y-5 overflow-y-auto py-4">
114
+ {isLoading || isFetching ? (
115
+ <div className="space-y-3">
116
+ <Skeleton className="h-6 w-2/3" />
117
+ <Skeleton className="h-4 w-full" />
118
+ <Skeleton className="h-24 w-full" />
119
+ <Skeleton className="h-20 w-full" />
120
+ </div>
121
+ ) : !activity ? (
122
+ <div className="rounded-md border border-dashed border-slate-300 px-6 py-10 text-center">
123
+ <div className="flex flex-col items-center gap-3 text-muted-foreground">
124
+ <CalendarClock className="h-10 w-10" />
125
+ <div>
126
+ <h3 className="text-base font-semibold text-foreground">
127
+ {t('empty.title')}
128
+ </h3>
129
+ <p className="text-sm">{t('empty.description')}</p>
130
+ </div>
131
+ </div>
132
+ </div>
133
+ ) : (
134
+ <>
135
+ <div className="space-y-3">
136
+ <div className="flex flex-wrap items-start gap-2">
137
+ <h2 className="text-lg font-semibold leading-tight text-foreground">
138
+ {activity.subject}
139
+ </h2>
140
+ <Badge
141
+ variant="outline"
142
+ className={cn('border', getStatusBadgeClass(activity.status))}
143
+ >
144
+ {t(`status.${activity.status}`)}
145
+ </Badge>
146
+ <Badge
147
+ variant="outline"
148
+ className={cn(
149
+ 'border',
150
+ getPriorityBadgeClass(activity.priority)
151
+ )}
152
+ >
153
+ {t(`priority.${activity.priority}`)}
154
+ </Badge>
155
+ </div>
156
+
157
+ <p className="text-sm text-muted-foreground">
158
+ {activity.notes || t('detail.emptyNotes')}
159
+ </p>
160
+ </div>
161
+
162
+ <Separator />
163
+
164
+ <div className="space-y-4">
165
+ <DetailRow label={t('detail.person')} value={activity.person.name} />
166
+ <DetailRow
167
+ label={t('detail.personType')}
168
+ value={t(`detail.personTypeValue.${activity.person.type}`)}
169
+ />
170
+ {activity.person.trade_name ? (
171
+ <DetailRow
172
+ label={t('detail.tradeName')}
173
+ value={activity.person.trade_name}
174
+ />
175
+ ) : null}
176
+ <DetailRow label={t('detail.owner')} value={ownerName} />
177
+ <DetailRow label={t('detail.type')} value={t(`type.${activity.type}`)} />
178
+ <DetailRow
179
+ label={t('detail.source')}
180
+ value={t(`detail.sourceKind.${activity.source_kind}`)}
181
+ />
182
+ <DetailRow
183
+ label={t('detail.dueAt')}
184
+ value={formatDateTime(
185
+ activity.due_at,
186
+ getSettingValue,
187
+ currentLocaleCode
188
+ )}
189
+ />
190
+ <DetailRow
191
+ label={t('detail.createdAt')}
192
+ value={formatDateTime(
193
+ activity.created_at,
194
+ getSettingValue,
195
+ currentLocaleCode
196
+ )}
197
+ />
198
+ <DetailRow
199
+ label={t('detail.completedAt')}
200
+ value={
201
+ activity.completed_at
202
+ ? formatDateTime(
203
+ activity.completed_at,
204
+ getSettingValue,
205
+ currentLocaleCode
206
+ )
207
+ : t('detail.notCompleted')
208
+ }
209
+ />
210
+ <DetailRow label={t('detail.createdBy')} value={createdByName} />
211
+ <DetailRow
212
+ label={t('detail.completedBy')}
213
+ value={isCompleted ? completedByName : t('detail.notCompleted')}
214
+ />
215
+ </div>
216
+ </>
217
+ )}
218
+ </div>
219
+
220
+ <SheetFooter className="border-t pt-4">
221
+ <Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
222
+ {t('detail.close')}
223
+ </Button>
224
+ <Button
225
+ type="button"
226
+ onClick={() => activityId && onComplete(activityId)}
227
+ disabled={!activityId || isCompleted || isCompleting}
228
+ >
229
+ {isCompleting ? (
230
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
231
+ ) : (
232
+ <CheckCircle2 className="mr-2 h-4 w-4" />
233
+ )}
234
+ {t('actions.complete')}
235
+ </Button>
236
+ </SheetFooter>
237
+ </SheetContent>
238
+ </Sheet>
239
+ );
240
+ }
@@ -0,0 +1,66 @@
1
+ export type ActivityType =
2
+ | 'call'
3
+ | 'email'
4
+ | 'meeting'
5
+ | 'whatsapp'
6
+ | 'task'
7
+ | 'note';
8
+
9
+ export type ActivityPriority = 'high' | 'medium' | 'low';
10
+ export type ActivityStatus = 'pending' | 'overdue' | 'completed';
11
+ export type ActivitySourceKind = 'manual' | 'followup' | 'interaction';
12
+ export type ActivityViewMode = 'table' | 'timeline';
13
+
14
+ export type PaginatedResult<T> = {
15
+ data: T[];
16
+ total: number;
17
+ page: number;
18
+ pageSize: number;
19
+ lastPage?: number;
20
+ prev?: number | null;
21
+ next?: number | null;
22
+ };
23
+
24
+ export type ActivityUser = {
25
+ id: number;
26
+ name: string;
27
+ };
28
+
29
+ export type ActivityPerson = {
30
+ id: number;
31
+ name: string;
32
+ type: 'individual' | 'company';
33
+ status: 'active' | 'inactive';
34
+ trade_name?: string | null;
35
+ };
36
+
37
+ export type ActivityListItem = {
38
+ id: number;
39
+ person_id: number;
40
+ person: ActivityPerson;
41
+ owner_user_id: number | null;
42
+ owner_user?: ActivityUser | null;
43
+ type: ActivityType;
44
+ subject: string;
45
+ notes?: string | null;
46
+ due_at: string;
47
+ completed_at?: string | null;
48
+ created_at: string;
49
+ priority: ActivityPriority;
50
+ status: ActivityStatus;
51
+ };
52
+
53
+ export type ActivityDetail = ActivityListItem & {
54
+ source_kind: ActivitySourceKind;
55
+ created_by_user_id?: number | null;
56
+ created_by_user?: ActivityUser | null;
57
+ completed_by_user_id?: number | null;
58
+ completed_by_user?: ActivityUser | null;
59
+ };
60
+
61
+ export type ActivityStats = {
62
+ total: number;
63
+ pending: number;
64
+ overdue: number;
65
+ completed: number;
66
+ };