Dev #11

Merged
serega404 merged 87 commits from dev into main 2026-05-25 03:22:55 +03:00
3 changed files with 55 additions and 7 deletions
Showing only changes of commit fb8ad6de7c - Show all commits
@@ -4,7 +4,7 @@ public record SyncScheduleRequest(
string? SpecialtyCode, string? SpecialtyCode,
DateTime? TimeMin, DateTime? TimeMin,
DateTime? TimeMax, DateTime? TimeMax,
string? TypeId IReadOnlyList<string>? TypeId
); );
public record SyncResultDto(int Created, int Updated, int Skipped, string? Error); public record SyncResultDto(int Created, int Updated, int Skipped, string? Error);
+11 -1
View File
@@ -144,11 +144,21 @@ export interface SyncStatusDto {
lastResult?: SyncResultDto | null lastResult?: SyncResultDto | null
} }
export type ApiScheduleTypeId =
| 'MID_CHECK'
| 'CONS'
| 'LAB'
| 'LECT'
| 'SEMI'
| 'EVENT_OTHER'
| 'SELF'
| 'CUR_CHECK'
export interface SyncScheduleRequest { export interface SyncScheduleRequest {
specialtyCode?: string | null specialtyCode?: string | null
timeMin?: string | null timeMin?: string | null
timeMax?: string | null timeMax?: string | null
typeId?: string | null typeId?: ApiScheduleTypeId[] | null
} }
export interface SyncResultDto { export interface SyncResultDto {
+43 -5
View File
@@ -1,7 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, inject, onMounted, ref } from 'vue' import { computed, inject, onMounted, ref } from 'vue'
import { coursesApi, lecturesApi, locationsApi, syncApi, tagsApi } from '@/api' 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 GlassCard from '@/components/ui/GlassCard.vue'
import DataTable from '@/components/ui/DataTable.vue' import DataTable from '@/components/ui/DataTable.vue'
import EmptyState from '@/components/ui/EmptyState.vue' import EmptyState from '@/components/ui/EmptyState.vue'
@@ -25,6 +33,16 @@ const syncError = ref('')
const syncStatus = ref<SyncStatusDto | null>(null) const syncStatus = ref<SyncStatusDto | null>(null)
const syncResult = ref<SyncResultDto | null>(null) const syncResult = ref<SyncResultDto | null>(null)
const addToast = inject('addToast') as ((message: string, type?: 'success' | 'error' | 'info') => void) | undefined 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) { function toInputDateTime(date: Date) {
const offsetMs = date.getTimezoneOffset() * 60000 const offsetMs = date.getTimezoneOffset() * 60000
@@ -37,7 +55,7 @@ inTwoWeeks.setDate(inTwoWeeks.getDate() + 14)
const syncForm = ref({ const syncForm = ref({
specialtyCode: '', specialtyCode: '',
typeId: '', typeIds: [] as ApiScheduleTypeId[],
timeMin: toInputDateTime(now), timeMin: toInputDateTime(now),
timeMax: toInputDateTime(inTwoWeeks), timeMax: toInputDateTime(inTwoWeeks),
}) })
@@ -168,7 +186,7 @@ async function runScheduleSync() {
try { try {
syncResult.value = await syncApi.schedule({ syncResult.value = await syncApi.schedule({
specialtyCode: syncForm.value.specialtyCode.trim() || null, 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, timeMin: syncForm.value.timeMin ? new Date(syncForm.value.timeMin).toISOString() : null,
timeMax: syncForm.value.timeMax ? new Date(syncForm.value.timeMax).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" /> <input v-model="syncForm.timeMax" class="glass-input" type="datetime-local" />
<label>Код специальности</label> <label>Код специальности</label>
<input v-model="syncForm.specialtyCode" class="glass-input" placeholder="Например, 09.03.04" /> <input v-model="syncForm.specialtyCode" class="glass-input" placeholder="Например, 09.03.04" />
<label>Тип занятия</label> <label>Типы пар</label>
<input v-model="syncForm.typeId" class="glass-input" placeholder="Оставьте пустым для всех типов" /> <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"> <div v-if="visibleSyncResult" class="sync-result">
Создано: {{ visibleSyncResult.created }}, Создано: {{ visibleSyncResult.created }},
@@ -285,6 +308,21 @@ onMounted(() => {
.form { display: flex; flex-direction: column; gap: 10px; } .form { display: flex; flex-direction: column; gap: 10px; }
.form label { font-size: 12px; font-weight: 600; color: var(--color-text-secondary); } .form label { font-size: 12px; font-weight: 600; color: var(--color-text-secondary); }
.form-actions { display: flex; gap: 10px; flex-wrap: wrap; } .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-meta { font-size: 12px; color: var(--color-text-secondary); margin-top: 4px; }
.sync-result { font-size: 13px; color: var(--color-text-secondary); } .sync-result { font-size: 13px; color: var(--color-text-secondary); }
.sync-error { font-size: 13px; color: var(--color-error); } .sync-error { font-size: 13px; color: var(--color-error); }