Catalyst / admin/Synapse-Cortex 14.8 GB / 57.8 GB 40.0 GB free
Help Sign in

admin / Synapse-Cortex

public

Self Hosted ITSM Tool with RBAC/Tenanting and MFA

Code Issues Pull requests Pipelines Packages Security Insights Wiki Settings
Synapse-Cortex / Synapse-Cortexv2 / frontend / src / pages / Dashboard.tsx 4670 B · main
 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
98
99
import { useQuery } from '@tanstack/react-query'
import { Link } from 'react-router-dom'
import { dashboardApi } from '../api/dashboard'
import { PriorityPill } from '../layout/Badges'
import { usePageHeader } from '../layout/PageHeaderContext'
import { useAuth } from '../auth/AuthContext'

const CARDS: { key: keyof import('../api/types').QueueCounts; label: string; filter: string; borderClass: string; okLabel: string }[] = [
  { key: 'unassigned', label: 'Unassigned', filter: 'unassigned', borderClass: 'border-t-accent-blue', okLabel: 'Needs Triage' },
  { key: 'awaiting_user', label: 'Awaiting User', filter: 'awaiting_user', borderClass: 'border-t-accent-green', okLabel: 'Pending Requester' },
  { key: 'sla_breaching', label: 'SLA Breaching', filter: 'sla_breaching', borderClass: 'border-t-accent-red', okLabel: 'All Clear' },
  { key: 'resolved_today', label: 'Resolved Today', filter: 'resolved_today', borderClass: 'border-t-accent-orange', okLabel: 'Closed Loop' },
]

export function Dashboard() {
  const { user, aiEnabled } = useAuth()
  usePageHeader(`Command Overview // ${user?.tenant_name.toUpperCase()}`)
  const { data } = useQuery({ queryKey: ['dashboard'], queryFn: dashboardApi.get })

  return (
    <div>
      <div className="mb-4">
        <span
          className={`text-[10px] px-2 py-0.5 rounded uppercase border ${
            aiEnabled
              ? 'bg-accent-blue/15 text-accent-blue border-accent-blue/30'
              : 'bg-cortex-panel2 text-cortex-muted border-cortex-border'
          }`}
        >
          AI {aiEnabled ? 'Enabled' : 'Disabled'}
        </span>
      </div>

      <div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
        {CARDS.map((c) => {
          const count = data?.queue[c.key] ?? 0
          const breaching = c.key === 'sla_breaching' && count > 0
          return (
            <Link
              key={c.key}
              to={`/tickets?filter=${c.filter}`}
              className={`block border border-cortex-border bg-cortex-panel rounded-md p-4 border-t-2 ${c.borderClass} hover:bg-cortex-panel2 transition ${breaching ? 'shadow-[0_0_24px_rgba(255,59,92,0.25)]' : ''}`}
            >
              <div className="text-[10px] text-cortex-muted uppercase tracking-wider">{c.label}</div>
              <div className={`text-3xl font-display font-bold mt-1 ${breaching ? 'text-accent-red animate-pulse' : 'text-chrome'}`}>
                {count}
              </div>
              <div className={`text-[10px] uppercase tracking-wider mt-1 ${breaching ? 'text-accent-red' : 'text-cortex-muted'}`}>
                {breaching ? '▲ Action Required' : c.okLabel}
              </div>
            </Link>
          )
        })}
      </div>

      <div className="border border-cortex-border bg-cortex-panel rounded-md">
        <div className="flex items-center justify-between px-4 py-3 border-b border-cortex-border">
          <div className="text-[10px] text-cortex-muted uppercase tracking-wider">Recent Tickets</div>
          <Link to="/tickets" className="text-xs text-accent-blue hover:text-accent-blue-bright">
            View all 
          </Link>
        </div>
        <table className="w-full text-sm">
          <thead>
            <tr className="text-left text-[10px] text-cortex-muted uppercase tracking-wider border-b border-cortex-border">
              <th className="px-4 py-2">Ticket</th>
              <th className="px-4 py-2">Title</th>
              <th className="px-4 py-2">Priority</th>
              <th className="px-4 py-2">Status</th>
            </tr>
          </thead>
          <tbody>
            {(data?.tickets ?? []).map((t) => (
              <tr key={t.id} className="border-b border-cortex-border/60 hover:bg-cortex-panel2">
                <td className="px-4 py-2">
                  <Link to={`/tickets/${t.id}`} className="text-accent-blue hover:text-accent-blue-bright hover:underline">
                    {t.ticket_number}
                  </Link>
                </td>
                <td className="px-4 py-2">{t.title}</td>
                <td className="px-4 py-2">
                  <PriorityPill priority={t.priority} />
                </td>
                <td className="px-4 py-2 text-xs uppercase text-chrome">{t.status.replace('_', ' ')}</td>
              </tr>
            ))}
            {data && data.tickets.length === 0 && (
              <tr>
                <td colSpan={4} className="px-4 py-6 text-center text-cortex-muted text-xs">
                  No tickets logged yet.
                </td>
              </tr>
            )}
          </tbody>
        </table>
      </div>
    </div>
  )
}