sposchedule

Форк
1
403 строки · 11.8 Кб
1
<script setup lang="ts">
2
  import { ref } from 'vue';
3
  import DataTable from 'primevue/datatable';
4
  import Column from 'primevue/column';
5
  import Select from 'primevue/select';
6
  import InputText from 'primevue/inputtext';
7
  import Button from 'primevue/button';
8
  import { useDateFormat } from '@vueuse/core';
9
  import { useToast } from 'primevue/usetoast';
10
  import Chip from 'primevue/chip';
11
  import MultiSelect from 'primevue/multiselect';
12
  import { FilterMatchMode } from '@primevue/core/api';
13
  import Textarea from 'primevue/textarea';
14
  import {
15
    useDestroyGroup,
16
    useGroupsQuery,
17
    useStoreGroup,
18
    useUpdateGroup,
19
  } from '../../queries/groups';
20
  import { useSemestersQuery } from '@/queries/semesters';
21
  import { useConfirm } from 'primevue/useconfirm';
22
  import { useBuildingsQuery } from '@/queries/buildings';
23

24
  const toast = useToast();
25
  const { data: groups } = useGroupsQuery();
26

27
  const newGroupName = ref('');
28
  const newGroupError = ref(false);
29

30
  const editingRows = ref([]);
31
  const selectedGroups = ref([]);
32
  const courses = [
33
    {
34
      label: 1,
35
      value: 1,
36
    },
37
    {
38
      label: 2,
39
      value: 2,
40
    },
41
    {
42
      label: 3,
43
      value: 3,
44
    },
45
    {
46
      label: 4,
47
      value: 4,
48
    },
49
  ];
50

51
  const { mutateAsync: updateGroup, isPending: isUpdated } = useUpdateGroup();
52

53
  const onRowEditSave = async event => {
54
    let { newData } = event;
55

56
    try {
57
      await updateGroup({
58
        id: newData.id,
59
        body: newData,
60
      });
61
    } catch (e) {
62
      toast.add({
63
        severity: 'error',
64
        summary: 'Ошибка',
65
        detail: e?.response.data.message,
66
        life: 3000,
67
        closable: true,
68
      });
69
      return;
70
    }
71
  };
72

73
  const regexGroup = /^[a-zA-Zа-яА-Я]{2,4}-[1-4]\d{1,4}$/;
74
  const { mutateAsync: storeSubject, isPending: isStored } = useStoreGroup();
75

76
  const addGroup = async () => {
77
    if (!regexGroup.test(newGroupName.value)) {
78
      toast.add({
79
        severity: 'error',
80
        summary: 'Ошибка',
81
        detail: 'Неверный формат названия Группы. Пример: ИС-401',
82
        life: 3000,
83
        closable: true,
84
      });
85
      newGroupError.value = true;
86
      return;
87
    }
88

89
    try {
90
      await storeSubject({
91
        specialization: newGroupName.value.split('-')[0],
92
        course: newGroupName.value.split('-')[1][0],
93
        index: newGroupName.value.split('-')[1].slice(1),
94
        semesters: selectedSemesters.value,
95
        buildings: selectedBuildings.value,
96
      });
97
    } catch (e) {
98
      toast.add({
99
        severity: 'error',
100
        summary: 'Ошибка',
101
        detail: e?.response.data.message,
102
        life: 3000,
103
        closable: true,
104
      });
105
      newGroupName.value = '';
106
    }
107

108
    newGroupError.value = false;
109
    newGroupName.value = '';
110
    selectedSemesters.value = [];
111
    selectedBuildings.value = [];
112
  };
113

114
  const confirm = useConfirm();
115

116
  const { mutateAsync: destroyGroup, isPending: isDestroyed } =
117
    useDestroyGroup();
118

119
  const confirmDelete = () => {
120
    confirm.require({
121
      message: 'Удаление групп может сломать расписание',
122
      header: 'Вы уверены?',
123
      icon: 'pi pi-info-circle',
124
      rejectLabel: 'Отмена',
125
      rejectProps: {
126
        label: 'Отмена',
127
        severity: 'secondary',
128
        outlined: true,
129
      },
130
      acceptProps: {
131
        label: 'Удалить',
132
        severity: 'danger',
133
      },
134
      accept: async () => {
135
        await deleteGroups();
136
      },
137
      reject: () => {},
138
    });
139
  };
