v-sistec-features 1.31.11 → 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.
- package/dist/v-sistec-features.css +1 -1
- package/dist/vDataAgenda.d.ts +595 -18
- package/dist/vDataAgenda.js +3638 -2753
- package/package.json +1 -1
- package/src/DataAgenda/VDataAgenda.vue +167 -182
- package/src/DataAgenda/components/AgendaMatrix.vue +30 -19
- package/src/DataAgenda/components/AgendaMatrixBase.vue +392 -264
- package/src/DataAgenda/components/AgendaMatrixUnico.vue +21 -29
- package/src/DataAgenda/components/AgendaTabelaHorario.vue +506 -0
- package/src/DataAgenda/components/AgendaVueCal.vue +118 -199
- package/src/DataAgenda/components/BlocoArrastavel.vue +62 -0
- package/src/DataAgenda/components/HeaderTabela.vue +13 -4
- package/src/DataAgenda/components/ModalReserva.vue +268 -133
- package/src/DataAgenda/composables/useAgenda.ts +13 -0
- package/src/DataAgenda/composables/useCoresAgenda.ts +40 -0
- package/src/DataAgenda/composables/useGradeCompartilhada.ts +216 -0
- package/src/DataAgenda/composables/useSelecaoArrasto.ts +167 -0
- package/src/DataAgenda/components/AgendaMatrixAntigo.vue +0 -403
|
@@ -1,211 +1,284 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
========
|
|
3
|
-
Este arquivo representa o componente base
|
|
4
|
-
Ele
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Este arquivo representa o componente base da visão em matriz horizontal ('tabela').
|
|
4
|
+
Ele ordena a semana a partir do dia inicial da agenda, exibe a barra de data seguida pelos horários
|
|
5
|
+
daquele dia em cada bloco diário, utiliza o componente genérico BlocoArrastavel para seleção horizontal
|
|
6
|
+
e mescla células de reservas contínuas com colspan.
|
|
7
7
|
========
|
|
8
8
|
-->
|
|
9
9
|
<script setup lang="ts">
|
|
10
|
-
import { computed,
|
|
11
|
-
import {
|
|
10
|
+
import { computed, inject } from 'vue';
|
|
11
|
+
import { useCoresAgenda } from '../composables/useCoresAgenda';
|
|
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';
|
|
21
|
+
import BlocoArrastavel from './BlocoArrastavel.vue';
|
|
12
22
|
import ModalReserva from './ModalReserva.vue';
|
|
13
23
|
import ModalDetalhesReserva from './ModalDetalhesReserva.vue';
|
|
14
24
|
|
|
25
|
+
interface MetadadoColuna {
|
|
26
|
+
corFundo: string | null;
|
|
27
|
+
corBorda: string;
|
|
28
|
+
fimDeSubbloco: boolean;
|
|
29
|
+
fimDeBlocoPrincipal: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
15
32
|
const props = defineProps<{
|
|
16
33
|
idsRecursosVisiveis: string[];
|
|
17
34
|
usaInterceptador?: boolean;
|
|
18
35
|
tituloPadrao?: string;
|
|
19
36
|
ocultarTituloModal?: boolean;
|
|
20
37
|
mensagemVazio?: string;
|
|
38
|
+
configuracaoBlocos?: any;
|
|
21
39
|
}>();
|
|
22
40
|
|
|
23
41
|
const api = inject('api-instance-agenda') as any;
|
|
24
|
-
|
|
42
|
+
defineEmits(['before-save']);
|
|
43
|
+
|
|
44
|
+
const { obterCoresBloco } = useCoresAgenda();
|
|
25
45
|
|
|
26
46
|
const {
|
|
27
47
|
dadosMatrix,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
48
|
+
carregando,
|
|
49
|
+
modalAberto,
|
|
50
|
+
dadosParaModal,
|
|
51
|
+
modalDetalhesAberto,
|
|
52
|
+
reservaSelecionadaDetalhes,
|
|
53
|
+
obterBlocosDoDia,
|
|
54
|
+
dispararBuscaSemanal
|
|
55
|
+
} = useGradeSemanal(api, () => props.configuracaoBlocos);
|
|
56
|
+
|
|
57
|
+
const controladorArrastoHorizontal = useSelecaoArrasto({
|
|
58
|
+
aoClicarItemIndisponivel: (item) => {
|
|
59
|
+
if (item?.slot?.reservado_slot && item?.slot?.detalhes_reserva) {
|
|
60
|
+
reservaSelecionadaDetalhes.value = item.slot.detalhes_reserva;
|
|
61
|
+
modalDetalhesAberto.value = true;
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
aoFinalizarSelecao: ({ itensSelecionados }) => {
|
|
65
|
+
const primeiro = itensSelecionados[0];
|
|
66
|
+
const ultimo = itensSelecionados[itensSelecionados.length - 1];
|
|
67
|
+
if (!primeiro?.slot || !ultimo?.slot) return;
|
|
68
|
+
|
|
69
|
+
const inicio = extrairDataHoraLocal(primeiro.slot.data_inicio_slot);
|
|
70
|
+
const fim = extrairDataHoraLocal(ultimo.slot.data_fim_slot);
|
|
71
|
+
|
|
72
|
+
dadosParaModal.value = {
|
|
73
|
+
id_recurso: Number(primeiro.idRecurso),
|
|
74
|
+
titulo_reserva: '',
|
|
75
|
+
data_inicio: inicio.data,
|
|
76
|
+
hora_inicio: inicio.hora,
|
|
77
|
+
data_fim: fim.data,
|
|
78
|
+
hora_fim: fim.hora
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
modalAberto.value = true;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
46
84
|
|
|
47
|
-
//
|
|
48
|
-
const
|
|
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.
|
|
86
|
+
const construirCabecalhosDeBlocos = (slotsBase: { horaInicio: string; horaFim: string }[], siglaDia: string) => {
|
|
87
|
+
const totalSlots = slotsBase.length;
|
|
88
|
+
const listaBlocosConfig = obterBlocosDoDia(siglaDia);
|
|
89
|
+
|
|
90
|
+
const metadadosColunas: MetadadoColuna[] = slotsBase.map(() => ({
|
|
91
|
+
corFundo: null,
|
|
92
|
+
corBorda: 'var(--border-light)',
|
|
93
|
+
fimDeSubbloco: false,
|
|
94
|
+
fimDeBlocoPrincipal: false
|
|
95
|
+
}));
|
|
96
|
+
|
|
97
|
+
if (!listaBlocosConfig || listaBlocosConfig.length === 0) {
|
|
98
|
+
return {
|
|
99
|
+
temBlocos: false,
|
|
100
|
+
temSubblocos: false,
|
|
101
|
+
blocosPrincipais: [],
|
|
102
|
+
subblocos: [],
|
|
103
|
+
metadadosColunas
|
|
104
|
+
};
|
|
105
|
+
}
|
|
49
106
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
107
|
+
const blocosPrincipais: any[] = [];
|
|
108
|
+
const subblocos: any[] = [];
|
|
109
|
+
let cursor = 0;
|
|
110
|
+
let algumSubblocoDefinido = false;
|
|
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
|
+
};
|
|
55
127
|
|
|
56
|
-
|
|
128
|
+
listaBlocosConfig.forEach((blocoCfg: any, idxBloco: number) => {
|
|
129
|
+
if (cursor >= totalSlots) return;
|
|
57
130
|
|
|
58
|
-
|
|
59
|
-
|
|
131
|
+
const qtdSolicitada = Number(blocoCfg.quantidade) || 0;
|
|
132
|
+
const qtdReal = Math.min(qtdSolicitada, totalSlots - cursor);
|
|
133
|
+
if (qtdReal <= 0) return;
|
|
60
134
|
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
const [
|
|
64
|
-
dataInicio = new Date(Number(ano), Number(mes) - 1, Number(dia));
|
|
65
|
-
} else {
|
|
66
|
-
dataInicio = new Date(valorData);
|
|
67
|
-
}
|
|
135
|
+
const { corFundo: corBloco, corBorda } = obterCoresBloco(idxBloco);
|
|
136
|
+
const slotPrimeiro = slotsBase[cursor];
|
|
137
|
+
const slotUltimo = slotsBase[cursor + qtdReal - 1];
|
|
68
138
|
|
|
69
|
-
|
|
70
|
-
|
|
139
|
+
blocosPrincipais.push({
|
|
140
|
+
nome: blocoCfg.nome || `Bloco ${idxBloco + 1}`,
|
|
141
|
+
colspan: qtdReal,
|
|
142
|
+
cor: corBloco,
|
|
143
|
+
corBorda,
|
|
144
|
+
horaInicio: slotPrimeiro?.horaInicio ?? '',
|
|
145
|
+
horaFim: slotUltimo?.horaFim ?? ''
|
|
146
|
+
});
|
|
71
147
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
148
|
+
for (let i = 0; i < qtdReal; i++) {
|
|
149
|
+
const meta = metadadosColunas[cursor + i];
|
|
150
|
+
if (meta) {
|
|
151
|
+
meta.corFundo = corBloco;
|
|
152
|
+
meta.corBorda = corBorda;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const tamanhoSubFixo = Number(blocoCfg.subdivisao) || 0;
|
|
157
|
+
const listaSubCustom = Array.isArray(blocoCfg.subdivisoes) ? blocoCfg.subdivisoes : null;
|
|
158
|
+
|
|
159
|
+
if (listaSubCustom && listaSubCustom.length > 0) {
|
|
160
|
+
algumSubblocoDefinido = true;
|
|
161
|
+
let consumido = 0;
|
|
162
|
+
listaSubCustom.forEach((tamSub: number) => {
|
|
163
|
+
if (consumido >= qtdReal) return;
|
|
164
|
+
const tamEfetivo = Math.min(Number(tamSub) || 1, qtdReal - consumido);
|
|
165
|
+
const ehUltimo = (consumido + tamEfetivo) >= qtdReal;
|
|
166
|
+
adicionarSubbloco(cursor + consumido, tamEfetivo, corBloco, corBorda, ehUltimo, true);
|
|
167
|
+
consumido += tamEfetivo;
|
|
168
|
+
});
|
|
169
|
+
if (consumido < qtdReal) {
|
|
170
|
+
adicionarSubbloco(cursor + consumido, qtdReal - consumido, corBloco, corBorda, true, true);
|
|
171
|
+
}
|
|
172
|
+
} else if (tamanhoSubFixo > 0) {
|
|
173
|
+
algumSubblocoDefinido = true;
|
|
174
|
+
let consumido = 0;
|
|
175
|
+
while (consumido < qtdReal) {
|
|
176
|
+
const tamEfetivo = Math.min(tamanhoSubFixo, qtdReal - consumido);
|
|
177
|
+
const ehUltimo = (consumido + tamEfetivo) >= qtdReal;
|
|
178
|
+
adicionarSubbloco(cursor + consumido, tamEfetivo, corBloco, corBorda, ehUltimo, true);
|
|
179
|
+
consumido += tamEfetivo;
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
adicionarSubbloco(cursor, qtdReal, corBloco, corBorda, true, false);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const metaFimPrincipal = metadadosColunas[cursor + qtdReal - 1];
|
|
186
|
+
if (metaFimPrincipal) metaFimPrincipal.fimDeBlocoPrincipal = true;
|
|
187
|
+
|
|
188
|
+
cursor += qtdReal;
|
|
189
|
+
});
|
|
78
190
|
|
|
79
|
-
|
|
80
|
-
|
|
191
|
+
if (cursor < totalSlots) {
|
|
192
|
+
const restante = totalSlots - cursor;
|
|
193
|
+
const { corFundo: corRestante, corBorda: corBordaRestante } = obterCoresBloco(blocosPrincipais.length);
|
|
194
|
+
const slotPrimeiro = slotsBase[cursor];
|
|
195
|
+
const slotUltimo = slotsBase[totalSlots - 1];
|
|
196
|
+
|
|
197
|
+
blocosPrincipais.push({
|
|
198
|
+
nome: 'Outros',
|
|
199
|
+
colspan: restante,
|
|
200
|
+
cor: corRestante,
|
|
201
|
+
corBorda: corBordaRestante,
|
|
202
|
+
horaInicio: slotPrimeiro?.horaInicio ?? '',
|
|
203
|
+
horaFim: slotUltimo?.horaFim ?? ''
|
|
204
|
+
});
|
|
81
205
|
|
|
82
|
-
|
|
83
|
-
|
|
206
|
+
for (let i = 0; i < restante; i++) {
|
|
207
|
+
const meta = metadadosColunas[cursor + i];
|
|
208
|
+
if (meta) {
|
|
209
|
+
meta.corFundo = corRestante;
|
|
210
|
+
meta.corBorda = corBordaRestante;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
84
213
|
|
|
85
|
-
|
|
86
|
-
await buscarReservas(`${strInicio}T00:00:00`, `${strFim}T23:59:59`);
|
|
87
|
-
carregando.value = false;
|
|
88
|
-
};
|
|
214
|
+
adicionarSubbloco(cursor, restante, corRestante, corBordaRestante, true, false);
|
|
89
215
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
watch([dataSelecionada, agendaSelecionada], () => {
|
|
95
|
-
dispararBuscaMatrix();
|
|
96
|
-
});
|
|
216
|
+
const metaUltimo = metadadosColunas[totalSlots - 1];
|
|
217
|
+
if (metaUltimo) metaUltimo.fimDeBlocoPrincipal = true;
|
|
218
|
+
}
|
|
97
219
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
220
|
+
return {
|
|
221
|
+
temBlocos: blocosPrincipais.length > 0,
|
|
222
|
+
temSubblocos: algumSubblocoDefinido,
|
|
223
|
+
blocosPrincipais,
|
|
224
|
+
subblocos,
|
|
225
|
+
metadadosColunas
|
|
226
|
+
};
|
|
103
227
|
};
|
|
104
228
|
|
|
105
|
-
//
|
|
106
|
-
const
|
|
229
|
+
// Gera a lista ordenada de dias operacionais com cabeçalhos e células mescladas por colspan.
|
|
230
|
+
const diasProcessados = computed(() => {
|
|
107
231
|
if (!dadosMatrix.value?.datas_matriz) return [];
|
|
108
|
-
const setMinutos = new Set<number>();
|
|
109
|
-
|
|
110
|
-
dadosMatrix.value.datas_matriz.forEach((dia: any) => {
|
|
111
|
-
dia.recursos_slot.forEach((rec: any) => {
|
|
112
|
-
rec.horarios_slot.forEach((slot: any) => {
|
|
113
|
-
setMinutos.add(extrairMinutos(slot.data_inicio_slot));
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
return Array.from(setMinutos).sort((a, b) => a - b);
|
|
119
|
-
});
|
|
120
232
|
|
|
121
|
-
|
|
122
|
-
const matrizProcessada = computed(() => {
|
|
123
|
-
if (!dadosMatrix.value?.datas_matriz || horariosGlobaisEmMinutos.value.length === 0) return [];
|
|
233
|
+
const resultado: any[] = [];
|
|
124
234
|
|
|
125
|
-
|
|
235
|
+
dadosMatrix.value.datas_matriz.forEach((dia: any) => {
|
|
236
|
+
if (dia.dia_operacional_slot === false) return;
|
|
126
237
|
|
|
127
|
-
|
|
128
|
-
const recursosFiltrados = dia.recursos_slot.filter((res: any) =>
|
|
238
|
+
const recursosFiltrados = (dia.recursos_slot || []).filter((res: any) =>
|
|
129
239
|
props.idsRecursosVisiveis.includes(String(res.id_recurso))
|
|
130
240
|
);
|
|
241
|
+
if (recursosFiltrados.length === 0) return;
|
|
242
|
+
|
|
243
|
+
const slotsValidosBase = (recursosFiltrados[0].horarios_slot || []).filter((s: any) => !s.bloqueado_slot);
|
|
244
|
+
if (slotsValidosBase.length === 0) return;
|
|
245
|
+
|
|
246
|
+
const definicaoColunas = slotsValidosBase.map((s: any) => ({
|
|
247
|
+
horaInicio: extrairHoraFormatada(s.data_inicio_slot),
|
|
248
|
+
horaFim: extrairHoraFormatada(s.data_fim_slot)
|
|
249
|
+
}));
|
|
250
|
+
|
|
251
|
+
const siglaDia = SIGLAS_DIAS_JS[criarDataLocal(dia.data_slot).getDay()] || 'SEG';
|
|
252
|
+
const infoBlocos = construirCabecalhosDeBlocos(definicaoColunas, siglaDia);
|
|
253
|
+
|
|
254
|
+
const recursosProcessados = recursosFiltrados.map((rec: any) => {
|
|
255
|
+
const slotsBrutos = (rec.horarios_slot || []).filter((s: any) => !s.bloqueado_slot);
|
|
256
|
+
return {
|
|
257
|
+
id_recurso: rec.id_recurso,
|
|
258
|
+
nome_recurso: rec.nome_recurso,
|
|
259
|
+
slotsBrutos,
|
|
260
|
+
horarios_slot: mesclarSlotsReservados(slotsBrutos, 'colspan')
|
|
261
|
+
};
|
|
262
|
+
});
|
|
131
263
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
let fimMin = extrairMinutos(slot.data_fim_slot);
|
|
138
|
-
|
|
139
|
-
if (fimMin === 0 && new Date(slot.data_fim_slot) > new Date(slot.data_inicio_slot)) {
|
|
140
|
-
fimMin = 24 * 60;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
let tamanhoColspan = 0;
|
|
144
|
-
for (let h = 0; h < mapaHorarios.length; h++) {
|
|
145
|
-
if ((mapaHorarios[h] ?? 0) >= inicioMin && (mapaHorarios[h] ?? 0) < fimMin) {
|
|
146
|
-
tamanhoColspan++;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
return { ...slot, colspan: tamanhoColspan > 0 ? tamanhoColspan : 1 };
|
|
150
|
-
});
|
|
151
|
-
return { ...rec, horarios_slot: slotsCalculados };
|
|
264
|
+
resultado.push({
|
|
265
|
+
data_slot: dia.data_slot,
|
|
266
|
+
colunasHorarios: definicaoColunas,
|
|
267
|
+
...infoBlocos,
|
|
268
|
+
recursos_slot: recursosProcessados
|
|
152
269
|
});
|
|
270
|
+
});
|
|
153
271
|
|
|
154
|
-
|
|
155
|
-
}).filter((dia: any) => dia !== null);
|
|
272
|
+
return resultado;
|
|
156
273
|
});
|
|
157
274
|
|
|
158
|
-
// Recebe minutos totais e devolve a string formatada HH:MM.
|
|
159
|
-
// É usada para exibir os horários no cabeçalho da tabela.
|
|
160
|
-
const formatarHoraDeMinutos = (minutosTotais: number) => {
|
|
161
|
-
const horas = Math.floor(minutosTotais / 60);
|
|
162
|
-
const minutos = minutosTotais % 60;
|
|
163
|
-
return `${String(horas).padStart(2, '0')}:${String(minutos).padStart(2, '0')}`;
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
// Recebe a string YYYY-MM-DD do dia e devolve o texto por extenso em português.
|
|
167
|
-
// É usada nas barras divisórias de cada dia na tabela.
|
|
168
275
|
const formatarData = (isoData: string) => {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
// Recebe o ID do recurso e o objeto slot clicado e não devolve retorno.
|
|
176
|
-
// Se estiver reservado, abre o modal de detalhes; se estiver livre e reservável, abre o modal de criação.
|
|
177
|
-
const aoClicarSlotMatrix = (idRecurso: number, slot: any) => {
|
|
178
|
-
if (slot.reservado_slot && slot.detalhes_reserva) {
|
|
179
|
-
reservaSelecionadaDetalhes.value = slot.detalhes_reserva;
|
|
180
|
-
modalDetalhesAberto.value = true;
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (!slot.reservavel_slot) return;
|
|
185
|
-
|
|
186
|
-
const extrair = (isoString: string) => {
|
|
187
|
-
const d = new Date(isoString);
|
|
188
|
-
const ano = d.getFullYear();
|
|
189
|
-
const mes = String(d.getMonth() + 1).padStart(2, '0');
|
|
190
|
-
const dia = String(d.getDate()).padStart(2, '0');
|
|
191
|
-
const horas = String(d.getHours()).padStart(2, '0');
|
|
192
|
-
const min = String(d.getMinutes()).padStart(2, '0');
|
|
193
|
-
return { data: `${ano}-${mes}-${dia}`, hora: `${horas}:${min}` };
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
const inicio = extrair(slot.data_inicio_slot);
|
|
197
|
-
const fim = extrair(slot.data_fim_slot);
|
|
198
|
-
|
|
199
|
-
dadosParaModal.value = {
|
|
200
|
-
id_recurso: idRecurso,
|
|
201
|
-
titulo_reserva: '',
|
|
202
|
-
data_inicio: inicio.data,
|
|
203
|
-
hora_inicio: inicio.hora,
|
|
204
|
-
data_fim: fim.data,
|
|
205
|
-
hora_fim: fim.hora
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
modalAberto.value = true;
|
|
276
|
+
return criarDataLocal(isoData).toLocaleDateString('pt-BR', {
|
|
277
|
+
weekday: 'long',
|
|
278
|
+
day: '2-digit',
|
|
279
|
+
month: 'long',
|
|
280
|
+
year: 'numeric'
|
|
281
|
+
});
|
|
209
282
|
};
|
|
210
283
|
</script>
|
|
211
284
|
|
|
@@ -216,11 +289,11 @@ const aoClicarSlotMatrix = (idRecurso: number, slot: any) => {
|
|
|
216
289
|
:dados-iniciais="dadosParaModal"
|
|
217
290
|
:usa-interceptador="props.usaInterceptador"
|
|
218
291
|
:ocultar-titulo-modal="props.ocultarTituloModal"
|
|
219
|
-
@before-save="$emit('before-save'
|
|
220
|
-
@salvo="
|
|
292
|
+
@before-save="$emit('before-save', $event)"
|
|
293
|
+
@salvo="dispararBuscaSemanal"
|
|
221
294
|
>
|
|
222
|
-
<template #
|
|
223
|
-
<slot name="
|
|
295
|
+
<template v-for="(_, nomeSlot) in $slots" :key="nomeSlot" #[nomeSlot]="slotProps">
|
|
296
|
+
<slot v-if="nomeSlot !== 'topo'" :name="nomeSlot" v-bind="slotProps ?? {}"></slot>
|
|
224
297
|
</template>
|
|
225
298
|
</ModalReserva>
|
|
226
299
|
|
|
@@ -228,7 +301,7 @@ const aoClicarSlotMatrix = (idRecurso: number, slot: any) => {
|
|
|
228
301
|
v-model="modalDetalhesAberto"
|
|
229
302
|
:reserva="reservaSelecionadaDetalhes"
|
|
230
303
|
:titulo-padrao="props.tituloPadrao"
|
|
231
|
-
@excluido="
|
|
304
|
+
@excluido="dispararBuscaSemanal"
|
|
232
305
|
/>
|
|
233
306
|
|
|
234
307
|
<div class="aviso-vazio" v-if="carregando">
|
|
@@ -239,52 +312,125 @@ const aoClicarSlotMatrix = (idRecurso: number, slot: any) => {
|
|
|
239
312
|
<div class="matriz-conteudo" v-else-if="dadosMatrix.datas_matriz">
|
|
240
313
|
<slot name="topo"></slot>
|
|
241
314
|
|
|
242
|
-
<div class="
|
|
243
|
-
<
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
</tr>
|
|
251
|
-
</thead>
|
|
252
|
-
<tbody>
|
|
253
|
-
<template v-for="dia in matrizProcessada" :key="dia.data_slot">
|
|
315
|
+
<div class="tabelas-scroll-container" v-if="diasProcessados.length > 0">
|
|
316
|
+
<div
|
|
317
|
+
class="bloco-dia-tabela"
|
|
318
|
+
v-for="dia in diasProcessados"
|
|
319
|
+
:key="dia.data_slot"
|
|
320
|
+
>
|
|
321
|
+
<table class="tabela-premium">
|
|
322
|
+
<thead>
|
|
254
323
|
<tr class="linha-data">
|
|
255
|
-
<
|
|
324
|
+
<th :colspan="dia.colunasHorarios.length + 1" class="th-barra-dia">
|
|
256
325
|
<div class="barra-data-full">
|
|
257
326
|
<span class="texto-data">{{ formatarData(dia.data_slot) }}</span>
|
|
258
327
|
</div>
|
|
259
|
-
</
|
|
328
|
+
</th>
|
|
260
329
|
</tr>
|
|
261
330
|
|
|
331
|
+
<tr v-if="dia.temBlocos" class="linha-blocos-principais">
|
|
332
|
+
<th
|
|
333
|
+
class="coluna-fixa cabecalho-canto"
|
|
334
|
+
:rowspan="dia.temSubblocos ? 3 : 2"
|
|
335
|
+
>
|
|
336
|
+
Nome
|
|
337
|
+
</th>
|
|
338
|
+
<th
|
|
339
|
+
v-for="(bloco, idxB) in dia.blocosPrincipais"
|
|
340
|
+
:key="idxB"
|
|
341
|
+
:colspan="bloco.colspan"
|
|
342
|
+
:style="{
|
|
343
|
+
backgroundColor: bloco.cor,
|
|
344
|
+
borderColor: bloco.corBorda
|
|
345
|
+
}"
|
|
346
|
+
class="th-bloco-principal borda-col-bloco-principal"
|
|
347
|
+
>
|
|
348
|
+
<div class="titulo-bloco-principal">{{ bloco.nome }}</div>
|
|
349
|
+
<div class="subtitulo-bloco-principal">{{ bloco.horaInicio }} - {{ bloco.horaFim }}</div>
|
|
350
|
+
</th>
|
|
351
|
+
</tr>
|
|
352
|
+
|
|
353
|
+
<tr v-if="dia.temBlocos && dia.temSubblocos" class="linha-subblocos">
|
|
354
|
+
<th
|
|
355
|
+
v-for="(sub, idxS) in dia.subblocos"
|
|
356
|
+
:key="idxS"
|
|
357
|
+
:colspan="sub.colspan"
|
|
358
|
+
:style="{
|
|
359
|
+
backgroundColor: sub.cor,
|
|
360
|
+
borderColor: sub.corBorda
|
|
361
|
+
}"
|
|
362
|
+
class="th-subbloco"
|
|
363
|
+
:class="{
|
|
364
|
+
'borda-col-bloco-principal': sub.fimDeBlocoPrincipal,
|
|
365
|
+
'borda-col-subbloco': !sub.fimDeBlocoPrincipal
|
|
366
|
+
}"
|
|
367
|
+
>
|
|
368
|
+
{{ sub.etiqueta }}
|
|
369
|
+
</th>
|
|
370
|
+
</tr>
|
|
371
|
+
|
|
372
|
+
<tr class="linha-horarios-individuais">
|
|
373
|
+
<th v-if="!dia.temBlocos" class="coluna-fixa cabecalho-canto">Nome</th>
|
|
374
|
+
<th
|
|
375
|
+
v-for="(col, idxH) in dia.colunasHorarios"
|
|
376
|
+
:key="idxH"
|
|
377
|
+
:style="{
|
|
378
|
+
backgroundColor: dia.metadadosColunas[idxH]?.corFundo || 'var(--bg-primary)',
|
|
379
|
+
borderColor: dia.metadadosColunas[idxH]?.corBorda || 'var(--border-light)'
|
|
380
|
+
}"
|
|
381
|
+
class="th-horario-individual"
|
|
382
|
+
:class="{
|
|
383
|
+
'borda-col-subbloco': dia.metadadosColunas[idxH]?.fimDeSubbloco,
|
|
384
|
+
'borda-col-bloco-principal': dia.metadadosColunas[idxH]?.fimDeBlocoPrincipal
|
|
385
|
+
}"
|
|
386
|
+
>
|
|
387
|
+
<div class="empilhado-horarios">
|
|
388
|
+
<span class="hora-topo">{{ col.horaInicio }}</span>
|
|
389
|
+
<span class="hora-base">{{ col.horaFim }}</span>
|
|
390
|
+
</div>
|
|
391
|
+
</th>
|
|
392
|
+
</tr>
|
|
393
|
+
</thead>
|
|
394
|
+
|
|
395
|
+
<tbody>
|
|
262
396
|
<tr v-for="recurso in dia.recursos_slot" :key="recurso.id_recurso">
|
|
263
397
|
<td class="coluna-fixa nome-recurso">{{ recurso.nome_recurso }}</td>
|
|
264
|
-
<
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
398
|
+
<template v-for="slot in recurso.horarios_slot" :key="slot.data_inicio_slot">
|
|
399
|
+
<BlocoArrastavel
|
|
400
|
+
v-if="!slot.ocultaPorColspan"
|
|
401
|
+
tag="td"
|
|
402
|
+
:grupo="`${dia.data_slot}__${recurso.id_recurso}`"
|
|
403
|
+
:indice="slot.indiceColuna"
|
|
404
|
+
:item="{ slot, idRecurso: recurso.id_recurso, dataSlot: dia.data_slot }"
|
|
405
|
+
:disponivel="Boolean(slot.reservavel_slot && !slot.reservado_slot)"
|
|
406
|
+
:controlador="controladorArrastoHorizontal"
|
|
407
|
+
:colspan="slot.colspan || 1"
|
|
408
|
+
class="td-celula-slot"
|
|
271
409
|
:class="{
|
|
272
|
-
'
|
|
273
|
-
'
|
|
274
|
-
'
|
|
275
|
-
'
|
|
410
|
+
'slot-ocupado': slot.reservado_slot,
|
|
411
|
+
'slot-api': slot.detalhes_reserva?.origem_criacao_reserva === 'API',
|
|
412
|
+
'slot-selecionado-arrasto': controladorArrastoHorizontal.verificarSelecionado(`${dia.data_slot}__${recurso.id_recurso}`, slot.indiceColuna),
|
|
413
|
+
'borda-col-subbloco': dia.metadadosColunas[slot.indiceColuna + (slot.colspan || 1) - 1]?.fimDeSubbloco,
|
|
414
|
+
'borda-col-bloco-principal': dia.metadadosColunas[slot.indiceColuna + (slot.colspan || 1) - 1]?.fimDeBlocoPrincipal
|
|
415
|
+
}"
|
|
416
|
+
:style="{
|
|
417
|
+
backgroundColor: controladorArrastoHorizontal.verificarSelecionado(`${dia.data_slot}__${recurso.id_recurso}`, slot.indiceColuna)
|
|
418
|
+
? 'var(--color-brand-light)'
|
|
419
|
+
: (dia.metadadosColunas[slot.indiceColuna]?.corFundo || 'var(--bg-primary)'),
|
|
420
|
+
borderColor: dia.metadadosColunas[slot.indiceColuna + (slot.colspan || 1) - 1]?.corBorda || 'var(--border-light)'
|
|
276
421
|
}"
|
|
277
|
-
@click="aoClicarSlotMatrix(recurso.id_recurso, slot)"
|
|
278
422
|
>
|
|
279
|
-
<
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
423
|
+
<div class="conteudo-slot-matrix" v-if="slot.reservado_slot">
|
|
424
|
+
<span class="etiqueta-reserva">
|
|
425
|
+
{{ slot.detalhes_reserva?.titulo_reserva?.trim() || props.tituloPadrao || 'Reservado' }}
|
|
426
|
+
</span>
|
|
427
|
+
</div>
|
|
428
|
+
</BlocoArrastavel>
|
|
429
|
+
</template>
|
|
284
430
|
</tr>
|
|
285
|
-
</
|
|
286
|
-
</
|
|
287
|
-
</
|
|
431
|
+
</tbody>
|
|
432
|
+
</table>
|
|
433
|
+
</div>
|
|
288
434
|
</div>
|
|
289
435
|
|
|
290
436
|
<div class="aviso-vazio" v-else>
|
|
@@ -295,79 +441,61 @@ const aoClicarSlotMatrix = (idRecurso: number, slot: any) => {
|
|
|
295
441
|
</template>
|
|
296
442
|
|
|
297
443
|
<style scoped>
|
|
298
|
-
.matriz-container { width: 100%; height: 100%; background: var(--bg-primary); display: flex; flex-direction: column; }
|
|
444
|
+
.matriz-container { width: 100%; height: 100%; background: var(--bg-primary); display: flex; flex-direction: column; user-select: none; }
|
|
299
445
|
.matriz-conteudo { display: flex; flex-direction: column; height: 100%; overflow: hidden; }
|
|
300
446
|
.aviso-vazio { display: flex; flex-direction: column; justify-content: center; align-items: center; flex-grow: 1; color: var(--text-secondary); font-weight: 500; font-size: 1rem; gap: 12px; }
|
|
301
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; }
|
|
302
448
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
303
|
-
.tabela-wrapper {
|
|
304
|
-
flex-grow: 1;
|
|
305
|
-
overflow: auto;
|
|
306
|
-
background: var(--bg-primary);
|
|
307
|
-
}
|
|
308
|
-
.tabela-premium { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 12.5px; text-align: center; table-layout: fixed; }
|
|
309
|
-
.tabela-premium th, .tabela-premium td { padding: 3px; min-width: 65px; height: 48px; border-bottom: 1px solid var(--border-light); border-right: 1px solid var(--border-light); }
|
|
310
|
-
.tabela-premium th:first-child, .tabela-premium td:first-child { border-left: 1px solid var(--border-light); }
|
|
311
|
-
.tabela-premium thead th { border-top: 1px solid var(--border-light); background: var(--bg-primary); position: sticky; top: 0; z-index: 3; font-weight: 700; color: var(--text-primary); box-shadow: var(--shadow-sm); }
|
|
312
|
-
.coluna-fixa { width: 200px; text-align: left; background: var(--bg-primary); position: sticky; left: 0; z-index: 1; }
|
|
313
|
-
.cabecalho-canto { z-index: 4 !important; padding-left: 24px !important; }
|
|
314
449
|
|
|
315
|
-
|
|
316
|
-
.tabela
|
|
317
|
-
|
|
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; }
|
|
471
|
+
.slot-ocupado {
|
|
472
|
+
font-weight: 700;
|
|
473
|
+
background-color: #e5e7eb !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;
|
|
318
475
|
}
|
|
319
476
|
|
|
320
|
-
|
|
321
|
-
.
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
background: color-mix(in srgb, var(--color-brand) 30%, white) !important;
|
|
328
|
-
color: var(--text-primary);
|
|
329
|
-
padding: 10px 0;
|
|
330
|
-
border-radius: 0;
|
|
331
|
-
box-shadow: none;
|
|
332
|
-
box-sizing: border-box;
|
|
333
|
-
border: none;
|
|
334
|
-
text-align: center;
|
|
335
|
-
}
|
|
336
|
-
.texto-data {
|
|
337
|
-
|
|
338
|
-
font-weight: 700; font-size: 13.5px; text-transform: capitalize; letter-spacing: 0.5px; }
|
|
339
|
-
.nome-recurso {
|
|
340
|
-
font-weight: 600;
|
|
341
|
-
color: var(--text-primary);
|
|
342
|
-
background: var(--bg-primary) !important;
|
|
343
|
-
padding-left: 24px !important;
|
|
344
|
-
box-shadow: 4px 0 8px rgba(0,0,0,0.02);
|
|
345
|
-
}
|
|
346
|
-
.linha-data td {
|
|
347
|
-
background: var(--bg-secondary) !important;
|
|
348
|
-
border: none !important;
|
|
349
|
-
border-top: 1px solid var(--border-light) !important;
|
|
350
|
-
border-bottom: 1px solid var(--border-light) !important;
|
|
351
|
-
padding: 0 !important;
|
|
352
|
-
}
|
|
353
|
-
.bloco-tempo { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; border-radius: 6px; transition: all 0.2s ease; box-sizing: border-box; padding: 4px; }
|
|
354
|
-
.bloqueado { background-color: var(--border-light); background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, rgba(0, 0, 0, 0.08) 8px, rgba(0, 0, 0, 0.08) 16px); cursor: not-allowed; border: 1px solid var(--border-editor); }
|
|
355
|
-
.livre { background-color: transparent; cursor: pointer; }
|
|
356
|
-
.livre:hover { background-color: var(--color-brand-light); box-shadow: inset 0 0 0 1px var(--color-brand); }
|
|
357
|
-
.reservado { background: var(--color-brand); color: var(--text-inverse); border: 1px solid var(--color-brand); cursor: pointer; }
|
|
358
|
-
.reservado:hover { filter: brightness(1.08); }
|
|
359
|
-
.api-forjada { background: var(--color-success); color: var(--text-inverse); border: 1px solid var(--color-success); cursor: pointer; }
|
|
360
|
-
.etiqueta-reserva { font-size: 11.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; width: 100%; font-weight: 600; }
|
|
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); }
|
|
361
484
|
</style>
|
|
362
485
|
|
|
363
486
|
<style>
|
|
364
|
-
|
|
365
|
-
[data-bs-theme="dark"] .
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
487
|
+
[data-bs-theme="dark"] .th-bloco-principal,
|
|
488
|
+
[data-bs-theme="dark"] .th-subbloco,
|
|
489
|
+
[data-bs-theme="dark"] .th-horario-individual,
|
|
490
|
+
[data-bs-theme="dark"] .hora-topo,
|
|
491
|
+
[data-bs-theme="dark"] .hora-base,
|
|
492
|
+
[data-bs-theme="dark"] .td-celula-slot,
|
|
493
|
+
[data-bs-theme="dark"] .etiqueta-reserva {
|
|
494
|
+
color: var(--text-primary) !important;
|
|
371
495
|
}
|
|
372
496
|
|
|
497
|
+
[data-bs-theme="dark"] .slot-ocupado {
|
|
498
|
+
background-color: #374151 !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;
|
|
500
|
+
}
|
|
373
501
|
</style>
|