v-sistec-features 1.31.12 → 1.31.13

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.
@@ -7,10 +7,17 @@ e mescla células de reservas contínuas com colspan.
7
7
  ========
8
8
  -->
9
9
  <script setup lang="ts">
10
- import { computed, onMounted, watch, ref, inject } from 'vue';
11
- import { useAgenda } from '../composables/useAgenda';
10
+ import { computed, inject } from 'vue';
12
11
  import { useCoresAgenda } from '../composables/useCoresAgenda';
13
12
  import { useSelecaoArrasto } from '../composables/useSelecaoArrasto';
13
+ import {
14
+ SIGLAS_DIAS_JS,
15
+ extrairDataHoraLocal,
16
+ extrairHoraFormatada,
17
+ criarDataLocal,
18
+ mesclarSlotsReservados,
19
+ useGradeSemanal
20
+ } from '../composables/useGradeCompartilhada';
14
21
  import BlocoArrastavel from './BlocoArrastavel.vue';
15
22
  import ModalReserva from './ModalReserva.vue';
16
23
  import ModalDetalhesReserva from './ModalDetalhesReserva.vue';
@@ -32,51 +39,21 @@ const props = defineProps<{
32
39
  }>();
33
40
 
34
41
  const api = inject('api-instance-agenda') as any;
35
- const emit = defineEmits(['before-save']);
42
+ defineEmits(['before-save']);
43
+
44
+ const { obterCoresBloco } = useCoresAgenda();
36
45
 
37
46
  const {
38
47
  dadosMatrix,
39
- buscarSlotsMatrix,
40
- buscarReservas,
41
- agendasDisponiveis,
42
- agendaSelecionada,
43
- dataSelecionada,
44
- converterParaUtc
45
- } = useAgenda(api);
46
-
47
- const { obterCoresBloco } = useCoresAgenda();
48
-
49
- // Lista de siglas dos dias da semana ordenada conforme o retorno de Date.getDay() do JavaScript (0=DOM a 6=SAB).
50
- const SIGLAS_DIAS_JS = ['DOM', 'SEG', 'TER', 'QUA', 'QUI', 'SEX', 'SAB'];
51
-
52
- // Indica se a matriz está buscando e processando dados na API.
53
- const carregando = ref(false);
54
-
55
- // Controla a abertura do modal de criação de reserva.
56
- const modalAberto = ref(false);
57
-
58
- // Armazena os dados do slot ou intervalo selecionado para preencher o modal de criação.
59
- const dadosParaModal = ref({});
60
-
61
- // Controla a abertura do modal de detalhes de uma reserva existente.
62
- const modalDetalhesAberto = ref(false);
63
-
64
- // Armazena o objeto da reserva clicada para exibição no modal de detalhes.
65
- const reservaSelecionadaDetalhes = ref<any>(null);
66
-
67
- // Recebe uma string ISO UTC e devolve objeto com data (YYYY-MM-DD) e hora (HH:MM) locais.
68
- // É usada para converter os limites dos slots selecionados para o formato esperado pelo ModalReserva.
69
- const extrairDataHoraLocal = (isoString: string) => {
70
- const d = new Date(isoString);
71
- const ano = d.getFullYear();
72
- const mes = String(d.getMonth() + 1).padStart(2, '0');
73
- const dia = String(d.getDate()).padStart(2, '0');
74
- const horas = String(d.getHours()).padStart(2, '0');
75
- const min = String(d.getMinutes()).padStart(2, '0');
76
- return { data: `${ano}-${mes}-${dia}`, hora: `${horas}:${min}` };
77
- };
48
+ carregando,
49
+ modalAberto,
50
+ dadosParaModal,
51
+ modalDetalhesAberto,
52
+ reservaSelecionadaDetalhes,
53
+ obterBlocosDoDia,
54
+ dispararBuscaSemanal
55
+ } = useGradeSemanal(api, () => props.configuracaoBlocos);
78
56
 
