@fayz-ai/plugin-workforce 0.2.0 → 0.2.3

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/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import React3 from 'react';
2
- import { createSafeDataProvider, getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core';
3
- import { createPluginContext, saveSchedule, registerEntityPath, useModuleNavigation, useModuleNavAccess, parseViewId, EntitlementGate, UpgradePrompt, ScheduleEditor, usePermissionOptional, adminNavigateTo, formatCurrency, PersonLink, createCrudPage } from '@fayz-ai/admin';
4
- import { ModulePage, FilterRail, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, MiniCalendar, toLocalISODate, RailWorkspace, Badge, Button, Skeleton, resolveRangePreset, compareCodes, PageToolbar, DateRangeControl, StatTiles, StatementTable, Modal, ModalContent, ModalHeader, ModalTitle, ModalBody, Input, ModalFooter, toast, Popover, PopoverTrigger, PopoverContent, Checkbox, Avatar, AvatarFallback, AvatarImage } from '@fayz-ai/ui';
5
- import { Send, CalendarDays, Plus, ExternalLink, HandCoins, AlertTriangle, Users, CalendarCheck, Unlock, Lock, Clock, LayoutTemplate, ListTree, UserPlus, UserMinus, CakeSlice, Plane, CalendarClock, ShieldAlert, Stethoscope, ClipboardList, Trash2, Pencil, Eye, ImageOff, MapPin, ScanFace } from 'lucide-react';
2
+ import { recordRailFields, RECORD_GROUP, activeFacet, createSafeDataProvider, getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core';
3
+ import { createPluginContext, saveSchedule, registerEntityPath, useModuleNavigation, useModuleNavAccess, parseViewId, EntitlementGate, UpgradePrompt, ScheduleEditor, usePermissionOptional, adminNavigateTo, formatCurrency, PersonLink, createCrudPage, WidgetSlot } from '@fayz-ai/admin';
4
+ import { ModulePage, FilterRail, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, MiniCalendar, toLocalISODate, RailWorkspace, Badge, Button, Skeleton, resolveRangePreset, compareCodes, PageToolbar, DateRangeControl, StatTiles, StatementTable, PageViewBar, ViewTabs, Modal, ModalContent, ModalHeader, ModalTitle, ModalBody, Input, ModalFooter, toast, Popover, PopoverTrigger, PopoverContent, Checkbox, Avatar, AvatarFallback, AvatarImage } from '@fayz-ai/ui';
5
+ import { Send, CalendarDays, Plus, ExternalLink, HandCoins, AlertTriangle, Users, CalendarCheck, Unlock, Lock, Clock, LayoutTemplate, ListTree, UserPlus, UserMinus, CakeSlice, Plane, CalendarClock, ShieldAlert, Stethoscope, ClipboardList, FileText, ChevronRight, Trash2, Pencil, Eye, ImageOff, MapPin, ScanFace } from 'lucide-react';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
7
  import { createStore } from 'zustand/vanilla';
8
8
 
@@ -182,7 +182,14 @@ function SchedulesView() {
182
182
  )
183
183
  ] }) });
184
184
  }
