-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathApp.tsx
More file actions
237 lines (214 loc) · 12.9 KB
/
App.tsx
File metadata and controls
237 lines (214 loc) · 12.9 KB
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
import React, { useState, useEffect } from 'react';
import { AppStep, ISMElement, SSIMData, ISMResult } from './types';
import FactorInput from './components/FactorInput';
import SSIMGrid from './components/SSIMGrid';
import ResultsView from './components/ResultsView';
import { runISMAnalysis } from './services/ismLogic';
import { HardHat, BookOpen, ChevronDown, ChevronUp, Construction, AlertTriangle } from 'lucide-react';
// Default factors based on the user provided list (Confined Space / Safety Factors)
const DEFAULT_FACTORS: ISMElement[] = [
{ id: 'L01', name: 'L01', description: 'Incompliance with Standards', category: 'Legal and Policy' },
{ id: 'L02', name: 'L02', description: 'Lack of On-site Confirmation', category: 'Legal and Policy' },
{ id: 'M01', name: 'M01', description: 'Ineffective Risk Assessment', category: 'Management' },
{ id: 'O01', name: 'O01', description: 'Improper Use of PPE', category: 'Organization' },
{ id: 'O05', name: 'O05', description: 'Inadequate PPE', category: 'Organization' },
{ id: 'O06', name: 'O06', description: 'Violation of Operating Procedures', category: 'Organization' },
{ id: 'O10', name: 'O10', description: 'Unauthorized Operation', category: 'Organization' },
{ id: 'O11', name: 'O11', description: 'Lack of Gas Detection', category: 'Organization' },
{ id: 'PE01', name: 'PE01', description: 'Oxygen Deficiency', category: 'Physical Environment' },
{ id: 'PE03', name: 'PE03', description: 'Toxic or Harmful Gases', category: 'Physical Environment' },
{ id: 'PE04', name: 'PE04', description: 'Inadequate Ventilation', category: 'Physical Environment' },
{ id: 'PR01', name: 'PR01', description: 'Lack of Safety Awareness', category: 'Personal Reason' },
{ id: 'S01', name: 'S01', description: 'Inadequate Supervision', category: 'Supervision' },
];
const FIXED_TOPIC = "Confined Space Risk Factors";
const App: React.FC = () => {
// Initialize directly to Factor Definition step with default data
const [step, setStep] = useState<AppStep>(AppStep.DEFINE_FACTORS);
const [topic] = useState(FIXED_TOPIC);
const [factors, setFactors] = useState<ISMElement[]>(DEFAULT_FACTORS);
const [ssim, setSsim] = useState<SSIMData>({});
const [result, setResult] = useState<ISMResult | null>(null);
const [isManualOpen, setIsManualOpen] = useState(true);
useEffect(() => {
window.scrollTo({ top: 0, behavior: 'smooth' });
}, [step]);
const goToSSIM = () => {
if (factors.length < 2) {
alert("Please define at least 2 factors to proceed.");
return;
}
setStep(AppStep.FILL_SSIM);
};
const calculateAndShowResults = () => {
const factorIds = factors.map(f => f.id);
const analysis = runISMAnalysis(factors.length, factorIds, ssim);
setResult(analysis);
setStep(AppStep.ANALYSIS_RESULT);
};
const resetAnalysis = () => {
if(window.confirm("This will clear the current analysis and SSIM data. The factors will remain. Continue?")) {
setSsim({});
setResult(null);
setStep(AppStep.DEFINE_FACTORS);
}
};
return (
<div className="min-h-screen bg-slate-50 text-slate-900 font-sans flex flex-col">
{/* Professional Header */}
<header className="bg-white border-b border-slate-200 sticky top-0 z-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 h-auto sm:h-18 flex flex-col sm:flex-row items-center justify-between py-3 gap-3 sm:gap-0">
<div className="flex items-center gap-4 w-full sm:w-auto">
{/* Realistic Construction Logo Badge */}
<div className="flex-shrink-0 flex items-center justify-center w-10 h-10 sm:w-12 sm:h-12 bg-slate-900 rounded-md shadow-sm border-b-2 border-yellow-500">
<HardHat className="w-6 h-6 sm:w-8 sm:h-8 text-yellow-500" strokeWidth={1.5} />
</div>
<div className="flex flex-col justify-center">
<h1 className="text-lg sm:text-xl font-bold text-slate-900 tracking-tight leading-none">
ISM Tool
</h1>
<p className="text-[10px] sm:text-xs text-slate-500 font-medium tracking-wide mt-1 uppercase">
SSIM & MICMAC Analysis
</p>
</div>
</div>
<div className="hidden md:flex items-center bg-slate-100 rounded-lg p-1">
<div className={`px-4 py-1.5 rounded-md text-sm font-medium transition-all ${step === AppStep.DEFINE_FACTORS ? 'bg-white text-slate-900 shadow-sm ring-1 ring-slate-200' : 'text-slate-500'}`}>
1. Factors
</div>
<div className="text-slate-300 px-2">/</div>
<div className={`px-4 py-1.5 rounded-md text-sm font-medium transition-all ${step === AppStep.FILL_SSIM ? 'bg-white text-slate-900 shadow-sm ring-1 ring-slate-200' : 'text-slate-500'}`}>
2. SSIM Matrix
</div>
<div className="text-slate-300 px-2">/</div>
<div className={`px-4 py-1.5 rounded-md text-sm font-medium transition-all ${step === AppStep.ANALYSIS_RESULT ? 'bg-white text-slate-900 shadow-sm ring-1 ring-slate-200' : 'text-slate-500'}`}>
3. Analysis
</div>
</div>
</div>
</header>
{/* Main Content */}
<main className="max-w-7xl mx-auto px-4 sm:px-6 py-4 print:p-0 flex-grow w-full">
{step === AppStep.DEFINE_FACTORS && (
<div className="space-y-6 animate-in fade-in slide-in-from-bottom-2 duration-500">
{/* Announcement Bar */}
<div className="bg-amber-50 border-l-4 border-amber-500 p-4 rounded-r shadow-sm flex items-start gap-3">
<AlertTriangle className="w-5 h-5 text-amber-500 flex-shrink-0 mt-0.5" />
<p className="text-sm text-amber-800 font-medium">
This Application cannot store data automatically, save your progress with JSON or CSV format before leaving the site.
</p>
</div>
{/* User Manual / Instructions */}
<div className="bg-white rounded-lg border border-slate-200 shadow-sm overflow-hidden">
<div
className="p-4 bg-slate-50 flex items-center justify-between cursor-pointer border-b border-slate-200 hover:bg-slate-100 transition-colors"
onClick={() => setIsManualOpen(!isManualOpen)}
>
<div className="flex items-center gap-3 text-slate-800 font-bold">
<div className="p-1.5 bg-white border border-slate-200 rounded text-slate-600">
<BookOpen className="w-5 h-5" />
</div>
<h2 className="text-sm sm:text-base lg:text-lg">User Manual & Workflow</h2>
</div>
{isManualOpen ? <ChevronUp className="w-5 h-5 text-slate-400" /> : <ChevronDown className="w-5 h-5 text-slate-400" />}
</div>
{isManualOpen && (
<div className="p-4 sm:p-8 space-y-8 text-slate-600 text-sm leading-relaxed">
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<div className="space-y-3">
<div className="flex items-center gap-2 mb-2">
<span className="flex items-center justify-center w-8 h-8 rounded-full bg-slate-900 text-white font-bold text-sm">1</span>
<h3 className="font-bold text-slate-900 text-base sm:text-lg">Define Factors</h3>
</div>
<p className="text-slate-500 pl-10">
Input the critical risk factors or barriers. For efficiency, download the CSV template and upload large datasets.
</p>
<p className="text-slate-500 pl-10 mt-1 text-xs italic">
Import function is available for JSON and CSV file.
</p>
<div className="pl-10 mt-2">
<div className="inline-flex gap-2 text-xs bg-slate-50 p-2 rounded border border-slate-200 text-slate-500">
<span>Tip: Use <strong>Import</strong> for bulk entry.</span>
</div>
</div>
</div>
<div className="space-y-3">
<div className="flex items-center gap-2 mb-2">
<span className="flex items-center justify-center w-8 h-8 rounded-full bg-slate-900 text-white font-bold text-sm">2</span>
<h3 className="font-bold text-slate-900 text-base sm:text-lg">Build SSIM</h3>
</div>
<p className="text-slate-500 pl-10">
Establish pairwise relationships in the Structural Self-Interaction Matrix.
</p>
<div className="pl-10 grid grid-cols-2 gap-2 text-xs">
<div className="bg-emerald-50 text-emerald-800 px-2 py-1 rounded border border-emerald-100 font-bold text-center">V (Forward)</div>
<div className="bg-amber-50 text-amber-800 px-2 py-1 rounded border border-amber-100 font-bold text-center">A (Reverse)</div>
<div className="bg-blue-50 text-blue-800 px-2 py-1 rounded border border-blue-100 font-bold text-center">X (Mutual)</div>
<div className="bg-slate-100 text-slate-500 px-2 py-1 rounded border border-slate-200 font-bold text-center">O (None)</div>
</div>
</div>
<div className="space-y-3">
<div className="flex items-center gap-2 mb-2">
<span className="flex items-center justify-center w-8 h-8 rounded-full bg-slate-900 text-white font-bold text-sm">3</span>
<h3 className="font-bold text-slate-900 text-base sm:text-lg">Analysis</h3>
</div>
<p className="text-slate-500 pl-10">
Automated generation of structural models:
</p>
<ul className="pl-14 list-disc space-y-1 text-slate-500">
<li>Reachability Matrix</li>
<li>Hierarchy Graph</li>
<li>Interrelationship Digraph</li>
<li>MICMAC Quadrants</li>
</ul>
</div>
</div>
</div>
)}
</div>
<FactorInput
factors={factors}
setFactors={setFactors}
topic={topic}
onNext={goToSSIM}
/>
</div>
)}
{step === AppStep.FILL_SSIM && (
<SSIMGrid
factors={factors}
ssim={ssim}
setSsim={setSsim}
topic={topic}
onNext={calculateAndShowResults}
onBack={() => setStep(AppStep.DEFINE_FACTORS)}
/>
)}
{step === AppStep.ANALYSIS_RESULT && result && (
<ResultsView
factors={factors}
result={result}
onReset={resetAnalysis}
onBack={goToSSIM}
/>
)}
</main>
{/* Professional Footer */}
<footer className="w-full border-t border-slate-200 bg-white py-8 mt-auto print:hidden">
<div className="max-w-7xl mx-auto px-6 flex flex-col items-center">
<div className="flex items-center gap-2 mb-2 opacity-50">
<Construction className="w-4 h-4" />
<span className="text-sm font-bold tracking-wider uppercase">ISM Construction Tool</span>
</div>
<div className="text-xs text-slate-400 max-w-2xl text-center leading-relaxed">
Disclaimer: The ISM Tool is built with React, Tailwind and Gemini by Atlas Cheung for academic use only. The tool is intended for educational and research purposes to support Interpretive Structural Modelling analysis. Results generated by this application are provided as-is and should be critically evaluated before any practical use. This application collects no personal information.
</div>
<p className="text-xs text-slate-400 max-w-2xl text-center leading-relaxed">
Last Update: 9 February 2026
</p>
</div>
</footer>
</div>
);
};
export default App;