-front works for student profile
This commit is contained in:
Alexandrina-Kuzeleva
2025-12-03 22:10:43 +03:00
parent ee9aed6bbc
commit 7c9ef2a702

View File

@@ -1,176 +1,424 @@
<template>
<div>
<div class="min-h-screen bg-surface-gray-1">
<NoPermission v-if="!$user.data" />
<div v-else-if="profile.error">
<p class="text-red-500">Ошибка загрузки профиля: {{ profile.error.message }}</p>
<div v-else-if="profile.error" class="p-6">
<div class="max-w-4xl mx-auto bg-white rounded-xl shadow-sm p-6 border border-red-200">
<p class="text-red-500 text-lg font-medium">Ошибка загрузки профиля: {{ profile.error.message }}</p>
</div>
</div>
<div v-else-if="profile.data">
<header class="sticky top-0 z-10 flex items-center justify-between border-b bg-surface-white px-3 py-2.5">
<header class="sticky top-0 z-10 flex items-center justify-between border-b border-gray-200 bg-white px-6 py-4 shadow-sm">
<Breadcrumbs class="h-7" :items="breadcrumbs" />
</header>
<div class="mx-auto -mt-10 max-w-4xl px-5">
<div class="flex items-center min-h-[100px]">
<div class="ml-6">
<h2 class="mt-2 text-3xl font-semibold text-ink-gray-9">{{ displayName }}</h2>
<div class="mt-2 text-base text-ink-gray-7">{{ profile.data.headline || '' }}</div>
</div>
<div class="ml-auto" v-if="$user.data && isSessionUser()">
<Button @click="toggleEdit()">
<template #prefix>
<Edit class="w-4 h-4 stroke-1.5 text-ink-gray-7" />
</template>
{{ editMode ? 'Отмена' : 'Редактировать' }}
</Button>
<div class="mx-auto max-w-6xl px-4 py-6">
<!-- Profile Header -->
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 p-6 -mt-4 relative">
<div class="flex flex-col md:flex-row md:items-center gap-6">
<div class="flex-1">
<h2 class="text-3xl font-bold text-gray-900">{{ displayName }}</h2>
<p class="mt-2 text-lg text-gray-600">{{ profile.data.headline || 'Нет информации о себе' }}</p>
</div>
<div v-if="$user.data && isSessionUser()" class="md:ml-auto">
<Button @click="toggleEdit()" class="bg-primary-600 hover:bg-primary-700 text-white px-5 py-2.5 rounded-lg transition-colors duration-200">
<template #prefix>
<Edit class="w-4 h-4 stroke-1.5" />
</template>
{{ editMode ? 'Отменить редактирование' : 'Редактировать профиль' }}
</Button>
</div>
</div>
</div>
<!-- VIEW MODE -->
<div v-if="!editMode" class="mt-4 space-y-3">
<div v-if="schoolProfile.loading">
<p>Загрузка данных профиля студента...</p>
<div v-if="!editMode" class="mt-6">
<div v-if="schoolProfile.loading" class="bg-white rounded-2xl shadow-sm border border-gray-200 p-8">
<div class="flex items-center justify-center py-12">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600"></div>
</div>
</div>
<div v-else-if="schoolProfile.error">
<p class="text-red-500">Ошибка загрузки данных студента: {{ schoolProfile.error.message }}</p>
<div v-else-if="schoolProfile.error" class="bg-white rounded-2xl shadow-sm border border-red-200 p-6">
<p class="text-red-500 text-lg font-medium">Ошибка загрузки данных студента: {{ schoolProfile.error.message }}</p>
</div>
<div v-else-if="schoolProfile.data">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<b>Фамилия:</b> {{ schoolProfile.data.last_name || '-' }}<br/>
<b>Имя:</b> {{ schoolProfile.data.first_name || '-' }}<br/>
<b>Отчество:</b> {{ schoolProfile.data.middle_name || '-' }}<br/>
<b>Дата рождения:</b> {{ formattedDate(schoolProfile.data.birth_date) || '-' }}
<b>Телефон (приватный):</b> {{ maskPrivate(schoolProfile.data.phone) }}<br/>
<b>Email (приватный):</b> {{ maskPrivate(schoolProfile.data.email_private) }}<br/>
<b>Telegram:</b>
<a v-if="schoolProfile.data.telegram" :href="formatTelegram(schoolProfile.data.telegram)" target="_blank">{{ schoolProfile.data.telegram }}</a>
<div v-else-if="schoolProfile.data" class="space-y-6">
<!-- Основная информация -->
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100 bg-gray-50">
<h3 class="text-xl font-semibold text-gray-900">Основная информация</h3>
</div>
<div>
<b>Университет:</b> {{ schoolProfile.data.school || '-' }}<br/>
<b>Уровень образования:</b> {{ schoolProfile.data.education_level || '-' }}<br/>
<b>Направление подготовки:</b> {{ schoolProfile.data.major || '-' }}<br/>
<b>Образовательная программа:</b> {{ schoolProfile.data.program || '-' }}<br/>
<b>Курс:</b> {{ schoolProfile.data.course || '-' }}<br/>
<b>Староста в группе или нет?</b> {{ schoolProfile.data.group_leader || '-' }}<br/>
<div class="p-6">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div class="space-y-4">
<div class="flex items-start">
<span class="inline-block w-40 text-gray-700 font-medium">Фамилия:</span>
<span class="text-gray-900">{{ schoolProfile.data.last_name || '' }}</span>
</div>
<div class="flex items-start">
<span class="inline-block w-40 text-gray-700 font-medium">Имя:</span>
<span class="text-gray-900">{{ schoolProfile.data.first_name || '—' }}</span>
</div>
<div class="flex items-start">
<span class="inline-block w-40 text-gray-700 font-medium">Отчество:</span>
<span class="text-gray-900">{{ schoolProfile.data.middle_name || '—' }}</span>
</div>
<div class="flex items-start">
<span class="inline-block w-40 text-gray-700 font-medium">Дата рождения:</span>
<span class="text-gray-900">{{ formattedDate(schoolProfile.data.birth_date) || '—' }}</span>
</div>
<div class="flex items-start">
<span class="inline-block w-40 text-gray-700 font-medium">Телефон:</span>
<span class="text-gray-900 font-mono">{{ maskPrivate(schoolProfile.data.phone) }}</span>
</div>
<div class="flex items-start">
<span class="inline-block w-40 text-gray-700 font-medium">Email:</span>
<span class="text-gray-900 font-mono">{{ maskPrivate(schoolProfile.data.email_private) }}</span>
</div>
<div class="flex items-start">
<span class="inline-block w-40 text-gray-700 font-medium">Telegram:</span>
<div class="flex-1">
<a v-if="schoolProfile.data.telegram"
:href="formatTelegram(schoolProfile.data.telegram)"
target="_blank"
class="inline-flex items-center gap-2 text-primary-600 hover:text-primary-700 font-medium transition-colors">
<span>@{{ schoolProfile.data.telegram.replace('@', '') }}</span>
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
</svg>
</a>
<span v-else class="text-gray-500"></span>
</div>
</div>
</div>
<div class="space-y-4">
<div class="flex items-start">
<span class="inline-block w-40 text-gray-700 font-medium">Университет:</span>
<span class="text-gray-900">{{ schoolProfile.data.school || '—' }}</span>
</div>
<div class="flex items-start">
<span class="inline-block w-40 text-gray-700 font-medium">Уровень образования:</span>
<span class="text-gray-900">{{ schoolProfile.data.education_level || '—' }}</span>
</div>
<div class="flex items-start">
<span class="inline-block w-40 text-gray-700 font-medium">Направление подготовки:</span>
<span class="text-gray-900">{{ schoolProfile.data.major || '—' }}</span>
</div>
<div class="flex items-start">
<span class="inline-block w-40 text-gray-700 font-medium">Образовательная программа:</span>
<span class="text-gray-900">{{ schoolProfile.data.program || '—' }}</span>
</div>
<div class="flex items-start">
<span class="inline-block w-40 text-gray-700 font-medium">Курс:</span>
<span class="text-gray-900">{{ schoolProfile.data.course || '—' }}</span>
</div>
<div class="flex items-start">
<span class="inline-block w-40 text-gray-700 font-medium">Староста в группе:</span>
<span class="text-gray-900">
<span :class="schoolProfile.data.group_leader === 'Да' ? 'text-green-600' : 'text-gray-600'">
{{ schoolProfile.data.group_leader || '—' }}
</span>
</span>
</div>
</div>
</div>
</div>
</div>
<div>
<b>Коротко о интересах:</b>
<div class="mt-1 p-3 bg-surface-gray-1 rounded">{{ schoolProfile.data.interests || '-' }}</div>
</div>
<div class="grid md:grid-cols-2 gap-4">
<div>
<b>Коротко о себе:</b>
<div class="mt-1 p-3 bg-surface-gray-1 rounded">{{ schoolProfile.data.about_me || '-' }}</div>
<!-- О себе -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden lg:col-span-2">
<div class="px-6 py-4 border-b border-gray-100 bg-gray-50">
<h3 class="text-xl font-semibold text-gray-900">Коротко о своих интересах</h3>
</div>
<div class="p-6">
<p class="text-gray-700 leading-relaxed whitespace-pre-line">{{ schoolProfile.data.interests || 'Информация не указана' }}</p>
</div>
</div>
<div>
<b>Коротко о мечтах:</b>
<div class="mt-1 p-3 bg-surface-gray-1 rounded">{{ schoolProfile.data.dreams || '-' }}</div>
<div class="space-y-6">
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100 bg-gray-50">
<h3 class="text-xl font-semibold text-gray-900">О себе</h3>
</div>
<div class="p-6">
<p class="text-gray-700 leading-relaxed whitespace-pre-line">{{ schoolProfile.data.about_me || 'Информация не указана' }}</p>
</div>
</div>
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100 bg-gray-50">
<h3 class="text-xl font-semibold text-gray-900">О мечтах</h3>
</div>
<div class="p-6">
<p class="text-gray-700 leading-relaxed whitespace-pre-line">{{ schoolProfile.data.dreams || 'Информация не указана' }}</p>
</div>
</div>
</div>
</div>
</div>
<div v-else>
<p>Данные профиля студента не найдены.</p>
<div v-else class="bg-white rounded-2xl shadow-sm border border-gray-200 p-8">
<div class="text-center py-12">
<div class="mx-auto w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mb-4">
<svg class="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
</svg>
</div>
<h3 class="text-xl font-semibold text-gray-900 mb-2">Данные профиля не найдены</h3>
<p class="text-gray-600">Информация о студенте отсутствует</p>
</div>
</div>
</div>
<!-- EDIT MODE -->
<div v-else class="mt-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<Input v-model="form.last_name" label="Фамилия" />
<Input v-model="form.first_name" label="Имя" />
<Input v-model="form.middle_name" label="Отчество" />
<div>
<label class="block text-sm font-medium text-ink-gray-7 mb-1">Дата рождения</label>
<DatePicker v-model="form.birth_date" label="Дата рождения" />
<div v-else class="mt-6">
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100 bg-gray-50">
<h3 class="text-xl font-semibold text-gray-900">Редактирование профиля</h3>
<p class="text-sm text-gray-600 mt-1">Заполните информацию о себе</p>
</div>
<Input v-model="form.phone" label="Телефон (не публиковать)" />
<Input v-model="form.email_private" label="Email (не публиковать)" />
<Input v-model="form.telegram" label="Telegram (например t.me/username)" />
<div>
<label class="block text-sm font-medium text-ink-gray-7 mb-1">Университет</label>
<input
type="text"
v-model="schoolQuery"
@input="debouncedSearchSchool"
class="w-full border rounded p-2"
placeholder="Начните вводить название университета"
/>
<div v-if="schoolResults.length" class="border rounded mt-1 max-h-44 overflow-auto bg-white">
<div
v-for="s in schoolResults"
:key="s.school"
class="p-2 cursor-pointer hover:bg-surface-gray-2"
@click="selectSchool(s)"
>
<div class="font-medium">{{ s.school}}</div>
<div class="text-xs text-ink-gray-6">{{ s.adress }}</div>
<div class="p-6">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Левая колонка -->
<div class="space-y-6">
<h4 class="text-lg font-semibold text-gray-900 border-b pb-2">Личная информация</h4>
<Input
v-model="form.last_name"
label="Фамилия"
class="bg-gray-50 border-gray-300 focus:border-primary-500 focus:ring-primary-500"
/>
<Input
v-model="form.first_name"
label="Имя"
class="bg-gray-50 border-gray-300 focus:border-primary-500 focus:ring-primary-500"
/>
<Input
v-model="form.middle_name"
label="Отчество"
class="bg-gray-50 border-gray-300 focus:border-primary-500 focus:ring-primary-500"
/>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Дата рождения</label>
<DatePicker
v-model="form.birth_date"
class="w-full bg-gray-50 border-gray-300 rounded-lg focus:border-primary-500 focus:ring-primary-500"
/>
</div>
<Input
v-model="form.phone"
label="Телефон (не публиковать)"
placeholder="+7 (XXX) XXX-XX-XX"
class="bg-gray-50 border-gray-300 focus:border-primary-500 focus:ring-primary-500"
/>
<Input
v-model="form.email_private"
label="Email (не публиковать)"
type="email"
placeholder="example@email.com"
class="bg-gray-50 border-gray-300 focus:border-primary-500 focus:ring-primary-500"
/>
<Input
v-model="form.telegram"
label="Telegram"
placeholder="username или t.me/username"
class="bg-gray-50 border-gray-300 focus:border-primary-500 focus:ring-primary-500"
/>
</div>
<!-- Правая колонка -->
<div class="space-y-6">
<h4 class="text-lg font-semibold text-gray-900 border-b pb-2">Образование</h4>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Университет</label>
<input
type="text"
v-model="schoolQuery"
@input="debouncedSearchSchool"
class="w-full bg-gray-50 border border-gray-300 rounded-lg px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-colors"
placeholder="Начните вводить название университета"
/>
<div v-if="schoolResults.length" class="mt-2 border border-gray-300 rounded-lg overflow-hidden shadow-lg bg-white">
<div
v-for="s in schoolResults"
:key="s.school"
class="p-3 cursor-pointer hover:bg-primary-50 border-b border-gray-100 last:border-b-0 transition-colors"
@click="selectSchool(s)"
>
<div class="font-medium text-gray-900">{{ s.school }}</div>
<div class="text-xs text-gray-500 mt-1">{{ s.adress }}</div>
</div>
</div>
<div v-if="form.school_name && !schoolResults.length" class="mt-2 text-sm text-gray-600">
<span class="font-medium">Выбрана:</span> {{ form.school }}
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Уровень образования</label>
<Select
v-model="form.education_level"
:options="['Бакалавриат','Магистратура','Аспирантура','Базовое высшее образование','Специализированное высшее образование','Профессиональная переподготовка','Повышение квалификации']"
class="bg-gray-50 border-gray-300 focus:border-primary-500 focus:ring-primary-500"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Направление подготовки</label>
<input
type="text"
v-model="majorQuery"
@input="debouncedSearchMajor"
class="w-full bg-gray-50 border border-gray-300 rounded-lg px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-colors"
placeholder="Начните вводить название направления"
/>
<div v-if="majorResults.length" class="mt-2 border border-gray-300 rounded-lg overflow-hidden shadow-lg bg-white">
<div
v-for="m in majorResults"
:key="m.major"
class="p-3 cursor-pointer hover:bg-primary-50 border-b border-gray-100 last:border-b-0 transition-colors"
@click="selectMajor(m)"
>
<div class="font-medium text-gray-900">{{ m.major_name }}</div>
</div>
</div>
<div v-if="form.major_name && !majorResults.length" class="mt-2 text-sm text-gray-600">
<span class="font-medium">Выбрано:</span> {{ form.major_name }}
</div>
</div>
<Input
v-model="form.program"
label="Образовательная программа"
class="bg-gray-50 border-gray-300 focus:border-primary-500 focus:ring-primary-500"
/>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Курс</label>
<Select
v-model="form.course"
:options="['1','2','3','4','5','6']"
class="bg-gray-50 border-gray-300 focus:border-primary-500 focus:ring-primary-500 w-full"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Староста в группе</label>
<Select
v-model="form.group_leader"
:options="['Да','Нет']"
class="bg-gray-50 border-gray-300 focus:border-primary-500 focus:ring-primary-500 w-full"
/>
</div>
</div>
</div>
</div>
<div v-if="form.school_name && !schoolResults.length" class="text-xs text-ink-gray-6 mt-1">
Выбрана: {{ form.school }}
</div>
</div>
<div>
<label class="block text-sm font-medium text-ink-gray-7 mb-1">Уровень образования</label>
<Select v-model="form.education_level" :options="['Бакалавриат','Магистратура','Аспирантура','Базовое высшее образование',
'Специализированное высшее образование','Профессиональная переподготовка','Повышение квалификации']" label="Уровень образования" />
</div>
<div>
<label class="block text-sm font-medium text-ink-gray-7 mb-1">Направление подготовки</label>
<input
type="text"
v-model="majorQuery"
@input="debouncedSearchMajor"
class="w-full border rounded p-2"
placeholder="Начните вводить название направления"
/>
<div v-if="majorResults.length" class="border rounded mt-1 max-h-44 overflow-auto bg-white">
<div
v-for="m in majorResults"
:key="m.major"
class="p-2 cursor-pointer hover:bg-surface-gray-2"
@click="selectMajor(m)"
<!-- Текстовые поля -->
<div class="mt-8 space-y-6">
<h4 class="text-lg font-semibold text-gray-900 border-b pb-2">Дополнительная информация</h4>
<Textarea
v-model="form.interests"
label="Коротко о своих интересах (2-3 предложения)"
rows="4"
class="bg-gray-50 border-gray-300 focus:border-primary-500 focus:ring-primary-500"
/>
<Textarea
v-model="form.about_me"
label="Коротко о себе"
rows="4"
class="bg-gray-50 border-gray-300 focus:border-primary-500 focus:ring-primary-500"
/>
<Textarea
v-model="form.dreams"
label="Коротко о своих мечтах"
rows="4"
class="bg-gray-50 border-gray-300 focus:border-primary-500 focus:ring-primary-500"
/>
</div>
<!-- Кнопки действий -->
<div class="mt-8 pt-6 border-t border-gray-200 flex gap-3">
<Button
@click="saveProfile"
:loading="saving"
class="bg-primary-600 hover:bg-primary-700 text-white px-6 py-3 rounded-lg font-medium transition-colors duration-200 flex items-center gap-2"
>
<div class="font-medium">{{ m.major_name}}</div>
</div>
</div>
<div v-if="form.major_name && !majorResults.length" class="text-xs text-ink-gray-6 mt-1">
Выбрано: {{ form.major_name }}
<svg v-if="!saving" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
</svg>
{{ saving ? 'Сохранение...' : 'Сохранить изменения' }}
</Button>
<Button
variant="outline"
@click="toggleEdit()"
class="border-gray-300 text-gray-700 hover:bg-gray-50 px-6 py-3 rounded-lg font-medium transition-colors duration-200"
>
Отмена
</Button>
</div>
</div>
<Input v-model="form.program" label="Образовательная программа" />
<div>
<label class="block text-sm font-medium text-ink-gray-7 mb-1">Курс</label>
<Select v-model="form.course" :options="['1','2','3','4','5','6']" label="Курс" />
<label class="block text-sm font-medium text-ink-gray-7 mb-1">Староста в группе или нет?</label>
<Select v-model="form.group_leader" :options="['Да','Нет']" label="Староста в группе или нет?" />
</div>
</div>
<div class="mt-4 grid md:grid-cols-2 gap-4">
<Textarea v-model="form.interests" label="Коротко о своих интересах (2-3 предложения)" />
<Textarea v-model="form.about_me" label="Коротко о себе" />
<Textarea class="md:col-span-2" v-model="form.dreams" label="Коротко о своих мечтах" />
</div>
<div class="mt-4 flex space-x-2">
<Button @click="saveProfile" :loading="saving">Сохранить</Button>
<Button variant="outline" @click="toggleEdit()">Отмена</Button>
</div>
</div>
</div>
</div>
<div v-else>
<p>Загрузка профиля...</p>
<div v-else class="flex items-center justify-center min-h-screen">
<div class="text-center">
<div class="animate-spin rounded-full h-16 w-16 border-b-2 border-primary-600 mx-auto"></div>
<p class="mt-4 text-lg text-gray-600">Загрузка профиля...</p>
</div>
</div>
</div>
</template>
<style scoped>
/* Плавные переходы для интерактивных элементов */
.border-gray-300 {
transition: border-color 0.2s ease;
}
.bg-primary-50 {
background-color: rgba(59, 130, 246, 0.05);
}
/* Стилизация скроллбара для выпадающих списков */
.overflow-auto::-webkit-scrollbar {
width: 6px;
}
.overflow-auto::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.overflow-auto::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.overflow-auto::-webkit-scrollbar-thumb:hover {
background: #a1a1a1;
}
</style>
<script setup>
import { ref, computed, inject, watch, onMounted } from 'vue';
import { Breadcrumbs, createResource, Button, Input, DatePicker, Select, Textarea } from 'frappe-ui';
@@ -600,4 +848,4 @@ watch(
});
}
);
</script>
</script>