11import { useState , useMemo } from 'react' ;
2- import styles from './ResourceManagement.module.css' ;
32import { useSelector } from 'react-redux' ;
4- import { ChevronLeft , ChevronRight , Calendar } from 'lucide-react' ;
5- import { MOCK_RESOURCES } from './MockData' ;
3+ import { ChevronLeft , ChevronRight , Calendar , X } from 'lucide-react' ;
64import { toast } from 'react-toastify' ;
75import PropTypes from 'prop-types' ;
86import * as XLSX from 'xlsx' ;
7+ import styles from './ResourceManagement.module.css' ;
8+ import { MOCK_RESOURCES } from './MockData' ;
99
1010function SearchBar ( { onSortToggle, darkMode, searchTerm, onSearchTermChange } ) {
1111 return (
@@ -26,6 +26,7 @@ function SearchBar({ onSortToggle, darkMode, searchTerm, onSearchTermChange }) {
2626 ⇅
2727 </ button >
2828 </ div >
29+
2930 < div className = { styles . searchBarContainerRight } >
3031 < input
3132 type = "text"
@@ -39,30 +40,147 @@ function SearchBar({ onSortToggle, darkMode, searchTerm, onSearchTermChange }) {
3940 ) ;
4041}
4142
43+ function AddLogModal ( { isOpen, onClose, onAdd } ) {
44+ const darkMode = useSelector ( state => state . theme . darkMode ) ;
45+ const [ formData , setFormData ] = useState ( {
46+ user : '' ,
47+ timeDuration : '' ,
48+ facilities : '' ,
49+ materials : '' ,
50+ date : '' ,
51+ } ) ;
52+ const [ errors , setErrors ] = useState ( { } ) ;
53+
54+ const handleChange = e => {
55+ const { name, value } = e . target ;
56+
57+ setFormData ( prev => ( {
58+ ...prev ,
59+ [ name ] : value ,
60+ } ) ) ;
61+
62+ setErrors ( prev => ( {
63+ ...prev ,
64+ [ name ] : '' ,
65+ } ) ) ;
66+ } ;
67+
68+ const validateForm = ( ) => {
69+ const newErrors = { } ;
70+ const timeRegex = / ^ ( [ 0 - 1 ] \d | 2 [ 0 - 3 ] ) : ( [ 0 - 5 ] \d ) : ( [ 0 - 5 ] \d ) $ / ;
71+ const textRegex = / ^ [ a - z A - Z \s ] + $ / ;
72+
73+ if ( ! formData . user . trim ( ) ) newErrors . user = 'User is required' ;
74+
75+ if ( ! formData . timeDuration . trim ( ) ) {
76+ newErrors . timeDuration = 'Time/Duration is required' ;
77+ } else if ( ! timeRegex . test ( formData . timeDuration ) ) {
78+ newErrors . timeDuration = 'Time must be in HH:MM:SS format' ;
79+ }
80+
81+ if ( ! formData . facilities . trim ( ) ) {
82+ newErrors . facilities = 'Facilities is required' ;
83+ } else if ( ! textRegex . test ( formData . facilities ) ) {
84+ newErrors . facilities = 'Facilities should contain only letters' ;
85+ }
86+
87+ if ( ! formData . materials . trim ( ) ) {
88+ newErrors . materials = 'Materials is required' ;
89+ } else if ( ! textRegex . test ( formData . materials ) ) {
90+ newErrors . materials = 'Materials should contain only letters' ;
91+ }
92+
93+ if ( ! formData . date ) newErrors . date = 'Date is required' ;
94+
95+ return newErrors ;
96+ } ;
97+
98+ const handleSubmit = e => {
99+ e . preventDefault ( ) ;
100+
101+ const validationErrors = validateForm ( ) ;
102+
103+ if ( Object . keys ( validationErrors ) . length > 0 ) {
104+ setErrors ( validationErrors ) ;
105+ return ;
106+ }
107+
108+ onAdd ( formData ) ;
109+
110+ setFormData ( {
111+ user : '' ,
112+ timeDuration : '' ,
113+ facilities : '' ,
114+ materials : '' ,
115+ date : '' ,
116+ } ) ;
117+
118+ setErrors ( { } ) ;
119+ onClose ( ) ;
120+ } ;
121+
122+ if ( ! isOpen ) return null ;
123+
124+ return (
125+ < div className = { styles . modalOverlay } >
126+ < div className = { `${ styles . modalContent } ${ darkMode ? styles . modalContentDark : '' } ` } >
127+ < h3 > Add New Log</ h3 >
128+
129+ < form onSubmit = { handleSubmit } className = { styles . formContainer } >
130+ { [ 'user' , 'timeDuration' , 'facilities' , 'materials' ] . map ( field => (
131+ < div className = { styles . formGroup } key = { field } >
132+ < label htmlFor = { field } >
133+ { field === 'timeDuration'
134+ ? 'Time/Duration'
135+ : field . charAt ( 0 ) . toUpperCase ( ) + field . slice ( 1 ) }
136+ </ label >
137+ < input
138+ id = { field }
139+ name = { field }
140+ value = { formData [ field ] }
141+ onChange = { handleChange }
142+ className = { errors [ field ] ? styles . inputError : '' }
143+ />
144+ { errors [ field ] && < span className = { styles . errorText } > { errors [ field ] } </ span > }
145+ </ div >
146+ ) ) }
147+
148+ < div className = { styles . formGroup } >
149+ < label htmlFor = "resource-date" > Date</ label >
150+ < input
151+ id = "resource-date"
152+ name = "date"
153+ type = "date"
154+ value = { formData . date }
155+ onChange = { handleChange }
156+ className = { errors . date ? styles . inputError : '' }
157+ />
158+ { errors . date && < span className = { styles . errorText } > { errors . date } </ span > }
159+ </ div >
160+
161+ < div className = { styles . modalActions } >
162+ < button type = "submit" className = { styles . submitButton } >
163+ Save Log
164+ </ button >
165+ < button type = "button" onClick = { onClose } className = { styles . cancelButton } >
166+ Cancel
167+ </ button >
168+ </ div >
169+ </ form >
170+ </ div >
171+ </ div >
172+ ) ;
173+ }
174+
42175const Pagination = ( { totalPages, currentPage, setCurrentPage, darkMode } ) => {
43176 const getPaginationGroup = ( ) => {
44- let pages = [ ] ;
45- const threshold = 5 ;
46-
47- if ( totalPages <= threshold ) {
48- pages = Array . from ( { length : totalPages } , ( _ , i ) => i + 1 ) ;
49- } else if ( currentPage <= 3 ) {
50- pages = [ 1 , 2 , 3 , 4 , 5 , '...' , totalPages ] ;
51- } else if ( currentPage > totalPages - 3 ) {
52- pages = [
53- 1 ,
54- '...' ,
55- totalPages - 4 ,
56- totalPages - 3 ,
57- totalPages - 2 ,
58- totalPages - 1 ,
59- totalPages ,
60- ] ;
61- } else {
62- pages = [ 1 , '...' , currentPage - 1 , currentPage , currentPage + 1 , '...' , totalPages ] ;
177+ if ( totalPages <= 5 ) return Array . from ( { length : totalPages } , ( _ , i ) => i + 1 ) ;
178+ if ( currentPage <= 3 ) return [ 1 , 2 , 3 , 4 , 5 , '...' , totalPages ] ;
179+ if ( currentPage > totalPages - 3 ) {
180+ return [ 1 , '...' , totalPages - 4 , totalPages - 3 , totalPages - 2 , totalPages - 1 , totalPages ] ;
63181 }
64182
65- return pages ;
183+ return [ 1 , '...' , currentPage - 1 , currentPage , currentPage + 1 , '...' , totalPages ] ;
66184 } ;
67185
68186 return (
@@ -107,14 +225,24 @@ const Pagination = ({ totalPages, currentPage, setCurrentPage, darkMode }) => {
107225} ;
108226
109227function ResourceManagement ( ) {
110- const [ resources ] = useState ( MOCK_RESOURCES ) ;
111- const [ sortConfig , setSortConfig ] = useState ( { key : 'date' , direction : 'desc' } ) ;
112228 const darkMode = useSelector ( state => state . theme . darkMode ) ;
229+ const [ resources , setResources ] = useState ( MOCK_RESOURCES ) ;
230+ const [ sortConfig , setSortConfig ] = useState ( { key : 'date' , direction : 'desc' } ) ;
113231 const [ searchTerm , setSearchTerm ] = useState ( '' ) ;
114232 const [ currentPage , setCurrentPage ] = useState ( 1 ) ;
115233 const [ selectedIds , setSelectedIds ] = useState ( new Set ( ) ) ;
234+ const [ showModal , setShowModal ] = useState ( false ) ;
235+ const [ showToast , setShowToast ] = useState ( false ) ;
116236 const itemsPerPage = 5 ;
117237
238+ const columns = [
239+ { key : 'user' , label : 'User' } ,
240+ { key : 'timeDuration' , label : 'Time/Duration' } ,
241+ { key : 'facilities' , label : 'Facilities' } ,
242+ { key : 'materials' , label : 'Materials' } ,
243+ { key : 'date' , label : 'Date' } ,
244+ ] ;
245+
118246 const onSearchTermChange = e => {
119247 setSearchTerm ( e . target . value ) ;
120248 setCurrentPage ( 1 ) ;
@@ -126,19 +254,20 @@ function ResourceManagement() {
126254 if ( ! term ) return resources ;
127255
128256 return resources . filter (
129- r =>
130- r . user . toLowerCase ( ) . includes ( term ) ||
131- r . facilities . toLowerCase ( ) . includes ( term ) ||
132- r . materials . toLowerCase ( ) . includes ( term ) ,
257+ resource =>
258+ resource . user . toLowerCase ( ) . includes ( term ) ||
259+ resource . facilities . toLowerCase ( ) . includes ( term ) ||
260+ resource . materials . toLowerCase ( ) . includes ( term ) ||
261+ resource . date . toLowerCase ( ) . includes ( term ) ,
133262 ) ;
134263 } , [ resources , searchTerm ] ) ;
135264
136265 const sortedResources = useMemo ( ( ) => {
137266 const sortableItems = [ ...filteredResources ] ;
138267
139268 sortableItems . sort ( ( a , b ) => {
140- const valA = sortConfig . key === 'date' ? a . timestamp : a [ sortConfig . key ] ?. toLowerCase ( ) ;
141- const valB = sortConfig . key === 'date' ? b . timestamp : b [ sortConfig . key ] ?. toLowerCase ( ) ;
269+ const valA = sortConfig . key === 'date' ? a . timestamp ?? 0 : a [ sortConfig . key ] ?. toLowerCase ( ) ;
270+ const valB = sortConfig . key === 'date' ? b . timestamp ?? 0 : b [ sortConfig . key ] ?. toLowerCase ( ) ;
142271
143272 if ( valA < valB ) return sortConfig . direction === 'asc' ? - 1 : 1 ;
144273 if ( valA > valB ) return sortConfig . direction === 'asc' ? 1 : - 1 ;
@@ -151,34 +280,27 @@ function ResourceManagement() {
151280
152281 const totalPages = Math . ceil ( sortedResources . length / itemsPerPage ) ;
153282
154- const columns = [
155- { key : 'user' , label : 'User' } ,
156- { key : 'timeDuration' , label : 'Time/Duration' } ,
157- { key : 'facilities' , label : 'Facilities' } ,
158- { key : 'materials' , label : 'Materials' } ,
159- { key : 'date' , label : 'Date' } ,
160- ] ;
161-
162283 const toggleSelect = id => {
163284 setSelectedIds ( prev => {
164285 const updated = new Set ( prev ) ;
165286
166- if ( updated . has ( id ) ) {
167- updated . delete ( id ) ;
168- } else {
169- updated . add ( id ) ;
170- }
287+ if ( updated . has ( id ) ) updated . delete ( id ) ;
288+ else updated . add ( id ) ;
171289
172290 return updated ;
173291 } ) ;
174292 } ;
175293
176294 const toggleSelectAll = e => {
177- setSelectedIds ( e . target . checked ? new Set ( sortedResources . map ( r => r . id ) ) : new Set ( ) ) ;
295+ setSelectedIds (
296+ e . target . checked ? new Set ( sortedResources . map ( resource => resource . id ) ) : new Set ( ) ,
297+ ) ;
178298 } ;
179299
180300 const getExportRows = ( ) =>
181- selectedIds . size > 0 ? sortedResources . filter ( r => selectedIds . has ( r . id ) ) : sortedResources ;
301+ selectedIds . size > 0
302+ ? sortedResources . filter ( resource => selectedIds . has ( resource . id ) )
303+ : sortedResources ;
182304
183305 const exportCSV = rows => {
184306 const header = columns . map ( col => col . label ) . join ( ',' ) ;
@@ -228,11 +350,8 @@ function ResourceManagement() {
228350 return ;
229351 }
230352
231- if ( format === 'csv' ) {
232- exportCSV ( rows ) ;
233- } else {
234- exportXLSX ( rows ) ;
235- }
353+ if ( format === 'csv' ) exportCSV ( rows ) ;
354+ else exportXLSX ( rows ) ;
236355 } ;
237356
238357 const requestSort = key => {
@@ -252,6 +371,18 @@ function ResourceManagement() {
252371 } ) ) ;
253372 } ;
254373
374+ const handleAddLog = newLog => {
375+ const newResource = {
376+ id : resources . length + 1 ,
377+ ...newLog ,
378+ date : 'Just now' ,
379+ timestamp : Date . now ( ) ,
380+ } ;
381+
382+ setResources ( prev => [ newResource , ...prev ] ) ;
383+ setShowToast ( true ) ;
384+ } ;
385+
255386 return (
256387 < div
257388 className = { `${ styles . resourceManagementDashboard } ${
@@ -262,7 +393,7 @@ function ResourceManagement() {
262393 < h2 > Used Resources</ h2 >
263394
264395 < div className = { styles . actionButtons } >
265- < button type = "button" className = { styles . addLogButton } >
396+ < button type = "button" className = { styles . addLogButton } onClick = { ( ) => setShowModal ( true ) } >
266397 Add New Log
267398 </ button >
268399
@@ -352,19 +483,15 @@ function ResourceManagement() {
352483 < div className = { `${ styles . resourceItemDetail } ${ styles . colUser } ` } >
353484 { resource . user }
354485 </ div >
355-
356486 < div className = { `${ styles . resourceItemDetail } ${ styles . colDuration } ` } >
357487 { resource . timeDuration }
358488 </ div >
359-
360489 < div className = { `${ styles . resourceItemDetail } ${ styles . colFacilities } ` } >
361490 { resource . facilities }
362491 </ div >
363-
364492 < div className = { `${ styles . resourceItemDetail } ${ styles . colMaterials } ` } >
365493 { resource . materials }
366494 </ div >
367-
368495 < div className = { `${ styles . resourceItemDetail } ${ styles . colDate } ` } >
369496 < Calendar size = { 14 } className = { styles . calendarIcon } /> { resource . date }
370497 </ div >
@@ -379,6 +506,22 @@ function ResourceManagement() {
379506 setCurrentPage = { setCurrentPage }
380507 darkMode = { darkMode }
381508 />
509+
510+ < AddLogModal isOpen = { showModal } onClose = { ( ) => setShowModal ( false ) } onAdd = { handleAddLog } />
511+
512+ { showToast && (
513+ < div className = { `${ styles . toast } ${ darkMode ? styles . toastDark : '' } ` } >
514+ < span > ✅ Log saved successfully!</ span >
515+ < button
516+ type = "button"
517+ className = { styles . toastCloseButton }
518+ onClick = { ( ) => setShowToast ( false ) }
519+ aria-label = "Close notification"
520+ >
521+ < X size = { 16 } aria-hidden = "true" />
522+ </ button >
523+ </ div >
524+ ) }
382525 </ div >
383526 ) ;
384527}
@@ -394,6 +537,12 @@ SearchBar.defaultProps = {
394537 darkMode : false ,
395538} ;
396539
540+ AddLogModal . propTypes = {
541+ isOpen : PropTypes . bool . isRequired ,
542+ onClose : PropTypes . func . isRequired ,
543+ onAdd : PropTypes . func . isRequired ,
544+ } ;
545+
397546Pagination . propTypes = {
398547 totalPages : PropTypes . number . isRequired ,
399548 currentPage : PropTypes . number . isRequired ,
0 commit comments