185
- var WEEKDAYS = ["D", "S", "T", "Q", "Q", "S", "S"];
185
+ var WEEKDAYS = ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "S\xE1b"];
186
+ var WEEKDAY_NAMES = ["Domingo", "Segunda", "Ter\xE7a", "Quarta", "Quinta", "Sexta", "S\xE1bado"];
187
+ function plural(n, one, many) {
188
+ return `${n} ${n === 1 ? one : many}`;
189
+ }
190
+ function seats(shift) {
191
+ return shift.requirements.reduce((sum, r) => sum + (r.requiredCount || 0), 0);
192
+ }
186
193
  function ShiftCard({ shift, onChanged }) {
187
194
  const provider = useWorkforceProvider();
188
195
  const [profession, setProfession] = React3.useState("");
@@ -220,7 +227,7 @@ function ShiftCard({ shift, onChanged }) {
220
227
  }
221
228
  return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-border p-3", children: [
222
229
  /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
223
- /* @__PURE__ */ jsxs("div", { children: [
230
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
224
231
  /* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: shift.label }),
225
232
  /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
226
233
  shift.startTime,
@@ -228,10 +235,10 @@ function ShiftCard({ shift, onChanged }) {
228
235
  shift.endTime,
229
236
  shift.crossesMidnight ? " \xB7 vira o dia" : ""
230
237
  ] }),
231
- /* @__PURE__ */ jsx("div", { className: "mt-1 flex gap-1", children: WEEKDAYS.map((d, i) => /* @__PURE__ */ jsx(
238
+ /* @__PURE__ */ jsx("div", { className: "mt-1 flex flex-wrap gap-1", children: WEEKDAYS.map((d, i) => /* @__PURE__ */ jsx(
232
239
  "span",
233
240
  {
234
- className: `inline-flex h-5 w-5 items-center justify-center rounded text-[10px] ${shift.weekdays.includes(i) ? "bg-primary/15 text-primary" : "bg-muted text-muted-foreground"}`,
241
+ className: `inline-flex h-5 items-center justify-center rounded px-1.5 text-[10px] ${shift.weekdays.includes(i) ? "bg-primary/15 text-primary" : "bg-muted text-muted-foreground"}`,
235
242
  children: d
236
243
  },
237
244
  i
@@ -246,8 +253,8 @@ function ShiftCard({ shift, onChanged }) {
246
253
  ")",
247
254
  /* @__PURE__ */ jsx("button", { onClick: () => removeRequirement(r.id), "aria-label": `Remover ${r.professionName}`, children: /* @__PURE__ */ jsx(Trash2, { className: "h-3 w-3", "aria-hidden": true }) })
248
255
  ] }, r.id)) }),
249
- /* @__PURE__ */ jsxs("div", { className: "mt-3 flex items-center gap-2", children: [
250
- /* @__PURE__ */ jsx(
256
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-2", children: [
257
+ /* @__PURE__ */ jsx("div", { className: "w-full sm:w-56", children: /* @__PURE__ */ jsx(
251
258
  Input,
252
259
  {
253
260
  value: profession,
@@ -255,25 +262,24 @@ function ShiftCard({ shift, onChanged }) {
255
262
  placeholder: "Fun\xE7\xE3o (ex.: Gar\xE7om)",
256
263
  className: "h-8"
257
264
  }
258
- ),
259
- /* @__PURE__ */ jsx(
265
+ ) }),
266
+ /* @__PURE__ */ jsx("div", { className: "w-20", children: /* @__PURE__ */ jsx(
260
267
  Input,
261
268
  {
262
269
  value: count,
263
270
  onChange: (e) => setCount(e.target.value),
264
271
  type: "number",
265
272
  min: 1,
266
- className: "h-8 w-20",
273
+ className: "h-8",
267
274
  "aria-label": "Vagas"
268
275
  }
269
- ),
276
+ ) }),
270
277
  /* @__PURE__ */ jsx(Button, { size: "sm", variant: "secondary", onClick: addRequirement, children: "Adicionar" })
271
278
  ] })
272
279
  ] });
273
280
  }
274
- function TemplateCard({ template, onChanged }) {
281
+ function NewShiftModal({ template, open, onOpenChange, onChanged }) {
275
282
  const provider = useWorkforceProvider();
276
- const [open, setOpen] = React3.useState(false);
277
283
  const [label, setLabel] = React3.useState("");
278
284
  const [start, setStart] = React3.useState("11:00");
279
285
  const [end, setEnd] = React3.useState("15:00");
@@ -289,64 +295,92 @@ function TemplateCard({ template, onChanged }) {
289
295
  weekdays,
290
296
  sortOrder: template.shifts.length
291
297
  });
292
- setOpen(false);
298
+ onOpenChange(false);
293
299
  setLabel("");
294
300
  onChanged();
295
301
  } catch (e) {
296
302
  toast.error(e?.message ?? "N\xE3o foi poss\xEDvel salvar o turno.");
297
303
  }
298
304
  }
305
+ return /* @__PURE__ */ jsx(Modal, { open, onOpenChange, children: /* @__PURE__ */ jsxs(ModalContent, { size: "sm", children: [
306
+ /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsx(ModalTitle, { children: "Novo turno" }) }),
307
+ /* @__PURE__ */ jsxs(ModalBody, { className: "space-y-3", children: [
308
+ /* @__PURE__ */ jsx(Input, { value: label, onChange: (e) => setLabel(e.target.value), placeholder: "Nome do turno (ex.: Almo\xE7o)" }),
309
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
310
+ /* @__PURE__ */ jsx(Input, { type: "time", value: start, onChange: (e) => setStart(e.target.value), "aria-label": "In\xEDcio" }),
311
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "at\xE9" }),
312
+ /* @__PURE__ */ jsx(Input, { type: "time", value: end, onChange: (e) => setEnd(e.target.value), "aria-label": "Fim" })
313
+ ] }),
314
+ /* @__PURE__ */ jsxs("div", { children: [
315
+ /* @__PURE__ */ jsx("div", { className: "mb-1 text-xs text-muted-foreground", children: "Dias da semana" }),
316
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: WEEKDAYS.map((d, i) => /* @__PURE__ */ jsx(
317
+ "button",
318
+ {
319
+ type: "button",
320
+ onClick: () => setWeekdays((w) => w.includes(i) ? w.filter((x) => x !== i) : [...w, i]),
321
+ className: `h-8 rounded px-2 text-xs ${weekdays.includes(i) ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"}`,
322
+ children: d
323
+ },
324
+ i
325
+ )) })
326
+ ] })
327
+ ] }),
328
+ /* @__PURE__ */ jsxs(ModalFooter, { children: [
329
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: () => onOpenChange(false), children: "Cancelar" }),
330
+ /* @__PURE__ */ jsx(Button, { onClick: addShift, children: "Salvar turno" })
331
+ ] })
332
+ ] }) });
333
+ }
334
+ function TemplateBlock({ template, shifts, onChanged }) {
335
+ const provider = useWorkforceProvider();
336
+ const [open, setOpen] = React3.useState(false);
299
337
  async function removeTemplate() {
300
338
  try {
301
339
  await provider.deleteTemplate(template.id);
302
340
  onChanged();
303
341
  } catch (e) {
304
- toast.error(e?.message ?? "N\xE3o foi poss\xEDvel remover o molde.");
342
+ toast.error(e?.message ?? "N\xE3o foi poss\xEDvel remover o template.");
305
343
  }
306
344
  }
307
- return /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-border p-4", children: [
308
- /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center justify-between gap-3", children: [
309
- /* @__PURE__ */ jsxs("div", { children: [
310
- /* @__PURE__ */ jsx("div", { className: "font-medium", children: template.name }),
311
- /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
312
- template.shifts.length,
313
- " turno(s)"
314
- ] })
315
- ] }),
316
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
345
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-border/60 bg-muted/20 p-3", children: [
346
+ /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between gap-2", children: [
347
+ /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-medium", children: template.name }),
348
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
317
349
  /* @__PURE__ */ jsxs(Button, { size: "sm", variant: "secondary", onClick: () => setOpen(true), children: [
318
350
  /* @__PURE__ */ jsx(Plus, { className: "mr-1 h-4 w-4", "aria-hidden": true }),
319
351
  " Turno"
320
352
  ] }),
