@hed-hog/lms 0.0.331 → 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/course-form-sheet.tsx.ejs +3 -3
- 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 +6 -1
- 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 +40 -13
- 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]/structure/_components/course-scheduled-classes-tab.tsx.ejs +406 -0
- 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/mock-data.ts.ejs +185 -0
- package/hedhog/frontend/app/courses/page.tsx.ejs +6 -1
- 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/exams/[id]/questions/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/exams/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +51 -104
- package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +625 -366
- package/hedhog/frontend/app/instructors/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/paths/page.tsx.ejs +9 -4
- package/hedhog/frontend/app/reports/evaluations/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/training/page.tsx.ejs +9 -4
- package/hedhog/frontend/messages/en.json +101 -10
- package/hedhog/frontend/messages/pt.json +101 -10
- package/hedhog/table/enterprise_student_license_event.yaml +30 -0
- package/hedhog/table/instructor_skill.yaml +0 -11
- package/package.json +7 -7
- 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
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EnterpriseAccount,
|
|
3
|
+
EnterpriseClass,
|
|
4
|
+
EnterpriseCourse,
|
|
5
|
+
EnterpriseUser,
|
|
6
|
+
} from './enterprise-types';
|
|
7
|
+
|
|
8
|
+
// ── CRM Accounts (picker options) ─────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
export const MOCK_CRM_ACCOUNTS = [
|
|
11
|
+
{ id: 101, name: 'Techcorp Brasil Ltda' },
|
|
12
|
+
{ id: 102, name: 'Grupo Atlas S.A.' },
|
|
13
|
+
{ id: 103, name: 'Horizonte Engenharia Ltda' },
|
|
14
|
+
{ id: 104, name: 'MedPlus Saúde S.A.' },
|
|
15
|
+
{ id: 105, name: 'Agro Prime Comércio Ltda' },
|
|
16
|
+
{ id: 106, name: 'LogTech Transportes Ltda' },
|
|
17
|
+
{ id: 107, name: 'Inova Tech S.A.' },
|
|
18
|
+
{ id: 108, name: 'Dinâmica Consultoria Ltda' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
// ── Accounts ───────────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
export const MOCK_ENTERPRISE_ACCOUNTS: EnterpriseAccount[] = [
|
|
24
|
+
{
|
|
25
|
+
id: 1,
|
|
26
|
+
name: 'Techcorp Brasil',
|
|
27
|
+
slug: 'techcorp-brasil',
|
|
28
|
+
status: 'active',
|
|
29
|
+
crmAccountId: 101,
|
|
30
|
+
crmAccountName: 'Techcorp Brasil Ltda',
|
|
31
|
+
portalEnabled: true,
|
|
32
|
+
usersCount: 120,
|
|
33
|
+
studentsCount: 104,
|
|
34
|
+
managersCount: 14,
|
|
35
|
+
adminsCount: 2,
|
|
36
|
+
coursesCount: 8,
|
|
37
|
+
classesCount: 3,
|
|
38
|
+
licenseLimit: 150,
|
|
39
|
+
notes: null,
|
|
40
|
+
createdAt: '2026-01-01T10:00:00Z',
|
|
41
|
+
updatedAt: '2026-04-10T08:30:00Z',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 2,
|
|
45
|
+
name: 'Grupo Financeiro Atlas',
|
|
46
|
+
slug: 'grupo-financeiro-atlas',
|
|
47
|
+
status: 'active',
|
|
48
|
+
crmAccountId: 102,
|
|
49
|
+
crmAccountName: 'Grupo Atlas S.A.',
|
|
50
|
+
portalEnabled: true,
|
|
51
|
+
usersCount: 250,
|
|
52
|
+
studentsCount: 220,
|
|
53
|
+
managersCount: 28,
|
|
54
|
+
adminsCount: 2,
|
|
55
|
+
coursesCount: 14,
|
|
56
|
+
classesCount: 5,
|
|
57
|
+
licenseLimit: 300,
|
|
58
|
+
notes: null,
|
|
59
|
+
createdAt: '2026-02-01T09:00:00Z',
|
|
60
|
+
updatedAt: '2026-04-15T11:00:00Z',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: 3,
|
|
64
|
+
name: 'Construtora Horizonte',
|
|
65
|
+
slug: 'construtora-horizonte',
|
|
66
|
+
status: 'active',
|
|
67
|
+
crmAccountId: null,
|
|
68
|
+
crmAccountName: null,
|
|
69
|
+
portalEnabled: false,
|
|
70
|
+
usersCount: 60,
|
|
71
|
+
studentsCount: 52,
|
|
72
|
+
managersCount: 6,
|
|
73
|
+
adminsCount: 1,
|
|
74
|
+
coursesCount: 4,
|
|
75
|
+
classesCount: 2,
|
|
76
|
+
licenseLimit: 80,
|
|
77
|
+
notes: null,
|
|
78
|
+
createdAt: '2026-03-01T08:00:00Z',
|
|
79
|
+
updatedAt: '2026-04-01T09:00:00Z',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 4,
|
|
83
|
+
name: 'MedPlus Saúde',
|
|
84
|
+
slug: 'medplus-saude',
|
|
85
|
+
status: 'trial',
|
|
86
|
+
crmAccountId: 104,
|
|
87
|
+
crmAccountName: 'MedPlus Saúde S.A.',
|
|
88
|
+
portalEnabled: true,
|
|
89
|
+
usersCount: 30,
|
|
90
|
+
studentsCount: 28,
|
|
91
|
+
managersCount: 2,
|
|
92
|
+
adminsCount: 1,
|
|
93
|
+
coursesCount: 3,
|
|
94
|
+
classesCount: 1,
|
|
95
|
+
licenseLimit: 50,
|
|
96
|
+
notes: null,
|
|
97
|
+
createdAt: '2026-04-01T11:00:00Z',
|
|
98
|
+
updatedAt: '2026-04-18T14:00:00Z',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: 5,
|
|
102
|
+
name: 'Agro Prime',
|
|
103
|
+
slug: 'agro-prime',
|
|
104
|
+
status: 'inactive',
|
|
105
|
+
crmAccountId: null,
|
|
106
|
+
crmAccountName: null,
|
|
107
|
+
portalEnabled: false,
|
|
108
|
+
usersCount: 0,
|
|
109
|
+
studentsCount: 0,
|
|
110
|
+
managersCount: 0,
|
|
111
|
+
adminsCount: 0,
|
|
112
|
+
coursesCount: 0,
|
|
113
|
+
classesCount: 0,
|
|
114
|
+
licenseLimit: null,
|
|
115
|
+
notes: null,
|
|
116
|
+
createdAt: '2025-09-01T14:00:00Z',
|
|
117
|
+
updatedAt: '2026-02-28T18:00:00Z',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: 6,
|
|
121
|
+
name: 'LogTech Transportes',
|
|
122
|
+
slug: 'logtech-transportes',
|
|
123
|
+
status: 'active',
|
|
124
|
+
crmAccountId: 106,
|
|
125
|
+
crmAccountName: 'LogTech Transportes Ltda',
|
|
126
|
+
portalEnabled: true,
|
|
127
|
+
usersCount: 100,
|
|
128
|
+
studentsCount: 88,
|
|
129
|
+
managersCount: 10,
|
|
130
|
+
adminsCount: 2,
|
|
131
|
+
coursesCount: 6,
|
|
132
|
+
classesCount: 2,
|
|
133
|
+
licenseLimit: 120,
|
|
134
|
+
notes: null,
|
|
135
|
+
createdAt: '2026-01-15T09:30:00Z',
|
|
136
|
+
updatedAt: '2026-04-12T10:00:00Z',
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
// ── Users (linked to account id=1) ─────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
export const MOCK_ENTERPRISE_USERS: EnterpriseUser[] = [
|
|
143
|
+
{
|
|
144
|
+
id: 1,
|
|
145
|
+
userId: 201,
|
|
146
|
+
name: 'Ana Beatriz Souza',
|
|
147
|
+
email: 'ana.souza@techcorp.com.br',
|
|
148
|
+
role: 'enterprise_admin',
|
|
149
|
+
status: 'active',
|
|
150
|
+
lastAccessAt: '2026-04-20T14:32:00Z',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: 2,
|
|
154
|
+
userId: 202,
|
|
155
|
+
name: 'Carlos Eduardo Lima',
|
|
156
|
+
email: 'carlos.lima@techcorp.com.br',
|
|
157
|
+
role: 'hr_manager',
|
|
158
|
+
status: 'active',
|
|
159
|
+
lastAccessAt: '2026-04-19T09:15:00Z',
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
id: 3,
|
|
163
|
+
userId: 203,
|
|
164
|
+
name: 'Fernanda Oliveira',
|
|
165
|
+
email: 'fernanda.oliveira@techcorp.com.br',
|
|
166
|
+
role: 'student',
|
|
167
|
+
status: 'active',
|
|
168
|
+
lastAccessAt: '2026-04-18T16:00:00Z',
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
id: 4,
|
|
172
|
+
userId: 204,
|
|
173
|
+
name: 'Ricardo Mendes',
|
|
174
|
+
email: 'ricardo.mendes@techcorp.com.br',
|
|
175
|
+
role: 'student',
|
|
176
|
+
status: 'active',
|
|
177
|
+
lastAccessAt: '2026-04-17T11:45:00Z',
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: 5,
|
|
181
|
+
userId: 205,
|
|
182
|
+
name: 'Juliana Castro',
|
|
183
|
+
email: 'juliana.castro@techcorp.com.br',
|
|
184
|
+
role: 'viewer',
|
|
185
|
+
status: 'inactive',
|
|
186
|
+
lastAccessAt: null,
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
id: 6,
|
|
190
|
+
userId: 206,
|
|
191
|
+
name: 'Marcos Vinicius Santos',
|
|
192
|
+
email: 'marcos.santos@techcorp.com.br',
|
|
193
|
+
role: 'student',
|
|
194
|
+
status: 'pending',
|
|
195
|
+
lastAccessAt: null,
|
|
196
|
+
},
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
// ── Courses (linked to account id=1) ──────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
export const MOCK_ENTERPRISE_COURSES: EnterpriseCourse[] = [
|
|
202
|
+
{
|
|
203
|
+
id: 10,
|
|
204
|
+
title: 'Liderança e Gestão de Equipes',
|
|
205
|
+
slug: 'lideranca-gestao-equipes',
|
|
206
|
+
status: 'published',
|
|
207
|
+
modality: 'Online',
|
|
208
|
+
enrolledCount: 45,
|
|
209
|
+
completionRate: 72,
|
|
210
|
+
contractedAt: '2026-01-05T00:00:00Z',
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
id: 11,
|
|
214
|
+
title: 'Compliance e Ética Corporativa',
|
|
215
|
+
slug: 'compliance-etica-corporativa',
|
|
216
|
+
status: 'published',
|
|
217
|
+
modality: 'Híbrido',
|
|
218
|
+
enrolledCount: 104,
|
|
219
|
+
completionRate: 88,
|
|
220
|
+
contractedAt: '2026-01-05T00:00:00Z',
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: 12,
|
|
224
|
+
title: 'Segurança da Informação',
|
|
225
|
+
slug: 'seguranca-informacao',
|
|
226
|
+
status: 'published',
|
|
227
|
+
modality: 'Online',
|
|
228
|
+
enrolledCount: 98,
|
|
229
|
+
completionRate: 61,
|
|
230
|
+
contractedAt: '2026-02-01T00:00:00Z',
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
id: 13,
|
|
234
|
+
title: 'Agile & Scrum na Prática',
|
|
235
|
+
slug: 'agile-scrum-pratica',
|
|
236
|
+
status: 'draft',
|
|
237
|
+
modality: 'Presencial',
|
|
238
|
+
enrolledCount: 0,
|
|
239
|
+
completionRate: 0,
|
|
240
|
+
contractedAt: '2026-03-10T00:00:00Z',
|
|
241
|
+
},
|
|
242
|
+
];
|
|
243
|
+
|
|
244
|
+
// ── Classes / Turmas (linked to account id=1) ──────────────────────────────────
|
|
245
|
+
|
|
246
|
+
export const MOCK_ENTERPRISE_CLASSES: EnterpriseClass[] = [
|
|
247
|
+
{
|
|
248
|
+
id: 20,
|
|
249
|
+
code: 'T2026-LG-01',
|
|
250
|
+
courseTitle: 'Liderança e Gestão de Equipes',
|
|
251
|
+
startDate: '2026-03-01',
|
|
252
|
+
endDate: '2026-06-30',
|
|
253
|
+
enrolledCount: 45,
|
|
254
|
+
capacity: 50,
|
|
255
|
+
status: 'ongoing',
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
id: 21,
|
|
259
|
+
code: 'T2026-CE-01',
|
|
260
|
+
courseTitle: 'Compliance e Ética Corporativa',
|
|
261
|
+
startDate: '2026-01-10',
|
|
262
|
+
endDate: '2026-04-10',
|
|
263
|
+
enrolledCount: 104,
|
|
264
|
+
capacity: 120,
|
|
265
|
+
status: 'completed',
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
id: 22,
|
|
269
|
+
code: 'T2026-SI-01',
|
|
270
|
+
courseTitle: 'Segurança da Informação',
|
|
271
|
+
startDate: '2026-04-15',
|
|
272
|
+
endDate: null,
|
|
273
|
+
enrolledCount: 98,
|
|
274
|
+
capacity: 100,
|
|
275
|
+
status: 'open',
|
|
276
|
+
},
|
|
277
|
+
];
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
4
|
+
import { ChartContainer, type ChartConfig } from '@/components/ui/chart';
|
|
5
|
+
import { Activity, Gauge, LineChart as LineChartIcon } from 'lucide-react';
|
|
6
|
+
import {
|
|
7
|
+
Bar,
|
|
8
|
+
BarChart,
|
|
9
|
+
CartesianGrid,
|
|
10
|
+
Cell,
|
|
11
|
+
Line,
|
|
12
|
+
LineChart,
|
|
13
|
+
Pie,
|
|
14
|
+
PieChart,
|
|
15
|
+
XAxis,
|
|
16
|
+
YAxis,
|
|
17
|
+
} from 'recharts';
|
|
18
|
+
import type { EnterpriseOverview } from './enterprise-types';
|
|
19
|
+
|
|
20
|
+
const timelineConfig = {
|
|
21
|
+
used: { label: 'Licencas em uso', color: 'hsl(var(--chart-1))' },
|
|
22
|
+
assigned: { label: 'Entradas', color: 'hsl(var(--chart-2))' },
|
|
23
|
+
revoked: { label: 'Saidas', color: 'hsl(var(--chart-5))' },
|
|
24
|
+
} satisfies ChartConfig;
|
|
25
|
+
|
|
26
|
+
const usageConfig = {
|
|
27
|
+
used: { label: 'Usadas', color: 'hsl(var(--chart-1))' },
|
|
28
|
+
available: { label: 'Disponiveis', color: 'hsl(var(--chart-2))' },
|
|
29
|
+
} satisfies ChartConfig;
|
|
30
|
+
|
|
31
|
+
const seatConfig = {
|
|
32
|
+
value: { label: 'Vagas', color: 'hsl(var(--chart-3))' },
|
|
33
|
+
} satisfies ChartConfig;
|
|
34
|
+
|
|
35
|
+
export function EnterpriseOverviewAnalytics({
|
|
36
|
+
overview,
|
|
37
|
+
}: {
|
|
38
|
+
overview: EnterpriseOverview;
|
|
39
|
+
}) {
|
|
40
|
+
const licenseLimit = overview.licenseUsage.limit;
|
|
41
|
+
const licensePie =
|
|
42
|
+
licenseLimit && licenseLimit > 0
|
|
43
|
+
? [
|
|
44
|
+
{
|
|
45
|
+
key: 'used',
|
|
46
|
+
name: 'Usadas',
|
|
47
|
+
value: overview.licenseUsage.used,
|
|
48
|
+
color: 'hsl(var(--chart-1))',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
key: 'available',
|
|
52
|
+
name: 'Disponiveis',
|
|
53
|
+
value: Math.max(licenseLimit - overview.licenseUsage.used, 0),
|
|
54
|
+
color: 'hsl(var(--chart-2))',
|
|
55
|
+
},
|
|
56
|
+
]
|
|
57
|
+
: [
|
|
58
|
+
{
|
|
59
|
+
key: 'used',
|
|
60
|
+
name: 'Usadas',
|
|
61
|
+
value: overview.licenseUsage.used,
|
|
62
|
+
color: 'hsl(var(--chart-1))',
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
const seatRows = [
|
|
67
|
+
{ name: 'Usadas', value: overview.scheduledSeats.used },
|
|
68
|
+
{ name: 'Abertas', value: overview.scheduledSeats.open },
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div className="grid gap-4 xl:grid-cols-[minmax(0,1.35fr)_minmax(280px,0.65fr)]">
|
|
73
|
+
<Card className="border-border/60">
|
|
74
|
+
<CardHeader className="pb-2">
|
|
75
|
+
<CardTitle className="flex items-center gap-2 text-sm font-medium">
|
|
76
|
+
<LineChartIcon className="h-4 w-4 text-muted-foreground" />
|
|
77
|
+
Uso de licencas no tempo
|
|
78
|
+
</CardTitle>
|
|
79
|
+
</CardHeader>
|
|
80
|
+
<CardContent>
|
|
81
|
+
{overview.licenseTimeline.length === 0 ? (
|
|
82
|
+
<div className="flex h-52 items-center justify-center text-xs text-muted-foreground">
|
|
83
|
+
Sem movimentacao de licencas.
|
|
84
|
+
</div>
|
|
85
|
+
) : (
|
|
86
|
+
<ChartContainer config={timelineConfig} className="h-56 w-full">
|
|
87
|
+
<LineChart data={overview.licenseTimeline}>
|
|
88
|
+
<CartesianGrid vertical={false} strokeDasharray="3 3" />
|
|
89
|
+
<XAxis
|
|
90
|
+
dataKey="label"
|
|
91
|
+
tickLine={false}
|
|
92
|
+
axisLine={false}
|
|
93
|
+
tickMargin={8}
|
|
94
|
+
fontSize={11}
|
|
95
|
+
/>
|
|
96
|
+
<YAxis
|
|
97
|
+
tickLine={false}
|
|
98
|
+
axisLine={false}
|
|
99
|
+
tickMargin={8}
|
|
100
|
+
fontSize={11}
|
|
101
|
+
allowDecimals={false}
|
|
102
|
+
/>
|
|
103
|
+
<Line
|
|
104
|
+
type="monotone"
|
|
105
|
+
dataKey="used"
|
|
106
|
+
stroke="var(--color-used)"
|
|
107
|
+
strokeWidth={2}
|
|
108
|
+
dot={false}
|
|
109
|
+
/>
|
|
110
|
+
</LineChart>
|
|
111
|
+
</ChartContainer>
|
|
112
|
+
)}
|
|
113
|
+
</CardContent>
|
|
114
|
+
</Card>
|
|
115
|
+
|
|
116
|
+
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-1">
|
|
117
|
+
<Card className="border-border/60">
|
|
118
|
+
<CardHeader className="pb-2">
|
|
119
|
+
<CardTitle className="flex items-center gap-2 text-sm font-medium">
|
|
120
|
+
<Gauge className="h-4 w-4 text-muted-foreground" />
|
|
121
|
+
Licencas atuais
|
|
122
|
+
</CardTitle>
|
|
123
|
+
</CardHeader>
|
|
124
|
+
<CardContent className="flex items-center gap-4">
|
|
125
|
+
<div className="relative shrink-0">
|
|
126
|
+
<ChartContainer config={usageConfig} className="h-32 w-32">
|
|
127
|
+
<PieChart>
|
|
128
|
+
<Pie
|
|
129
|
+
data={licensePie}
|
|
130
|
+
innerRadius={38}
|
|
131
|
+
outerRadius={58}
|
|
132
|
+
dataKey="value"
|
|
133
|
+
strokeWidth={2}
|
|
134
|
+
stroke="hsl(var(--background))"
|
|
135
|
+
>
|
|
136
|
+
{licensePie.map((item) => (
|
|
137
|
+
<Cell key={item.key} fill={item.color} />
|
|
138
|
+
))}
|
|
139
|
+
</Pie>
|
|
140
|
+
</PieChart>
|
|
141
|
+
</ChartContainer>
|
|
142
|
+
<div className="pointer-events-none absolute inset-0 flex flex-col items-center justify-center">
|
|
143
|
+
<span className="text-xl font-semibold tabular-nums">
|
|
144
|
+
{overview.licenseUsage.used}
|
|
145
|
+
</span>
|
|
146
|
+
<span className="text-[10px] text-muted-foreground">
|
|
147
|
+
usadas
|
|
148
|
+
</span>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
<div className="min-w-0 text-sm">
|
|
152
|
+
<p className="font-medium">
|
|
153
|
+
{licenseLimit === null
|
|
154
|
+
? 'Sem limite definido'
|
|
155
|
+
: `${overview.licenseUsage.used} / ${licenseLimit}`}
|
|
156
|
+
</p>
|
|
157
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
158
|
+
{licenseLimit === null
|
|
159
|
+
? 'Defina um limite para acompanhar a ocupacao.'
|
|
160
|
+
: `${overview.licenseUsage.percent}% do limite contratado`}
|
|
161
|
+
</p>
|
|
162
|
+
{overview.licenseUsage.available !== null && (
|
|
163
|
+
<p className="mt-2 text-xs font-medium text-emerald-600">
|
|
164
|
+
{overview.licenseUsage.available} disponiveis
|
|
165
|
+
</p>
|
|
166
|
+
)}
|
|
167
|
+
</div>
|
|
168
|
+
</CardContent>
|
|
169
|
+
</Card>
|
|
170
|
+
|
|
171
|
+
<Card className="border-border/60">
|
|
172
|
+
<CardHeader className="pb-2">
|
|
173
|
+
<CardTitle className="flex items-center gap-2 text-sm font-medium">
|
|
174
|
+
<Activity className="h-4 w-4 text-muted-foreground" />
|
|
175
|
+
Vagas em turmas agendadas
|
|
176
|
+
</CardTitle>
|
|
177
|
+
</CardHeader>
|
|
178
|
+
<CardContent>
|
|
179
|
+
<ChartContainer config={seatConfig} className="h-32 w-full">
|
|
180
|
+
<BarChart data={seatRows} layout="vertical">
|
|
181
|
+
<XAxis type="number" hide />
|
|
182
|
+
<YAxis
|
|
183
|
+
dataKey="name"
|
|
184
|
+
type="category"
|
|
185
|
+
tickLine={false}
|
|
186
|
+
axisLine={false}
|
|
187
|
+
fontSize={11}
|
|
188
|
+
width={58}
|
|
189
|
+
/>
|
|
190
|
+
<Bar dataKey="value" radius={4}>
|
|
191
|
+
<Cell fill="hsl(var(--chart-1))" />
|
|
192
|
+
<Cell fill="hsl(var(--chart-2))" />
|
|
193
|
+
</Bar>
|
|
194
|
+
</BarChart>
|
|
195
|
+
</ChartContainer>
|
|
196
|
+
<p className="mt-2 text-xs text-muted-foreground">
|
|
197
|
+
{overview.scheduledSeats.used} usadas, {overview.scheduledSeats.open}{' '}
|
|
198
|
+
abertas de {overview.scheduledSeats.capacity} vagas.
|
|
199
|
+
</p>
|
|
200
|
+
</CardContent>
|
|
201
|
+
</Card>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
4
|
+
import { useMemo } from 'react';
|
|
5
|
+
import { PersonFormSheet } from '../../../contact/person/_components/person-form-sheet';
|
|
6
|
+
import type {
|
|
7
|
+
ContactTypeOption,
|
|
8
|
+
DocumentTypeOption,
|
|
9
|
+
Person,
|
|
10
|
+
} from '../../../contact/person/_components/person-types';
|
|
11
|
+
|
|
12
|
+
export interface EnterprisePersonEditSheetProps {
|
|
13
|
+
open: boolean;
|
|
14
|
+
onOpenChange: (open: boolean) => void;
|
|
15
|
+
personId: number | null;
|
|
16
|
+
onSaved?: (person?: Person) => void | Promise<void>;
|
|
17
|
+
title?: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
allowedTypes?: Array<Person['type']>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function EnterprisePersonEditSheet({
|
|
23
|
+
open,
|
|
24
|
+
onOpenChange,
|
|
25
|
+
personId,
|
|
26
|
+
onSaved,
|
|
27
|
+
title,
|
|
28
|
+
description,
|
|
29
|
+
allowedTypes,
|
|
30
|
+
}: EnterprisePersonEditSheetProps) {
|
|
31
|
+
const { request, currentLocaleCode } = useApp();
|
|
32
|
+
|
|
33
|
+
const { data: contactTypes = [] } = useQuery<ContactTypeOption[]>({
|
|
34
|
+
queryKey: ['enterprise-person-edit-contact-types', currentLocaleCode],
|
|
35
|
+
queryFn: async () => {
|
|
36
|
+
const response = await request<{ data: ContactTypeOption[] }>({
|
|
37
|
+
url: '/person-contact-type?pageSize=100',
|
|
38
|
+
method: 'GET',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return response.data.data ?? [];
|
|
42
|
+
},
|
|
43
|
+
placeholderData: (previous) => previous ?? [],
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const { data: documentTypes = [] } = useQuery<DocumentTypeOption[]>({
|
|
47
|
+
queryKey: ['enterprise-person-edit-document-types', currentLocaleCode],
|
|
48
|
+
queryFn: async () => {
|
|
49
|
+
const response = await request<{ data: DocumentTypeOption[] }>({
|
|
50
|
+
url: '/person-document-type?pageSize=100',
|
|
51
|
+
method: 'GET',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return response.data.data ?? [];
|
|
55
|
+
},
|
|
56
|
+
placeholderData: (previous) => previous ?? [],
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const { data: person } = useQuery<Person | null>({
|
|
60
|
+
queryKey: ['enterprise-person-edit-data', personId],
|
|
61
|
+
enabled: open && typeof personId === 'number',
|
|
62
|
+
queryFn: async () => {
|
|
63
|
+
if (typeof personId !== 'number') return null;
|
|
64
|
+
|
|
65
|
+
const response = await request<Person>({
|
|
66
|
+
url: `/person/${personId}`,
|
|
67
|
+
method: 'GET',
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return ((response as unknown as { data?: Person }).data ??
|
|
71
|
+
(response as unknown as Person)) as Person;
|
|
72
|
+
},
|
|
73
|
+
placeholderData: (previous) => previous ?? null,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const canRenderSheet = useMemo(
|
|
77
|
+
() =>
|
|
78
|
+
open && (personId === null || (typeof personId === 'number' && person)),
|
|
79
|
+
[open, person, personId]
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
if (!canRenderSheet) return null;
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<PersonFormSheet
|
|
86
|
+
open={open}
|
|
87
|
+
person={personId === null ? null : (person ?? null)}
|
|
88
|
+
contactTypes={contactTypes}
|
|
89
|
+
documentTypes={documentTypes}
|
|
90
|
+
onOpenChange={onOpenChange}
|
|
91
|
+
onSuccess={(savedPerson) => onSaved?.(savedPerson)}
|
|
92
|
+
title={title}
|
|
93
|
+
description={description}
|
|
94
|
+
allowedTypes={allowedTypes}
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
97
|
+
}
|