599 lines
24 KiB
HTML
599 lines
24 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Complaint Workflow — PX360</title>
|
|
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
|
<style>
|
|
:root {
|
|
--navy: #0a1e3f;
|
|
--blue: #007bbd;
|
|
--light: #e8f1f8;
|
|
--slate: #64748b;
|
|
--green: #16a34a;
|
|
--red: #dc2626;
|
|
--amber: #d97706;
|
|
--purple: #7c3aed;
|
|
--teal: #0d9488;
|
|
}
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body {
|
|
font-family: 'Inter', sans-serif;
|
|
background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
|
|
color: #1e293b;
|
|
line-height: 1.6;
|
|
min-height: 100vh;
|
|
}
|
|
.header {
|
|
background: linear-gradient(135deg, var(--navy), var(--blue));
|
|
color: white;
|
|
padding: 3rem 2rem;
|
|
text-align: center;
|
|
}
|
|
.header h1 { font-size: 2rem; font-weight: 800; margin-bottom: 0.5rem; }
|
|
.header p { opacity: 0.85; font-size: 0.95rem; }
|
|
.container { max-width: 1100px; margin: 0 auto; padding: 2rem 1.5rem 4rem; }
|
|
|
|
.section-card {
|
|
background: white;
|
|
border-radius: 1rem;
|
|
padding: 2rem;
|
|
margin-bottom: 2rem;
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.04);
|
|
}
|
|
.section-card h2 {
|
|
font-size: 1.35rem;
|
|
font-weight: 700;
|
|
color: var(--navy);
|
|
margin-bottom: 0.25rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
.section-card .subtitle {
|
|
color: var(--slate);
|
|
font-size: 0.875rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
.section-card h3 {
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
color: var(--blue);
|
|
margin: 1.5rem 0 0.75rem;
|
|
}
|
|
|
|
.mermaid {
|
|
display: flex;
|
|
justify-content: center;
|
|
padding: 1rem 0;
|
|
}
|
|
.mermaid-wrapper {
|
|
background: #fafbfc;
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 0.75rem;
|
|
padding: 1.5rem;
|
|
overflow-x: auto;
|
|
}
|
|
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 0.875rem;
|
|
}
|
|
th {
|
|
text-align: left;
|
|
padding: 0.6rem 0.75rem;
|
|
background: var(--light);
|
|
color: var(--navy);
|
|
font-weight: 600;
|
|
border-bottom: 2px solid var(--blue);
|
|
}
|
|
td {
|
|
padding: 0.6rem 0.75rem;
|
|
border-bottom: 1px solid #e2e8f0;
|
|
vertical-align: top;
|
|
}
|
|
tr:hover td { background: #f8fafc; }
|
|
code {
|
|
background: #f1f5f9;
|
|
padding: 0.125rem 0.375rem;
|
|
border-radius: 0.25rem;
|
|
font-size: 0.8125rem;
|
|
color: var(--blue);
|
|
}
|
|
|
|
.badge {
|
|
display: inline-block;
|
|
padding: 0.125rem 0.5rem;
|
|
border-radius: 9999px;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
}
|
|
.badge-green { background: #dcfce7; color: #15803d; }
|
|
.badge-red { background: #fee2e2; color: #b91c1c; }
|
|
.badge-amber { background: #fef3c7; color: #b45309; }
|
|
.badge-blue { background: #dbeafe; color: #1e40af; }
|
|
|
|
.stepper {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: 0;
|
|
margin: 1.5rem 0;
|
|
position: relative;
|
|
}
|
|
.stepper::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 22px;
|
|
left: 5%;
|
|
right: 5%;
|
|
height: 3px;
|
|
background: #e2e8f0;
|
|
z-index: 0;
|
|
}
|
|
.step {
|
|
flex: 1;
|
|
text-align: center;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
.step-circle {
|
|
width: 44px;
|
|
height: 44px;
|
|
border-radius: 50%;
|
|
background: white;
|
|
border: 3px solid var(--blue);
|
|
color: var(--blue);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin: 0 auto 0.5rem;
|
|
font-weight: 700;
|
|
font-size: 1rem;
|
|
}
|
|
.step-label { font-size: 0.8125rem; font-weight: 600; color: var(--navy); }
|
|
.step-flag { font-size: 0.6875rem; color: var(--slate); margin-top: 0.125rem; }
|
|
|
|
.callout {
|
|
border-left: 4px solid;
|
|
border-radius: 0.5rem;
|
|
padding: 0.75rem 1rem;
|
|
margin: 1rem 0;
|
|
font-size: 0.875rem;
|
|
}
|
|
.callout-warn { border-color: var(--amber); background: #fffbeb; color: #92400e; }
|
|
.callout-info { border-color: var(--blue); background: #eff6ff; color: #1e40af; }
|
|
|
|
.sub-workflow-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
gap: 1rem;
|
|
margin-top: 1rem;
|
|
}
|
|
.sw-card {
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 0.75rem;
|
|
padding: 1.25rem;
|
|
background: #fafbfc;
|
|
}
|
|
.sw-card h4 {
|
|
font-size: 0.9375rem;
|
|
font-weight: 700;
|
|
color: var(--navy);
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
.sw-card .field { font-size: 0.75rem; color: var(--slate); margin-bottom: 0.5rem; }
|
|
.sw-states {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.25rem;
|
|
}
|
|
.sw-state {
|
|
background: white;
|
|
border: 1px solid #e2e8f0;
|
|
padding: 0.1875rem 0.5rem;
|
|
border-radius: 0.375rem;
|
|
font-size: 0.6875rem;
|
|
color: #475569;
|
|
}
|
|
.sw-arrow { color: var(--slate); font-size: 0.6875rem; }
|
|
|
|
.legend {
|
|
display: flex;
|
|
gap: 1rem;
|
|
flex-wrap: wrap;
|
|
margin-top: 1rem;
|
|
font-size: 0.8125rem;
|
|
}
|
|
.legend-item { display: flex; align-items: center; gap: 0.375rem; }
|
|
.legend-dot { width: 12px; height: 12px; border-radius: 50%; }
|
|
|
|
.tab-bar {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
margin-bottom: 1.5rem;
|
|
border-bottom: 2px solid #e2e8f0;
|
|
overflow-x: auto;
|
|
}
|
|
.tab-btn {
|
|
padding: 0.75rem 1rem;
|
|
border: none;
|
|
background: none;
|
|
cursor: pointer;
|
|
font-size: 0.875rem;
|
|
font-weight: 600;
|
|
color: var(--slate);
|
|
border-bottom: 2px solid transparent;
|
|
margin-bottom: -2px;
|
|
white-space: nowrap;
|
|
transition: all 0.2s;
|
|
}
|
|
.tab-btn.active { color: var(--blue); border-bottom-color: var(--blue); }
|
|
.tab-btn:hover { color: var(--navy); }
|
|
.tab-panel { display: none; }
|
|
.tab-panel.active { display: block; }
|
|
|
|
.footer {
|
|
text-align: center;
|
|
padding: 2rem;
|
|
color: var(--slate);
|
|
font-size: 0.8125rem;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="header">
|
|
<h1>Complaint Workflow Map</h1>
|
|
<p>Status state machine, stepper, entry points & sub-workflows — auto-extracted from source</p>
|
|
</div>
|
|
|
|
<div class="container">
|
|
|
|
<div class="tab-bar">
|
|
<button class="tab-btn active" onclick="switchTab(event, 'overview')">Overview</button>
|
|
<button class="tab-btn" onclick="switchTab(event, 'stepper')">Stepper</button>
|
|
<button class="tab-btn" onclick="switchTab(event, 'entrypoints')">Entry Points</button>
|
|
<button class="tab-btn" onclick="switchTab(event, 'subworkflows')">Sub-Workflows</button>
|
|
</div>
|
|
|
|
<!-- TAB 1: OVERVIEW -->
|
|
<div id="overview" class="tab-panel active">
|
|
|
|
<div class="section-card">
|
|
<h2>1. Status State Machine</h2>
|
|
<p class="subtitle">8 statuses defined in <code>ComplaintStatus</code> (<code>models.py:25</code>). Transition map enforced at <code>complaint_service.py:284</code> — only via <code>ComplaintService.change_status</code>. PX Admins bypass.</p>
|
|
|
|
<div class="mermaid-wrapper">
|
|
<pre class="mermaid">
|
|
stateDiagram-v2
|
|
[*] --> open
|
|
|
|
open --> in_progress : activate
|
|
open --> cancelled
|
|
|
|
in_progress --> partially_resolved
|
|
in_progress --> resolved
|
|
in_progress --> cancelled
|
|
in_progress --> pending_external
|
|
in_progress --> ovr_pending : escalate
|
|
|
|
partially_resolved --> resolved
|
|
partially_resolved --> in_progress
|
|
partially_resolved --> cancelled
|
|
partially_resolved --> pending_external
|
|
|
|
resolved --> closed
|
|
resolved --> in_progress : reopen
|
|
|
|
closed --> in_progress : reopen
|
|
|
|
cancelled --> open
|
|
cancelled --> in_progress
|
|
|
|
pending_external --> resolved
|
|
pending_external --> in_progress
|
|
pending_external --> cancelled
|
|
pending_external --> closed
|
|
|
|
ovr_pending --> in_progress : approve / reject
|
|
ovr_pending --> resolved
|
|
ovr_pending --> cancelled
|
|
|
|
closed --> [*]
|
|
</pre>
|
|
</div>
|
|
|
|
<h3>Transition Table</h3>
|
|
<table>
|
|
<thead>
|
|
<tr><th>From</th><th>Allowed Next Statuses</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr><td><code>open</code></td><td>in_progress, cancelled</td></tr>
|
|
<tr><td><code>in_progress</code></td><td>partially_resolved, resolved, cancelled, pending_external, ovr_pending</td></tr>
|
|
<tr><td><code>partially_resolved</code></td><td>resolved, in_progress, cancelled, pending_external</td></tr>
|
|
<tr><td><code>resolved</code></td><td>closed, in_progress</td></tr>
|
|
<tr><td><code>closed</code></td><td>in_progress</td></tr>
|
|
<tr><td><code>cancelled</code></td><td>open, in_progress</td></tr>
|
|
<tr><td><code>pending_external</code></td><td>resolved, in_progress, cancelled, closed</td></tr>
|
|
<tr><td><code>ovr_pending</code></td><td>in_progress, resolved, cancelled</td></tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<h3>Active vs Inactive (<code>is_active_status</code>)</h3>
|
|
<table>
|
|
<thead>
|
|
<tr><th>Active</th><th>Inactive</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>
|
|
<span class="badge badge-green">open</span>
|
|
<span class="badge badge-green">in_progress</span>
|
|
<span class="badge badge-green">partially_resolved</span>
|
|
<span class="badge badge-green">pending_external</span>
|
|
</td>
|
|
<td>
|
|
<span class="badge badge-red">resolved</span>
|
|
<span class="badge badge-red">closed</span>
|
|
<span class="badge badge-red">cancelled</span>
|
|
<span class="badge badge-amber">ovr_pending ⚠️</span>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<div class="callout callout-warn">
|
|
<strong>Inconsistency:</strong> <code>ovr_pending</code> is treated as <em>inactive</em> by <code>is_active_status</code> even though it's a mid-lifecycle state. Service methods guarding on active status would reject operations on OVR-pending complaints.
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- TAB 2: STEPPER -->
|
|
<div id="stepper" class="tab-panel">
|
|
|
|
<div class="section-card">
|
|
<h2>2. Detail Page Stepper</h2>
|
|
<p class="subtitle">6 ordered progress flags rendered inline in <code>complaint_detail.html:147</code>. Driven by a boolean dict built at <code>ui_views.py:745</code>. Hidden when status is <code>cancelled</code>.</p>
|
|
|
|
<div class="stepper">
|
|
<div class="step">
|
|
<div class="step-circle">1</div>
|
|
<div class="step-label">Created</div>
|
|
<div class="step-flag">always ✓</div>
|
|
</div>
|
|
<div class="step">
|
|
<div class="step-circle">2</div>
|
|
<div class="step-label">Activate</div>
|
|
<div class="step-flag">activated_at set</div>
|
|
</div>
|
|
<div class="step">
|
|
<div class="step-circle">3</div>
|
|
<div class="step-label">Taxonomy</div>
|
|
<div class="step-flag">taxonomy_reviewed_at</div>
|
|
</div>
|
|
<div class="step">
|
|
<div class="step-circle">4</div>
|
|
<div class="step-label">Send to Dept</div>
|
|
<div class="step-flag">sent_to_department</div>
|
|
</div>
|
|
<div class="step">
|
|
<div class="step-circle">5</div>
|
|
<div class="step-label">Response</div>
|
|
<div class="step-flag">dept responded</div>
|
|
</div>
|
|
<div class="step">
|
|
<div class="step-circle">6</div>
|
|
<div class="step-label">Resolve</div>
|
|
<div class="step-flag">resolved / closed</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h3>Step Details</h3>
|
|
<table>
|
|
<thead>
|
|
<tr><th>#</th><th>Step</th><th>Flag</th><th>Icon</th><th>Action when not done</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr><td>1</td><td><strong>Created</strong></td><td>always green</td><td><code>check</code></td><td>—</td></tr>
|
|
<tr><td>2</td><td><strong>Activate</strong></td><td><code>activated_at</code></td><td><code>play</code></td><td>"Activate this complaint" → <code>complaint_activate</code></td></tr>
|
|
<tr><td>3</td><td><strong>Taxonomy</strong></td><td><code>taxonomy_reviewed_at</code></td><td><code>clipboard-check</code></td><td>"Review taxonomy" → <code>confirm_taxonomy</code></td></tr>
|
|
<tr><td>4</td><td><strong>Send to Dept</strong></td><td><code>sent_to_department</code> / <code>forwarded_to_dept_at</code></td><td><code>send</code></td><td>"Send to department" modal</td></tr>
|
|
<tr><td>5</td><td><strong>Response</strong></td><td>explanation used OR dept responded</td><td><code>message-square</code></td><td>"Awaiting department response"</td></tr>
|
|
<tr><td>6</td><td><strong>Resolve</strong></td><td><code>status in (resolved, closed)</code></td><td><code>check-circle-2</code></td><td>"Generate Resolution"</td></tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<div class="callout callout-info">
|
|
The stepper is <strong>flag-driven, not status-driven</strong>. E.g. "resolved" lights up green whenever <code>status ∈ (resolved, closed)</code>, regardless of path taken.
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- TAB 3: ENTRY POINTS -->
|
|
<div id="entrypoints" class="tab-panel">
|
|
|
|
<div class="section-card">
|
|
<h2>3. Status-Mutating Entry Points</h2>
|
|
<p class="subtitle">Code paths that modify <code>complaint.status</code>. Only <code>ComplaintService.change_status</code> enforces the transition map — the rest set status directly.</p>
|
|
|
|
<table>
|
|
<thead>
|
|
<tr><th>Action</th><th>Location</th><th>Transition enforced?</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr><td><strong>Generic change status</strong></td><td><code>ui_views.py:1365</code></td><td><span class="badge badge-green">Yes</span></td></tr>
|
|
<tr><td>Activate (open→in_progress)</td><td><code>ui_views.py:2125</code></td><td><span class="badge badge-green">service guard</span></td></tr>
|
|
<tr><td>Assign / reopen-on-assign</td><td><code>ui_views.py:851</code></td><td><span class="badge badge-green">service guard</span></td></tr>
|
|
<tr><td>Reopen as new complaint</td><td><code>ui_views.py:1777</code></td><td><span class="badge badge-green">service guard</span></td></tr>
|
|
<tr><td>OVR toggle</td><td><code>ui_views.py:1549</code></td><td><span class="badge badge-red">direct</span></td></tr>
|
|
<tr><td>OVR approve</td><td><code>ui_views.py:1640</code></td><td><span class="badge badge-red">direct</span></td></tr>
|
|
<tr><td>OVR reject</td><td><code>ui_views.py:1755</code></td><td><span class="badge badge-red">direct</span></td></tr>
|
|
<tr><td>API auto-resolve</td><td><code>views.py:2040</code></td><td><span class="badge badge-red">direct</span></td></tr>
|
|
<tr><td>Convert to appreciation</td><td><code>views.py:2198</code></td><td><span class="badge badge-red">direct</span></td></tr>
|
|
<tr><td>Meeting auto-resolve</td><td><code>views.py:3279</code></td><td><span class="badge badge-red">direct</span></td></tr>
|
|
<tr><td>Bulk status change</td><td><code>utils.py:263</code></td><td><span class="badge badge-red">direct</span></td></tr>
|
|
<tr><td><code>close_stale_complaints</code> cmd</td><td><code>close_stale_complaints.py:113</code></td><td><span class="badge badge-red">direct (bulk UPDATE)</span></td></tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<div class="callout callout-warn">
|
|
<strong>7 code paths bypass the transition map.</strong> This means invalid transitions can occur via OVR views, bulk operations, API auto-resolve, convert-to-appreciation, meeting-resolve, and the stale-close command.
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- TAB 4: SUB-WORKFLOWS -->
|
|
<div id="subworkflows" class="tab-panel">
|
|
|
|
<div class="section-card">
|
|
<h2>4. Sub-Workflows</h2>
|
|
<p class="subtitle">Independent state machines attached to a complaint, each with its own field/model.</p>
|
|
|
|
<div class="mermaid-wrapper" style="margin-bottom: 1.5rem;">
|
|
<pre class="mermaid">
|
|
graph TD
|
|
C[COMPLAINT<br/>status: 8 states]
|
|
|
|
C --> DR["Dept Routing<br/>routing_status"]
|
|
C --> CI["Champion Investigation<br/>status"]
|
|
C --> MR["Manager Review<br/>manager_review_status"]
|
|
C --> PC["Patient Contact<br/>patient_contact_status"]
|
|
C --> SA["Satisfaction<br/>satisfaction"]
|
|
C --> AA["Adverse Action<br/>verification_status"]
|
|
|
|
DR --> |sent| DR2["accepted / rejected"]
|
|
CI --> |questions_sent| CI2["answers_received"]
|
|
CI2 --> CI3["reply_submitted / direct_reply_in_progress"]
|
|
MR --> |pending| MR2["approved / rejected"]
|
|
PC --> |not_contacted| PC2["contacted / no_response"]
|
|
SA --> SA2["satisfied / neutral / dissatisfied / no_response"]
|
|
AA --> |reported| AA2["under_investigation"]
|
|
AA2 --> AA3["verified / unfounded / resolved"]
|
|
|
|
style C fill:#0a1e3f,color:#fff,stroke:none
|
|
style DR fill:#dbeafe,stroke:#007bbd
|
|
style CI fill:#dcfce7,stroke:#16a34a
|
|
style MR fill:#fef3c7,stroke:#d97706
|
|
style PC fill:#fce7f3,stroke:#db2777
|
|
style SA fill:#ede9fe,stroke:#7c3aed
|
|
style AA fill:#fee2e2,stroke:#dc2626
|
|
</pre>
|
|
</div>
|
|
|
|
<div class="sub-workflow-grid">
|
|
<div class="sw-card">
|
|
<h4>Department Routing</h4>
|
|
<div class="field"><code>ComplaintInvolvedDepartment.routing_status</code></div>
|
|
<div class="sw-states">
|
|
<span class="sw-state">sent</span>
|
|
<span class="sw-arrow">→</span>
|
|
<span class="sw-state">accepted</span>
|
|
<span class="sw-state">rejected</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sw-card">
|
|
<h4>Champion Investigation</h4>
|
|
<div class="field"><code>ChampionInvestigation.status</code></div>
|
|
<div class="sw-states">
|
|
<span class="sw-state">questions_sent</span>
|
|
<span class="sw-arrow">→</span>
|
|
<span class="sw-state">answers_received</span>
|
|
<span class="sw-arrow">→</span>
|
|
<span class="sw-state">reply_submitted</span>
|
|
<span class="sw-state">direct_reply_in_progress</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sw-card">
|
|
<h4>Manager Review</h4>
|
|
<div class="field"><code>ComplaintInvolvedDepartment.manager_review_status</code></div>
|
|
<div class="sw-states">
|
|
<span class="sw-state">pending</span>
|
|
<span class="sw-arrow">→</span>
|
|
<span class="sw-state">approved</span>
|
|
<span class="sw-state">rejected</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sw-card">
|
|
<h4>Patient Contact</h4>
|
|
<div class="field"><code>Complaint.patient_contact_status</code></div>
|
|
<div class="sw-states">
|
|
<span class="sw-state">not_contacted</span>
|
|
<span class="sw-arrow">→</span>
|
|
<span class="sw-state">contacted</span>
|
|
<span class="sw-state">contacted_no_response</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sw-card">
|
|
<h4>Satisfaction</h4>
|
|
<div class="field"><code>Complaint.satisfaction</code></div>
|
|
<div class="sw-states">
|
|
<span class="sw-state">satisfied</span>
|
|
<span class="sw-state">neutral</span>
|
|
<span class="sw-state">dissatisfied</span>
|
|
<span class="sw-state">no_response</span>
|
|
</div>
|
|
<div class="field" style="margin-top:0.5rem;">🔒 Locked by patient submission · max 3 PX-team changes · 5-day window</div>
|
|
</div>
|
|
|
|
<div class="sw-card">
|
|
<h4>Adverse Action</h4>
|
|
<div class="field"><code>ComplaintAdverseAction.verification_status</code></div>
|
|
<div class="sw-states">
|
|
<span class="sw-state">reported</span>
|
|
<span class="sw-arrow">→</span>
|
|
<span class="sw-state">under_investigation</span>
|
|
<span class="sw-arrow">→</span>
|
|
<span class="sw-state">verified</span>
|
|
<span class="sw-state">unfounded</span>
|
|
<span class="sw-state">resolved</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="footer">
|
|
Generated from source · apps/complaints/ · PX360
|
|
</div>
|
|
|
|
<script>
|
|
mermaid.initialize({
|
|
startOnLoad: true,
|
|
theme: 'base',
|
|
themeVariables: {
|
|
primaryColor: '#e8f1f8',
|
|
primaryTextColor: '#0a1e3f',
|
|
primaryBorderColor: '#007bbd',
|
|
lineColor: '#64748b',
|
|
fontSize: '14px',
|
|
fontFamily: 'Inter, sans-serif',
|
|
},
|
|
flowchart: { curve: 'basis', padding: 20 },
|
|
stateDiagram: { backgroundColor: '#fafbfc' },
|
|
});
|
|
|
|
function switchTab(e, tabId) {
|
|
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
|
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
|
|
e.currentTarget.classList.add('active');
|
|
document.getElementById(tabId).classList.add('active');
|
|
}
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|