|
|
|
@@ -1,7 +1,15 @@
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { computed, inject, onMounted, ref } from 'vue'
|
|
|
|
|
import { coursesApi, lecturesApi, locationsApi, syncApi, tagsApi } from '@/api'
|
|
|
|
|
import type { CourseDto, LectureDto, LocationDto, SyncResultDto, SyncStatusDto, TagDto } from '@/api/types'
|
|
|
|
|
import type {
|
|
|
|
|
ApiScheduleTypeId,
|
|
|
|
|
CourseDto,
|
|
|
|
|
LectureDto,
|
|
|
|
|
LocationDto,
|
|
|
|
|
SyncResultDto,
|
|
|
|
|
SyncStatusDto,
|
|
|
|
|
TagDto,
|
|
|
|
|
} from '@/api/types'
|
|
|
|
|
import GlassCard from '@/components/ui/GlassCard.vue'
|
|
|
|
|
import DataTable from '@/components/ui/DataTable.vue'
|
|
|
|
|
import EmptyState from '@/components/ui/EmptyState.vue'
|
|
|
|
@@ -25,6 +33,16 @@ const syncError = ref('')
|
|
|
|
|
const syncStatus = ref<SyncStatusDto | null>(null)
|
|
|
|
|
const syncResult = ref<SyncResultDto | null>(null)
|
|
|
|
|
const addToast = inject('addToast') as ((message: string, type?: 'success' | 'error' | 'info') => void) | undefined
|
|
|
|
|
const scheduleTypeOptions: Array<{ id: ApiScheduleTypeId; label: string }> = [
|
|
|
|
|
{ id: 'MID_CHECK', label: 'Аттестация' },
|
|
|
|
|
{ id: 'CONS', label: 'Консультация' },
|
|
|
|
|
{ id: 'LAB', label: 'Лабораторное занятие' },
|
|
|
|
|
{ id: 'LECT', label: 'Лекционное занятие' },
|
|
|
|
|
{ id: 'SEMI', label: 'Практическое занятие' },
|
|
|
|
|
{ id: 'EVENT_OTHER', label: 'Прочее' },
|
|
|
|
|
{ id: 'SELF', label: 'Самостоятельная работа' },
|
|
|
|
|
{ id: 'CUR_CHECK', label: 'Текущий контроль' },
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
function toInputDateTime(date: Date) {
|
|
|
|
|
const offsetMs = date.getTimezoneOffset() * 60000
|
|
|
|
@@ -37,7 +55,7 @@ inTwoWeeks.setDate(inTwoWeeks.getDate() + 14)
|
|
|
|
|
|
|
|
|
|
const syncForm = ref({
|
|
|
|
|
specialtyCode: '',
|
|
|
|
|
typeId: '',
|
|
|
|
|
typeIds: [] as ApiScheduleTypeId[],
|
|
|
|
|
timeMin: toInputDateTime(now),
|
|
|
|
|
timeMax: toInputDateTime(inTwoWeeks),
|
|
|
|
|
})
|
|
|
|
@@ -168,7 +186,7 @@ async function runScheduleSync() {
|
|
|
|
|
try {
|
|
|
|
|
syncResult.value = await syncApi.schedule({
|
|
|
|
|
specialtyCode: syncForm.value.specialtyCode.trim() || null,
|
|
|
|
|
typeId: syncForm.value.typeId.trim() || null,
|
|
|
|
|
typeId: syncForm.value.typeIds.length ? syncForm.value.typeIds : null,
|
|
|
|
|
timeMin: syncForm.value.timeMin ? new Date(syncForm.value.timeMin).toISOString() : null,
|
|
|
|
|
timeMax: syncForm.value.timeMax ? new Date(syncForm.value.timeMax).toISOString() : null,
|
|
|
|
|
})
|
|
|
|
@@ -248,8 +266,13 @@ onMounted(() => {
|
|
|
|
|
<input v-model="syncForm.timeMax" class="glass-input" type="datetime-local" />
|
|
|
|
|
<label>Код специальности</label>
|
|
|
|
|
<input v-model="syncForm.specialtyCode" class="glass-input" placeholder="Например, 09.03.04" />
|
|
|
|
|
<label>Тип занятия</label>
|
|
|
|
|
<input v-model="syncForm.typeId" class="glass-input" placeholder="Оставьте пустым для всех типов" />
|
|
|
|
|
<label>Типы пар</label>
|
|
|
|
|
<div class="type-grid">
|
|
|
|
|
<label v-for="type in scheduleTypeOptions" :key="type.id" class="type-option">
|
|
|
|
|
<input v-model="syncForm.typeIds" type="checkbox" :value="type.id" />
|
|
|
|
|
<span>{{ type.label }}</span>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div v-if="visibleSyncResult" class="sync-result">
|
|
|
|
|
Создано: {{ visibleSyncResult.created }},
|
|
|
|
@@ -285,6 +308,21 @@ onMounted(() => {
|
|
|
|
|
.form { display: flex; flex-direction: column; gap: 10px; }
|
|
|
|
|
.form label { font-size: 12px; font-weight: 600; color: var(--color-text-secondary); }
|
|
|
|
|
.form-actions { display: flex; gap: 10px; flex-wrap: wrap; }
|
|
|
|
|
.type-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 8px; }
|
|
|
|
|
.type-option {
|
|
|
|
|
min-height: 38px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
padding: 8px 10px;
|
|
|
|
|
border: 1px solid var(--color-border-glass);
|
|
|
|
|
border-radius: var(--radius-sm);
|
|
|
|
|
background: rgba(255,255,255,0.72);
|
|
|
|
|
color: var(--color-text-primary);
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
.type-option input { flex: 0 0 auto; }
|
|
|
|
|
.type-option span { font-size: 13px; line-height: 1.2; }
|
|
|
|
|
.sync-meta { font-size: 12px; color: var(--color-text-secondary); margin-top: 4px; }
|
|
|
|
|
.sync-result { font-size: 13px; color: var(--color-text-secondary); }
|
|
|
|
|
.sync-error { font-size: 13px; color: var(--color-error); }
|
|
|
|
|