|
1
|
import { useState } from "react"; |
|
2
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; |
|
3
|
import { Button } from "@/components/ui/button"; |
|
4
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; |
|
5
|
import { apiRequest } from "@/lib/queryClient"; |
|
6
|
import { |
|
7
|
Bookmark, |
|
8
|
X, |
|
9
|
AlertTriangle, |
|
10
|
Wrench, |
|
11
|
Clock, |
|
12
|
Sparkles, |
|
13
|
CheckCircle2, |
|
14
|
Info, |
|
15
|
ShieldAlert, |
|
16
|
Share2, |
|
17
|
Printer, |
|
18
|
Laptop, |
|
19
|
Monitor, |
|
20
|
Cpu, |
|
21
|
Code, |
|
22
|
Eraser, |
|
23
|
MoveUp, |
|
24
|
ExternalLink, |
|
25
|
ChevronRight, |
|
26
|
Zap, |
|
27
|
Gauge, |
|
28
|
ListOrdered, |
|
29
|
FileCheck2, |
|
30
|
SlidersHorizontal, |
|
31
|
Check |
|
32
|
} from "lucide-react"; |
|
33
|
import type { RepairGuide, DeviceModel, RepairStep } from "@shared/schema"; |
|
34
|
import { cn } from "@/lib/utils"; |
|
35
|
import { useToast } from "@/hooks/use-toast"; |
|
36
|
|
|
37
|
interface GuideDetailModalProps { |
|
38
|
guideId: string; |
|
39
|
onClose: () => void; |
|
40
|
isPage?: boolean; |
|
41
|
} |
|
42
|
|
|
43
|
export default function GuideDetailModal({ guideId, onClose, isPage = false }: GuideDetailModalProps) { |
|
44
|
const queryClient = useQueryClient(); |
|
45
|
const { toast } = useToast(); |
|
46
|
const [completedSteps, setCompletedSteps] = useState<number[]>([]); |
|
47
|
const [checkedSubSteps, setCheckedSubSteps] = useState<Record<string, boolean>>({}); |
|
48
|
|
|
49
|
const { data: guide, isLoading, error } = useQuery<RepairGuide>({ |
|
50
|
queryKey: ["/api/repair-guides", guideId], |
|
51
|
queryFn: () => fetch(`/api/repair-guides/${guideId}`).then(res => res.json()), |
|
52
|
enabled: !!guideId, |
|
53
|
}); |
|
54
|
|
|
55
|
const { data: compatibleModels = [] } = useQuery<DeviceModel[]>({ |
|
56
|
queryKey: ["/api/guides", guideId, "models"], |
|
57
|
queryFn: () => fetch(`/api/guides/${guideId}/models`).then(res => res.json()), |
|
58
|
enabled: !!guideId, |
|
59
|
}); |
|
60
|
|
|
61
|
const bookmarkMutation = useMutation({ |
|
62
|
mutationFn: () => apiRequest("PATCH", `/api/repair-guides/${guideId}/bookmark`), |
|
63
|
onSuccess: () => { |
|
64
|
queryClient.invalidateQueries({ queryKey: ["/api/repair-guides", guideId] }); |
|
65
|
queryClient.invalidateQueries({ queryKey: ["api", "repair-guides"] }); |
|
66
|
toast({ |
|
67
|
title: guide?.isBookmarked ? "Removed from Bookmarks" : "Saved to Bookmarks", |
|
68
|
description: guide?.isBookmarked ? "Guide removed from your favorites." : "Guide added to your saved offline repair library.", |
|
69
|
}); |
|
70
|
}, |
|
71
|
}); |
|
72
|
|
|
73
|
const toggleStep = (stepNumber: number) => { |
|
74
|
setCompletedSteps(prev => |
|
75
|
prev.includes(stepNumber) |
|
76
|
? prev.filter(s => s !== stepNumber) |
|
77
|
: [...prev, stepNumber] |
|
78
|
); |
|
79
|
}; |
|
80
|
|
|
81
|
const toggleSubStep = (stepNumber: number, subIndex: number, e: React.MouseEvent) => { |
|
82
|
e.stopPropagation(); |
|
83
|
const key = `${stepNumber}-${subIndex}`; |
|
84
|
setCheckedSubSteps(prev => ({ |
|
85
|
...prev, |
|
86
|
[key]: !prev[key] |
|
87
|
})); |
|
88
|
}; |
|
89
|
|
|
90
|
const handleCopyLink = () => { |
|
91
|
const url = `${window.location.origin}/guide/${guideId}`; |
|
92
|
navigator.clipboard.writeText(url); |
|
93
|
toast({ |
|
94
|
title: "Link Copied", |
|
95
|
description: "Direct guide URL has been copied to your clipboard.", |
|
96
|
}); |
|
97
|
}; |
|
98
|
|
|
99
|
const handlePrint = () => { |
|
100
|
window.print(); |
|
101
|
}; |
|
102
|
|
|
103
|
if (isLoading) { |
|
104
|
const SkeletonUI = ( |
|
105
|
<div className="p-6 md:p-8 space-y-6 animate-pulse"> |
|
106
|
<div className="h-8 bg-muted rounded-2xl w-3/4" /> |
|
107
|
<div className="h-4 bg-muted rounded-xl w-1/2" /> |
|
108
|
<div className="grid grid-cols-3 gap-4 pt-4"> |
|
109
|
<div className="h-20 bg-muted rounded-2xl" /> |
|
110
|
<div className="h-20 bg-muted rounded-2xl" /> |
|
111
|
<div className="h-20 bg-muted rounded-2xl" /> |
|
112
|
</div> |
|
113
|
<div className="space-y-4 pt-4"> |
|
114
|
<div className="h-32 bg-muted rounded-2xl" /> |
|
115
|
<div className="h-32 bg-muted rounded-2xl" /> |
|
116
|
</div> |
|
117
|
</div> |
|
118
|
); |
|
119
|
|
|
120
|
if (isPage) return <div className="bg-card rounded-3xl border border-border/50">{SkeletonUI}</div>; |
|
121
|
return ( |
|
122
|
<Dialog open={true} onOpenChange={() => onClose()}> |
|
123
|
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto p-0 rounded-3xl border-border/60 bg-card"> |
|
124
|
{SkeletonUI} |
|
125
|
</DialogContent> |
|
126
|
</Dialog> |
|
127
|
); |
|
128
|
} |
|
129
|
|
|
130
|
if (error || !guide) { |
|
131
|
const ErrorUI = ( |
|
132
|
<div className="p-8 text-center space-y-4"> |
|
133
|
<div className="h-12 w-12 rounded-full bg-destructive/10 text-destructive flex items-center justify-center mx-auto"> |
|
134
|
<ShieldAlert className="h-6 w-6" /> |
|
135
|
</div> |
|
136
|
<h3 className="text-lg font-bold">Failed to load repair guide</h3> |
|
137
|
<p className="text-sm text-muted-foreground">The requested manual could not be retrieved from the database.</p> |
|
138
|
<Button onClick={onClose} variant="outline" className="rounded-full">Close</Button> |
|
139
|
</div> |
|
140
|
); |
|
141
|
|
|
142
|
if (isPage) return <div className="bg-card rounded-3xl border border-border/50">{ErrorUI}</div>; |
|
143
|
return ( |
|
144
|
<Dialog open={true} onOpenChange={() => onClose()}> |
|
145
|
<DialogContent className="max-w-md p-0 rounded-3xl border-border/60 bg-card">{ErrorUI}</DialogContent> |
|
146
|
</Dialog> |
|
147
|
); |
|
148
|
} |
|
149
|
|
|
150
|
const getDifficultyColor = (diff: string) => { |
|
151
|
switch (diff) { |
|
152
|
case "easy": return "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/20"; |
|
153
|
case "medium": return "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20"; |
|
154
|
case "hard": return "bg-rose-500/10 text-rose-600 dark:text-rose-400 border-rose-500/20"; |
|
155
|
default: return "bg-muted text-muted-foreground"; |
|
156
|
} |
|
157
|
}; |
|
158
|
|
|
159
|
const getCategoryTheme = (cat: string) => { |
|
160
|
switch (cat) { |
|
161
|
case "hardware": return { label: "Hardware Repair", icon: Cpu, badge: "bg-indigo-500/10 text-indigo-600 dark:text-indigo-400 border-indigo-500/20" }; |
|
162
|
case "software": return { label: "Software / OS Protocol", icon: Code, badge: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/20" }; |
|
163
|
case "cleaning": return { label: "Thermal & Cleaning", icon: Eraser, badge: "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20" }; |
|
164
|
case "upgrades": return { label: "Performance Upgrade", icon: MoveUp, badge: "bg-purple-500/10 text-purple-600 dark:text-purple-400 border-purple-500/20" }; |
|
165
|
default: return { label: "Technical Guide", icon: Wrench, badge: "bg-primary/10 text-primary border-primary/20" }; |
|
166
|
} |
|
167
|
}; |
|
168
|
|
|
169
|
const catTheme = getCategoryTheme(guide.category); |
|
170
|
const CategoryIcon = catTheme.icon; |
|
171
|
const steps = (guide.steps || []) as RepairStep[]; |
|
172
|
|
|
173
|
const content = ( |
|
174
|
<div className="space-y-6"> |
|
175
|
{/* Header Banner */} |
|
176
|
<div className="p-6 md:p-8 border-b border-border/40 space-y-4 bg-muted/20"> |
|
177
|
<div className="flex flex-wrap items-center justify-between gap-3"> |
|
178
|
<div className="flex flex-wrap items-center gap-2"> |
|
179
|
<span className={cn("text-[11px] font-bold uppercase tracking-wider px-3 py-1 rounded-full border flex items-center gap-1.5", catTheme.badge)}> |
|
180
|
<CategoryIcon className="h-3.5 w-3.5" /> |
|
181
|
{catTheme.label} |
|
182
|
</span> |
|
183
|
<span className={cn("text-[11px] font-bold uppercase tracking-wider px-3 py-1 rounded-full border", getDifficultyColor(guide.difficulty))}> |
|
184
|
{guide.difficulty} Difficulty |
|
185
|
</span> |
|
186
|
<span className="text-[11px] font-semibold text-muted-foreground bg-muted/80 px-3 py-1 rounded-full border border-border/50 flex items-center gap-1"> |
|
187
|
{guide.deviceType === "laptop" ? <Laptop className="h-3.5 w-3.5" /> : <Monitor className="h-3.5 w-3.5" />} |
|
188
|
{guide.deviceType === "laptop" ? "Laptop & Ultrabook" : "Desktop PC & Tower"} |
|
189
|
</span> |
|
190
|
</div> |
|
191
|
|
|
192
|
<div className="flex items-center gap-2"> |
|
193
|
<Button |
|
194
|
variant="outline" |
|
195
|
size="sm" |
|
196
|
onClick={() => bookmarkMutation.mutate()} |
|
197
|
className={cn("h-8 rounded-xl text-xs gap-1.5 border-border/60 transition-colors", guide.isBookmarked && "bg-amber-500/10 text-amber-600 border-amber-500/30")} |
|
198
|
> |
|
199
|
<Bookmark className={cn("h-3.5 w-3.5", guide.isBookmarked && "fill-amber-500 text-amber-500")} /> |
|
200
|
<span>{guide.isBookmarked ? "Saved" : "Save"}</span> |
|
201
|
</Button> |
|
202
|
<Button |
|
203
|
variant="outline" |
|
204
|
size="sm" |
|
205
|
onClick={handleCopyLink} |
|
206
|
className="h-8 rounded-xl text-xs gap-1.5 border-border/60" |
|
207
|
> |
|
208
|
<Share2 className="h-3.5 w-3.5" /> |
|
209
|
<span>Share</span> |
|
210
|
</Button> |
|
211
|
<Button |
|
212
|
variant="outline" |
|
213
|
size="sm" |
|
214
|
onClick={handlePrint} |
|
215
|
className="h-8 rounded-xl text-xs gap-1.5 border-border/60 hidden sm:flex" |
|
216
|
> |
|
217
|
<Printer className="h-3.5 w-3.5" /> |
|
218
|
<span>Print</span> |
|
219
|
</Button> |
|
220
|
{!isPage && ( |
|
221
|
<Button |
|
222
|
variant="ghost" |
|
223
|
size="icon" |
|
224
|
className="h-8 w-8 rounded-full ml-1" |
|
225
|
onClick={onClose} |
|
226
|
> |
|
227
|
<X className="h-5 w-5" /> |
|
228
|
</Button> |
|
229
|
)} |
|
230
|
</div> |
|
231
|
</div> |
|
232
|
|
|
233
|
<DialogTitle className="text-xl sm:text-2xl md:text-3xl font-extrabold tracking-tight text-foreground leading-snug"> |
|
234
|
{guide.title} |
|
235
|
</DialogTitle> |
|
236
|
|
|
237
|
<p className="text-xs sm:text-sm text-muted-foreground leading-relaxed max-w-3xl"> |
|
238
|
{guide.description} |
|
239
|
</p> |
|
240
|
|
|
241
|
{/* Quick Bench Metrics */} |
|
242
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 pt-2"> |
|
243
|
<div className="p-3.5 rounded-2xl bg-card/60 border border-border/40 flex items-center gap-3"> |
|
244
|
<div className="h-9 w-9 rounded-xl bg-primary/10 text-primary flex items-center justify-center shrink-0"> |
|
245
|
<Clock className="h-4 w-4" /> |
|
246
|
</div> |
|
247
|
<div> |
|
248
|
<div className="text-[10px] font-bold text-muted-foreground uppercase">Estimated Time</div> |
|
249
|
<div className="text-xs font-bold text-foreground">{guide.estimatedTime}</div> |
|
250
|
</div> |
|
251
|
</div> |
|
252
|
|
|
253
|
<div className="p-3.5 rounded-2xl bg-card/60 border border-border/40 flex items-center gap-3"> |
|
254
|
<div className="h-9 w-9 rounded-xl bg-indigo-500/10 text-indigo-500 flex items-center justify-center shrink-0"> |
|
255
|
<Wrench className="h-4 w-4" /> |
|
256
|
</div> |
|
257
|
<div> |
|
258
|
<div className="text-[10px] font-bold text-muted-foreground uppercase">Bench Tools</div> |
|
259
|
<div className="text-xs font-bold text-foreground">{guide.toolsRequired ? guide.toolsRequired.length : 0} items</div> |
|
260
|
</div> |
|
261
|
</div> |
|
262
|
|
|
263
|
<div className="p-3.5 rounded-2xl bg-card/60 border border-border/40 flex items-center gap-3"> |
|
264
|
<div className="h-9 w-9 rounded-xl bg-purple-500/10 text-purple-500 flex items-center justify-center shrink-0"> |
|
265
|
<ListOrdered className="h-4 w-4" /> |
|
266
|
</div> |
|
267
|
<div> |
|
268
|
<div className="text-[10px] font-bold text-muted-foreground uppercase">Total Steps</div> |
|
269
|
<div className="text-xs font-bold text-foreground">{steps.length} detailed phases</div> |
|
270
|
</div> |
|
271
|
</div> |
|
272
|
|
|
273
|
<div className="p-3.5 rounded-2xl bg-card/60 border border-border/40 flex items-center gap-3"> |
|
274
|
<div className="h-9 w-9 rounded-xl bg-emerald-500/10 text-emerald-500 flex items-center justify-center shrink-0"> |
|
275
|
<CheckCircle2 className="h-4 w-4" /> |
|
276
|
</div> |
|
277
|
<div> |
|
278
|
<div className="text-[10px] font-bold text-muted-foreground uppercase">Bench Progress</div> |
|
279
|
<div className="text-xs font-bold text-foreground">{completedSteps.length} of {steps.length} complete</div> |
|
280
|
</div> |
|
281
|
</div> |
|
282
|
</div> |
|
283
|
</div> |
|
284
|
|
|
285
|
<div className="p-6 md:p-8 space-y-8"> |
|
286
|
{/* Safety Warnings Checklist */} |
|
287
|
{guide.safetyWarnings && guide.safetyWarnings.length > 0 && ( |
|
288
|
<div className="p-5 rounded-3xl bg-rose-500/10 border border-rose-500/30 text-rose-950 dark:text-rose-200 space-y-3"> |
|
289
|
<div className="flex items-center gap-2 font-bold text-rose-700 dark:text-rose-400 text-sm"> |
|
290
|
<AlertTriangle className="h-5 w-5" /> |
|
291
|
Critical Safety & ESD Directives (Must Review Before Disassembly) |
|
292
|
</div> |
|
293
|
<ul className="space-y-2 pl-2 text-xs sm:text-sm"> |
|
294
|
{guide.safetyWarnings.map((warning, idx) => ( |
|
295
|
<li key={idx} className="flex items-start gap-2.5"> |
|
296
|
<span className="h-2 w-2 rounded-full bg-rose-500 shrink-0 mt-1.5" /> |
|
297
|
<span className="leading-relaxed">{warning}</span> |
|
298
|
</li> |
|
299
|
))} |
|
300
|
</ul> |
|
301
|
</div> |
|
302
|
)} |
|
303
|
|
|
304
|
{/* Required Tools Grid */} |
|
305
|
{guide.toolsRequired && guide.toolsRequired.length > 0 && ( |
|
306
|
<div className="space-y-3"> |
|
307
|
<h3 className="text-sm font-bold uppercase tracking-wider text-foreground flex items-center gap-2"> |
|
308
|
<Wrench className="h-4 w-4 text-primary" /> |
|
309
|
Required Bench Equipment & Precision Drivers |
|
310
|
</h3> |
|
311
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-2.5"> |
|
312
|
{guide.toolsRequired.map((tool, idx) => ( |
|
313
|
<div key={idx} className="p-3 rounded-2xl bg-muted/40 border border-border/50 text-xs font-medium flex items-center gap-2.5"> |
|
314
|
<div className="h-2 w-2 rounded-full bg-primary shrink-0" /> |
|
315
|
<span className="text-foreground/90">{tool}</span> |
|
316
|
</div> |
|
317
|
))} |
|
318
|
</div> |
|
319
|
</div> |
|
320
|
)} |
|
321
|
|
|
322
|
{/* Compatible Device Models */} |
|
323
|
{compatibleModels.length > 0 && ( |
|
324
|
<div className="space-y-3"> |
|
325
|
<h3 className="text-sm font-bold uppercase tracking-wider text-foreground flex items-center gap-2"> |
|
326
|
<Sparkles className="h-4 w-4 text-indigo-500" /> |
|
327
|
Compatible Hardware Series |
|
328
|
</h3> |
|
329
|
<div className="flex flex-wrap gap-2"> |
|
330
|
{compatibleModels.map((model) => ( |
|
331
|
<span key={model.id} className="px-3 py-1.5 rounded-2xl bg-card border border-border/60 text-xs font-semibold text-foreground/90"> |
|
332
|
{model.name} {model.year ? `(${model.year})` : ""} |
|
333
|
</span> |
|
334
|
))} |
|
335
|
</div> |
|
336
|
</div> |
|
337
|
)} |
|
338
|
|
|
339
|
{/* Start-to-Finish Intimate Step-by-Step Procedure */} |
|
340
|
<div className="space-y-6"> |
|
341
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2 border-b border-border/40 pb-3"> |
|
342
|
<div> |
|
343
|
<h3 className="text-lg sm:text-xl font-bold flex items-center gap-2 text-foreground"> |
|
344
|
<FileCheck2 className="h-5 w-5 text-emerald-500" /> |
|
345
|
Start-to-Finish Comprehensive Repair Sequence |
|
346
|
</h3> |
|
347
|
<p className="text-xs text-muted-foreground"> |
|
348
|
Follow all technical phases in chronological order. Check off completed items to track bench progress. |
|
349
|
</p> |
|
350
|
</div> |
|
351
|
<span className="text-xs font-semibold text-primary bg-primary/10 px-3 py-1 rounded-full w-fit"> |
|
352
|
{completedSteps.length}/{steps.length} Steps Completed |
|
353
|
</span> |
|
354
|
</div> |
|
355
|
|
|
356
|
<div className="space-y-5"> |
|
357
|
{steps.map((step, sIdx) => { |
|
358
|
const isCompleted = completedSteps.includes(step.stepNumber); |
|
359
|
|
|
360
|
return ( |
|
361
|
<div |
|
362
|
key={step.stepNumber} |
|
363
|
onClick={() => toggleStep(step.stepNumber)} |
|
364
|
className={cn( |
|
365
|
"p-5 sm:p-6 rounded-3xl border transition-all duration-300 cursor-pointer space-y-4", |
|
366
|
isCompleted |
|
367
|
? "bg-emerald-500/5 border-emerald-500/30" |
|
368
|
: "bg-card/40 hover:bg-card/70 border-border/50 hover:border-primary/40" |
|
369
|
)} |
|
370
|
> |
|
371
|
{/* Step Header with Phase & Tool Specifications */} |
|
372
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 border-b border-border/30 pb-3"> |
|
373
|
<div className="flex items-center gap-3"> |
|
374
|
<div className={cn( |
|
375
|
"h-8 w-8 rounded-full flex items-center justify-center text-xs font-bold transition-colors shrink-0", |
|
376
|
isCompleted ? "bg-emerald-500 text-white" : "bg-primary text-primary-foreground" |
|
377
|
)}> |
|
378
|
{isCompleted ? <Check className="h-4 w-4" /> : step.stepNumber} |
|
379
|
</div> |
|
380
|
|
|
381
|
<div> |
|
382
|
{step.phase && ( |
|
383
|
<div className="text-[10px] font-bold uppercase tracking-wider text-primary"> |
|
384
|
{step.phase} |
|
385
|
</div> |
|
386
|
)} |
|
387
|
<h4 className={cn("font-bold text-base sm:text-lg text-foreground", isCompleted && "line-through text-muted-foreground")}> |
|
388
|
{step.title} |
|
389
|
</h4> |
|
390
|
</div> |
|
391
|
</div> |
|
392
|
|
|
393
|
<div className="flex flex-wrap items-center gap-2 pl-11 sm:pl-0"> |
|
394
|
{step.torqueSpec && ( |
|
395
|
<span className="text-[11px] font-mono font-bold px-2.5 py-0.5 rounded-full bg-indigo-500/10 text-indigo-600 dark:text-indigo-400 border border-indigo-500/20 flex items-center gap-1"> |
|
396
|
<Gauge className="h-3 w-3" /> |
|
397
|
{step.torqueSpec} |
|
398
|
</span> |
|
399
|
)} |
|
400
|
{step.toolRequired && ( |
|
401
|
<span className="text-[11px] font-semibold px-2.5 py-0.5 rounded-full bg-muted text-muted-foreground border border-border/40 flex items-center gap-1"> |
|
402
|
<Wrench className="h-3 w-3" /> |
|
403
|
{step.toolRequired} |
|
404
|
</span> |
|
405
|
)} |
|
406
|
<span className={cn( |
|
407
|
"text-[10px] font-bold uppercase tracking-wider px-2.5 py-0.5 rounded-full", |
|
408
|
isCompleted ? "bg-emerald-500/20 text-emerald-600 dark:text-emerald-400" : "bg-muted text-muted-foreground" |
|
409
|
)}> |
|
410
|
{isCompleted ? "Step Completed" : `Step ${step.stepNumber} of ${steps.length}`} |
|
411
|
</span> |
|
412
|
</div> |
|
413
|
</div> |
|
414
|
|
|
415
|
{/* Primary Detailed Technical Description */} |
|
416
|
<p className="text-xs sm:text-sm text-foreground/90 leading-relaxed pl-0 sm:pl-11"> |
|
417
|
{step.description} |
|
418
|
</p> |
|
419
|
|
|
420
|
{/* Intimate Sub-Steps Checklist */} |
|
421
|
{step.subSteps && step.subSteps.length > 0 && ( |
|
422
|
<div className="pl-0 sm:pl-11 space-y-2 pt-1"> |
|
423
|
<div className="text-[11px] font-bold uppercase tracking-wider text-muted-foreground flex items-center gap-1.5"> |
|
424
|
<ListOrdered className="h-3.5 w-3.5 text-primary" /> |
|
425
|
Granular Micro-Procedures Checklist |
|
426
|
</div> |
|
427
|
<div className="space-y-1.5"> |
|
428
|
{step.subSteps.map((sub, subIdx) => { |
|
429
|
const isSubChecked = !!checkedSubSteps[`${step.stepNumber}-${subIdx}`]; |
|
430
|
return ( |
|
431
|
<div |
|
432
|
key={subIdx} |
|
433
|
onClick={(e) => toggleSubStep(step.stepNumber, subIdx, e)} |
|
434
|
className={cn( |
|
435
|
"p-2.5 rounded-xl border text-xs flex items-start gap-2.5 transition-colors cursor-pointer", |
|
436
|
isSubChecked |
|
437
|
? "bg-emerald-500/10 border-emerald-500/30 text-muted-foreground" |
|
438
|
: "bg-background/60 hover:bg-muted/60 border-border/40 text-foreground/90" |
|
439
|
)} |
|
440
|
> |
|
441
|
<div className={cn( |
|
442
|
"h-4 w-4 rounded-md border flex items-center justify-center shrink-0 mt-0.5 transition-colors", |
|
443
|
isSubChecked ? "bg-emerald-500 border-emerald-500 text-white" : "border-border/80 bg-background" |
|
444
|
)}> |
|
445
|
{isSubChecked && <Check className="h-3 w-3" />} |
|
446
|
</div> |
|
447
|
<span className={cn("leading-relaxed", isSubChecked && "line-through")}> |
|
448
|
{sub} |
|
449
|
</span> |
|
450
|
</div> |
|
451
|
); |
|
452
|
})} |
|
453
|
</div> |
|
454
|
</div> |
|
455
|
)} |
|
456
|
|
|
457
|
{/* Step Image */} |
|
458
|
{step.imageUrl && ( |
|
459
|
<div className="pl-0 sm:pl-11 pt-2"> |
|
460
|
<img |
|
461
|
src={step.imageUrl} |
|
462
|
alt={step.title} |
|
463
|
className="rounded-2xl border border-border/40 w-full max-h-80 object-cover shadow-2xs" |
|
464
|
loading="lazy" |
|
465
|
/> |
|
466
|
</div> |
|
467
|
)} |
|
468
|
|
|
469
|
{/* Verification Checkpoints */} |
|
470
|
{step.checkpoints && step.checkpoints.length > 0 && ( |
|
471
|
<div className="ml-0 sm:ml-11 p-3.5 rounded-2xl bg-emerald-500/10 border border-emerald-500/20 text-xs text-emerald-950 dark:text-emerald-200 space-y-1.5"> |
|
472
|
<div className="font-bold flex items-center gap-1.5 text-emerald-700 dark:text-emerald-400"> |
|
473
|
<CheckCircle2 className="h-4 w-4" /> |
|
474
|
Verification Checkpoints (Confirm Before Proceeding) |
|
475
|
</div> |
|
476
|
<ul className="space-y-1 pl-5 list-disc text-emerald-900 dark:text-emerald-200"> |
|
477
|
{step.checkpoints.map((cp, cpIdx) => ( |
|
478
|
<li key={cpIdx}>{cp}</li> |
|
479
|
))} |
|
480
|
</ul> |
|
481
|
</div> |
|
482
|
)} |
|
483
|
|
|
484
|
{/* Warnings / Cautions */} |
|
485
|
{step.warnings && step.warnings.length > 0 && ( |
|
486
|
<div className="ml-0 sm:ml-11 p-3.5 rounded-2xl bg-rose-500/10 border border-rose-500/20 text-xs text-rose-950 dark:text-rose-200 flex items-start gap-2.5"> |
|
487
|
<AlertTriangle className="h-4 w-4 text-rose-500 shrink-0 mt-0.5" /> |
|
488
|
<div> |
|
489
|
<strong>Technician Caution:</strong> {step.warnings.join(" ")} |
|
490
|
</div> |
|
491
|
</div> |
|
492
|
)} |
|
493
|
|
|
494
|
{/* Pro Tips */} |
|
495
|
{step.tips && step.tips.length > 0 && ( |
|
496
|
<div className="ml-0 sm:ml-11 p-3.5 rounded-2xl bg-indigo-500/10 border border-indigo-500/20 text-xs text-indigo-950 dark:text-indigo-200 flex items-start gap-2.5"> |
|
497
|
<Info className="h-4 w-4 text-indigo-500 shrink-0 mt-0.5" /> |
|
498
|
<div> |
|
499
|
<strong>Bench Pro Tip:</strong> {step.tips.join(" ")} |
|
500
|
</div> |
|
501
|
</div> |
|
502
|
)} |
|
503
|
|
|
504
|
{/* Engineering Notes */} |
|
505
|
{step.notes && step.notes.length > 0 && ( |
|
506
|
<div className="ml-0 sm:ml-11 p-3 rounded-xl bg-muted/40 border border-border/40 text-xs text-muted-foreground"> |
|
507
|
<strong>Specification Note:</strong> {step.notes.join(" ")} |
|
508
|
</div> |
|
509
|
)} |
|
510
|
</div> |
|
511
|
); |
|
512
|
})} |
|
513
|
</div> |
|
514
|
</div> |
|
515
|
|
|
516
|
{/* Alternative Solutions & Post-Repair Validation */} |
|
517
|
{guide.alternativeSolutions && ( |
|
518
|
<div className="p-5 rounded-3xl bg-muted/40 border border-border/50 space-y-2"> |
|
519
|
<h4 className="font-bold text-sm text-foreground flex items-center gap-2"> |
|
520
|
<Sparkles className="h-4 w-4 text-primary" /> |
|
521
|
Post-Repair Quality Assurance & Alternative Methods |
|
522
|
</h4> |
|
523
|
<p className="text-xs sm:text-sm text-muted-foreground leading-relaxed"> |
|
524
|
{guide.alternativeSolutions} |
|
525
|
</p> |
|
526
|
</div> |
|
527
|
)} |
|
528
|
</div> |
|
529
|
|
|
530
|
{/* Footer Actions */} |
|
531
|
<div className="p-6 md:p-8 bg-muted/30 border-t border-border/40 flex flex-col sm:flex-row items-center justify-between gap-4"> |
|
532
|
<div className="flex items-center gap-2 text-xs text-emerald-600 dark:text-emerald-400 font-semibold"> |
|
533
|
<CheckCircle2 className="h-4 w-4" /> |
|
534
|
<span>JCR Certified Intimate Engineering Standard</span> |
|
535
|
</div> |
|
536
|
|
|
537
|
<div className="flex items-center gap-2 w-full sm:w-auto"> |
|
538
|
{!isPage && ( |
|
539
|
<Button |
|
540
|
variant="outline" |
|
541
|
onClick={onClose} |
|
542
|
className="rounded-2xl text-xs h-10 px-5 flex-1 sm:flex-initial border-border/60" |
|
543
|
> |
|
544
|
Close |
|
545
|
</Button> |
|
546
|
)} |
|
547
|
<Button |
|
548
|
onClick={() => { |
|
549
|
toast({ |
|
550
|
title: "Guide Exported", |
|
551
|
description: `Complete technical package for "${guide.title}" is saved for offline bench reference.`, |
|
552
|
}); |
|
553
|
}} |
|
554
|
className="rounded-2xl text-xs font-bold h-10 px-6 gap-2 bg-primary hover:bg-primary/90 text-primary-foreground flex-1 sm:flex-initial shadow-xs" |
|
555
|
> |
|
556
|
<Bookmark className="h-4 w-4" /> |
|
557
|
<span>Export Offline Guide</span> |
|
558
|
</Button> |
|
559
|
</div> |
|
560
|
</div> |
|
561
|
</div> |
|
562
|
); |
|
563
|
|
|
564
|
if (isPage) { |
|
565
|
return <div className="bg-card rounded-3xl border border-border/50 shadow-glass overflow-hidden">{content}</div>; |
|
566
|
} |
|
567
|
|
|
568
|
return ( |
|
569
|
<Dialog open={true} onOpenChange={() => onClose()}> |
|
570
|
<DialogContent className="max-w-4xl max-h-[92vh] overflow-y-auto p-0 rounded-3xl border-border/60 bg-card/95 backdrop-blur-2xl shadow-glass"> |
|
571
|
{content} |
|
572
|
</DialogContent> |
|
573
|
</Dialog> |
|
574
|
); |
|
575
|
} |