<script setup lang="ts">
import { computed, nextTick, onMounted, ref, watch } from 'vue'
import { Document, Download } from '@element-plus/icons-vue'
import { useRoute } from 'vue-router'
import http, { apiErrorMessage } from '../api/http'
import { downloadApiFile } from '../api/excel'
import { useAuthStore } from '../stores/auth'

const route = useRoute()
const auth = useAuthStore()
const isMine = computed(() => route.meta.mine === true)
const isPublic = computed(() => route.meta.public === true)
const isManager = computed(() =>
  !isMine.value &&
  !isPublic.value &&
  (auth.user?.roles.some((role) =>
    ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Leader'].includes(role)) ?? false),
)
const loading = ref(false)
const exportingPdf = ref(false)
const terms = ref<any[]>([])
const colleges = ref<any[]>([])
const majors = ref<any[]>([])
const classes = ref<any[]>([])
const teachers = ref<any[]>([])
const campuses = ref<any[]>([])
const buildings = ref<any[]>([])
const classrooms = ref<any[]>([])
const plans = ref<any[]>([])
const termId = ref('')
const planId = ref('')
const resourceType = ref<'Class' | 'Teacher' | 'Classroom'>('Class')
const grade = ref<number | undefined>()
const collegeId = ref('')
const majorId = ref('')
const classId = ref('')
const teacherId = ref('')
const campusId = ref('')
const buildingId = ref('')
const classroomId = ref('')
const timetable = ref<any | null>(null)
const viewMode = ref<'week' | 'day'>('week')
const selectedDay = ref(1)
const exportArea = ref<HTMLElement | null>(null)
const weekdays = ['', '周一', '周二', '周三', '周四', '周五', '周六', '周日']
const patternLabels: Record<string, string> = { All: '每周', Odd: '单周', Even: '双周' }
const planStatusLabels: Record<string, string> = {
  Draft: '草稿',
  Published: '已发布',
  Archived: '已归档',
}

const grades = computed(() =>
  [...new Set(classes.value.map((item) => item.grade))].sort((a, b) => b - a),
)
const filteredMajors = computed(() =>
  majors.value.filter((item) => !collegeId.value || item.collegeId === collegeId.value),
)
const filteredClasses = computed(() =>
  classes.value.filter((item) =>
    (!grade.value || item.grade === grade.value) &&
    (!collegeId.value || item.collegeId === collegeId.value) &&
    (!majorId.value || item.majorId === majorId.value),
  ),
)
const filteredTeachers = computed(() =>
  teachers.value.filter((item) => !collegeId.value || item.collegeId === collegeId.value),
)
const filteredBuildings = computed(() =>
  buildings.value.filter((item) => !campusId.value || item.campusId === campusId.value),
)
const filteredClassrooms = computed(() =>
  classrooms.value.filter((item) =>
    (!campusId.value || item.campusId === campusId.value) &&
    (!buildingId.value || item.buildingId === buildingId.value),
  ),
)
const selectedResourceId = computed(() => {
  if (resourceType.value === 'Teacher') return teacherId.value
  if (resourceType.value === 'Classroom') return classroomId.value
  return classId.value
})
const maxPeriods = computed(() => {
  const slotMaximum = Math.max(0, ...((timetable.value?.slots ?? []).map((x: any) => x.periodNumber)))
  const entryMaximum = Math.max(
    0,
    ...((timetable.value?.entries ?? []).map((x: any) => x.startPeriod + x.periodCount - 1)),
  )
  return Math.max(8, slotMaximum, entryMaximum)
})
const slotMap = computed<Map<number, any>>(() =>
  new Map<number, any>(
    (timetable.value?.slots ?? []).map((item: any) => [item.periodNumber, item]),
  ),
)
const dayEntries = computed(() =>
  (timetable.value?.entries ?? []).filter((entry: any) => entry.dayOfWeek === selectedDay.value),
)

function formatTime(value: string) {
  return value?.slice(0, 5) ?? ''
}

function entryStyle(entry: any) {
  return {
    gridColumn: String(entry.dayOfWeek + 1),
    gridRow: `${entry.startPeriod + 1} / span ${entry.periodCount}`,
  }
}

function weeks(entry: any) {
  const pattern = patternLabels[entry.weekPattern]
  return `${entry.startWeek}—${entry.endWeek}周${pattern === '每周' ? '' : ` · ${pattern}`}`
}

function location(entry: any) {
  return [entry.campusName, entry.buildingName, entry.classroomName].filter(Boolean).join(' · ')
}

function dayEntriesAt(period: number) {
  return dayEntries.value.filter((entry: any) => entry.startPeriod === period)
}

function setClassFilters(item: any) {
  if (!item) return
  grade.value = item.grade
  collegeId.value = item.collegeId
  majorId.value = item.majorId
}

async function loadPublicOptions() {
  const { data } = await http.get('/timetables/options')
  terms.value = data.terms
  colleges.value = data.colleges
  majors.value = data.majors
  classes.value = data.classes
  termId.value = data.terms.find((item: any) => item.isCurrent)?.id
    ?? data.terms.find((item: any) => item.hasPublishedTimetable)?.id
    ?? data.terms[0]?.id
    ?? ''
  const initialClass = data.classes.find((item: any) => item.hasPublishedTimetable)
    ?? data.classes[0]
  classId.value = initialClass?.id ?? ''
  setClassFilters(initialClass)
}

async function loadManagementOptions() {
  if (!isManager.value || !termId.value) return
  const { data } = await http.get('/timetables/management/options', {
    params: { academicTermId: termId.value },
  })
  plans.value = data.plans
  colleges.value = data.colleges
  majors.value = data.majors
  classes.value = data.classes
  teachers.value = data.teachers
  campuses.value = data.campuses
  buildings.value = data.buildings
  classrooms.value = data.classrooms
  if (!data.plans.some((item: any) => item.id === planId.value)) {
    planId.value = data.plans.find((item: any) => item.status === 'Published')?.id
      ?? data.plans[0]?.id
      ?? ''
  }
  if (!data.classes.some((item: any) => item.id === classId.value)) {
    classId.value = data.classes[0]?.id ?? ''
    setClassFilters(data.classes[0])
  }
  if (!data.teachers.some((item: any) => item.id === teacherId.value)) {
    teacherId.value = data.teachers[0]?.id ?? ''
  }
  if (!data.classrooms.some((item: any) => item.id === classroomId.value)) {
    classroomId.value = data.classrooms[0]?.id ?? ''
  }
}

async function onTermChanged() {
  if (isManager.value) await loadManagementOptions()
  await loadTimetable()
}

function onGradeChanged() {
  classId.value = ''
}

function onCollegeChanged() {
  majorId.value = ''
  classId.value = ''
  teacherId.value = ''
}

function onMajorChanged() {
  classId.value = ''
}

function onCampusChanged() {
  buildingId.value = ''
  classroomId.value = ''
}

function onBuildingChanged() {
  classroomId.value = ''
}

function onResourceTypeChanged() {
  collegeId.value = ''
  majorId.value = ''
  grade.value = undefined
  campusId.value = ''
  buildingId.value = ''
  if (resourceType.value === 'Class') {
    classId.value = classes.value[0]?.id ?? ''
    setClassFilters(classes.value[0])
  } else if (resourceType.value === 'Teacher') {
    teacherId.value = teachers.value[0]?.id ?? ''
  } else {
    classroomId.value = classrooms.value[0]?.id ?? ''
  }
}

async function loadTimetable() {
  if (!termId.value || (!isMine.value && !selectedResourceId.value)) {
    timetable.value = null
    return
  }
  loading.value = true
  try {
    if (isMine.value) {
      timetable.value = (await http.get('/timetables/mine', {
        params: { academicTermId: termId.value },
      })).data
    } else if (isManager.value) {
      timetable.value = (await http.get('/timetables/management/query', {
        params: {
          resourceType: resourceType.value,
          resourceId: selectedResourceId.value,
          academicTermId: termId.value,
          schedulePlanId: planId.value || undefined,
        },
      })).data
    } else {
      timetable.value = (await http.get(`/timetables/classes/${classId.value}`, {
        params: { academicTermId: termId.value },
      })).data
    }
  } catch (error) {
    timetable.value = null
    ElMessage.error(apiErrorMessage(error))
  } finally {
    loading.value = false
  }
}

async function exportExcel() {
  try {
    if (isMine.value) {
      await downloadApiFile(
        `/timetables/mine/export.xlsx?academicTermId=${termId.value}`,
        '我的课表.xlsx',
      )
    } else if (isManager.value) {
      const query = new URLSearchParams({
        resourceType: resourceType.value,
        resourceId: selectedResourceId.value,
        academicTermId: termId.value,
      })
      if (planId.value) query.set('schedulePlanId', planId.value)
      await downloadApiFile(
        `/timetables/management/export.xlsx?${query}`,
        `${timetable.value?.subject?.name ?? '课表'}.xlsx`,
      )
    } else {
      await downloadApiFile(
        `/timetables/classes/${classId.value}/export.xlsx?academicTermId=${termId.value}`,
        `${timetable.value?.subject?.name ?? '班级'}课表.xlsx`,
      )
    }
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
}

async function exportPdf() {
  if (!exportArea.value || !timetable.value) return
  exportingPdf.value = true
  try {
    await nextTick()
    const [{ default: html2canvas }, { jsPDF }] = await Promise.all([
      import('html2canvas'),
      import('jspdf'),
    ])
    const canvas = await html2canvas(exportArea.value, {
      scale: 2,
      useCORS: true,
      backgroundColor: '#ffffff',
    })
    const pdf = new jsPDF({ orientation: 'landscape', unit: 'mm', format: 'a4' })
    const pageWidth = 297
    const pageHeight = 210
    const imageHeight = canvas.height * pageWidth / canvas.width
    const image = canvas.toDataURL('image/png')
    let remaining = imageHeight
    let position = 0
    pdf.addImage(image, 'PNG', 0, position, pageWidth, imageHeight)
    remaining -= pageHeight
    while (remaining > 0) {
      position = remaining - imageHeight
      pdf.addPage()
      pdf.addImage(image, 'PNG', 0, position, pageWidth, imageHeight)
      remaining -= pageHeight
    }
    pdf.save(`${timetable.value.subject?.name ?? '课表'}-${timetable.value.term.name}.pdf`)
  } catch (error) {
    ElMessage.error(`PDF 导出失败：${apiErrorMessage(error)}`)
  } finally {
    exportingPdf.value = false
  }
}

watch([classId, teacherId, classroomId, planId, resourceType], loadTimetable)

onMounted(async () => {
  try {
    await loadPublicOptions()
    if (isManager.value) await loadManagementOptions()
    await loadTimetable()
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
})
</script>

<template>
  <main :class="['timetable-page', { 'public-timetable': isPublic }]">
    <header v-if="isPublic" class="public-header">
      <router-link class="public-brand" to="/timetable">明序教务 · 课表查询</router-link>
      <router-link class="login-link" to="/login">登录工作台</router-link>
    </header>

    <section class="timetable-heading">
      <div>
        <span class="section-kicker">{{ isMine ? 'MY TIMETABLE' : isManager ? 'TIMETABLE CENTER' : 'CLASS TIMETABLE' }}</span>
        <h2>{{ isMine ? '我的课表' : isManager ? '课表查询中心' : '班级课表查询' }}</h2>
        <p v-if="isMine">行政班课程与本人已选课程统一展示，仅采用教务处已发布课表。</p>
        <p v-else-if="isManager">查询班级、教师与场地课表，可切换草稿或正式发布版本。</p>
        <p v-else>按年级、学院、专业和班级分类查询正式发布的课程安排。</p>
      </div>
      <div class="timetable-filters">
        <el-select v-model="termId" filterable placeholder="选择学期" @change="onTermChanged">
          <el-option
            v-for="term in terms"
            :key="term.id"
            :label="`${term.name}${!isManager && !term.hasPublishedTimetable ? '（未发布）' : ''}`"
            :value="term.id"
          />
        </el-select>
        <el-select v-if="isManager" v-model="planId" placeholder="选择课表版本">
          <el-option
            v-for="item in plans"
            :key="item.id"
            :label="`${item.version} · ${item.name} · ${planStatusLabels[item.status]}`"
            :value="item.id"
          >
            <span>{{ item.version }} · {{ item.name }}</span>
            <el-tag
              size="small"
              :type="item.status === 'Published' ? 'success' : 'warning'"
            >{{ planStatusLabels[item.status] }}</el-tag>
          </el-option>
        </el-select>
      </div>
    </section>

    <section v-if="!isMine" class="resource-filter-panel">
      <div v-if="isManager" class="resource-type-tabs">
        <el-radio-group v-model="resourceType" @change="onResourceTypeChanged">
          <el-radio-button value="Class">班级课表</el-radio-button>
          <el-radio-button value="Teacher">教师课表</el-radio-button>
          <el-radio-button value="Classroom">场地课表</el-radio-button>
        </el-radio-group>
      </div>
      <div v-if="resourceType === 'Class'" class="hierarchy-filters">
        <el-select v-model="grade" clearable placeholder="全部年级" @change="onGradeChanged">
          <el-option v-for="item in grades" :key="item" :label="`${item} 级`" :value="item" />
        </el-select>
        <el-select v-model="collegeId" clearable placeholder="全部学院" @change="onCollegeChanged">
          <el-option v-for="item in colleges" :key="item.id" :label="item.name" :value="item.id" />
        </el-select>
        <el-select v-model="majorId" clearable placeholder="全部专业" @change="onMajorChanged">
          <el-option v-for="item in filteredMajors" :key="item.id" :label="item.name" :value="item.id" />
        </el-select>
        <el-select v-model="classId" filterable placeholder="选择行政班">
          <el-option
            v-for="item in filteredClasses"
            :key="item.id"
            :label="`${item.code} · ${item.name}`"
            :value="item.id"
          />
        </el-select>
      </div>
      <div v-else-if="resourceType === 'Teacher'" class="hierarchy-filters compact">
        <el-select v-model="collegeId" clearable placeholder="全部学院" @change="onCollegeChanged">
          <el-option v-for="item in colleges" :key="item.id" :label="item.name" :value="item.id" />
        </el-select>
        <el-select v-model="teacherId" filterable placeholder="选择教师">
          <el-option
            v-for="item in filteredTeachers"
            :key="item.id"
            :label="`${item.teacherNumber} · ${item.name}${item.title ? ` · ${item.title}` : ''}`"
            :value="item.id"
          />
        </el-select>
      </div>
      <div v-else class="hierarchy-filters compact">
        <el-select v-model="campusId" clearable placeholder="全部校区" @change="onCampusChanged">
          <el-option v-for="item in campuses" :key="item.id" :label="item.name" :value="item.id" />
        </el-select>
        <el-select v-model="buildingId" clearable placeholder="全部教学楼" @change="onBuildingChanged">
          <el-option v-for="item in filteredBuildings" :key="item.id" :label="item.name" :value="item.id" />
        </el-select>
        <el-select v-model="classroomId" filterable placeholder="选择教室">
          <el-option
            v-for="item in filteredClassrooms"
            :key="item.id"
            :label="`${item.buildingName} · ${item.name}（${item.capacity} 人）`"
            :value="item.id"
          />
        </el-select>
      </div>
      <small>
        当前可选：
        {{ resourceType === 'Class' ? filteredClasses.length : resourceType === 'Teacher' ? filteredTeachers.length : filteredClassrooms.length }}
        项
      </small>
    </section>

    <section v-loading="loading" class="timetable-sheet">
      <div v-if="timetable" class="timetable-toolbar">
        <div>
          <el-radio-group v-model="viewMode" size="small">
            <el-radio-button value="week">周视图</el-radio-button>
            <el-radio-button value="day">日视图</el-radio-button>
          </el-radio-group>
          <el-select v-if="viewMode === 'day'" v-model="selectedDay" size="small">
            <el-option v-for="day in 7" :key="day" :label="weekdays[day]" :value="day" />
          </el-select>
        </div>
        <div>
          <el-button :icon="Download" @click="exportExcel">导出 Excel</el-button>
          <el-button :icon="Document" :loading="exportingPdf" @click="exportPdf">导出 PDF</el-button>
        </div>
      </div>

      <div v-if="timetable" ref="exportArea" class="timetable-export-area">
      <div class="sheet-meta">
        <div>
          <strong>{{ timetable.subject.name }}</strong>
          <span>
            {{ timetable.subject.code }}
            <template v-if="timetable.subject.collegeName"> · {{ timetable.subject.collegeName }}</template>
            <template v-if="timetable.subject.majorName"> · {{ timetable.subject.majorName }}</template>
            <template v-if="timetable.subject.description"> · {{ timetable.subject.description }}</template>
          </span>
        </div>
        <div v-if="timetable.student">
          <strong>{{ timetable.student.name }}</strong>
          <span>{{ timetable.student.studentNumber }}</span>
        </div>
        <div>
          <strong>{{ timetable.term.name }}</strong>
          <span v-if="timetable.plan">
            {{ timetable.plan.version }} · {{ planStatusLabels[timetable.plan.status] }}
            <template v-if="timetable.plan.publishedAt">
              · 发布于 {{ new Date(timetable.plan.publishedAt).toLocaleString('zh-CN') }}
            </template>
          </span>
          <span v-else>本学期课表尚未发布</span>
        </div>
       </div>

      <section v-if="timetable?.flexibleCourses?.length" class="flexible-courses">
        <div class="flexible-heading">
          <div>
            <span>非排时课程</span>
            <strong>不占正常上课时间与场地</strong>
          </div>
          <small>共 {{ timetable.flexibleCourses.length }} 门</small>
        </div>
        <div class="flexible-course-list">
          <article v-for="course in timetable.flexibleCourses" :key="course.id">
            <span>{{ course.courseCode }} · {{ course.taskNumber }}</span>
            <strong>{{ course.courseName }}</strong>
            <p>{{ course.teacherNames.join('、') || '教师待定' }}</p>
            <small>
              第 {{ course.startWeek }}—{{ course.endWeek }} 周 ·
              每周 {{ course.weeklyHours }} 学时 · 共 {{ course.totalHours }} 学时
            </small>
          </article>
        </div>
      </section>

      <div v-if="viewMode === 'week' && timetable?.plan && timetable.entries.length" class="timetable-scroll">
        <div
          class="week-grid"
          :style="{ gridTemplateRows: `48px repeat(${maxPeriods}, 110px)` }"
        >
          <div class="grid-corner">节次</div>
          <div v-for="day in 7" :key="`head-${day}`" class="day-head">{{ weekdays[day] }}</div>
          <template v-for="period in maxPeriods" :key="`period-${period}`">
            <div class="period-head" :style="{ gridRow: String(period + 1) }">
              <strong>第 {{ period }} 节</strong>
              <span v-if="slotMap.get(period)">
                {{ formatTime(slotMap.get(period).startsAt) }}—{{ formatTime(slotMap.get(period).endsAt) }}
              </span>
            </div>
            <div
              v-for="day in 7"
              :key="`cell-${period}-${day}`"
              class="grid-cell"
              :style="{ gridColumn: String(day + 1), gridRow: String(period + 1) }"
            />
          </template>
          <article
            v-for="entry in timetable.entries"
            :key="entry.id"
            class="course-block"
            :style="entryStyle(entry)"
          >
            <strong>{{ entry.courseName }}</strong>
            <span>{{ entry.teacherNames.join('、') || '教师待定' }}</span>
            <span>{{ location(entry) }}</span>
            <small>{{ weeks(entry) }} · 第 {{ entry.startPeriod }}—{{ entry.startPeriod + entry.periodCount - 1 }} 节</small>
          </article>
        </div>
      </div>
      <div v-else-if="viewMode === 'day' && timetable?.plan" class="day-view">
        <header>
          <strong>{{ weekdays[selectedDay] }}</strong>
          <span>按开始节次排列 · 共 {{ dayEntries.length }} 项安排</span>
        </header>
        <div v-for="period in maxPeriods" :key="`day-${period}`" class="day-period-row">
          <div class="day-period-label">
            <strong>第 {{ period }} 节</strong>
            <span v-if="slotMap.get(period)">
              {{ formatTime(slotMap.get(period).startsAt) }}—{{ formatTime(slotMap.get(period).endsAt) }}
            </span>
          </div>
          <div class="day-course-list">
            <article v-for="entry in dayEntriesAt(period)" :key="entry.id">
              <strong>{{ entry.courseName }}</strong>
              <span>{{ entry.teacherNames.join('、') || '教师待定' }}</span>
              <span>{{ location(entry) }}</span>
              <small>{{ weeks(entry) }} · 连续 {{ entry.periodCount }} 节</small>
            </article>
            <span v-if="!dayEntriesAt(period).length" class="day-empty">无课程安排</span>
          </div>
        </div>
      </div>
      <el-empty
        v-else-if="timetable && !loading && !timetable.flexibleCourses?.length"
        :description="timetable.plan ? '该课表暂时没有课程安排' : '所选学期尚未发布课表'"
      />
      </div>
    </section>
  </main>
</template>

<style scoped>
.timetable-page { min-width: 0; width: 100%; display: grid; grid-template-columns: minmax(0, 1fr); gap: 20px; }
.public-timetable { max-width: 100vw; min-height: 100vh; padding: 0 32px 40px; box-sizing: border-box; background: #f4f7fa; }
.public-header { height: 68px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #dce4eb; }
.public-brand { color: #17324d; font-weight: 750; text-decoration: none; letter-spacing: .02em; }
.login-link { color: #176b87; text-decoration: none; font-weight: 650; }
.timetable-heading { display: flex; flex-wrap: wrap; align-items: end; justify-content: space-between; gap: 24px; padding: 24px 28px; background: #fff; border: 1px solid #dce4eb; }
.timetable-heading h2 { margin: 5px 0 8px; color: #17324d; font-size: 27px; }
.timetable-heading p { margin: 0; color: #647587; }
.timetable-filters { display: flex; flex-wrap: wrap; gap: 12px; }
.timetable-filters .el-select { width: 270px; }
.resource-filter-panel { padding: 16px 18px; display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 14px; border: 1px solid #dce4eb; border-left: 4px solid #176b87; background: #fff; }
.resource-type-tabs { flex-shrink: 0; }
.hierarchy-filters { min-width: 0; display: grid; grid-template-columns: repeat(4, minmax(140px, 1fr)); gap: 10px; }
.hierarchy-filters.compact { grid-template-columns: repeat(3, minmax(170px, 260px)); }
.resource-filter-panel > small { color: #718191; white-space: nowrap; }
.timetable-sheet { min-width: 0; min-height: 360px; padding: 22px; background: #fff; border: 1px solid #dce4eb; }
.timetable-toolbar { margin-bottom: 16px; display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.timetable-toolbar > div { display: flex; align-items: center; gap: 10px; }
.timetable-toolbar .el-select { width: 120px; }
.timetable-export-area { min-width: 0; padding: 2px; background: #fff; }
.sheet-meta { display: flex; justify-content: space-between; gap: 24px; margin-bottom: 18px; padding-bottom: 16px; border-bottom: 1px solid #e6ebf0; }
.sheet-meta div { display: grid; gap: 3px; }
.sheet-meta strong { color: #17324d; }
.sheet-meta span { color: #718191; font-size: 13px; }
.flexible-courses { margin-bottom: 20px; border: 1px solid #cfe1dc; background: #f5faf8; }
.flexible-heading { padding: 12px 15px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #dce9e5; }
.flexible-heading > div { display: flex; align-items: baseline; gap: 10px; }
.flexible-heading span { color: #176b5d; font-size: 12px; font-weight: 750; }
.flexible-heading strong { color: #506c65; font-size: 12px; }
.flexible-heading small { color: #718b84; }
.flexible-course-list { padding: 12px; display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 10px; }
.flexible-course-list article { padding: 12px 13px; display: grid; gap: 4px; border-left: 3px solid #2d8975; background: #fff; }
.flexible-course-list article > span { color: #277766; font: 700 10px/1.3 Consolas, monospace; }
.flexible-course-list article strong { color: #173f38; font-size: 14px; }
.flexible-course-list article p { margin: 0; color: #5e746f; font-size: 12px; }
.flexible-course-list article small { color: #778b86; font-size: 11px; }
.timetable-scroll { overflow: auto; }
.week-grid { min-width: 1120px; display: grid; grid-template-columns: 92px repeat(7, minmax(140px, 1fr)); position: relative; }
.grid-corner, .day-head, .period-head { z-index: 2; background: #f3f6f8; border: 1px solid #dce4eb; color: #3f5366; }
.grid-corner, .day-head { display: grid; place-items: center; font-weight: 700; }
.grid-corner { grid-column: 1; grid-row: 1; }
.day-head { grid-row: 1; }
.day-head:nth-of-type(2) { grid-column: 2; }
.day-head:nth-of-type(3) { grid-column: 3; }
.day-head:nth-of-type(4) { grid-column: 4; }
.day-head:nth-of-type(5) { grid-column: 5; }
.day-head:nth-of-type(6) { grid-column: 6; }
.day-head:nth-of-type(7) { grid-column: 7; }
.day-head:nth-of-type(8) { grid-column: 8; }
.period-head { grid-column: 1; display: grid; place-content: center; gap: 4px; text-align: center; }
.period-head span { color: #7c8b98; font-size: 11px; }
.grid-cell { border: 1px solid #e4e9ee; background: #fff; }
.course-block { z-index: 3; margin: 4px; padding: 9px 10px; overflow: hidden; display: flex; flex-direction: column; gap: 4px; border-left: 4px solid #176b87; background: #e9f3f5; color: #24475a; box-shadow: 0 2px 5px rgba(23, 50, 77, .08); }
.course-block strong { color: #123a4b; font-size: 14px; }
.course-block span { font-size: 12px; }
.course-block small { margin-top: auto; color: #5f7885; font-size: 11px; }
.day-view { border: 1px solid #dce4eb; }
.day-view > header { padding: 14px 16px; display: flex; align-items: baseline; justify-content: space-between; background: #173e72; color: #fff; }
.day-view > header strong { font-size: 18px; }
.day-view > header span { color: #dce8f3; font-size: 12px; }
.day-period-row { min-height: 88px; display: grid; grid-template-columns: 120px minmax(0, 1fr); border-top: 1px solid #e1e7eb; }
.day-period-label { padding: 12px; display: grid; place-content: center; gap: 4px; text-align: center; background: #f2f5f7; color: #43586a; }
.day-period-label span { color: #7b8b98; font-size: 11px; }
.day-course-list { padding: 9px; display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 8px; align-items: stretch; }
.day-course-list article { padding: 10px 12px; display: grid; gap: 3px; border-left: 4px solid #176b87; background: #e9f3f5; color: #395d6c; }
.day-course-list article strong { color: #123a4b; }
.day-course-list article span, .day-course-list article small { font-size: 11px; }
.day-empty { align-self: center; color: #9aa7b1; font-size: 12px; }
@media (max-width: 1100px) {
  .resource-filter-panel { grid-template-columns: 1fr; }
  .hierarchy-filters { grid-template-columns: repeat(2, minmax(150px, 1fr)); }
  .resource-filter-panel > small { white-space: normal; }
}
@media (max-width: 760px) {
  .public-timetable { padding: 0 14px 24px; }
  .timetable-heading, .sheet-meta { align-items: stretch; flex-direction: column; }
  .timetable-filters { flex-direction: column; }
  .timetable-filters .el-select { width: 100%; }
  .hierarchy-filters, .hierarchy-filters.compact { grid-template-columns: 1fr; }
  .timetable-toolbar { align-items: stretch; flex-direction: column; }
  .timetable-toolbar > div { flex-wrap: wrap; }
  .timetable-sheet { padding: 12px; }
  .day-period-row { grid-template-columns: 90px minmax(0, 1fr); }
  .day-course-list { grid-template-columns: 1fr; }
}
</style>
