sposchedule

Форк
1
/
PrintBellsView.vue 
338 строк · 9.4 Кб
1
<script setup lang="ts">
2
  import DatePicker from 'primevue/datepicker';
3
  import { computed, ref, watch, watchEffect } from 'vue';
4
  import { useRoute } from 'vue-router';
5
  import { useBuildingsQuery } from '@/queries/buildings';
6
  import MultiSelect from 'primevue/multiselect';
7
  import Button from 'primevue/button';
8
  import LoadingBar from '@/components/LoadingBar.vue';
9
  import router from '@/router';
10
  import { usePublicBellsPrintQuery } from '@/queries/bells';
11
  import { useDateFormat } from '@vueuse/core';
12
  import { dateRegex } from '@/composables/constants';
13
  import {
14
    dayNamesWithPreposition,
15
    monthDeclensions,
16
  } from '@/composables/constants';
17

18
  const route = useRoute();
19

20
  const date = ref(null);
21
  const formattedDate = computed(() => {
22
    return date.value ? useDateFormat(date.value, 'DD.MM.YYYY').value : null;
23
  });
24

25
  const { data: buildingsData, isFetched: buildingsFetched } =
26
    useBuildingsQuery();
27
  const selectedBuildings = ref(null);
28
  const buildings = computed(() => {
29
    return (
30
      buildingsData.value?.map(building => ({
31
        value: building.name,
32
        label: `${building.name} корпус`,
33
      })) || []
34
    );
35
  });
36

37
  const buildingsArray = computed(() => {
38
    return [selectedBuildings.value?.map(obj => obj.value)];
39
  });
40

41
  function printPage() {
42
    window.print();
43
  }
44

45
  function updateQueryParams() {
46
    router.replace({
47
      query: {
48
        ...route.query,
49
        date: formattedDate.value || undefined,
50
        buildings: buildingsArray.value || undefined,
51
      },
52
    });
53
  }
54

55
  watch(
56
    [date, selectedBuildings],
57
    () => {
58
      updateQueryParams();
59
    },
60
    { deep: true }
61
  );
62

63
  watchEffect(() => {
64
    if (buildingsFetched.value) {
65
      if (route.query.date && dateRegex.test(route.query.date as string)) {
66
        const [day, month, year] = (route.query.date as string)
67
          .split('.')
68
          .map(Number);
69
        date.value = new Date(year, month - 1, day);
70
      }
71
      if (route.query.buildings) {
72
        const buildingNames = route.query.buildings.toString(); // если строка, разбиваем на массив
73

74
        selectedBuildings.value = buildings.value?.filter(building =>
75
          buildingNames.includes(building.value)
76
        );
77
      }
78
    }
79
  });
80

81
  const { data: publicBells, isFetched: isFetchedBells } =
82
    usePublicBellsPrintQuery(buildingsArray, formattedDate);
83

84
  const mergedBells = computed(() => {
85
    // Функция для сравнения periods между разными корпусами
86
    const periodsEqual = (periods1, periods2) => {
87
      if (periods1.length !== periods2.length) return false;
88
      return periods1.every((p1, index) => {
89
        const p2 = periods2[index];
90
        return (
91
          p1.index === p2.index &&
92
          p1.has_break === p2.has_break &&
93
          p1.period_from === p2.period_from &&
94
          p1.period_to === p2.period_to &&
95
          p1.period_from_after === p2.period_from_after &&
96
          p1.period_to_after === p2.period_to_after
97
        );
98
      });
99
    };
100

101
    // Группируем звонки по одинаковым периодам
102
    const grouped = [];
103

104
    publicBells.value?.forEach(bell => {
105
      // Находим группу, у которой совпадают periods
106
      let group = grouped.find(g =>
107
        periodsEqual(g.bells.periods, bell.periods)
108
      );
109

110
      if (group) {
111
        // Если такая группа найдена, добавляем туда здание
112
        group.building += `, ${bell.building}`;
113
      } else {
114
        // Если группа не найдена, создаем новую
115
        grouped.push({
116
          building: String(bell.building),
117
          bells: bell,
118
        });
119
      }
120
    });
121

122
    return grouped;
123
  });
124

125
  const getIndexesFromBells = computed(() => {
126
    const indexes = new Set<number>();
127
    mergedBells.value?.forEach(bell => {
128
      bell.bells.periods.forEach(period => {
129
        indexes.add(period.index);
130
      });
131
    });
132
    return 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
140
      v-model="date"
141
      fluid
142
      show-icon
143
      icon-display="input"
144
      date-format="dd.mm.yy"
145
    />
146
    <MultiSelect
147
      v-model="selectedBuildings"
148
      :max-selected-labels="2"
149
      :selected-items-label="'{0} выбрано'"
150
      :options="buildings"
151
      placeholder="Корпуса"
152
      option-label="label"
153
    />
154
    <Button
155
      label="Печать"
156
      :disabled="!date || !selectedBuildings"
157
      icon="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
        {{
167
          dayNamesWithPreposition[
168
            useDateFormat(date, 'dddd', {
169
              locales: 'ru-RU',
170
            }).value
171
          ]
172
        }}
173
        {{
174
          `${
175
            useDateFormat(date, 'DD', {
176
              locales: 'ru-RU',
177
            }).value
178
          } ${
179
            monthDeclensions[
180
              useDateFormat(date, 'MMMM', {
181
                locales: 'ru-RU',
182
              }).value
183
            ]
184
          } ${
185
            useDateFormat(date, 'YYYY', {
186
              locales: 'ru-RU',
187
            }).value
188
          }`
189
        }}
190
        года
191
      </h1>
192
      <span
193
        v-if="publicBells?.type"
194
        :class="{
195
          'text-green-400 ': publicBells?.type !== 'main',
196
          'text-surface-400 ': publicBells?.type === 'main',
197
        }"
198
        class="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
                      }"
226
                      class="text-sm text-right rounded-lg"
227
                      >{{
228
                        bell.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
240
                    v-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
254
                    v-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 {
271
      display: none;
272
    }
273

274
    .main {
275
      overflow: visible !important;
276
      /* Убираем возможные разрывы страниц и переносы */
277
      /* page-break-inside: avoid; */
278
    }
279
  }
280

281
  .bells-table {
282
    border-collapse: collapse;
283
    /* width: 100%; */
284
  }
285

286
  .bells-table td {
287
    padding: 0.75rem 1rem;
288
  }
289

290
  .group-header {
291
    width: 150px;
292
  }
293

294
  .bg-line {
295
    height: 1rem;
296
    background: rgba(45, 116, 209, 0.582);
297
  }
298

299
  .main {
300
    font-family: 'Arial', Times, serif;
301
    font-size: 1.5rem;
302
    padding: 1.2rem;
303
    overflow: auto;
304
  }
305

306
  table {
307
    table-layout: fixed;
308
    border-collapse: collapse;
309
    /* width: 100%; */
310
  }
311

312
  tbody th {
313
    line-height: normal;
314
  }
315

316
  th,
317
  td {
318
    border: 1px solid black;
319
    padding-right: 4px;
320
    padding-left: 4px;
321
    padding-top: 0;
322
    padding-bottom: 0;
323
    line-height: normal;
324

325
    /* padding: 5px; */
326
  }
327

328
  .info * {
329
    line-height: normal;
330
    /* font-size: 2rem; */
331
    text-align: center;
332
    font-weight: bold;
333
  }
334

335
  .info {
336
    margin-bottom: 1rem;
337
  }
338
</style>
339

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.