140

141
  const deleteGroups = async () => {
142
    if (selectedGroups.value.length === 0) return;
143

144
    for (let i = 0; i < selectedGroups.value.length; i++) {
145
      try {
146
        await destroyGroup(selectedGroups.value[i].id);
147
      } catch (e) {
148
        toast.add({
149
          severity: 'error',
150
          summary: 'Ошибка',
151
          detail: e?.response.data.message,
152
          life: 3000,
153
          closable: true,
154
        });
155
        return;
156
      }
157
    }
158
    selectedGroups.value = [];
159
  };
160

161
  const { data: semesters } = useSemestersQuery();
162

163
  const selectedSemesters = ref([]);
164

165
  const filters = ref({
166
    global: { value: null, matchMode: FilterMatchMode.CONTAINS },
167
    name: { value: null, matchMode: FilterMatchMode.STARTS_WITH },
168
    course: { value: null, matchMode: FilterMatchMode.STARTS_WITH },
169
  });
170

171
  const importGroupsState = ref();
172
  const importingGroups = ref();
173

174
  const parseAndSendGroups = async () => {
175
    // Разделяем введенные группы на массив строк, убирая пустые строки и пробелы
176
    const groups = importingGroups.value
177
      .split('\n')
178
      .map(group => group.trim())
179
      .filter(group => group);
180

181
    // Проходим по каждой группе и отправляем её на сервер
182
    for (const group of groups) {
183
      if (!regexGroup.test(group)) {
184
        toast.add({
185
          severity: 'error',
186
          summary: 'Ошибка',
187
          detail: `Неверный формат названия группы: ${group}. Пример: ИС-401`,
188
          life: 3000,
189
          closable: true,
190
        });
191
        continue; // Пропускаем группу с неверным форматом
192
      }
193

194
      try {
195
        // Отправляем данные на сервер
196
        await storeSubject({
197
          specialization: group.split('-')[0],
198
          course: group.split('-')[1][0],
199
          index: group.split('-')[1].slice(1),
200
          semesters: selectedSemesters.value,
201
          buildings: selectedBuildings.value,
202
        });
203

204
        // Успешное добавление группы
205
        toast.add({
206
          severity: 'success',
207
          summary: 'Успех',
208
          detail: `Группа ${group} успешно добавлена`,
209
          life: 3000,
210
          closable: true,
211
        });
212
      } catch (e) {
213
        // Обработка ошибки при отправке
214
        toast.add({
215
          severity: 'error',
216
          summary: 'Ошибка',
217
          detail:
218
            e?.response?.data?.message ||
219
            `Ошибка при добавлении группы ${group}`,
220
          life: 3000,
221
          closable: true,
222
        });
223
      }
224
    }
225

226
    // Очистка поля после завершения отправки
227
    importingGroups.value = '';
228
    selectedSemesters.value = [];
229
    selectedBuildings.value = [];
230
  };
231

232
  const { data: buildings } = useBuildingsQuery();
233
  const selectedBuildings = ref();
234
</script>
235

236
<template>
237
  <div class="flex flex-col gap-4">
238
    <div class="flex flex-wrap justify-between items-baseline">
239
      <h1 class="text-2xl">Группы</h1>
240
    </div>
241
    <div class="">
242
      <form
243
        class="flex flex-wrap items-center gap-4 p-4 rounded-lg bg-surface-100 dark:bg-surface-800"
244
      >
245
        <InputText
246
          v-model="newGroupName"
247
          :invalid="newGroupError"
248
          placeholder="Пример: ИС-401"
249
          class="w-full md:w-56"
250
        />
251
        <MultiSelect
252
          v-model="selectedSemesters"
253
          display="chip"
254
          :options="semesters"
255
          option-label="name"
256
          filter
257
          :max-selected-labels="3"
258
          placeholder="Выбрать семестры"
259
          class="w-full md:w-60"
260
        />
261
        <MultiSelect
262
          v-model="selectedBuildings"
263
          filter
264
          auto-filter-focus
265
          option-label="name"
266
          :options="buildings"
267
          placeholder="Корпус"
