cryogenic22 commited on
Commit
c574fb0
·
verified ·
1 Parent(s): ddc48a1

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1045 -8
index.html CHANGED
@@ -1,6 +1,5 @@
1
  <!DOCTYPE html>
2
  <html lang="en">
3
- <!-- Update in the <head> section of index.html -->
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -9,13 +8,11 @@
9
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/lucide-static@latest/font/Lucide.css">
10
  <script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
11
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
12
- <!-- Use the enhanced styles file directly, not the .txt version -->
13
- <link rel="stylesheet" href="enhanced_styles.css">
14
  <style>
15
- /* Base styles that complement enhanced_styles.css */
16
  body {
17
  font-family: 'Inter', sans-serif;
18
- background-color: #f9fafb; /* gray-50 */
19
  }
20
  .doc-link, .flow-link { cursor: pointer; color: #2563eb; text-decoration: underline; font-weight: 500; }
21
  .doc-link:hover, .flow-link:hover { color: #1d4ed8; }
@@ -41,9 +38,116 @@
41
  .mermaid text, .mermaid tspan { user-select: text !important; -webkit-user-select: text !important; -moz-user-select: text !important; -ms-user-select: text !important; }
42
  .mermaid .node { cursor: pointer; }
43
  .hidden-container { display: none !important; }
 
44
  /* Dashboard Card Specific Styles */
45
  .dashboard-card { transition: all 0.3s ease; }
46
- .dashboard-card:hover { transform: translateY(-5px); box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); } /* Tailwind shadow-lg */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  /* Text truncation utility */
48
  .line-clamp-2 {
49
  display: -webkit-box;
@@ -71,7 +175,9 @@
71
  <input type="text" id="headerSearchInput" placeholder="Search all docs..." class="bg-blue-500 placeholder-blue-200 text-white rounded-full py-1.5 px-4 pl-10 text-sm focus:outline-none focus:ring-2 focus:ring-white">
72
  <i class="lucide lucide-search absolute left-3 top-1/2 transform -translate-y-1/2 text-blue-200"></i>
73
  </div>
74
- <button class="text-white hover:text-blue-200"><i class="lucide lucide-settings text-xl"></i></button>
 
 
75
  </div>
76
  </header>
77
 
@@ -196,7 +302,938 @@
196
  </div>
197
  </div>
198
 
199
- <script src="script.js"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  </body>
202
  </html>
 
1
  <!DOCTYPE html>
2
  <html lang="en">
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
8
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/lucide-static@latest/font/Lucide.css">
9
  <script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
10
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
 
 
11
  <style>
12
+ /* Base styles */
13
  body {
14
  font-family: 'Inter', sans-serif;
15
+ background-color: #f9fafb;
16
  }
17
  .doc-link, .flow-link { cursor: pointer; color: #2563eb; text-decoration: underline; font-weight: 500; }
18
  .doc-link:hover, .flow-link:hover { color: #1d4ed8; }
 
38
  .mermaid text, .mermaid tspan { user-select: text !important; -webkit-user-select: text !important; -moz-user-select: text !important; -ms-user-select: text !important; }
39
  .mermaid .node { cursor: pointer; }
40
  .hidden-container { display: none !important; }
41
+
42
  /* Dashboard Card Specific Styles */
43
  .dashboard-card { transition: all 0.3s ease; }
44
+ .dashboard-card:hover { transform: translateY(-5px); box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); }
45
+
46
+ /* Document item enhancements */
47
+ .document-item {
48
+ transition: all 0.3s ease;
49
+ border-left: 4px solid transparent;
50
+ }
51
+
52
+ .document-item:hover {
53
+ transform: translateX(5px);
54
+ background-color: #f8fafc;
55
+ }
56
+
57
+ /* Phase-specific colors */
58
+ .document-item[data-phase="Discovery"] { border-left-color: #3b82f6; }
59
+ .document-item[data-phase="Preclinical"] { border-left-color: #10b981; }
60
+ .document-item[data-phase="Clinical Phase 1"] { border-left-color: #6366f1; }
61
+ .document-item[data-phase="Clinical Phase 2"] { border-left-color: #8b5cf6; }
62
+ .document-item[data-phase="Clinical Phase 3"] { border-left-color: #a855f7; }
63
+ .document-item[data-phase="Clinical (All Phases)"] { border-left-color: #7c3aed; }
64
+ .document-item[data-phase="Regulatory Submission"] { border-left-color: #ec4899; }
65
+ .document-item[data-phase="Post-Marketing"] { border-left-color: #f59e0b; }
66
+
67
+ /* Tab styles for document detail modal */
68
+ .details-tabs {
69
+ display: flex;
70
+ border-bottom: 1px solid #e5e7eb;
71
+ margin-bottom: 1rem;
72
+ }
73
+
74
+ .details-tab {
75
+ padding: 0.5rem 1rem;
76
+ cursor: pointer;
77
+ border-bottom: 3px solid transparent;
78
+ font-weight: 500;
79
+ transition: all 0.2s ease;
80
+ }
81
+
82
+ .details-tab.active {
83
+ border-bottom-color: #3b82f6;
84
+ color: #1e40af;
85
+ }
86
+
87
+ .details-tab:hover:not(.active) {
88
+ border-bottom-color: #e5e7eb;
89
+ }
90
+
91
+ .details-content {
92
+ display: none;
93
+ }
94
+
95
+ .details-content.active {
96
+ display: block;
97
+ animation: fadeIn 0.3s ease;
98
+ }
99
+
100
+ @keyframes fadeIn {
101
+ from { opacity: 0; }
102
+ to { opacity: 1; }
103
+ }
104
+
105
+ /* Dependency graph styles */
106
+ .dependency-graph {
107
+ width: 100%;
108
+ height: 400px;
109
+ border: 1px solid #e5e7eb;
110
+ border-radius: 0.375rem;
111
+ overflow: hidden;
112
+ }
113
+
114
+ /* Flow card styles */
115
+ .flow-card {
116
+ cursor: pointer;
117
+ transition: all 0.3s ease;
118
+ border: 1px solid #e5e7eb;
119
+ border-radius: 0.375rem;
120
+ padding: 1rem;
121
+ margin-bottom: 1rem;
122
+ background-color: white;
123
+ }
124
+
125
+ .flow-card:hover {
126
+ transform: translateY(-3px);
127
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
128
+ border-color: #3b82f6;
129
+ }
130
+
131
+ .flow-card.active {
132
+ border-color: #3b82f6;
133
+ background-color: #eff6ff;
134
+ }
135
+
136
+ /* Loading spinner */
137
+ .loading-spinner {
138
+ width: 40px;
139
+ height: 40px;
140
+ margin: 2rem auto;
141
+ border-radius: 50%;
142
+ border: 4px solid #e5e7eb;
143
+ border-top-color: #3b82f6;
144
+ animation: spin 1s linear infinite;
145
+ }
146
+
147
+ @keyframes spin {
148
+ to { transform: rotate(360deg); }
149
+ }
150
+
151
  /* Text truncation utility */
152
  .line-clamp-2 {
153
  display: -webkit-box;
 
175
  <input type="text" id="headerSearchInput" placeholder="Search all docs..." class="bg-blue-500 placeholder-blue-200 text-white rounded-full py-1.5 px-4 pl-10 text-sm focus:outline-none focus:ring-2 focus:ring-white">
176
  <i class="lucide lucide-search absolute left-3 top-1/2 transform -translate-y-1/2 text-blue-200"></i>
177
  </div>
178
+ <button id="showExampleFlowBtn" class="bg-white text-blue-700 hover:bg-blue-50 font-semibold py-2 px-4 rounded-md shadow text-sm flex items-center">
179
+ <i class="lucide lucide-git-fork"></i> Show Example Flow
180
+ </button>
181
  </div>
182
  </header>
183
 
 
302
  </div>
303
  </div>
304
 
305
+ <div id="loadingSpinner" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
306
+ <div class="bg-white p-8 rounded-lg shadow-lg flex flex-col items-center">
307
+ <div class="w-16 h-16 border-4 border-blue-500 border-t-transparent rounded-full animate-spin mb-4"></div>
308
+ <p class="text-gray-700 font-medium">Loading DocMap Agent...</p>
309
+ </div>
310
+ </div>
311
+
312
+ <script>
313
+ // Initialize Mermaid
314
+ mermaid.initialize({
315
+ startOnLoad: false,
316
+ theme: 'base',
317
+ securityLevel: 'loose',
318
+ themeVariables: {
319
+ primaryColor: '#eff6ff',
320
+ primaryTextColor: '#1e3a8a',
321
+ primaryBorderColor: '#60a5fa',
322
+ lineColor: '#6b7280',
323
+ secondaryColor: '#f1f5f9',
324
+ tertiaryColor: '#e0f2fe'
325
+ }
326
+ });
327
+
328
+ // --- Global Variables ---
329
+ let documentsData = [];
330
+ let templateData = [];
331
+ const flowDefinitions = {
332
+ "p1_sad": `graph TD; subgraph Preclinical & Setup; IB(IB v1):::input --> CLI-PROT-P1(Phase 1 Protocol):::core; PRE-REP-TOX(Tox Report):::input --> IB; PRE-REP-PK(PK Report):::input --> IB; PRE-REP-CMC-STAB(Stability Report):::input --> IB; CLI-PROT-P1 --> REG-SUB-IND(IND / CTA):::output; CLI-PROT-P1 --> ICF(Informed Consent Form):::output; CLI-PROT-P1 --> CRF(eCRF Spec):::output; CLI-PROT-P1 --> CLI-PLAN-SAP(Stat Analysis Plan):::output; CLI-PROT-P1 --> CLI-PLAN-DMP(Data Mgt Plan):::output; CLI-PROT-P1 --> CLI-MAN-IMPHANDLE(IMP Handling Manual):::output; CLI-PROT-P1 --> CMC-LABEL-IMP(IMP Label Spec):::output; end; subgraph Execution & Reporting; ICF --> SiteOps[Site Operations / Enrollment]; CRF --> SiteOps; CLI-MAN-IMPHANDLE --> SiteOps; CMC-LABEL-IMP --> SiteOps; CLI-PLAN-DMP --> SiteOps; SiteOps --> ClinicalData[(Clinical Database)]; CLI-PLAN-SAP --> Analysis[Statistical Analysis]; ClinicalData --> Analysis; Analysis --> CLI-REP-CSR(Phase 1 CSR):::core; ClinicalData --> CLI-REP-CSR; IB --> CLI-REP-CSR; end; subgraph Updates & Follow-on; CLI-REP-CSR --> IB_v2(IB Update v2):::output; CLI-REP-CSR --> REG-AR(IND Annual Report / DSUR):::output; CLI-REP-CSR --> CLI-PLAN-CDP(Clinical Dev Plan Update):::output; end; classDef input fill:#f3e8ff,stroke:#a855f7,color:#581c87; classDef core fill:#e0f2fe,stroke:#38bdf8,color:#075985; classDef output fill:#f0fdf4,stroke:#4ade80,color:#15803d; click IB call displayDetailsAndGraphFromGraph("IB") "View Details"; click PRE-REP-TOX call displayDetailsAndGraphFromGraph("PRE-REP-TOX") "View Details"; click PRE-REP-PK call displayDetailsAndGraphFromGraph("PRE-REP-PK") "View Details"; click PRE-REP-CMC-STAB call displayDetailsAndGraphFromGraph("PRE-REP-CMC-STAB") "View Details"; click CLI-PROT-P1 call displayDetailsAndGraphFromGraph("CLI-PROT-P1") "View Details"; click REG-SUB-IND call displayDetailsAndGraphFromGraph("REG-SUB-IND") "View Details"; click ICF call displayDetailsAndGraphFromGraph("ICF") "View Details"; click CRF call displayDetailsAndGraphFromGraph("CRF") "View Details"; click CLI-PLAN-SAP call displayDetailsAndGraphFromGraph("CLI-PLAN-SAP") "View Details"; click CLI-PLAN-DMP call displayDetailsAndGraphFromGraph("CLI-PLAN-DMP") "View Details"; click CLI-MAN-IMPHANDLE call displayDetailsAndGraphFromGraph("CLI-MAN-IMPHANDLE") "View Details"; click CMC-LABEL-IMP call displayDetailsAndGraphFromGraph("CMC-LABEL-IMP") "View Details"; click CLI-REP-CSR call displayDetailsAndGraphFromGraph("CLI-REP-CSR") "View Details"; click IB_v2 call displayDetailsAndGraphFromGraph("IB") "View Details (Latest IB)"; click REG-AR call displayDetailsAndGraphFromGraph("REG-AR") "View Details"; click CLI-PLAN-CDP call displayDetailsAndGraphFromGraph("CLI-PLAN-CDP") "View Details";`,
333
+ "nda_submission": `graph TD; subgraph Inputs; CSRs(All Phase 1-3 CSRs):::input --> REG-ISS(ISS):::core; CSRs --> REG-ISE(ISE):::core; NonClinReps(All Nonclinical Reports):::input --> REG-CTD-M2(CTD Module 2 Summaries):::core; CMCDataPkg(Full CMC Data Package):::input --> REG-CTD-M3(CTD Module 3 Quality):::core; ProposedLabel(Proposed Label / SmPC):::input --> REG-CTD-M1(CTD Module 1 Admin & Label):::core; end; subgraph CTD_Assembly; REG-ISS --> REG-CTD-M5(CTD Module 5 Clinical):::output; REG-ISE --> REG-CTD-M5; CSRs --> REG-CTD-M5; NonClinReps --> REG-CTD-M4(CTD Module 4 Nonclinical):::output; REG-CTD-M1 --> FullSubmission[eCTD Submission Package]; REG-CTD-M2 --> FullSubmission; REG-CTD-M3 --> FullSubmission; REG-CTD-M4 --> FullSubmission; REG-CTD-M5 --> FullSubmission; end; subgraph Submission_Output; FullSubmission --> REG-SUB-NDA(NDA / MAA Submission):::final; REG-SUB-NDA --> AgencyReview{Agency Review}; AgencyReview --> REG-RTQ(Responses to Questions):::input; REG-RTQ --> AgencyReview; AgencyReview --> ApprovalDecision[Approval / Rejection]; end; classDef input fill:#fef9c3,stroke:#eab308,color:#854d0e; classDef core fill:#e0f2fe,stroke:#38bdf8,color:#075985; classDef output fill:#f0fdf4,stroke:#4ade80,color:#15803d; classDef final fill:#fee2e2,stroke:#f87171,color:#991b1b; click CSRs call displayDetailsAndGraphFromGraph("CLI-REP-CSR") "View CSR Details (Example)"; click NonClinReps call displayDetailsAndGraphFromGraph("PRE-REP-TOX") "View Tox Report (Example)"; click CMCDataPkg call displayDetailsAndGraphFromGraph("PRE-REP-CMC-PROCDEV") "View CMC Report (Example)"; click ProposedLabel call displayDetailsAndGraphFromGraph("REG-LABEL-US") "View Label Details (Example)"; click REG-ISS call displayDetailsAndGraphFromGraph("REG-ISS") "View Details"; click REG-ISE call displayDetailsAndGraphFromGraph("REG-ISE") "View Details"; click REG-CTD-M1 call displayDetailsAndGraphFromGraph("REG-CTD-M1") "View Details"; click REG-CTD-M2 call displayDetailsAndGraphFromGraph("REG-CTD-M2") "View Details"; click REG-CTD-M3 call displayDetailsAndGraphFromGraph("REG-CTD-M3") "View Details"; click REG-CTD-M4 call displayDetailsAndGraphFromGraph("REG-CTD-M4") "View Details"; click REG-CTD-M5 call displayDetailsAndGraphFromGraph("REG-CTD-M5") "View Details"; click REG-SUB-NDA call displayDetailsAndGraphFromGraph("REG-SUB-NDA") "View Details"; click REG-RTQ call displayDetailsAndGraphFromGraph("REG-RTQ") "View Details";`,
334
+ "ind_pathway": `graph TD;
335
+ DIS-REP-TVAL(Target Validation Report):::discovery --> DIS-REP-LO(Lead Optimization Report):::discovery;
336
+ DIS-REP-LO --> DIS-REP-CANDSEL(Candidate Selection Report):::discovery;
337
+ DIS-REP-CANDSEL --> PRE-PLAN-DEV(Preclinical Development Plan):::preclinical;
338
+ PRE-PLAN-DEV --> PRE-PROT-TOX(Toxicology Study Protocol):::preclinical;
339
+ PRE-PLAN-DEV --> PRE-PROT-PK(PK Study Protocol):::preclinical;
340
+ PRE-PLAN-DEV --> PRE-REP-CMC-PROCDEV(CMC Process Development):::preclinical;
341
+ PRE-PROT-TOX --> PRE-REP-TOX(Toxicology Study Report):::preclinical;
342
+ PRE-PROT-PK --> PRE-REP-PK(PK Study Report):::preclinical;
343
+ PRE-REP-CMC-PROCDEV --> PRE-REP-CMC-STAB(Stability Report):::preclinical;
344
+ PRE-REP-TOX --> IB(Investigator's Brochure):::clinical;
345
+ PRE-REP-PK --> IB;
346
+ PRE-REP-CMC-STAB --> IB;
347
+ IB --> REG-SUB-IND(IND Submission):::regulatory;
348
+ IB --> CLI-PROT-P1(Phase 1 Protocol):::clinical;
349
+ CLI-PROT-P1 --> REG-SUB-IND;
350
+ REG-SUB-IND --> CLI-REP-CSR(Clinical Study Reports):::clinical;
351
+
352
+ classDef discovery fill:#dbeafe,stroke:#3b82f6,color:#1e40af;
353
+ classDef preclinical fill:#dcfce7,stroke:#22c55e,color:#166534;
354
+ classDef clinical fill:#ede9fe,stroke:#8b5cf6,color:#5b21b6;
355
+ classDef regulatory fill:#fef3c7,stroke:#f59e0b,color:#92400e;
356
+
357
+ click DIS-REP-TVAL call displayDetailsAndGraphFromGraph("DIS-REP-TVAL") "View Details";
358
+ click DIS-REP-LO call displayDetailsAndGraphFromGraph("DIS-REP-LO") "View Details";
359
+ click DIS-REP-CANDSEL call displayDetailsAndGraphFromGraph("DIS-REP-CANDSEL") "View Details";
360
+ click PRE-PLAN-DEV call displayDetailsAndGraphFromGraph("PRE-PLAN-DEV") "View Details";
361
+ click PRE-PROT-TOX call displayDetailsAndGraphFromGraph("PRE-PROT-TOX") "View Details";
362
+ click PRE-PROT-PK call displayDetailsAndGraphFromGraph("PRE-PROT-PK") "View Details";
363
+ click PRE-REP-CMC-PROCDEV call displayDetailsAndGraphFromGraph("PRE-REP-CMC-PROCDEV") "View Details";
364
+ click PRE-REP-TOX call displayDetailsAndGraphFromGraph("PRE-REP-TOX") "View Details";
365
+ click PRE-REP-PK call displayDetailsAndGraphFromGraph("PRE-REP-PK") "View Details";
366
+ click PRE-REP-CMC-STAB call displayDetailsAndGraphFromGraph("PRE-REP-CMC-STAB") "View Details";
367
+ click IB call displayDetailsAndGraphFromGraph("IB") "View Details";
368
+ click CLI-PROT-P1 call displayDetailsAndGraphFromGraph("CLI-PROT-P1") "View Details";
369
+ click REG-SUB-IND call displayDetailsAndGraphFromGraph("REG-SUB-IND") "View Details";
370
+ click CLI-REP-CSR call displayDetailsAndGraphFromGraph("CLI-REP-CSR") "View Details";`,
371
+ "clinical_program": `graph TD;
372
+ CLI-PLAN-TPP(Target Product Profile):::planning --> CLI-PLAN-CDP(Clinical Development Plan):::planning;
373
+ CLI-PLAN-CDP --> CLI-PROT-P1(Phase 1 Protocol):::phase1;
374
+ CLI-PLAN-CDP --> CLI-PROT-P2(Phase 2 Protocol):::phase2;
375
+ CLI-PLAN-CDP --> CLI-PROT-P3(Phase 3 Protocol):::phase3;
376
+ CLI-PROT-P1 --> ICF1(Phase 1 ICF):::phase1;
377
+ CLI-PROT-P1 --> CRF1(Phase 1 CRF):::phase1;
378
+ CLI-PROT-P1 --> CLI-PLAN-SAP1(Phase 1 SAP):::phase1;
379
+ CLI-PROT-P2 --> ICF2(Phase 2 ICF):::phase2;
380
+ CLI-PROT-P2 --> CRF2(Phase 2 CRF):::phase2;
381
+ CLI-PROT-P2 --> CLI-PLAN-SAP2(Phase 2 SAP):::phase2;
382
+ CLI-PROT-P3 --> ICF3(Phase 3 ICF):::phase3;
383
+ CLI-PROT-P3 --> CRF3(Phase 3 CRF):::phase3;
384
+ CLI-PROT-P3 --> CLI-PLAN-SAP3(Phase 3 SAP):::phase3;
385
+ CLI-PROT-P3 --> CLI-CHARTER-DMC(DMC Charter):::phase3;
386
+ CLI-PLAN-SAP1 --> CLI-REP-CSR1(Phase 1 CSR):::phase1;
387
+ CLI-PLAN-SAP2 --> CLI-REP-CSR2(Phase 2 CSR):::phase2;
388
+ CLI-PLAN-SAP3 --> CLI-REP-CSR3(Phase 3 CSR):::phase3;
389
+ CLI-REP-CSR1 & CLI-REP-CSR2 & CLI-REP-CSR3 --> REG-ISS(Integrated Summary of Safety):::submission;
390
+ CLI-REP-CSR2 & CLI-REP-CSR3 --> REG-ISE(Integrated Summary of Efficacy):::submission;
391
+ REG-ISS & REG-ISE --> REG-SUB-NDA(NDA Submission):::submission;
392
+
393
+ classDef planning fill:#dbeafe,stroke:#3b82f6,color:#1e40af;
394
+ classDef phase1 fill:#ede9fe,stroke:#8b5cf6,color:#5b21b6;
395
+ classDef phase2 fill:#fae8ff,stroke:#d946ef,color:#86198f;
396
+ classDef phase3 fill:#fce7f3,stroke:#ec4899,color:#9d174d;
397
+ classDef submission fill:#fee2e2,stroke:#f87171,color:#991b1b;
398
+
399
+ click CLI-PLAN-TPP call displayDetailsAndGraphFromGraph("CLI-PLAN-TPP") "View Details";
400
+ click CLI-PLAN-CDP call displayDetailsAndGraphFromGraph("CLI-PLAN-CDP") "View Details";
401
+ click CLI-PROT-P1 call displayDetailsAndGraphFromGraph("CLI-PROT-P1") "View Details";
402
+ click CLI-PROT-P2 call displayDetailsAndGraphFromGraph("CLI-PROT-P2") "View Details";
403
+ click CLI-PROT-P3 call displayDetailsAndGraphFromGraph("CLI-PROT-P3") "View Details";
404
+ click ICF1 call displayDetailsAndGraphFromGraph("ICF") "View Details";
405
+ click CRF1 call displayDetailsAndGraphFromGraph("CRF") "View Details";
406
+ click CLI-PLAN-SAP1 call displayDetailsAndGraphFromGraph("CLI-PLAN-SAP") "View Details";
407
+ click ICF2 call displayDetailsAndGraphFromGraph("ICF") "View Details";
408
+ click CRF2 call displayDetailsAndGraphFromGraph("CRF") "View Details";
409
+ click CLI-PLAN-SAP2 call displayDetailsAndGraphFromGraph("CLI-PLAN-SAP") "View Details";
410
+ click ICF3 call displayDetailsAndGraphFromGraph("ICF") "View Details";
411
+ click CRF3 call displayDetailsAndGraphFromGraph("CRF") "View Details";
412
+ click CLI-PLAN-SAP3 call displayDetailsAndGraphFromGraph("CLI-PLAN-SAP") "View Details";
413
+ click CLI-CHARTER-DMC call displayDetailsAndGraphFromGraph("CLI-CHARTER-DMC") "View Details";
414
+ click CLI-REP-CSR1 call displayDetailsAndGraphFromGraph("CLI-REP-CSR") "View Details";
415
+ click CLI-REP-CSR2 call displayDetailsAndGraphFromGraph("CLI-REP-CSR") "View Details";
416
+ click CLI-REP-CSR3 call displayDetailsAndGraphFromGraph("CLI-REP-CSR") "View Details";
417
+ click REG-ISS call displayDetailsAndGraphFromGraph("REG-ISS") "View Details";
418
+ click REG-ISE call displayDetailsAndGraphFromGraph("REG-ISE") "View Details";
419
+ click REG-SUB-NDA call displayDetailsAndGraphFromGraph("REG-SUB-NDA") "View Details";`
420
+ };
421
+
422
+ // --- DOM Elements Cache ---
423
+ const mainContentArea = document.getElementById('mainContentArea');
424
+ const homeSection = document.getElementById('home');
425
+ const documentViewWrapper = document.getElementById('documentViewWrapper');
426
+ const flowsViewWrapper = document.getElementById('flowsViewWrapper');
427
+
428
+ const searchInputDocView = document.getElementById('searchInputDocView');
429
+ const headerSearchInput = document.getElementById('headerSearchInput');
430
+ const mainTabsDocViewContainer = document.getElementById('mainTabsDocView');
431
+ const documentListDocViewContainer = document.getElementById('documentListDocView');
432
+
433
+ const flowsListContainer = document.getElementById('flowsList');
434
+ const mermaidFlowGraphContainer = document.getElementById('mermaidFlowGraph');
435
+ const flowPlaceholder = document.getElementById('flowPlaceholder');
436
+ const showExampleFlowBtnFlowView = document.getElementById('showExampleFlowBtnFlowView');
437
+
438
+ const homeButton = document.getElementById('homeButton');
439
+ const breadcrumbNav = document.getElementById('breadcrumbNav');
440
+
441
+ // Modals
442
+ const showExampleFlowBtnHeader = document.getElementById('showExampleFlowBtn');
443
+ const exampleFlowModal = document.getElementById('exampleFlowModal');
444
+ const exampleMermaidGraphContainer = document.getElementById('exampleMermaidGraph');
445
+ const closeExampleModalBtn = document.getElementById('closeExampleModalBtn');
446
+ const detailsModal = document.getElementById('detailsModal');
447
+ const detailsModalTitle = document.getElementById('detailsModalTitle');
448
+ const detailsContentInModal = document.getElementById('detailsContentInModal');
449
+ const closeDetailsModalBtn = document.getElementById('closeDetailsModalBtn');
450
+ const prevDocBtn = document.getElementById('prevDocBtn');
451
+ const nextDocBtn = document.getElementById('nextDocBtn');
452
+ const loadingSpinner = document.getElementById('loadingSpinner');
453
+
454
+ // --- State Variables ---
455
+ let currentVisibleView = 'home';
456
+ let currentDocViewTab = 'All';
457
+ let currentSelectedDocId = null;
458
+ let currentSelectedFlowId = null;
459
+ let currentDocListIndices = { prev: null, next: null };
460
+
461
+ // --- Utility Functions ---
462
+ function getDocNameById(docId) {
463
+ const doc = documentsData.find(d => d.Doc_ID_Type === docId);
464
+ return doc ? (doc.Document_Name.split('(')[0].trim() || doc.Document_Name) : docId;
465
+ }
466
+
467
+ function extractDocIDs(text) {
468
+ if (!text || documentsData.length === 0) return [];
469
+ const knownIDs = new Set(documentsData.map(doc => doc.Doc_ID_Type));
470
+ const potentialIDs = text.match(/[A-Z0-9]+(?:-[A-Z0-9]+)*\b/g) || [];
471
+ return potentialIDs.filter(id => knownIDs.has(id));
472
+ }
473
+
474
+ function getComplexityIcon(complexity) {
475
+ switch (complexity?.toLowerCase()) {
476
+ case 'low': return '<i class="lucide lucide-bar-chart text-green-500" title="Low Complexity"></i>';
477
+ case 'low-medium': return '<i class="lucide lucide-bar-chart-2 text-lime-500" title="Low-Medium Complexity"></i>';
478
+ case 'medium': return '<i class="lucide lucide-bar-chart-3 text-yellow-500" title="Medium Complexity"></i>';
479
+ case 'medium-high': return '<i class="lucide lucide-bar-chart-4 text-orange-500" title="Medium-High Complexity"></i>';
480
+ case 'high': return '<i class="lucide lucide-bar-chart-big text-red-500" title="High Complexity"></i>';
481
+ default: return '';
482
+ }
483
+ }
484
+
485
+ function getRegulatoryIcon(significance) {
486
+ if (!significance) return '';
487
+ const lowerSig = significance.toLowerCase();
488
+ if (lowerSig.includes('submission critical')) return '<i class="lucide lucide-shield-check text-red-600" title="Submission Critical"></i>';
489
+ if (lowerSig.includes('gcp')) return '<i class="lucide lucide-clipboard-check text-blue-600" title="GCP Relevant"></i>';
490
+ if (lowerSig.includes('glp')) return '<i class="lucide lucide-flask-conical text-purple-600" title="GLP Relevant"></i>';
491
+ if (lowerSig.includes('gmp')) return '<i class="lucide lucide-factory text-indigo-600" title="GMP Relevant"></i>';
492
+ if (lowerSig.includes('gvp')) return '<i class="lucide lucide-activity text-teal-600" title="GVP Relevant"></i>';
493
+ if (lowerSig.includes('regulatory requirement')) return '<i class="lucide lucide-shield-alert text-orange-600" title="Regulatory Requirement"></i>';
494
+ if (lowerSig.includes('internal')) return '<i class="lucide lucide-home text-gray-500" title="Internal Governance/Strategy"></i>';
495
+ return '<i class="lucide lucide-shield text-gray-400" title="Regulatory Significance"></i>';
496
+ }
497
+
498
+ function getMainTabIcon(tabName) {
499
+ if (!tabName) return '<i class="lucide lucide-file-question"></i>';
500
+ const lowerTab = tabName.toLowerCase();
501
+ if (lowerTab.includes('discovery')) return '<i class="lucide lucide-search"></i>';
502
+ if (lowerTab.includes('preclinical')) return '<i class="lucide lucide-flask-conical"></i>';
503
+ if (lowerTab === 'clinical development') return '<i class="lucide lucide-users"></i>';
504
+ if (lowerTab === 'regulatory submission') return '<i class="lucide lucide-file-check-2"></i>';
505
+ if (lowerTab === 'post-marketing & quality') return '<i class="lucide lucide-recycle"></i>';
506
+ if (lowerTab.includes('flows')) return '<i class="lucide lucide-git-fork"></i>';
507
+ if (lowerTab.includes('all')) return '<i class="lucide lucide-layers"></i>';
508
+ return '<i class="lucide lucide-folder-open"></i>';
509
+ }
510
+
511
+ function linkDocumentIDsForDetails(text) {
512
+ if (!text || documentsData.length === 0) return 'N/A';
513
+ const knownIDs = new Set(documentsData.map(doc => doc.Doc_ID_Type));
514
+ let linkedText = text.replace(/(\b[A-Z0-9]+(?:-[A-Z0-9]+)*\b)/g, (match) => {
515
+ if (knownIDs.has(match)) {
516
+ return `<span class="doc-link" data-doc-id="${match}">${match}</span>`;
517
+ }
518
+ return match;
519
+ });
520
+ return linkedText;
521
+ }
522
+
523
+ // --- Core Rendering & View Switching ---
524
+ function switchToView(viewId, initialTab = null) {
525
+ console.log(`Switching view to: ${viewId}, Initial Tab: ${initialTab}`);
526
+ currentVisibleView = viewId;
527
+
528
+ // Hide all main sections
529
+ homeSection.classList.add('hidden-container');
530
+ documentViewWrapper.classList.add('hidden-container');
531
+ flowsViewWrapper.classList.add('hidden-container');
532
+
533
+ // Show the target section
534
+ const targetSection = document.getElementById(viewId);
535
+ if (targetSection) {
536
+ targetSection.classList.remove('hidden-container');
537
+
538
+ // Handle specific view initializations
539
+ if (viewId === 'documentViewWrapper') {
540
+ currentDocViewTab = initialTab || 'All';
541
+ renderDocumentViewTabs();
542
+ renderDocumentList();
543
+ } else if (viewId === 'flowsViewWrapper') {
544
+ renderFlowsList();
545
+ mermaidFlowGraphContainer.innerHTML = '';
546
+ flowPlaceholder.style.display = 'block';
547
+ currentSelectedFlowId = null;
548
+ } else {
549
+ currentDocViewTab = 'All';
550
+ }
551
+ } else {
552
+ console.error(`Target view section not found: ${viewId}. Defaulting to home.`);
553
+ homeSection.classList.remove('hidden-container');
554
+ currentVisibleView = 'home';
555
+ }
556
+ updateBreadcrumb();
557
+ clearSelection();
558
+ }
559
+
560
+ function updateBreadcrumb() {
561
+ breadcrumbNav.innerHTML = '';
562
+ const homeLink = `<a href="#" data-view-target="home" class="text-blue-600 hover:underline">Home</a>`;
563
+
564
+ if (currentVisibleView === 'home') {
565
+ breadcrumbNav.innerHTML = `<span class="text-gray-500">Home</span>`;
566
+ } else if (currentVisibleView === 'documentViewWrapper') {
567
+ breadcrumbNav.innerHTML = `${homeLink} <span class="mx-2 text-gray-400">/</span> <span class="text-gray-500">Document Catalog (${currentDocViewTab})</span>`;
568
+ } else if (currentVisibleView === 'flowsViewWrapper') {
569
+ breadcrumbNav.innerHTML = `${homeLink} <span class="mx-2 text-gray-400">/</span> <span class="text-gray-500">Process Visualization</span>`;
570
+ }
571
+ }
572
+
573
+ function renderDocumentViewTabs() {
574
+ const phaseMap = {
575
+ 'Discovery': 'Discovery', 'Preclinical': 'Preclinical',
576
+ 'Clinical Phase 1': 'Clinical Development', 'Clinical Phase 2': 'Clinical Development', 'Clinical Phase 3': 'Clinical Development', 'Clinical (All Phases)': 'Clinical Development',
577
+ 'Regulatory Submission': 'Regulatory Submission', 'Regulatory Submission Review Phase': 'Regulatory Submission',
578
+ 'Post-Marketing': 'Post-Marketing & Quality', 'All Phases': 'Post-Marketing & Quality',
579
+ 'Discovery, Preclinical': 'Discovery', 'Preclinical, Clinical': 'Preclinical',
580
+ 'Preclinical (End), Clinical Phase 1': 'Preclinical', 'Discovery (late), Preclinical, Clinical': 'Discovery',
581
+ 'Pre/Post-Approval':'Regulatory Submission', 'Clinical (Annual)': 'Clinical Development',
582
+ 'Preclinical / Clinical':'Preclinical', 'Clinical (Early Phase 2/End of Phase 2)': 'Clinical Development'
583
+ };
584
+ const uniquePhases = [...new Set(documentsData.map(doc => phaseMap[doc.Phase] || 'Other'))];
585
+ const tabOrder = ['All', 'Discovery', 'Preclinical', 'Clinical Development', 'Regulatory Submission', 'Post-Marketing & Quality', 'Other'];
586
+ const sortedTabs = tabOrder.filter(tab => uniquePhases.includes(tab) || tab === 'All');
587
+ uniquePhases.forEach(phase => { if (!sortedTabs.includes(phase)) sortedTabs.push(phase); });
588
+
589
+ let tabsHtml = '';
590
+ sortedTabs.forEach(tabName => {
591
+ const isActive = tabName === currentDocViewTab;
592
+ tabsHtml += `
593
+ <button
594
+ data-tab-name="${tabName}"
595
+ class="main-tab doc-view-tab px-4 py-2 text-sm font-medium text-gray-500 hover:text-gray-700 focus:outline-none whitespace-nowrap ${isActive ? 'active' : ''}"
596
+ >
597
+ ${getMainTabIcon(tabName)} ${tabName}
598
+ </button>
599
+ `;
600
+ });
601
+ mainTabsDocViewContainer.innerHTML = tabsHtml;
602
+
603
+ // Add event listeners to THESE tabs
604
+ mainTabsDocViewContainer.querySelectorAll('.doc-view-tab').forEach(tab => {
605
+ tab.addEventListener('click', () => {
606
+ currentDocViewTab = tab.dataset.tabName;
607
+ renderDocumentViewTabs();
608
+ renderDocumentList();
609
+ updateBreadcrumb();
610
+ clearSelection();
611
+ });
612
+ });
613
+ }
614
+
615
+ function renderDocumentList() {
616
+ if (currentVisibleView !== 'documentViewWrapper') return;
617
+
618
+ const phaseMap = {
619
+ 'Discovery': 'Discovery', 'Preclinical': 'Preclinical',
620
+ 'Clinical Phase 1': 'Clinical Development', 'Clinical Phase 2': 'Clinical Development', 'Clinical Phase 3': 'Clinical Development', 'Clinical (All Phases)': 'Clinical Development',
621
+ 'Regulatory Submission': 'Regulatory Submission', 'Regulatory Submission Review Phase': 'Regulatory Submission',
622
+ 'Post-Marketing': 'Post-Marketing & Quality', 'All Phases': 'Post-Marketing & Quality',
623
+ 'Discovery, Preclinical': 'Discovery', 'Preclinical, Clinical': 'Preclinical',
624
+ 'Preclinical (End), Clinical Phase 1': 'Preclinical', 'Discovery (late), Preclinical, Clinical': 'Discovery',
625
+ 'Pre/Post-Approval':'Regulatory Submission', 'Clinical (Annual)': 'Clinical Development',
626
+ 'Preclinical / Clinical':'Preclinical', 'Clinical (Early Phase 2/End of Phase 2)': 'Clinical Development'
627
+ };
628
+
629
+ const searchTerm = searchInputDocView.value.toLowerCase();
630
+ let filteredDocs = documentsData;
631
+
632
+ // Apply phase filter based on currentDocViewTab
633
+ if (currentDocViewTab !== 'All') {
634
+ filteredDocs = filteredDocs.filter(doc => {
635
+ const primaryPhase = phaseMap[doc.Phase] || 'Other';
636
+ return primaryPhase === currentDocViewTab;
637
+ });
638
+ }
639
+
640
+ // Apply search filter
641
+ if (searchTerm) {
642
+ filteredDocs = filteredDocs.filter(doc =>
643
+ doc.Document_Name.toLowerCase().includes(searchTerm) ||
644
+ doc.Doc_ID_Type.toLowerCase().includes(searchTerm) ||
645
+ (doc.Sub_Phase_Discipline && doc.Sub_Phase_Discipline.toLowerCase().includes(searchTerm)) ||
646
+ (doc.Purpose_Key_Content && doc.Purpose_Key_Content.toLowerCase().includes(searchTerm)) ||
647
+ (doc["Authoring_Department(s)"] && doc["Authoring_Department(s)"].toLowerCase().includes(searchTerm)) ||
648
+ (doc.Key_Metadata && doc.Key_Metadata.toLowerCase().includes(searchTerm))
649
+ );
650
+ }
651
+
652
+ // Render logic with enhanced styling
653
+ let listHtml = '';
654
+ if (filteredDocs.length === 0) {
655
+ listHtml = `<p class="text-gray-500 text-center py-4">No documents found ${searchTerm ? 'matching search in' : 'for'} ${currentDocViewTab === 'All' ? 'any phase' : currentDocViewTab}.</p>`;
656
+ } else {
657
+ const sortedDocs = filteredDocs.sort((a, b) => a.Document_Name.localeCompare(b.Document_Name));
658
+ listHtml = '<ul class="divide-y divide-gray-200">';
659
+ sortedDocs.forEach(doc => {
660
+ const isSelected = doc.Doc_ID_Type === currentSelectedDocId;
661
+ listHtml += `
662
+ <li data-doc-id="${doc.Doc_ID_Type}" data-phase="${doc.Phase}" class="document-item doc-list-item px-3 py-3 hover:bg-blue-50 cursor-pointer text-sm flex justify-between items-start ${isSelected ? 'bg-blue-100 font-semibold' : ''}">
663
+ <div class="flex flex-col min-w-0 pr-2">
664
+ <div class="flex items-center">
665
+ <i class="lucide lucide-file-text text-blue-500 mr-2 flex-shrink-0"></i>
666
+ <span class="font-medium truncate" title="${doc.Document_Name}">${doc.Document_Name}</span>
667
+ </div>
668
+ <div class="text-xs text-gray-500 mt-1 ml-6">
669
+ <span class="mr-2">${doc.Doc_ID_Type}</span> •
670
+ <span class="mx-2">${doc.Phase || 'N/A'}</span> •
671
+ <span class="mx-2">${doc.Sub_Phase_Discipline || 'N/A'}</span>
672
+ </div>
673
+ <div class="text-xs text-gray-600 mt-1 ml-6 line-clamp-2" title="${doc.Purpose_Key_Content || 'No description available'}">
674
+ ${doc.Purpose_Key_Content || 'No description available'}
675
+ </div>
676
+ </div>
677
+ <div class="flex items-center space-x-1 flex-shrink-0">
678
+ ${getComplexityIcon(doc.Complexity_Authoring)}
679
+ ${getRegulatoryIcon(doc.Regulatory_Significance)}
680
+ </div>
681
+ </li>`;
682
+ });
683
+ listHtml += '</ul>';
684
+ }
685
+
686
+ if(documentListDocViewContainer) {
687
+ documentListDocViewContainer.innerHTML = listHtml;
688
+ } else {
689
+ console.error("documentListDocViewContainer element not found!");
690
+ return;
691
+ }
692
 
693
+ // Add event listeners for items in THIS list
694
+ document.querySelectorAll('#documentListDocView li.doc-list-item').forEach(item => {
695
+ item.addEventListener('click', () => {
696
+ currentSelectedDocId = item.dataset.docId;
697
+ displayDetailsInModal(currentSelectedDocId);
698
+ renderDocumentList();
699
+ });
700
+ });
701
+ }
702
+
703
+ function findNextPrevDocs(currentId) {
704
+ const listItems = documentListDocViewContainer.querySelectorAll('li[data-doc-id]');
705
+ const docIds = Array.from(listItems).map(li => li.dataset.docId);
706
+ const currentIndex = docIds.indexOf(currentId);
707
+
708
+ if (currentIndex === -1 || docIds.length <= 1) {
709
+ return { prev: null, next: null };
710
+ }
711
+ const prevIndex = currentIndex > 0 ? currentIndex - 1 : docIds.length - 1;
712
+ const nextIndex = currentIndex < docIds.length - 1 ? currentIndex + 1 : 0;
713
+
714
+ return {
715
+ prev: docIds[prevIndex],
716
+ next: docIds[nextIndex]
717
+ };
718
+ }
719
+
720
+ async function displayDetailsInModal(docId) {
721
+ const doc = documentsData.find(d => d.Doc_ID_Type === docId);
722
+ if (!doc) return;
723
+
724
+ currentSelectedDocId = docId;
725
+ detailsModalTitle.textContent = `${doc.Document_Name} (${doc.Doc_ID_Type})`;
726
+
727
+ // Create tabs for different views
728
+ const tabsHtml = `
729
+ <div class="details-tabs mb-4">
730
+ <div class="details-tab active" data-tab="info">Information</div>
731
+ <div class="details-tab" data-tab="dependencies">Dependencies</div>
732
+ </div>
733
+ `;
734
+
735
+ // Create content sections
736
+ const infoHtml = `
737
+ <div class="details-content active" id="tab-content-info">
738
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
739
+ <div>
740
+ <div class="mb-3">
741
+ <span class="text-sm font-medium text-gray-600">Phase:</span>
742
+ <span class="ml-2 text-sm px-2 py-1 bg-blue-100 text-blue-800 rounded-full">${doc.Phase || 'N/A'}</span>
743
+ </div>
744
+ <div class="mb-3">
745
+ <span class="text-sm font-medium text-gray-600">Discipline:</span>
746
+ <span class="ml-2">${doc.Sub_Phase_Discipline || 'N/A'}</span>
747
+ </div>
748
+ <div class="mb-3">
749
+ <span class="text-sm font-medium text-gray-600">Authoring Department(s):</span>
750
+ <span class="ml-2">${doc['Authoring_Department(s)'] || 'N/A'}</span>
751
+ </div>
752
+ <div class="mb-3">
753
+ <span class="text-sm font-medium text-gray-600">Review/Approval Dept(s):</span>
754
+ <span class="ml-2">${doc['Review_Approval_Dept(s)'] || 'N/A'}</span>
755
+ </div>
756
+ <div class="mb-3 flex items-center">
757
+ <span class="text-sm font-medium text-gray-600">Complexity:</span>
758
+ <span class="ml-2 flex items-center">${getComplexityIcon(doc.Complexity_Authoring)} ${doc.Complexity_Authoring || 'N/A'}</span>
759
+ </div>
760
+ <div class="mb-3 flex items-center">
761
+ <span class="text-sm font-medium text-gray-600">Regulatory Significance:</span>
762
+ <span class="ml-2 flex items-center">${getRegulatoryIcon(doc.Regulatory_Significance)} ${doc.Regulatory_Significance || 'N/A'}</span>
763
+ </div>
764
+ </div>
765
+ <div>
766
+ <div class="mb-3">
767
+ <div class="text-sm font-medium text-gray-600 mb-1">Purpose / Key Content:</div>
768
+ <div class="bg-gray-50 p-3 rounded text-sm max-h-28 overflow-y-auto custom-scroll">${doc.Purpose_Key_Content || 'N/A'}</div>
769
+ </div>
770
+ <div class="mb-3">
771
+ <div class="text-sm font-medium text-gray-600 mb-1">Key Metadata:</div>
772
+ <div class="bg-gray-50 p-3 rounded text-sm">${doc.Key_Metadata || 'N/A'}</div>
773
+ </div>
774
+ </div>
775
+ </div>
776
+ <div class="mt-4">
777
+ <div class="text-sm font-medium text-gray-600 mb-1">Input Docs/Data:</div>
778
+ <div class="bg-gray-50 p-3 rounded text-sm">${linkDocumentIDsForDetails(doc.Input_Documents_Data_Sources) || 'N/A'}</div>
779
+ </div>
780
+ <div class="mt-4">
781
+ <div class="text-sm font-medium text-gray-600 mb-1">Output/Informs Docs:</div>
782
+ <div class="bg-gray-50 p-3 rounded text-sm">${linkDocumentIDsForDetails(doc.Output_Informs_Documents) || 'N/A'}</div>
783
+ </div>
784
+ </div>
785
+ `;
786
+
787
+ const dependenciesHtml = `
788
+ <div class="details-content" id="tab-content-dependencies">
789
+ <div id="document-graph-container" class="dependency-graph mt-3"></div>
790
+ </div>
791
+ `;
792
+
793
+ // Combine all content
794
+ detailsContentInModal.innerHTML = tabsHtml + infoHtml + dependenciesHtml;
795
+
796
+ // Add tab switching functionality
797
+ detailsContentInModal.querySelectorAll('.details-tab').forEach(tab => {
798
+ tab.addEventListener('click', () => {
799
+ // Update active tab
800
+ detailsContentInModal.querySelectorAll('.details-tab').forEach(t => t.classList.remove('active'));
801
+ tab.classList.add('active');
802
+
803
+ // Show corresponding content
804
+ const tabId = tab.dataset.tab;
805
+ detailsContentInModal.querySelectorAll('.details-content').forEach(c => c.classList.remove('active'));
806
+ document.getElementById(`tab-content-${tabId}`).classList.add('active');
807
+
808
+ // If dependencies tab, render the graph
809
+ if (tabId === 'dependencies') {
810
+ renderDependencyGraph(docId);
811
+ }
812
+ });
813
+ });
814
+
815
+ // Add event listeners to document links within the modal
816
+ detailsContentInModal.querySelectorAll('.doc-link').forEach(link => {
817
+ link.addEventListener('click', (e) => {
818
+ displayDetailsInModal(e.target.dataset.docId);
819
+ // Optionally update the main list highlight if visible
820
+ if (currentVisibleView === 'documentViewWrapper') {
821
+ renderDocumentList();
822
+ }
823
+ });
824
+ });
825
+
826
+ // Setup Next/Prev Buttons
827
+ currentDocListIndices = findNextPrevDocs(docId);
828
+ prevDocBtn.disabled = !currentDocListIndices.prev;
829
+ nextDocBtn.disabled = !currentDocListIndices.next;
830
+
831
+ // Show Modal
832
+ detailsModal.style.display = 'flex';
833
+
834
+ // Only re-render list if doc view is active
835
+ if (currentVisibleView === 'documentViewWrapper') {
836
+ renderDocumentList();
837
+ }
838
+ }
839
+
840
+ // New function to render dependency graph using Mermaid
841
+ async function renderDependencyGraph(docId) {
842
+ const doc = documentsData.find(d => d.Doc_ID_Type === docId);
843
+ if (!doc) return;
844
+
845
+ const container = document.getElementById('document-graph-container');
846
+ if (!container) return;
847
+
848
+ // Show loading spinner
849
+ container.innerHTML = '<div class="loading-spinner"></div>';
850
+
851
+ const inputIDs = extractDocIDs(doc.Input_Documents_Data_Sources);
852
+ const outputIDs = extractDocIDs(doc.Output_Informs_Documents);
853
+
854
+ let mermaidDefinition = 'graph TD;\n';
855
+ const centerNodeName = getDocNameById(doc.Doc_ID_Type);
856
+
857
+ // Define center node with improved styling
858
+ mermaidDefinition += ` ${doc.Doc_ID_Type}("${centerNodeName}\\n(${doc.Doc_ID_Type})"):::focus;\n`;
859
+
860
+ // Define input nodes and connections
861
+ inputIDs.forEach(inputId => {
862
+ const inputNodeName = getDocNameById(inputId);
863
+ mermaidDefinition += ` ${inputId}("${inputNodeName}\\n(${inputId})"):::input --> ${doc.Doc_ID_Type};\n`;
864
+ });
865
+
866
+ // Define output nodes and connections
867
+ outputIDs.forEach(outputId => {
868
+ const outputNodeName = getDocNameById(outputId);
869
+ mermaidDefinition += ` ${doc.Doc_ID_Type} --> ${outputId}("${outputNodeName}\\n(${outputId})"):::output;\n`;
870
+ });
871
+
872
+ // Add class definitions for better styling
873
+ mermaidDefinition += ` classDef focus fill:#e0f2fe,stroke:#38bdf8,stroke-width:2px,color:#075985;\n`;
874
+ mermaidDefinition += ` classDef input fill:#f1f5f9,stroke:#94a3b8,color:#334155;\n`;
875
+ mermaidDefinition += ` classDef output fill:#f1f5f9,stroke:#94a3b8,color:#334155;\n`;
876
+
877
+ // Add click handlers for all nodes
878
+ [doc.Doc_ID_Type, ...inputIDs, ...outputIDs].forEach(id => {
879
+ mermaidDefinition += ` click ${id} call displayDetailsAndGraphFromModal("${id}") "View Details";\n`;
880
+ });
881
+
882
+ try {
883
+ const graphId = `mermaid-modal-graph-${docId}-${Date.now()}`;
884
+ const { svg } = await mermaid.render(graphId, mermaidDefinition);
885
+ container.innerHTML = svg;
886
+
887
+ // Make the SVG responsive
888
+ const svgElement = container.querySelector('svg');
889
+ if (svgElement) {
890
+ svgElement.setAttribute('width', '100%');
891
+ svgElement.setAttribute('height', '100%');
892
+ svgElement.style.maxHeight = '400px';
893
+ }
894
+ } catch (error) {
895
+ console.error("Mermaid rendering error:", error);
896
+ container.innerHTML = `
897
+ <div class="text-center text-red-500 py-4">
898
+ <i class="lucide lucide-alert-triangle text-2xl mb-2"></i>
899
+ <p>Failed to render dependency graph.</p>
900
+ </div>
901
+ `;
902
+ }
903
+ }
904
+
905
+ // Callbacks from Mermaid graphs
906
+ window.displayDetailsAndGraphFromGraph = async (docId) => {
907
+ console.log("Graph node clicked (main flow or example):", docId);
908
+ await displayDetailsInModal(docId);
909
+ setTimeout(() => {
910
+ // If the doc list view is active, scroll the item into view
911
+ if (currentVisibleView === 'documentViewWrapper') {
912
+ const listItem = document.querySelector(`#documentListDocView li[data-doc-id="${docId}"]`);
913
+ listItem?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
914
+ }
915
+ }, 100);
916
+ };
917
+
918
+ window.displayDetailsAndGraphFromModal = async (docId) => {
919
+ console.log("Modal graph node clicked:", docId);
920
+ await displayDetailsInModal(docId);
921
+ setTimeout(() => {
922
+ if (currentVisibleView === 'documentViewWrapper') {
923
+ const listItem = document.querySelector(`#documentListDocView li[data-doc-id="${docId}"]`);
924
+ listItem?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
925
+ }
926
+ }, 100);
927
+ };
928
+
929
+ function clearSelection() {
930
+ currentSelectedDocId = null;
931
+ if (currentVisibleView === 'documentViewWrapper') {
932
+ renderDocumentList();
933
+ }
934
+ }
935
+
936
+ // Enhance flow list rendering with better UI
937
+ function renderFlowsList() {
938
+ const flowDisplayTitles = {
939
+ "p1_sad": "Phase 1 SAD Study Documents",
940
+ "nda_submission": "NDA/MAA Submission Process",
941
+ "ind_pathway": "IND Pathway Documents",
942
+ "clinical_program": "Clinical Program Development"
943
+ };
944
+
945
+ let flowsHtml = `
946
+ <div class="mb-4">
947
+ <h3 class="text-lg font-semibold text-gray-700 mb-2">Document Workflows</h3>
948
+ <p class="text-sm text-gray-500 mb-4">Select a flow to visualize document relationships and dependencies in typical R&D processes.</p>
949
+ </div>
950
+ `;
951
+
952
+ Object.keys(flowDefinitions).forEach(id => {
953
+ const title = flowDisplayTitles[id] || `Flow ${id}`;
954
+ const isSelected = id === currentSelectedFlowId;
955
+
956
+ flowsHtml += `
957
+ <div class="flow-card ${isSelected ? 'active' : ''}" data-flow-id="${id}">
958
+ <div class="flex items-center justify-between">
959
+ <div class="flex items-center">
960
+ <i class="lucide lucide-git-branch text-purple-500 mr-2"></i>
961
+ <span class="font-medium">${title}</span>
962
+ </div>
963
+ <i class="lucide lucide-chevron-right text-gray-400"></i>
964
+ </div>
965
+ <p class="text-xs text-gray-500 mt-1 ml-6">
966
+ ${id === 'p1_sad' ? 'Documents required for First-in-Human studies' :
967
+ id === 'nda_submission' ? 'Regulatory submission package assembly' :
968
+ id === 'ind_pathway' ? 'Discovery to IND enabling documents' :
969
+ id === 'clinical_program' ? 'Clinical phase documentation flow' :
970
+ 'Document workflow visualization'}
971
+ </p>
972
+ </div>
973
+ `;
974
+ });
975
+
976
+ flowsListContainer.innerHTML = flowsHtml;
977
+
978
+ // Add event listeners
979
+ flowsListContainer.querySelectorAll('.flow-card').forEach(card => {
980
+ card.addEventListener('click', () => {
981
+ const flowId = card.dataset.flowId;
982
+ currentSelectedFlowId = flowId;
983
+ displayFlowGraph(flowId);
984
+ renderFlowsList(); // Update active state
985
+ });
986
+ });
987
+ }
988
+
989
+ // Enhanced flow graph display
990
+ async function displayFlowGraph(flowId) {
991
+ const definition = flowDefinitions[flowId];
992
+ if (!definition) {
993
+ mermaidFlowGraphContainer.innerHTML = `
994
+ <div class="flex flex-col items-center justify-center p-10 text-gray-500">
995
+ <i class="lucide lucide-alert-circle text-3xl mb-3"></i>
996
+ <p>Flow definition not found.</p>
997
+ </div>
998
+ `;
999
+ flowPlaceholder.style.display = 'none';
1000
+ return;
1001
+ }
1002
+
1003
+ // Show loading indicator
1004
+ mermaidFlowGraphContainer.innerHTML = `
1005
+ <div class="flex flex-col items-center justify-center p-10">
1006
+ <div class="loading-spinner"></div>
1007
+ <p class="text-gray-500 mt-4">Rendering flow graph...</p>
1008
+ </div>
1009
+ `;
1010
+ flowPlaceholder.style.display = 'none';
1011
+
1012
+ try {
1013
+ if (flowsViewWrapper.classList.contains('hidden-container')) return;
1014
+
1015
+ const clickableDefinition = definition.replace(/click ([A-Z0-9_\-]+) call displayDetailsAndGraphFromGraph/g,'click $1 call displayDetailsAndGraphFromGraph');
1016
+ const graphId = `mermaid-flow-${flowId}-${Date.now()}`;
1017
+
1018
+ const { svg } = await mermaid.render(graphId, clickableDefinition);
1019
+ mermaidFlowGraphContainer.innerHTML = svg;
1020
+
1021
+ // Make the SVG responsive
1022
+ const svgElement = mermaidFlowGraphContainer.querySelector('svg');
1023
+ if (svgElement) {
1024
+ svgElement.setAttribute('width', '100%');
1025
+ svgElement.setAttribute('height', '100%');
1026
+ svgElement.style.maxHeight = '700px'; // Taller to accommodate complex flows
1027
+ }
1028
+
1029
+ // Add title and description based on flow ID
1030
+ const flowDisplayTitles = {
1031
+ "p1_sad": "Phase 1 SAD Study Documents",
1032
+ "nda_submission": "NDA/MAA Submission Process",
1033
+ "ind_pathway": "IND Pathway Documents",
1034
+ "clinical_program": "Clinical Program Development"
1035
+ };
1036
+
1037
+ const flowDescriptions = {
1038
+ "p1_sad": "This diagram shows the key documents needed for a Phase 1 Single Ascending Dose study, from preclinical inputs through to clinical execution and reporting.",
1039
+ "nda_submission": "The NDA/MAA submission process flow showing how various documents and data packages are assembled into a regulatory submission.",
1040
+ "ind_pathway": "Documents required from Discovery through Preclinical development to enable an IND/CTA submission.",
1041
+ "clinical_program": "The integrated flow of clinical documentation across Phases 1-3 leading to regulatory submission."
1042
+ };
1043
+
1044
+ const title = flowDisplayTitles[flowId] || `Flow ${flowId}`;
1045
+ const description = flowDescriptions[flowId] || "Document workflow visualization";
1046
+
1047
+ // Add title and description above the graph
1048
+ const titleContainer = document.createElement('div');
1049
+ titleContainer.className = 'mb-4';
1050
+ titleContainer.innerHTML = `
1051
+ <h3 class="text-xl font-semibold text-gray-800 mb-2">${title}</h3>
1052
+ <p class="text-sm text-gray-600">${description}</p>
1053
+ `;
1054
+
1055
+ mermaidFlowGraphContainer.insertBefore(titleContainer, mermaidFlowGraphContainer.firstChild);
1056
+
1057
+ } catch (error) {
1058
+ console.error(`Mermaid rendering error for flow ${flowId}:`, error);
1059
+ mermaidFlowGraphContainer.innerHTML = `
1060
+ <div class="text-center p-10">
1061
+ <i class="lucide lucide-alert-triangle text-red-500 text-3xl mb-3"></i>
1062
+ <p class="text-red-500 mb-4">Error rendering flow graph.</p>
1063
+ <div class="bg-gray-100 p-4 rounded text-xs overflow-auto max-h-60">
1064
+ ${error.message}
1065
+ </div>
1066
+ </div>
1067
+ `;
1068
+ }
1069
+ }
1070
+
1071
+ async function showExampleFlow() {
1072
+ const exampleDefinition = flowDefinitions['p1_sad'];
1073
+
1074
+ try {
1075
+ // Show loading indicator
1076
+ exampleMermaidGraphContainer.innerHTML = `
1077
+ <div class="flex flex-col items-center justify-center p-10">
1078
+ <div class="loading-spinner"></div>
1079
+ <p class="text-gray-500 mt-4">Rendering example flow...</p>
1080
+ </div>
1081
+ `;
1082
+
1083
+ const clickableDefinition = exampleDefinition.replace(/click ([A-Z0-9_\-]+) call displayDetailsAndGraphFromGraph/g, 'click $1 call displayDetailsAndGraphFromModal');
1084
+ const graphId = `example-mermaid-graph-render-${Date.now()}`;
1085
+ const { svg } = await mermaid.render(graphId, clickableDefinition);
1086
+ exampleMermaidGraphContainer.innerHTML = svg;
1087
+
1088
+ // Add title and description
1089
+ const titleContainer = document.createElement('div');
1090
+ titleContainer.className = 'mb-4';
1091
+ titleContainer.innerHTML = `
1092
+ <h3 class="text-lg font-semibold text-gray-800 mb-2">Phase 1 SAD Study Documents</h3>
1093
+ <p class="text-sm text-gray-600">This diagram shows the key documents needed for a Phase 1 Single Ascending Dose study, from preclinical inputs through to clinical execution and reporting.</p>
1094
+ `;
1095
+ exampleMermaidGraphContainer.insertBefore(titleContainer, exampleMermaidGraphContainer.firstChild);
1096
+
1097
+ // Make the SVG responsive
1098
+ const svgElement = exampleMermaidGraphContainer.querySelector('svg');
1099
+ if (svgElement) {
1100
+ svgElement.setAttribute('width', '100%');
1101
+ svgElement.setAttribute('height', '100%');
1102
+ }
1103
+
1104
+ exampleFlowModal.style.display = "flex";
1105
+ } catch (error) {
1106
+ console.error("Mermaid rendering error for example:", error);
1107
+ exampleMermaidGraphContainer.innerHTML = `
1108
+ <div class="text-center p-10">
1109
+ <i class="lucide lucide-alert-triangle text-red-500 text-3xl mb-3"></i>
1110
+ <p class="text-red-500">Error rendering example flow graph.</p>
1111
+ </div>
1112
+ `;
1113
+ exampleFlowModal.style.display = "flex";
1114
+ }
1115
+ }
1116
+
1117
+ // --- Global Search Functionality ---
1118
+ function performGlobalSearch(searchTerm) {
1119
+ if (!searchTerm) return;
1120
+
1121
+ searchTerm = searchTerm.toLowerCase();
1122
+ let results = documentsData.filter(doc =>
1123
+ doc.Document_Name.toLowerCase().includes(searchTerm) ||
1124
+ doc.Doc_ID_Type.toLowerCase().includes(searchTerm) ||
1125
+ (doc.Purpose_Key_Content && doc.Purpose_Key_Content.toLowerCase().includes(searchTerm))
1126
+ );
1127
+
1128
+ // Switch to document view with search results
1129
+ switchToView('documentViewWrapper', 'All');
1130
+
1131
+ // Set the search input in document view to match the global search
1132
+ searchInputDocView.value = searchTerm;
1133
+
1134
+ // Render the filtered list
1135
+ renderDocumentList();
1136
+ }
1137
+
1138
+ // --- Event Listeners ---
1139
+
1140
+ // Search input specific to document view
1141
+ searchInputDocView?.addEventListener('input', renderDocumentList);
1142
+
1143
+ // Global header search
1144
+ headerSearchInput?.addEventListener('keypress', (e) => {
1145
+ if (e.key === 'Enter') {
1146
+ performGlobalSearch(e.target.value);
1147
+ }
1148
+ });
1149
+
1150
+ // Home button listener
1151
+ homeButton?.addEventListener('click', () => switchToView('home'));
1152
+
1153
+ // Breadcrumb listener (delegated)
1154
+ breadcrumbNav?.addEventListener('click', (e) => {
1155
+ if (e.target.tagName === 'A' && e.target.dataset.viewTarget) {
1156
+ e.preventDefault();
1157
+ switchToView(e.target.dataset.viewTarget);
1158
+ }
1159
+ });
1160
+
1161
+ // Dashboard card listeners (delegated to main content area)
1162
+ mainContentArea?.addEventListener('click', (e) => {
1163
+ const card = e.target.closest('.dashboard-card[data-target-view]');
1164
+ if (card) {
1165
+ const targetView = card.dataset.targetView;
1166
+ const initialTab = card.dataset.initialTab; // Get initial tab if specified
1167
+ if (targetView) {
1168
+ switchToView(targetView, initialTab);
1169
+ }
1170
+ }
1171
+ });
1172
+
1173
+ // Example Flow Buttons (Header and Flow View)
1174
+ showExampleFlowBtnHeader?.addEventListener('click', showExampleFlow);
1175
+ showExampleFlowBtnFlowView?.addEventListener('click', showExampleFlow);
1176
+
1177
+ // Modal Close Listeners
1178
+ closeExampleModalBtn?.addEventListener('click', () => exampleFlowModal.style.display = "none");
1179
+ window.addEventListener('click', (event) => { if (event.target == exampleFlowModal) exampleFlowModal.style.display = "none"; });
1180
+ closeDetailsModalBtn?.addEventListener('click', () => { detailsModal.style.display = "none"; clearSelection(); });
1181
+ window.addEventListener('click', (event) => { if (event.target == detailsModal) { detailsModal.style.display = "none"; clearSelection(); } });
1182
+
1183
+ // Modal Next/Prev Button Listeners
1184
+ prevDocBtn?.addEventListener('click', () => { if (currentDocListIndices.prev) displayDetailsInModal(currentDocListIndices.prev); });
1185
+ nextDocBtn?.addEventListener('click', () => { if (currentDocListIndices.next) displayDetailsInModal(currentDocListIndices.next); });
1186
+
1187
+ // --- Initialization ---
1188
+ document.addEventListener('DOMContentLoaded', async () => {
1189
+ console.log("DOM Loaded. Fetching data...");
1190
+
1191
+ try {
1192
+ // Load document data
1193
+ const docsResponse = await fetch('documents.json');
1194
+ if (!docsResponse.ok) {
1195
+ throw new Error(`HTTP error! status: ${docsResponse.status}`);
1196
+ }
1197
+ documentsData = await docsResponse.json();
1198
+ console.log(`Successfully loaded ${documentsData.length} documents from documents.json`);
1199
+
1200
+ // Try to load template data if available
1201
+ try {
1202
+ const templatesResponse = await fetch('document_templates.json');
1203
+ if (templatesResponse.ok) {
1204
+ templateData = await templatesResponse.json();
1205
+ console.log(`Successfully loaded ${templateData.length} templates from document_templates.json`);
1206
+ } else {
1207
+ console.warn("Templates file not found. Document templates will not be available.");
1208
+ templateData = [];
1209
+ }
1210
+ } catch (templateError) {
1211
+ console.warn("Error loading templates:", templateError);
1212
+ templateData = [];
1213
+ }
1214
+
1215
+ // Hide the loading spinner
1216
+ loadingSpinner.style.display = 'none';
1217
+
1218
+ // Start on the 'Home' view
1219
+ switchToView('home');
1220
+
1221
+ } catch (error) {
1222
+ console.error("Failed to load documents.json:", error);
1223
+
1224
+ // Hide spinner and show error message
1225
+ loadingSpinner.style.display = 'none';
1226
+
1227
+ // Display error message more prominently
1228
+ mainContentArea.innerHTML = `
1229
+ <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative m-4" role="alert">
1230
+ <strong class="font-bold">Error!</strong>
1231
+ <span class="block sm:inline">Could not load document database (documents.json). Please ensure the file exists in the same folder and is valid JSON.</span>
1232
+ <p class="text-xs mt-1">(${error.message})</p>
1233
+ </div>
1234
+ `;
1235
+ }
1236
+ });
1237
+ </script>
1238
  </body>
1239
  </html>