321
- /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: removeTemplate, "aria-label": "Remover molde", children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4", "aria-hidden": true }) })
353
+ /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: removeTemplate, "aria-label": `Remover ${template.name}`, children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4", "aria-hidden": true }) })
322
354
  ] })
323
355
  ] }),
324
- /* @__PURE__ */ jsx("div", { className: "space-y-2", children: template.shifts.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Nenhum turno neste molde ainda." }) : template.shifts.map((s) => /* @__PURE__ */ jsx(ShiftCard, { shift: s, onChanged }, s.id)) }),
325
- /* @__PURE__ */ jsx(Modal, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxs(ModalContent, { size: "sm", children: [
326
- /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsx(ModalTitle, { children: "Novo turno" }) }),
327
- /* @__PURE__ */ jsxs(ModalBody, { className: "space-y-3", children: [
328
- /* @__PURE__ */ jsx(Input, { value: label, onChange: (e) => setLabel(e.target.value), placeholder: "Nome do turno (ex.: Almo\xE7o)" }),
329
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
330
- /* @__PURE__ */ jsx(Input, { type: "time", value: start, onChange: (e) => setStart(e.target.value), "aria-label": "In\xEDcio" }),
331
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "at\xE9" }),
332
- /* @__PURE__ */ jsx(Input, { type: "time", value: end, onChange: (e) => setEnd(e.target.value), "aria-label": "Fim" })
333
- ] }),
334
- /* @__PURE__ */ jsx("div", { className: "flex gap-1", children: WEEKDAYS.map((d, i) => /* @__PURE__ */ jsx(
335
- "button",
336
- {
337
- type: "button",
338
- onClick: () => setWeekdays((w) => w.includes(i) ? w.filter((x) => x !== i) : [...w, i]),
339
- className: `h-8 w-8 rounded text-xs ${weekdays.includes(i) ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"}`,
340
- children: d
341
- },
342
- i
343
- )) })
344
- ] }),
345
- /* @__PURE__ */ jsxs(ModalFooter, { children: [
346
- /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: () => setOpen(false), children: "Cancelar" }),
347
- /* @__PURE__ */ jsx(Button, { onClick: addShift, children: "Salvar turno" })
348
- ] })
349
- ] }) })
356
+ shifts.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Nenhum turno neste template ainda." }) : /* @__PURE__ */ jsx("div", { className: "space-y-2", children: shifts.map((s) => /* @__PURE__ */ jsx(ShiftCard, { shift: s, onChanged }, s.id)) }),
357
+ /* @__PURE__ */ jsx(NewShiftModal, { template, open, onOpenChange: setOpen, onChanged })
358
+ ] });
359
+ }
360
+ function DayRow({ title, summary, empty, open, onToggle, children }) {
361
+ return /* @__PURE__ */ jsxs("div", { className: "border-b border-border last:border-b-0", children: [
362
+ /* @__PURE__ */ jsxs(
363
+ "button",
364
+ {
365
+ type: "button",
366
+ onClick: onToggle,
367
+ "aria-expanded": open,
368
+ disabled: empty,
369
+ className: "flex w-full items-center gap-3 px-3 py-2.5 text-left transition-colors hover:bg-muted/50 disabled:cursor-default disabled:hover:bg-transparent",
370
+ children: [
371
+ /* @__PURE__ */ jsx(
372
+ ChevronRight,
373
+ {
374
+ className: `h-4 w-4 shrink-0 text-muted-foreground transition-transform ${open ? "rotate-90" : ""} ${empty ? "opacity-0" : ""}`,
375
+ "aria-hidden": true
376
+ }
377
+ ),
378
+ /* @__PURE__ */ jsx("span", { className: `w-24 shrink-0 text-sm ${empty ? "text-muted-foreground" : "font-medium"}`, children: title }),
379
+ /* @__PURE__ */ jsx("span", { className: "truncate text-sm text-muted-foreground", children: summary })
380
+ ]
381
+ }
382
+ ),
383
+ open && /* @__PURE__ */ jsx("div", { className: "space-y-3 px-3 pb-3", children })
350
384
  ] });
351
385
  }
