Skip to content

Commit ea5657b

Browse files
authored
Merge pull request #92 from pineapaul/sandbox
Redesign system configuration page with modern UI and integrate exist…
2 parents 7e498af + 77b1bd2 commit ea5657b

File tree

8 files changed

+950
-52
lines changed

8 files changed

+950
-52
lines changed

app/components/Icon.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ import {
8686
faHome,
8787
faRefresh,
8888
faCircleXmark,
89-
faFileExport
89+
faFileExport,
90+
faListCheck
9091
} from '@fortawesome/free-solid-svg-icons'
9192

9293
interface IconProps {
@@ -166,6 +167,7 @@ export default function Icon({ name, size = 20, className = '' }: IconProps) {
166167
'user-group': faUsers,
167168
'people-group': faUsers,
168169
'shield-exclamation': faShieldHalved,
170+
'shield-halved': faShieldHalved,
169171
'message-circle': faMessage,
170172
send: faPaperPlane,
171173
comments: faComments,
@@ -194,6 +196,7 @@ export default function Icon({ name, size = 20, className = '' }: IconProps) {
194196
'x-circle': faCircleXmark,
195197
'circle-xmark': faCircleXmark,
196198
'file-export': faFileExport,
199+
'list-check': faListCheck,
197200
}
198201

199202
const icon = icons[name]
@@ -279,6 +282,7 @@ function getFallbackEmoji(name: string): string {
279282
'user-group': '👥',
280283
'people-group': '👥',
281284
'shield-exclamation': '🛡️',
285+
'shield-halved': '🛡️',
282286
'shield-x': '🛡️❌',
283287
'message-circle': '💬',
284288
send: '📤',
@@ -307,6 +311,7 @@ function getFallbackEmoji(name: string): string {
307311
'x-circle': '❌',
308312
'circle-xmark': '❌',
309313
'file-export': '📤',
314+
'list-check': '✅',
310315
}
311316
return emojiMap[name] || '📄'
312317
}

app/components/RiskMatrix.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,20 +170,20 @@ export default function RiskMatrix({
170170

171171
React.useEffect(() => {
172172
if (selected) setActive({ l: selected.likelihoodIndex, c: selected.consequenceIndex });
173-
}, [selected?.likelihoodIndex, selected?.consequenceIndex]);
173+
}, [selected]);
174174

175175
const currentRiskCoords = useMemo(() =>
176176
currentRisk
177177
? findMatrixCoordinates(currentRisk.likelihoodRating, currentRisk.consequenceRating, likelihoodLabels, consequenceLabels)
178178
: null,
179-
[currentRisk?.likelihoodRating, currentRisk?.consequenceRating, likelihoodLabels, consequenceLabels]
179+
[currentRisk, likelihoodLabels, consequenceLabels]
180180
);
181181

182182
const residualRiskCoords = useMemo(() =>
183183
residualRisk
184184
? findMatrixCoordinates(residualRisk.residualLikelihood, residualRisk.residualConsequence, likelihoodLabels, consequenceLabels)
185185
: null,
186-
[residualRisk?.residualLikelihood, residualRisk?.residualConsequence, likelihoodLabels, consequenceLabels]
186+
[residualRisk, likelihoodLabels, consequenceLabels]
187187
);
188188

189189
const handleSelect = (lIndex: number, cIndex: number) => {

app/hooks/useToast.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
'use client'
2+
3+
import { useState, useCallback, useEffect, useRef } from 'react'
4+
import type { ToastProps } from '@/app/components/Toast'
5+
6+
export interface ToastOptions {
7+
type: 'success' | 'error' | 'warning' | 'info'
8+
title: string
9+
message?: string
10+
duration?: number
11+
}
12+
13+
export function useToast() {
14+
const [toasts, setToasts] = useState<ToastProps[]>([])
15+
const timeoutsRef = useRef<Map<string, NodeJS.Timeout>>(new Map())
16+
17+
// Cleanup function to clear all timeouts
18+
const clearAllTimeouts = useCallback(() => {
19+
timeoutsRef.current.forEach(timeout => clearTimeout(timeout))
20+
timeoutsRef.current.clear()
21+
}, [])
22+
23+
const removeToast = useCallback((id: string) => {
24+
// Clear the timeout if it exists
25+
const timeout = timeoutsRef.current.get(id)
26+
if (timeout) {
27+
clearTimeout(timeout)
28+
timeoutsRef.current.delete(id)
29+
}
30+
31+
setToasts(prev => prev.filter(toast => toast.id !== id))
32+
}, [])
33+
34+
// Auto-remove toast after duration
35+
const scheduleToastRemoval = useCallback((id: string, duration: number) => {
36+
const timeout = setTimeout(() => {
37+
removeToast(id)
38+
}, duration)
39+
40+
timeoutsRef.current.set(id, timeout)
41+
}, [removeToast])
42+
43+
const showToast = useCallback((options: ToastOptions) => {
44+
const id = crypto.randomUUID()
45+
const newToast: ToastProps = {
46+
id,
47+
type: options.type,
48+
title: options.title,
49+
message: options.message,
50+
duration: options.duration || 5000,
51+
onClose: (toastId: string) => {
52+
removeToast(toastId)
53+
}
54+
}
55+
56+
setToasts(prev => [...prev, newToast])
57+
58+
// Schedule auto-removal
59+
scheduleToastRemoval(id, newToast.duration || 5000)
60+
}, [scheduleToastRemoval, removeToast])
61+
62+
// Cleanup on unmount
63+
useEffect(() => {
64+
return () => {
65+
clearAllTimeouts()
66+
}
67+
}, [clearAllTimeouts])
68+
69+
return {
70+
toasts,
71+
showToast,
72+
removeToast
73+
}
74+
}

app/inventory/information-assets/page.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { useState, useEffect } from 'react'
3+
import { useState, useEffect, useCallback } from 'react'
44
import { useRouter } from 'next/navigation'
55
import DataTable, { Column } from '@/app/components/DataTable'
66
import Icon from '@/app/components/Icon'
@@ -49,11 +49,7 @@ export default function InformationAssetsPage() {
4949
'availability', 'criticality', 'additionalInfo', 'actions'
5050
]))
5151

52-
useEffect(() => {
53-
fetchAssets()
54-
}, [])
55-
56-
const fetchAssets = async () => {
52+
const fetchAssets = useCallback(async () => {
5753
try {
5854
setLoading(true)
5955
const response = await fetch('/api/information-assets')
@@ -70,7 +66,11 @@ export default function InformationAssetsPage() {
7066
} finally {
7167
setLoading(false)
7268
}
73-
}
69+
}, [])
70+
71+
useEffect(() => {
72+
fetchAssets()
73+
}, [fetchAssets])
7474

7575
const allColumns: Column[] = [
7676
{

app/inventory/third-parties/new/page.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { useState, useEffect } from 'react'
3+
import { useState, useEffect, useCallback } from 'react'
44
import { useRouter } from 'next/navigation'
55
import Icon from '@/app/components/Icon'
66
import Link from 'next/link'
@@ -32,11 +32,7 @@ export default function NewThirdPartyPage() {
3232
status: 'Active'
3333
})
3434

35-
useEffect(() => {
36-
fetchInformationAssets()
37-
}, [])
38-
39-
const fetchInformationAssets = async () => {
35+
const fetchInformationAssets = useCallback(async () => {
4036
try {
4137
const response = await fetch('/api/information-assets')
4238
const result = await response.json()
@@ -47,7 +43,11 @@ export default function NewThirdPartyPage() {
4743
} catch (error) {
4844
console.error('Error fetching information assets:', error)
4945
}
50-
}
46+
}, [])
47+
48+
useEffect(() => {
49+
fetchInformationAssets()
50+
}, [fetchInformationAssets])
5151

5252
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
5353
const { name, value } = e.target

app/inventory/third-parties/page.tsx

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { useState, useEffect } from 'react'
3+
import { useState, useEffect, useCallback } from 'react'
44
import DataTable, { Column } from '@/app/components/DataTable'
55
import Icon from '@/app/components/Icon'
66
import Link from 'next/link'
@@ -54,23 +54,7 @@ export default function ThirdPartiesPage() {
5454
sortOrder: 'asc'
5555
})
5656

57-
useEffect(() => {
58-
fetchThirdParties()
59-
fetchInformationAssets()
60-
}, [])
61-
62-
// Handle filter changes
63-
const handleFilterChange = (newFilters: any) => {
64-
setFilters(prev => ({ ...prev, ...newFilters }))
65-
fetchThirdParties(1, { ...filters, ...newFilters })
66-
}
67-
68-
// Handle pagination
69-
const handlePageChange = (newPage: number) => {
70-
fetchThirdParties(newPage, filters)
71-
}
72-
73-
const fetchThirdParties = async (page = 1, newFilters = filters) => {
57+
const fetchThirdParties = useCallback(async (page = 1, newFilters = filters) => {
7458
try {
7559
setLoading(true)
7660

@@ -101,19 +85,35 @@ export default function ThirdPartiesPage() {
10185
} finally {
10286
setLoading(false)
10387
}
104-
}
88+
}, [filters, pagination.limit])
10589

106-
const fetchInformationAssets = async () => {
90+
const fetchInformationAssets = useCallback(async () => {
10791
try {
10892
const response = await fetch('/api/information-assets')
10993
const result = await response.json()
11094

11195
if (result.success) {
11296
setInformationAssets(result.data)
11397
}
114-
} catch (error) {
115-
console.error('Error fetching information assets:', error)
98+
} catch (err) {
99+
console.error('Error fetching information assets:', err)
116100
}
101+
}, [])
102+
103+
useEffect(() => {
104+
fetchThirdParties()
105+
fetchInformationAssets()
106+
}, [fetchThirdParties, fetchInformationAssets])
107+
108+
// Handle filter changes
109+
const handleFilterChange = (newFilters: any) => {
110+
setFilters(prev => ({ ...prev, ...newFilters }))
111+
fetchThirdParties(1, { ...filters, ...newFilters })
112+
}
113+
114+
// Handle pagination
115+
const handlePageChange = (newPage: number) => {
116+
fetchThirdParties(newPage, filters)
117117
}
118118

119119

app/risk-management/risks/new/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ export default function NewRisk() {
182182
fetchInformationAssets()
183183
fetchSOAControls()
184184
generateAndSetRiskId()
185-
}, [])
185+
}, [showToast])
186186

187187
// Modal functions
188188
const openAssetModal = () => {

0 commit comments

Comments
 (0)