sposchedule

Форк
1
617 строк · 17.5 Кб
1
<script setup lang="ts">
2
  import SelectButton from 'primevue/selectbutton';
3
  import Select from 'primevue/select';
4
  import { computed, onMounted, ref, watch, watchEffect } from 'vue';
5
  import DatePicker from 'primevue/datepicker';
6
  import InputText from 'primevue/inputtext';
7
  import Checkbox from 'primevue/checkbox';
8
  import DataTable from 'primevue/datatable';
9
  import Column from 'primevue/column';
10
  import {
11
    useApplyPreset,
12
    useBellsQuery,
13
    useDestroyBells,
14
    usePresetsBells,
15
    useStoreBell,
16
    useStorePeriod,
17
    useStorePresetBell,
18
    useUpdateBell,
19
  } from '@/queries/bells';
20
  import Button from 'primevue/button';
21
  import { useDateFormat } from '@vueuse/core';
22
  import { useToast } from 'primevue/usetoast';
23
  import RowPeriodBell from '../../components/bells/AdminRowPeriodBell.vue';
24
  import { useBellsStore } from '@/stores/bells';
25
  import { storeToRefs } from 'pinia';
26
  import { useBuildingsQuery } from '@/queries/buildings';
27
  import ToggleButton from 'primevue/togglebutton';
28
  import Dialog from 'primevue/dialog';
29
  import router from '@/router';
30
  import { useRoute } from 'vue-router';
31
  import { dateRegex } from '@/composables/constants';
32

33
  const toast = useToast();
34

35
  const type = ref('Основное');
36
  const typeOptions = ref(['Основное', 'Изменения']);
37

38
  const typeState = computed(() => {
39
    return type.value === typeOptions.value[0];
40
  });
41

42
  const weekDay = ref('ПН');
43
  const weekDaysOptions = ref([
44
    {
45
      label: 'Понедельник',
46
      value: 'ПН',
47
    },
48
    {
49
      label: 'Вторник',
50
      value: 'ВТ',
51
    },
52
    {
53
      label: 'Среда',
54
      value: 'СР',
55
    },
56
    {
57
      label: 'Четверг',
58
      value: 'ЧТ',
59
    },
60
    {
61
      label: 'Пятница',
62
      value: 'ПТ',
63
    },
64
    {
65
      label: 'Суббота',
66
      value: 'СБ',
67
    },
68
    {
69
      label: 'Воскресенье',
70
      value: 'ВС',
71
    },
72
  ]);
73

74
  const date = ref(new Date());
75

76
  const { data: buildingsFethed, isFetched: buildingsFetched } =
77
    useBuildingsQuery();
78
  const building = ref(null);
79
  building.value = buildingsFethed.value?.[0].name;
80

81
  const buildings = computed(() => {
82
    return (
83
      buildingsFethed.value?.map(building => ({
84
        value: building.name,
85
        label: `${building.name} корпус`,
86
      })) || []
87
    );
88
  });
89

90
  const formattedDate = computed(() => {
91
    return date.value ? useDateFormat(date.value, 'DD.MM.YYYY').value : null;
92
  });
93

94
  const { data, isSuccess } = useBellsQuery(
95
    type,
96
    building,
97
    weekDay,
98
    formattedDate
99
  );
100
  const { data: bellsPresets } = usePresetsBells();
101

102
  const bellsStore = useBellsStore();
103
  const { bells } = storeToRefs(bellsStore);
104
  const { setBells } = bellsStore;
105

106
  onMounted(() => {
107
    // building.value = buildingsFethed.value?.[0].name
108
  });
109

110
  watch(buildingsFethed, () => {
111
    building.value = buildingsFethed.value?.[0].name;
112
  });
113
  watch(data, setBells);
114

115
  let newPeriod = ref({
116
    index: 0,
117
    period_from: '',
118
    period_to: '',
119
    has_break: false,
120
    period_from_after: null,
121
    period_to_after: null,
122
  });
123

124
  function formatTime(dateString) {
125
    const date = new Date(dateString);
126
    // Получаем часы и минуты и добавляем ведущий ноль, если значение меньше 10
127
    const hours = date.getHours().toString().padStart(2, '0');
128
    const minutes = date.getMinutes().toString().padStart(2, '0');
129
    return `${hours}:${minutes}`;
130
  }
131

132
  const { mutateAsync: storePeriod } = useStorePeriod();
133
  const { mutateAsync: storeBell, data: newBell } = useStoreBell();
134
  const { mutateAsync: storeBellPreset } = useStorePresetBell();
135

136
  const isoDate = computed(() => {
137
    return date.value ? useDateFormat(date.value, 'DD.MM.YYYY').value : null;
138
  });
139