352
386
  function TemplatesView() {
@@ -356,9 +390,22 @@ function TemplatesView() {
356
390
  const fetchTemplates = useWorkforceStore((s) => s.fetchTemplates);
357
391
  const [open, setOpen] = React3.useState(false);
358
392
  const [name, setName] = React3.useState("");
393
+ const [openDay, setOpenDay] = React3.useState(null);
359
394
  React3.useEffect(() => {
360
395
  void fetchTemplates();
361
396
  }, [fetchTemplates]);
397
+ const byDay = React3.useMemo(() => {
398
+ return WEEKDAY_NAMES.map((_, day) => {
399
+ const blocks = templates.map((t) => ({ template: t, shifts: t.shifts.filter((s) => s.weekdays.includes(day)) })).filter((b) => b.shifts.length > 0);
400
+ const shiftCount = blocks.reduce((n, b) => n + b.shifts.length, 0);
401
+ const seatCount = blocks.reduce((n, b) => n + b.shifts.reduce((m, s) => m + seats(s), 0), 0);
402
+ return { day, blocks, shiftCount, seatCount };
403
+ });
404
+ }, [templates]);
405
+ const withoutShifts = React3.useMemo(
406
+ () => templates.filter((t) => t.shifts.length === 0),
407
+ [templates]
408
+ );
362
409
  async function createTemplate() {
363
410
  if (!name.trim()) return;
364
411
  try {
@@ -367,18 +414,18 @@ function TemplatesView() {
367
414
  setOpen(false);
368
415
  await fetchTemplates();
369
416
  } catch (e) {
370
- toast.error(e?.message ?? "N\xE3o foi poss\xEDvel criar o molde.");
417
+ toast.error(e?.message ?? "N\xE3o foi poss\xEDvel criar o template.");
371
418
  }
372
419
  }
420
+ const onChanged = React3.useCallback(() => {
421
+ void fetchTemplates();
422
+ }, [fetchTemplates]);
373
423
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4 p-6", children: [
374
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
375
- /* @__PURE__ */ jsxs("div", { children: [
376
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: "Templates de escala" }),
377
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "O molde vem primeiro: sem ele n\xE3o se cria escala nenhuma." })
378
- ] }),
424
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [
425
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: "Templates de escala" }),
379
426
  /* @__PURE__ */ jsxs(Button, { onClick: () => setOpen(true), children: [
380
427
  /* @__PURE__ */ jsx(Plus, { className: "mr-2 h-4 w-4", "aria-hidden": true }),
381
- " Novo molde"
428
+ " Novo template"
382
429
  ] })
