fix: redesign add-event modal — vertical layout, toggle switch, unified time picker
Some checks failed
Deploy / deploy (push) Failing after 2m9s
Some checks failed
Deploy / deploy (push) Failing after 2m9s
This commit is contained in:
@@ -44,9 +44,6 @@ function AddEventModal({ defaultDate, onClose, onSaved }: { defaultDate: string;
|
|||||||
|
|
||||||
const selectedCal = CALENDAR_OPTIONS.find(c => c.owner === owner) || CALENDAR_OPTIONS[0]
|
const selectedCal = CALENDAR_OPTIONS.find(c => c.owner === owner) || CALENDAR_OPTIONS[0]
|
||||||
|
|
||||||
const dateObj = date ? new Date(date + 'T00:00:00') : new Date()
|
|
||||||
const dateLabel = dateObj.toLocaleDateString('ru-RU', { weekday: 'short', day: 'numeric', month: 'long' })
|
|
||||||
|
|
||||||
const save = async () => {
|
const save = async () => {
|
||||||
if (!title.trim()) { setError('Введите название'); return }
|
if (!title.trim()) { setError('Введите название'); return }
|
||||||
setSaving(true); setError('')
|
setSaving(true); setError('')
|
||||||
@@ -59,148 +56,197 @@ function AddEventModal({ defaultDate, onClose, onSaved }: { defaultDate: string;
|
|||||||
} catch (e: any) { setError(e.message || 'Ошибка сохранения'); setSaving(false) }
|
} catch (e: any) { setError(e.message || 'Ошибка сохранения'); setSaving(false) }
|
||||||
}
|
}
|
||||||
|
|
||||||
const fieldInputStyle: React.CSSProperties = {
|
|
||||||
padding: '14px 18px', borderRadius: 14, width: '100%',
|
|
||||||
background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.07)',
|
|
||||||
color: 'var(--text-primary)', fontSize: 15, outline: 'none', fontFamily: 'inherit',
|
|
||||||
}
|
|
||||||
|
|
||||||
const fieldLabelStyle: React.CSSProperties = {
|
|
||||||
fontSize: 11, color: 'var(--text-secondary)', fontWeight: 600,
|
|
||||||
marginBottom: 8, display: 'block', textTransform: 'uppercase', letterSpacing: '0.08em',
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.6)', backdropFilter: 'blur(12px)', zIndex: 100, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20 }} onClick={onClose}>
|
<div style={{
|
||||||
|
position: 'fixed', inset: 0,
|
||||||
|
background: 'rgba(0,0,0,0.6)', backdropFilter: 'blur(12px)',
|
||||||
|
zIndex: 100, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
|
padding: 20,
|
||||||
|
}} onClick={onClose}>
|
||||||
<div style={{
|
<div style={{
|
||||||
background: 'rgba(16,16,30,0.97)', backdropFilter: 'blur(40px)',
|
background: 'rgba(16,16,30,0.97)', backdropFilter: 'blur(40px)',
|
||||||
border: '1px solid rgba(255,255,255,0.07)', borderRadius: 28,
|
border: '1px solid rgba(255,255,255,0.07)', borderRadius: 28,
|
||||||
width: 460, maxWidth: '95vw', overflow: 'hidden',
|
width: 420, maxWidth: '95vw', overflow: 'hidden',
|
||||||
boxShadow: '0 30px 90px rgba(0,0,0,0.6)',
|
boxShadow: '0 30px 90px rgba(0,0,0,0.6)',
|
||||||
}} onClick={e => e.stopPropagation()}>
|
}} onClick={e => e.stopPropagation()}>
|
||||||
|
|
||||||
{/* Colored header */}
|
{/* Header */}
|
||||||
<div style={{
|
<div style={{
|
||||||
background: `linear-gradient(135deg, ${selectedCal.color}20, ${selectedCal.color}08)`,
|
padding: '24px 28px 20px',
|
||||||
borderBottom: `1px solid ${selectedCal.color}15`,
|
borderBottom: '1px solid rgba(255,255,255,0.05)',
|
||||||
padding: '28px 32px 22px',
|
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
||||||
transition: 'background 0.3s ease',
|
|
||||||
}}>
|
}}>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<span style={{ fontSize: 18, fontWeight: 700, color: 'var(--text-primary)' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
|
Новое событие
|
||||||
<div style={{
|
</span>
|
||||||
width: 44, height: 44, borderRadius: 14,
|
<button onClick={onClose} style={{
|
||||||
background: `${selectedCal.color}25`,
|
width: 32, height: 32, borderRadius: 10,
|
||||||
border: `1px solid ${selectedCal.color}30`,
|
background: 'rgba(255,255,255,0.05)',
|
||||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
boxShadow: `0 0 20px ${selectedCal.color}15`,
|
color: 'var(--text-secondary)',
|
||||||
}}>
|
}}>
|
||||||
<Plus size={22} color={selectedCal.color} />
|
<X size={16} />
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div style={{ fontSize: 20, fontWeight: 700, color: 'var(--text-primary)' }}>Новое событие</div>
|
|
||||||
<div style={{ fontSize: 12, color: 'var(--text-secondary)', marginTop: 2, textTransform: 'capitalize' }}>{dateLabel}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button onClick={onClose} style={{ color: 'var(--text-secondary)', padding: 8, borderRadius: 12, background: 'rgba(255,255,255,0.05)' }}>
|
|
||||||
<X size={18} />
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Body */}
|
{/* Body */}
|
||||||
<div style={{ padding: '24px 32px 30px', display: 'flex', flexDirection: 'column', gap: 18 }}>
|
<div style={{ padding: '20px 28px 28px' }}>
|
||||||
|
|
||||||
{/* Calendar selector */}
|
{/* Calendar selector */}
|
||||||
<div>
|
<div style={{ display: 'flex', gap: 8, marginBottom: 20 }}>
|
||||||
<label style={fieldLabelStyle}>Календарь</label>
|
|
||||||
<div style={{ display: 'flex', gap: 8 }}>
|
|
||||||
{CALENDAR_OPTIONS.map(cal => {
|
{CALENDAR_OPTIONS.map(cal => {
|
||||||
const isSelected = owner === cal.owner
|
const sel = owner === cal.owner
|
||||||
return (
|
return (
|
||||||
<button key={cal.owner} onClick={() => setOwner(cal.owner)} style={{
|
<button key={cal.owner} onClick={() => setOwner(cal.owner)} style={{
|
||||||
flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
|
flex: 1, padding: '11px 0', borderRadius: 12,
|
||||||
padding: '12px 16px', borderRadius: 14,
|
background: sel ? `${cal.color}15` : 'rgba(255,255,255,0.03)',
|
||||||
background: isSelected ? `${cal.color}18` : 'rgba(255,255,255,0.03)',
|
border: `1.5px solid ${sel ? cal.color + '40' : 'rgba(255,255,255,0.06)'}`,
|
||||||
border: `1px solid ${isSelected ? cal.color + '35' : 'rgba(255,255,255,0.06)'}`,
|
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
|
||||||
transition: 'all 0.25s ease',
|
transition: 'all 0.2s ease',
|
||||||
}}>
|
}}>
|
||||||
<div style={{
|
<div style={{
|
||||||
width: 8, height: 8, borderRadius: '50%',
|
width: 10, height: 10, borderRadius: '50%',
|
||||||
background: cal.color,
|
background: sel ? cal.color : 'rgba(255,255,255,0.15)',
|
||||||
boxShadow: isSelected ? `0 0 8px ${cal.color}60` : 'none',
|
boxShadow: sel ? `0 0 8px ${cal.color}50` : 'none',
|
||||||
|
transition: 'all 0.2s ease',
|
||||||
}} />
|
}} />
|
||||||
<span style={{
|
<span style={{
|
||||||
fontSize: 14, fontWeight: isSelected ? 600 : 500,
|
fontSize: 14, fontWeight: sel ? 600 : 400,
|
||||||
color: isSelected ? cal.color : 'var(--text-secondary)',
|
color: sel ? cal.color : 'var(--text-secondary)',
|
||||||
}}>
|
}}>{cal.name}</span>
|
||||||
{cal.name}
|
|
||||||
</span>
|
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<div>
|
<input
|
||||||
<label style={fieldLabelStyle}>Название</label>
|
value={title} onChange={e => setTitle(e.target.value)}
|
||||||
<input value={title} onChange={e => setTitle(e.target.value)} placeholder="Что запланировано?" autoFocus style={fieldInputStyle} />
|
placeholder="Название события"
|
||||||
|
autoFocus
|
||||||
|
style={{
|
||||||
|
width: '100%', padding: '15px 18px', borderRadius: 14,
|
||||||
|
background: 'rgba(255,255,255,0.04)',
|
||||||
|
border: '1px solid rgba(255,255,255,0.07)',
|
||||||
|
color: 'var(--text-primary)', fontSize: 16, fontWeight: 500,
|
||||||
|
outline: 'none', fontFamily: 'inherit',
|
||||||
|
marginBottom: 16,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Date */}
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<div style={{
|
||||||
|
fontSize: 11, color: 'var(--text-secondary)', fontWeight: 600,
|
||||||
|
textTransform: 'uppercase', letterSpacing: '0.08em',
|
||||||
|
marginBottom: 8,
|
||||||
|
}}>Дата</div>
|
||||||
|
<input
|
||||||
|
type="date" value={date} onChange={e => setDate(e.target.value)}
|
||||||
|
style={{
|
||||||
|
width: '100%', padding: '14px 18px', borderRadius: 14,
|
||||||
|
background: 'rgba(255,255,255,0.04)',
|
||||||
|
border: '1px solid rgba(255,255,255,0.07)',
|
||||||
|
color: 'var(--text-primary)', fontSize: 15,
|
||||||
|
outline: 'none', fontFamily: 'inherit',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Date + All day */}
|
{/* All day toggle */}
|
||||||
<div style={{ display: 'flex', gap: 10, alignItems: 'flex-end' }}>
|
|
||||||
<div style={{ flex: 1 }}>
|
|
||||||
<label style={fieldLabelStyle}>Дата</label>
|
|
||||||
<input type="date" value={date} onChange={e => setDate(e.target.value)} style={fieldInputStyle} />
|
|
||||||
</div>
|
|
||||||
<button onClick={() => setAllDay(v => !v)} style={{
|
<button onClick={() => setAllDay(v => !v)} style={{
|
||||||
padding: '14px 18px', borderRadius: 14, whiteSpace: 'nowrap',
|
width: '100%', padding: '13px 18px', borderRadius: 14,
|
||||||
background: allDay ? `${selectedCal.color}12` : 'rgba(255,255,255,0.03)',
|
background: allDay ? `${selectedCal.color}10` : 'rgba(255,255,255,0.025)',
|
||||||
border: `1px solid ${allDay ? selectedCal.color + '25' : 'rgba(255,255,255,0.06)'}`,
|
border: `1px solid ${allDay ? selectedCal.color + '25' : 'rgba(255,255,255,0.06)'}`,
|
||||||
color: allDay ? selectedCal.color : 'var(--text-secondary)',
|
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
||||||
fontSize: 13, fontWeight: 600, transition: 'all 0.25s ease',
|
marginBottom: 16, transition: 'all 0.2s ease',
|
||||||
}}>
|
}}>
|
||||||
{allDay ? '✓ Весь день' : 'Весь день'}
|
<span style={{
|
||||||
|
fontSize: 14, fontWeight: 500,
|
||||||
|
color: allDay ? selectedCal.color : 'var(--text-secondary)',
|
||||||
|
}}>Весь день</span>
|
||||||
|
<div style={{
|
||||||
|
width: 40, height: 22, borderRadius: 11,
|
||||||
|
background: allDay ? selectedCal.color : 'rgba(255,255,255,0.1)',
|
||||||
|
position: 'relative', transition: 'background 0.2s ease',
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
width: 18, height: 18, borderRadius: '50%',
|
||||||
|
background: '#fff', position: 'absolute', top: 2,
|
||||||
|
left: allDay ? 20 : 2,
|
||||||
|
transition: 'left 0.2s ease',
|
||||||
|
boxShadow: '0 1px 3px rgba(0,0,0,0.3)',
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Time */}
|
{/* Time pickers */}
|
||||||
{!allDay && (
|
{!allDay && (
|
||||||
<div>
|
<div style={{ marginBottom: 16 }}>
|
||||||
<label style={fieldLabelStyle}>Время</label>
|
<div style={{
|
||||||
<div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
|
fontSize: 11, color: 'var(--text-secondary)', fontWeight: 600,
|
||||||
<div style={{ flex: 1, position: 'relative' }}>
|
textTransform: 'uppercase', letterSpacing: '0.08em',
|
||||||
<Clock size={14} color="var(--text-tertiary)" style={{ position: 'absolute', left: 14, top: '50%', transform: 'translateY(-50%)', pointerEvents: 'none' }} />
|
marginBottom: 8,
|
||||||
<input type="time" value={startTime} onChange={e => setStartTime(e.target.value)} style={{ ...fieldInputStyle, paddingLeft: 36 }} />
|
}}>Время</div>
|
||||||
</div>
|
<div style={{
|
||||||
<div style={{ width: 20, height: 2, borderRadius: 1, background: 'rgba(255,255,255,0.12)', flexShrink: 0 }} />
|
display: 'flex', alignItems: 'center',
|
||||||
<div style={{ flex: 1, position: 'relative' }}>
|
background: 'rgba(255,255,255,0.04)',
|
||||||
<Clock size={14} color="var(--text-tertiary)" style={{ position: 'absolute', left: 14, top: '50%', transform: 'translateY(-50%)', pointerEvents: 'none' }} />
|
border: '1px solid rgba(255,255,255,0.07)',
|
||||||
<input type="time" value={endTime} onChange={e => setEndTime(e.target.value)} style={{ ...fieldInputStyle, paddingLeft: 36 }} />
|
borderRadius: 14, overflow: 'hidden',
|
||||||
</div>
|
}}>
|
||||||
|
<input
|
||||||
|
type="time" value={startTime} onChange={e => setStartTime(e.target.value)}
|
||||||
|
style={{
|
||||||
|
flex: 1, padding: '14px 18px', border: 'none',
|
||||||
|
background: 'transparent', color: 'var(--text-primary)',
|
||||||
|
fontSize: 16, fontWeight: 600, fontFamily: 'inherit',
|
||||||
|
outline: 'none', textAlign: 'center',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div style={{
|
||||||
|
padding: '0 12px', color: 'var(--text-tertiary)',
|
||||||
|
fontSize: 13, fontWeight: 500, flexShrink: 0,
|
||||||
|
borderLeft: '1px solid rgba(255,255,255,0.06)',
|
||||||
|
borderRight: '1px solid rgba(255,255,255,0.06)',
|
||||||
|
height: '100%', display: 'flex', alignItems: 'center',
|
||||||
|
background: 'rgba(255,255,255,0.02)',
|
||||||
|
}}>до</div>
|
||||||
|
<input
|
||||||
|
type="time" value={endTime} onChange={e => setEndTime(e.target.value)}
|
||||||
|
style={{
|
||||||
|
flex: 1, padding: '14px 18px', border: 'none',
|
||||||
|
background: 'transparent', color: 'var(--text-primary)',
|
||||||
|
fontSize: 16, fontWeight: 600, fontFamily: 'inherit',
|
||||||
|
outline: 'none', textAlign: 'center',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Error */}
|
||||||
{error && (
|
{error && (
|
||||||
<div style={{ color: '#f87171', fontSize: 13, padding: '10px 14px', borderRadius: 12, background: 'rgba(239,68,68,0.08)', border: '1px solid rgba(239,68,68,0.12)' }}>
|
<div style={{
|
||||||
{error}
|
padding: '10px 14px', borderRadius: 12, marginBottom: 16,
|
||||||
</div>
|
background: 'rgba(239,68,68,0.08)', border: '1px solid rgba(239,68,68,0.12)',
|
||||||
|
color: '#f87171', fontSize: 13, fontWeight: 500,
|
||||||
|
}}>{error}</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Submit */}
|
{/* Submit */}
|
||||||
<button onClick={save} disabled={saving} style={{
|
<button onClick={save} disabled={saving} style={{
|
||||||
padding: '16px', borderRadius: 16, marginTop: 2,
|
width: '100%', padding: '15px', borderRadius: 14,
|
||||||
background: saving ? `${selectedCal.color}15` : `linear-gradient(135deg, ${selectedCal.color}50, ${selectedCal.color}35)`,
|
background: saving
|
||||||
border: `1px solid ${selectedCal.color}40`,
|
? 'rgba(255,255,255,0.04)'
|
||||||
color: selectedCal.color === '#ec4899' ? '#f9a8d4' : '#a5b4fc',
|
: `linear-gradient(135deg, ${selectedCal.color}45, ${selectedCal.color}30)`,
|
||||||
|
border: `1px solid ${selectedCal.color}35`,
|
||||||
|
color: selectedCal.color === '#ec4899' ? '#f9a8d4' : '#c7d2fe',
|
||||||
fontSize: 15, fontWeight: 700,
|
fontSize: 15, fontWeight: 700,
|
||||||
cursor: saving ? 'default' : 'pointer',
|
cursor: saving ? 'default' : 'pointer',
|
||||||
transition: 'all 0.3s ease',
|
transition: 'all 0.25s ease',
|
||||||
boxShadow: saving ? 'none' : `0 4px 20px ${selectedCal.color}20`,
|
boxShadow: saving ? 'none' : `0 4px 16px ${selectedCal.color}18`,
|
||||||
}}>
|
}}>
|
||||||
{saving ? 'Сохранение...' : 'Создать событие'}
|
{saving ? 'Сохранение...' : 'Создать'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user