268
          class="w-full md:w-36"
269
        />
270

271
        <Button
272
          type="submit"
273
          :disabled="!newGroupName"
274
          @click.prevent="addGroup"
275
        >
276
          Добавить группу
277
        </Button>
278
        <Button
279
          icon="pi pi-file-import"
280
          outlined
281
          type="submit"
282
          label="Импорт"
283
          @click.prevent="importGroupsState = !importGroupsState"
284
        />
285
        <div v-if="importGroupsState" class="flex flex-col gap-2">
286
          <Textarea
287
            v-model="importingGroups"
288
            placeholder="Введите в столбик название групп"
289
            rows="5"
290
            cols="30"
291
          />
292
          <Button type="submit" @click.prevent="parseAndSendGroups">
293
            Импортировать
294
          </Button>
295
        </div>
296
      </form>
297
    </div>
298
    <div class="">
299
      <DataTable
300
        v-model:filters="filters"
301
        v-model:selection="selectedGroups"
302
        v-model:editing-rows="editingRows"
303
        paginator
304
        :rows="10"
305
        :global-filter-fields="['name', 'course']"
306
        :loading="isUpdated || isDestroyed || isStored"
307
        :value="groups"
308
        edit-mode="row"
309
        data-key="id"
310
        :pt="{
311
          table: { style: 'min-width: 50rem' },
312
        }"
313
        @row-edit-save="onRowEditSave"
314
      >
315
        <template #header>
316
          <div class="flex flex-wrap items-center gap-2 justify-between">
317
            <Button
318
              severity="danger"
319
              :disabled="!selectedGroups.length || !groups.length"
320
              type="button"
321
              icon="pi pi-trash"
322
              label="Удалить"
323
              outlined
324
              @click="confirmDelete"
325
            />
326
            <InputText v-model="filters['global'].value" placeholder="Поиск" />
327
          </div>
328
        </template>
329
        <Column selection-mode="multiple" header-style="width: 3rem" />
330
        <Column field="name" header="Название группы" />
331
        <Column field="buildings" header="Корпус">
332
          <template #body="slotProps">
333
            <div class="flex gap-2 flex-wrap">
334
              <Chip
335
                v-for="building in slotProps.data.buildings"
336
                :label="building.name"
337
              />
338
            </div>
339
          </template>
340
          <template #editor="{ data, field }">
341
            <MultiSelect
342
              v-model="data.buildings"
343
              data-key="name"
344
              :options="buildings"
345
              display="chip"
346
              option-label="name"
347
              filter
348
              placeholder="Выберите корпуса"
349
              class=""
350
            />
351
          </template>
352
        </Column>
353
        <Column field="specialization" header="Специальность">
354
          <template #editor="{ data, field }">
355
            <InputText v-model="data[field]" fluid />
356
          </template>
357
        </Column>
358
        <Column field="course" header="Курс">
359
          <template #editor="{ data, field }">
360
            <Select
361
              v-model="data[field]"
362
              :options="courses"
363
              option-label="label"
364
              option-value="value"
365
            />
366
          </template>
367
        </Column>
368
        <Column field="semesters" header="Семестры">
369
          <template #body="slotProps">
370
            <div class="flex gap-2 flex-wrap">
371
              <Chip
372
                v-for="semester in slotProps.data.semesters"
373
                :label="semester.name"
374
              />
375
            </div>
376
          </template>
377
          <template #editor="{ data, field }">
378
            <MultiSelect
379
              v-model="data.semesters"
380
              :max-selected-labels="2"
381
              display="chip"
382
              :options="semesters"
383
              option-label="name"
384
              filter
385
              placeholder="Выберите семестры"
386
              class="w-48"
387
            />
388
          </template>
389
        </Column>
390
        <Column field="updated_at" header="Дата изменения" style="width: 20%">
391
          <template #body="slotProps">
392
            {{ useDateFormat(slotProps.data.updated_at, 'DD.MM.YY HH:mm:ss') }}
393
          </template>
394
        </Column>
395
        <Column
396
          :row-editor="true"
397
          style="width: 10%; min-width: 8rem"
398
          body-style="text-align:center"
399
        />
400
      </DataTable>
401
    </div>
402
  </div>
403
</template>
404

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

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

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

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