383
430
  ] }),
384
431
  loading ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
@@ -386,10 +433,27 @@ function TemplatesView() {
386
433
  /* @__PURE__ */ jsx(Skeleton, { className: "h-24 w-full" })
387
434
  ] }) : templates.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-dashed border-border p-8 text-center", children: [
388
435
  /* @__PURE__ */ jsx(LayoutTemplate, { className: "mx-auto mb-3 h-8 w-8 text-muted-foreground", "aria-hidden": true }),
389
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Nenhum molde cadastrado." })
390
- ] }) : /* @__PURE__ */ jsx("div", { className: "grid gap-4 md:grid-cols-2", children: templates.map((t) => /* @__PURE__ */ jsx(TemplateCard, { template: t, onChanged: () => void fetchTemplates() }, t.id)) }),
436
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Nenhum template cadastrado." })
437
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
438
+ /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-xl border border-border", children: byDay.map(({ day, blocks, shiftCount, seatCount }) => /* @__PURE__ */ jsx(
439
+ DayRow,
440
+ {
441
+ title: WEEKDAY_NAMES[day],
442
+ summary: shiftCount === 0 ? "Ningu\xE9m escalado" : `${plural(shiftCount, "turno", "turnos")} \xB7 ${plural(seatCount, "vaga", "vagas")}`,
443
+ empty: shiftCount === 0,
444
+ open: openDay === day,
445
+ onToggle: () => setOpenDay((d) => d === day ? null : day),
446
+ children: blocks.map((b) => /* @__PURE__ */ jsx(TemplateBlock, { template: b.template, shifts: b.shifts, onChanged }, b.template.id))
447
+ },
448
+ day
449
+ )) }),
450
+ withoutShifts.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
451
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Sem turno \u2014 n\xE3o entram em dia nenhum at\xE9 ganharem o primeiro." }),
452
+ withoutShifts.map((t) => /* @__PURE__ */ jsx(TemplateBlock, { template: t, shifts: [], onChanged }, t.id))
453
+ ] })
454
+ ] }),
391
455
  /* @__PURE__ */ jsx(Modal, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxs(ModalContent, { size: "sm", children: [
392
- /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsx(ModalTitle, { children: "Novo molde de escala" }) }),
456
+ /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsx(ModalTitle, { children: "Novo template" }) }),
393
457
  /* @__PURE__ */ jsx(ModalBody, { children: /* @__PURE__ */ jsx(Input, { value: name, onChange: (e) => setName(e.target.value), placeholder: "Nome (ex.: Almo\xE7o Seg. a Sex.)" }) }),
