Files
enlight-lms/frontend/src/pages/StudentProfile.vue
Alexandrina-Kuzeleva 7c9ef2a702 TEST UPD
-front works for student profile
2025-12-03 22:10:43 +03:00

852 lines
35 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="min-h-screen bg-surface-gray-1">
<NoPermission v-if="!$user.data" />
<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 border-gray-200 bg-white px-6 py-4 shadow-sm">
<Breadcrumbs class="h-7" :items="breadcrumbs" />
</header>
<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-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" 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" 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">
<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 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 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 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-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>
<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 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"
>
<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>
</div>
</div>
</div>
</div>
<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';
import { sessionStore } from '@/stores/session';
import NoPermission from '@/components/NoPermission.vue';
import { Edit } from 'lucide-vue-next';
import { convertToTitleCase, updateDocumentTitle } from '@/utils';
import debounce from 'lodash/debounce';
const { user } = sessionStore();
const $user = inject('$user');
// Логирование инициализации
console.log('[DEBUG] Инициализация компонента:', {
user: user,
$user: $user.data,
username: $user.data?.username,
});
const props = defineProps({
username: {
type: String,
required: false,
default: '',
},
});
const effectiveUsername = computed(() => {
const username = props.username || $user.data?.username || '';
console.log('[DEBUG] Вычисление effectiveUsername:', { propsUsername: props.username, sessionUsername: $user.data?.username, result: username });
return username;
});
const editMode = ref(false);
const saving = ref(false);
const profile = createResource({
url: 'frappe.client.get',
makeParams(values) {
const username = effectiveUsername.value;
console.log('[DEBUG] Запрос profile:', { doctype: 'User', filters: { username } });
return {
doctype: 'User',
filters: { username },
};
},
onSuccess(data) {
console.log('[DEBUG] Профиль загружен:', data);
},
onError(error) {
console.error('[DEBUG] Ошибка загрузки профиля:', error);
window.frappe?.msgprint({
title: 'Ошибка',
message: 'Не удалось загрузить профиль пользователя: ' + (error.message || 'Неизвестная ошибка'),
indicator: 'red',
});
},
});
const schoolProfile = createResource({
url: 'frappe.client.get',
params: {
doctype: 'Schoolchildren Profile',
filters: { user:user },
},
auto: false,
onSuccess(data) {
console.log('[DEBUG] Профиль школьника загружен:', data);
},
onError(error) {
console.error('[DEBUG] Ошибка загрузки профиля школьника:', error);
window.frappe?.msgprint({
title: 'Ошибка',
message: 'Не удалось загрузить профиль школьника: ' + (error.message || 'Неизвестная ошибка'),
indicator: 'red',
});
},
});
const form = ref({
first_name: '',
last_name: '',
middle_name: '',
birth_date: '',
phone: '',
email_private: '',
telegram: '',
school: '',
education_level: '',
major: '',
program: '',
course: '',
group_leader: '',
interests: '',
about_me: '',
dreams: ''
});
const breadcrumbs = computed(() => {
const username = effectiveUsername.value;
const crumbs = [
{
label: 'People',
route: { name: 'People' },
},
{
label: profile.data?.full_name || 'Профиль',
route: username ? {
name: 'Profile',
params: { username },
} : undefined,
},
];
console.log('[DEBUG] Хлебные крошки:', crumbs);
return crumbs;
});
const pageMeta = computed(() => {
const meta = {
title: profile.data?.full_name || 'Профиль',
description: profile.data?.headline || '',
};
console.log('[DEBUG] Мета-данные страницы:', meta);
return meta;
});
const displayName = computed(() => {
if (!profile.data) {
console.log('[DEBUG] displayName: profile.data не загружен');
return 'Загрузка...';
}
const name = profile.data?.full_name || `${form.value.first_name || ''} ${form.value.last_name || ''}`.trim();
console.log('[DEBUG] Отображаемое имя:', name);
return name;
});
const isSessionUser = () => {
const sessionUser = $user.data?.username;
const profileUser = effectiveUsername.value;
const isSession = sessionUser === profileUser;
console.log('[DEBUG] Проверка isSessionUser:', { sessionUser, profileUser, isSession });
return isSession;
};
function formattedDate(d) {
if (!d) return '';
try {
return new Date(d).toLocaleDateString('ru-RU');
} catch (e) {
console.error('[DEBUG] Ошибка форматирования даты:', e, { date: d });
return d;
}
}
function maskPrivate(val) {
if (!val) return '-';
if (val.includes('@')) {
const parts = val.split('@');
return parts[0].slice(0, 1) + '***@' + parts[1];
}
return val.slice(0, 3) + '***' + val.slice(-2);
}
function formatTelegram(t) {
if (!t) return '';
if (t.startsWith('t.me/') || t.startsWith('https://t.me/')) return (t.startsWith('http') ? t : 'https://' + t);
return 'https://t.me/' + t.replace(/^@/, '');
}
function fillFormFromProfile() {
console.log('[DEBUG] Заполнение формы:', {
schoolProfile: schoolProfile.data,
profile: profile.data,
currentForm: JSON.stringify(form.value, null, 2),
});
form.value.first_name = schoolProfile.data?.first_name || profile.data?.first_name || '';
form.value.last_name = schoolProfile.data?.last_name || profile.data?.last_name || '';
form.value.middle_name = schoolProfile.data?.middle_name || '';
form.value.birth_date = schoolProfile.data?.birth_date || '';
form.value.phone = schoolProfile.data?.phone || '';
form.value.email_private = schoolProfile.data?.email_private || '';
form.value.telegram = schoolProfile.data?.telegram || '';
form.value.school = schoolProfile.data?.school || '';
form.value.education_level = schoolProfile.data?.education_level || '';
form.value.major = schoolProfile.data?.major || '';
form.value.program = schoolProfile.data?.program || '';
form.value.course = schoolProfile.data?.course || '';
form.value.group_leader = schoolProfile.data?.group_leader || '';
form.value.interests = schoolProfile.data?.interests || '';
form.value.about_me = schoolProfile.data?.about_me || '';
form.value.dreams = schoolProfile.data?.dreams || '';
console.log('[DEBUG] Форма после заполнения:', JSON.stringify(form.value, null, 2));
}
function toggleEdit() {
editMode.value = !editMode.value;
if (editMode.value) fillFormFromProfile();
console.log('[DEBUG] Переключение режима редактирования:', { editMode: editMode.value });
}
function validateExams(exams) {
console.log('[DEBUG] Валидация exams:', { exams, validOptions: examOptions });
return exams.every(exam => examOptions.includes(exam));
}
function validateLearnSubjects(subjects) {
console.log('[DEBUG] Валидация learn_subjects:', { subjects, validOptions: learnOptions });
return subjects.every(subject => learnOptions.includes(subject));
}
async function saveProfile() {
console.log('[DEBUG] Сохранение профиля:', { form: form.value });
saving.value = true;
try {
// Создаём копию данных формы
const formData = { ...form.value };
console.log('[DEBUG] Копия formData:', JSON.stringify(formData, null, 2));
// Обновление full_name в User, если нужно
if (formData.first_name || formData.last_name) {
const fullName = `${formData.first_name || ''} ${formData.last_name || ''}`.trim();
console.log('[DEBUG] Обновление User.full_name:', { name: profile.data?.name, fullName });
await createResource({
url: 'frappe.client.set_value',
params: {
doctype: 'User',
name: profile.data?.name,
fieldname: 'full_name',
value: fullName,
},
}).submit();
}
// Получаем docname
let docname = '';
try {
await schoolProfile.reload();
console.log('[DEBUG] Schoolprofile:', { schoolProfile });
docname = schoolProfile?.data?.name;
console.log('[DEBUG] Выбранное имя документа:', docname);
} catch (error) {
console.log('[DEBUG] Ошибка загрузки schoolProfile, продолжаем с profile:', error.message);
}
// Формируем payload из копии данных формы
let payload = {
doctype: 'Schoolchildren Profile',
user: profile.data?.name,
first_name: formData.first_name,
last_name: formData.last_name,
middle_name: formData.middle_name,
birth_date: formData.birth_date,
phone: formData.phone,
email_private: formData.email_private,
telegram: formData.telegram,
school: formData.school || '',
education_level: formData.education_level,
major: formData.major || '',
program: formData.program,
course: formData.course,
group_leader: formData.group_leader,
interests: formData.interests,
about_me: formData.about_me,
dreams: formData.dreams,
last_updated: new Date().toISOString(),
};
console.log('[DEBUG] Сохранение Schoolchildren Profile (payload):', { docname, payload });
// Сохранение или создание документа
if (docname) {
await createResource({
url: 'frappe.client.save',
params: { doc: { ...schoolProfile.data, ...payload } },
}).submit();
} else {
await createResource({
url: 'frappe.client.insert',
params: { doc: payload },
}).submit();
}
editMode.value = false;
if (window.frappe && window.frappe.msgprint) window.frappe.msgprint('Профиль сохранён');
console.log('[DEBUG] Профиль успешно сохранён');
} catch (e) {
console.error('[DEBUG] Ошибка при сохранении профиля:', e);
if (window.frappe && window.frappe.msgprint) window.frappe.msgprint({
title: 'Ошибка',
message: (e && e.message) || 'Ошибка при сохранении',
indicator: 'red',
});
} finally {
saving.value = false;
}
await schoolProfile.reload();
}
const schoolQuery = ref('');
const schoolResults = ref([]);
async function searchSchool(q) {
if (!q) {
schoolResults.value = [];
return;
}
try {
console.log('[DEBUG] Поиск школы:', { query: q });
const res = await createResource({
url: 'frappe.client.get_list',
params: {
doctype: 'Schools',
fields: ['school', 'address'],
filters: [['school', 'like', '%' + q + '%']],
limit_page_length: 20,
},
}).submit();
schoolResults.value = res || [];
console.log('[DEBUG] Результаты поиска школы:', schoolResults.value);
} catch (e) {
schoolResults.value = [];
console.error('[DEBUG] Ошибка поиска школы:', e);
}
}
const debouncedSearchSchool = debounce(() => searchSchool(schoolQuery.value), 300);
function selectSchool(s) {
form.value.school = s.school;
//form.value.school_name = s.school_name;
schoolResults.value = [];
schoolQuery.value = s.school;
console.log('[DEBUG] Выбрана школа:', { school: s });
console.log('[DEBUG] Форма после заполнения:', JSON.stringify(form.value, null, 2));
}
const majorQuery = ref('');
const majorResults = ref([]);
async function searchMajor(q) {
if (!q) {
majorResults.value = [];
return;
}
try {
console.log('[DEBUG] Поиск направления:', { query: q });
const res = await createResource({
url: 'frappe.client.get_list',
params: {
doctype: 'Majors',
fields: ['code', 'major_name'],
filters: [['major_name', 'like', '%' + q + '%']],
limit_page_length: 20,
},
}).submit();
majorResults.value = res || [];
console.log('[DEBUG] Результаты поиска направления:', majorResults.value);
} catch (e) {
majorResults.value = [];
console.error('[DEBUG] Ошибка поиска направления:', e);
}
}
const debouncedSearchMajor = debounce(() => searchMajor(majorQuery.value), 300);
function selectMajor(m) {
form.value.major = m.major_name;
//form.value.school_name = s.school_name;
majorResults.value = [];
majorQuery.value = m.major_name;
console.log('[DEBUG] Выбрана школа:', { major: m });
console.log('[DEBUG] Форма после заполнения:', JSON.stringify(form.value, null, 2));
}
onMounted(() => {
console.log('[DEBUG] Компонент смонтирован:', {
propsUsername: props.username,
sessionUsername: $user.data?.username,
user: user,
$user: $user.data,
});
if ($user.data) {
console.log('[DEBUG] Запуск profile.reload()');
profile.reload();
}
});
watch(
() => props.username,
(newUsername, oldUsername) => {
console.log('[DEBUG] Изменение props.username:', { old: oldUsername, new: newUsername });
profile.reload();
}
);
watch(
() => profile.data,
(newData, oldData) => {
console.log('[DEBUG] Изменение profile.data:', { old: oldData, new: newData });
if (newData) {
console.log('[DEBUG] Запуск schoolProfile.reload()');
schoolProfile.reload();
}
}
);
watch(
() => schoolProfile.data,
(newData, oldData) => {
console.log('[DEBUG] Изменение schoolProfile.data:', { old: oldData, new: newData });
if (newData && !editMode.value) {
console.log('[DEBUG] Заполнение формы из schoolProfile');
fillFormFromProfile();
}
}
);
watch(
() => effectiveUsername.value,
(newUsername) => {
console.log('[DEBUG] Изменение effectiveUsername для schoolProfile:', newUsername);
schoolProfile.update({
params: {
doctype: 'Schoolchildren Profile',
filters: { user: newUsername },
},
});
}
);
</script>