uchill/front_material/components/profile/ParentChildNotificationSett...

281 lines
11 KiB
TypeScript
Raw Permalink 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.

'use client';
import React, { useState, useEffect } from 'react';
import { useAuth } from '@/contexts/AuthContext';
import { Switch } from '@/components/common/Switch';
import { getParentDashboard } from '@/api/dashboard';
import {
getParentChildNotificationSettingsForChild,
updateParentChildNotificationSettings,
type ParentChildNotificationSettings,
} from '@/api/notifications';
const NOTIFICATION_TYPES = [
{ value: 'lesson_created', label: 'Создано занятие' },
{ value: 'lesson_updated', label: 'Занятие обновлено' },
{ value: 'lesson_cancelled', label: 'Занятие отменено' },
{ value: 'lesson_rescheduled', label: 'Занятие перенесено' },
{ value: 'lesson_reminder', label: 'Напоминание о занятии' },
{ value: 'lesson_completed', label: 'Занятие завершено' },
{ value: 'homework_assigned', label: 'Назначено домашнее задание' },
{ value: 'homework_submitted', label: 'ДЗ сдано' },
{ value: 'homework_reviewed', label: 'ДЗ проверено' },
{ value: 'homework_returned', label: 'ДЗ возвращено на доработку' },
{ value: 'homework_deadline_reminder', label: 'Напоминание о дедлайне ДЗ' },
{ value: 'material_added', label: 'Добавлен материал' },
];
function getAvatarUrl(child: { avatar_url?: string | null; avatar?: string | null }): string | null {
const url = child.avatar_url || child.avatar;
if (!url) return null;
if (url.startsWith('http')) return url;
const base = typeof window !== 'undefined' ? `${window.location.protocol}//${window.location.hostname}:8123` : '';
return url.startsWith('/') ? `${base}${url}` : `${base}/${url}`;
}
export function ParentChildNotificationSettings() {
const { user } = useAuth();
const [children, setChildren] = useState<{ id: string; name: string; avatar?: string | null; avatar_url?: string | null }[]>([]);
const [settings, setSettings] = useState<Record<string, ParentChildNotificationSettings>>({});
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState<Record<string, boolean>>({});
const [error, setError] = useState<string | null>(null);
const [expandedChild, setExpandedChild] = useState<string | null>(null);
useEffect(() => {
const load = async () => {
if (user?.role !== 'parent') {
setLoading(false);
return;
}
try {
setLoading(true);
setError(null);
const stats = await getParentDashboard();
const rawChildren = (stats as any).children || (stats as any).children_stats || [];
const list = rawChildren.map((item: any) => {
const c = item.child || item;
return {
id: String(c.id),
name: c.name || `${c.first_name || ''} ${c.last_name || ''}`.trim() || 'Ребёнок',
avatar: c.avatar,
avatar_url: c.avatar_url,
};
});
setChildren(list);
const settingsMap: Record<string, ParentChildNotificationSettings> = {};
for (const child of list) {
try {
const s = await getParentChildNotificationSettingsForChild(child.id);
settingsMap[child.id] = s;
} catch (e: any) {
if (e?.response?.status === 404 || e?.status === 404) {
settingsMap[child.id] = {
child_id: parseInt(child.id),
child_name: child.name,
enabled: true,
type_settings: {},
};
}
}
}
setSettings(settingsMap);
} catch (e) {
console.error(e);
setError('Не удалось загрузить настройки');
} finally {
setLoading(false);
}
};
load();
}, [user?.role]);
const handleToggleEnabled = async (childId: string, enabled: boolean) => {
try {
setSaving((p) => ({ ...p, [childId]: true }));
const updated = await updateParentChildNotificationSettings(childId, { enabled });
setSettings((p) => ({ ...p, [childId]: updated }));
} catch (e) {
setError('Не удалось обновить');
} finally {
setSaving((p) => ({ ...p, [childId]: false }));
}
};
const handleToggleType = async (childId: string, type: string, enabled: boolean) => {
try {
setSaving((p) => ({ ...p, [childId]: true }));
const curr = settings[childId] || { enabled: true, type_settings: {} };
const newTypeSettings = { ...curr.type_settings, [type]: enabled };
const updated = await updateParentChildNotificationSettings(childId, {
type_settings: newTypeSettings,
});
setSettings((p) => ({ ...p, [childId]: updated }));
} catch (e) {
setError('Не удалось обновить');
} finally {
setSaving((p) => ({ ...p, [childId]: false }));
}
};
if (user?.role !== 'parent') return null;
if (loading) {
return (
<div style={{ padding: 16, color: 'var(--md-sys-color-on-surface-variant)', fontSize: 14 }}>
Загрузка настроек для детей
</div>
);
}
if (children.length === 0) {
return (
<div
className="ios26-panel"
style={{
padding: 16,
marginTop: 16,
background: 'var(--md-sys-color-surface-container-low)',
}}
>
<div style={{ fontSize: 14, fontWeight: 500, marginBottom: 8 }}>Уведомления которые хотите получать вместе с учеником</div>
<p style={{ margin: 0, fontSize: 13, color: 'var(--md-sys-color-on-surface-variant)' }}>
Добавьте детей на странице «Мои дети», чтобы настроить уведомления для каждого ребёнка.
</p>
</div>
);
}
return (
<div style={{ marginTop: 16 }}>
<div
style={{
fontSize: 10,
fontWeight: 600,
letterSpacing: '0.05em',
color: 'var(--md-sys-color-on-surface-variant)',
marginBottom: 8,
}}
>
Уведомления которые хотите получать вместе с учеником
</div>
{error && (
<p style={{ color: 'var(--md-sys-color-error)', fontSize: 13, marginBottom: 12 }}>{error}</p>
)}
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
{children.map((child) => {
const childSettings = settings[child.id] || { enabled: true, type_settings: {} };
const isExpanded = expandedChild === child.id;
const isSaving = saving[child.id];
return (
<div
key={child.id}
className="ios26-panel"
style={{ overflow: 'hidden' }}
>
<div
onClick={() => setExpandedChild(isExpanded ? null : child.id)}
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '10px 12px',
cursor: 'pointer',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div
style={{
width: 36,
height: 36,
borderRadius: 8,
overflow: 'hidden',
background: 'var(--md-sys-color-primary-container)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'var(--md-sys-color-primary)',
fontSize: 14,
fontWeight: 600,
}}
>
{getAvatarUrl(child) ? (
<img
src={getAvatarUrl(child)!}
alt=""
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
) : (
child.name.charAt(0) || '?'
)}
</div>
<div>
<div style={{ fontSize: 14, fontWeight: 600 }}>{child.name}</div>
<div style={{ fontSize: 11, color: 'var(--md-sys-color-on-surface-variant)' }}>
{childSettings.enabled ? 'Включены' : 'Выключены'}
</div>
</div>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<div onClick={(e) => e.stopPropagation()}>
<Switch
checked={childSettings.enabled}
onChange={(v) => handleToggleEnabled(child.id, v)}
disabled={isSaving}
size="compact"
/>
</div>
<span
className="material-symbols-outlined"
style={{
fontSize: 18,
color: 'var(--md-sys-color-on-surface-variant)',
transform: isExpanded ? 'rotate(180deg)' : 'none',
}}
>
expand_more
</span>
</div>
</div>
{isExpanded && (
<div
style={{
padding: '0 12px 12px 12px',
borderTop: '1px solid var(--ios26-list-divider)',
}}
>
<div style={{ display: 'flex', flexDirection: 'column', gap: 4, marginTop: 8 }}>
{NOTIFICATION_TYPES.map((type) => {
const isEnabled = childSettings.type_settings[type.value] !== false;
return (
<div
key={type.value}
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '6px 0',
opacity: childSettings.enabled ? 1 : 0.5,
}}
>
<span style={{ fontSize: 13 }}>{type.label}</span>
<Switch
checked={isEnabled}
onChange={(v) => handleToggleType(child.id, type.value, v)}
disabled={isSaving || !childSettings.enabled}
size="compact"
/>
</div>
);
})}
</div>
</div>
)}
</div>
);
})}
</div>
</div>
);
}