140
  const typeValues = {
141
    Основное: 'main',
142
    Изменения: 'changes',
143
  };
144

145
  const bodyBellPeriod = computed(() => {
146
    let body = {
147
      ...newPeriod.value,
148
      bells_id: data?.value?.id ? data.value.id : newBell.value.data.id,
149
      period_to: formatTime(newPeriod.value.period_to),
150
      period_from: formatTime(newPeriod.value.period_from),
151
    };
152
    if (newPeriod.value.period_from_after || newPeriod.value.period_to_after) {
153
      body.period_from_after = formatTime(newPeriod.value.period_from_after);
154
      body.period_to_after = formatTime(newPeriod.value.period_to_after);
155
    }
156
    return body;
157
  });
158

159
  async function addPeriod() {
160
    if (!data?.value?.id) {
161
      if (date.value) {
162
        try {
163
          await storeBell({
164
            type: typeValues[type.value],
165
            building: building.value,
166
            date: typeValues[type.value] === 'changes' ? isoDate.value : null,
167
            week_day: typeValues[type.value] === 'main' ? weekDay.value : null,
168
          });
169
        } catch (e) {
170
          toast.add({
171
            severity: 'error',
172
            summary: 'Ошибка',
173
            detail: e?.response?.data.message,
174
            life: 3000,
175
            closable: true,
176
          });
177
          return;
178
        }
179
      }
180
    }
181
    try {
182
      await storePeriod(bodyBellPeriod.value);
183
      newPeriod = ref({
184
        index: Number(newPeriod.value.index) + 1,
185
        period_from: '',
186
        period_to: '',
187
        has_break: false,
188
        period_from_after: null,
189
        period_to_after: null,
190
      });
191
    } catch (e) {
192
      toast.add({
193
        severity: 'error',
194
        summary: 'Ошибка',
195
        detail: e?.response?.data.message,
196
        life: 3000,
197
        closable: true,
198
      });
199
      return;
200
    }
201
  }
202

203
  const selectedBells = ref();
204
  const { mutateAsync: destroyBells } = useDestroyBells();
205
  const deleteBells = async () => {
206
    if (!selectedBells.value.length) return;
207

208
    for (let i = 0; i < selectedBells.value.length; i++) {
209
      try {
210
        await destroyBells(selectedBells.value[i].id);
211
      } catch (e) {
212
        toast.add({
213
          severity: 'error',
214
          summary: 'Ошибка',
215
          detail: e?.response.data.message,
216
          life: 3000,
217
          closable: true,
218
        });
219
        return;
220
      }
221
    }
222
    selectedBells.value = [];
223
  };
224
  const { mutateAsync: updateBell } = useUpdateBell();
225

226
  const showAddNewBellPeriod = ref(false);
227

228
  const visible = ref(false);
229
  const presetName = ref('');
230
  function savePreset() {
231
    visible.value = false;
232
    try {
233
      storeBellPreset({ bells_id: bells.value.id, name: presetName.value });
234
      presetName.value = '';
235
    } catch (e) {
236
      toast.add({
237
        severity: 'error',
238
        summary: 'Ошибка',
239
        detail: e?.response?.data.message,
240
        life: 3000,
241
        closable: true,
242
      });
243
      return;
244
    }
245
  }
246
  const { mutateAsync: applyPreset } = useApplyPreset();
247
  const selectedPreset = ref(null);
248
  watch(selectedPreset, async () => {
249
    if (!selectedPreset.value) return;
250
    if (!data.value?.id) {
251
      try {
252
        await storeBell({
253
          type: typeValues[type.value],
254
          building: building.value,
255
          date: typeValues[type.value] === 'changes' ? isoDate.value : null,
256
          week_day: typeValues[type.value] === 'main' ? weekDay.value : null,
257
        });
258
      } catch (e) {
259
        toast.add({
260
          severity: 'error',
261
          summary: 'Ошибка',
262
          detail: e?.response?.data.message,
263
          life: 3000,
264
          closable: true,
265
        });
266
        return;
267
      }
268
    }
269

270
    try {
271
      await applyPreset({
272
        bells_id: !data.value?.id ? newBell.value.data.id : bells.value.id,
273
        preset_id: selectedPreset.value.id,
274
      });
275

276
      selectedPreset.value = null;
277
    } catch (e) {
278
      toast.add({
279
        severity: 'error',
280
        summary: 'Ошибка',
281
        detail: e?.response?.data.message,
282
        life: 3000,
283
        closable: true,
284
      });
285
      return;
286
    }
287
  });
288

289
  const published = ref(null);
290
  watch(
291
    () => bells.value?.published,
292
    () => {
293
      published.value = bells.value.published;
294
    }
295
  );
296

