Skip to content

Commit a040b11

Browse files
Merge pull request #4331 from OneCommunityGlobal/Neeraj_Resource_Management_Dashboard
Neeraj Fix Resource Management Dashboard
2 parents eda18e6 + 969032b commit a040b11

2 files changed

Lines changed: 421 additions & 157 deletions

File tree

src/components/ResourceManagement/ResourceManagement.jsx

Lines changed: 205 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { useState, useMemo } from 'react';
2-
import styles from './ResourceManagement.module.css';
32
import { 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';
64
import { toast } from 'react-toastify';
75
import PropTypes from 'prop-types';
86
import * as XLSX from 'xlsx';
7+
import styles from './ResourceManagement.module.css';
8+
import { MOCK_RESOURCES } from './MockData';
99

1010
function 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-zA-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+
42175
const 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

109227
function 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+
397546
Pagination.propTypes = {
398547
totalPages: PropTypes.number.isRequired,
399548
currentPage: PropTypes.number.isRequired,

0 commit comments

Comments
 (0)