394
458
  /* @__PURE__ */ jsxs(ModalFooter, { children: [
395
459
  /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: () => setOpen(false), children: "Cancelar" }),
@@ -1131,39 +1195,169 @@ function PeriodsView() {
1131
1195
  ] }) })
1132
1196
  ] });
1133
1197
  }
1198
+ var PEOPLE_BASE_PATH = "/workforce/people";
1134
1199
  var DEFAULT_STAFF_ENTITY = {
1135
1200
  name: "Colaborador",
1136
1201
  namePlural: "Pessoas",
1137
1202
  icon: "UserCog",
1138
1203
  layout: "person",
1139
1204
  displayField: "name",
1205
+ subtitleField: "documentNumber",
1140
1206
  defaultSort: "name",
1207
+ formColumns: 2,
1208
+ facets: [activeFacet({ active: "Ativos", inactive: "Inativos", all: "Todos" })],
1209
+ fieldGroups: [
1210
+ { id: "identity", label: "Dados pessoais", columns: 2 },
1211
+ { id: "contact", label: "Contato", columns: 2 },
1212
+ { id: "where", label: "Endere\xE7o", columns: 1 },
1213
+ {
1214
+ id: "work",
1215
+ label: "V\xEDnculo",
1216
+ columns: 2,
1217
+ description: "O que a folha e o fisco esperam desta pessoa."
1218
+ },
1219
+ {
1220
+ id: "banking",
1221
+ label: "Dados banc\xE1rios",
1222
+ columns: 1,
1223
+ description: "Para onde o sal\xE1rio SAI \u2014 n\xE3o \xE9 conta da casa."
1224
+ },
1225
+ { ...RECORD_GROUP }
1226
+ ],
1141
1227
  fields: [
1142
- { key: "name", label: "Nome", type: "text", required: true, showInTable: true, searchable: true },
1143
- { key: "phone", label: "Telefone", type: "phone", showInTable: true },
1144
- { key: "email", label: "E-mail", type: "email", showInTable: false },
1145
- { key: "documentNumber", label: "CPF", type: "text", showInTable: true },
1146
- { key: "isActive", label: "Ativo", type: "boolean", showInTable: true, defaultValue: true, inlineToggle: true }
1228
+ { key: "name", label: "Nome completo", type: "text", required: true, showInTable: true, searchable: true, group: "identity", span: 2 },
1229
+ { key: "documentNumber", label: "CPF", type: "text", showInTable: true, searchable: true, group: "identity" },
1230
+ { key: "rg", label: "RG", type: "text", showInTable: false, group: "identity" },
1231
+ { key: "dateOfBirth", label: "Data de nascimento", type: "date", showInTable: false, group: "identity" },
1232
+ {
1233
+ key: "gender",
1234
+ label: "Sexo",
1235
+ type: "select",
1236
+ showInTable: false,
1237
+ group: "identity",
1238
+ options: [
1239
+ { value: "masculino", label: "Masculino" },
1240
+ { value: "feminino", label: "Feminino" },
1241
+ { value: "outro", label: "Outro" }
1242
+ ]
1243
+ },
1244
+ { key: "phone", label: "Telefone", type: "phone", showInTable: true, searchable: true, group: "contact" },
1245
+ { key: "mobile", label: "Celular", type: "phone", showInTable: true, group: "contact" },
1246
+ { key: "email", label: "E-mail", type: "email", showInTable: false, searchable: true, group: "contact" },
1247
+ { key: "address", label: "Endere\xE7o", type: "address", showInTable: false, showInDetail: false, group: "where" },
1248
+ {
1249
+ key: "employmentType",
1250
+ label: "Tipo de v\xEDnculo",
1251
+ type: "select",
1252
+ showInTable: true,
1253
+ group: "work",
1254
+ options: [
1255
+ { value: "clt", label: "CLT" },
1256
+ { value: "pj", label: "Pessoa jur\xEDdica" },
1257
+ { value: "autonomo", label: "Aut\xF4nomo" },
1258
+ { value: "estagio", label: "Est\xE1gio" },
1259
+ { value: "socio", label: "S\xF3cio" }
1260
+ ]
1261
+ },
1262
+ { key: "jobTitle", label: "Cargo", type: "text", showInTable: true, group: "work" },
1263
+ { key: "admissionDate", label: "Admiss\xE3o", type: "date", showInTable: false, group: "work" },
1264
+ // O ACESSO ÀS AGENDAS dos profissionais: o V1 pergunta isto no cadastro de
1265
+ // funcionário porque a recepção marca para os outros, e quem não marca não
1266
+ // precisa ver a semana inteira de ninguém.
1267
+ {
1268
+ key: "isBookable",
1269
+ label: "Aparece na agenda",
1270
+ type: "boolean",
1271
+ showInTable: false,
1272
+ defaultValue: false,
1273
+ group: "work",
1274
+ hint: "Clientes podem ser marcados com esta pessoa."
1275
+ },
1276
+ // Para onde o salário SAI — a mesma linha de `payee_accounts` que o
1277
+ // fornecedor usa. Quem a casa paga é quem a casa paga.
1278
+ { key: "payeeAccount", label: "Conta banc\xE1ria", type: "bank", showInTable: false, showInDetail: false, group: "banking" },
1279
+ ...recordRailFields({ tagsTable: "people", activeLabel: "Ativo" })
1147
1280
  ],
1148
1281
  data: {
1149
1282
  table: "people",
1150
1283
  tenantScoped: true,
1151
1284
  archetype: "person",
1152
1285
  archetypeKind: "staff",
1153
- searchColumns: ["name", "email", "phone"]
1286
+ customFields: ["rg", "gender", "mobile", "employmentType", "jobTitle", "admissionDate"],
1287
+ searchColumns: ["name", "email", "phone", "document_number"]
1154
1288
  }
1155
1289
  };
