Skip to content

Commit 9f49bd4

Browse files
committed
done
1 parent 47a3b88 commit 9f49bd4

3 files changed

Lines changed: 393 additions & 9 deletions

File tree

src/components/BMDashboard/ToolPurchaseRequest/PurchaseForm.jsx

Lines changed: 257 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from 'react';
1+
import { useEffect, useState } from 'react';
22
import { useSelector } from 'react-redux';
33
import { useHistory } from 'react-router-dom';
44
import { toast } from 'react-toastify';
@@ -10,15 +10,94 @@ import { purchaseTools } from '~/actions/bmdashboard/toolActions';
1010

1111
import styles from './PurchaseForm.module.css';
1212

13+
const PRIORITY_ORDER = { Low: 1, Medium: 2, High: 3 };
14+
const RECENT_REQUEST_WINDOW_DAYS = 30;
15+
16+
function getObjectId(value) {
17+
if (!value) return '';
18+
if (typeof value === 'string') return value;
19+
if (typeof value === 'object' && value._id) return value._id;
20+
return '';
21+
}
22+
23+
function formatDate(dateValue) {
24+
if (!dateValue) return 'No prior requests';
25+
26+
const date = new Date(dateValue);
27+
28+
if (Number.isNaN(date.getTime())) {
29+
return 'No prior requests';
30+
}
31+
32+
return date.toLocaleDateString('en-US', {
33+
month: 'short',
34+
day: 'numeric',
35+
year: 'numeric',
36+
});
37+
}
38+
39+
function getDaysSince(dateValue) {
40+
if (!dateValue) return Number.POSITIVE_INFINITY;
41+
42+
const date = new Date(dateValue);
43+
44+
if (Number.isNaN(date.getTime())) {
45+
return Number.POSITIVE_INFINITY;
46+
}
47+
48+
return Math.floor((Date.now() - date.getTime()) / (1000 * 60 * 60 * 24));
49+
}
50+
51+
function getSuggestedPriority(records, availabilitySummary) {
52+
if (records.length) {
53+
const counts = records.reduce((acc, record) => {
54+
const recordPriority = record.priority || 'Low';
55+
const current = acc[recordPriority] || { count: 0, latest: 0 };
56+
const latest = new Date(record.date || 0).getTime();
57+
58+
acc[recordPriority] = {
59+
count: current.count + 1,
60+
latest: Math.max(current.latest, Number.isNaN(latest) ? 0 : latest),
61+
};
62+
63+
return acc;
64+
}, {});
65+
66+
return Object.entries(counts).sort((a, b) => {
67+
if (b[1].count !== a[1].count) return b[1].count - a[1].count;
68+
if (b[1].latest !== a[1].latest) return b[1].latest - a[1].latest;
69+
return PRIORITY_ORDER[b[0]] - PRIORITY_ORDER[a[0]];
70+
})[0][0];
71+
}
72+
73+
if (
74+
availabilitySummary.projectAvailableCount === 0 &&
75+
availabilitySummary.projectUsingCount > 0
76+
) {
77+
return 'High';
78+
}
79+
80+
if (
81+
availabilitySummary.projectAvailableCount === 0 &&
82+
availabilitySummary.globalAvailableCount > 0
83+
) {
84+
return 'Medium';
85+
}
86+
87+
return 'Low';
88+
}
89+
1390
export default function PurchaseForm() {
1491
const bmProjects = useSelector(state => state.bmProjects);
1592
const tools = useSelector(state => state.bmInvTypes.list);
93+
const toolInventory = useSelector(state => state.bmTools.toolslist || []);
1694
const history = useHistory();
1795

1896
const [projectId, setProjectId] = useState('');
1997
const [toolId, setToolId] = useState('');
2098
const [quantity, setQty] = useState('');
2199
const [priority, setPriority] = useState('Low');
100+
const [priorityTouched, setPriorityTouched] = useState(false);
22101
const [makeModel, setMakeModel] = useState('');
23102
const [estTime, setEstTime] = useState('');
24103
const [desc, setDesc] = useState('');
@@ -40,6 +119,105 @@ export default function PurchaseForm() {
40119
makeModel: Joi.string().allow(''),
41120
});
42121

