@hed-hog/lms 0.0.350 → 0.0.353

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 (160) hide show
  1. package/dist/certificate/certificate.controller.d.ts +2 -2
  2. package/dist/certificate/certificate.controller.d.ts.map +1 -1
  3. package/dist/certificate/certificate.controller.js +8 -6
  4. package/dist/certificate/certificate.controller.js.map +1 -1
  5. package/dist/certificate/certificate.service.d.ts +5 -2
  6. package/dist/certificate/certificate.service.d.ts.map +1 -1
  7. package/dist/certificate/certificate.service.js +70 -6
  8. package/dist/certificate/certificate.service.js.map +1 -1
  9. package/dist/course/course-structure.controller.d.ts +24 -10
  10. package/dist/course/course-structure.controller.d.ts.map +1 -1
  11. package/dist/course/course-structure.controller.js +23 -2
  12. package/dist/course/course-structure.controller.js.map +1 -1
  13. package/dist/course/course-structure.service.d.ts +16 -8
  14. package/dist/course/course-structure.service.d.ts.map +1 -1
  15. package/dist/course/course-structure.service.js +61 -30
  16. package/dist/course/course-structure.service.js.map +1 -1
  17. package/dist/course/course-video-conversion.service.d.ts +37 -0
  18. package/dist/course/course-video-conversion.service.d.ts.map +1 -0
  19. package/dist/course/course-video-conversion.service.js +308 -0
  20. package/dist/course/course-video-conversion.service.js.map +1 -0
  21. package/dist/course/course.controller.d.ts +17 -0
  22. package/dist/course/course.controller.d.ts.map +1 -1
  23. package/dist/course/course.controller.js +23 -0
  24. package/dist/course/course.controller.js.map +1 -1
  25. package/dist/course/course.module.d.ts.map +1 -1
  26. package/dist/course/course.module.js +15 -2
  27. package/dist/course/course.module.js.map +1 -1
  28. package/dist/course/course.service.d.ts +15 -0
  29. package/dist/course/course.service.d.ts.map +1 -1
  30. package/dist/course/course.service.js +103 -49
  31. package/dist/course/course.service.js.map +1 -1
  32. package/dist/course/dto/create-course-structure-lesson.dto.d.ts +5 -1
  33. package/dist/course/dto/create-course-structure-lesson.dto.d.ts.map +1 -1
  34. package/dist/course/dto/create-course-structure-lesson.dto.js +16 -2
  35. package/dist/course/dto/create-course-structure-lesson.dto.js.map +1 -1
  36. package/dist/course/dto/create-course.dto.d.ts +1 -0
  37. package/dist/course/dto/create-course.dto.d.ts.map +1 -1
  38. package/dist/course/dto/create-course.dto.js +9 -0
  39. package/dist/course/dto/create-course.dto.js.map +1 -1
  40. package/dist/enterprise/enterprise.controller.d.ts +3 -3
  41. package/dist/enterprise/enterprise.controller.d.ts.map +1 -1
  42. package/dist/enterprise/enterprise.controller.js +0 -1
  43. package/dist/enterprise/enterprise.controller.js.map +1 -1
  44. package/dist/enterprise/enterprise.service.d.ts +3 -3
  45. package/dist/evaluation/evaluation.service.d.ts.map +1 -1
  46. package/dist/evaluation/evaluation.service.js +9 -2
  47. package/dist/evaluation/evaluation.service.js.map +1 -1
  48. package/dist/index.d.ts +1 -0
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +1 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/lms.module.d.ts.map +1 -1
  53. package/dist/lms.module.js +3 -0
  54. package/dist/lms.module.js.map +1 -1
  55. package/dist/video-resolution-profile/dto/create-video-resolution-profile.dto.d.ts +6 -0
  56. package/dist/video-resolution-profile/dto/create-video-resolution-profile.dto.d.ts.map +1 -0
  57. package/dist/video-resolution-profile/dto/create-video-resolution-profile.dto.js +33 -0
  58. package/dist/video-resolution-profile/dto/create-video-resolution-profile.dto.js.map +1 -0
  59. package/dist/video-resolution-profile/dto/update-video-resolution-profile.dto.d.ts +6 -0
  60. package/dist/video-resolution-profile/dto/update-video-resolution-profile.dto.d.ts.map +1 -0
  61. package/dist/video-resolution-profile/dto/update-video-resolution-profile.dto.js +33 -0
  62. package/dist/video-resolution-profile/dto/update-video-resolution-profile.dto.js.map +1 -0
  63. package/dist/video-resolution-profile/video-resolution-profile.controller.d.ts +38 -0
  64. package/dist/video-resolution-profile/video-resolution-profile.controller.d.ts.map +1 -0
  65. package/dist/video-resolution-profile/video-resolution-profile.controller.js +89 -0
  66. package/dist/video-resolution-profile/video-resolution-profile.controller.js.map +1 -0
  67. package/dist/video-resolution-profile/video-resolution-profile.mcp-tools.d.ts +26 -0
  68. package/dist/video-resolution-profile/video-resolution-profile.mcp-tools.d.ts.map +1 -0
  69. package/dist/video-resolution-profile/video-resolution-profile.mcp-tools.js +160 -0
  70. package/dist/video-resolution-profile/video-resolution-profile.mcp-tools.js.map +1 -0
  71. package/dist/video-resolution-profile/video-resolution-profile.module.d.ts +3 -0
  72. package/dist/video-resolution-profile/video-resolution-profile.module.d.ts.map +1 -0
  73. package/dist/video-resolution-profile/video-resolution-profile.module.js +26 -0
  74. package/dist/video-resolution-profile/video-resolution-profile.module.js.map +1 -0
  75. package/dist/video-resolution-profile/video-resolution-profile.service.d.ts +45 -0
  76. package/dist/video-resolution-profile/video-resolution-profile.service.d.ts.map +1 -0
  77. package/dist/video-resolution-profile/video-resolution-profile.service.js +117 -0
  78. package/dist/video-resolution-profile/video-resolution-profile.service.js.map +1 -0
  79. package/hedhog/data/menu.yaml +17 -0
  80. package/hedhog/data/route.yaml +133 -0
  81. package/hedhog/data/video_resolution_profile.yaml +7 -0
  82. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +269 -324
  83. package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +124 -70
  84. package/hedhog/frontend/app/_components/create-lms-instructor-sheet.tsx.ejs +7 -4
  85. package/hedhog/frontend/app/_components/create-lms-person-sheet.tsx.ejs +2 -2
  86. package/hedhog/frontend/app/_components/create-lms-student-person-sheet.tsx.ejs +2 -2
  87. package/hedhog/frontend/app/_lib/editor/templateSerializer.ts.ejs +34 -4
  88. package/hedhog/frontend/app/_lib/editor/types.ts.ejs +28 -3
  89. package/hedhog/frontend/app/achievements/page.tsx.ejs +9 -3
  90. package/hedhog/frontend/app/bitcodes/page.tsx.ejs +9 -3
  91. package/hedhog/frontend/app/certificates/issued/page.tsx.ejs +7 -3
  92. package/hedhog/frontend/app/certificates/models/CanvasStage.tsx.ejs +29 -8
  93. package/hedhog/frontend/app/certificates/models/LeftPanel.tsx.ejs +14 -0
  94. package/hedhog/frontend/app/certificates/models/RightPanel.tsx.ejs +194 -9
  95. package/hedhog/frontend/app/certificates/models/page.tsx.ejs +15 -5
  96. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +9 -5
  97. package/hedhog/frontend/app/classes/page.tsx.ejs +73 -47
  98. package/hedhog/frontend/app/courses/[id]/_components/CourseCertificateCard.tsx.ejs +19 -9
  99. package/hedhog/frontend/app/courses/[id]/_components/CourseClassificationCard.tsx.ejs +24 -1
  100. package/hedhog/frontend/app/courses/[id]/_components/CourseContentCard.tsx.ejs +1 -1
  101. package/hedhog/frontend/app/courses/[id]/_components/CourseMainInfoCard.tsx.ejs +1 -1
  102. package/hedhog/frontend/app/courses/[id]/_components/CourseRelationsCard.tsx.ejs +28 -16
  103. package/hedhog/frontend/app/courses/[id]/_components/CourseSectionCard.tsx.ejs +11 -6
  104. package/hedhog/frontend/app/courses/[id]/_components/CourseSummaryCard.tsx.ejs +7 -4
  105. package/hedhog/frontend/app/courses/[id]/_components/course-edit-types.ts.ejs +1 -0
  106. package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +24 -87
  107. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +892 -411
  108. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +1004 -293
  109. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-session.tsx.ejs +11 -11
  110. package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +62 -52
  111. package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +2 -0
  112. package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +19 -6
  113. package/hedhog/frontend/app/courses/[id]/structure/_data/services/course-structure.service.ts.ejs +86 -1
  114. package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +3 -0
  115. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +1 -0
  116. package/hedhog/frontend/app/courses/page.tsx.ejs +112 -89
  117. package/hedhog/frontend/app/enterprise/[id]/page.tsx.ejs +1 -1
  118. package/hedhog/frontend/app/enterprise/_components/enterprise-admin-create-sheet.tsx.ejs +10 -3
  119. package/hedhog/frontend/app/enterprise/_components/enterprise-detail-sheet.tsx.ejs +8 -4
  120. package/hedhog/frontend/app/enterprise/_components/enterprise-person-edit-sheet.tsx.ejs +2 -2
  121. package/hedhog/frontend/app/enterprise/_components/enterprise-sheet.tsx.ejs +10 -4
  122. package/hedhog/frontend/app/enterprise/_components/enterprise-student-create-sheet.tsx.ejs +10 -3
  123. package/hedhog/frontend/app/enterprise/_components/enterprise-user-create-sheet.tsx.ejs +10 -3
  124. package/hedhog/frontend/app/evaluations/_components/evaluation-topic-form-sheet.tsx.ejs +10 -3
  125. package/hedhog/frontend/app/exams/[id]/questions/page.tsx.ejs +23 -9
  126. package/hedhog/frontend/app/exams/page.tsx.ejs +14 -6
  127. package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +9 -3
  128. package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +190 -17
  129. package/hedhog/frontend/app/layout.tsx.ejs +5 -1
  130. package/hedhog/frontend/app/paths/page.tsx.ejs +13 -5
  131. package/hedhog/frontend/app/reports/evaluations/page.tsx.ejs +10 -10
  132. package/hedhog/frontend/app/training/page.tsx.ejs +13 -5
  133. package/hedhog/frontend/app/video-resolution-profiles/page.tsx.ejs +607 -0
  134. package/hedhog/frontend/messages/en.json +250 -9
  135. package/hedhog/frontend/messages/pt.json +250 -9
  136. package/hedhog/table/course.yaml +4 -0
  137. package/hedhog/table/course_lesson_file.yaml +8 -0
  138. package/hedhog/table/course_video_resolution_profile.yaml +22 -0
  139. package/hedhog/table/video_resolution_profile.yaml +18 -0
  140. package/package.json +9 -8
  141. package/src/certificate/certificate.controller.ts +19 -14
  142. package/src/certificate/certificate.service.ts +106 -11
  143. package/src/course/course-structure.controller.ts +24 -2
  144. package/src/course/course-structure.service.ts +21 -4
  145. package/src/course/course-video-conversion.service.ts +415 -0
  146. package/src/course/course.controller.ts +18 -0
  147. package/src/course/course.module.ts +15 -2
  148. package/src/course/course.service.ts +72 -2
  149. package/src/course/dto/create-course-structure-lesson.dto.ts +13 -2
  150. package/src/course/dto/create-course.dto.ts +8 -0
  151. package/src/enterprise/enterprise.controller.ts +0 -1
  152. package/src/evaluation/evaluation.service.ts +9 -2
  153. package/src/index.ts +1 -0
  154. package/src/lms.module.ts +3 -0
  155. package/src/video-resolution-profile/dto/create-video-resolution-profile.dto.ts +16 -0
  156. package/src/video-resolution-profile/dto/update-video-resolution-profile.dto.ts +16 -0
  157. package/src/video-resolution-profile/video-resolution-profile.controller.ts +62 -0
  158. package/src/video-resolution-profile/video-resolution-profile.mcp-tools.ts +128 -0
  159. package/src/video-resolution-profile/video-resolution-profile.module.ts +13 -0
  160. package/src/video-resolution-profile/video-resolution-profile.service.ts +117 -0
