File size: 4,811 Bytes
cd99321
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import React, { useEffect, useState } from 'react'
import { adminApi } from '../../api/client'
import { useToast } from '../../components/shared/Toast'
import { ADMIN_EVENT_LABEL_KEYS, ADMIN_CHANNEL_LABEL_KEYS } from './AdminPage.constants'

// Per-event × per-channel admin notification preference matrix.
// Loads its own data and auto-saves each toggle. Markup identical to AdminPage.
export default function AdminNotificationsPanel({ t, toast }: { t: (k: string) => string; toast: ReturnType<typeof useToast> }) {
  const [matrix, setMatrix] = useState<any>(null)
  const [saving, setSaving] = useState(false)

  useEffect(() => {
    adminApi.getNotificationPreferences().then((data: any) => setMatrix(data)).catch(() => {})
  }, [])

  if (!matrix) return <p className="text-content-faint" style={{ fontSize: 12, fontStyle: 'italic', padding: 16 }}>Loading…</p>

  const visibleChannels = (['inapp', 'email', 'webhook', 'ntfy'] as const).filter(ch => {
    if (!matrix.available_channels[ch]) return false
    return matrix.event_types.some((evt: string) => matrix.implemented_combos[evt]?.includes(ch))
  })

  const toggle = async (eventType: string, channel: string) => {
    const current = matrix.preferences[eventType]?.[channel] ?? true
    const updated = { ...matrix.preferences, [eventType]: { ...matrix.preferences[eventType], [channel]: !current } }
    setMatrix((m: any) => m ? { ...m, preferences: updated } : m)
    setSaving(true)
    try {
      await adminApi.updateNotificationPreferences(updated)
    } catch {
      setMatrix((m: any) => m ? { ...m, preferences: matrix.preferences } : m)
      toast.error(t('common.error'))
    } finally {
      setSaving(false)
    }
  }

  if (matrix.event_types.length === 0) {
    return (
      <div className="bg-white rounded-xl border border-slate-200 p-6">
        <p className="text-content-faint" style={{ fontSize: 13 }}>{t('settings.notificationPreferences.noChannels')}</p>
      </div>
    )
  }

  return (
    <div className="space-y-4">
      <div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
        <div className="px-6 py-4 border-b border-slate-100">
          <h2 className="font-semibold text-slate-900">{t('admin.tabs.notifications')}</h2>
          <p className="text-xs text-slate-400 mt-1">{t('admin.notifications.adminNotificationsHint')}</p>
        </div>
        <div className="p-6">
          {saving && <p className="text-content-faint" style={{ fontSize: 11, marginBottom: 8 }}>Saving…</p>}
          {/* Header row */}
          <div className="border-b border-edge" style={{ display: 'grid', gridTemplateColumns: `1fr ${visibleChannels.map(() => '80px').join(' ')}`, gap: 4, paddingBottom: 6, marginBottom: 4 }}>
            <span />
            {visibleChannels.map(ch => (
              <span key={ch} className="text-content-faint" style={{ fontSize: 11, fontWeight: 600, textAlign: 'center', textTransform: 'uppercase', letterSpacing: '0.04em' }}>
                {t(ADMIN_CHANNEL_LABEL_KEYS[ch]) || ch}
              </span>
            ))}
          </div>
          {/* Event rows */}
          {matrix.event_types.map((eventType: string) => {
            const implementedForEvent = matrix.implemented_combos[eventType] ?? []
            return (
              <div key={eventType} className="border-b border-edge" style={{ display: 'grid', gridTemplateColumns: `1fr ${visibleChannels.map(() => '80px').join(' ')}`, gap: 4, alignItems: 'center', padding: '8px 0' }}>
                <span className="text-content" style={{ fontSize: 13 }}>
                  {t(ADMIN_EVENT_LABEL_KEYS[eventType]) || eventType}
                </span>
                {visibleChannels.map(ch => {
                  if (!implementedForEvent.includes(ch)) {
                    return <span key={ch} className="text-content-faint" style={{ textAlign: 'center', fontSize: 14 }}></span>
                  }
                  const isOn = matrix.preferences[eventType]?.[ch] ?? true
                  return (
                    <div key={ch} style={{ display: 'flex', justifyContent: 'center' }}>
                      <button
                        onClick={() => toggle(eventType, ch)}
                        className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors flex-shrink-0 ${isOn ? 'bg-content' : 'bg-edge'}`}
                      >
                        <span className="absolute left-0.5 h-4 w-4 rounded-full bg-white transition-transform duration-200"
                          style={{ transform: isOn ? 'translateX(16px)' : 'translateX(0)' }} />
                      </button>
                    </div>
                  )
                })}
              </div>
            )
          })}
        </div>
      </div>
    </div>
  )
}