@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.
Files changed (128) hide show
  1. package/dist/class-group/class-group.controller.d.ts +3 -3
  2. package/dist/class-group/class-group.service.d.ts +3 -3
  3. package/dist/course/course.service.d.ts.map +1 -1
  4. package/dist/course/course.service.js +12 -20
  5. package/dist/course/course.service.js.map +1 -1
  6. package/dist/enterprise/enterprise.controller.d.ts +72 -0
  7. package/dist/enterprise/enterprise.controller.d.ts.map +1 -1
  8. package/dist/enterprise/enterprise.controller.js +10 -0
  9. package/dist/enterprise/enterprise.controller.js.map +1 -1
  10. package/dist/enterprise/enterprise.service.d.ts +78 -0
  11. package/dist/enterprise/enterprise.service.d.ts.map +1 -1
  12. package/dist/enterprise/enterprise.service.js +413 -40
  13. package/dist/enterprise/enterprise.service.js.map +1 -1
  14. package/dist/enterprise/training/training-admin.controller.d.ts +6 -3
  15. package/dist/enterprise/training/training-admin.controller.d.ts.map +1 -1
  16. package/dist/enterprise/training/training-admin.controller.js +10 -6
  17. package/dist/enterprise/training/training-admin.controller.js.map +1 -1
  18. package/dist/enterprise/training/training-admin.service.d.ts +8 -2
  19. package/dist/enterprise/training/training-admin.service.d.ts.map +1 -1
  20. package/dist/enterprise/training/training-admin.service.js +108 -52
  21. package/dist/enterprise/training/training-admin.service.js.map +1 -1
  22. package/dist/enterprise/training/training-viewer.controller.d.ts +3 -0
  23. package/dist/enterprise/training/training-viewer.controller.d.ts.map +1 -1
  24. package/dist/evaluation/evaluation.controller.d.ts +4 -4
  25. package/dist/evaluation/evaluation.service.d.ts +4 -4
  26. package/dist/instructor/dto/create-instructor-skill.dto.d.ts +0 -4
  27. package/dist/instructor/dto/create-instructor-skill.dto.d.ts.map +1 -1
  28. package/dist/instructor/dto/create-instructor-skill.dto.js +0 -21
  29. package/dist/instructor/dto/create-instructor-skill.dto.js.map +1 -1
  30. package/dist/instructor/dto/update-instructor-skill.dto.d.ts +0 -4
  31. package/dist/instructor/dto/update-instructor-skill.dto.d.ts.map +1 -1
  32. package/dist/instructor/dto/update-instructor-skill.dto.js +0 -22
  33. package/dist/instructor/dto/update-instructor-skill.dto.js.map +1 -1
  34. package/dist/instructor/instructor-skill.controller.d.ts +4 -4
  35. package/dist/instructor/instructor-skill.service.d.ts +4 -7
  36. package/dist/instructor/instructor-skill.service.d.ts.map +1 -1
  37. package/dist/instructor/instructor-skill.service.js +2 -89
  38. package/dist/instructor/instructor-skill.service.js.map +1 -1
  39. package/dist/instructor/instructor.controller.d.ts +20 -0
  40. package/dist/instructor/instructor.controller.d.ts.map +1 -1
  41. package/dist/instructor/instructor.controller.js +19 -0
  42. package/dist/instructor/instructor.controller.js.map +1 -1
  43. package/dist/instructor/instructor.service.d.ts +25 -0
  44. package/dist/instructor/instructor.service.d.ts.map +1 -1
  45. package/dist/instructor/instructor.service.js +70 -18
  46. package/dist/instructor/instructor.service.js.map +1 -1
  47. package/dist/lms.module.d.ts.map +1 -1
  48. package/dist/lms.module.js.map +1 -1
  49. package/hedhog/data/route.yaml +23 -1
  50. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +42 -24
  51. package/hedhog/frontend/app/_components/create-lms-instructor-sheet.tsx.ejs +591 -0
  52. package/hedhog/frontend/app/certificates/issued/page.tsx.ejs +6 -1
  53. package/hedhog/frontend/app/certificates/models/page.tsx.ejs +7 -2
  54. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +17 -17
  55. package/hedhog/frontend/app/classes/page.tsx.ejs +6 -1
  56. package/hedhog/frontend/app/courses/[id]/_components/CourseClassificationCard.tsx.ejs +3 -33
  57. package/hedhog/frontend/app/courses/[id]/_components/CourseContentCard.tsx.ejs +9 -9
  58. package/hedhog/frontend/app/courses/[id]/_components/CourseMainInfoCard.tsx.ejs +109 -0
  59. package/hedhog/frontend/app/courses/[id]/_components/CourseMultiEntityPicker.tsx.ejs +42 -15
  60. package/hedhog/frontend/app/courses/[id]/_components/CourseRelationsCard.tsx.ejs +76 -81
  61. package/hedhog/frontend/app/courses/[id]/_components/CourseSummaryCard.tsx.ejs +60 -0
  62. package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +3 -3
  63. package/hedhog/frontend/app/courses/[id]/structure/_components/course-scheduled-classes-tab.tsx.ejs +406 -0
  64. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +1 -1
  65. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +134 -0
  66. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-course.tsx.ejs +113 -0
  67. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +314 -0
  68. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-session.tsx.ejs +174 -0
  69. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +242 -33
  70. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +228 -152
  71. package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +185 -0
  72. package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +71 -31
  73. package/hedhog/frontend/app/courses/page.tsx.ejs +6 -1
  74. package/hedhog/frontend/app/enterprise/[id]/page.tsx.ejs +37 -41
  75. package/hedhog/frontend/app/enterprise/_components/enterprise-activity-timeline.tsx.ejs +87 -0
  76. package/hedhog/frontend/app/enterprise/_components/enterprise-admin-create-sheet.tsx.ejs +4 -0
  77. package/hedhog/frontend/app/enterprise/_components/enterprise-administrators-tab.tsx.ejs +31 -5
  78. package/hedhog/frontend/app/enterprise/_components/enterprise-classes-tab.tsx.ejs +79 -20
  79. package/hedhog/frontend/app/enterprise/_components/enterprise-company-identity-card.tsx.ejs +11 -2
  80. package/hedhog/frontend/app/enterprise/_components/enterprise-course-edit-sheet.tsx.ejs +201 -0
  81. package/hedhog/frontend/app/enterprise/_components/enterprise-courses-tab.tsx.ejs +55 -24
  82. package/hedhog/frontend/app/enterprise/_components/enterprise-detail-sheet.tsx.ejs +430 -296
  83. package/hedhog/frontend/app/enterprise/_components/enterprise-mocks.ts.ejs +277 -0
  84. package/hedhog/frontend/app/enterprise/_components/enterprise-overview-analytics.tsx.ejs +205 -0
  85. package/hedhog/frontend/app/enterprise/_components/enterprise-person-edit-sheet.tsx.ejs +97 -0
  86. package/hedhog/frontend/app/enterprise/_components/enterprise-sheet.tsx.ejs +82 -57
  87. package/hedhog/frontend/app/enterprise/_components/enterprise-student-create-sheet.tsx.ejs +4 -0
  88. package/hedhog/frontend/app/enterprise/_components/enterprise-students-tab.tsx.ejs +60 -22
  89. package/hedhog/frontend/app/enterprise/_components/enterprise-types.ts.ejs +54 -0
  90. package/hedhog/frontend/app/enterprise/_components/enterprise-user-create-sheet.tsx.ejs +211 -0
  91. package/hedhog/frontend/app/enterprise/page.tsx.ejs +39 -7
  92. package/hedhog/frontend/app/evaluations/_components/evaluation-topic-form-sheet.tsx.ejs +1 -1
  93. package/hedhog/frontend/app/exams/[id]/questions/page.tsx.ejs +6 -1
  94. package/hedhog/frontend/app/exams/page.tsx.ejs +12 -3
  95. package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +51 -104
  96. package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +712 -427
  97. package/hedhog/frontend/app/instructors/page.tsx.ejs +77 -53
  98. package/hedhog/frontend/app/paths/page.tsx.ejs +14 -5
  99. package/hedhog/frontend/app/reports/courses/page.tsx.ejs +5 -5
  100. package/hedhog/frontend/app/reports/dashboard/page.tsx.ejs +8 -8
  101. package/hedhog/frontend/app/reports/evaluations/page.tsx.ejs +6 -1
  102. package/hedhog/frontend/app/reports/page.tsx.ejs +7 -7
  103. package/hedhog/frontend/app/reports/students/page.tsx.ejs +6 -6
  104. package/hedhog/frontend/app/training/page.tsx.ejs +8 -3
  105. package/hedhog/frontend/messages/en.json +394 -55
  106. package/hedhog/frontend/messages/pt.json +389 -48
  107. package/hedhog/frontend/widgets/active-classes-kpi.tsx.ejs +1 -1
  108. package/hedhog/frontend/widgets/active-courses-kpi.tsx.ejs +1 -1
  109. package/hedhog/frontend/widgets/approval-rate-kpi.tsx.ejs +1 -1
  110. package/hedhog/frontend/widgets/class-calendar.tsx.ejs +2 -2
  111. package/hedhog/frontend/widgets/completion-rate-kpi.tsx.ejs +1 -1
  112. package/hedhog/frontend/widgets/issued-certificates-kpi.tsx.ejs +1 -1
  113. package/hedhog/frontend/widgets/total-students-kpi.tsx.ejs +1 -1
  114. package/hedhog/table/enterprise_student_license_event.yaml +30 -0
  115. package/hedhog/table/instructor_qualification.yaml +1 -1
  116. package/hedhog/table/instructor_skill.yaml +0 -11
  117. package/package.json +8 -8
  118. package/src/course/course.service.ts +12 -24
  119. package/src/enterprise/enterprise.controller.ts +5 -0
  120. package/src/enterprise/enterprise.service.ts +507 -29
  121. package/src/enterprise/training/training-admin.controller.ts +4 -0
  122. package/src/enterprise/training/training-admin.service.ts +115 -51
  123. package/src/instructor/dto/create-instructor-skill.dto.ts +0 -17
  124. package/src/instructor/dto/update-instructor-skill.dto.ts +0 -18
  125. package/src/instructor/instructor-skill.service.ts +2 -97
  126. package/src/instructor/instructor.controller.ts +16 -0
  127. package/src/instructor/instructor.service.ts +85 -10
  128. 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
+ }