@hed-hog/lms 0.0.330 → 0.0.338
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/dist/class-group/class-group.controller.d.ts +3 -3
- package/dist/class-group/class-group.service.d.ts +3 -3
- package/dist/course/course.service.d.ts.map +1 -1
- package/dist/course/course.service.js +12 -20
- package/dist/course/course.service.js.map +1 -1
- package/dist/enterprise/enterprise.controller.d.ts +72 -0
- package/dist/enterprise/enterprise.controller.d.ts.map +1 -1
- package/dist/enterprise/enterprise.controller.js +10 -0
- package/dist/enterprise/enterprise.controller.js.map +1 -1
- package/dist/enterprise/enterprise.service.d.ts +78 -0
- package/dist/enterprise/enterprise.service.d.ts.map +1 -1
- package/dist/enterprise/enterprise.service.js +413 -40
- package/dist/enterprise/enterprise.service.js.map +1 -1
- package/dist/enterprise/training/training-admin.controller.d.ts +6 -3
- package/dist/enterprise/training/training-admin.controller.d.ts.map +1 -1
- package/dist/enterprise/training/training-admin.controller.js +10 -6
- package/dist/enterprise/training/training-admin.controller.js.map +1 -1
- package/dist/enterprise/training/training-admin.service.d.ts +8 -2
- package/dist/enterprise/training/training-admin.service.d.ts.map +1 -1
- package/dist/enterprise/training/training-admin.service.js +108 -52
- package/dist/enterprise/training/training-admin.service.js.map +1 -1
- package/dist/enterprise/training/training-viewer.controller.d.ts +3 -0
- package/dist/enterprise/training/training-viewer.controller.d.ts.map +1 -1
- package/dist/evaluation/evaluation.controller.d.ts +4 -4
- package/dist/evaluation/evaluation.service.d.ts +4 -4
- package/dist/instructor/dto/create-instructor-skill.dto.d.ts +0 -4
- package/dist/instructor/dto/create-instructor-skill.dto.d.ts.map +1 -1
- package/dist/instructor/dto/create-instructor-skill.dto.js +0 -21
- package/dist/instructor/dto/create-instructor-skill.dto.js.map +1 -1
- package/dist/instructor/dto/update-instructor-skill.dto.d.ts +0 -4
- package/dist/instructor/dto/update-instructor-skill.dto.d.ts.map +1 -1
- package/dist/instructor/dto/update-instructor-skill.dto.js +0 -22
- package/dist/instructor/dto/update-instructor-skill.dto.js.map +1 -1
- package/dist/instructor/instructor-skill.controller.d.ts +4 -4
- package/dist/instructor/instructor-skill.service.d.ts +4 -7
- package/dist/instructor/instructor-skill.service.d.ts.map +1 -1
- package/dist/instructor/instructor-skill.service.js +2 -89
- package/dist/instructor/instructor-skill.service.js.map +1 -1
- package/dist/instructor/instructor.controller.d.ts +20 -0
- package/dist/instructor/instructor.controller.d.ts.map +1 -1
- package/dist/instructor/instructor.controller.js +19 -0
- package/dist/instructor/instructor.controller.js.map +1 -1
- package/dist/instructor/instructor.service.d.ts +25 -0
- package/dist/instructor/instructor.service.d.ts.map +1 -1
- package/dist/instructor/instructor.service.js +70 -18
- package/dist/instructor/instructor.service.js.map +1 -1
- package/dist/lms.module.d.ts.map +1 -1
- package/dist/lms.module.js.map +1 -1
- package/hedhog/data/route.yaml +23 -1
- package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +42 -24
- package/hedhog/frontend/app/_components/create-lms-instructor-sheet.tsx.ejs +591 -0
- package/hedhog/frontend/app/certificates/issued/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/certificates/models/page.tsx.ejs +7 -2
- package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +17 -17
- package/hedhog/frontend/app/classes/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/courses/[id]/_components/CourseClassificationCard.tsx.ejs +3 -33
- package/hedhog/frontend/app/courses/[id]/_components/CourseContentCard.tsx.ejs +9 -9
- package/hedhog/frontend/app/courses/[id]/_components/CourseMainInfoCard.tsx.ejs +109 -0
- package/hedhog/frontend/app/courses/[id]/_components/CourseMultiEntityPicker.tsx.ejs +42 -15
- package/hedhog/frontend/app/courses/[id]/_components/CourseRelationsCard.tsx.ejs +76 -81
- package/hedhog/frontend/app/courses/[id]/_components/CourseSummaryCard.tsx.ejs +60 -0
- package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +3 -3
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-scheduled-classes-tab.tsx.ejs +406 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +1 -1
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +134 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-course.tsx.ejs +113 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +314 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-session.tsx.ejs +174 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +242 -33
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +228 -152
- package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +185 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +71 -31
- package/hedhog/frontend/app/courses/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/enterprise/[id]/page.tsx.ejs +37 -41
- package/hedhog/frontend/app/enterprise/_components/enterprise-activity-timeline.tsx.ejs +87 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-admin-create-sheet.tsx.ejs +4 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-administrators-tab.tsx.ejs +31 -5
- package/hedhog/frontend/app/enterprise/_components/enterprise-classes-tab.tsx.ejs +79 -20
- package/hedhog/frontend/app/enterprise/_components/enterprise-company-identity-card.tsx.ejs +11 -2
- package/hedhog/frontend/app/enterprise/_components/enterprise-course-edit-sheet.tsx.ejs +201 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-courses-tab.tsx.ejs +55 -24
- package/hedhog/frontend/app/enterprise/_components/enterprise-detail-sheet.tsx.ejs +430 -296
- package/hedhog/frontend/app/enterprise/_components/enterprise-mocks.ts.ejs +277 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-overview-analytics.tsx.ejs +205 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-person-edit-sheet.tsx.ejs +97 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-sheet.tsx.ejs +82 -57
- package/hedhog/frontend/app/enterprise/_components/enterprise-student-create-sheet.tsx.ejs +4 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-students-tab.tsx.ejs +60 -22
- package/hedhog/frontend/app/enterprise/_components/enterprise-types.ts.ejs +54 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-user-create-sheet.tsx.ejs +211 -0
- package/hedhog/frontend/app/enterprise/page.tsx.ejs +39 -7
- package/hedhog/frontend/app/evaluations/_components/evaluation-topic-form-sheet.tsx.ejs +1 -1
- package/hedhog/frontend/app/exams/[id]/questions/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/exams/page.tsx.ejs +12 -3
- package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +51 -104
- package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +712 -427
- package/hedhog/frontend/app/instructors/page.tsx.ejs +77 -53
- package/hedhog/frontend/app/paths/page.tsx.ejs +14 -5
- package/hedhog/frontend/app/reports/courses/page.tsx.ejs +5 -5
- package/hedhog/frontend/app/reports/dashboard/page.tsx.ejs +8 -8
- package/hedhog/frontend/app/reports/evaluations/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/reports/page.tsx.ejs +7 -7
- package/hedhog/frontend/app/reports/students/page.tsx.ejs +6 -6
- package/hedhog/frontend/app/training/page.tsx.ejs +8 -3
- package/hedhog/frontend/messages/en.json +394 -55
- package/hedhog/frontend/messages/pt.json +389 -48
- package/hedhog/frontend/widgets/active-classes-kpi.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/active-courses-kpi.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/approval-rate-kpi.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/class-calendar.tsx.ejs +2 -2
- package/hedhog/frontend/widgets/completion-rate-kpi.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/issued-certificates-kpi.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/total-students-kpi.tsx.ejs +1 -1
- package/hedhog/table/enterprise_student_license_event.yaml +30 -0
- package/hedhog/table/instructor_qualification.yaml +1 -1
- package/hedhog/table/instructor_skill.yaml +0 -11
- package/package.json +8 -8
- package/src/course/course.service.ts +12 -24
- package/src/enterprise/enterprise.controller.ts +5 -0
- package/src/enterprise/enterprise.service.ts +507 -29
- package/src/enterprise/training/training-admin.controller.ts +4 -0
- package/src/enterprise/training/training-admin.service.ts +115 -51
- package/src/instructor/dto/create-instructor-skill.dto.ts +0 -17
- package/src/instructor/dto/update-instructor-skill.dto.ts +0 -18
- package/src/instructor/instructor-skill.service.ts +2 -97
- package/src/instructor/instructor.controller.ts +16 -0
- package/src/instructor/instructor.service.ts +85 -10
- package/src/lms.module.ts +1 -0
|
@@ -1,34 +1,55 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
AlertDialog,
|
|
5
|
+
AlertDialogCancel,
|
|
6
|
+
AlertDialogContent,
|
|
7
|
+
AlertDialogDescription,
|
|
8
|
+
AlertDialogFooter,
|
|
9
|
+
AlertDialogHeader,
|
|
10
|
+
AlertDialogTitle,
|
|
11
|
+
} from '@/components/ui/alert-dialog';
|
|
12
|
+
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
|
3
13
|
import { Badge } from '@/components/ui/badge';
|
|
4
14
|
import { Button } from '@/components/ui/button';
|
|
5
15
|
import { Card, CardContent } from '@/components/ui/card';
|
|
6
|
-
import {
|
|
16
|
+
import { Input } from '@/components/ui/input';
|
|
7
17
|
import { KpiCardsGrid, type KpiCardItem } from '@/components/ui/kpi-cards-grid';
|
|
8
|
-
import { Separator } from '@/components/ui/separator';
|
|
9
18
|
import {
|
|
10
19
|
Sheet,
|
|
11
20
|
SheetContent,
|
|
21
|
+
SheetDescription,
|
|
12
22
|
SheetHeader,
|
|
13
23
|
SheetTitle,
|
|
14
24
|
} from '@/components/ui/sheet';
|
|
15
25
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
16
26
|
import { useApp } from '@hed-hog/next-app-provider';
|
|
17
27
|
import {
|
|
28
|
+
AlertTriangle,
|
|
18
29
|
BookOpen,
|
|
19
|
-
Building2,
|
|
20
30
|
CalendarDays,
|
|
21
|
-
|
|
22
|
-
Globe,
|
|
23
|
-
Link2,
|
|
31
|
+
LineChart as LineChartIcon,
|
|
24
32
|
Pencil,
|
|
25
|
-
|
|
33
|
+
Trash2,
|
|
26
34
|
UserCheck,
|
|
35
|
+
UserRound,
|
|
27
36
|
} from 'lucide-react';
|
|
28
37
|
import { useTranslations } from 'next-intl';
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
|
|
38
|
+
import { useEffect, useRef, useState } from 'react';
|
|
39
|
+
import {
|
|
40
|
+
Bar,
|
|
41
|
+
BarChart,
|
|
42
|
+
CartesianGrid,
|
|
43
|
+
Cell,
|
|
44
|
+
Line,
|
|
45
|
+
LineChart,
|
|
46
|
+
Pie,
|
|
47
|
+
PieChart,
|
|
48
|
+
ResponsiveContainer,
|
|
49
|
+
Tooltip,
|
|
50
|
+
XAxis,
|
|
51
|
+
YAxis,
|
|
52
|
+
} from 'recharts';
|
|
32
53
|
import { AdministratorsTab } from './enterprise-administrators-tab';
|
|
33
54
|
import { ClassesTab } from './enterprise-classes-tab';
|
|
34
55
|
import { CompanyIdentityCard } from './enterprise-company-identity-card';
|
|
@@ -36,8 +57,7 @@ import { CoursesTab } from './enterprise-courses-tab';
|
|
|
36
57
|
import { STATUS_LABEL, STATUS_VARIANT } from './enterprise-detail-constants';
|
|
37
58
|
import { EnterpriseSheet, type EnterpriseFormValues } from './enterprise-sheet';
|
|
38
59
|
import { StudentsTab } from './enterprise-students-tab';
|
|
39
|
-
import type { EnterpriseAccount } from './enterprise-types';
|
|
40
|
-
import { UserDistributionChart } from './enterprise-user-distribution-chart';
|
|
60
|
+
import type { EnterpriseAccount, EnterpriseOverview } from './enterprise-types';
|
|
41
61
|
|
|
42
62
|
// ── Props ─────────────────────────────────────────────────────────────────────
|
|
43
63
|
|
|
@@ -58,26 +78,25 @@ export function EnterpriseDetailSheet({
|
|
|
58
78
|
onAccountUpdate,
|
|
59
79
|
onDataChange,
|
|
60
80
|
}: EnterpriseDetailSheetProps) {
|
|
61
|
-
// Local mirror so saves refresh the sheet without closing it
|
|
62
81
|
const [currentAccount, setCurrentAccount] =
|
|
63
82
|
useState<EnterpriseAccount | null>(account);
|
|
83
|
+
const [overview, setOverview] = useState<EnterpriseOverview | null>(null);
|
|
64
84
|
const [editSheetOpen, setEditSheetOpen] = useState(false);
|
|
65
|
-
const [
|
|
66
|
-
const [
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const [savingCompany, setSavingCompany] = useState(false);
|
|
85
|
+
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
86
|
+
const [deleteConfirmText, setDeleteConfirmText] = useState('');
|
|
87
|
+
const [isDeleting, setIsDeleting] = useState(false);
|
|
88
|
+
const onAccountUpdateRef = useRef(onAccountUpdate);
|
|
70
89
|
const { request } = useApp();
|
|
71
|
-
const router = useRouter();
|
|
72
90
|
const t = useTranslations('lms.EnterpriseDetailPage');
|
|
73
91
|
|
|
74
|
-
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
onAccountUpdateRef.current = onAccountUpdate;
|
|
94
|
+
}, [onAccountUpdate]);
|
|
95
|
+
|
|
75
96
|
useEffect(() => {
|
|
76
97
|
setCurrentAccount(account);
|
|
77
98
|
}, [account]);
|
|
78
99
|
|
|
79
|
-
// Refetch full account when sheet opens so studentsCount / managersCount are populated
|
|
80
|
-
// (the list endpoint omits these counts; only getById computes them)
|
|
81
100
|
useEffect(() => {
|
|
82
101
|
if (!open || !account?.id) return;
|
|
83
102
|
request<EnterpriseAccount>({
|
|
@@ -85,23 +104,49 @@ export function EnterpriseDetailSheet({
|
|
|
85
104
|
method: 'GET',
|
|
86
105
|
})
|
|
87
106
|
.then((res) => {
|
|
88
|
-
const refreshed: EnterpriseAccount =
|
|
107
|
+
const refreshed: EnterpriseAccount =
|
|
108
|
+
(res as unknown as { data?: EnterpriseAccount }).data ??
|
|
109
|
+
(res as unknown as EnterpriseAccount);
|
|
89
110
|
setCurrentAccount(refreshed);
|
|
90
111
|
})
|
|
91
112
|
.catch(() => {
|
|
92
113
|
// keep the stale list-row data as fallback
|
|
93
114
|
});
|
|
94
|
-
|
|
95
|
-
|
|
115
|
+
}, [open, account?.id, request]);
|
|
116
|
+
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
if (!open || !account?.id) return;
|
|
119
|
+
request<EnterpriseOverview>({
|
|
120
|
+
url: `/lms/enterprise/${account.id}/overview`,
|
|
121
|
+
method: 'GET',
|
|
122
|
+
})
|
|
123
|
+
.then((res) => {
|
|
124
|
+
const data: EnterpriseOverview =
|
|
125
|
+
(res as unknown as { data?: EnterpriseOverview }).data ??
|
|
126
|
+
(res as unknown as EnterpriseOverview);
|
|
127
|
+
setOverview(data);
|
|
128
|
+
if (data?.account) {
|
|
129
|
+
setCurrentAccount(data.account);
|
|
130
|
+
onAccountUpdateRef.current?.(data.account);
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
.catch(() => {
|
|
134
|
+
setOverview(null);
|
|
135
|
+
});
|
|
136
|
+
}, [open, account?.id, request]);
|
|
96
137
|
|
|
97
138
|
async function handleInternalSave(_values: EnterpriseFormValues) {
|
|
98
139
|
if (!currentAccount) return;
|
|
99
140
|
try {
|
|
100
|
-
const res = await request<
|
|
101
|
-
url: `/lms/enterprise/${currentAccount.id}`,
|
|
141
|
+
const res = await request<EnterpriseOverview>({
|
|
142
|
+
url: `/lms/enterprise/${currentAccount.id}/overview`,
|
|
102
143
|
method: 'GET',
|
|
103
144
|
});
|
|
104
|
-
const
|
|
145
|
+
const payload: EnterpriseOverview =
|
|
146
|
+
(res as unknown as { data?: EnterpriseOverview }).data ??
|
|
147
|
+
(res as unknown as EnterpriseOverview);
|
|
148
|
+
const refreshed = payload.account;
|
|
149
|
+
setOverview(payload);
|
|
105
150
|
setCurrentAccount(refreshed);
|
|
106
151
|
onAccountUpdate?.(refreshed);
|
|
107
152
|
} catch {
|
|
@@ -128,11 +173,15 @@ export function EnterpriseDetailSheet({
|
|
|
128
173
|
onDataChange?.();
|
|
129
174
|
if (!currentAccount?.id) return;
|
|
130
175
|
try {
|
|
131
|
-
const res = await request<
|
|
132
|
-
url: `/lms/enterprise/${currentAccount.id}`,
|
|
176
|
+
const res = await request<EnterpriseOverview>({
|
|
177
|
+
url: `/lms/enterprise/${currentAccount.id}/overview`,
|
|
133
178
|
method: 'GET',
|
|
134
179
|
});
|
|
135
|
-
const
|
|
180
|
+
const payload: EnterpriseOverview =
|
|
181
|
+
(res as unknown as { data?: EnterpriseOverview }).data ??
|
|
182
|
+
(res as unknown as EnterpriseOverview);
|
|
183
|
+
const refreshed = payload.account;
|
|
184
|
+
setOverview(payload);
|
|
136
185
|
setCurrentAccount(refreshed);
|
|
137
186
|
onAccountUpdate?.(refreshed);
|
|
138
187
|
} catch {
|
|
@@ -140,31 +189,36 @@ export function EnterpriseDetailSheet({
|
|
|
140
189
|
}
|
|
141
190
|
}
|
|
142
191
|
|
|
143
|
-
async function
|
|
192
|
+
async function handleDeleteAccount() {
|
|
144
193
|
if (!currentAccount?.id) return;
|
|
145
|
-
|
|
194
|
+
setIsDeleting(true);
|
|
146
195
|
try {
|
|
147
196
|
await request({
|
|
148
197
|
url: `/lms/enterprise/${currentAccount.id}`,
|
|
149
|
-
method: '
|
|
150
|
-
data: { crm_person_id: newPersonId },
|
|
198
|
+
method: 'DELETE',
|
|
151
199
|
});
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
} catch {
|
|
156
|
-
// toast handled by caller
|
|
200
|
+
setDeleteDialogOpen(false);
|
|
201
|
+
onOpenChange(false);
|
|
202
|
+
onDataChange?.();
|
|
157
203
|
} finally {
|
|
158
|
-
|
|
204
|
+
setIsDeleting(false);
|
|
159
205
|
}
|
|
160
206
|
}
|
|
161
207
|
|
|
208
|
+
function getPersonAvatarUrl(avatarId?: number | null) {
|
|
209
|
+
return typeof avatarId === 'number' && avatarId > 0
|
|
210
|
+
? `${process.env.NEXT_PUBLIC_API_BASE_URL}/person/avatar/${avatarId}`
|
|
211
|
+
: undefined;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const overviewKpis = overview?.kpis;
|
|
215
|
+
|
|
162
216
|
const kpiItems: KpiCardItem[] = currentAccount
|
|
163
217
|
? [
|
|
164
218
|
{
|
|
165
219
|
key: 'students',
|
|
166
220
|
title: t('kpis.students.label'),
|
|
167
|
-
value: currentAccount.studentsCount,
|
|
221
|
+
value: overviewKpis?.students ?? currentAccount.studentsCount,
|
|
168
222
|
description: t('kpis.students.description'),
|
|
169
223
|
icon: UserCheck,
|
|
170
224
|
accentClassName:
|
|
@@ -174,7 +228,7 @@ export function EnterpriseDetailSheet({
|
|
|
174
228
|
{
|
|
175
229
|
key: 'classes',
|
|
176
230
|
title: t('kpis.contractedClasses.label'),
|
|
177
|
-
value: currentAccount.classesCount,
|
|
231
|
+
value: overviewKpis?.classes ?? currentAccount.classesCount,
|
|
178
232
|
description: t('kpis.contractedClasses.description'),
|
|
179
233
|
icon: CalendarDays,
|
|
180
234
|
accentClassName:
|
|
@@ -184,33 +238,94 @@ export function EnterpriseDetailSheet({
|
|
|
184
238
|
{
|
|
185
239
|
key: 'courses',
|
|
186
240
|
title: t('kpis.courses.label'),
|
|
187
|
-
value: currentAccount.coursesCount,
|
|
241
|
+
value: overviewKpis?.courses ?? currentAccount.coursesCount,
|
|
188
242
|
description: t('kpis.courses.description'),
|
|
189
243
|
icon: BookOpen,
|
|
190
244
|
accentClassName: 'from-blue-500/20 via-cyan-500/10 to-transparent',
|
|
191
245
|
iconContainerClassName: 'bg-blue-50 text-blue-600',
|
|
192
246
|
},
|
|
247
|
+
{
|
|
248
|
+
key: 'administrators',
|
|
249
|
+
title: t('kpis.users.label'),
|
|
250
|
+
value:
|
|
251
|
+
overviewKpis?.administrators ??
|
|
252
|
+
currentAccount.adminsCount + currentAccount.managersCount,
|
|
253
|
+
description: t('kpis.users.description'),
|
|
254
|
+
icon: UserRound,
|
|
255
|
+
accentClassName: 'from-amber-500/20 via-orange-500/10 to-transparent',
|
|
256
|
+
iconContainerClassName: 'bg-amber-50 text-amber-600',
|
|
257
|
+
},
|
|
193
258
|
]
|
|
194
259
|
: [];
|
|
195
260
|
|
|
261
|
+
const licenseUsageChart = [
|
|
262
|
+
{
|
|
263
|
+
name: 'Usadas',
|
|
264
|
+
value: overview?.licenseUsage.used ?? 0,
|
|
265
|
+
color: 'hsl(221 83% 53%)',
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
name: 'Disponiveis',
|
|
269
|
+
value: overview?.licenseUsage.available ?? 0,
|
|
270
|
+
color: 'hsl(160 84% 39%)',
|
|
271
|
+
},
|
|
272
|
+
];
|
|
273
|
+
|
|
196
274
|
return (
|
|
197
275
|
<>
|
|
198
|
-
{/* ── Detail Sheet ── */}
|
|
199
276
|
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
200
277
|
<SheetContent
|
|
201
278
|
side="right"
|
|
202
|
-
className="flex w-full flex-col gap-0 overflow-y-auto p-0 sm:max-w-
|
|
279
|
+
className="flex w-full flex-col gap-0 overflow-y-auto p-0 sm:max-w-[95vw] lg:max-w-6xl"
|
|
203
280
|
>
|
|
204
281
|
{currentAccount && (
|
|
205
282
|
<>
|
|
206
283
|
<SheetHeader className="flex flex-row items-center justify-between gap-4 border-b px-6 py-4">
|
|
207
|
-
<div className="flex items-center gap-3">
|
|
208
|
-
<
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
284
|
+
<div className="flex min-w-0 items-center gap-3">
|
|
285
|
+
<Avatar className="h-10 w-10 border">
|
|
286
|
+
<AvatarImage
|
|
287
|
+
src={getPersonAvatarUrl(
|
|
288
|
+
currentAccount.crmAccount?.avatarId ?? null
|
|
289
|
+
)}
|
|
290
|
+
/>
|
|
291
|
+
<AvatarFallback>
|
|
292
|
+
{currentAccount.name
|
|
293
|
+
.split(' ')
|
|
294
|
+
.filter(Boolean)
|
|
295
|
+
.slice(0, 2)
|
|
296
|
+
.map((part) => part[0]?.toUpperCase() ?? '')
|
|
297
|
+
.join('')}
|
|
298
|
+
</AvatarFallback>
|
|
299
|
+
</Avatar>
|
|
300
|
+
<div className="min-w-0">
|
|
301
|
+
<SheetTitle className="truncate text-base font-semibold">
|
|
302
|
+
{currentAccount.name}
|
|
303
|
+
</SheetTitle>
|
|
304
|
+
<div className="mt-0.5 flex items-center gap-2 text-xs text-muted-foreground">
|
|
305
|
+
<span className="font-mono">{currentAccount.slug}</span>
|
|
306
|
+
<Badge
|
|
307
|
+
variant={STATUS_VARIANT[currentAccount.status]}
|
|
308
|
+
className="text-[10px]"
|
|
309
|
+
>
|
|
310
|
+
{STATUS_LABEL[currentAccount.status]}
|
|
311
|
+
</Badge>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
<SheetDescription className="sr-only">
|
|
316
|
+
{currentAccount.name} overview, metrics, classes, courses,
|
|
317
|
+
administrators, and students.
|
|
318
|
+
</SheetDescription>
|
|
319
|
+
<div className="flex items-center gap-2">
|
|
320
|
+
<Button
|
|
321
|
+
variant="outline"
|
|
322
|
+
size="sm"
|
|
323
|
+
onClick={() => setEditSheetOpen(true)}
|
|
324
|
+
className="shrink-0"
|
|
325
|
+
>
|
|
326
|
+
<Pencil className="mr-2 h-4 w-4" />
|
|
327
|
+
{t('actions.edit')}
|
|
328
|
+
</Button>
|
|
214
329
|
</div>
|
|
215
330
|
</SheetHeader>
|
|
216
331
|
|
|
@@ -221,9 +336,6 @@ export function EnterpriseDetailSheet({
|
|
|
221
336
|
<TabsTrigger value="overview">
|
|
222
337
|
{t('tabs.overview')}
|
|
223
338
|
</TabsTrigger>
|
|
224
|
-
<TabsTrigger value="empresa">
|
|
225
|
-
{t('tabs.empresa')}
|
|
226
|
-
</TabsTrigger>
|
|
227
339
|
<TabsTrigger value="classes">
|
|
228
340
|
{t('tabs.classes')}
|
|
229
341
|
</TabsTrigger>
|
|
@@ -237,269 +349,236 @@ export function EnterpriseDetailSheet({
|
|
|
237
349
|
{t('tabs.administrators')}
|
|
238
350
|
</TabsTrigger>
|
|
239
351
|
</TabsList>
|
|
240
|
-
<Button
|
|
241
|
-
variant="outline"
|
|
242
|
-
size="sm"
|
|
243
|
-
onClick={() => setEditSheetOpen(true)}
|
|
244
|
-
className="shrink-0"
|
|
245
|
-
>
|
|
246
|
-
<Pencil className="mr-2 h-4 w-4" />
|
|
247
|
-
{t('actions.edit')}
|
|
248
|
-
</Button>
|
|
249
352
|
</div>
|
|
250
353
|
|
|
251
|
-
{/* ── Overview tab ── */}
|
|
252
354
|
<TabsContent value="overview" className="mt-5 space-y-5">
|
|
253
|
-
{/* Company identity card — click opens edit sheet */}
|
|
254
355
|
<CompanyIdentityCard
|
|
255
356
|
account={currentAccount}
|
|
256
357
|
onEditClick={() => setEditSheetOpen(true)}
|
|
257
358
|
/>
|
|
258
359
|
|
|
259
|
-
{/* KPI row */}
|
|
260
360
|
<KpiCardsGrid items={kpiItems} />
|
|
261
361
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
362
|
+
<div className="grid grid-cols-1 gap-5 lg:grid-cols-3">
|
|
363
|
+
<Card className="border-border/60 lg:col-span-2">
|
|
364
|
+
<CardContent className="p-4">
|
|
365
|
+
<div className="mb-2 flex items-center gap-2 text-sm font-medium">
|
|
366
|
+
<LineChartIcon className="h-4 w-4 text-muted-foreground" />
|
|
367
|
+
Uso de licencas no tempo
|
|
368
|
+
</div>
|
|
369
|
+
<div className="h-64 w-full">
|
|
370
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
371
|
+
<LineChart data={overview?.licenseTimeline ?? []}>
|
|
372
|
+
<CartesianGrid
|
|
373
|
+
strokeDasharray="3 3"
|
|
374
|
+
vertical={false}
|
|
375
|
+
/>
|
|
376
|
+
<XAxis
|
|
377
|
+
dataKey="label"
|
|
378
|
+
tickLine={false}
|
|
379
|
+
axisLine={false}
|
|
380
|
+
/>
|
|
381
|
+
<YAxis
|
|
382
|
+
tickLine={false}
|
|
383
|
+
axisLine={false}
|
|
384
|
+
allowDecimals={false}
|
|
385
|
+
/>
|
|
386
|
+
<Tooltip />
|
|
387
|
+
<Bar
|
|
388
|
+
dataKey="assigned"
|
|
389
|
+
fill="hsl(160 84% 39%)"
|
|
390
|
+
radius={[4, 4, 0, 0]}
|
|
391
|
+
/>
|
|
392
|
+
<Bar
|
|
393
|
+
dataKey="revoked"
|
|
394
|
+
fill="hsl(0 84% 60%)"
|
|
395
|
+
radius={[4, 4, 0, 0]}
|
|
396
|
+
/>
|
|
397
|
+
<Line
|
|
398
|
+
type="monotone"
|
|
399
|
+
dataKey="used"
|
|
400
|
+
stroke="hsl(221 83% 53%)"
|
|
401
|
+
strokeWidth={2.5}
|
|
402
|
+
dot={{ r: 3 }}
|
|
403
|
+
/>
|
|
404
|
+
</LineChart>
|
|
405
|
+
</ResponsiveContainer>
|
|
406
|
+
</div>
|
|
407
|
+
</CardContent>
|
|
408
|
+
</Card>
|
|
268
409
|
|
|
269
|
-
{/* ── Empresa tab ── */}
|
|
270
|
-
<TabsContent value="empresa" className="mt-4 space-y-4">
|
|
271
|
-
{/* ── Linked company card ── */}
|
|
272
|
-
{currentAccount.crmAccountId &&
|
|
273
|
-
currentAccount.crmAccountName ? (
|
|
274
410
|
<Card className="border-border/60">
|
|
275
|
-
<CardContent className="p-
|
|
276
|
-
<div className="
|
|
277
|
-
|
|
278
|
-
<div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-xl border bg-muted/40">
|
|
279
|
-
<Building2 className="h-6 w-6 text-muted-foreground" />
|
|
280
|
-
</div>
|
|
281
|
-
<div>
|
|
282
|
-
<p className="text-base font-semibold leading-tight">
|
|
283
|
-
{currentAccount.crmAccount?.tradeName ||
|
|
284
|
-
currentAccount.crmAccountName}
|
|
285
|
-
</p>
|
|
286
|
-
{currentAccount.crmAccount?.tradeName &&
|
|
287
|
-
currentAccount.crmAccount.tradeName !==
|
|
288
|
-
currentAccount.crmAccountName && (
|
|
289
|
-
<p className="text-xs text-muted-foreground">
|
|
290
|
-
{currentAccount.crmAccountName}
|
|
291
|
-
</p>
|
|
292
|
-
)}
|
|
293
|
-
{currentAccount.crmAccount?.lifecycleStage && (
|
|
294
|
-
<Badge
|
|
295
|
-
variant="outline"
|
|
296
|
-
className="mt-1 text-[10px]"
|
|
297
|
-
>
|
|
298
|
-
{currentAccount.crmAccount.lifecycleStage}
|
|
299
|
-
</Badge>
|
|
300
|
-
)}
|
|
301
|
-
</div>
|
|
302
|
-
</div>
|
|
303
|
-
<Button
|
|
304
|
-
variant="outline"
|
|
305
|
-
size="sm"
|
|
306
|
-
onClick={() =>
|
|
307
|
-
router.push(
|
|
308
|
-
`/contact/accounts?id=${currentAccount.crmAccountId}`
|
|
309
|
-
)
|
|
310
|
-
}
|
|
311
|
-
className="shrink-0"
|
|
312
|
-
>
|
|
313
|
-
<ExternalLink className="mr-2 h-4 w-4" />
|
|
314
|
-
{t('empresa.openRecord')}
|
|
315
|
-
</Button>
|
|
411
|
+
<CardContent className="space-y-4 p-4">
|
|
412
|
+
<div className="text-sm font-medium">
|
|
413
|
+
Licencas atuais
|
|
316
414
|
</div>
|
|
415
|
+
<div className="mx-auto h-40 w-40">
|
|
416
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
417
|
+
<PieChart>
|
|
418
|
+
<Pie
|
|
419
|
+
data={licenseUsageChart}
|
|
420
|
+
dataKey="value"
|
|
421
|
+
innerRadius={42}
|
|
422
|
+
outerRadius={64}
|
|
423
|
+
strokeWidth={2}
|
|
424
|
+
>
|
|
425
|
+
{licenseUsageChart.map((slice) => (
|
|
426
|
+
<Cell key={slice.name} fill={slice.color} />
|
|
427
|
+
))}
|
|
428
|
+
</Pie>
|
|
429
|
+
<Tooltip />
|
|
430
|
+
</PieChart>
|
|
431
|
+
</ResponsiveContainer>
|
|
432
|
+
</div>
|
|
433
|
+
<div className="text-xs text-muted-foreground">
|
|
434
|
+
{overview?.licenseUsage.used ?? 0} usadas
|
|
435
|
+
{typeof overview?.licenseUsage.limit === 'number'
|
|
436
|
+
? ` de ${overview.licenseUsage.limit} limite`
|
|
437
|
+
: ' (ilimitado)'}
|
|
438
|
+
</div>
|
|
439
|
+
</CardContent>
|
|
440
|
+
</Card>
|
|
441
|
+
</div>
|
|
317
442
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
onClick={() => setCompanyPickerOpen((v) => !v)}
|
|
368
|
-
>
|
|
369
|
-
<RefreshCw className="mr-2 h-3.5 w-3.5" />
|
|
370
|
-
{t('empresa.changeCompany')}
|
|
371
|
-
</Button>
|
|
443
|
+
<div className="grid grid-cols-1 gap-5 lg:grid-cols-2">
|
|
444
|
+
<Card className="border-border/60">
|
|
445
|
+
<CardContent className="space-y-3 p-4">
|
|
446
|
+
<div className="flex items-center justify-between">
|
|
447
|
+
<p className="text-sm font-medium">
|
|
448
|
+
Vagas em turmas agendadas
|
|
449
|
+
</p>
|
|
450
|
+
<Badge variant="outline">
|
|
451
|
+
{overview?.scheduledSeats.capacity ?? 0} total
|
|
452
|
+
</Badge>
|
|
453
|
+
</div>
|
|
454
|
+
<div className="h-56 w-full">
|
|
455
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
456
|
+
<BarChart
|
|
457
|
+
data={[
|
|
458
|
+
{
|
|
459
|
+
name: 'Vagas',
|
|
460
|
+
usadas: overview?.scheduledSeats.used ?? 0,
|
|
461
|
+
abertas: overview?.scheduledSeats.open ?? 0,
|
|
462
|
+
},
|
|
463
|
+
]}
|
|
464
|
+
>
|
|
465
|
+
<CartesianGrid
|
|
466
|
+
strokeDasharray="3 3"
|
|
467
|
+
vertical={false}
|
|
468
|
+
/>
|
|
469
|
+
<XAxis
|
|
470
|
+
dataKey="name"
|
|
471
|
+
tickLine={false}
|
|
472
|
+
axisLine={false}
|
|
473
|
+
/>
|
|
474
|
+
<YAxis
|
|
475
|
+
tickLine={false}
|
|
476
|
+
axisLine={false}
|
|
477
|
+
allowDecimals={false}
|
|
478
|
+
/>
|
|
479
|
+
<Tooltip />
|
|
480
|
+
<Bar
|
|
481
|
+
dataKey="usadas"
|
|
482
|
+
stackId="seats"
|
|
483
|
+
fill="hsl(221 83% 53%)"
|
|
484
|
+
/>
|
|
485
|
+
<Bar
|
|
486
|
+
dataKey="abertas"
|
|
487
|
+
stackId="seats"
|
|
488
|
+
fill="hsl(160 84% 39%)"
|
|
489
|
+
/>
|
|
490
|
+
</BarChart>
|
|
491
|
+
</ResponsiveContainer>
|
|
372
492
|
</div>
|
|
373
493
|
</CardContent>
|
|
374
494
|
</Card>
|
|
375
|
-
) : (
|
|
376
|
-
/* ── Empty state ── */
|
|
377
|
-
<div className="flex flex-col items-center justify-center py-10 text-center">
|
|
378
|
-
<div className="mb-4 text-muted-foreground/30">
|
|
379
|
-
<Link2 className="h-12 w-12" />
|
|
380
|
-
</div>
|
|
381
|
-
<p className="text-sm font-medium text-muted-foreground">
|
|
382
|
-
{t('empresa.emptyTitle')}
|
|
383
|
-
</p>
|
|
384
|
-
<p className="mt-1 max-w-xs text-xs text-muted-foreground/60">
|
|
385
|
-
{t('empresa.emptyDescription')}
|
|
386
|
-
</p>
|
|
387
|
-
<Button
|
|
388
|
-
variant="outline"
|
|
389
|
-
size="sm"
|
|
390
|
-
className="mt-6"
|
|
391
|
-
onClick={() => setCompanyPickerOpen(true)}
|
|
392
|
-
>
|
|
393
|
-
<Link2 className="mr-2 h-4 w-4" />
|
|
394
|
-
{t('empresa.linkCompany')}
|
|
395
|
-
</Button>
|
|
396
|
-
</div>
|
|
397
|
-
)}
|
|
398
495
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
value={companyPickerValue}
|
|
411
|
-
onChange={(val) => setCompanyPickerValue(val)}
|
|
412
|
-
label=""
|
|
413
|
-
placeholder={t('empresa.pickerPlaceholder')}
|
|
414
|
-
searchPlaceholder={t(
|
|
415
|
-
'empresa.pickerSearchPlaceholder'
|
|
416
|
-
)}
|
|
417
|
-
emptyLabel={t('empresa.pickerEmptyLabel')}
|
|
418
|
-
entityLabel={t('empresa.pickerEntityLabel')}
|
|
419
|
-
clearable={false}
|
|
420
|
-
valueType="number"
|
|
421
|
-
loadOptions={async ({ page, pageSize, search }) => {
|
|
422
|
-
const params = new URLSearchParams({
|
|
423
|
-
page: String(page),
|
|
424
|
-
pageSize: String(pageSize),
|
|
425
|
-
});
|
|
426
|
-
if (search.trim())
|
|
427
|
-
params.set('search', search.trim());
|
|
428
|
-
const res = await request<{
|
|
429
|
-
data: Array<{ id: number; name: string }>;
|
|
430
|
-
total: number;
|
|
431
|
-
lastPage?: number;
|
|
432
|
-
}>({
|
|
433
|
-
url: `/person/accounts?${params}`,
|
|
434
|
-
method: 'GET',
|
|
435
|
-
});
|
|
436
|
-
return {
|
|
437
|
-
items: res.data.data ?? [],
|
|
438
|
-
hasMore: page < (res.data.lastPage ?? 1),
|
|
439
|
-
};
|
|
440
|
-
}}
|
|
441
|
-
getOptionValue={(opt) => (opt as any).id}
|
|
442
|
-
getOptionLabel={(opt) => (opt as any).name ?? ''}
|
|
443
|
-
createFields={[
|
|
444
|
-
{
|
|
445
|
-
name: 'name',
|
|
446
|
-
label: t('empresa.createFieldLabel'),
|
|
447
|
-
placeholder: t(
|
|
448
|
-
'empresa.createFieldPlaceholder'
|
|
449
|
-
),
|
|
450
|
-
required: true,
|
|
451
|
-
},
|
|
452
|
-
]}
|
|
453
|
-
mapSearchToCreateValues={(search) => ({
|
|
454
|
-
name: search,
|
|
455
|
-
})}
|
|
456
|
-
onCreate={async (values) => {
|
|
457
|
-
const res = await request<{
|
|
458
|
-
id: number;
|
|
459
|
-
name: string;
|
|
460
|
-
}>({
|
|
461
|
-
url: '/person/accounts',
|
|
462
|
-
method: 'POST',
|
|
463
|
-
data: {
|
|
464
|
-
name: (values.name ?? '').trim(),
|
|
465
|
-
status: 'active',
|
|
466
|
-
},
|
|
467
|
-
});
|
|
468
|
-
const created = res.data as unknown as {
|
|
469
|
-
id: number;
|
|
470
|
-
name: string;
|
|
471
|
-
};
|
|
472
|
-
// immediately save the new company link
|
|
473
|
-
await handleCompanySave(created.id);
|
|
474
|
-
return created;
|
|
475
|
-
}}
|
|
476
|
-
/>
|
|
477
|
-
<div className="mt-4 flex justify-end gap-2">
|
|
478
|
-
<Button
|
|
479
|
-
variant="outline"
|
|
480
|
-
size="sm"
|
|
481
|
-
onClick={() => {
|
|
482
|
-
setCompanyPickerOpen(false);
|
|
483
|
-
setCompanyPickerValue(null);
|
|
484
|
-
}}
|
|
485
|
-
>
|
|
486
|
-
{t('actions.cancel')}
|
|
487
|
-
</Button>
|
|
488
|
-
<Button
|
|
489
|
-
size="sm"
|
|
490
|
-
disabled={!companyPickerValue || savingCompany}
|
|
491
|
-
onClick={() =>
|
|
492
|
-
handleCompanySave(companyPickerValue as number)
|
|
496
|
+
<Card className="border-border/60">
|
|
497
|
+
<CardContent className="space-y-3 p-4">
|
|
498
|
+
<div className="flex items-center justify-between">
|
|
499
|
+
<p className="text-sm font-medium">
|
|
500
|
+
Atividades recentes
|
|
501
|
+
</p>
|
|
502
|
+
<Badge
|
|
503
|
+
variant={
|
|
504
|
+
overview?.kpis.portalEnabled
|
|
505
|
+
? 'default'
|
|
506
|
+
: 'outline'
|
|
493
507
|
}
|
|
494
508
|
>
|
|
495
|
-
{
|
|
496
|
-
?
|
|
497
|
-
:
|
|
498
|
-
</
|
|
509
|
+
{overview?.kpis.portalEnabled
|
|
510
|
+
? 'Portal Hcode Training ativo'
|
|
511
|
+
: 'Sem acesso ao portal'}
|
|
512
|
+
</Badge>
|
|
513
|
+
</div>
|
|
514
|
+
<div className="space-y-3">
|
|
515
|
+
{(overview?.activities ?? [])
|
|
516
|
+
.slice(0, 8)
|
|
517
|
+
.map((activity) => (
|
|
518
|
+
<div
|
|
519
|
+
key={activity.id}
|
|
520
|
+
className="rounded-md border border-border/70 px-3 py-2"
|
|
521
|
+
>
|
|
522
|
+
<div className="flex items-center justify-between gap-2">
|
|
523
|
+
<p className="text-sm font-medium">
|
|
524
|
+
{activity.title}
|
|
525
|
+
</p>
|
|
526
|
+
<span className="text-[11px] text-muted-foreground">
|
|
527
|
+
{new Date(
|
|
528
|
+
activity.createdAt
|
|
529
|
+
).toLocaleString('pt-BR')}
|
|
530
|
+
</span>
|
|
531
|
+
</div>
|
|
532
|
+
<p className="mt-0.5 text-xs text-muted-foreground">
|
|
533
|
+
{activity.description}
|
|
534
|
+
</p>
|
|
535
|
+
</div>
|
|
536
|
+
))}
|
|
537
|
+
{(overview?.activities ?? []).length === 0 && (
|
|
538
|
+
<p className="text-sm text-muted-foreground">
|
|
539
|
+
Sem registros recentes.
|
|
540
|
+
</p>
|
|
541
|
+
)}
|
|
499
542
|
</div>
|
|
500
543
|
</CardContent>
|
|
501
544
|
</Card>
|
|
502
|
-
|
|
545
|
+
</div>
|
|
546
|
+
|
|
547
|
+
{/* ── Danger Zone ──────────────────────────────────────── */}
|
|
548
|
+
<Card className="border-destructive/40">
|
|
549
|
+
<CardContent className="p-4">
|
|
550
|
+
<div className="mb-3 flex items-center gap-2">
|
|
551
|
+
<AlertTriangle className="h-4 w-4 text-destructive" />
|
|
552
|
+
<span className="text-sm font-semibold text-destructive">
|
|
553
|
+
Zona de perigo
|
|
554
|
+
</span>
|
|
555
|
+
</div>
|
|
556
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
|
557
|
+
<div className="space-y-1">
|
|
558
|
+
<p className="text-sm font-medium">
|
|
559
|
+
Excluir conta enterprise
|
|
560
|
+
</p>
|
|
561
|
+
<p className="text-xs text-muted-foreground">
|
|
562
|
+
Esta ação é irreversível. Todos os dados desta
|
|
563
|
+
conta — turmas, cursos, alunos e histórico — serão
|
|
564
|
+
permanentemente removidos.
|
|
565
|
+
</p>
|
|
566
|
+
</div>
|
|
567
|
+
<Button
|
|
568
|
+
variant="destructive"
|
|
569
|
+
size="sm"
|
|
570
|
+
className="shrink-0"
|
|
571
|
+
onClick={() => {
|
|
572
|
+
setDeleteConfirmText('');
|
|
573
|
+
setDeleteDialogOpen(true);
|
|
574
|
+
}}
|
|
575
|
+
>
|
|
576
|
+
<Trash2 className="mr-2 h-4 w-4" />
|
|
577
|
+
Excluir conta
|
|
578
|
+
</Button>
|
|
579
|
+
</div>
|
|
580
|
+
</CardContent>
|
|
581
|
+
</Card>
|
|
503
582
|
</TabsContent>
|
|
504
583
|
|
|
505
584
|
<TabsContent value="classes" className="mt-4">
|
|
@@ -536,6 +615,61 @@ export function EnterpriseDetailSheet({
|
|
|
536
615
|
</SheetContent>
|
|
537
616
|
</Sheet>
|
|
538
617
|
|
|
618
|
+
{/* ── Delete Confirmation Dialog ── */}
|
|
619
|
+
<AlertDialog
|
|
620
|
+
open={deleteDialogOpen}
|
|
621
|
+
onOpenChange={(v) => {
|
|
622
|
+
if (!v) setDeleteConfirmText('');
|
|
623
|
+
setDeleteDialogOpen(v);
|
|
624
|
+
}}
|
|
625
|
+
>
|
|
626
|
+
<AlertDialogContent>
|
|
627
|
+
<AlertDialogHeader>
|
|
628
|
+
<AlertDialogTitle className="flex items-center gap-2 text-destructive">
|
|
629
|
+
<AlertTriangle className="h-5 w-5" />
|
|
630
|
+
Excluir conta enterprise
|
|
631
|
+
</AlertDialogTitle>
|
|
632
|
+
<AlertDialogDescription asChild>
|
|
633
|
+
<div className="space-y-3 text-sm text-muted-foreground">
|
|
634
|
+
<p>
|
|
635
|
+
Esta ação é <strong>irreversível</strong>. Todos os dados
|
|
636
|
+
desta conta — turmas, cursos, alunos e histórico — serão
|
|
637
|
+
permanentemente removidos.
|
|
638
|
+
</p>
|
|
639
|
+
<p>
|
|
640
|
+
Para confirmar, digite o nome da conta:{' '}
|
|
641
|
+
<strong className="text-foreground">
|
|
642
|
+
{currentAccount?.name}
|
|
643
|
+
</strong>
|
|
644
|
+
</p>
|
|
645
|
+
<Input
|
|
646
|
+
value={deleteConfirmText}
|
|
647
|
+
onChange={(e) => setDeleteConfirmText(e.target.value)}
|
|
648
|
+
placeholder={currentAccount?.name ?? ''}
|
|
649
|
+
autoComplete="off"
|
|
650
|
+
autoCorrect="off"
|
|
651
|
+
spellCheck={false}
|
|
652
|
+
/>
|
|
653
|
+
</div>
|
|
654
|
+
</AlertDialogDescription>
|
|
655
|
+
</AlertDialogHeader>
|
|
656
|
+
<AlertDialogFooter>
|
|
657
|
+
<AlertDialogCancel disabled={isDeleting}>
|
|
658
|
+
Cancelar
|
|
659
|
+
</AlertDialogCancel>
|
|
660
|
+
<Button
|
|
661
|
+
variant="destructive"
|
|
662
|
+
disabled={
|
|
663
|
+
deleteConfirmText !== (currentAccount?.name ?? '') || isDeleting
|
|
664
|
+
}
|
|
665
|
+
onClick={() => void handleDeleteAccount()}
|
|
666
|
+
>
|
|
667
|
+
{isDeleting ? 'Excluindo…' : 'Excluir definitivamente'}
|
|
668
|
+
</Button>
|
|
669
|
+
</AlertDialogFooter>
|
|
670
|
+
</AlertDialogContent>
|
|
671
|
+
</AlertDialog>
|
|
672
|
+
|
|
539
673
|
{/* ── Edit Sheet (slides over the detail sheet) ── */}
|
|
540
674
|
<EnterpriseSheet
|
|
541
675
|
open={editSheetOpen}
|