marketing/src/components/Family/PersonModal.tsx

350 lines
9.2 KiB
TypeScript
Raw Normal View History

2026-08-24 10:07:16 +00:00
import { useEffect, useState } from 'react';
import styled from 'styled-components';
import type { Gender, User } from '../../types/user';
import { TagsInput } from './TagsInput';
interface PersonModalProps {
isOpen: boolean;
user: User | null;
availableTags: string[];
onClose: () => void;
onSave: (user: User) => void;
}
interface PersonForm {
name: string;
gender: Gender;
age: string;
weight: string;
height: string;
avoid: string[];
favorite: string[];
}
const defaultForm: PersonForm = {
name: '',
gender: 'm',
age: '',
weight: '',
height: '',
avoid: [],
favorite: [],
};
export function PersonModal({
isOpen,
user,
availableTags,
onClose,
onSave,
}: PersonModalProps) {
const [form, setForm] = useState<PersonForm>(defaultForm);
useEffect(() => {
if (!isOpen) return;
if (user) {
setForm({
name: user.name,
gender: user.gender,
age: String(user.age),
weight: user.weight ? String(user.weight) : '',
height: user.height ? String(user.height) : '',
avoid: user.avoid ?? [],
favorite: user.favorite ?? [],
});
} else {
setForm(defaultForm);
}
}, [isOpen, user]);
if (!isOpen) return null;
function updateForm<TName extends keyof PersonForm>(name: TName, value: PersonForm[TName]) {
setForm(current => ({
...current,
[name]: value,
}));
}
function handleAvoidChange(tags: string[]) {
setForm(current => ({
...current,
avoid: tags,
favorite: current.favorite.filter(tag => !tags.includes(tag)),
}));
}
function handleFavoriteChange(tags: string[]) {
setForm(current => ({
...current,
favorite: tags,
avoid: current.avoid.filter(tag => !tags.includes(tag)),
}));
}
function handleSave() {
const age = Number(form.age);
if (!form.name.trim()) {
alert('Пожалуйста, введите имя.');
return;
}
if (!age || age < 7) {
alert('Возраст должен быть от 7 лет.');
return;
}
onSave({
id: user?.id,
name: form.name.trim(),
gender: form.gender,
age,
weight: form.weight ? Number(form.weight) : undefined,
height: form.height ? Number(form.height) : undefined,
avoid: form.avoid,
favorite: form.favorite,
});
}
return (
<Overlay onClick={onClose}>
<Content onClick={event => event.stopPropagation()}>
<Header>
<h2>{user ? 'Редактировать человека' : 'Добавить человека'}</h2>
<CloseButton type="button" onClick={onClose}>
×
</CloseButton>
</Header>
<FormGroup>
<label>Имя</label>
<input
type="text"
placeholder="Имя"
value={form.name}
onChange={event => updateForm('name', event.target.value)}
/>
</FormGroup>
<FormGroup>
<label>Пол</label>
<GenderToggles>
<GenderButton
type="button"
$active={form.gender === 'f'}
onClick={() => updateForm('gender', 'f')}
>
Женский
</GenderButton>
<GenderButton
type="button"
$active={form.gender === 'm'}
onClick={() => updateForm('gender', 'm')}
>
Мужской
</GenderButton>
</GenderToggles>
</FormGroup>
<FormRow>
<FormGroup>
<label>Возраст</label>
<input
type="number"
value={form.age}
onChange={event => updateForm('age', event.target.value)}
/>
<Hint>от 7 лет</Hint>
</FormGroup>
<FormGroup>
<label>Вес</label>
<input
type="number"
value={form.weight}
onChange={event => updateForm('weight', event.target.value)}
/>
</FormGroup>
<FormGroup>
<label>Рост</label>
<input
type="number"
value={form.height}
onChange={event => updateForm('height', event.target.value)}
/>
</FormGroup>
</FormRow>
<PrefsGrid>
<TagsInput
title="Запрещённые"
type="avoid"
value={form.avoid}
availableTags={availableTags}
excludedTags={form.favorite}
onChange={handleAvoidChange}
/>
<TagsInput
title="Любимые"
type="favorite"
value={form.favorite}
availableTags={availableTags}
excludedTags={form.avoid}
onChange={handleFavoriteChange}
/>
</PrefsGrid>
<SaveButton type="button" onClick={handleSave}>
Сохранить
</SaveButton>
</Content>
</Overlay>
);
}
const Overlay = styled.div`
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(2px);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 20px;
`;
const Content = styled.div`
background: #ffffff;
border-radius: 24px;
width: 100%;
max-width: 440px;
padding: 24px;
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2);
max-height: 95vh;
overflow-y: auto;
`;
const Header = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
h2 {
font-size: 20px;
font-weight: 600;
color: ${({ theme }) => theme.colors.darkGreen};
}
`;
const CloseButton = styled.button`
font-size: 24px;
color: #b0b0b0;
cursor: pointer;
line-height: 1;
background: transparent;
border: 0;
&:hover {
color: ${({ theme }) => theme.colors.darkGreen};
}
`;
const FormGroup = styled.div`
margin-bottom: 16px;
flex: 1;
label {
display: block;
font-size: 14px;
color: ${({ theme }) => theme.colors.darkGreen};
margin-bottom: 6px;
}
input {
width: 100%;
padding: 10px 12px;
border: 1px solid #e1e1e1;
border-radius: 8px;
font-size: 14px;
outline: none;
transition: 0.2s;
&:focus {
border-color: ${({ theme }) => theme.colors.green};
}
}
`;
const FormRow = styled.div`
display: flex;
gap: 16px;
margin-bottom: 16px;
${FormGroup} {
margin-bottom: 0;
}
@media (max-width: 600px) {
flex-direction: column;
}
`;
const Hint = styled.span`
display: block;
font-size: 12px;
color: ${({ theme }) => theme.colors.gray};
margin-top: 4px;
`;
const GenderToggles = styled.div`
display: flex;
gap: 8px;
margin-bottom: 16px;
`;
const GenderButton = styled.button<{ $active: boolean }>`
flex: 1;
padding: 10px;
border: 1px solid ${({ $active }) => ($active ? '#e9f6d9' : '#e1e1e1')};
border-radius: 8px;
background: ${({ $active }) => ($active ? '#e9f6d9' : '#ffffff')};
font-size: 14px;
cursor: pointer;
transition: 0.2s;
font-weight: ${({ $active }) => ($active ? 500 : 400)};
`;
const PrefsGrid = styled.div`
display: flex;
gap: 16px;
margin-bottom: 20px;
border-top: 1px solid #f0f0f0;
padding-top: 16px;
@media (max-width: 600px) {
flex-direction: column;
}
`;
const SaveButton = styled.button`
width: 100%;
padding: 12px;
background: ${({ theme }) => theme.colors.green};
color: #fff;
border: none;
border-radius: 12px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: 0.2s;
&:hover {
background: #5b8f29;
}
`;