297
  const onRowEditSave = async event => {
298
    let { newData } = event;
299
    try {
300
      await updateBell({ id: newData.id, body: newData });
301
    } catch (e) {
302
      toast.add({
303
        severity: 'error',
304
        summary: 'Ошибка',
305
        detail: e?.response.data.message,
306
        life: 3000,
307
        closable: true,
308
      });
309
      return;
310
    }
311
  };
312

313
  const editingRows = ref();
314

315
  const route = useRoute();
316

317
  function updateQueryParams() {
318
    router.replace({
319
      query: {
320
        ...route.query,
321
        type: type.value || undefined,
322
        building: building.value || undefined,
323
        weekDay: weekDay.value || undefined,
324
        date: isoDate.value || undefined,
325
      },
326
    });
327
  }
328

329
  watch(
330
    [type, building, weekDay, date],
331
    () => {
332
      updateQueryParams();
333
    },
334
    { deep: true }
335
  );
336

337
  watchEffect(() => {
338
    if (buildingsFetched.value) {
339
      if (route.query.date && dateRegex.test(route.query.date as string)) {
340
        const [day, month, year] = (route.query.date as string)
341
          .split('.')
342
          .map(Number);
343
        date.value = new Date(year, month - 1, day);
344
      } else {
345
        date.value = new Date();
346
      }
347

348
      if (route.query.building) {
349
        building.value = route.query.building;
350
      }
351

352
      if (route.query.weekDay) {
353
        weekDay.value = route.query.weekDay as any;
354
      }
355

356
      if (route.query.type) {
357
        type.value = route.query.type as any;
358
      }
359
    }
360
  });
361
</script>
362

363
<template>
364
  <div class="flex flex-col gap-4">
365
    <div class="flex flex-wrap justify-between items-baseline">
366
      <h1 class="text-2xl">Звонки</h1>
367
    </div>
368
    <div class="">
369
      <form
370
        class="flex flex-wrap items-center gap-2 p-4 rounded-lg bg-surface-100 dark:bg-surface-800"
371
      >
372
        <div class="flex flex-wrap items-center gap-2">
373
          <SelectButton
374
            v-model="type"
375
            :allow-empty="false"
376
            :options="typeOptions"
377
            aria-labelledby="basic"
378
          />
379
          <Select
380
            v-model="building"
381
            title="Корпус"
382
            option-value="value"
383
            :options="buildings"
384
            option-label="label"
385
            placeholder="Корпус"
386
          />
387
          <Select
388
            v-if="typeState"
389
            v-model="weekDay"
390
            option-value="value"
391
            :options="weekDaysOptions"
392
            option-label="label"
393
            placeholder="День недели"
394
            class="w-full md:w-56"
395
          />
396

397
          <DatePicker
398
            v-else
399
            v-model="date"
400
            append-to="self"
401
            date-format="dd.mm.yy"
402
          />
403
          <ToggleButton
404
            v-model="published"
405
            :disabled="!data"
406
            class="text-sm"
407
            fluid
408
            on-label="Снять с публикации"
409
            off-label="Опубликовать"
410
            @change="
411
              updateBell({ id: bells?.id, body: { published: published } })
412
            "
413
          />
414
          <!-- <Button @click="copyState = !copyState" text icon="pi pi-clone" title="Скопировать"></Button> -->
415
          <div class="border-l border-surface-600 flex gap-2 pl-2">
416
            <Button
417
              outlined
418
              icon="pi pi-clone"
419
              label="Сохранить заготовку"
420
              title="Сохранить в заготовки"
421
              @click="visible = !visible"
422
            />
423
            <Select
424
              v-model="selectedPreset"
425
              show-clear
426
              placeholder="Применить заготовку"
427
              option-label="name_preset"
428
              :options="bellsPresets"
429
            />
430
          </div>
431
          <Button
432
            target="_blank"
433
            icon="pi pi-print"
434
            as="router-link"
435
            :to="{
436
              path: '/print/bells',
437
            }"
438
          />
439

440
          <Dialog
441
            v-model:visible="visible"
442
            modal
443
            header="Создание заготовки"
444
            :style="{ width: '25rem' }"
445
          >
446
            <div class="flex items-center gap-4 mb-4">
447
              <label for="name" class="font-semibold w-24"
448
                >Название заготовки</label
449
              >
450
              <InputText
451
                id="name"
452
                v-model="presetName"
453
                placeholder="Пример: сокр. пары"
454
                class="flex-auto"
455
              />
456
            </div>
457
            <div class="flex justify-end gap-2">
458
              <Button
459
                type="button"
460
                label="Отмена"
461
                severity="secondary"
462
                @click="visible = false"
463
              />
464
              <Button type="button" label="Сохранить" @click="savePreset" />
