feat: redesigned add-event modal with calendar selector (Даниил/Света)
Some checks failed
Deploy / deploy (push) Has been cancelled
Some checks failed
Deploy / deploy (push) Has been cancelled
This commit is contained in:
@@ -105,7 +105,7 @@ export async function GET(req: Request) {
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const body = await req.json()
|
||||
const { title, date, startTime, endTime, allDay } = body
|
||||
const { title, date, startTime, endTime, allDay, owner } = body
|
||||
|
||||
const auth = getAuth(false)
|
||||
if (!auth) return NextResponse.json({ error: 'not_configured' }, { status: 500 })
|
||||
@@ -113,6 +113,15 @@ export async function POST(req: Request) {
|
||||
const calendarClient = google.calendar({ version: 'v3', auth: auth as any })
|
||||
|
||||
const daniilCalendarId = process.env.DANIIL_CALENDAR_ID || 'daniilklimov25@gmail.com'
|
||||
const svetaCalendarId = process.env.SVETA_CALENDAR_ID || ''
|
||||
|
||||
const calendars: Record<string, { id: string; name: string; color: string }> = {
|
||||
daniil: { id: daniilCalendarId, name: 'Даниил', color: '#6366f1' },
|
||||
...(svetaCalendarId ? { sveta: { id: svetaCalendarId, name: 'Света', color: '#ec4899' } } : {}),
|
||||
}
|
||||
|
||||
const selectedOwner = owner && calendars[owner] ? owner : 'daniil'
|
||||
const cal = calendars[selectedOwner]
|
||||
|
||||
let start: any, end: any
|
||||
if (allDay) {
|
||||
@@ -125,7 +134,7 @@ export async function POST(req: Request) {
|
||||
|
||||
try {
|
||||
const res = await calendarClient.events.insert({
|
||||
calendarId: daniilCalendarId,
|
||||
calendarId: cal.id,
|
||||
requestBody: { summary: title, start, end },
|
||||
})
|
||||
const e = res.data
|
||||
@@ -136,9 +145,9 @@ export async function POST(req: Request) {
|
||||
start: e.start?.dateTime || e.start?.date,
|
||||
end: e.end?.dateTime || e.end?.date,
|
||||
allDay: !e.start?.dateTime,
|
||||
owner: 'daniil',
|
||||
ownerName: 'Даниил',
|
||||
color: '#6366f1',
|
||||
owner: selectedOwner,
|
||||
ownerName: cal.name,
|
||||
color: cal.color,
|
||||
}
|
||||
})
|
||||
} catch (err: any) {
|
||||
|
||||
@@ -27,27 +27,31 @@ function formatFullDate(iso: string): string {
|
||||
}
|
||||
|
||||
// ————— Add Event Modal —————
|
||||
const CALENDAR_OPTIONS = [
|
||||
{ owner: 'daniil', name: 'Даниил', color: '#6366f1' },
|
||||
{ owner: 'sveta', name: 'Света', color: '#ec4899' },
|
||||
]
|
||||
|
||||
function AddEventModal({ defaultDate, onClose, onSaved }: { defaultDate: string; onClose: () => void; onSaved: (e: any) => void }) {
|
||||
const [title, setTitle] = useState('')
|
||||
const [date, setDate] = useState(defaultDate)
|
||||
const [startTime, setStartTime] = useState('10:00')
|
||||
const [endTime, setEndTime] = useState('11:00')
|
||||
const [allDay, setAllDay] = useState(false)
|
||||
const [owner, setOwner] = useState('daniil')
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
|
||||
const inputStyle: React.CSSProperties = {
|
||||
padding: '14px 18px', borderRadius: 14,
|
||||
background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.08)',
|
||||
color: 'var(--text-primary)', fontSize: 15, outline: 'none', fontFamily: 'inherit',
|
||||
width: '100%',
|
||||
}
|
||||
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 () => {
|
||||
if (!title.trim()) { setError('Введите название'); return }
|
||||
setSaving(true); setError('')
|
||||
try {
|
||||
const body = { title: title.trim(), date, startTime: allDay ? null : startTime, endTime: allDay ? null : endTime, allDay }
|
||||
const body = { title: title.trim(), date, startTime: allDay ? null : startTime, endTime: allDay ? null : endTime, allDay, owner }
|
||||
const r = await fetch('/api/calendar', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) })
|
||||
const d = await r.json()
|
||||
if (d.error) throw new Error(d.error)
|
||||
@@ -55,75 +59,146 @@ function AddEventModal({ defaultDate, onClose, onSaved }: { defaultDate: string;
|
||||
} 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 (
|
||||
<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={{
|
||||
background: 'rgba(18,18,35,0.95)', backdropFilter: 'blur(40px)',
|
||||
border: '1px solid rgba(255,255,255,0.08)', borderRadius: 28,
|
||||
padding: '32px 36px', width: 440, maxWidth: '95vw',
|
||||
boxShadow: '0 25px 80px rgba(0,0,0,0.6)',
|
||||
background: 'rgba(16,16,30,0.97)', backdropFilter: 'blur(40px)',
|
||||
border: '1px solid rgba(255,255,255,0.07)', borderRadius: 28,
|
||||
width: 460, maxWidth: '95vw', overflow: 'hidden',
|
||||
boxShadow: '0 30px 90px rgba(0,0,0,0.6)',
|
||||
}} onClick={e => e.stopPropagation()}>
|
||||
{/* Header */}
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 28 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<div style={{
|
||||
width: 40, height: 40, borderRadius: 12,
|
||||
background: 'linear-gradient(135deg, rgba(99,102,241,0.2), rgba(139,92,246,0.15))',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
}}>
|
||||
<Plus size={20} color="#a5b4fc" />
|
||||
|
||||
{/* Colored header */}
|
||||
<div style={{
|
||||
background: `linear-gradient(135deg, ${selectedCal.color}20, ${selectedCal.color}08)`,
|
||||
borderBottom: `1px solid ${selectedCal.color}15`,
|
||||
padding: '28px 32px 22px',
|
||||
transition: 'background 0.3s ease',
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
|
||||
<div style={{
|
||||
width: 44, height: 44, borderRadius: 14,
|
||||
background: `${selectedCal.color}25`,
|
||||
border: `1px solid ${selectedCal.color}30`,
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
boxShadow: `0 0 20px ${selectedCal.color}15`,
|
||||
}}>
|
||||
<Plus size={22} color={selectedCal.color} />
|
||||
</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>
|
||||
<span style={{ fontSize: 20, fontWeight: 700, color: 'var(--text-primary)' }}>Новое событие</span>
|
||||
<button onClick={onClose} style={{ color: 'var(--text-secondary)', padding: 8, borderRadius: 12, background: 'rgba(255,255,255,0.05)' }}>
|
||||
<X size={18} />
|
||||
</button>
|
||||
</div>
|
||||
<button onClick={onClose} style={{ color: 'var(--text-secondary)', padding: 6, borderRadius: 10, background: 'rgba(255,255,255,0.04)' }}><X size={18} /></button>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
{/* Title */}
|
||||
{/* Body */}
|
||||
<div style={{ padding: '24px 32px 30px', display: 'flex', flexDirection: 'column', gap: 18 }}>
|
||||
|
||||
{/* Calendar selector */}
|
||||
<div>
|
||||
<label style={{ fontSize: 12, color: 'var(--text-secondary)', fontWeight: 600, marginBottom: 6, display: 'block', textTransform: 'uppercase', letterSpacing: '0.05em' }}>Название</label>
|
||||
<input value={title} onChange={e => setTitle(e.target.value)} placeholder="Встреча, звонок, задача..." autoFocus style={inputStyle} />
|
||||
<label style={fieldLabelStyle}>Календарь</label>
|
||||
<div style={{ display: 'flex', gap: 8 }}>
|
||||
{CALENDAR_OPTIONS.map(cal => {
|
||||
const isSelected = owner === cal.owner
|
||||
return (
|
||||
<button key={cal.owner} onClick={() => setOwner(cal.owner)} style={{
|
||||
flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
|
||||
padding: '12px 16px', borderRadius: 14,
|
||||
background: isSelected ? `${cal.color}18` : 'rgba(255,255,255,0.03)',
|
||||
border: `1px solid ${isSelected ? cal.color + '35' : 'rgba(255,255,255,0.06)'}`,
|
||||
transition: 'all 0.25s ease',
|
||||
}}>
|
||||
<div style={{
|
||||
width: 8, height: 8, borderRadius: '50%',
|
||||
background: cal.color,
|
||||
boxShadow: isSelected ? `0 0 8px ${cal.color}60` : 'none',
|
||||
}} />
|
||||
<span style={{
|
||||
fontSize: 14, fontWeight: isSelected ? 600 : 500,
|
||||
color: isSelected ? cal.color : 'var(--text-secondary)',
|
||||
}}>
|
||||
{cal.name}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Date */}
|
||||
{/* Title */}
|
||||
<div>
|
||||
<label style={{ fontSize: 12, color: 'var(--text-secondary)', fontWeight: 600, marginBottom: 6, display: 'block', textTransform: 'uppercase', letterSpacing: '0.05em' }}>Дата</label>
|
||||
<input type="date" value={date} onChange={e => setDate(e.target.value)} style={inputStyle} />
|
||||
<label style={fieldLabelStyle}>Название</label>
|
||||
<input value={title} onChange={e => setTitle(e.target.value)} placeholder="Что запланировано?" autoFocus style={fieldInputStyle} />
|
||||
</div>
|
||||
|
||||
{/* Date + All day */}
|
||||
<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={{
|
||||
padding: '14px 18px', borderRadius: 14, whiteSpace: 'nowrap',
|
||||
background: allDay ? `${selectedCal.color}12` : 'rgba(255,255,255,0.03)',
|
||||
border: `1px solid ${allDay ? selectedCal.color + '25' : 'rgba(255,255,255,0.06)'}`,
|
||||
color: allDay ? selectedCal.color : 'var(--text-secondary)',
|
||||
fontSize: 13, fontWeight: 600, transition: 'all 0.25s ease',
|
||||
}}>
|
||||
{allDay ? '✓ Весь день' : 'Весь день'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Time */}
|
||||
{!allDay && (
|
||||
<div>
|
||||
<label style={{ fontSize: 12, color: 'var(--text-secondary)', fontWeight: 600, marginBottom: 6, display: 'block', textTransform: 'uppercase', letterSpacing: '0.05em' }}>Время</label>
|
||||
<label style={fieldLabelStyle}>Время</label>
|
||||
<div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
|
||||
<input type="time" value={startTime} onChange={e => setStartTime(e.target.value)} style={{ ...inputStyle, flex: 1 }} />
|
||||
<span style={{ color: 'var(--text-secondary)', fontSize: 14, flexShrink: 0 }}>—</span>
|
||||
<input type="time" value={endTime} onChange={e => setEndTime(e.target.value)} style={{ ...inputStyle, flex: 1 }} />
|
||||
<div style={{ flex: 1, position: 'relative' }}>
|
||||
<Clock size={14} color="var(--text-tertiary)" style={{ position: 'absolute', left: 14, top: '50%', transform: 'translateY(-50%)', pointerEvents: 'none' }} />
|
||||
<input type="time" value={startTime} onChange={e => setStartTime(e.target.value)} style={{ ...fieldInputStyle, paddingLeft: 36 }} />
|
||||
</div>
|
||||
<div style={{ width: 20, height: 2, borderRadius: 1, background: 'rgba(255,255,255,0.12)', flexShrink: 0 }} />
|
||||
<div style={{ flex: 1, position: 'relative' }}>
|
||||
<Clock size={14} color="var(--text-tertiary)" style={{ position: 'absolute', left: 14, top: '50%', transform: 'translateY(-50%)', pointerEvents: 'none' }} />
|
||||
<input type="time" value={endTime} onChange={e => setEndTime(e.target.value)} style={{ ...fieldInputStyle, paddingLeft: 36 }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* All day toggle */}
|
||||
<label style={{
|
||||
display: 'flex', alignItems: 'center', gap: 10,
|
||||
color: 'var(--text-secondary)', fontSize: 14, cursor: 'pointer',
|
||||
padding: '10px 16px', borderRadius: 12,
|
||||
background: allDay ? 'rgba(99,102,241,0.08)' : 'rgba(255,255,255,0.02)',
|
||||
border: allDay ? '1px solid rgba(129,140,248,0.15)' : '1px solid rgba(255,255,255,0.05)',
|
||||
transition: 'all 0.2s ease',
|
||||
}}>
|
||||
<input type="checkbox" checked={allDay} onChange={e => setAllDay(e.target.checked)} style={{ accentColor: '#818cf8' }} />
|
||||
<span style={{ fontWeight: 500 }}>Весь день</span>
|
||||
</label>
|
||||
|
||||
{error && <div style={{ color: '#f87171', fontSize: 13, padding: '8px 12px', borderRadius: 10, background: 'rgba(239,68,68,0.08)' }}>{error}</div>}
|
||||
{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)' }}>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Submit */}
|
||||
<button onClick={save} disabled={saving} style={{
|
||||
padding: '15px', borderRadius: 16, marginTop: 4,
|
||||
background: saving ? 'rgba(99,102,241,0.15)' : 'linear-gradient(135deg, rgba(99,102,241,0.4), rgba(139,92,246,0.3))',
|
||||
border: '1px solid rgba(129,140,248,0.3)', color: '#a5b4fc',
|
||||
fontSize: 15, fontWeight: 700, cursor: saving ? 'default' : 'pointer',
|
||||
transition: 'all 0.25s ease',
|
||||
padding: '16px', borderRadius: 16, marginTop: 2,
|
||||
background: saving ? `${selectedCal.color}15` : `linear-gradient(135deg, ${selectedCal.color}50, ${selectedCal.color}35)`,
|
||||
border: `1px solid ${selectedCal.color}40`,
|
||||
color: selectedCal.color === '#ec4899' ? '#f9a8d4' : '#a5b4fc',
|
||||
fontSize: 15, fontWeight: 700,
|
||||
cursor: saving ? 'default' : 'pointer',
|
||||
transition: 'all 0.3s ease',
|
||||
boxShadow: saving ? 'none' : `0 4px 20px ${selectedCal.color}20`,
|
||||
}}>
|
||||
{saving ? 'Сохранение...' : 'Создать событие'}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user