79
- // Instância do controlador genérico de arrasto configurada para operar nas linhas horizontais da matriz.
80
57
  const controladorArrastoHorizontal = useSelecaoArrasto({
81
58
  aoClicarItemIndisponivel: (item) => {
82
59
  if (item?.slot?.reservado_slot && item?.slot?.detalhes_reserva) {
@@ -105,117 +82,10 @@ const controladorArrastoHorizontal = useSelecaoArrasto({
105
82
  }
106
83
  });
107
84
 
108
- // Calcula a data de início da semana (Segunda-feira por padrão) com base na data selecionada.
109
- const obterInicioDaSemana = (dataRef: Date, idAgenda: number | null) => {
110
- const d = new Date(dataRef.getFullYear(), dataRef.getMonth(), dataRef.getDate());
111
- const agendaObj = agendasDisponiveis.value.find((a: any) => Number(a.id_agenda) === Number(idAgenda));
112
- const diaInicioPython = agendaObj?.detalhes?.dia_inicio_semana_agenda ?? 0;
113
- const diaInicioJs = (diaInicioPython + 1) % 7;
114
- const diff = (d.getDay() - diaInicioJs + 7) % 7;
115
- d.setDate(d.getDate() - diff);
116
- return d;
117
- };
118
-
119
- // Busca os slots da matriz e as reservas correspondentes à semana ordenada a partir do dia inicial da agenda.
120
- const dispararBuscaMatrix = async () => {
121
- const idAgenda = agendaSelecionada.value;
122
- if (!idAgenda) return;
123
-
124
- carregando.value = true;
125
-
126
- const valorData = dataSelecionada.value || new Date();
127
- let dataBase: Date;
128
-
129
- if (typeof valorData === 'string') {
130
- const apenasData = String(valorData).substring(0, 10);
131
- const [ano, mes, dia] = apenasData.split('-');
132
- dataBase = new Date(Number(ano), Number(mes) - 1, Number(dia));
133
- } else {
134
- dataBase = new Date(valorData);
135
- }
136
-
137
- const dataInicio = obterInicioDaSemana(dataBase, idAgenda);
138
- const dataFim = new Date(dataInicio);
139
- dataFim.setDate(dataInicio.getDate() + 6);
140
-
141
- const formatar = (dt: Date) => {
142
- const ano = dt.getFullYear();
143
- const mes = String(dt.getMonth() + 1).padStart(2, '0');
144
- const dia = String(dt.getDate()).padStart(2, '0');
145
- return `${ano}-${mes}-${dia}`;
146
- };
147
-
148
- const strInicio = formatar(dataInicio);
149
- const strFim = formatar(dataFim);
150
-
151
- const inicioUtc = encodeURIComponent(converterParaUtc(strInicio, '00:00'));
152
- const fimUtc = encodeURIComponent(converterParaUtc(strFim, '23:59'));
153
-
154
- await buscarSlotsMatrix(idAgenda, inicioUtc, fimUtc);
155
- await buscarReservas(`${strInicio}T00:00:00`, `${strFim}T23:59:59`);
156
- carregando.value = false;
157
- };
158
-
159
- onMounted(() => {
160
- dispararBuscaMatrix();
161
- });
162
-
163
- watch([dataSelecionada, agendaSelecionada], () => {
164
- dispararBuscaMatrix();
165
- });
166
-
167
- // Recebe uma string ISO UTC e devolve uma string no formato HH:MM local.
168
- const extrairHoraFormatada = (isoStr: string) => {
169
- const d = new Date(isoStr);
170
- const horas = String(d.getHours()).padStart(2, '0');
171
- const minutos = String(d.getMinutes()).padStart(2, '0');
172
- return `${horas}:${minutos}`;
173
- };
174
-
175
- // Recebe uma string de data YYYY-MM-DD e devolve a sigla do dia da semana correspondente (SEG, TER, etc.).
176
- const obterSiglaDia = (isoData: string): string => {
177
- const apenasData = String(isoData).substring(0, 10);
178
- const [ano, mes, dia] = apenasData.split('-');
179
- const data = new Date(Number(ano), Number(mes) - 1, Number(dia));
180
- return SIGLAS_DIAS_JS[data.getDay()] || 'SEG';
181
- };
182
-
183
- // Localiza e devolve a configuração de blocos correspondente à agenda exibida no momento.
184
- const configuracaoAgendaAtual = computed(() => {
185
- if (!props.configuracaoBlocos || !agendaSelecionada.value) return null;
186
-
187
- const idAtual = Number(agendaSelecionada.value);
188
-
189
- if (Array.isArray(props.configuracaoBlocos)) {
190
- return props.configuracaoBlocos.find((c: any) => Number(c.id_agenda) === idAtual) || null;
191
- }
192
-
193
- if (typeof props.configuracaoBlocos === 'object') {
194
- if (Number(props.configuracaoBlocos.id_agenda) === idAtual) {
195
- return props.configuracaoBlocos;
196
- }
197
- if (props.configuracaoBlocos[idAtual]) {
198
- return props.configuracaoBlocos[idAtual];
199
- }
200
- }
201
-
202
- return null;
203
- });
204
-
205
85
  // Constrói os blocos principais, subdivisões, cores e bordas por coluna para o cabeçalho e células de um dia específico.
206
86
  const construirCabecalhosDeBlocos = (slotsBase: { horaInicio: string; horaFim: string }[], siglaDia: string) => {
207
87
  const totalSlots = slotsBase.length;
208
- const configAgenda = configuracaoAgendaAtual.value;
209
-
210
- let listaBlocosConfig: any[] | null = null;
211
-
212
- if (configAgenda) {
213
- if (configAgenda.por_dia && configAgenda.por_dia[siglaDia]) {
214
- listaBlocosConfig = configAgenda.por_dia[siglaDia];
215
- } else if (Array.isArray(configAgenda.blocos)) {
216
- listaBlocosConfig = configAgenda.blocos;
217
- }
218
- }
88
+ const listaBlocosConfig = obterBlocosDoDia(siglaDia);
219
89
 
220
90
  const metadadosColunas: MetadadoColuna[] = slotsBase.map(() => ({
221
91
  corFundo: null,
@@ -239,17 +109,30 @@ const construirCabecalhosDeBlocos = (slotsBase: { horaInicio: string; horaFim: s
239
109
  let cursor = 0;
240
110
  let algumSubblocoDefinido = false;
241
111
 
112
+ const adicionarSubbloco = (inicioIdx: number, tam: number, cor: string, corBorda: string, fimPrincipal: boolean, marcarSub: boolean) => {
113
+ const iniSub = slotsBase[inicioIdx];
114
+ const fimSub = slotsBase[inicioIdx + tam - 1];
115
+ subblocos.push({
116
+ colspan: tam,
117
+ cor,
118
+ corBorda,
119
+ fimDeBlocoPrincipal: fimPrincipal,
120
+ etiqueta: `${iniSub?.horaInicio ?? ''} - ${fimSub?.horaFim ?? ''}`
121
+ });
122
+ if (marcarSub) {
123
+ const metaFimSub = metadadosColunas[inicioIdx + tam - 1];
124
+ if (metaFimSub) metaFimSub.fimDeSubbloco = true;
125
+ }
126
+ };
127
+
242
128
  listaBlocosConfig.forEach((blocoCfg: any, idxBloco: number) => {
243
129
  if (cursor >= totalSlots) return;
244
130
 
245
131
  const qtdSolicitada = Number(blocoCfg.quantidade) || 0;
246
- if (qtdSolicitada <= 0) return;
247
-
248
132
  const qtdReal = Math.min(qtdSolicitada, totalSlots - cursor);
249
133
  if (qtdReal <= 0) return;
250
134
 
251
135
  const { corFundo: corBloco, corBorda } = obterCoresBloco(idxBloco);
252
-
253
136
  const slotPrimeiro = slotsBase[cursor];
254
137
  const slotUltimo = slotsBase[cursor + qtdReal - 1];
255
138
 
@@ -257,7 +140,7 @@ const construirCabecalhosDeBlocos = (slotsBase: { horaInicio: string; horaFim: s
257
140
  nome: blocoCfg.nome || `Bloco ${idxBloco + 1}`,
258
141
  colspan: qtdReal,
259
142
  cor: corBloco,
260
- corBorda: corBorda,
143
+ corBorda,
261
144
  horaInicio: slotPrimeiro?.horaInicio ?? '',
262
145
  horaFim: slotUltimo?.horaFim ?? ''
263
146
  });
@@ -279,83 +162,35 @@ const construirCabecalhosDeBlocos = (slotsBase: { horaInicio: string; horaFim: s
279
162
  listaSubCustom.forEach((tamSub: number) => {
280
163
  if (consumido >= qtdReal) return;
281
164
  const tamEfetivo = Math.min(Number(tamSub) || 1, qtdReal - consumido);
282
- const iniSub = slotsBase[cursor + consumido];
283
- const fimSub = slotsBase[cursor + consumido + tamEfetivo - 1];
284
- const ehUltimoDoBloco = (consumido + tamEfetivo) >= qtdReal;
285
-
286
- subblocos.push({
287
- colspan: tamEfetivo,
288
- cor: corBloco,
289
- corBorda: corBorda,
290
- fimDeBlocoPrincipal: ehUltimoDoBloco,
291
- etiqueta: `${iniSub?.horaInicio ?? ''} - ${fimSub?.horaFim ?? ''}`
292
- });
293
-
165
+ const ehUltimo = (consumido + tamEfetivo) >= qtdReal;
166
+ adicionarSubbloco(cursor + consumido, tamEfetivo, corBloco, corBorda, ehUltimo, true);
294
167
  consumido += tamEfetivo;
295
- const metaFimSub = metadadosColunas[cursor + consumido - 1];
296
- if (metaFimSub) metaFimSub.fimDeSubbloco = true;
297
168
  });
298
169
  if (consumido < qtdReal) {
299
- const restante = qtdReal - consumido;
300
- const iniSub = slotsBase[cursor + consumido];
301
- const fimSub = slotsBase[cursor + qtdReal - 1];
302
- subblocos.push({
303
- colspan: restante,
304
- cor: corBloco,
305
- corBorda: corBorda,
306
- fimDeBlocoPrincipal: true,
307
- etiqueta: `${iniSub?.horaInicio ?? ''} - ${fimSub?.horaFim ?? ''}`
308
- });
309
- const metaFimSub = metadadosColunas[cursor + qtdReal - 1];
310
- if (metaFimSub) metaFimSub.fimDeSubbloco = true;
170
+ adicionarSubbloco(cursor + consumido, qtdReal - consumido, corBloco, corBorda, true, true);
311
171
  }
312
172
  } else if (tamanhoSubFixo > 0) {
313
173
  algumSubblocoDefinido = true;
314
174
  let consumido = 0;
315
175
  while (consumido < qtdReal) {
316
176
  const tamEfetivo = Math.min(tamanhoSubFixo, qtdReal - consumido);
317
- const iniSub = slotsBase[cursor + consumido];
318
- const fimSub = slotsBase[cursor + consumido + tamEfetivo - 1];
319
- const ehUltimoDoBloco = (consumido + tamEfetivo) >= qtdReal;
320
-
321
- subblocos.push({
322
- colspan: tamEfetivo,
323
- cor: corBloco,
324
- corBorda: corBorda,
325
- fimDeBlocoPrincipal: ehUltimoDoBloco,
326
- etiqueta: `${iniSub?.horaInicio ?? ''} - ${fimSub?.horaFim ?? ''}`
327
- });
328
-
177
+ const ehUltimo = (consumido + tamEfetivo) >= qtdReal;
178
+ adicionarSubbloco(cursor + consumido, tamEfetivo, corBloco, corBorda, ehUltimo, true);
329
179
  consumido += tamEfetivo;
330
- const metaFimSub = metadadosColunas[cursor + consumido - 1];
331
- if (metaFimSub) metaFimSub.fimDeSubbloco = true;
332
180
  }
333
181
  } else {
334
- const iniSub = slotsBase[cursor];
335
- const fimSub = slotsBase[cursor + qtdReal - 1];
336
- subblocos.push({
337
- colspan: qtdReal,
338
- cor: corBloco,
339
- corBorda: corBorda,
340
- fimDeBlocoPrincipal: true,
341
- etiqueta: `${iniSub?.horaInicio ?? ''} - ${fimSub?.horaFim ?? ''}`
342
- });
182
+ adicionarSubbloco(cursor, qtdReal, corBloco, corBorda, true, false);
343
183
  }
344
184
 
345
185
  const metaFimPrincipal = metadadosColunas[cursor + qtdReal - 1];
346
- if (metaFimPrincipal) {
347
- metaFimPrincipal.fimDeBlocoPrincipal = true;
348
- }
186
+ if (metaFimPrincipal) metaFimPrincipal.fimDeBlocoPrincipal = true;
349
187
 
350
188
  cursor += qtdReal;
351
189
  });
352
190
 
353
191
  if (cursor < totalSlots) {
354
192
  const restante = totalSlots - cursor;
355
- const idxExtra = blocosPrincipais.length;
356
-
357
- const { corFundo: corRestante, corBorda: corBordaRestante } = obterCoresBloco(idxExtra);
358
-
193
+ const { corFundo: corRestante, corBorda: corBordaRestante } = obterCoresBloco(blocosPrincipais.length);
359
194
  const slotPrimeiro = slotsBase[cursor];
360
195
  const slotUltimo = slotsBase[totalSlots - 1];
361
196
 
@@ -376,18 +211,10 @@ const construirCabecalhosDeBlocos = (slotsBase: { horaInicio: string; horaFim: s
376
211
  }
377
212
  }
378
213
 
379
- subblocos.push({
380
- colspan: restante,
381
- cor: corRestante,
382
- corBorda: corBordaRestante,
383
- fimDeBlocoPrincipal: true,
384
- etiqueta: `${slotPrimeiro?.horaInicio ?? ''} - ${slotUltimo?.horaFim ?? ''}`
385
- });
214
+ adicionarSubbloco(cursor, restante, corRestante, corBordaRestante, true, false);
386
215
 
387
216
  const metaUltimo = metadadosColunas[totalSlots - 1];
388
- if (metaUltimo) {
389
- metaUltimo.fimDeBlocoPrincipal = true;
390
- }
217
+ if (metaUltimo) metaUltimo.fimDeBlocoPrincipal = true;
391
218
  }
392
219
 
393
220
  return {
@@ -399,7 +226,7 @@ const construirCabecalhosDeBlocos = (slotsBase: { horaInicio: string; horaFim: s
399
226
  };
400
227
  };
401
228
 
402
- // Gera a lista ordenada de dias operacionais, cada um com sua barra de data no topo e seu cabeçalho de horários logo abaixo.
229
+ // Gera a lista ordenada de dias operacionais com cabeçalhos e células mescladas por colspan.
403
230
  const diasProcessados = computed(() => {
404
231
  if (!dadosMatrix.value?.datas_matriz) return [];
405
232
 
@@ -411,12 +238,9 @@ const diasProcessados = computed(() => {
411
238
  const recursosFiltrados = (dia.recursos_slot || []).filter((res: any) =>
412
239
  props.idsRecursosVisiveis.includes(String(res.id_recurso))
413
240
  );
414
-
415
241
  if (recursosFiltrados.length === 0) return;
416
242
 
417
- const primeiroRecurso = recursosFiltrados[0];
418
- const slotsValidosBase = (primeiroRecurso.horarios_slot || []).filter((s: any) => !s.bloqueado_slot);
419
-
243
+ const slotsValidosBase = (recursosFiltrados[0].horarios_slot || []).filter((s: any) => !s.bloqueado_slot);
420
244
  if (slotsValidosBase.length === 0) return;
421
245
 
422
246
  const definicaoColunas = slotsValidosBase.map((s: any) => ({
@@ -424,69 +248,23 @@ const diasProcessados = computed(() => {
424
248
  horaFim: extrairHoraFormatada(s.data_fim_slot)
425
249
  }));
426
250
 
427
- const siglaDia = obterSiglaDia(dia.data_slot);
251
+ const siglaDia = SIGLAS_DIAS_JS[criarDataLocal(dia.data_slot).getDay()] || 'SEG';
428
252
  const infoBlocos = construirCabecalhosDeBlocos(definicaoColunas, siglaDia);
429
253
 
430
254
  const recursosProcessados = recursosFiltrados.map((rec: any) => {
431
255
  const slotsBrutos = (rec.horarios_slot || []).filter((s: any) => !s.bloqueado_slot);
432
- const celulasMescladas: any[] = [];
433
-
434
- let i = 0;
435
- while (i < slotsBrutos.length) {
436
- const slotAtual = slotsBrutos[i];
437
- const codRef = slotAtual?.detalhes_reserva?.codigo_referencia_reserva;
438
-
439
- if (slotAtual?.reservado_slot && codRef) {
440
- let span = 1;
441
- while (
442
- i + span < slotsBrutos.length &&
443
- slotsBrutos[i + span]?.reservado_slot &&
444
- slotsBrutos[i + span]?.detalhes_reserva?.codigo_referencia_reserva === codRef
445
- ) {
446
- span++;
447
- }
448
- celulasMescladas.push({
449
- ...slotAtual,
450
- indiceColuna: i,
451
- colspan: span,
452
- ocultaPorColspan: false
453
- });
454
- for (let k = 1; k < span; k++) {
455
- celulasMescladas.push({
456
- ...slotsBrutos[i + k],
457
- indiceColuna: i + k,
458
- colspan: 1,
459
- ocultaPorColspan: true
460
- });
461
- }
462
- i += span;
463
- } else {
464
- celulasMescladas.push({
465
- ...slotAtual,
466
- indiceColuna: i,
467
- colspan: 1,
468
- ocultaPorColspan: false
469
- });
470
- i++;
471
- }
472
- }
473
-
474
256
  return {
475
257
  id_recurso: rec.id_recurso,
476
258
  nome_recurso: rec.nome_recurso,
477
259
  slotsBrutos,
478
- horarios_slot: celulasMescladas
260
+ horarios_slot: mesclarSlotsReservados(slotsBrutos, 'colspan')
479
261
  };
480
262
  });
481
263
 
482
264
  resultado.push({
483
265
  data_slot: dia.data_slot,
484
266
  colunasHorarios: definicaoColunas,
485
- temBlocos: infoBlocos.temBlocos,
486
- temSubblocos: infoBlocos.temSubblocos,
487
- blocosPrincipais: infoBlocos.blocosPrincipais,
488
- subblocos: infoBlocos.subblocos,
489
- metadadosColunas: infoBlocos.metadadosColunas,
267
+ ...infoBlocos,
490
268
  recursos_slot: recursosProcessados
491
269
  });
492
270
  });
@@ -494,12 +272,13 @@ const diasProcessados = computed(() => {
494
272
  return resultado;
495
273
  });
496
274
 
497
- // Formata a data YYYY-MM-DD por extenso em português para a barra superior de cada dia.
498
275
  const formatarData = (isoData: string) => {
499
- const apenasData = String(isoData).substring(0, 10);
500
- const [ano, mes, dia] = apenasData.split('-');
501
- const data = new Date(Number(ano), Number(mes) - 1, Number(dia));
502
- return data.toLocaleDateString('pt-BR', { weekday: 'long', day: '2-digit', month: 'long', year: 'numeric' });
276
+ return criarDataLocal(isoData).toLocaleDateString('pt-BR', {
277
+ weekday: 'long',
278
+ day: '2-digit',
279
+ month: 'long',
280
+ year: 'numeric'
281
+ });
503
282
  };
504
283
  </script>
505
284
 
@@ -511,10 +290,10 @@ const formatarData = (isoData: string) => {
511
290
  :usa-interceptador="props.usaInterceptador"
512
291
  :ocultar-titulo-modal="props.ocultarTituloModal"
513
292
  @before-save="$emit('before-save', $event)"
514
- @salvo="dispararBuscaMatrix"
293
+ @salvo="dispararBuscaSemanal"
515
294
  >
516
- <template #campos-extras>
517
- <slot name="campos-extras"></slot>
295
+ <template v-for="(_, nomeSlot) in $slots" :key="nomeSlot" #[nomeSlot]="slotProps">
296
+ <slot v-if="nomeSlot !== 'topo'" :name="nomeSlot" v-bind="slotProps ?? {}"></slot>
518
297
  </template>
519
298
  </ModalReserva>
520
299
 
@@ -522,7 +301,7 @@ const formatarData = (isoData: string) => {
522
301
  v-model="modalDetalhesAberto"
523
302
  :reserva="reservaSelecionadaDetalhes"
524
303
  :titulo-padrao="props.tituloPadrao"
525
- @excluido="dispararBuscaMatrix"
304
+ @excluido="dispararBuscaSemanal"
526
305
  />
527
306
 
528
307
  <div class="aviso-vazio" v-if="carregando">
@@ -668,224 +447,43 @@ const formatarData = (isoData: string) => {
668
447
  .spinner { width: 32px; height: 32px; border: 3px solid var(--border-light); border-top-color: var(--color-brand); border-radius: 50%; animation: spin 1s linear infinite; }
669
448
  @keyframes spin { to { transform: rotate(360deg); } }
670
449
 
671
- .tabelas-scroll-container {
672
- flex-grow: 1;
673
- overflow: auto;
674
- background: var(--bg-primary);
675
- display: flex;
676
- flex-direction: column;
677
- gap: 18px;
678
- padding-bottom: 24px;
679
- }
680
-
681
- .bloco-dia-tabela {
682
- width: 100%;
683
- }
684
-
685
- .tabela-premium {
686
- width: 100%;
687
- border-collapse: separate;
688
- border-spacing: 0;
689
- font-size: 12.5px;
690
- text-align: center;
691
- }
692
-
693
- .tabela-premium th,
694
- .tabela-premium td {
695
- padding: 4px 6px;
696
- min-width: 68px;
697
- height: 44px;
698
- border-bottom: 1px solid var(--border-light);
699
- border-right: 1px solid var(--border-light);
700
- box-sizing: border-box;
701
- }
702
-
703
- .tabela-premium th:first-child,
704
- .tabela-premium td:first-child {
705
- border-left: 1px solid var(--border-light);
706
- }
707
-
708
- .tabela-premium thead th {
709
- background: var(--bg-primary);
710
- font-weight: 700;
711
- color: var(--text-primary);
712
- }
713
-
714
- .coluna-fixa {
715
- width: 200px;
716
- min-width: 200px;
717
- max-width: 200px;
718
- text-align: left;
719
- background: var(--bg-primary);
720
- position: sticky;
721
- left: 0;
722
- z-index: 2;
723
- border-right: 2px solid var(--border-light) !important;
724
- }
725
-
726
- .cabecalho-canto {
727
- z-index: 3 !important;
728
- padding-left: 24px !important;
729
- background: var(--bg-primary) !important;
730
- }
731
-
732
- .th-barra-dia {
733
- padding: 0 !important;
734
- height: auto !important;
735
- border-top: 1px solid #005291 !important;
736
- border-bottom: 1px solid #005291 !important;
737
- }
738
-
739
- .th-bloco-principal {
740
- color: #111827 !important;
741
- padding: 6px 4px !important;
742
- height: 36px !important;
743
- border-bottom-width: 1.5px !important;
744
- border-bottom-style: solid !important;
745
- }
746
-
747
- .titulo-bloco-principal {
748
- font-size: 13px;
749
- font-weight: 800;
750
- letter-spacing: 0.3px;
751
- line-height: 1.2;
752
- text-transform: uppercase;
753
- }
754
-
755
- .subtitulo-bloco-principal {
756
- font-size: 10.5px;
757
- font-weight: 700;
758
- opacity: 0.9;
759
- }
760
-
761
- .th-subbloco {
762
- color: #111827 !important;
763
- font-size: 11px !important;
764
- font-weight: 700 !important;
765
- height: 26px !important;
766
- padding: 2px 4px !important;
767
- border-bottom-width: 1.5px !important;
768
- border-bottom-style: solid !important;
769
- }
770
-
771
- .th-horario-individual {
772
- height: 42px !important;
773
- padding: 4px 2px !important;
774
- color: #111827 !important;
775
- border-bottom-width: 2px !important;
776
- border-bottom-style: solid !important;
777
- }
778
-
779
- .empilhado-horarios {
780
- display: flex;
781
- flex-direction: column;
782
- align-items: center;
783
- justify-content: center;
784
- line-height: 1.15;
785
- gap: 2px;
786
- }
787
-
788
- .hora-topo {
789
- font-size: 11.5px;
790
- font-weight: 700;
791
- color: #111827;
792
- }
793
-
794
- .hora-base {
795
- font-size: 11.5px;
796
- font-weight: 700;
797
- color: #111827;
798
- }
799
-
800
- .td-celula-slot {
801
- cursor: pointer;
802
- transition: filter 0.12s ease, box-shadow 0.12s ease;
803
- vertical-align: middle;
804
- color: #111827 !important;
805
- }
806
-
807
- .td-celula-slot:hover {
808
- filter: brightness(0.93);
809
- box-shadow: inset 0 0 0 1.5px var(--color-brand);
810
- }
811
-
812
- .slot-selecionado-arrasto {
813
- box-shadow: inset 0 0 0 2px var(--color-brand) !important;
814
- }
815
-
450
+ .tabelas-scroll-container { flex-grow: 1; overflow: auto; background: var(--bg-primary); display: flex; flex-direction: column; gap: 18px; padding-bottom: 24px; }
451
+ .bloco-dia-tabela { width: 100%; }
452
+ .tabela-premium { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 12.5px; text-align: center; }
453
+ .tabela-premium th, .tabela-premium td { padding: 4px 6px; min-width: 68px; height: 44px; border-bottom: 1px solid var(--border-light); border-right: 1px solid var(--border-light); box-sizing: border-box; }
454
+ .tabela-premium th:first-child, .tabela-premium td:first-child { border-left: 1px solid var(--border-light); }
455
+ .tabela-premium thead th { background: var(--bg-primary); font-weight: 700; color: var(--text-primary); }
456
+
457
+ .coluna-fixa { width: 200px; min-width: 200px; max-width: 200px; text-align: left; background: var(--bg-primary); position: sticky; left: 0; z-index: 2; border-right: 2px solid var(--border-light) !important; }
458
+ .cabecalho-canto { z-index: 3 !important; padding-left: 24px !important; background: var(--bg-primary) !important; }
459
+ .th-barra-dia { padding: 0 !important; height: auto !important; border-top: 1px solid #005291 !important; border-bottom: 1px solid #005291 !important; }
460
+ .th-bloco-principal { color: #111827 !important; padding: 6px 4px !important; height: 36px !important; border-bottom-width: 1.5px !important; border-bottom-style: solid !important; }
461
+ .titulo-bloco-principal { font-size: 13px; font-weight: 800; letter-spacing: 0.3px; line-height: 1.2; text-transform: uppercase; }
462
+ .subtitulo-bloco-principal { font-size: 10.5px; font-weight: 700; opacity: 0.9; }
463
+ .th-subbloco { color: #111827 !important; font-size: 11px !important; font-weight: 700 !important; height: 26px !important; padding: 2px 4px !important; border-bottom-width: 1.5px !important; border-bottom-style: solid !important; }
464
+ .th-horario-individual { height: 42px !important; padding: 4px 2px !important; color: #111827 !important; border-bottom-width: 2px !important; border-bottom-style: solid !important; }
465
+ .empilhado-horarios { display: flex; flex-direction: column; align-items: center; justify-content: center; line-height: 1.15; gap: 2px; }
466
+ .hora-topo, .hora-base { font-size: 11.5px; font-weight: 700; color: #111827; }
467
+
468
+ .td-celula-slot { cursor: pointer; transition: filter 0.12s ease, box-shadow 0.12s ease; vertical-align: middle; color: #111827 !important; }
469
+ .td-celula-slot:hover { filter: brightness(0.93); box-shadow: inset 0 0 0 1.5px var(--color-brand); }
470
+ .slot-selecionado-arrasto { box-shadow: inset 0 0 0 2px var(--color-brand) !important; }
816
471
  .slot-ocupado {
817
472
  font-weight: 700;
818
473
  background-color: #e5e7eb !important;
819
- background-image: repeating-linear-gradient(
820
- 45deg,
821
- rgba(0, 0, 0, 0.06),
822
- rgba(0, 0, 0, 0.06) 10px,
823
- transparent 10px,
824
- transparent 20px
825
- ) !important;
474
+ background-image: repeating-linear-gradient(45deg, rgba(0, 0, 0, 0.06), rgba(0, 0, 0, 0.06) 10px, transparent 10px, transparent 20px) !important;
826
475
  }
827
476
 
828
- .conteudo-slot-matrix {
829
- display: flex;
830
- align-items: center;
831
- justify-content: center;
832
- width: 100%;
833
- height: 100%;
834
- }
835
-
836
- .etiqueta-reserva {
837
- font-size: 12px;
838
- font-weight: 700;
839
- color: #111827;
840
- line-height: 1.25;
841
- word-break: break-word;
842
- }
843
-
844
- .borda-col-subbloco {
845
- border-right-width: 2px !important;
846
- }
847
-
848
- .borda-col-bloco-principal {
849
- border-right-width: 2.5px !important;
850
- }
851
-
852
- .barra-data-full {
853
- display: flex;
854
- align-items: center;
855
- justify-content: center;
856
- width: 100%;
857
- background-color: #0066B3 !important;
858
- color: #FFFFFF !important;
859
- padding: 9px 0;
860
- border-radius: 0;
861
- box-shadow: none;
862
- box-sizing: border-box;
863
- border: none;
864
- text-align: center;
865
- }
866
-
867
- .texto-data {
868
- font-weight: 800;
869
- font-size: 13.5px;
870
- text-transform: uppercase;
871
- letter-spacing: 0.5px;
872
- }
873
-
874
- .nome-recurso {
875
- font-weight: 700;
876
- color: var(--text-primary);
877
- background: var(--bg-primary) !important;
878
- padding-left: 20px !important;
879
- padding-right: 12px !important;
880
- white-space: nowrap;
881
- overflow: hidden;
882
- text-overflow: ellipsis;
883
- box-shadow: 4px 0 8px rgba(0,0,0,0.03);
884
- }
477
+ .conteudo-slot-matrix { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; }
478
+ .etiqueta-reserva { font-size: 12px; font-weight: 700; color: #111827; line-height: 1.25; word-break: break-word; }
479
+ .borda-col-subbloco { border-right-width: 2px !important; }
480
+ .borda-col-bloco-principal { border-right-width: 2.5px !important; }
481
+ .barra-data-full { display: flex; align-items: center; justify-content: center; width: 100%; background-color: #0066B3 !important; color: #FFFFFF !important; padding: 9px 0; box-sizing: border-box; text-align: center; }
482
+ .texto-data { font-weight: 800; font-size: 13.5px; text-transform: uppercase; letter-spacing: 0.5px; }
483
+ .nome-recurso { font-weight: 700; color: var(--text-primary); background: var(--bg-primary) !important; padding-left: 20px !important; padding-right: 12px !important; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; box-shadow: 4px 0 8px rgba(0,0,0,0.03); }
885
484
  </style>
886
485
 
887
486
  <style>
888
- /* CSS injetado globalmente para os blocos da matriz base escurecerem o texto no dark theme */
889
487
  [data-bs-theme="dark"] .th-bloco-principal,
890
488
  [data-bs-theme="dark"] .th-subbloco,
891
489
  [data-bs-theme="dark"] .th-horario-individual,
@@ -898,12 +496,6 @@ const formatarData = (isoData: string) => {
898
496
 
899
497
  [data-bs-theme="dark"] .slot-ocupado {
900
498
  background-color: #374151 !important;
901
- background-image: repeating-linear-gradient(
902
- 45deg,
903
- rgba(255, 255, 255, 0.03),
904
- rgba(255, 255, 255, 0.03) 10px,
905
- transparent 10px,
906
- transparent 20px
907
- ) !important;
499
+ background-image: repeating-linear-gradient(45deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0.03) 10px, transparent 10px, transparent 20px) !important;
908
500
  }
909
501
  </style>