1290
+ function TeamDocuments() {
1291
+ const provider = useWorkforceProvider();
1292
+ const [team, setTeam] = React3.useState(null);
1293
+ const [person, setPerson] = React3.useState(null);
1294
+ React3.useEffect(() => {
1295
+ let alive = true;
1296
+ provider.getStaff().then((people) => {
1297
+ if (alive) setTeam(people);
1298
+ }).catch(() => {
1299
+ if (alive) setTeam([]);
1300
+ });
1301
+ return () => {
1302
+ alive = false;
1303
+ };
1304
+ }, [provider]);
1305
+ if (!team) return /* @__PURE__ */ jsx(Skeleton, { className: "h-40 w-full" });
1306
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1307
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
1308
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: "Documentos de" }),
1309
+ /* @__PURE__ */ jsxs(
1310
+ Select,
1311
+ {
1312
+ value: person?.id ?? "",
1313
+ onValueChange: (id) => setPerson(team.find((p) => p.id === id) ?? null),
1314
+ children: [
1315
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "w-full sm:w-72", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: team.length ? "Escolha algu\xE9m da equipe\u2026" : "Nenhuma pessoa cadastrada" }) }),
1316
+ /* @__PURE__ */ jsx(SelectContent, { children: team.map((p) => /* @__PURE__ */ jsx(SelectItem, { value: p.id, children: p.name || "Sem nome" }, p.id)) })
1317
+ ]
1318
+ }
1319
+ )
1320
+ ] }),
1321
+ person ? /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1322
+ /* @__PURE__ */ jsx(WidgetSlot, { zone: "person.detail.documents", item: person }),
1323
+ /* @__PURE__ */ jsx(WidgetSlot, { zone: "person.detail.files", item: person })
1324
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-dashed border-border p-8 text-center", children: [
1325
+ /* @__PURE__ */ jsx(FileText, { className: "mx-auto mb-3 h-8 w-8 text-muted-foreground", "aria-hidden": true }),
1326
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Documentos, contratos, certificados, RG/CPF, etc. Escolha algu\xE9m da equipe acima." })
1327
+ ] })
1328
+ ] });
1329
+ }
1156
1330
  var cache = /* @__PURE__ */ new Map();
