@hed-hog/core 0.0.304 → 0.0.305
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.
- package/README.md +84 -36
- package/dist/auth/auth.controller.d.ts +0 -3
- package/dist/auth/auth.controller.d.ts.map +1 -1
- package/dist/auth/auth.controller.js +4 -7
- package/dist/auth/auth.controller.js.map +1 -1
- package/dist/auth/auth.service.d.ts.map +1 -1
- package/dist/auth/auth.service.js +6 -3
- package/dist/auth/auth.service.js.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +9 -0
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.controller.js +12 -0
- package/dist/dashboard/dashboard-core/dashboard-core.controller.js.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +7 -0
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.js +45 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.js.map +1 -1
- package/dist/mail-sent/dto/query-mail-sent-list.dto.d.ts +9 -0
- package/dist/mail-sent/dto/query-mail-sent-list.dto.d.ts.map +1 -0
- package/dist/mail-sent/dto/query-mail-sent-list.dto.js +49 -0
- package/dist/mail-sent/dto/query-mail-sent-list.dto.js.map +1 -0
- package/dist/mail-sent/mail-sent.controller.d.ts +24 -2
- package/dist/mail-sent/mail-sent.controller.d.ts.map +1 -1
- package/dist/mail-sent/mail-sent.controller.js +5 -3
- package/dist/mail-sent/mail-sent.controller.js.map +1 -1
- package/dist/mail-sent/mail-sent.service.d.ts +28 -3
- package/dist/mail-sent/mail-sent.service.d.ts.map +1 -1
- package/dist/mail-sent/mail-sent.service.js +108 -3
- package/dist/mail-sent/mail-sent.service.js.map +1 -1
- package/dist/oauth/oauth-callback-coordinator.service.d.ts +18 -0
- package/dist/oauth/oauth-callback-coordinator.service.d.ts.map +1 -0
- package/dist/oauth/oauth-callback-coordinator.service.js +137 -0
- package/dist/oauth/oauth-callback-coordinator.service.js.map +1 -0
- package/dist/oauth/oauth.controller.d.ts +4 -2
- package/dist/oauth/oauth.controller.d.ts.map +1 -1
- package/dist/oauth/oauth.controller.js +39 -11
- package/dist/oauth/oauth.controller.js.map +1 -1
- package/dist/oauth/oauth.errors.d.ts +16 -0
- package/dist/oauth/oauth.errors.d.ts.map +1 -0
- package/dist/oauth/oauth.errors.js +30 -0
- package/dist/oauth/oauth.errors.js.map +1 -0
- package/dist/oauth/oauth.module.d.ts.map +1 -1
- package/dist/oauth/oauth.module.js +10 -1
- package/dist/oauth/oauth.module.js.map +1 -1
- package/dist/oauth/oauth.service.d.ts +9 -1
- package/dist/oauth/oauth.service.d.ts.map +1 -1
- package/dist/oauth/oauth.service.js +97 -24
- package/dist/oauth/oauth.service.js.map +1 -1
- package/dist/oauth/providers/abstract.provider.d.ts +6 -0
- package/dist/oauth/providers/abstract.provider.d.ts.map +1 -1
- package/dist/oauth/providers/abstract.provider.js +34 -0
- package/dist/oauth/providers/abstract.provider.js.map +1 -1
- package/dist/oauth/providers/microsoft-entra-id.provider.d.ts +3 -0
- package/dist/oauth/providers/microsoft-entra-id.provider.d.ts.map +1 -1
- package/dist/oauth/providers/microsoft-entra-id.provider.js +40 -22
- package/dist/oauth/providers/microsoft-entra-id.provider.js.map +1 -1
- package/dist/security/security.service.d.ts +1 -0
- package/dist/security/security.service.d.ts.map +1 -1
- package/dist/security/security.service.js +9 -10
- package/dist/security/security.service.js.map +1 -1
- package/dist/session/session.service.d.ts +5 -1
- package/dist/session/session.service.d.ts.map +1 -1
- package/dist/session/session.service.js +76 -10
- package/dist/session/session.service.js.map +1 -1
- package/dist/setting/setting.service.js +4 -4
- package/dist/setting/setting.service.js.map +1 -1
- package/dist/token/token.service.d.ts +1 -0
- package/dist/token/token.service.d.ts.map +1 -1
- package/dist/token/token.service.js +23 -12
- package/dist/token/token.service.js.map +1 -1
- package/dist/user/user.service.d.ts +1 -1
- package/dist/user/user.service.d.ts.map +1 -1
- package/dist/user/user.service.js +3 -0
- package/dist/user/user.service.js.map +1 -1
- package/hedhog/data/dashboard.yaml +2 -2
- package/hedhog/data/route.yaml +8 -0
- package/hedhog/frontend/app/account/components/change-email-form.tsx.ejs +87 -8
- package/hedhog/frontend/app/account/components/profile-form.tsx.ejs +90 -10
- package/hedhog/frontend/app/ai_agent/page.tsx.ejs +145 -31
- package/hedhog/frontend/app/configurations/layout.tsx.ejs +1 -1
- package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +67 -2
- package/hedhog/frontend/app/dashboard/components/add-widget-selector-dialog.tsx.ejs +13 -9
- package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +250 -45
- package/hedhog/frontend/app/dashboard/management/tabs/component-roles-tab.tsx.ejs +111 -15
- package/hedhog/frontend/app/dashboard/management/tabs/dashboard-roles-tab.tsx.ejs +110 -15
- package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +113 -15
- package/hedhog/frontend/app/mail/log/page.tsx.ejs +481 -70
- package/hedhog/frontend/app/menu/page.tsx.ejs +202 -30
- package/hedhog/frontend/app/roles/menus.tsx.ejs +1 -1
- package/hedhog/frontend/app/roles/page.tsx.ejs +288 -104
- package/hedhog/frontend/app/users/page.tsx.ejs +168 -23
- package/hedhog/frontend/messages/en.json +45 -6
- package/hedhog/frontend/messages/pt.json +45 -6
- package/package.json +5 -5
- package/src/auth/auth.controller.ts +15 -19
- package/src/auth/auth.service.ts +13 -8
- package/src/dashboard/dashboard-core/dashboard-core.controller.ts +22 -9
- package/src/dashboard/dashboard-core/dashboard-core.service.ts +75 -1
- package/src/mail-sent/dto/query-mail-sent-list.dto.ts +30 -0
- package/src/mail-sent/mail-sent.controller.ts +14 -2
- package/src/mail-sent/mail-sent.service.ts +159 -4
- package/src/oauth/oauth-callback-coordinator.service.ts +158 -0
- package/src/oauth/oauth.controller.ts +58 -1
- package/src/oauth/oauth.errors.ts +32 -0
- package/src/oauth/oauth.module.ts +10 -1
- package/src/oauth/oauth.service.ts +178 -28
- package/src/oauth/providers/abstract.provider.ts +64 -0
- package/src/oauth/providers/microsoft-entra-id.provider.ts +92 -56
- package/src/security/security.service.ts +12 -10
- package/src/session/session.service.ts +84 -9
- package/src/setting/setting.service.ts +10 -10
- package/src/token/token.service.ts +29 -11
- package/src/user/user.service.ts +10 -6
|
@@ -45,6 +45,8 @@ import {
|
|
|
45
45
|
TableRow,
|
|
46
46
|
} from '@/components/ui/table';
|
|
47
47
|
import { useDebounce } from '@/hooks/use-debounce';
|
|
48
|
+
import { useFormDraft } from '@/hooks/use-form-draft';
|
|
49
|
+
import { formatDateTime } from '@/lib/format-date';
|
|
48
50
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
49
51
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
50
52
|
import {
|
|
@@ -55,9 +57,11 @@ import {
|
|
|
55
57
|
IconPlus,
|
|
56
58
|
IconTrash,
|
|
57
59
|
} from '@tabler/icons-react';
|
|
60
|
+
import { formatDistanceToNow } from 'date-fns';
|
|
61
|
+
import { enUS, ptBR } from 'date-fns/locale';
|
|
58
62
|
import { useTranslations } from 'next-intl';
|
|
59
|
-
import { useState } from 'react';
|
|
60
|
-
import { useForm } from 'react-hook-form';
|
|
63
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
64
|
+
import { useForm, useWatch } from 'react-hook-form';
|
|
61
65
|
import { toast } from 'sonner';
|
|
62
66
|
import { z } from 'zod';
|
|
63
67
|
|
|
@@ -66,6 +70,13 @@ type RoleFormData = {
|
|
|
66
70
|
role_id: number;
|
|
67
71
|
};
|
|
68
72
|
|
|
73
|
+
type ComponentRoleDraftPayload = {
|
|
74
|
+
values: RoleFormData;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const COMPONENT_ROLE_DRAFT_STORAGE_KEY =
|
|
78
|
+
'core-dashboard-component-role-form-draft';
|
|
79
|
+
|
|
69
80
|
interface ComponentRole {
|
|
70
81
|
id: number;
|
|
71
82
|
component_id: number;
|
|
@@ -106,9 +117,25 @@ interface Role {
|
|
|
106
117
|
}>;
|
|
107
118
|
}
|
|
108
119
|
|
|
120
|
+
type ComponentListResponse = {
|
|
121
|
+
data?: Component[];
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
type RoleListResponse = {
|
|
125
|
+
data?: Role[];
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
type RequestError = {
|
|
129
|
+
response?: {
|
|
130
|
+
data?: {
|
|
131
|
+
message?: string;
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
|
|
109
136
|
export function ComponentRolesTab() {
|
|
110
137
|
const t = useTranslations('core.DashboardManagement');
|
|
111
|
-
const { request, currentLocaleCode } = useApp();
|
|
138
|
+
const { request, currentLocaleCode, getSettingValue } = useApp();
|
|
112
139
|
|
|
113
140
|
const roleSchema = z.object({
|
|
114
141
|
component_id: z.coerce.number().min(1, t('selectComponentRequired')),
|
|
@@ -120,7 +147,7 @@ export function ComponentRolesTab() {
|
|
|
120
147
|
const [roleToDelete, setRoleToDelete] = useState<number | null>(null);
|
|
121
148
|
const [page, setPage] = useState(1);
|
|
122
149
|
const [pageSize, setPageSize] = useState(10);
|
|
123
|
-
const
|
|
150
|
+
const searchQuery = '';
|
|
124
151
|
const debouncedSearch = useDebounce(searchQuery, 500);
|
|
125
152
|
|
|
126
153
|
const {
|
|
@@ -153,10 +180,10 @@ export function ComponentRolesTab() {
|
|
|
153
180
|
const total = paginatedData?.total ?? 0;
|
|
154
181
|
const totalPages = Math.ceil(total / pageSize);
|
|
155
182
|
|
|
156
|
-
const { data: components } = useQuery<
|
|
183
|
+
const { data: components } = useQuery<ComponentListResponse>({
|
|
157
184
|
queryKey: ['dashboard-components'],
|
|
158
185
|
queryFn: async () => {
|
|
159
|
-
const { data } = await request<
|
|
186
|
+
const { data } = await request<ComponentListResponse>({
|
|
160
187
|
url: '/dashboard-component',
|
|
161
188
|
method: 'GET',
|
|
162
189
|
});
|
|
@@ -164,10 +191,10 @@ export function ComponentRolesTab() {
|
|
|
164
191
|
},
|
|
165
192
|
});
|
|
166
193
|
|
|
167
|
-
const { data: availableRoles } = useQuery<
|
|
194
|
+
const { data: availableRoles } = useQuery<RoleListResponse>({
|
|
168
195
|
queryKey: ['roles'],
|
|
169
196
|
queryFn: async () => {
|
|
170
|
-
const { data } = await request<
|
|
197
|
+
const { data } = await request<RoleListResponse>({
|
|
171
198
|
url: '/role',
|
|
172
199
|
method: 'GET',
|
|
173
200
|
});
|
|
@@ -183,6 +210,67 @@ export function ComponentRolesTab() {
|
|
|
183
210
|
},
|
|
184
211
|
});
|
|
185
212
|
|
|
213
|
+
const watchedValues = useWatch({
|
|
214
|
+
control: form.control,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const {
|
|
218
|
+
clearDraft,
|
|
219
|
+
loadDraft,
|
|
220
|
+
hasDraft,
|
|
221
|
+
savedAt: draftSavedAt,
|
|
222
|
+
} = useFormDraft<ComponentRoleDraftPayload>({
|
|
223
|
+
storageKey: COMPONENT_ROLE_DRAFT_STORAGE_KEY,
|
|
224
|
+
value: {
|
|
225
|
+
values: {
|
|
226
|
+
component_id: Number(watchedValues.component_id ?? 0),
|
|
227
|
+
role_id: Number(watchedValues.role_id ?? 0),
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
hasData:
|
|
231
|
+
Number(watchedValues.component_id ?? 0) > 0 ||
|
|
232
|
+
Number(watchedValues.role_id ?? 0) > 0,
|
|
233
|
+
enabled: open,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const draftStatusContent = useMemo(() => {
|
|
237
|
+
if (!hasDraft || !draftSavedAt) {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const savedDate = new Date(draftSavedAt);
|
|
242
|
+
if (Number.isNaN(savedDate.getTime())) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const locale = currentLocaleCode.startsWith('pt') ? ptBR : enUS;
|
|
247
|
+
const relativeLabel = formatDistanceToNow(savedDate, {
|
|
248
|
+
addSuffix: true,
|
|
249
|
+
locale,
|
|
250
|
+
});
|
|
251
|
+
const absoluteLabel = formatDateTime(
|
|
252
|
+
savedDate,
|
|
253
|
+
getSettingValue,
|
|
254
|
+
currentLocaleCode
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
return currentLocaleCode.startsWith('pt')
|
|
258
|
+
? `Rascunho salvo ${relativeLabel} • Último salvamento: ${absoluteLabel}`
|
|
259
|
+
: `Draft saved ${relativeLabel} • Last saved: ${absoluteLabel}`;
|
|
260
|
+
}, [draftSavedAt, currentLocaleCode, getSettingValue, hasDraft]);
|
|
261
|
+
|
|
262
|
+
useEffect(() => {
|
|
263
|
+
if (!open) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const storedDraft = loadDraft();
|
|
268
|
+
form.reset({
|
|
269
|
+
component_id: storedDraft?.payload.values.component_id ?? 0,
|
|
270
|
+
role_id: storedDraft?.payload.values.role_id ?? 0,
|
|
271
|
+
});
|
|
272
|
+
}, [form, loadDraft, open]);
|
|
273
|
+
|
|
186
274
|
const onSubmit = async (values: RoleFormData) => {
|
|
187
275
|
try {
|
|
188
276
|
await request({
|
|
@@ -194,12 +282,14 @@ export function ComponentRolesTab() {
|
|
|
194
282
|
},
|
|
195
283
|
});
|
|
196
284
|
toast.success(t('roleAdded'));
|
|
285
|
+
clearDraft();
|
|
197
286
|
setOpen(false);
|
|
198
287
|
form.reset();
|
|
199
288
|
refetch();
|
|
200
|
-
} catch (error:
|
|
289
|
+
} catch (error: unknown) {
|
|
201
290
|
console.error('Erro ao adicionar permissão:', error);
|
|
202
|
-
|
|
291
|
+
const message = (error as RequestError)?.response?.data?.message ?? '';
|
|
292
|
+
if (message.includes('already exists')) {
|
|
203
293
|
toast.error(t('roleAlreadyExists'));
|
|
204
294
|
} else {
|
|
205
295
|
toast.error(t('errorAddingRole'));
|
|
@@ -273,8 +363,8 @@ export function ComponentRolesTab() {
|
|
|
273
363
|
<FormItem>
|
|
274
364
|
<FormLabel>{t('component')}</FormLabel>
|
|
275
365
|
<Select
|
|
276
|
-
|
|
277
|
-
|
|
366
|
+
value={field.value ? String(field.value) : undefined}
|
|
367
|
+
onValueChange={(value) => field.onChange(Number(value))}
|
|
278
368
|
>
|
|
279
369
|
<FormControl>
|
|
280
370
|
<SelectTrigger className="w-full">
|
|
@@ -311,8 +401,8 @@ export function ComponentRolesTab() {
|
|
|
311
401
|
<FormItem>
|
|
312
402
|
<FormLabel>{t('role')}</FormLabel>
|
|
313
403
|
<Select
|
|
314
|
-
|
|
315
|
-
|
|
404
|
+
value={field.value ? String(field.value) : undefined}
|
|
405
|
+
onValueChange={(value) => field.onChange(Number(value))}
|
|
316
406
|
>
|
|
317
407
|
<FormControl>
|
|
318
408
|
<SelectTrigger className="w-full">
|
|
@@ -337,6 +427,12 @@ export function ComponentRolesTab() {
|
|
|
337
427
|
)}
|
|
338
428
|
/>
|
|
339
429
|
|
|
430
|
+
{draftStatusContent ? (
|
|
431
|
+
<p className="text-xs text-muted-foreground">
|
|
432
|
+
{draftStatusContent}
|
|
433
|
+
</p>
|
|
434
|
+
) : null}
|
|
435
|
+
|
|
340
436
|
<DialogFooter>
|
|
341
437
|
<Button type="submit">{t('save')}</Button>
|
|
342
438
|
</DialogFooter>
|
|
@@ -353,7 +449,7 @@ export function ComponentRolesTab() {
|
|
|
353
449
|
<TableHead>ID</TableHead>
|
|
354
450
|
<TableHead>{t('component')}</TableHead>
|
|
355
451
|
<TableHead>{t('role')}</TableHead>
|
|
356
|
-
<TableHead className="w-
|
|
452
|
+
<TableHead className="w-25">{t('actions')}</TableHead>
|
|
357
453
|
</TableRow>
|
|
358
454
|
</TableHeader>
|
|
359
455
|
<TableBody>
|
|
@@ -45,6 +45,8 @@ import {
|
|
|
45
45
|
TableRow,
|
|
46
46
|
} from '@/components/ui/table';
|
|
47
47
|
import { useDebounce } from '@/hooks/use-debounce';
|
|
48
|
+
import { useFormDraft } from '@/hooks/use-form-draft';
|
|
49
|
+
import { formatDateTime } from '@/lib/format-date';
|
|
48
50
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
49
51
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
50
52
|
import {
|
|
@@ -55,9 +57,11 @@ import {
|
|
|
55
57
|
IconPlus,
|
|
56
58
|
IconTrash,
|
|
57
59
|
} from '@tabler/icons-react';
|
|
60
|
+
import { formatDistanceToNow } from 'date-fns';
|
|
61
|
+
import { enUS, ptBR } from 'date-fns/locale';
|
|
58
62
|
import { useTranslations } from 'next-intl';
|
|
59
|
-
import { useState } from 'react';
|
|
60
|
-
import { useForm } from 'react-hook-form';
|
|
63
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
64
|
+
import { useForm, useWatch } from 'react-hook-form';
|
|
61
65
|
import { toast } from 'sonner';
|
|
62
66
|
import { z } from 'zod';
|
|
63
67
|
|
|
@@ -66,6 +70,12 @@ type RoleFormData = {
|
|
|
66
70
|
role_id: number;
|
|
67
71
|
};
|
|
68
72
|
|
|
73
|
+
type DashboardRoleDraftPayload = {
|
|
74
|
+
values: RoleFormData;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const DASHBOARD_ROLE_DRAFT_STORAGE_KEY = 'core-dashboard-role-form-draft';
|
|
78
|
+
|
|
69
79
|
interface DashboardRole {
|
|
70
80
|
id: number;
|
|
71
81
|
dashboard_id: number;
|
|
@@ -106,9 +116,25 @@ interface Role {
|
|
|
106
116
|
}>;
|
|
107
117
|
}
|
|
108
118
|
|
|
119
|
+
type DashboardListResponse = {
|
|
120
|
+
data?: Dashboard[];
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
type RoleListResponse = {
|
|
124
|
+
data?: Role[];
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
type RequestError = {
|
|
128
|
+
response?: {
|
|
129
|
+
data?: {
|
|
130
|
+
message?: string;
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
|
|
109
135
|
export function DashboardRolesTab() {
|
|
110
136
|
const t = useTranslations('core.DashboardManagement');
|
|
111
|
-
const { request, currentLocaleCode } = useApp();
|
|
137
|
+
const { request, currentLocaleCode, getSettingValue } = useApp();
|
|
112
138
|
|
|
113
139
|
const roleSchema = z.object({
|
|
114
140
|
dashboard_id: z.coerce.number().min(1, t('selectDashboardRequired')),
|
|
@@ -120,7 +146,7 @@ export function DashboardRolesTab() {
|
|
|
120
146
|
const [roleToDelete, setRoleToDelete] = useState<number | null>(null);
|
|
121
147
|
const [page, setPage] = useState(1);
|
|
122
148
|
const [pageSize, setPageSize] = useState(10);
|
|
123
|
-
const
|
|
149
|
+
const searchQuery = '';
|
|
124
150
|
const debouncedSearch = useDebounce(searchQuery, 500);
|
|
125
151
|
|
|
126
152
|
const {
|
|
@@ -153,10 +179,10 @@ export function DashboardRolesTab() {
|
|
|
153
179
|
const total = paginatedData?.total ?? 0;
|
|
154
180
|
const totalPages = Math.ceil(total / pageSize);
|
|
155
181
|
|
|
156
|
-
const { data: dashboards } = useQuery<
|
|
182
|
+
const { data: dashboards } = useQuery<DashboardListResponse>({
|
|
157
183
|
queryKey: ['dashboards-list'],
|
|
158
184
|
queryFn: async () => {
|
|
159
|
-
const { data } = await request<
|
|
185
|
+
const { data } = await request<DashboardListResponse>({
|
|
160
186
|
url: '/dashboard',
|
|
161
187
|
method: 'GET',
|
|
162
188
|
});
|
|
@@ -164,10 +190,10 @@ export function DashboardRolesTab() {
|
|
|
164
190
|
},
|
|
165
191
|
});
|
|
166
192
|
|
|
167
|
-
const { data: availableRoles } = useQuery<
|
|
193
|
+
const { data: availableRoles } = useQuery<RoleListResponse>({
|
|
168
194
|
queryKey: ['roles'],
|
|
169
195
|
queryFn: async () => {
|
|
170
|
-
const { data } = await request<
|
|
196
|
+
const { data } = await request<RoleListResponse>({
|
|
171
197
|
url: '/role',
|
|
172
198
|
method: 'GET',
|
|
173
199
|
});
|
|
@@ -183,6 +209,67 @@ export function DashboardRolesTab() {
|
|
|
183
209
|
},
|
|
184
210
|
});
|
|
185
211
|
|
|
212
|
+
const watchedValues = useWatch({
|
|
213
|
+
control: form.control,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const {
|
|
217
|
+
clearDraft,
|
|
218
|
+
loadDraft,
|
|
219
|
+
hasDraft,
|
|
220
|
+
savedAt: draftSavedAt,
|
|
221
|
+
} = useFormDraft<DashboardRoleDraftPayload>({
|
|
222
|
+
storageKey: DASHBOARD_ROLE_DRAFT_STORAGE_KEY,
|
|
223
|
+
value: {
|
|
224
|
+
values: {
|
|
225
|
+
dashboard_id: Number(watchedValues.dashboard_id ?? 0),
|
|
226
|
+
role_id: Number(watchedValues.role_id ?? 0),
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
hasData:
|
|
230
|
+
Number(watchedValues.dashboard_id ?? 0) > 0 ||
|
|
231
|
+
Number(watchedValues.role_id ?? 0) > 0,
|
|
232
|
+
enabled: open,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const draftStatusContent = useMemo(() => {
|
|
236
|
+
if (!hasDraft || !draftSavedAt) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const savedDate = new Date(draftSavedAt);
|
|
241
|
+
if (Number.isNaN(savedDate.getTime())) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const locale = currentLocaleCode.startsWith('pt') ? ptBR : enUS;
|
|
246
|
+
const relativeLabel = formatDistanceToNow(savedDate, {
|
|
247
|
+
addSuffix: true,
|
|
248
|
+
locale,
|
|
249
|
+
});
|
|
250
|
+
const absoluteLabel = formatDateTime(
|
|
251
|
+
savedDate,
|
|
252
|
+
getSettingValue,
|
|
253
|
+
currentLocaleCode
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
return currentLocaleCode.startsWith('pt')
|
|
257
|
+
? `Rascunho salvo ${relativeLabel} • Último salvamento: ${absoluteLabel}`
|
|
258
|
+
: `Draft saved ${relativeLabel} • Last saved: ${absoluteLabel}`;
|
|
259
|
+
}, [draftSavedAt, currentLocaleCode, getSettingValue, hasDraft]);
|
|
260
|
+
|
|
261
|
+
useEffect(() => {
|
|
262
|
+
if (!open) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const storedDraft = loadDraft();
|
|
267
|
+
form.reset({
|
|
268
|
+
dashboard_id: storedDraft?.payload.values.dashboard_id ?? 0,
|
|
269
|
+
role_id: storedDraft?.payload.values.role_id ?? 0,
|
|
270
|
+
});
|
|
271
|
+
}, [form, loadDraft, open]);
|
|
272
|
+
|
|
186
273
|
const onSubmit = async (values: RoleFormData) => {
|
|
187
274
|
try {
|
|
188
275
|
await request({
|
|
@@ -194,12 +281,14 @@ export function DashboardRolesTab() {
|
|
|
194
281
|
},
|
|
195
282
|
});
|
|
196
283
|
toast.success(t('roleAdded'));
|
|
284
|
+
clearDraft();
|
|
197
285
|
setOpen(false);
|
|
198
286
|
form.reset();
|
|
199
287
|
refetch();
|
|
200
|
-
} catch (error:
|
|
288
|
+
} catch (error: unknown) {
|
|
201
289
|
console.error('Erro ao adicionar permissão:', error);
|
|
202
|
-
|
|
290
|
+
const message = (error as RequestError)?.response?.data?.message ?? '';
|
|
291
|
+
if (message.includes('already exists')) {
|
|
203
292
|
toast.error(t('roleAlreadyExists'));
|
|
204
293
|
} else {
|
|
205
294
|
toast.error(t('errorAddingRole'));
|
|
@@ -273,8 +362,8 @@ export function DashboardRolesTab() {
|
|
|
273
362
|
<FormItem>
|
|
274
363
|
<FormLabel>{t('dashboard')}</FormLabel>
|
|
275
364
|
<Select
|
|
276
|
-
|
|
277
|
-
|
|
365
|
+
value={field.value ? String(field.value) : undefined}
|
|
366
|
+
onValueChange={(value) => field.onChange(Number(value))}
|
|
278
367
|
>
|
|
279
368
|
<FormControl>
|
|
280
369
|
<SelectTrigger className="w-full">
|
|
@@ -311,8 +400,8 @@ export function DashboardRolesTab() {
|
|
|
311
400
|
<FormItem>
|
|
312
401
|
<FormLabel>{t('role')}</FormLabel>
|
|
313
402
|
<Select
|
|
314
|
-
|
|
315
|
-
|
|
403
|
+
value={field.value ? String(field.value) : undefined}
|
|
404
|
+
onValueChange={(value) => field.onChange(Number(value))}
|
|
316
405
|
>
|
|
317
406
|
<FormControl>
|
|
318
407
|
<SelectTrigger className="w-full">
|
|
@@ -337,6 +426,12 @@ export function DashboardRolesTab() {
|
|
|
337
426
|
)}
|
|
338
427
|
/>
|
|
339
428
|
|
|
429
|
+
{draftStatusContent ? (
|
|
430
|
+
<p className="text-xs text-muted-foreground">
|
|
431
|
+
{draftStatusContent}
|
|
432
|
+
</p>
|
|
433
|
+
) : null}
|
|
434
|
+
|
|
340
435
|
<DialogFooter>
|
|
341
436
|
<Button type="submit">{t('save')}</Button>
|
|
342
437
|
</DialogFooter>
|
|
@@ -353,7 +448,7 @@ export function DashboardRolesTab() {
|
|
|
353
448
|
<TableHead>ID</TableHead>
|
|
354
449
|
<TableHead>{t('dashboard')}</TableHead>
|
|
355
450
|
<TableHead>{t('role')}</TableHead>
|
|
356
|
-
<TableHead className="w-
|
|
451
|
+
<TableHead className="w-25">{t('actions')}</TableHead>
|
|
357
452
|
</TableRow>
|
|
358
453
|
</TableHeader>
|
|
359
454
|
<TableBody>
|
|
@@ -45,6 +45,8 @@ import {
|
|
|
45
45
|
TableRow,
|
|
46
46
|
} from '@/components/ui/table';
|
|
47
47
|
import { useDebounce } from '@/hooks/use-debounce';
|
|
48
|
+
import { useFormDraft } from '@/hooks/use-form-draft';
|
|
49
|
+
import { formatDateTime } from '@/lib/format-date';
|
|
48
50
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
49
51
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
50
52
|
import {
|
|
@@ -55,9 +57,11 @@ import {
|
|
|
55
57
|
IconPlus,
|
|
56
58
|
IconTrash,
|
|
57
59
|
} from '@tabler/icons-react';
|
|
60
|
+
import { formatDistanceToNow } from 'date-fns';
|
|
61
|
+
import { enUS, ptBR } from 'date-fns/locale';
|
|
58
62
|
import { useTranslations } from 'next-intl';
|
|
59
|
-
import { useState } from 'react';
|
|
60
|
-
import { useForm } from 'react-hook-form';
|
|
63
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
64
|
+
import { useForm, useWatch } from 'react-hook-form';
|
|
61
65
|
import { toast } from 'sonner';
|
|
62
66
|
import { z } from 'zod';
|
|
63
67
|
|
|
@@ -70,6 +74,12 @@ type ItemFormData = {
|
|
|
70
74
|
y_axis: number;
|
|
71
75
|
};
|
|
72
76
|
|
|
77
|
+
type DashboardItemDraftPayload = {
|
|
78
|
+
values: ItemFormData;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const DASHBOARD_ITEM_DRAFT_STORAGE_KEY = 'core-dashboard-item-form-draft';
|
|
82
|
+
|
|
73
83
|
interface DashboardItem {
|
|
74
84
|
id: number;
|
|
75
85
|
dashboard_id: number;
|
|
@@ -108,9 +118,17 @@ interface Component {
|
|
|
108
118
|
}>;
|
|
109
119
|
}
|
|
110
120
|
|
|
121
|
+
type DashboardListResponse = {
|
|
122
|
+
data?: Dashboard[];
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
type ComponentListResponse = {
|
|
126
|
+
data?: Component[];
|
|
127
|
+
};
|
|
128
|
+
|
|
111
129
|
export function ItemsTab() {
|
|
112
130
|
const t = useTranslations('core.DashboardManagement');
|
|
113
|
-
const { request, currentLocaleCode } = useApp();
|
|
131
|
+
const { request, currentLocaleCode, getSettingValue } = useApp();
|
|
114
132
|
|
|
115
133
|
const itemSchema = z.object({
|
|
116
134
|
dashboard_id: z.coerce.number().min(1, t('selectDashboardRequired')),
|
|
@@ -126,7 +144,7 @@ export function ItemsTab() {
|
|
|
126
144
|
const [itemToDelete, setItemToDelete] = useState<number | null>(null);
|
|
127
145
|
const [page, setPage] = useState(1);
|
|
128
146
|
const [pageSize, setPageSize] = useState(10);
|
|
129
|
-
const
|
|
147
|
+
const searchQuery = '';
|
|
130
148
|
const debouncedSearch = useDebounce(searchQuery, 500);
|
|
131
149
|
|
|
132
150
|
const {
|
|
@@ -159,10 +177,10 @@ export function ItemsTab() {
|
|
|
159
177
|
const total = paginatedData?.total ?? 0;
|
|
160
178
|
const totalPages = Math.ceil(total / pageSize);
|
|
161
179
|
|
|
162
|
-
const { data: dashboards } = useQuery<
|
|
180
|
+
const { data: dashboards } = useQuery<DashboardListResponse>({
|
|
163
181
|
queryKey: ['dashboards'],
|
|
164
182
|
queryFn: async () => {
|
|
165
|
-
const { data } = await request<
|
|
183
|
+
const { data } = await request<DashboardListResponse>({
|
|
166
184
|
url: '/dashboard',
|
|
167
185
|
method: 'GET',
|
|
168
186
|
});
|
|
@@ -170,10 +188,10 @@ export function ItemsTab() {
|
|
|
170
188
|
},
|
|
171
189
|
});
|
|
172
190
|
|
|
173
|
-
const { data: components } = useQuery<
|
|
191
|
+
const { data: components } = useQuery<ComponentListResponse>({
|
|
174
192
|
queryKey: ['dashboard-components'],
|
|
175
193
|
queryFn: async () => {
|
|
176
|
-
const { data } = await request<
|
|
194
|
+
const { data } = await request<ComponentListResponse>({
|
|
177
195
|
url: '/dashboard-component',
|
|
178
196
|
method: 'GET',
|
|
179
197
|
});
|
|
@@ -193,6 +211,79 @@ export function ItemsTab() {
|
|
|
193
211
|
},
|
|
194
212
|
});
|
|
195
213
|
|
|
214
|
+
const watchedValues = useWatch({
|
|
215
|
+
control: form.control,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const {
|
|
219
|
+
clearDraft,
|
|
220
|
+
loadDraft,
|
|
221
|
+
hasDraft,
|
|
222
|
+
savedAt: draftSavedAt,
|
|
223
|
+
} = useFormDraft<DashboardItemDraftPayload>({
|
|
224
|
+
storageKey: DASHBOARD_ITEM_DRAFT_STORAGE_KEY,
|
|
225
|
+
value: {
|
|
226
|
+
values: {
|
|
227
|
+
dashboard_id: Number(watchedValues.dashboard_id ?? 0),
|
|
228
|
+
component_id: Number(watchedValues.component_id ?? 0),
|
|
229
|
+
width: Number(watchedValues.width ?? 3),
|
|
230
|
+
height: Number(watchedValues.height ?? 2),
|
|
231
|
+
x_axis: Number(watchedValues.x_axis ?? 0),
|
|
232
|
+
y_axis: Number(watchedValues.y_axis ?? 0),
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
hasData:
|
|
236
|
+
Number(watchedValues.dashboard_id ?? 0) > 0 ||
|
|
237
|
+
Number(watchedValues.component_id ?? 0) > 0 ||
|
|
238
|
+
Number(watchedValues.width ?? 3) !== 3 ||
|
|
239
|
+
Number(watchedValues.height ?? 2) !== 2 ||
|
|
240
|
+
Number(watchedValues.x_axis ?? 0) !== 0 ||
|
|
241
|
+
Number(watchedValues.y_axis ?? 0) !== 0,
|
|
242
|
+
enabled: open,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const draftStatusContent = useMemo(() => {
|
|
246
|
+
if (!hasDraft || !draftSavedAt) {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const savedDate = new Date(draftSavedAt);
|
|
251
|
+
if (Number.isNaN(savedDate.getTime())) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const locale = currentLocaleCode.startsWith('pt') ? ptBR : enUS;
|
|
256
|
+
const relativeLabel = formatDistanceToNow(savedDate, {
|
|
257
|
+
addSuffix: true,
|
|
258
|
+
locale,
|
|
259
|
+
});
|
|
260
|
+
const absoluteLabel = formatDateTime(
|
|
261
|
+
savedDate,
|
|
262
|
+
getSettingValue,
|
|
263
|
+
currentLocaleCode
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
return currentLocaleCode.startsWith('pt')
|
|
267
|
+
? `Rascunho salvo ${relativeLabel} • Último salvamento: ${absoluteLabel}`
|
|
268
|
+
: `Draft saved ${relativeLabel} • Last saved: ${absoluteLabel}`;
|
|
269
|
+
}, [draftSavedAt, currentLocaleCode, getSettingValue, hasDraft]);
|
|
270
|
+
|
|
271
|
+
useEffect(() => {
|
|
272
|
+
if (!open) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const storedDraft = loadDraft();
|
|
277
|
+
form.reset({
|
|
278
|
+
dashboard_id: storedDraft?.payload.values.dashboard_id ?? 0,
|
|
279
|
+
component_id: storedDraft?.payload.values.component_id ?? 0,
|
|
280
|
+
width: storedDraft?.payload.values.width ?? 3,
|
|
281
|
+
height: storedDraft?.payload.values.height ?? 2,
|
|
282
|
+
x_axis: storedDraft?.payload.values.x_axis ?? 0,
|
|
283
|
+
y_axis: storedDraft?.payload.values.y_axis ?? 0,
|
|
284
|
+
});
|
|
285
|
+
}, [form, loadDraft, open]);
|
|
286
|
+
|
|
196
287
|
const onSubmit = async (values: ItemFormData) => {
|
|
197
288
|
try {
|
|
198
289
|
await request({
|
|
@@ -202,6 +293,7 @@ export function ItemsTab() {
|
|
|
202
293
|
});
|
|
203
294
|
|
|
204
295
|
toast.success(t('itemAdded'));
|
|
296
|
+
clearDraft();
|
|
205
297
|
setOpen(false);
|
|
206
298
|
form.reset();
|
|
207
299
|
refetch();
|
|
@@ -277,8 +369,8 @@ export function ItemsTab() {
|
|
|
277
369
|
<FormItem>
|
|
278
370
|
<FormLabel>{t('dashboard')}</FormLabel>
|
|
279
371
|
<Select
|
|
280
|
-
|
|
281
|
-
|
|
372
|
+
value={field.value ? field.value.toString() : undefined}
|
|
373
|
+
onValueChange={(value) => field.onChange(Number(value))}
|
|
282
374
|
>
|
|
283
375
|
<FormControl>
|
|
284
376
|
<SelectTrigger className="w-full">
|
|
@@ -286,7 +378,7 @@ export function ItemsTab() {
|
|
|
286
378
|
</SelectTrigger>
|
|
287
379
|
</FormControl>
|
|
288
380
|
<SelectContent>
|
|
289
|
-
{(dashboards
|
|
381
|
+
{(dashboards?.data ?? []).map(
|
|
290
382
|
(dashboard: Dashboard) => {
|
|
291
383
|
const name =
|
|
292
384
|
dashboard.dashboard_locale.find(
|
|
@@ -315,8 +407,8 @@ export function ItemsTab() {
|
|
|
315
407
|
<FormItem>
|
|
316
408
|
<FormLabel>{t('component')}</FormLabel>
|
|
317
409
|
<Select
|
|
318
|
-
|
|
319
|
-
|
|
410
|
+
value={field.value ? field.value.toString() : undefined}
|
|
411
|
+
onValueChange={(value) => field.onChange(Number(value))}
|
|
320
412
|
>
|
|
321
413
|
<FormControl>
|
|
322
414
|
<SelectTrigger className="w-full">
|
|
@@ -324,7 +416,7 @@ export function ItemsTab() {
|
|
|
324
416
|
</SelectTrigger>
|
|
325
417
|
</FormControl>
|
|
326
418
|
<SelectContent>
|
|
327
|
-
{(components
|
|
419
|
+
{(components?.data ?? []).map(
|
|
328
420
|
(component: Component) => {
|
|
329
421
|
const name =
|
|
330
422
|
component.dashboard_component_locale.find(
|
|
@@ -444,6 +536,12 @@ export function ItemsTab() {
|
|
|
444
536
|
</p>
|
|
445
537
|
</div>
|
|
446
538
|
|
|
539
|
+
{draftStatusContent ? (
|
|
540
|
+
<p className="text-xs text-muted-foreground">
|
|
541
|
+
{draftStatusContent}
|
|
542
|
+
</p>
|
|
543
|
+
) : null}
|
|
544
|
+
|
|
447
545
|
<DialogFooter>
|
|
448
546
|
<Button type="submit">{t('save')}</Button>
|
|
449
547
|
</DialogFooter>
|
|
@@ -460,7 +558,7 @@ export function ItemsTab() {
|
|
|
460
558
|
<TableHead>{t('id')}</TableHead>
|
|
461
559
|
<TableHead>{t('dashboard')}</TableHead>
|
|
462
560
|
<TableHead>{t('component')}</TableHead>
|
|
463
|
-
<TableHead className="w-
|
|
561
|
+
<TableHead className="w-25">{t('actions')}</TableHead>
|
|
464
562
|
</TableRow>
|
|
465
563
|
</TableHeader>
|
|
466
564
|
<TableBody>
|