@@ -31,12 +31,12 @@ import {
31
31
  } from '@/components/ui/select';
32
32
  import {
33
33
  Sheet,
34
- SheetContent,
35
34
  SheetDescription,
36
35
  SheetFooter,
37
36
  SheetHeader,
38
37
  SheetTitle,
39
38
  } from '@/components/ui/sheet';
39
+ import { ResizableSheetContent } from '@/components/ui/resizable-sheet-content';
40
40
  import { Textarea } from '@/components/ui/textarea';
41
41
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
42
42
  import {
@@ -227,10 +227,16 @@ export function getCourseSheetSchema(t: (key: string) => string) {
227
227
  slug: z
228
228
  .string()
229
229
  .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, t('form.validation.slugPattern')),
230
+ code: z
231
+ .string()
232
+ .regex(/^[A-Z0-9]{2,}$/, t('form.validation.editionCodePattern'))
233
+ .or(z.literal(''))
234
+ .optional(),
230
235
  tituloComercial: z.string().optional(),
231
236
  descricao: z.string().optional(),
232
237
  nivel: z.enum(['iniciante', 'intermediario', 'avancado']).optional(),
233
238
  status: z.enum(['ativo', 'rascunho', 'arquivado']).optional(),
239
+ offeringType: z.enum(['on_demand', 'agendado', 'hibrido']).optional(),
234
240
  categorias: z.array(z.string()).optional(),
235
241
  operationsProjectId: z.string().optional(),
236
242
  primaryColor: z.string().optional(),
@@ -242,10 +248,12 @@ export function getCourseSheetSchema(t: (key: string) => string) {
242
248
  export type CourseSheetFormValues = {
243
249
  nomeInterno: string;
244
250
  slug: string;
251
+ code: string;
245
252
  tituloComercial: string;
246
253
  descricao: string;
247
254
  nivel: 'iniciante' | 'intermediario' | 'avancado';
248
255
  status: 'ativo' | 'rascunho' | 'arquivado';
256
+ offeringType: 'on_demand' | 'agendado' | 'hibrido';
249
257
  categorias: string[];
250
258
  operationsProjectId: string;
251
259
  primaryColor: string;
@@ -256,10 +264,12 @@ export type CourseSheetFormValues = {
256
264
  export const DEFAULT_COURSE_FORM_VALUES: CourseSheetFormValues = {
257
265
  nomeInterno: '',
258
266
  slug: '',
267
+ code: '',
259
268
  tituloComercial: '',
260
269
  descricao: '',
261
270
  nivel: 'iniciante',
262
271
  status: 'rascunho',
272
+ offeringType: 'on_demand',
263
273
  categorias: [],
264
274
  operationsProjectId: '',
265
275
  primaryColor: '#1D4ED8',
@@ -439,7 +449,11 @@ export function CourseFormSheet({
439
449
 
440
450
  return (
441
451
  <Sheet open={open} onOpenChange={onOpenChange}>
442
- <SheetContent
452
+ <ResizableSheetContent
453
+ sheetId="lms-course-form-sheet"
454
+ defaultWidth={560}
455
+ minWidth={420}
456
+ maxWidth={920}
443
457
  side="right"
444
458
  className="flex w-full flex-col sm:max-w-lg overflow-y-auto"
445
459
  >
@@ -456,61 +470,80 @@ export function CourseFormSheet({
456
470
 
457
471
  <form
458
472
  onSubmit={form.handleSubmit(onSubmit)}
459
- className="flex flex-1 flex-col gap-5 py-6 px-4"
473
+ className="flex flex-1 flex-col gap-4 py-4 px-4"
460
474
  >
461
- <Field>
462
- <FieldLabel htmlFor="nomeInterno">
463
- {t('form.fields.internalName.label')}{' '}
464
- <span className="text-destructive">*</span>
465
- </FieldLabel>
466
- <Input
467
- id="nomeInterno"
468
- placeholder={t('form.fields.internalName.placeholder')}
469
- {...form.register('nomeInterno')}
470
- />
471
- <FieldDescription>
472
- {t('form.fields.internalName.description')}
473
- </FieldDescription>
474
- <FieldError>
475
- {form.formState.errors.nomeInterno?.message}
476
- </FieldError>
477
- </Field>
475
+ {/* Internal name + Slug */}
476
+ <div className="grid grid-cols-2 gap-3">
477
+ <Field>
478
+ <FieldLabel htmlFor="nomeInterno">
479
+ {t('form.fields.internalName.label')}{' '}
480
+ <span className="text-destructive">*</span>
481
+ </FieldLabel>
482
+ <Input
483
+ id="nomeInterno"
484
+ placeholder={t('form.fields.internalName.placeholder')}
485
+ {...form.register('nomeInterno')}
486
+ />
487
+ <FieldError>
488
+ {form.formState.errors.nomeInterno?.message}
489
+ </FieldError>
490
+ </Field>
491
+
492
+ <Field>
493
+ <FieldLabel htmlFor="slug">
494
+ {t('form.fields.slug.label')}{' '}
495
+ <span className="text-destructive">*</span>
496
+ </FieldLabel>
497
+ <Input
498
+ id="slug"
499
+ placeholder={t('form.fields.slug.placeholder')}
500
+ {...form.register('slug', {
501
+ onChange: () => {
502
+ slugTouchedRef.current = true;
503
+ },
504
+ })}
505
+ />
506
+ <FieldError>{form.formState.errors.slug?.message}</FieldError>
507
+ </Field>
508
+ </div>
478
509
 
510
+ {/* Edition code */}
479
511
  <Field>
480
- <FieldLabel htmlFor="slug">
481
- {t('form.fields.slug.label')}{' '}
482
- <span className="text-destructive">*</span>
512
+ <FieldLabel htmlFor="code">
513
+ {t('form.fields.code.label')}
483
514
  </FieldLabel>
484
515
  <Input
485
- id="slug"
486
- placeholder={t('form.fields.slug.placeholder')}
487
- {...form.register('slug', {
488
- onChange: () => {
489
- slugTouchedRef.current = true;
516
+ id="code"
517
+ placeholder={t('form.fields.code.placeholder')}
518
+ className="font-mono"
519
+ {...form.register('code', {
520
+ onChange: (e) => {
521
+ const cleaned = e.target.value
522
+ .toUpperCase()
523
+ .replace(/[^A-Z0-9]/g, '');
524
+ form.setValue('code', cleaned, { shouldValidate: true });
490
525
  },
491
526
  })}
492
527
  />
493
- <FieldDescription>
494
- {t('form.fields.slug.description')}
495
- </FieldDescription>
496
- <FieldError>{form.formState.errors.slug?.message}</FieldError>
528
+ <FieldError>{form.formState.errors.code?.message}</FieldError>
497
529
  </Field>
498
530
 
531
+ {/* Logo */}
499
532
  <Field>
500
533
  <FieldLabel>{t('form.fields.logo.label')}</FieldLabel>
501
- <div className="flex items-start gap-4">
534
+ <div className="flex items-center gap-3">
502
535
  {logoPreviewUrl ? (
503
536
  <img
504
537
  src={logoPreviewUrl}
505
538
  alt={t('form.fields.logo.alt')}
506
- className="size-16 shrink-0 rounded-lg border object-cover"
539
+ className="size-12 shrink-0 rounded-lg border object-cover"
507
540
  />
508
541
  ) : (
509
- <div className="flex size-16 shrink-0 items-center justify-center rounded-lg border border-dashed bg-muted/40">
510
- <ImageIcon className="size-6 text-muted-foreground/50" />
542
+ <div className="flex size-12 shrink-0 items-center justify-center rounded-lg border border-dashed bg-muted/40">
543
+ <ImageIcon className="size-5 text-muted-foreground/50" />
511
544
  </div>
512
545
  )}
513
- <div className="flex flex-col gap-2">
546
+ <div className="flex items-center gap-2">
514
547
  <input
515
548
  ref={logoInputRef}
516
549
  type="file"
@@ -547,40 +580,69 @@ export function CourseFormSheet({
547
580
  {t('form.fields.logo.remove')}
548
581
  </Button>
549
582
  )}
583
+ {colorExtracting && (
584
+ <span className="flex items-center gap-1 text-xs text-muted-foreground">
585
+ <Loader2 className="size-3 animate-spin" />
586
+ Detectando cores...
587
+ </span>
588
+ )}
550
589
  </div>
551
590
  </div>
552
- <FieldDescription>
553
- {t('form.fields.logo.description')}
554
- </FieldDescription>
555
591
  </Field>
556
592
 
557
- <Field>
558
- <FieldLabel htmlFor="tituloComercial">
559
- {t('form.fields.commercialTitle.label')}
560
- </FieldLabel>
561
- <Input
562
- id="tituloComercial"
563
- placeholder={t('form.fields.commercialTitle.placeholder')}
564
- {...form.register('tituloComercial')}
565
- />
566
- <FieldError>
567
- {form.formState.errors.tituloComercial?.message}
568
- </FieldError>
569
- </Field>
593
+ {/* Commercial title + Offering type */}
594
+ <div className="grid grid-cols-2 gap-3">
595
+ <Field>
596
+ <FieldLabel htmlFor="tituloComercial">
597
+ {t('form.fields.commercialTitle.label')}
598
+ </FieldLabel>
599
+ <Input
600
+ id="tituloComercial"
601
+ placeholder={t('form.fields.commercialTitle.placeholder')}
602
+ {...form.register('tituloComercial')}
603
+ />
604
+ <FieldError>
605
+ {form.formState.errors.tituloComercial?.message}
606
+ </FieldError>
607
+ </Field>
570
608
 
609
+ <Field>
610
+ <FieldLabel>Tipo do curso</FieldLabel>
611
+ <Controller
612
+ name="offeringType"
613
+ control={form.control}
614
+ render={({ field }) => (
615
+ <Select onValueChange={field.onChange} value={field.value}>
616
+ <SelectTrigger>
617
+ <SelectValue placeholder="Selecione o tipo" />
618
+ </SelectTrigger>
619
+ <SelectContent>
620
+ <SelectItem value="on_demand">On Demand</SelectItem>
621
+ <SelectItem value="agendado">Agendado</SelectItem>
622
+ <SelectItem value="hibrido">Híbrido</SelectItem>
623
+ </SelectContent>
624
+ </Select>
625
+ )}
626
+ />
627
+ <FieldError>{form.formState.errors.offeringType?.message}</FieldError>
628
+ </Field>
629
+ </div>
630
+
631
+ {/* Description */}
571
632
  <Field>
572
633
  <FieldLabel htmlFor="descricao">
573
634
  {t('form.fields.description.label')}
574
635
  </FieldLabel>
575
636
  <Textarea
576
637
  id="descricao"
577
- rows={3}
638
+ rows={2}
578
639
  placeholder={t('form.fields.description.placeholder')}
579
640
  {...form.register('descricao')}
580
641
  />
581
642
  <FieldError>{form.formState.errors.descricao?.message}</FieldError>
582
643
  </Field>
583
644
 
645
+ {/* Operations project */}
584
646
  <Field>
585
647
  <FieldLabel>Projeto vinculado</FieldLabel>
586
648
  <Controller
@@ -608,16 +670,13 @@ export function CourseFormSheet({
608
670
  />
609
671
  )}
610
672
  />
611
- <FieldDescription>
612
- Quando o curso estiver vinculado a um projeto, cada nova aula gera
613
- uma tarefa automaticamente.
614
- </FieldDescription>
615
673
  <FieldError>
616
674
  {form.formState.errors.operationsProjectId?.message}
617
675
  </FieldError>
618
676
  </Field>
619
677
 
620
- <div className="grid grid-cols-2 gap-4">
678
+ {/* Level + Status */}
679
+ <div className="grid grid-cols-2 gap-3">
621
680
  <Field>
622
681
  <FieldLabel>{t('form.fields.level.label')}</FieldLabel>
623
682
  <Controller
@@ -677,6 +736,7 @@ export function CourseFormSheet({
677
736
  </Field>
678
737
  </div>
679
738
 
739
+ {/* Categories */}
680
740
  <Field>
681
741
  <FieldLabel>{t('form.fields.categories.label')}</FieldLabel>
682
742
  <Controller
@@ -831,14 +891,8 @@ export function CourseFormSheet({
831
891
  <FieldError>{form.formState.errors.categorias?.message}</FieldError>
832
892
  </Field>
833
893
 
834
- {colorExtracting && (
835
- <div className="flex items-center gap-1.5 text-xs text-muted-foreground">
836
- <Loader2 className="size-3 animate-spin" />
837
- Detectando cores...
838
- </div>
839
- )}
840
-
841
- <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
894
+ {/* Primary + Secondary colors */}
895
+ <div className="grid grid-cols-2 gap-3">
842
896
  <Field>
843
897
  <FieldLabel htmlFor="primaryColor">
844
898
  {t('form.fields.primaryColor.label')}
@@ -851,7 +905,7 @@ export function CourseFormSheet({
851
905
  <Input
852
906
  id="primaryColor"
853
907
  type="color"
854
- className="h-10 w-16 p-1"
908
+ className="h-9 w-12 p-1"
855
909
  value={field.value}
856
910
  onChange={field.onChange}
857
911
  />
@@ -880,7 +934,7 @@ export function CourseFormSheet({
880
934
  <Input
881
935
  id="secondaryColor"
882
936
  type="color"
883
- className="h-10 w-16 p-1"
937
+ className="h-9 w-12 p-1"
884
938
  value={field.value}
885
939
  onChange={field.onChange}
886
940
  />
@@ -905,7 +959,7 @@ export function CourseFormSheet({
905
959
  </Button>
906
960
  </SheetFooter>
907
961
  </form>
908
- </SheetContent>
962
+ </ResizableSheetContent>
909
963
  </Sheet>
910
964
  );
911
965
  }
@@ -12,12 +12,12 @@ import { Input } from '@/components/ui/input';
12
12
  import { Progress } from '@/components/ui/progress';
13
13
  import {
14
14
  Sheet,
15
- SheetContent,
16
15
  SheetDescription,
17
16
  SheetFooter,
18
17
  SheetHeader,
19
18
  SheetTitle,
20
19
  } from '@/components/ui/sheet';
20
+ import { ResizableSheetContent } from '@/components/ui/resizable-sheet-content';
21
21
  import { Switch } from '@/components/ui/switch';
22
22
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
23
23
  import { zodResolver } from '@hookform/resolvers/zod';
@@ -402,7 +402,11 @@ export function CreateLmsInstructorSheet({
402
402
 
403
403
  return (
404
404
  <Sheet open={open} onOpenChange={handleSheetOpenChange}>
405
- <SheetContent
405
+ <ResizableSheetContent
406
+ sheetId="lms-create-instructor-sheet"
407
+ defaultWidth={560}
408
+ minWidth={420}
409
+ maxWidth={920}
406
410
  side="right"
407
411
  className="flex w-full flex-col sm:max-w-lg overflow-y-auto"
408
412
  >
@@ -413,7 +417,6 @@ export function CreateLmsInstructorSheet({
413
417
 
414
418
  {isEditing && loadingInstructorDetails ? (
415
419
  <div className="flex items-center gap-2 px-4 py-10 text-sm text-muted-foreground">
416
- <Loader2 className="size-4 animate-spin" />
417
420
  Carregando dados do instrutor...
418
421
  </div>
419
422
  ) : (
@@ -585,7 +588,7 @@ export function CreateLmsInstructorSheet({
585
588
  </SheetFooter>
586
589
  </form>
587
590
  )}
588
- </SheetContent>
591
+ </ResizableSheetContent>
589
592
  </Sheet>
590
593
  );
591
594
  }
@@ -1,11 +1,11 @@
1
1
  'use client';
2
2
 
3
- import { PersonFormSheet } from '@/app/(app)/(libraries)/contact/person/_components/person-form-sheet';
3
+ import { PersonFormSheet } from '@/app/(app)/(libraries)/crm/person/_components/person-form-sheet';
4
4
  import type {
5
5
  ContactTypeOption,
6
6
  DocumentTypeOption,
7
7
  Person,
8
- } from '@/app/(app)/(libraries)/contact/person/_components/person-types';
8
+ } from '@/app/(app)/(libraries)/crm/person/_components/person-types';
9
9
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
10
10
  import { useTranslations } from 'next-intl';
11
11
  import { useMemo } from 'react';
@@ -1,11 +1,11 @@
1
1
  'use client';
2
2
 
3
- import { PersonFormSheet } from '@/app/(app)/(libraries)/contact/person/_components/person-form-sheet';
3
+ import { PersonFormSheet } from '@/app/(app)/(libraries)/crm/person/_components/person-form-sheet';
4
4
  import type {
5
5
  ContactTypeOption,
6
6
  DocumentTypeOption,
7
7
  Person,
8
- } from '@/app/(app)/(libraries)/contact/person/_components/person-types';
8
+ } from '@/app/(app)/(libraries)/crm/person/_components/person-types';
9
9
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
10
10
  import { useTranslations } from 'next-intl';
11
11
 
@@ -105,7 +105,20 @@ export function fabricObjToFull(fObj: any): TemplateObject {
105
105
 
106
106
  /* image */
107
107
  if (type === 'image') {
108
- obj.image = { src: fObj._tplImageSrc ?? '' };
108
+ const rawFileId = fObj._tplImageFileId;
109
+ const rawResizeMode = fObj._tplImageResizeMode;
110
+ const resizeMode =
111
+ rawResizeMode === 'contain' ||
112
+ rawResizeMode === 'cover' ||
113
+ rawResizeMode === 'stretch' ||
114
+ rawResizeMode === 'center'
115
+ ? rawResizeMode
116
+ : 'contain';
117
+ const fileId =
118
+ typeof rawFileId === 'number' && Number.isFinite(rawFileId)
119
+ ? rawFileId
120
+ : null;
121
+ obj.image = { file_id: fileId, resizeMode };
109
122
  }
110
123
 
111
124
  return obj;
@@ -219,17 +232,34 @@ export function createFabricFromTemplate(
219
232
  : 2,
220
233
  });
221
234
  } else if (tObj.type === 'image') {
235
+ const isCoursePlaceholder =
236
+ tObj.key === 'courseLogo' || tObj.key === 'courseBanner';
222
237
  fObj = new fabric.Rect({
223
238
  ...common,
224
- width: w || 150,
225
- height: h || 150,
239
+ width: w || (tObj.key === 'courseBanner' ? 420 : 150),
240
+ height: h || (tObj.key === 'courseBanner' ? 140 : 150),
226
241
  fill: '#e2e8f0',
227
242
  stroke: '#cbd5e1',
228
243
  strokeWidth: 1,
244
+ strokeDashArray: isCoursePlaceholder ? [8, 4] : undefined,
229
245
  rx: 4,
230
246
  ry: 4,
231
247
  });
232
- fObj._tplImageSrc = tObj.image?.src ?? '';
248
+ fObj._tplImageFileId =
249
+ typeof tObj.image?.file_id === 'number' ? tObj.image.file_id : null;
250
+ fObj._tplImageResizeMode =
251
+ tObj.image?.resizeMode === 'cover' ||
252
+ tObj.image?.resizeMode === 'stretch' ||
253
+ tObj.image?.resizeMode === 'center'
254
+ ? tObj.image.resizeMode
255
+ : 'contain';
256
+ fObj._tplImageSrc =
257
+ typeof tObj.image?.file_id === 'number' && tObj.image.file_id > 0
258
+ ? `/file/open/${tObj.image.file_id}`
259
+ : '';
260
+ if (isCoursePlaceholder) {
261
+ fObj._tplImagePlaceholder = true;
262
+ }
233
263
  }
234
264
 
235
265
  if (fObj) {
@@ -30,9 +30,11 @@ export interface TemplateObject {
30
30
  stroke?: { color: string; widthPct: number };
31
31
  shadow?: { xPct: number; yPct: number; blurPct: number; color: string };
32
32
  };
33
- image?: { src: string };
33
+ image?: { file_id: number | null; resizeMode?: ImageResizeMode };
34
34
  }
35
35
 
36
+ export type ImageResizeMode = 'contain' | 'cover' | 'stretch' | 'center';
37
+
36
38
  export interface Template {
37
39
  version: 1;
38
40
  name: string;
@@ -53,6 +55,10 @@ export const FIELD_KEYS = [
53
55
  'endDate',
54
56
  'workloadHours',
55
57
  'certificateId',
58
+ 'enrollmentDate',
59
+ 'issueDate',
60
+ 'instructorName',
61
+ 'qrCode',
56
62
  ] as const;
57
63
 
58
64
  export type FieldKey = (typeof FIELD_KEYS)[number];
@@ -64,6 +70,10 @@ export const FIELD_LABELS: Record<FieldKey, string> = {
64
70
  endDate: 'Data Fim',
65
71
  workloadHours: 'Carga Horária',
66
72
  certificateId: 'ID Certificado',
73
+ enrollmentDate: 'Data de Matrícula',
74
+ issueDate: 'Data de Emissão',
75
+ instructorName: 'Nome do Instrutor',
76
+ qrCode: 'QR Code',
67
77
  };
68
78
 
69
79
  export const FONT_FAMILIES = ['Inter', 'Montserrat', 'Roboto'] as const;
@@ -79,15 +89,30 @@ export function createDefaultTemplate(): Template {
79
89
  }
80
90
 
81
91
  export function getObjectLabel(obj: TemplateObject): string {
92
+ const fieldLabelMap: Record<string, string> = {
93
+ studentName: 'Nome do Aluno',
94
+ courseName: 'Nome do Curso',
95
+ startDate: 'Data Início',
96
+ endDate: 'Data Fim',
97
+ workloadHours: 'Carga Horária',
98
+ certificateId: 'ID Certificado',
99
+ enrollmentDate: 'Data de Matrícula',
100
+ issueDate: 'Data de Emissão',
101
+ instructorName: 'Nome do Instrutor',
102
+ qrCode: 'QR Code',
103
+ courseLogo: 'Logo do Curso',
104
+ courseBanner: 'Banner do Curso',
105
+ };
106
+
82
107
  switch (obj.type) {
83
108
  case 'field':
84
- return `Field: ${obj.key ?? '?'}`;
109
+ return `Field: ${fieldLabelMap[obj.key ?? ''] ?? obj.key ?? '?'}`;
85
110
  case 'staticText':
86
111
  return `Text: ${(obj.text ?? 'Texto').slice(0, 20)}`;
87
112
  case 'shape':
88
113
  return obj.shape === 'line' ? 'Line' : 'Rect';
89
114
  case 'image':
90
- return 'Image';
115
+ return obj.key ? `Image: ${fieldLabelMap[obj.key] ?? obj.key}` : 'Image';
91
116
  default:
92
117
  return 'Object';
93
118
  }
@@ -44,10 +44,10 @@ import {
44
44
  } from '@/components/ui/select';
45
45
  import {
46
46
  Sheet,
47
- SheetContent,
48
47
  SheetHeader,
49
48
  SheetTitle,
50
49
  } from '@/components/ui/sheet';
50
+ import { ResizableSheetContent } from '@/components/ui/resizable-sheet-content';
51
51
  import { Skeleton } from '@/components/ui/skeleton';
52
52
  import {
53
53
  Table,
@@ -370,7 +370,13 @@ function AchievementFormSheet({
370
370
 
371
371
  return (
372
372
  <Sheet open={open} onOpenChange={onOpenChange}>
373
- <SheetContent className="w-full overflow-y-auto sm:max-w-2xl">
373
+ <ResizableSheetContent
374
+ sheetId="lms-achievements-form-sheet"
375
+ defaultWidth={672}
376
+ minWidth={420}
377
+ maxWidth={960}
378
+ className="w-full overflow-y-auto"
379
+ >
374
380
  <SheetHeader>
375
381
  <SheetTitle>
376
382
  {achievementToEdit ? t('sheet.editTitle') : t('sheet.createTitle')}
@@ -532,7 +538,7 @@ function AchievementFormSheet({
532
538
  </div>
533
539
  </form>
534
540
  </Form>
535
- </SheetContent>
541
+ </ResizableSheetContent>
536
542
  </Sheet>
537
543
  );
538
544
  }
@@ -45,11 +45,11 @@ import {
45
45
  } from '@/components/ui/select';
46
46
  import {
47
47
  Sheet,
48
- SheetContent,
49
48
  SheetDescription,
50
49
  SheetHeader,
51
50
  SheetTitle,
52
51
  } from '@/components/ui/sheet';
52
+ import { ResizableSheetContent } from '@/components/ui/resizable-sheet-content';
53
53
  import { Skeleton } from '@/components/ui/skeleton';
54
54
  import {
55
55
  Table,
@@ -364,7 +364,13 @@ function WalletFormSheet({
364
364
  return (
365
365
  <>
366
366
  <Sheet open={open} onOpenChange={onOpenChange}>
367
- <SheetContent className="w-full overflow-y-auto sm:max-w-4xl">
367
+ <ResizableSheetContent
368
+ sheetId="lms-bitcodes-wallet-sheet"
369
+ defaultWidth={896}
370
+ minWidth={560}
371
+ maxWidth={1200}
372
+ className="w-full overflow-y-auto"
373
+ >
368
374
  <SheetHeader>
369
375
  <SheetTitle>
370
376
  {walletToEdit ? t('sheet.editTitle') : t('sheet.createTitle')}
@@ -680,7 +686,7 @@ function WalletFormSheet({
680
686
  ) : null
681
687
  ) : null}
682
688
  </div>
683
- </SheetContent>
689
+ </ResizableSheetContent>
684
690
  </Sheet>
685
691
  </>
686
692
  );
@@ -21,12 +21,12 @@ import {
21
21
  } from '@/components/ui/select';
22
22
  import {
23
23
  Sheet,
24
- SheetContent,
25
24
  SheetDescription,
26
25
  SheetFooter,
27
26
  SheetHeader,
28
27
  SheetTitle,
29
28
  } from '@/components/ui/sheet';
29
+ import { ResizableSheetContent } from '@/components/ui/resizable-sheet-content';
30
30
  import { Skeleton } from '@/components/ui/skeleton';
31
31
  import { Switch } from '@/components/ui/switch';
32
32
  import { Textarea } from '@/components/ui/textarea';
@@ -616,7 +616,11 @@ export default function IssuedCertificatesPage() {
616
616
  ) : null}
617
617
 
618
618
  <Sheet open={isTemplateSheetOpen} onOpenChange={setIsTemplateSheetOpen}>
619
- <SheetContent
619
+ <ResizableSheetContent
620
+ sheetId="lms-certificates-issued-create-template-sheet"
621
+ defaultWidth={560}
622
+ minWidth={420}
623
+ maxWidth={920}
620
624
  side="right"
621
625
  className="flex w-full flex-col sm:max-w-lg overflow-y-auto"
622
626
  >
@@ -695,7 +699,7 @@ export default function IssuedCertificatesPage() {
695
699
  {t('sheet.submit')}
696
700
  </Button>
697
701
  </SheetFooter>
698
- </SheetContent>
702
+ </ResizableSheetContent>
699
703
  </Sheet>
700
704
  </Page>
701
705
  );