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
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
========
|
|
3
3
|
Este arquivo representa o componente de calendário visual utilizando a biblioteca vue-cal.
|
|
4
4
|
Ele exibe e gerencia os eventos e reservas na interface, permitindo a criação de novos agendamentos,
|
|
5
|
-
a abertura do modal de detalhes ao clicar em reservas existentes
|
|
5
|
+
a abertura do modal de detalhes ao clicar em reservas existentes, a navegação entre datas e a exposição
|
|
6
|
+
da lista contendo o recurso atualmente em exibição.
|
|
6
7
|
========
|
|
7
8
|
-->
|
|
8
9
|
<script setup lang="ts">
|
|
@@ -10,6 +11,7 @@ import { ref, computed, inject } from 'vue';
|
|
|
10
11
|
import { VueCal } from 'vue-cal';
|
|
11
12
|
import 'vue-cal/style.css';
|
|
12
13
|
import { useAgenda } from '../composables/useAgenda';
|
|
14
|
+
import { extrairDataHoraLocal, formatarDataIso } from '../composables/useGradeCompartilhada';
|
|
13
15
|
import ModalReserva from './ModalReserva.vue';
|
|
14
16
|
import ModalDetalhesReserva from './ModalDetalhesReserva.vue';
|
|
15
17
|
|
|
@@ -22,10 +24,11 @@ const props = defineProps({
|
|
|
22
24
|
ocultarTituloModal: { type: Boolean, default: false }
|
|
23
25
|
});
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
defineEmits(['before-save']);
|
|
26
28
|
|
|
27
29
|
const {
|
|
28
30
|
reservasBrutas,
|
|
31
|
+
recursosDisponiveis,
|
|
29
32
|
recursoSelecionado,
|
|
30
33
|
dataSelecionada,
|
|
31
34
|
dataVisao,
|
|
@@ -36,38 +39,34 @@ const {
|
|
|
36
39
|
buscarReservas
|
|
37
40
|
} = useAgenda(api);
|
|
38
41
|
|
|
39
|
-
// Armazena a referência direta à instância do componente vue-cal.
|
|
40
42
|
const calendarioRef = ref<any>(null);
|
|
41
|
-
|
|
42
|
-
// Controla o estado de visibilidade da janela modal de criação de reservas.
|
|
43
43
|
const modalAberto = ref(false);
|
|
44
|
-
|
|
45
|
-
// Armazena a função de resolução interna (Promise) fornecida pelo vue-cal durante a criação de um evento.
|
|
46
44
|
const resolverCriacaoVueCal = ref<any>(null);
|
|
47
|
-
|
|
48
|
-
// Armazena os dados processados resultantes de um clique na grade para alimentar os campos do modal de criação.
|
|
49
45
|
const dadosParaModal = ref({});
|
|
50
|
-
|
|
51
|
-
// Controla a visibilidade do modal de detalhes de uma reserva já existente.
|
|
52
46
|
const modalDetalhesAberto = ref(false);
|
|
53
|
-
|
|
54
|
-
// Armazena o objeto bruto da reserva clicada no calendário para exibição detalhada.
|
|
55
47
|
const reservaSelecionadaDetalhes = ref<any>(null);
|
|
56
48
|
|
|
49
|
+
const recursosEmExibicao = computed(() => {
|
|
50
|
+
if (!recursoSelecionado.value) return [];
|
|
51
|
+
const encontrado = recursosDisponiveis.value.find(
|
|
52
|
+
(r: any) => Number(r.id_recurso) === Number(recursoSelecionado.value)
|
|
53
|
+
);
|
|
54
|
+
return encontrado ? [encontrado] : [];
|
|
55
|
+
});
|
|
56
|
+
|
|
57
57
|
defineExpose({
|
|
58
|
+
recursosEmExibicao,
|
|
58
59
|
irParaHoje: () => calendarioRef.value?.view?.goToToday(),
|
|
59
60
|
periodoAnterior: () => calendarioRef.value?.view?.previous(),
|
|
60
61
|
periodoProximo: () => calendarioRef.value?.view?.next(),
|
|
61
62
|
mudarVisao: (v: string) => { visaoAtiva.value = v; calendarioRef.value?.view?.switch(v); }
|
|
62
63
|
});
|
|
63
64
|
|
|
64
|
-
// Recebe um objeto de evento opcional e não devolve retorno.
|
|
65
|
-
// É usada para deduzir o período atualmente em exibição no calendário e buscar as reservas correspondentes na API.
|
|
66
65
|
const atualizarVisualizacao = (evento: any = null) => {
|
|
67
66
|
let dataInicio: Date;
|
|
68
67
|
let dataFim: Date;
|
|
69
68
|
|
|
70
|
-
if (evento
|
|
69
|
+
if (evento?.startDate && evento?.endDate) {
|
|
71
70
|
dataInicio = new Date(evento.startDate);
|
|
72
71
|
dataFim = new Date(evento.endDate);
|
|
73
72
|
} else {
|
|
@@ -84,35 +83,9 @@ const atualizarVisualizacao = (evento: any = null) => {
|
|
|
84
83
|
}
|
|
85
84
|
}
|
|
86
85
|
|
|
87
|
-
|
|
88
|
-
const ano = d.getFullYear();
|
|
89
|
-
const mes = String(d.getMonth() + 1).padStart(2, '0');
|
|
90
|
-
const dia = String(d.getDate()).padStart(2, '0');
|
|
91
|
-
return `${ano}-${mes}-${dia}`;
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
buscarReservas(`${formatar(dataInicio)}T00:00:00`, `${formatar(dataFim)}T23:59:59`);
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// Recebe um objeto Date nativo e devolve uma string estruturada no formato YYYY-MM-DD.
|
|
98
|
-
// É usada para adequar instâncias de data ao formato exigido pelos inputs tipo "date" do HTML5.
|
|
99
|
-
const formatarDataInput = (d: Date) => {
|
|
100
|
-
const ano = d.getFullYear();
|
|
101
|
-
const mes = String(d.getMonth() + 1).padStart(2, '0');
|
|
102
|
-
const dia = String(d.getDate()).padStart(2, '0');
|
|
103
|
-
return `${ano}-${mes}-${dia}`;
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
// Recebe um objeto Date nativo e devolve uma string estruturada no formato HH:mm.
|
|
107
|
-
// É usada para adequar instâncias de data ao formato exigido pelos inputs tipo "time" do HTML5.
|
|
108
|
-
const formatarHoraInput = (d: Date) => {
|
|
109
|
-
const horas = String(d.getHours()).padStart(2, '0');
|
|
110
|
-
const minutos = String(d.getMinutes()).padStart(2, '0');
|
|
111
|
-
return `${horas}:${minutos}`;
|
|
86
|
+
buscarReservas(`${formatarDataIso(dataInicio)}T00:00:00`, `${formatarDataIso(dataFim)}T23:59:59`);
|
|
112
87
|
};
|
|
113
88
|
|
|
114
|
-
// Recebe um objeto contendo os detalhes gerados pelo vue-cal e a função finalizadora da promessa.
|
|
115
|
-
// É usada para interceptar a criação de evento na grade e abrir o modal com os horários preenchidos.
|
|
116
89
|
const aoCriarEvento = ({ event, resolve }: any) => {
|
|
117
90
|
resolverCriacaoVueCal.value = resolve;
|
|
118
91
|
|
|
@@ -122,20 +95,21 @@ const aoCriarEvento = ({ event, resolve }: any) => {
|
|
|
122
95
|
if (isNaN(start.getTime())) start = new Date();
|
|
123
96
|
if (isNaN(end.getTime())) end = new Date();
|
|
124
97
|
|
|
98
|
+
const inicio = extrairDataHoraLocal(start.toISOString());
|
|
99
|
+
const fim = extrairDataHoraLocal(end.toISOString());
|
|
100
|
+
|
|
125
101
|
dadosParaModal.value = {
|
|
126
102
|
id_recurso: recursoSelecionado.value,
|
|
127
103
|
titulo_reserva: '',
|
|
128
|
-
data_inicio:
|
|
129
|
-
hora_inicio:
|
|
130
|
-
data_fim:
|
|
131
|
-
hora_fim:
|
|
104
|
+
data_inicio: inicio.data,
|
|
105
|
+
hora_inicio: inicio.hora,
|
|
106
|
+
data_fim: fim.data,
|
|
107
|
+
hora_fim: fim.hora
|
|
132
108
|
};
|
|
133
109
|
|
|
134
110
|
modalAberto.value = true;
|
|
135
111
|
};
|
|
136
112
|
|
|
137
|
-
// Recebe o objeto de evento disparado pelo clique em uma reserva no vue-cal e não devolve retorno.
|
|
138
|
-
// É usada para localizar a reserva correspondente em reservasBrutas e abrir o modal de detalhes.
|
|
139
113
|
const aoClicarEvento = (eventoVueCal: any) => {
|
|
140
114
|
const idClicado = eventoVueCal?.id || eventoVueCal?.event?.id;
|
|
141
115
|
if (!idClicado) return;
|
|
@@ -150,88 +124,56 @@ const aoClicarEvento = (eventoVueCal: any) => {
|
|
|
150
124
|
}
|
|
151
125
|
};
|
|
152
126
|
|
|
153
|
-
|
|
154
|
-
// É usada para encerrar a promessa pendente do vue-cal e atualizar a grade após salvar uma nova reserva.
|
|
155
|
-
const aoSalvar = () => {
|
|
127
|
+
const encerrarCriacaoProvisoria = () => {
|
|
156
128
|
if (resolverCriacaoVueCal.value) {
|
|
157
129
|
resolverCriacaoVueCal.value(false);
|
|
158
130
|
resolverCriacaoVueCal.value = null;
|
|
159
131
|
}
|
|
160
|
-
atualizarVisualizacao();
|
|
161
132
|
};
|
|
162
133
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (resolverCriacaoVueCal.value) {
|
|
167
|
-
resolverCriacaoVueCal.value(false);
|
|
168
|
-
resolverCriacaoVueCal.value = null;
|
|
169
|
-
}
|
|
134
|
+
const aoSalvar = () => {
|
|
135
|
+
encerrarCriacaoProvisoria();
|
|
136
|
+
atualizarVisualizacao();
|
|
170
137
|
};
|
|
171
138
|
|
|
172
139
|
type FormatoDataVueCal = `${number}${number}${number}${number}-${number}${number}-${number}${number} ${number}${number}:${number}${number}`;
|
|
173
140
|
|
|
174
|
-
// Recebe a lista de reservas padronizadas da API e devolve a coleção formatada para o componente vue-cal.
|
|
175
|
-
// É usada para converter os horários UTC para o fuso local do navegador e definir o estilo visual conforme a autoria.
|
|
176
|
-
const mapearEventosVisuais = (reservas: any[]) => {
|
|
177
|
-
const formatarDataLocal = (dataIsoUtc: string): FormatoDataVueCal => {
|
|
178
|
-
const d = new Date(dataIsoUtc);
|
|
179
|
-
const ano = d.getFullYear();
|
|
180
|
-
const mes = String(d.getMonth() + 1).padStart(2, '0');
|
|
181
|
-
const dia = String(d.getDate()).padStart(2, '0');
|
|
182
|
-
const horas = String(d.getHours()).padStart(2, '0');
|
|
183
|
-
const minutos = String(d.getMinutes()).padStart(2, '0');
|
|
184
|
-
return `${ano}-${mes}-${dia} ${horas}:${minutos}` as FormatoDataVueCal;
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
return reservas.map(reserva => {
|
|
188
|
-
const identificadorAutor = reserva.email_responsavel_reserva || reserva.usuario_responsavel_reserva;
|
|
189
|
-
const ehMeu = Boolean(
|
|
190
|
-
identificadorAutor &&
|
|
191
|
-
props.usuarioLogado &&
|
|
192
|
-
String(identificadorAutor).toLowerCase() === props.usuarioLogado.toLowerCase()
|
|
193
|
-
);
|
|
194
|
-
const tituloLimpo = reserva.titulo_reserva ? String(reserva.titulo_reserva).trim() : '';
|
|
195
|
-
|
|
196
|
-
return {
|
|
197
|
-
id: reserva.codigo_referencia_reserva,
|
|
198
|
-
title: tituloLimpo || props.tituloPadrao,
|
|
199
|
-
start: formatarDataLocal(reserva.data_inicio_reserva),
|
|
200
|
-
end: formatarDataLocal(reserva.data_fim_reserva),
|
|
201
|
-
id_recurso: reserva.id_recurso,
|
|
202
|
-
draggable: ehMeu,
|
|
203
|
-
resizable: ehMeu,
|
|
204
|
-
deletable: ehMeu,
|
|
205
|
-
class: ehMeu ? 'reserva-minha' : 'reserva-bloqueada'
|
|
206
|
-
};
|
|
207
|
-
});
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
// Armazena a lista reativa de reservas filtradas exclusivamente para o recurso selecionado no momento.
|
|
211
141
|
const eventosProcessados = computed(() => {
|
|
212
142
|
if (!recursoSelecionado.value) return [];
|
|
213
143
|
|
|
214
|
-
return
|
|
215
|
-
Number(
|
|
216
|
-
|
|
144
|
+
return reservasBrutas.value
|
|
145
|
+
.filter(reserva => Number(reserva.id_recurso) === Number(recursoSelecionado.value))
|
|
146
|
+
.map(reserva => {
|
|
147
|
+
const identificadorAutor = reserva.email_responsavel_reserva || reserva.usuario_responsavel_reserva;
|
|
148
|
+
const ehMeu = Boolean(
|
|
149
|
+
identificadorAutor &&
|
|
150
|
+
props.usuarioLogado &&
|
|
151
|
+
String(identificadorAutor).toLowerCase() === props.usuarioLogado.toLowerCase()
|
|
152
|
+
);
|
|
153
|
+
const ini = extrairDataHoraLocal(reserva.data_inicio_reserva);
|
|
154
|
+
const fim = extrairDataHoraLocal(reserva.data_fim_reserva);
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
id: reserva.codigo_referencia_reserva,
|
|
158
|
+
title: String(reserva.titulo_reserva || '').trim() || props.tituloPadrao,
|
|
159
|
+
start: `${ini.data} ${ini.hora}` as FormatoDataVueCal,
|
|
160
|
+
end: `${fim.data} ${fim.hora}` as FormatoDataVueCal,
|
|
161
|
+
id_recurso: reserva.id_recurso,
|
|
162
|
+
draggable: ehMeu,
|
|
163
|
+
resizable: ehMeu,
|
|
164
|
+
deletable: ehMeu,
|
|
165
|
+
class: ehMeu ? 'reserva-minha' : 'reserva-bloqueada'
|
|
166
|
+
};
|
|
167
|
+
});
|
|
217
168
|
});
|
|
218
169
|
|
|
219
|
-
// Recebe uma data do cabeçalho do calendário e devolve um booleano indicando se é a data selecionada.
|
|
220
|
-
// É usada para destacar visualmente o dia ativo no topo da grade.
|
|
221
170
|
const ehDataSelecionada = (data: any) => {
|
|
222
171
|
if (!dataSelecionada.value || !data) return false;
|
|
223
|
-
|
|
224
|
-
const dCabecalho = new Date(data);
|
|
225
|
-
return dCabecalho.toDateString() === dSelecionada.toDateString();
|
|
172
|
+
return new Date(data).toDateString() === new Date(dataSelecionada.value).toDateString();
|
|
226
173
|
};
|
|
227
174
|
|
|
228
|
-
|
|
229
|
-
// É usada na renderização customizada do cabeçalho dos dias.
|
|
230
|
-
const obterNomeDiaCurto = (data: Date) => {
|
|
231
|
-
return ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'][data.getDay()];
|
|
232
|
-
};
|
|
175
|
+
const obterNomeDiaCurto = (data: Date) => ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'][data.getDay()];
|
|
233
176
|
|
|
234
|
-
// Armazena a configuração de horários fora do expediente para bloqueio visual no componente vue-cal.
|
|
235
177
|
const horasBloqueadasNativas = computed(() => {
|
|
236
178
|
const bloqueios = [
|
|
237
179
|
{ from: 0, to: minutosInicio.value, allowEvents: false, class: 'fora-do-expediente' },
|
|
@@ -242,76 +184,71 @@ const horasBloqueadasNativas = computed(() => {
|
|
|
242
184
|
</script>
|
|
243
185
|
|
|
244
186
|
<template>
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
>
|
|
255
|
-
<template #campos-extras>
|
|
256
|
-
<slot name="campos-extras"></slot>
|
|
257
|
-
</template>
|
|
258
|
-
</ModalReserva>
|
|
259
|
-
|
|
260
|
-
<ModalDetalhesReserva
|
|
261
|
-
v-model="modalDetalhesAberto"
|
|
262
|
-
:reserva="reservaSelecionadaDetalhes"
|
|
263
|
-
:titulo-padrao="props.tituloPadrao"
|
|
264
|
-
@excluido="atualizarVisualizacao"
|
|
265
|
-
/>
|
|
266
|
-
|
|
267
|
-
<vue-cal
|
|
268
|
-
ref="calendarioRef"
|
|
269
|
-
class="calendario-principal vuecal--blue-theme"
|
|
270
|
-
:events="eventosProcessados"
|
|
271
|
-
v-model:active-view="visaoAtiva"
|
|
272
|
-
v-model:selected-date="dataSelecionada"
|
|
273
|
-
v-model:view-date="dataVisao"
|
|
274
|
-
@update:view-date="dataVisao = $event"
|
|
275
|
-
:views="['day', 'week']"
|
|
276
|
-
:hide-view-selector="true"
|
|
277
|
-
:hide-title-bar="true"
|
|
278
|
-
:click-to-navigate="false"
|
|
279
|
-
locale="pt-br"
|
|
280
|
-
:time-from="minutosInicio"
|
|
281
|
-
:time-to="minutosFim"
|
|
282
|
-
:disable-days="datasDesabilitadas"
|
|
283
|
-
:special-hours="horasBloqueadasNativas"
|
|
284
|
-
:snap-to-interval="15"
|
|
285
|
-
:start-week-on-sunday="true"
|
|
286
|
-
:editable-events="{ drag: true, resize: true, delete: true, create: true }"
|
|
287
|
-
@ready="atualizarVisualizacao"
|
|
288
|
-
@view-change="atualizarVisualizacao"
|
|
289
|
-
@event-create="aoCriarEvento"
|
|
290
|
-
@event-click="aoClicarEvento"
|
|
291
|
-
>
|
|
292
|
-
<template #weekday-heading="{ date }">
|
|
293
|
-
<div
|
|
294
|
-
class="cabecalho-dia-customizado"
|
|
295
|
-
@click="dataSelecionada = date; dataVisao = date"
|
|
296
|
-
style="cursor: pointer;"
|
|
187
|
+
<div class="wrapper-calendario">
|
|
188
|
+
<ModalReserva
|
|
189
|
+
v-model="modalAberto"
|
|
190
|
+
:dados-iniciais="dadosParaModal"
|
|
191
|
+
:usa-interceptador="usaInterceptador"
|
|
192
|
+
:ocultar-titulo-modal="props.ocultarTituloModal"
|
|
193
|
+
@before-save="$emit('before-save', $event)"
|
|
194
|
+
@salvo="aoSalvar"
|
|
195
|
+
@cancelado="encerrarCriacaoProvisoria"
|
|
297
196
|
>
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
197
|
+
<template v-for="(_, nomeSlot) in $slots" :key="nomeSlot" #[nomeSlot]="slotProps">
|
|
198
|
+
<slot :name="nomeSlot" v-bind="slotProps ?? {}"></slot>
|
|
199
|
+
</template>
|
|
200
|
+
</ModalReserva>
|
|
201
|
+
|
|
202
|
+
<ModalDetalhesReserva
|
|
203
|
+
v-model="modalDetalhesAberto"
|
|
204
|
+
:reserva="reservaSelecionadaDetalhes"
|
|
205
|
+
:titulo-padrao="props.tituloPadrao"
|
|
206
|
+
@excluido="atualizarVisualizacao"
|
|
207
|
+
/>
|
|
208
|
+
|
|
209
|
+
<vue-cal
|
|
210
|
+
ref="calendarioRef"
|
|
211
|
+
class="calendario-principal vuecal--blue-theme"
|
|
212
|
+
:events="eventosProcessados"
|
|
213
|
+
v-model:active-view="visaoAtiva"
|
|
214
|
+
v-model:selected-date="dataSelecionada"
|
|
215
|
+
v-model:view-date="dataVisao"
|
|
216
|
+
@update:view-date="dataVisao = $event"
|
|
217
|
+
:views="['day', 'week']"
|
|
218
|
+
:hide-view-selector="true"
|
|
219
|
+
:hide-title-bar="true"
|
|
220
|
+
:click-to-navigate="false"
|
|
221
|
+
locale="pt-br"
|
|
222
|
+
:time-from="minutosInicio"
|
|
223
|
+
:time-to="minutosFim"
|
|
224
|
+
:disable-days="datasDesabilitadas"
|
|
225
|
+
:special-hours="horasBloqueadasNativas"
|
|
226
|
+
:snap-to-interval="15"
|
|
227
|
+
:start-week-on-sunday="true"
|
|
228
|
+
:editable-events="{ drag: true, resize: true, delete: true, create: true }"
|
|
229
|
+
@ready="atualizarVisualizacao"
|
|
230
|
+
@view-change="atualizarVisualizacao"
|
|
231
|
+
@event-create="aoCriarEvento"
|
|
232
|
+
@event-click="aoClicarEvento"
|
|
233
|
+
>
|
|
234
|
+
<template #weekday-heading="{ date }">
|
|
235
|
+
<div class="cabecalho-dia-customizado" @click="dataSelecionada = date; dataVisao = date" style="cursor: pointer;">
|
|
236
|
+
<span class="nome-dia">{{ obterNomeDiaCurto(date) }}</span>
|
|
237
|
+
<span
|
|
238
|
+
class="numero-dia"
|
|
239
|
+
:class="{ 'dia-hoje': date.toDateString() === new Date().toDateString(), 'dia-selecionado': ehDataSelecionada(date) }"
|
|
240
|
+
>
|
|
241
|
+
{{ date.getDate() }}
|
|
242
|
+
</span>
|
|
243
|
+
</div>
|
|
244
|
+
</template>
|
|
245
|
+
</vue-cal>
|
|
246
|
+
</div>
|
|
309
247
|
</template>
|
|
310
248
|
|
|
311
249
|
<style scoped>
|
|
312
250
|
.wrapper-calendario { display: flex; flex-direction: column; flex-grow: 1; height: 100%; overflow: hidden; background: var(--bg-primary); }
|
|
313
251
|
.calendario-principal { --vuecal-weekday-bar-size: 75px !important; background: var(--bg-primary); flex-grow: 1; height: 100%; overflow: hidden; border-radius: 0 !important; border: none !important; box-shadow: none !important; }
|
|
314
|
-
|
|
315
252
|
:deep(.vuecal__header), :deep(.vuecal__title-bar) { display: none !important; }
|
|
316
253
|
:deep(.vuecal__weekdays-headings) { border-bottom: 1px solid var(--border-light); background-color: var(--bg-primary); color: var(--text-primary); }
|
|
317
254
|
:deep(.vuecal__weekday) { background-color: var(--bg-primary); justify-content: center; border-right: 1px solid var(--border-light); padding: 0; }
|
|
@@ -331,28 +268,10 @@ const horasBloqueadasNativas = computed(() => {
|
|
|
331
268
|
:deep(.vuecal__event:hover), :deep(.vuecal__event--focus) { box-shadow: var(--shadow-float); filter: brightness(1.1); }
|
|
332
269
|
:deep(.vuecal__event.reserva-minha) { background-color: var(--color-success) !important; border-color: var(--color-success) !important; color: var(--text-inverse) !important; }
|
|
333
270
|
:deep(.vuecal__event.reserva-bloqueada) { background-color: var(--border-editor) !important; border-color: var(--border-light) !important; color: var(--text-primary) !important; font-weight: bold; opacity: 0.75; cursor: pointer !important; background-image: repeating-linear-gradient(45deg, transparent, transparent 10px, rgba(0, 0, 0, 0.1) 10px, rgba(0, 0, 0, 0.1) 20px) !important; }
|
|
334
|
-
:deep(.vuecal__time-column) {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
:deep(.
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
:deep(.vuecal__time-cell-line::before),
|
|
342
|
-
:deep(.vuecal__cell::before) {
|
|
343
|
-
border-color: var(--border-editor) !important;
|
|
344
|
-
}
|
|
345
|
-
:deep(.vuecal__cell) {
|
|
346
|
-
background-color: transparent !important;
|
|
347
|
-
border-right: 1px solid var(--border-editor) !important;
|
|
348
|
-
}
|
|
349
|
-
:deep(.vuecal__now-line) {
|
|
350
|
-
color: var(--text-primary) !important;
|
|
351
|
-
}
|
|
352
|
-
:deep(.vuecal__now-line span) {
|
|
353
|
-
color: var(--text-primary) !important;
|
|
354
|
-
}
|
|
355
|
-
:deep(.vuecal__cell::before) {
|
|
356
|
-
border-color: var(--border-light) !important;
|
|
357
|
-
}
|
|
271
|
+
:deep(.vuecal__time-column) { background-color: var(--bg-primary) !important; border-right: 1px solid var(--border-editor) !important; }
|
|
272
|
+
:deep(.vuecal__time-cell) { color: var(--text-secondary) !important; }
|
|
273
|
+
:deep(.vuecal__time-cell-line::before) { border-color: var(--border-editor) !important; }
|
|
274
|
+
:deep(.vuecal__cell) { background-color: transparent !important; border-right: 1px solid var(--border-editor) !important; }
|
|
275
|
+
:deep(.vuecal__now-line), :deep(.vuecal__now-line span) { color: var(--text-primary) !important; }
|
|
276
|
+
:deep(.vuecal__cell::before) { border-color: var(--border-light) !important; }
|
|
358
277
|
</style>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
========
|
|
3
|
+
Este arquivo define um componente headless genérico para blocos selecionáveis via clique e arrasto.
|
|
4
|
+
Ele não possui estilização fixa e funciona em qualquer tag HTML (como 'td', 'div' ou 'li'),
|
|
5
|
+
recebendo o dado que representa, sua coordenada e o controlador de arrasto para funcionar tanto na horizontal quanto na vertical.
|
|
6
|
+
========
|
|
7
|
+
-->
|
|
8
|
+
<script setup lang="ts">
|
|
9
|
+
import { computed, watchEffect, onUnmounted } from 'vue';
|
|
10
|
+
|
|
11
|
+
const props = withDefaults(defineProps<{
|
|
12
|
+
tag?: string;
|
|
13
|
+
grupo: string | number;
|
|
14
|
+
indice: number;
|
|
15
|
+
item: any;
|
|
16
|
+
disponivel?: boolean;
|
|
17
|
+
controlador: {
|
|
18
|
+
verificarSelecionado: (grupo: string | number, indice: number) => boolean;
|
|
19
|
+
iniciarArrasto: (grupo: string | number, indice: number, item: any, disponivel: boolean) => void;
|
|
20
|
+
atualizarArrasto: (grupo: string | number, indice: number) => void;
|
|
21
|
+
registrarItemNoMapa: (grupo: string | number, indice: number, item: any, disponivel: boolean) => void;
|
|
22
|
+
removerItemDoMapa: (grupo: string | number, indice: number) => void;
|
|
23
|
+
};
|
|
24
|
+
}>(), {
|
|
25
|
+
tag: 'div',
|
|
26
|
+
disponivel: true
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Mantém o item registrado no controlador para validação automática de caminho durante o arrasto.
|
|
30
|
+
watchEffect(() => {
|
|
31
|
+
props.controlador.registrarItemNoMapa(props.grupo, props.indice, props.item, props.disponivel);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
onUnmounted(() => {
|
|
35
|
+
props.controlador.removerItemDoMapa(props.grupo, props.indice);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Calcula reativamente se este bloco específico faz parte da seleção de arrasto ativa no momento.
|
|
39
|
+
const estaSelecionado = computed(() => {
|
|
40
|
+
return props.controlador.verificarSelecionado(props.grupo, props.indice);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Aciona o início da seleção no controlador ao pressionar o botão do mouse sobre o bloco.
|
|
44
|
+
const aoPressionarMouse = () => {
|
|
45
|
+
props.controlador.iniciarArrasto(props.grupo, props.indice, props.item, props.disponivel);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Atualiza o limite final da seleção no controlador quando o cursor entra neste bloco durante o arrasto.
|
|
49
|
+
const aoEntrarMouse = () => {
|
|
50
|
+
props.controlador.atualizarArrasto(props.grupo, props.indice);
|
|
51
|
+
};
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<template>
|
|
55
|
+
<component
|
|
56
|
+
:is="props.tag"
|
|
57
|
+
@mousedown.prevent="aoPressionarMouse"
|
|
58
|
+
@mouseenter="aoEntrarMouse"
|
|
59
|
+
>
|
|
60
|
+
<slot :esta-selecionado="estaSelecionado" :item="props.item"></slot>
|
|
61
|
+
</component>
|
|
62
|
+
</template>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
========
|
|
3
|
-
Componente de cabeçalho
|
|
4
|
-
controles de navegação semanal
|
|
3
|
+
Componente de cabeçalho dos modos Tabela e Tabela-Horário, contendo o alternador de menu lateral,
|
|
4
|
+
controles de navegação semanal alinhados ao início da semana da agenda e seletor de agendas.
|
|
5
5
|
========
|
|
6
6
|
-->
|
|
7
7
|
<script setup lang="ts">
|
|
@@ -18,10 +18,19 @@ const props = defineProps<{
|
|
|
18
18
|
|
|
19
19
|
const { dataSelecionada, agendasDisponiveis, agendaSelecionada } = useAgenda(api);
|
|
20
20
|
|
|
21
|
-
// Calcula e formata o intervalo
|
|
21
|
+
// Calcula e formata o intervalo da semana começando no dia inicial configurado na agenda (Segunda-feira por padrão).
|
|
22
22
|
const textoPeriodoTabela = computed(() => {
|
|
23
23
|
if (!dataSelecionada.value) return '';
|
|
24
|
-
const
|
|
24
|
+
const dBase = new Date(dataSelecionada.value);
|
|
25
|
+
const inicio = new Date(dBase.getFullYear(), dBase.getMonth(), dBase.getDate());
|
|
26
|
+
|
|
27
|
+
const agendaObj = agendasDisponiveis.value.find((a: any) => Number(a.id_agenda) === Number(agendaSelecionada.value));
|
|
28
|
+
const diaInicioPython = agendaObj?.detalhes?.dia_inicio_semana_agenda ?? 0;
|
|
29
|
+
const diaInicioJs = (diaInicioPython + 1) % 7;
|
|
30
|
+
const diff = (inicio.getDay() - diaInicioJs + 7) % 7;
|
|
31
|
+
|
|
32
|
+
inicio.setDate(inicio.getDate() - diff);
|
|
33
|
+
|
|
25
34
|
const fim = new Date(inicio);
|
|
26
35
|
fim.setDate(inicio.getDate() + 6);
|
|
27
36
|
|