122+
const sortedTools = [...tools].sort((a, b) => a.name.localeCompare(b.name));
123+
const selectedTool = sortedTools.find(tool => tool._id === toolId);
124+
125+
const selectedProjectInventory = projectId
126+
? toolInventory.filter(tool => getObjectId(tool.project) === projectId)
127+
: [];
128+
129+
const projectSpecificToolIds = new Set();
130+
131+
selectedProjectInventory.forEach(tool => {
132+
const itemTypeId = getObjectId(tool.itemType);
133+
134+
if (itemTypeId) {
135+
projectSpecificToolIds.add(itemTypeId);
136+
}
137+
});
138+
139+
sortedTools.forEach(tool => {
140+
const availableOnProject = (tool.available || []).some(
141+
item => getObjectId(item.project) === projectId,
142+
);
143+
const inUseOnProject = (tool.using || []).some(item => getObjectId(item.project) === projectId);
144+
145+
if (availableOnProject || inUseOnProject) {
146+
projectSpecificToolIds.add(tool._id);
147+
}
148+
});
149+
150+
const projectFocusedTools = sortedTools.filter(tool => projectSpecificToolIds.has(tool._id));
151+
const otherTools = sortedTools.filter(tool => !projectSpecificToolIds.has(tool._id));
152+
const selectedProjectName = bmProjects.find(project => project._id === projectId)?.name || '';
153+
154+
const matchingToolRecords = toolId
155+
? toolInventory.filter(tool => getObjectId(tool.itemType) === toolId)
156+
: [];
157+
const matchingProjectToolRecords = matchingToolRecords.filter(
158+
tool => getObjectId(tool.project) === projectId,
159+
);
160+
161+
const projectPurchaseHistory = matchingProjectToolRecords
162+
.flatMap(tool => tool.purchaseRecord || [])
163+
.sort((a, b) => new Date(b.date || 0) - new Date(a.date || 0));
164+
165+
const crossProjectPurchaseHistory = matchingToolRecords
166+
.flatMap(tool => tool.purchaseRecord || [])
167+
.sort((a, b) => new Date(b.date || 0) - new Date(a.date || 0));
168+
169+
const projectAvailableCount = (selectedTool?.available || []).filter(
170+
item => getObjectId(item.project) === projectId,
171+
).length;
172+
const projectUsingCount = (selectedTool?.using || []).filter(
173+
item => getObjectId(item.project) === projectId,
174+
).length;
175+
const globalAvailableCount = selectedTool?.available?.length || 0;
176+
const globalUsingCount = selectedTool?.using?.length || 0;
177+
178+
const availabilitySummary = {
179+
projectAvailableCount,
180+
projectUsingCount,
181+
globalAvailableCount,
182+
globalUsingCount,
183+
};
184+
185+
const suggestedPriority = getSuggestedPriority(projectPurchaseHistory, availabilitySummary);
186+
const lastRequestedRecord = projectPurchaseHistory[0] || crossProjectPurchaseHistory[0] || null;
187+
const recentDuplicateRecord =
188+
projectPurchaseHistory.find(
189+
record => getDaysSince(record.date) <= RECENT_REQUEST_WINDOW_DAYS,
190+
) || null;
191+
192+
const availabilityStatus = (() => {
193+
if (!selectedTool) return 'Select a tool to view availability.';
194+
if (!projectId)
195+
return `${globalAvailableCount} available and ${globalUsingCount} in use across all projects.`;
196+
if (projectAvailableCount > 0) {
197+
return `${projectAvailableCount} available on ${selectedProjectName}.`;
198+
}
199+
if (projectUsingCount > 0) {
200+
return `${projectUsingCount} currently in use on ${selectedProjectName}.`;
201+
}
202+
if (globalAvailableCount > 0) {
203+
return `${globalAvailableCount} available on other projects.`;
204+
}
205+
if (globalUsingCount > 0) {
206+
return `${globalUsingCount} currently in use across other projects.`;
207+
}
208+
return 'No active inventory found for this tool yet.';
209+
})();
210+
211+
useEffect(() => {
212+
if (!priorityTouched) {
213+
setPriority(suggestedPriority);
214+
}
215+
}, [suggestedPriority, priorityTouched]);
216+
217+
useEffect(() => {
218+
setPriorityTouched(false);
219+
}, [projectId, toolId]);
220+
43221
const handleSubmit = async e => {
44222
e.preventDefault();
45223
const { error } = schema.validate({
@@ -73,6 +251,7 @@ export default function PurchaseForm() {
73251
setToolId('');
74252
setQty('');
75253
setPriority('Low');
254+
setPriorityTouched(false);
76255
setMakeModel('');
77256
setEstTime('');
78257
setDesc('');
@@ -128,13 +307,70 @@ export default function PurchaseForm() {
128307
<option disabled hidden value="">
129308
{' '}
130309
</option>
131-
{tools.map(({ _id, name }) => (
132-
<option value={_id} key={_id}>
133-
{name}
134-
</option>
135-
))}
310+
{!projectId &&
311+
sortedTools.map(({ _id, name }) => (
312+
<option value={_id} key={_id}>
313+
{name}
314+
</option>
315+
))}
316+
{!!projectId && !!projectFocusedTools.length && (
317+
<optgroup label={`Suggested for ${selectedProjectName}`}>
318+
{projectFocusedTools.map(({ _id, name }) => (
319+
<option value={_id} key={_id}>
320+
{name}
321+
</option>
322+
))}
323+
</optgroup>
324+
)}
325+
{!!projectId && !!otherTools.length && (
326+
<optgroup label={projectFocusedTools.length ? 'Other Tool Types' : 'All Tool Types'}>
327+
{otherTools.map(({ _id, name }) => (
328+
<option value={_id} key={_id}>
329+
{name}
330+
</option>
331+
))}
332+
</optgroup>
333+
)}
136334
</Input>
335+
{projectId && (
336+
<FormText>
337+
{projectFocusedTools.length
338+
? 'Tools already used, available, or previously requested on this project are grouped first.'
339+
: 'No prior tool history was found for this project, so the full catalog is shown.'}
340+
</FormText>
341+
)}
137342
</FormGroup>
343+
{toolId && (
344+
<section className={styles.toolInsightPanel}>
345+
<div className={styles.toolInsightHeader}>
346+
<h3>Selection Insights</h3>
347+
<span className={styles.toolInsightPriority}>
348+
Suggested priority: {suggestedPriority}
349+
</span>
350+
</div>
351+
<dl className={styles.toolInsightGrid}>
352+
<div>
353+
<dt>Availability Status</dt>
354+
<dd>{availabilityStatus}</dd>
355+
</div>
356+
<div>
357+
<dt>Last Requested</dt>
358+
<dd>{formatDate(lastRequestedRecord?.date)}</dd>
359+
</div>
360+
<div>
361+
<dt>Common Use Case</dt>
362+
<dd>{selectedTool?.description || 'No usage guidance added for this tool yet.'}</dd>
363+
</div>
364+
</dl>
365+
{recentDuplicateRecord && selectedProjectName && (
366+
<p className={styles.toolWarning}>
367+
Warning: this tool was already requested for {selectedProjectName} on{' '}
368+
{formatDate(recentDuplicateRecord.date)} with {recentDuplicateRecord.priority}{' '}
369+
priority.
370+
</p>
371+
)}
372+
</section>
373+
)}
138374
<div className={`${styles.purchaseToolFlexGroup}`}>
139375
<FormGroup className={`${styles.flexGroupQty}`}>
140376
<Label for="input-quantity">Quantity</Label>
@@ -159,6 +395,7 @@ export default function PurchaseForm() {
159395
value={priority}
160396
onChange={({ currentTarget }) => {
161397
setValidationError('');
398+
setPriorityTouched(true);
162399
setPriority(currentTarget.value);
163400
}}
164401
>
@@ -210,6 +447,20 @@ export default function PurchaseForm() {
210447
<div className={`${styles.purchaseToolError}`}>
211448
{validationError && <p>{validationError}</p>}
212449
</div>
450+
{!!projectId && !!toolId && (
451+
<div className={styles.confirmationSummary}>
452+
<h3>Ready to Submit</h3>
453+
<p>
454+
You are requesting {quantity || '0'} {selectedTool?.name || 'tool'} for{' '}
455+
{selectedProjectName || 'the selected project'} with {priority} priority.
456+
</p>
457+
<p>
458+
Expected use: {estTime || 'not provided yet'}.
459+
{makeModel ? ` Preferred make/model: ${makeModel}.` : ''}
460+
</p>
461+
<p>{desc || 'Add a short usage description before submitting.'}</p>
462+
</div>
463+
)}
213464
<div className={`${styles.purchaseToolButtons}`}>
214465
<Button
215466
type="button"

0 commit comments

Comments
 (0)