sposchedule
338 строк · 9.4 Кб
1<script setup lang="ts">
2import DatePicker from 'primevue/datepicker';
3import { computed, ref, watch, watchEffect } from 'vue';
4import { useRoute } from 'vue-router';
5import { useBuildingsQuery } from '@/queries/buildings';
6import MultiSelect from 'primevue/multiselect';
7import Button from 'primevue/button';
8import LoadingBar from '@/components/LoadingBar.vue';
9import router from '@/router';
10import { usePublicBellsPrintQuery } from '@/queries/bells';
11import { useDateFormat } from '@vueuse/core';
12import { dateRegex } from '@/composables/constants';
13import {
14dayNamesWithPreposition,
15monthDeclensions,
16} from '@/composables/constants';
17
18const route = useRoute();
19
20const date = ref(null);
21const formattedDate = computed(() => {
22return date.value ? useDateFormat(date.value, 'DD.MM.YYYY').value : null;
23});
24
25const { data: buildingsData, isFetched: buildingsFetched } =
26useBuildingsQuery();
27const selectedBuildings = ref(null);
28const buildings = computed(() => {
29return (
30buildingsData.value?.map(building => ({
31value: building.name,
32label: `${building.name} корпус`,
33})) || []
34);
35});
36
37const buildingsArray = computed(() => {
38return [selectedBuildings.value?.map(obj => obj.value)];
39});
40
41function printPage() {
42window.print();
43}
44
45function updateQueryParams() {
46router.replace({
47query: {
48...route.query,
49date: formattedDate.value || undefined,
50buildings: buildingsArray.value || undefined,
51},
52});
53}
54
55watch(
56[date, selectedBuildings],
57() => {
58updateQueryParams();
59},
60{ deep: true }
61);
62
63watchEffect(() => {
64if (buildingsFetched.value) {
65if (route.query.date && dateRegex.test(route.query.date as string)) {
66const [day, month, year] = (route.query.date as string)
67.split('.')
68.map(Number);
69date.value = new Date(year, month - 1, day);
70}
71if (route.query.buildings) {
72const buildingNames = route.query.buildings.toString(); // если строка, разбиваем на массив
73
74selectedBuildings.value = buildings.value?.filter(building =>
75buildingNames.includes(building.value)
76);
77}
78}
79});
80
81const { data: publicBells, isFetched: isFetchedBells } =
82usePublicBellsPrintQuery(buildingsArray, formattedDate);
83
84const mergedBells = computed(() => {
85// Функция для сравнения periods между разными корпусами
86const periodsEqual = (periods1, periods2) => {
87if (periods1.length !== periods2.length) return false;
88return periods1.every((p1, index) => {
89const p2 = periods2[index];
90return (
91p1.index === p2.index &&
92p1.has_break === p2.has_break &&
93p1.period_from === p2.period_from &&
94p1.period_to === p2.period_to &&
95p1.period_from_after === p2.period_from_after &&
96p1.period_to_after === p2.period_to_after
97);
98});
99};
100
101// Группируем звонки по одинаковым периодам
102const grouped = [];
103
104publicBells.value?.forEach(bell => {
105// Находим группу, у которой совпадают periods
106let group = grouped.find(g =>
107periodsEqual(g.bells.periods, bell.periods)
108);
109
110if (group) {
111// Если такая группа найдена, добавляем туда здание
112group.building += `, ${bell.building}`;
113} else {
114// Если группа не найдена, создаем новую
115grouped.push({
116building: String(bell.building),
117bells: bell,
118});
119}
120});
121
122return grouped;
123});
124
125const getIndexesFromBells = computed(() => {
126const indexes = new Set<number>();
127mergedBells.value?.forEach(bell => {
128bell.bells.periods.forEach(period => {
129indexes.add(period.index);
130});
131});
132return Array.from(indexes).sort((a, b) => a - b);
133});
134</script>
135
136<template>
137<LoadingBar />
138<div class="controls py-2 flex flex-wrap gap-2 items-center pl-2">
139<DatePicker
140v-model="date"
141fluid
142show-icon
143icon-display="input"
144date-format="dd.mm.yy"
145/>
146<MultiSelect
147v-model="selectedBuildings"
148:max-selected-labels="2"
149:selected-items-label="'{0} выбрано'"
150:options="buildings"
151placeholder="Корпуса"
152option-label="label"
153/>
154<Button
155label="Печать"
156:disabled="!date || !selectedBuildings"
157icon="pi pi-print"
158@click="printPage()"
159/>
160</div>
161<div class="main">
162<div class="flex flex-col gap-2 items-center w-full">
163<h1 v-if="publicBells" class="font-bold text-center py-2">
164<span class="uppercase">Расписание звонков</span> <br />
165на
166{{
167dayNamesWithPreposition[
168useDateFormat(date, 'dddd', {
169locales: 'ru-RU',
170}).value
171]
172}}
173{{
174`${
175useDateFormat(date, 'DD', {
176locales: 'ru-RU',
177}).value
178} ${
179monthDeclensions[
180useDateFormat(date, 'MMMM', {
181locales: 'ru-RU',
182}).value
183]
184} ${
185useDateFormat(date, 'YYYY', {
186locales: 'ru-RU',
187}).value
188}`
189}}
190года
191</h1>
192<span
193v-if="publicBells?.type"
194:class="{
195'text-green-400 ': publicBells?.type !== 'main',
196'text-surface-400 ': publicBells?.type === 'main',
197}"
198class="text-sm text-right py-1 px-2 rounded-lg"
199>{{ publicBells?.type === 'main' ? 'Основное' : 'Изменения' }}</span
200>
201<div class="">
202<h2 v-if="!publicBells && isFetchedBells" class="text-2xl text-center">
203На эту дату расписание звонков не найдено
204</h2>
205<div v-if="publicBells" class="">
206<table class="bells-table rounded">
207<thead>
208<tr>
209<th>
210<div class="flex gap-2 flex-col text-lg p-2">
211<span class="self-end">Корпус</span>
212<span class="border rotate-12" />
213<span class="self-start">№ пары</span>
214</div>
215</th>
216<th v-for="bell in mergedBells" :key="bell?.building">
217<div class="flex flex-col gap-1 items-center">
218<span>
219{{ bell?.building }}
220</span>
221<span
222:class="{
223'text-green-400 ': bell.bells?.type !== 'main',
224'text-surface-400 ': bell.bells?.type === 'main',
225}"
226class="text-sm text-right rounded-lg"
227>{{
228bell.bells?.type === 'main' ? 'Основное' : 'Изменения'
229}}</span
230>
231</div>
232</th>
233</tr>
234</thead>
235<tbody>
236<tr v-for="index in getIndexesFromBells" :key="index" class="">
237<td class="text-center py-4 font-bold">{{ index }} пара</td>
238<template v-for="bell in mergedBells" :key="bell?.building">
239<template
240v-for="period in bell.bells.periods"
241:key="period.index"
242>
243<td v-if="period?.index === index">
244<div>
245{{ period.period_from }} - {{ period.period_to }}
246</div>
247<div v-if="period?.period_from_after">
248{{ period.period_from_after }} -
249{{ period.period_to_after }}
250</div>
251</td>
252</template>
253<td
254v-if="
255!bell.bells.periods.find(period => period.index === index)
256"
257/>
258</template>
259</tr>
260</tbody>
261</table>
262</div>
263</div>
264</div>
265</div>
266</template>
267
268<style scoped>
269@media print {
270.controls {
271display: none;
272}
273
274.main {
275overflow: visible !important;
276/* Убираем возможные разрывы страниц и переносы */
277/* page-break-inside: avoid; */
278}
279}
280
281.bells-table {
282border-collapse: collapse;
283/* width: 100%; */
284}
285
286.bells-table td {
287padding: 0.75rem 1rem;
288}
289
290.group-header {
291width: 150px;
292}
293
294.bg-line {
295height: 1rem;
296background: rgba(45, 116, 209, 0.582);
297}
298
299.main {
300font-family: 'Arial', Times, serif;
301font-size: 1.5rem;
302padding: 1.2rem;
303overflow: auto;
304}
305
306table {
307table-layout: fixed;
308border-collapse: collapse;
309/* width: 100%; */
310}
311
312tbody th {
313line-height: normal;
314}
315
316th,
317td {
318border: 1px solid black;
319padding-right: 4px;
320padding-left: 4px;
321padding-top: 0;
322padding-bottom: 0;
323line-height: normal;
324
325/* padding: 5px; */
326}
327
328.info * {
329line-height: normal;
330/* font-size: 2rem; */
331text-align: center;
332font-weight: bold;
333}
334
335.info {
336margin-bottom: 1rem;
337}
338</style>
339