// ScheduleTab.jsx - Business Hours & Holiday Calendar Management // Exported as window.ScheduleTab (function () { const { useState, useEffect, useCallback, useRef } = React; const DAY_KEYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']; const DAY_LABELS = { mon: 'Pazartesi', tue: 'Salı', wed: 'Çarşamba', thu: 'Perşembe', fri: 'Cuma', sat: 'Cumartesi', sun: 'Pazar' }; const TIMEZONES = ['Europe/Istanbul', 'UTC', 'Europe/London', 'Europe/Berlin', 'America/New_York']; function defaultSchedule() { const sched = {}; DAY_KEYS.forEach(k => { sched[k] = { enabled: ['mon', 'tue', 'wed', 'thu', 'fri'].includes(k), open: '09:00', close: '18:00' }; }); return sched; } // ─── Holiday Row ─────────────────────────────────────────────────────────── function HolidayRow({ holiday, scheduleId, onDelete, toast }) { async function handleDelete() { try { const res = await authFetch(`/api/schedules/${scheduleId}/holidays/${holiday.id}`, { method: 'DELETE' }); if (res.ok) { toast && toast('Tatil silindi', 'success'); onDelete(); } else { const d = await res.json(); toast && toast(d.error || 'Silme hatası', 'error'); } } catch (e) { toast && toast('Hata: ' + e.message, 'error'); } } return React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '5px 0', borderBottom: '1px solid var(--border)', fontSize: 13 } }, React.createElement('span', null, holiday.date, ' — ', React.createElement('strong', null, holiday.name)), holiday.route_type && React.createElement('span', { style: { color: 'var(--text-dim)', fontSize: 11, marginLeft: 8 } }, holiday.route_type, ': ', holiday.route_target ), React.createElement('button', { className: 'btn btn-danger', onClick: handleDelete, style: { fontSize: 11, padding: '2px 8px', marginLeft: 8 } }, '') ); } // ─── Add Holiday Form ────────────────────────────────────────────────────── function AddHolidayForm({ scheduleId, onAdded, toast }) { const [date, setDate] = useState(''); const [name, setName] = useState(''); const [routeType, setRouteType] = useState(''); const [routeTarget, setRouteTarget] = useState(''); const [saving, setSaving] = useState(false); async function handleSubmit(e) { e.preventDefault(); if (!date || !name) return; setSaving(true); try { const body = { date, name }; if (routeType) { body.route_type = routeType; body.route_target = routeTarget; } const res = await authFetch(`/api/schedules/${scheduleId}/holidays`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); if (res.ok) { toast && toast('Tatil eklendi', 'success'); setDate(''); setName(''); setRouteType(''); setRouteTarget(''); onAdded(); } else { const d = await res.json(); toast && toast(d.error || 'Ekleme hatası', 'error'); } } catch (e) { toast && toast('Hata: ' + e.message, 'error'); } finally { setSaving(false); } } return React.createElement('form', { onSubmit: handleSubmit, style: { display: 'flex', flexWrap: 'wrap', gap: 8, marginTop: 10, alignItems: 'flex-end' } }, React.createElement('div', null, React.createElement('label', { style: { fontSize: 11, color: 'var(--text-dim)', display: 'block' } }, 'Tarih'), React.createElement('input', { type: 'date', className: 'form-input', value: date, onChange: e =>setDate(e.target.value), required: true, style: { fontSize: 12 } }) ), React.createElement('div', null, React.createElement('label', { style: { fontSize: 11, color: 'var(--text-dim)', display: 'block' } }, 'Ad'), React.createElement('input', { type: 'text', className: 'form-input', value: name, placeholder: 'Tatil adı', onChange: e =>setName(e.target.value), required: true, style: { fontSize: 12 } }) ), React.createElement('div', null, React.createElement('label', { style: { fontSize: 11, color: 'var(--text-dim)', display: 'block' } }, 'Yönlendirme Tipi'), React.createElement('select', { className: 'form-select', value: routeType, onChange: e =>setRouteType(e.target.value), style: { fontSize: 12 } }, React.createElement('option', { value: '' }, '— Yok —'), React.createElement('option', { value: 'ivr' }, 'IVR'), React.createElement('option', { value: 'queue' }, 'Kuyruk'), React.createElement('option', { value: 'number' }, 'Numara') ) ), routeType && React.createElement('div', null, React.createElement('label', { style: { fontSize: 11, color: 'var(--text-dim)', display: 'block' } }, 'Hedef'), React.createElement('input', { type: 'text', className: 'form-input', value: routeTarget, placeholder: 'Hedef...', onChange: e =>setRouteTarget(e.target.value), style: { fontSize: 12 } }) ), React.createElement('button', { type: 'submit', className: 'btn btn-primary', disabled: saving, style: { fontSize: 12 } }, saving ? 'Ekleniyor...' : '+ Tatil Ekle') ); } // ─── Holidays Section (expandable per schedule card) ───────────────────── function HolidaysSection({ schedule, toast }) { const [open, setOpen] = useState(false); const [holidays, setHolidays] = useState([]); const [loading, setLoading] = useState(false); async function fetchHolidays() { setLoading(true); try { const res = await authFetch(`/api/schedules/${schedule.id}/holidays`); if (res.ok) { const d = await res.json(); setHolidays(d.holidays || d || []); } } catch (e) {} finally { setLoading(false); } } function handleToggle() { if (!open) fetchHolidays(); setOpen(o => !o); } return React.createElement('div', { style: { marginTop: 10 } }, React.createElement('button', { className: 'btn btn-secondary', onClick: handleToggle, style: { fontSize: 12, width: '100%', textAlign: 'left' } }, `${open ? '▼' : '▶'} Tatiller (${schedule.holiday_count || 0})` ), open && React.createElement('div', { style: { marginTop: 8 } }, loading ? React.createElement('div', { style: { color: 'var(--text-dim)', fontSize: 12 } }, 'Yükleniyor...') : holidays.length === 0 ? React.createElement('div', { style: { color: 'var(--text-dim)', fontSize: 12 } }, 'Tatil kaydı yok.') : holidays.map(h =>React.createElement(HolidayRow, { key: h.id, holiday: h, scheduleId: schedule.id, onDelete: fetchHolidays, toast })), React.createElement(AddHolidayForm, { scheduleId: schedule.id, onAdded: fetchHolidays, toast }) ) ); } // ─── Status Badge ───────────────────────────────────────────────────────── function StatusBadge({ scheduleId }) { const [status, setStatus] = useState(null); useEffect(() => { async function check() { try { const res = await authFetch(`/api/schedules/${scheduleId}/check`); if (res.ok) { const d = await res.json(); setStatus(d); } } catch (e) {} } check(); const iv = setInterval(check, 60000); return () =>clearInterval(iv); }, [scheduleId]); if (!status) return React.createElement('span', { style: { color: 'var(--text-dim)', fontSize: 12 } }, '...'); return React.createElement('span', { style: { display: 'inline-block', padding: '2px 10px', borderRadius: 12, fontSize: 12, fontWeight: 600, background: status.is_open ? '#16a34a22' : '#dc262622', color: status.is_open ? '#22c55e' : '#ef4444' }, title: status.reason || '' }, status.is_open ? '● Açık' : '● Kapalı'); } // ─── Apply to DID Modal ─────────────────────────────────────────────────── function ApplyToDIDModal({ schedule, onClose, toast }) { const [dids, setDids] = useState([]); const [selectedDID, setSelectedDID] = useState(''); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); useEffect(() => { async function fetchDIDs() { try { const res = await authFetch('/api/dids'); if (res.ok) { const d = await res.json(); setDids(d.dids || d || []); } } catch (e) {} finally { setLoading(false); } } fetchDIDs(); }, []); async function handleApply() { if (!selectedDID) return; setSaving(true); try { const res = await authFetch(`/api/dids/${selectedDID}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ schedule_id: schedule.id }) }); if (res.ok) { toast && toast('Program DID\'e uygulandı', 'success'); onClose(); } else { const d = await res.json(); toast && toast(d.error || 'Uygulama hatası', 'error'); } } catch (e) { toast && toast('Hata: ' + e.message, 'error'); } finally { setSaving(false); } } return React.createElement('div', { style: { position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.6)', zIndex: 2000, display: 'flex', alignItems: 'center', justifyContent: 'center' } }, React.createElement('div', { className: 'card', style: { padding: 24, minWidth: 340, maxWidth: 420 } }, React.createElement('h3', { style: { marginTop: 0, marginBottom: 16 } }, `"${schedule.name}" Programını DID'e Uygula` ), loading ? React.createElement('div', { style: { color: 'var(--text-dim)' } }, 'DID\'ler yükleniyor...') : React.createElement('select', { className: 'form-select', value: selectedDID, onChange: e =>setSelectedDID(e.target.value), style: { width: '100%', marginBottom: 16 } }, React.createElement('option', { value: '' }, '— DID seçin —'), dids.map(d =>React.createElement('option', { key: d.id || d.did, value: d.id || d.did }, d.did || d.number || d.id )) ), React.createElement('div', { style: { display: 'flex', gap: 10 } }, React.createElement('button', { className: 'btn btn-primary', onClick: handleApply, disabled: !selectedDID || saving }, saving ? 'Uygulanıyor...' : 'Uygula'), React.createElement('button', { className: 'btn btn-secondary', onClick: onClose }, 'İptal') ) ) ); } // ─── Schedule Card ───────────────────────────────────────────────────────── function ScheduleCard({ schedule, onEdit, onDelete, toast }) { const [applyDIDTarget, setApplyDIDTarget] = useState(null); return React.createElement('div', { className: 'card', style: { padding: 16, marginBottom: 14 } }, // Header row React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8, flexWrap: 'wrap', gap: 8 } }, React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 10 } }, React.createElement('span', { style: { fontWeight: 600, fontSize: 15 } }, schedule.name), React.createElement(StatusBadge, { scheduleId: schedule.id }), React.createElement('span', { style: { color: 'var(--text-dim)', fontSize: 12 } }, schedule.timezone ) ), React.createElement('div', { style: { display: 'flex', gap: 8 } }, React.createElement('button', { className: 'btn btn-secondary', onClick: () =>setApplyDIDTarget(schedule), style: { fontSize: 12 } }, 'DID Uygula'), React.createElement('button', { className: 'btn btn-secondary', onClick: () =>onEdit(schedule), style: { fontSize: 12 } }, 'Düzenle'), React.createElement('button', { className: 'btn btn-danger', onClick: () =>onDelete(schedule), style: { fontSize: 12 } }, 'Sil') ) ), // Day summary chips React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 8 } }, DAY_KEYS.map(k => { const day = schedule.schedule && schedule.schedule[k]; const enabled = day && day.enabled; return React.createElement('span', { key: k, style: { fontSize: 11, padding: '2px 8px', borderRadius: 10, background: enabled ? 'var(--primary)22' : 'var(--bg-hover)', color: enabled ? 'var(--primary)' : 'var(--text-dim)', border: `1px solid ${enabled ? 'var(--primary)' : 'var(--border)'}` } }, DAY_LABELS[k], enabled && day.open ? ` ${day.open}–${day.close}` : '' ); }) ), // Holidays section React.createElement(HolidaysSection, { schedule, toast }), // Apply to DID modal applyDIDTarget && React.createElement(ApplyToDIDModal, { schedule: applyDIDTarget, onClose: () =>setApplyDIDTarget(null), toast }) ); } // ─── Create/Edit Schedule Modal ─────────────────────────────────────────── function ScheduleModal({ initial, onClose, onSaved, toast }) { const editing = !!initial; const [name, setName] = useState(initial ? initial.name : ''); const [timezone, setTimezone] = useState(initial ? initial.timezone : 'Europe/Istanbul'); const [schedule, setSchedule] = useState( initial && initial.schedule ? initial.schedule : defaultSchedule() ); const [saving, setSaving] = useState(false); function setDay(key, field, value) { setSchedule(s => ({ ...s, [key]: { ...s[key], [field]: value } })); } async function handleSubmit(e) { e.preventDefault(); if (!name.trim()) return; setSaving(true); try { const payload = { name: name.trim(), timezone, schedule }; const url = editing ? `/api/schedules/${initial.id}` : '/api/schedules'; const method = editing ? 'PUT' : 'POST'; const res = await authFetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (res.ok) { toast && toast(editing ? 'Program güncellendi' : 'Program oluşturuldu', 'success'); onSaved(); } else { const d = await res.json(); toast && toast(d.error || 'Kayıt hatası', 'error'); } } catch (err) { toast && toast('Hata: ' + err.message, 'error'); } finally { setSaving(false); } } return React.createElement('div', { style: { position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.65)', zIndex: 1500, display: 'flex', alignItems: 'center', justifyContent: 'center', overflowY: 'auto', padding: '20px 0' }, onClick: e => { if (e.target === e.currentTarget) onClose(); } }, React.createElement('div', { className: 'card', style: { padding: 28, minWidth: 520, maxWidth: 640, width: '95%' } }, React.createElement('h3', { style: { marginTop: 0, marginBottom: 20 } }, editing ? 'Programı Düzenle' : 'Yeni Program Oluştur' ), React.createElement('form', { onSubmit: handleSubmit }, // Name & Timezone React.createElement('div', { style: { display: 'flex', gap: 14, marginBottom: 18, flexWrap: 'wrap' } }, React.createElement('div', { style: { flex: 2, minWidth: 160 } }, React.createElement('label', { style: { fontSize: 12, color: 'var(--text-dim)', display: 'block', marginBottom: 4 } }, 'Program Adı *'), React.createElement('input', { type: 'text', className: 'form-input', value: name, required: true, onChange: e =>setName(e.target.value), placeholder: 'Mesai saatleri...', style: { width: '100%' } }) ), React.createElement('div', { style: { flex: 1, minWidth: 160 } }, React.createElement('label', { style: { fontSize: 12, color: 'var(--text-dim)', display: 'block', marginBottom: 4 } }, 'Saat Dilimi'), React.createElement('select', { className: 'form-select', value: timezone, onChange: e =>setTimezone(e.target.value), style: { width: '100%' } }, TIMEZONES.map(tz =>React.createElement('option', { key: tz, value: tz }, tz)) ) ) ), // Weekly grid React.createElement('div', { style: { marginBottom: 20 } }, React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '110px 40px 90px 16px 90px', gap: '4px 8px', alignItems: 'center', marginBottom: 6, fontSize: 11, color: 'var(--text-dim)', fontWeight: 600 } }, React.createElement('span', null, 'Gün'), React.createElement('span', { style: { textAlign: 'center' } }, 'Açık'), React.createElement('span', null, 'Açılış'), React.createElement('span', null), React.createElement('span', null, 'Kapanış') ), DAY_KEYS.map(k => { const day = schedule[k] || { enabled: false, open: '09:00', close: '18:00' }; return React.createElement('div', { key: k, style: { display: 'grid', gridTemplateColumns: '110px 40px 90px 16px 90px', gap: '4px 8px', alignItems: 'center', marginBottom: 4, padding: '6px 8px', borderRadius: 6, background: day.enabled ? 'var(--bg-hover)' : 'transparent', border: '1px solid var(--border)' } }, React.createElement('span', { style: { fontWeight: day.enabled ? 600 : 400, color: day.enabled ? undefined : 'var(--text-dim)' } }, DAY_LABELS[k] ), React.createElement('div', { style: { textAlign: 'center' } }, React.createElement('input', { type: 'checkbox', checked: !!day.enabled, onChange: e =>setDay(k, 'enabled', e.target.checked), style: { width: 16, height: 16, cursor: 'pointer' } }) ), React.createElement('input', { type: 'time', className: 'form-input', value: day.open || '09:00', onChange: e =>setDay(k, 'open', e.target.value), disabled: !day.enabled, style: { fontSize: 13, opacity: day.enabled ? 1 : 0.4 } }), React.createElement('span', { style: { textAlign: 'center', color: 'var(--text-dim)' } }, '–'), React.createElement('input', { type: 'time', className: 'form-input', value: day.close || '18:00', onChange: e =>setDay(k, 'close', e.target.value), disabled: !day.enabled, style: { fontSize: 13, opacity: day.enabled ? 1 : 0.4 } }) ); }) ), // Buttons React.createElement('div', { style: { display: 'flex', gap: 10, justifyContent: 'flex-end' } }, React.createElement('button', { type: 'button', className: 'btn btn-secondary', onClick: onClose }, 'İptal'), React.createElement('button', { type: 'submit', className: 'btn btn-primary', disabled: saving }, saving ? 'Kaydediliyor...' : (editing ? 'Güncelle' : 'Oluştur') ) ) ) ) ); } // ─── Confirm Delete Dialog ──────────────────────────────────────────────── function ConfirmDeleteDialog({ schedule, onConfirm, onCancel }) { return React.createElement('div', { style: { position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.6)', zIndex: 2000, display: 'flex', alignItems: 'center', justifyContent: 'center' } }, React.createElement('div', { className: 'card', style: { padding: 28, minWidth: 320, textAlign: 'center' } }, React.createElement('p', { style: { marginBottom: 20 } }, `"${schedule.name}" programını silmek istediğinize emin misiniz?` ), React.createElement('div', { style: { display: 'flex', gap: 12, justifyContent: 'center' } }, React.createElement('button', { className: 'btn btn-danger', onClick: onConfirm }, 'Sil'), React.createElement('button', { className: 'btn btn-secondary', onClick: onCancel }, 'İptal') ) ) ); } // ─── Main ScheduleTab ───────────────────────────────────────────────────── function ScheduleTab({ toast }) { const [schedules, setSchedules] = useState([]); const [loading, setLoading] = useState(false); const [showModal, setShowModal] = useState(false); const [editTarget, setEditTarget] = useState(null); const [deleteTarget, setDeleteTarget] = useState(null); const fetchSchedules = useCallback(async () => { setLoading(true); try { const res = await authFetch('/api/schedules'); if (res.ok) { const d = await res.json(); setSchedules(d.schedules || d || []); } else { toast && toast('Programlar yüklenemedi', 'error'); } } catch (e) { toast && toast('Hata: ' + e.message, 'error'); } finally { setLoading(false); } }, []); useEffect(() => { fetchSchedules(); }, []); function handleEdit(schedule) { setEditTarget(schedule); setShowModal(true); } function handleCreate() { setEditTarget(null); setShowModal(true); } function handleModalClose() { setShowModal(false); setEditTarget(null); } function handleSaved() { handleModalClose(); fetchSchedules(); } async function handleDelete(schedule) { try { const res = await authFetch(`/api/schedules/${schedule.id}`, { method: 'DELETE' }); if (res.ok) { toast && toast('Program silindi', 'success'); fetchSchedules(); } else { const d = await res.json(); toast && toast(d.error || 'Silme hatası', 'error'); } } catch (e) { toast && toast('Hata: ' + e.message, 'error'); } finally { setDeleteTarget(null); } } return React.createElement('div', null, // ── Header ──────────────────────────────────────────────────────────── React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 } }, React.createElement('h2', { style: { margin: 0 } }, 'Mesai Programları'), React.createElement('button', { className: 'btn btn-primary', onClick: handleCreate }, '+ Yeni Program') ), // ── Loading / Empty ─────────────────────────────────────────────────── loading ? React.createElement('div', { style: { textAlign: 'center', padding: 40, color: 'var(--text-dim)' } }, 'Yükleniyor...') : schedules.length === 0 ? React.createElement('div', { className: 'card', style: { textAlign: 'center', padding: 40, color: 'var(--text-dim)' } }, React.createElement('p', { style: { marginBottom: 14 } }, 'Henüz program oluşturulmamış.'), React.createElement('button', { className: 'btn btn-primary', onClick: handleCreate }, '+ İlk Programı Oluştur') ) : schedules.map(s => React.createElement(ScheduleCard, { key: s.id, schedule: s, onEdit: handleEdit, onDelete: sc =>setDeleteTarget(sc), toast }) ), // ── Create/Edit Modal ───────────────────────────────────────────────── showModal && React.createElement(ScheduleModal, { initial: editTarget, onClose: handleModalClose, onSaved: handleSaved, toast }), // ── Confirm Delete Dialog ───────────────────────────────────────────── deleteTarget && React.createElement(ConfirmDeleteDialog, { schedule: deleteTarget, onConfirm: () =>handleDelete(deleteTarget), onCancel: () =>setDeleteTarget(null) }) ); } window.ScheduleTab = ScheduleTab; })();