1157
- function PeopleView({ entity }) {
1331
+ function PeopleView({ entity, mode }) {
1158
1332
  const def = entity ?? DEFAULT_STAFF_ENTITY;
1159
1333
  const Page = React3.useMemo(() => {
1160
1334
  const cached = cache.get(def);
1161
1335
  if (cached) return cached;
1162
1336
  const created = createCrudPage(def);
1337
+ created.__crudBasePath = PEOPLE_BASE_PATH;
1163
1338
  cache.set(def, created);
1164
1339
  return created;
1165
1340
  }, [def]);
1166
- return /* @__PURE__ */ jsx(Page, {});
1341
+ const [tab, setTab] = React3.useState("people");
1342
+ const onList = !mode || mode === "list";
1343
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1344
+ onList && /* @__PURE__ */ jsxs(PageViewBar, { children: [
1345
+ /* @__PURE__ */ jsx(
1346
+ ViewTabs,
1347
+ {
1348
+ value: tab,
1349
+ onChange: setTab,
1350
+ options: [
1351
+ { value: "people", label: "Pessoas" },
1352
+ { value: "documents", label: "Documentos" }
1353
+ ],
1354
+ "aria-label": "Pessoas e Documentos"
1355
+ }
1356
+ ),
1357
+ /* @__PURE__ */ jsx("span", {})
1358
+ ] }),
1359
+ onList && tab === "documents" ? /* @__PURE__ */ jsx(TeamDocuments, {}) : /* @__PURE__ */ jsx(Page, {})
1360
+ ] });
1167
1361
  }
1168
1362
  var KEY = "workforce.payroll_account_codes";
1169
1363
  function message(e) {
@@ -2056,7 +2250,7 @@ function WorkforcePage({ config, provider, store, staffEntity }) {
2056
2250
  case "periods":
2057
2251
  return /* @__PURE__ */ jsx(PeriodsView, {});
2058
2252
  case "people":
2059
- return /* @__PURE__ */ jsx(PeopleView, { entity: staffEntity });
2253
+ return /* @__PURE__ */ jsx(PeopleView, { entity: staffEntity, mode: intent.mode });
2060
2254
  case "hr":
2061
2255
  return /* @__PURE__ */ jsx(HrView, {});
2062
2256
  case "payroll-costs":