245 lines
11 KiB
TypeScript
245 lines
11 KiB
TypeScript
'use client';
|
||
|
||
import React from 'react';
|
||
import { Switch } from '@/components/common/Switch';
|
||
import type { NotificationPreference } from '@/api/notifications';
|
||
|
||
const NOTIFICATION_TYPES = [
|
||
{ key: 'lesson_created', label: 'Создание занятия', description: 'Уведомления о новых занятиях' },
|
||
{ key: 'lesson_cancelled', label: 'Отмена занятия', description: 'Уведомления об отмене занятий' },
|
||
{ key: 'lesson_reminder', label: 'Напоминания о занятиях', description: 'Напоминания о предстоящих занятиях' },
|
||
{ key: 'homework_assigned', label: 'Назначение ДЗ', description: 'Уведомления о новых домашних заданиях' },
|
||
{ key: 'homework_submitted', label: 'Сдача ДЗ', description: 'Уведомления о сданных домашних заданиях' },
|
||
{ key: 'homework_reviewed', label: 'Проверка ДЗ', description: 'Уведомления о проверенных домашних заданиях' },
|
||
{ key: 'message_received', label: 'Сообщения', description: 'Уведомления о новых сообщениях в чате' },
|
||
{ key: 'subscription_expiring', label: 'Истечение подписки', description: 'Уведомления об истечении подписки' },
|
||
{ key: 'subscription_expired', label: 'Подписка истекла', description: 'Уведомления об истекшей подписке' },
|
||
];
|
||
|
||
const PARENT_EXCLUDED_TYPES = [
|
||
'lesson_created',
|
||
'lesson_cancelled',
|
||
'lesson_reminder',
|
||
'homework_assigned',
|
||
'homework_submitted',
|
||
'homework_reviewed',
|
||
];
|
||
|
||
interface NotificationSettingsSectionProps {
|
||
preferences: NotificationPreference | null;
|
||
onChange: (prefs: NotificationPreference) => void;
|
||
userRole?: string;
|
||
hasTelegram?: boolean;
|
||
disabled?: boolean;
|
||
}
|
||
|
||
export function NotificationSettingsSection({
|
||
preferences,
|
||
onChange,
|
||
userRole = 'mentor',
|
||
hasTelegram = false,
|
||
disabled = false,
|
||
}: NotificationSettingsSectionProps) {
|
||
const types =
|
||
userRole === 'parent'
|
||
? NOTIFICATION_TYPES.filter((t) => !PARENT_EXCLUDED_TYPES.includes(t.key))
|
||
: NOTIFICATION_TYPES;
|
||
|
||
const toggleChannel = (field: keyof NotificationPreference, value: boolean) => {
|
||
if (!preferences) return;
|
||
onChange({ ...preferences, [field]: value });
|
||
};
|
||
|
||
const toggleType = (notificationType: string, channel: 'in_app' | 'email' | 'telegram', value: boolean) => {
|
||
if (!preferences) return;
|
||
const typePrefs = { ...(preferences.type_preferences || {}) };
|
||
if (!typePrefs[notificationType]) typePrefs[notificationType] = {};
|
||
typePrefs[notificationType] = { ...typePrefs[notificationType], [channel]: value };
|
||
onChange({ ...preferences, type_preferences: typePrefs });
|
||
};
|
||
|
||
const getTypeValue = (typeKey: string, channel: 'in_app' | 'email' | 'telegram') => {
|
||
const typePrefs = preferences?.type_preferences?.[typeKey];
|
||
if (typePrefs && typePrefs[channel] !== undefined) return typePrefs[channel];
|
||
if (channel === 'in_app') return preferences?.in_app_enabled ?? true;
|
||
if (channel === 'email') return preferences?.email_enabled ?? true;
|
||
return preferences?.telegram_enabled ?? false;
|
||
};
|
||
|
||
const isChannelDisabled = (channel: 'in_app' | 'email' | 'telegram') => {
|
||
if (preferences?.enabled === false) return true;
|
||
if (channel === 'in_app') return !(preferences?.in_app_enabled ?? true);
|
||
if (channel === 'email') return !(preferences?.email_enabled ?? true);
|
||
return !(preferences?.telegram_enabled ?? false) || !hasTelegram;
|
||
};
|
||
|
||
if (!preferences) return null;
|
||
|
||
return (
|
||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||
{/* Глобальное включение */}
|
||
<div
|
||
style={{
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'space-between',
|
||
padding: '10px 0',
|
||
borderBottom: '1px solid var(--ios26-list-divider)',
|
||
}}
|
||
>
|
||
<div>
|
||
<div style={{ fontSize: 14, fontWeight: 600, color: 'var(--md-sys-color-on-surface)' }}>
|
||
Уведомления включены
|
||
</div>
|
||
<div style={{ fontSize: 11, color: 'var(--md-sys-color-on-surface-variant)', marginTop: 2 }}>
|
||
Полностью отключить все уведомления
|
||
</div>
|
||
</div>
|
||
<Switch
|
||
checked={preferences.enabled !== false}
|
||
onChange={(v) => toggleChannel('enabled', v)}
|
||
disabled={disabled}
|
||
size="compact"
|
||
/>
|
||
</div>
|
||
|
||
{/* Каналы */}
|
||
<div>
|
||
<div
|
||
style={{
|
||
fontSize: 10,
|
||
fontWeight: 600,
|
||
letterSpacing: '0.05em',
|
||
color: 'var(--md-sys-color-on-surface-variant)',
|
||
marginBottom: 8,
|
||
}}
|
||
>
|
||
КАНАЛЫ
|
||
</div>
|
||
<div
|
||
style={{
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
gap: 6,
|
||
opacity: preferences.enabled === false ? 0.5 : 1,
|
||
}}
|
||
>
|
||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '6px 0' }}>
|
||
<div>
|
||
<div style={{ fontSize: 13, fontWeight: 500 }}>Email</div>
|
||
<div style={{ fontSize: 11, color: 'var(--md-sys-color-on-surface-variant)' }}>
|
||
По электронной почте
|
||
</div>
|
||
</div>
|
||
<Switch
|
||
checked={preferences.email_enabled ?? true}
|
||
onChange={(v) => toggleChannel('email_enabled', v)}
|
||
disabled={disabled || preferences.enabled === false}
|
||
size="compact"
|
||
/>
|
||
</div>
|
||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '6px 0' }}>
|
||
<div>
|
||
<div style={{ fontSize: 13, fontWeight: 500 }}>Telegram</div>
|
||
<div style={{ fontSize: 11, color: 'var(--md-sys-color-on-surface-variant)' }}>
|
||
{hasTelegram ? 'Подключен' : 'Не подключен'}
|
||
</div>
|
||
</div>
|
||
<Switch
|
||
checked={preferences.telegram_enabled ?? false}
|
||
onChange={(v) => toggleChannel('telegram_enabled', v)}
|
||
disabled={disabled || !hasTelegram || preferences.enabled === false}
|
||
size="compact"
|
||
/>
|
||
</div>
|
||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '6px 0' }}>
|
||
<div>
|
||
<div style={{ fontSize: 13, fontWeight: 500 }}>В приложении</div>
|
||
<div style={{ fontSize: 11, color: 'var(--md-sys-color-on-surface-variant)' }}>
|
||
Показывать в приложении
|
||
</div>
|
||
</div>
|
||
<Switch
|
||
checked={preferences.in_app_enabled ?? true}
|
||
onChange={(v) => toggleChannel('in_app_enabled', v)}
|
||
disabled={disabled || preferences.enabled === false}
|
||
size="compact"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Матрица типов по каналам */}
|
||
<div>
|
||
<div
|
||
style={{
|
||
fontSize: 10,
|
||
fontWeight: 600,
|
||
letterSpacing: '0.05em',
|
||
color: 'var(--md-sys-color-on-surface-variant)',
|
||
marginBottom: 8,
|
||
}}
|
||
>
|
||
ТИПЫ ПО КАНАЛАМ
|
||
</div>
|
||
<div
|
||
style={{
|
||
border: '1px solid var(--ios26-list-divider)',
|
||
borderRadius: 10,
|
||
overflow: 'hidden',
|
||
display: 'grid',
|
||
gridTemplateColumns: '1fr auto auto auto',
|
||
background: 'var(--md-sys-color-surface-container-low)',
|
||
}}
|
||
>
|
||
{/* Header */}
|
||
<div style={{ padding: 8, fontWeight: 600, fontSize: 11, color: 'var(--md-sys-color-on-surface-variant)' }}>Тип</div>
|
||
<div style={{ padding: 8, textAlign: 'center', fontWeight: 600, fontSize: 11, color: 'var(--md-sys-color-on-surface-variant)', minWidth: 64 }}>В приложении</div>
|
||
<div style={{ padding: 8, textAlign: 'center', fontWeight: 600, fontSize: 11, color: 'var(--md-sys-color-on-surface-variant)', minWidth: 64 }}>Email</div>
|
||
<div style={{ padding: 8, textAlign: 'center', fontWeight: 600, fontSize: 11, color: 'var(--md-sys-color-on-surface-variant)', minWidth: 64 }}>Telegram</div>
|
||
{/* Rows */}
|
||
{types.map((type) => (
|
||
<React.Fragment key={type.key}>
|
||
<div
|
||
style={{
|
||
padding: 8,
|
||
borderTop: '1px solid var(--ios26-list-divider)',
|
||
background: '#fff',
|
||
fontSize: 12,
|
||
}}
|
||
>
|
||
<div style={{ fontWeight: 500 }}>{type.label}</div>
|
||
<div style={{ fontSize: 10, color: 'var(--md-sys-color-on-surface-variant)', marginTop: 1 }}>{type.description}</div>
|
||
</div>
|
||
<div style={{ padding: 6, display: 'flex', alignItems: 'center', justifyContent: 'center', borderTop: '1px solid var(--ios26-list-divider)', background: '#fff' }}>
|
||
<Switch
|
||
checked={getTypeValue(type.key, 'in_app')}
|
||
onChange={(v) => toggleType(type.key, 'in_app', v)}
|
||
disabled={disabled || isChannelDisabled('in_app')}
|
||
size="compact"
|
||
/>
|
||
</div>
|
||
<div style={{ padding: 6, display: 'flex', alignItems: 'center', justifyContent: 'center', borderTop: '1px solid var(--ios26-list-divider)', background: '#fff' }}>
|
||
<Switch
|
||
checked={getTypeValue(type.key, 'email')}
|
||
onChange={(v) => toggleType(type.key, 'email', v)}
|
||
disabled={disabled || isChannelDisabled('email')}
|
||
size="compact"
|
||
/>
|
||
</div>
|
||
<div style={{ padding: 6, display: 'flex', alignItems: 'center', justifyContent: 'center', borderTop: '1px solid var(--ios26-list-divider)', background: '#fff' }}>
|
||
<Switch
|
||
checked={getTypeValue(type.key, 'telegram')}
|
||
onChange={(v) => toggleType(type.key, 'telegram', v)}
|
||
disabled={disabled || isChannelDisabled('telegram')}
|
||
size="compact"
|
||
/>
|
||
</div>
|
||
</React.Fragment>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|