465
            </div>
466
          </Dialog>
467
        </div>
468
      </form>
469
    </div>
470
    <div class="">
471
      <div class="">
472
        <div class="overflow-x-auto">
473
          <table class="w-full border-collapse">
474
            <thead>
475
              <tr
476
                class="border dark:border-surface-700 border-surface-200 bg-surface-100 dark:bg-surface-800"
477
              >
478
                <th>№</th>
479
                <th>Начало - Конец</th>
480

481
                <th>С перерывом</th>
482
                <th>Действия</th>
483
              </tr>
484
            </thead>
485
            <tbody>
486
              <RowPeriodBell
487
                v-for="period in bells?.periods"
488
                v-show="isSuccess"
489
                :key="period.id"
490
                :period="period"
491
              />
492

493
              <tr
494
                v-show="showAddNewBellPeriod"
495
                class="border-t-primary-500 border-t border dark:border-surface-700 border-surface-200 bg-surface-100 dark:bg-surface-800"
496
              >
497
                <td class="">
498
                  <div class="flex justify-center">
499
                    <InputText
500
                      v-model="newPeriod.index"
501
                      class="text-center max-w-12"
502
                    />
503
                  </div>
504
                </td>
505
                <td class="">
506
                  <div
507
                    class="flex justify-center items-center flex-col gap-2 py-2"
508
                  >
509
                    <div class="flex gap-2 items-center">
510
                      <DatePicker
511
                        id="datepicker-timeonly"
512
                        v-model="newPeriod.period_from"
513
                        time-only
514
                        fluid
515
                      />
516
                      -
517
                      <DatePicker
518
                        id="datepicker-timeonly"
519
                        v-model="newPeriod.period_to"
520
                        time-only
521
                        fluid
522
                      />
523
                    </div>
524
                    <div
525
                      v-if="newPeriod.has_break"
526
                      class="flex gap-2 items-center"
527
                    >
528
                      <DatePicker
529
                        id="datepicker-timeonly"
530
                        v-model="newPeriod.period_from_after"
531
                        time-only
532
                        fluid
533
                      />
534
                      -
535
                      <DatePicker
536
                        id="datepicker-timeonly"
537
                        v-model="newPeriod.period_to_after"
538
                        time-only
539
                        fluid
540
                      />
541
                    </div>
542
                  </div>
543
                </td>
544

545
                <td class="text-center">
546
                  <Checkbox v-model="newPeriod.has_break" :binary="true" />
547
                </td>
548
                <td class="">
549
                  <div class="px-6 flex justify-center">
550
                    <Button
551
                      outlined
552
                      text
553
                      icon="pi pi-save"
554
                      @click="addPeriod"
555
                    />
556
                  </div>
557
                </td>
558
              </tr>
559
            </tbody>
560
          </table>
561
        </div>
562
      </div>
563

564
      <div class="mt-2 flex items-center justify-center">
565
        <Button
566
          label="Новый звонок"
567
          title="Открыть форму для добавления звонка"
568
          size="small"
569
          outlined
570
          severity="secondary"
571
          class="w-full"
572
          :icon="!showAddNewBellPeriod ? 'pi pi-angle-down' : 'pi pi-angle-up'"
573
          @click="showAddNewBellPeriod = !showAddNewBellPeriod"
574
        />
575
      </div>
576
    </div>
577
    <div class="flex flex-col gap-4 mt-4">
578
      <h1 class="text-2xl">Заготовки звонков</h1>
579
      <DataTable
580
        v-model:editing-rows="editingRows"
581
        v-model:selection="selectedBells"
582
        :rows="10"
583
        edit-mode="row"
584
        :value="bellsPresets"
585
        table-style="min-width: 50rem"
586
        @row-edit-save="onRowEditSave"
587
      >
588
        <template #header>
589
          <div class="flex flex-wrap items-center gap-2 justify-between">
590
            <Button
591
              severity="danger"
592
              :disabled="!selectedBells?.length || !bellsPresets.length"
593
              type="button"
594
              icon="pi pi-trash"
595
              label="Удалить"
596
              outlined
597
              @click="deleteBells"
598
            />
599
          </div>
600
        </template>
601
        <Column selection-mode="multiple" header-style="width: 3rem" />
602
        <!-- <Column field="id" header="ID"></Column> -->
603
        <Column field="name_preset" header="Название">
604
          <template #editor="{ data, field }">
605
            <InputText v-model="data[field]" />
606
          </template>
607
        </Column>
608

609
        <Column
610
          :row-editor="true"
611
          style="width: 10%; min-width: 8rem"
612
          body-style="text-align:center"
613
        />
614
      </DataTable>
615
    </div>
616
  </div>
617
</template>
618

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

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

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

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