369 lines
14 KiB
TypeScript
369 lines
14 KiB
TypeScript
import { useState, type FormEvent } from 'react';
|
||
import { Modal, Button, Input } from '@krona/ui';
|
||
import { useCommunicationsStore } from '../services/communicationsService';
|
||
import { useToast } from '../../../shared/ui';
|
||
import { Paperclip } from 'lucide-react';
|
||
|
||
export function ComposeEmailModal({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) {
|
||
const { sendEmail } = useCommunicationsStore();
|
||
const { showToast } = useToast();
|
||
const [toEmail, setToEmail] = useState('');
|
||
const [subject, setSubject] = useState('');
|
||
const [body, setBody] = useState('');
|
||
|
||
const handleSubmit = (e: FormEvent) => {
|
||
e.preventDefault();
|
||
if (!toEmail.trim() || !subject.trim()) return;
|
||
|
||
sendEmail({
|
||
mailboxId: 'mb-work',
|
||
folder: 'sent',
|
||
from: { name: 'Krona Office', email: 'office@krona.systems' },
|
||
to: { name: toEmail.split('@')[0], email: toEmail.trim() },
|
||
subject: subject.trim(),
|
||
preview: body.slice(0, 80) + '...',
|
||
body: body.trim(),
|
||
isUnread: false,
|
||
hasAttachment: false,
|
||
});
|
||
|
||
showToast({
|
||
title: 'Письмо отправлено',
|
||
message: `Отправлено на адрес ${toEmail}`,
|
||
type: 'success',
|
||
});
|
||
onClose();
|
||
};
|
||
|
||
return (
|
||
<Modal isOpen={isOpen} onClose={onClose} title="Новое сообщение">
|
||
<form onSubmit={handleSubmit} className="space-y-4">
|
||
<div>
|
||
<label className="block text-xs font-semibold text-[var(--krona-text-secondary)] mb-1">
|
||
Кому (Email)
|
||
</label>
|
||
<Input
|
||
type="email"
|
||
value={toEmail}
|
||
onChange={(e) => setToEmail(e.target.value)}
|
||
placeholder="client@company.ru"
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-xs font-semibold text-[var(--krona-text-secondary)] mb-1">
|
||
Тема
|
||
</label>
|
||
<Input
|
||
type="text"
|
||
value={subject}
|
||
onChange={(e) => setSubject(e.target.value)}
|
||
placeholder="Тема письма..."
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-xs font-semibold text-[var(--krona-text-secondary)] mb-1">
|
||
Текст письма
|
||
</label>
|
||
<textarea
|
||
rows={6}
|
||
value={body}
|
||
onChange={(e) => setBody(e.target.value)}
|
||
placeholder="Здравствуйте!..."
|
||
className="w-full p-3 rounded-xl bg-[var(--krona-bg-workspace)] border border-[var(--krona-border)] text-sm text-[var(--krona-text-primary)] placeholder-[var(--krona-text-muted)] focus:outline-none focus:border-[var(--krona-primary)] resize-none"
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-between pt-2 border-t border-[var(--krona-border-subtle)]">
|
||
<button
|
||
type="button"
|
||
className="text-xs text-[var(--krona-text-secondary)] hover:text-[var(--krona-text-primary)] flex items-center gap-1.5"
|
||
>
|
||
<Paperclip className="w-3.5 h-3.5" />
|
||
<span>Прикрепить файл</span>
|
||
</button>
|
||
|
||
<div className="flex items-center gap-2">
|
||
<Button variant="secondary" size="md" onClick={onClose}>
|
||
Отмена
|
||
</Button>
|
||
<Button variant="primary" size="md" type="submit">
|
||
Отправить
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</Modal>
|
||
);
|
||
}
|
||
|
||
export function CreateChannelModal({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) {
|
||
const { createChannel } = useCommunicationsStore();
|
||
const { showToast } = useToast();
|
||
const [name, setName] = useState('');
|
||
const [topic, setTopic] = useState('');
|
||
const [isPrivate, setIsPrivate] = useState(false);
|
||
|
||
const handleSubmit = (e: FormEvent) => {
|
||
e.preventDefault();
|
||
if (!name.trim()) return;
|
||
|
||
createChannel({
|
||
name: name.trim(),
|
||
topic: topic.trim() || 'Новый канал организации',
|
||
isPrivate,
|
||
});
|
||
|
||
showToast({
|
||
title: 'Канал создан',
|
||
message: `Канал #${name} успешно добавлен`,
|
||
type: 'success',
|
||
});
|
||
onClose();
|
||
};
|
||
|
||
return (
|
||
<Modal isOpen={isOpen} onClose={onClose} title="Создание внутреннего канала">
|
||
<form onSubmit={handleSubmit} className="space-y-4">
|
||
<div>
|
||
<label className="block text-xs font-semibold text-[var(--krona-text-secondary)] mb-1">
|
||
Название канала
|
||
</label>
|
||
<Input
|
||
type="text"
|
||
value={name}
|
||
onChange={(e) => setName(e.target.value)}
|
||
placeholder="маркетинг-и-реклама"
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-xs font-semibold text-[var(--krona-text-secondary)] mb-1">
|
||
Тема / Описание канала
|
||
</label>
|
||
<Input
|
||
type="text"
|
||
value={topic}
|
||
onChange={(e) => setTopic(e.target.value)}
|
||
placeholder="Обсуждение совместных проектов..."
|
||
/>
|
||
</div>
|
||
|
||
<div className="flex items-center gap-3 p-3 rounded-xl bg-[var(--krona-bg-workspace)] border border-[var(--krona-border-subtle)]">
|
||
<input
|
||
type="checkbox"
|
||
id="channel-private"
|
||
checked={isPrivate}
|
||
onChange={(e) => setIsPrivate(e.target.checked)}
|
||
className="w-4 h-4 rounded text-[var(--krona-primary)] focus:ring-[var(--krona-primary)]"
|
||
/>
|
||
<label htmlFor="channel-private" className="text-xs text-[var(--krona-text-primary)] cursor-pointer">
|
||
<strong className="block font-semibold">Закрытый канал (Private)</strong>
|
||
<span className="text-[var(--krona-text-secondary)]">Доступ только по приглашению руководителей</span>
|
||
</label>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-end gap-2 pt-2 border-t border-[var(--krona-border-subtle)]">
|
||
<Button variant="secondary" size="md" onClick={onClose}>
|
||
Отмена
|
||
</Button>
|
||
<Button variant="primary" size="md" type="submit">
|
||
Создать канал
|
||
</Button>
|
||
</div>
|
||
</form>
|
||
</Modal>
|
||
);
|
||
}
|
||
|
||
export function AddContactModal({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) {
|
||
const { addContact } = useCommunicationsStore();
|
||
const { showToast } = useToast();
|
||
const [name, setName] = useState('');
|
||
const [company, setCompany] = useState('');
|
||
const [role, setRole] = useState('Клиент');
|
||
const [channelValue, setChannelValue] = useState('');
|
||
const [isVip, setIsVip] = useState(false);
|
||
|
||
const handleSubmit = (e: FormEvent) => {
|
||
e.preventDefault();
|
||
if (!name.trim()) return;
|
||
|
||
addContact({
|
||
name: name.trim(),
|
||
company: company.trim() || 'Частное лицо',
|
||
role: role.trim() || 'Клиент',
|
||
isVip,
|
||
channels: [
|
||
{
|
||
type: 'telegram',
|
||
value: channelValue.trim() || '@client_tg',
|
||
label: 'Telegram',
|
||
isConnected: true,
|
||
},
|
||
],
|
||
});
|
||
|
||
showToast({
|
||
title: 'Контакт добавлен',
|
||
message: `${name} добавлен в справочник и список диалогов`,
|
||
type: 'success',
|
||
});
|
||
onClose();
|
||
};
|
||
|
||
return (
|
||
<Modal isOpen={isOpen} onClose={onClose} title="Новый контакт">
|
||
<form onSubmit={handleSubmit} className="space-y-4">
|
||
<div>
|
||
<label className="block text-xs font-semibold text-[var(--krona-text-secondary)] mb-1">
|
||
ФИО / Имя контакта
|
||
</label>
|
||
<Input
|
||
type="text"
|
||
value={name}
|
||
onChange={(e) => setName(e.target.value)}
|
||
placeholder="Константин Васильев"
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-xs font-semibold text-[var(--krona-text-secondary)] mb-1">
|
||
Компания
|
||
</label>
|
||
<Input
|
||
type="text"
|
||
value={company}
|
||
onChange={(e) => setCompany(e.target.value)}
|
||
placeholder="ООО «Северный Альянс»"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-xs font-semibold text-[var(--krona-text-secondary)] mb-1">
|
||
Должность
|
||
</label>
|
||
<Input
|
||
type="text"
|
||
value={role}
|
||
onChange={(e) => setRole(e.target.value)}
|
||
placeholder="Коммерческий директор"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-xs font-semibold text-[var(--krona-text-secondary)] mb-1">
|
||
Telegram / Телефон
|
||
</label>
|
||
<Input
|
||
type="text"
|
||
value={channelValue}
|
||
onChange={(e) => setChannelValue(e.target.value)}
|
||
placeholder="@vasiliev_kostya или +7 999 123-45-67"
|
||
/>
|
||
</div>
|
||
|
||
<div className="flex items-center gap-2">
|
||
<input
|
||
type="checkbox"
|
||
id="contact-vip"
|
||
checked={isVip}
|
||
onChange={(e) => setIsVip(e.target.checked)}
|
||
className="w-4 h-4 rounded text-[var(--krona-gold)]"
|
||
/>
|
||
<label htmlFor="contact-vip" className="text-xs font-semibold text-[var(--krona-text-primary)] cursor-pointer">
|
||
VIP статус клиента
|
||
</label>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-end gap-2 pt-2 border-t border-[var(--krona-border-subtle)]">
|
||
<Button variant="secondary" size="md" onClick={onClose}>
|
||
Отмена
|
||
</Button>
|
||
<Button variant="primary" size="md" type="submit">
|
||
Сохранить контакт
|
||
</Button>
|
||
</div>
|
||
</form>
|
||
</Modal>
|
||
);
|
||
}
|
||
|
||
export function ConnectChannelModal({ isOpen, onClose, mode = 'messenger' }: { isOpen: boolean; onClose: () => void; mode?: 'messenger' | 'email' }) {
|
||
const { showToast } = useToast();
|
||
const [platform, setPlatform] = useState('telegram');
|
||
const [identifier, setIdentifier] = useState('');
|
||
const [useVpn, setUseVpn] = useState(false);
|
||
|
||
const handleSubmit = (e: FormEvent) => {
|
||
e.preventDefault();
|
||
showToast({
|
||
title: mode === 'email' ? 'Почта подключена' : 'Мессенджер подключен',
|
||
message: `${mode === 'email' ? 'Ящик' : platform.toUpperCase()} (${identifier}) успешно подключен${useVpn && mode === 'messenger' ? ' через VPN' : ''}`,
|
||
type: 'success',
|
||
});
|
||
onClose();
|
||
};
|
||
|
||
return (
|
||
<Modal isOpen={isOpen} onClose={onClose} title={mode === 'email' ? 'Добавление почтового ящика' : 'Добавление мессенджера'}>
|
||
<form onSubmit={handleSubmit} className="space-y-4">
|
||
<div>
|
||
<label className="block text-xs font-semibold text-[var(--krona-text-secondary)] mb-1">
|
||
{mode === 'email' ? 'Почтовый сервис' : 'Мессенджер'}
|
||
</label>
|
||
<select
|
||
value={mode === 'email' ? 'email' : platform}
|
||
onChange={(e) => setPlatform(e.target.value)}
|
||
className="w-full h-10 px-3 rounded-xl bg-[var(--krona-bg-workspace)] border border-[var(--krona-border)] text-sm text-[var(--krona-text-primary)] focus:outline-none focus:border-[var(--krona-primary)]"
|
||
>
|
||
{mode === 'email' ? <>
|
||
<option value="email">IMAP / SMTP</option>
|
||
<option value="yandex">Яндекс Почта</option>
|
||
<option value="gmail">Google Workspace / Gmail</option>
|
||
<option value="mailru">Mail.ru</option>
|
||
</> : <>
|
||
<option value="telegram">Telegram</option>
|
||
<option value="whatsapp">WhatsApp</option>
|
||
<option value="vk">VK Мессенджер</option>
|
||
<option value="max">MAX</option>
|
||
</>}
|
||
</select>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-xs font-semibold text-[var(--krona-text-secondary)] mb-1">
|
||
{mode === 'email' ? 'Адрес электронной почты' : 'Телефон или логин аккаунта'}
|
||
</label>
|
||
<Input
|
||
type="text"
|
||
value={identifier}
|
||
onChange={(e) => setIdentifier(e.target.value)}
|
||
placeholder={mode === 'email' ? 'name@company.ru' : '+7 900 000-00-00 или @username'}
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
{mode === 'messenger' && <label className="p-3 rounded-xl bg-[var(--krona-bg-workspace)] border border-[var(--krona-border)] flex items-center gap-3 text-sm text-[var(--krona-text-primary)] cursor-pointer">
|
||
<input type="checkbox" checked={useVpn} onChange={(e) => setUseVpn(e.target.checked)} className="w-4 h-4 accent-[var(--krona-primary)]" />
|
||
<span><strong className="block">Подключать через VPN</strong><span className="text-xs text-[var(--krona-text-secondary)]">Использовать VPN-шлюз организации для этого аккаунта</span></span>
|
||
</label>}
|
||
|
||
<div className="flex items-center justify-end gap-2 pt-2 border-t border-[var(--krona-border-subtle)]">
|
||
<Button variant="secondary" size="md" onClick={onClose}>
|
||
Отмена
|
||
</Button>
|
||
<Button variant="primary" size="md" type="submit">
|
||
Подключить
|
||
</Button>
|
||
</div>
|
||
</form>
|
||
</Modal>
|
||
);
|
||
}
|