Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
4a8dcb8
WIP Admin Dashboard
willzoo Feb 23, 2026
931b353
WIP: Drawable boxes
willzoo Feb 23, 2026
1493ed6
Fix locations not putting
willzoo Feb 28, 2026
5a65d33
WIP: Protected locations
willzoo Feb 28, 2026
a22255a
Make inventory context get data solely from api
willzoo Feb 28, 2026
3c366a6
Make protected a database property
willzoo Feb 28, 2026
3affdf1
Remove workbench category - add "other" category
willzoo Feb 28, 2026
d0505e5
Add new location pane edits
willzoo Feb 28, 2026
17124a4
Make location edge drag fix
willzoo Feb 28, 2026
8c10316
Move top drawer/cabinet y pos down
willzoo Feb 28, 2026
ca11f7f
Fix zoom reset bug on add location
willzoo Feb 28, 2026
c072d78
WIP: Move items
willzoo Mar 1, 2026
57a93f1
Move working
willzoo Mar 1, 2026
06ed288
fix arrowhead direction
willzoo Mar 1, 2026
f79ed15
show +X on non shelves on add mode
willzoo Mar 1, 2026
171798f
Fix delete not applying, other minor things
willzoo Mar 2, 2026
f6fb6c9
Add move mode to admin dashboard
willzoo Mar 2, 2026
08ae476
Edit mode for locations
willzoo Mar 2, 2026
29cef13
WIP: Change history
willzoo Mar 3, 2026
367f6bb
Changes history and undo feature - currently testing this feature
willzoo Mar 3, 2026
921c1a3
fix seed data not seeding teams/cats
willzoo Mar 3, 2026
24e3408
add folder for tests and fix supply routes
willzoo Mar 3, 2026
c5ac0fb
Remove optimistic updates from inventory context
willzoo Mar 3, 2026
0767bb5
add button on admin dash to take back to user dash
willzoo Mar 4, 2026
958ca55
track locations in history
willzoo Mar 4, 2026
fd4c946
rename delete some to subtract for clarity
willzoo Mar 4, 2026
a5a3722
undone records fully removed
willzoo Mar 4, 2026
e2e6b68
finish renaming add to create
willzoo Mar 4, 2026
b7d7d39
renames
willzoo Mar 5, 2026
33b0359
add filter & dropdown
willzoo Mar 5, 2026
d720452
wip master filter
willzoo Mar 5, 2026
906d85a
fix filter bug
willzoo Mar 5, 2026
35aff37
add table sorting
willzoo Mar 5, 2026
dce2db8
wip: scrollbar fix
willzoo Mar 5, 2026
a170dd7
wip: fix scrollbar
willzoo Mar 5, 2026
3ade328
fix last modified to account for add/subtract
willzoo Mar 5, 2026
eb1fcaa
word wrapping for inventory table
willzoo Mar 5, 2026
9bcc07c
increase item preview height
willzoo Mar 5, 2026
86fa103
fix move mode
willzoo Mar 5, 2026
534823a
Display correct team name
willzoo Mar 5, 2026
bb9a69e
move admin
willzoo Mar 14, 2026
83c9bdb
Move all components into their own folder
willzoo Mar 14, 2026
5dac7fe
fix edge cases in history modal
willzoo Mar 14, 2026
ee96c31
Fix "Clear" button positioning
willzoo Mar 14, 2026
135d715
Add custom fields feature
willzoo Mar 17, 2026
3bb27d4
fix custom fields not pushing to db
willzoo Mar 23, 2026
2d6bcd4
replace default dialog with custom dialog
willzoo Mar 23, 2026
545d1eb
error handling for malformed undos
willzoo Mar 23, 2026
c542447
fix undo exceptions
willzoo Mar 23, 2026
09eabd1
WIP - Item types
willzoo Mar 23, 2026
7e147ef
Type feature almost done
willzoo Mar 23, 2026
1938c2e
wip - free place
willzoo Mar 29, 2026
cf14ef8
fix est conversion
willzoo Mar 29, 2026
0bf08ba
formatting free coordinate in menu
willzoo Mar 29, 2026
39a3eab
fix item type child desync bug
willzoo Mar 29, 2026
30b703c
Tune type dropdown
willzoo Mar 29, 2026
72c47d0
add respository pattern
willzoo Mar 29, 2026
461dba8
small fixes
willzoo Mar 31, 2026
91ebd37
allow duplicate names and add type modal to user
willzoo Mar 31, 2026
91a66b7
clean up backend
willzoo Apr 3, 2026
71a282b
touch up type feature
willzoo Apr 3, 2026
51919cb
add sign up feature
willzoo Apr 20, 2026
ab340d4
rename sql to tables
willzoo Apr 20, 2026
f1b8358
add delivery truck
willzoo Apr 20, 2026
6ee9495
add lost items box to seed
willzoo Apr 20, 2026
ab9ca8c
Add modifiable shelf count to locations
willzoo Apr 26, 2026
facc566
add unsorted box and editing for types
willzoo Apr 26, 2026
d84e254
more sensible type group headers
willzoo Apr 26, 2026
cf2b3e5
sort headers
willzoo Apr 26, 2026
76b758c
cleanup
willzoo Apr 30, 2026
124a6c4
more cleanup
willzoo Apr 30, 2026
ab6dcc0
fix shelves
willzoo Apr 30, 2026
d0167d7
filter across multiple dimensions
willzoo May 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
replace default dialog with custom dialog
  • Loading branch information
willzoo committed Mar 23, 2026
commit 2d6bcd4faba84af029da1932058d62cda0eedcc8
3 changes: 3 additions & 0 deletions milventory/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import AddModePreview from './components/Map/AddModePreview';
import Login from './components/Auth/Login';
import ErrorToast from './components/Common/ErrorToast';
import ConflictErrorModal from './components/Common/ConflictErrorModal';
import { BlockingDialogProvider } from './components/Common/BlockingDialogContext';
import HistoryModal from './components/History/HistoryModal';
import AdminDashboard from './components/Admin/AdminDashboard';
import { auth } from './api';
Expand Down Expand Up @@ -55,6 +56,7 @@ function App() {

return (
<BrowserRouter>
<BlockingDialogProvider>
<Routes>
<Route
path="/"
Expand All @@ -80,6 +82,7 @@ function App() {
/>
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</BlockingDialogProvider>
</BrowserRouter>
);
}
Expand Down
8 changes: 7 additions & 1 deletion milventory/src/components/Admin/CustomFieldsTable.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React, { useState, useEffect } from 'react';
import { admin } from '../../api';
import { useBlockingDialog } from '../Common/BlockingDialogContext';

const TYPES = ['text', 'number', 'date'];

const CustomFieldsTable = () => {
const { showConfirm } = useBlockingDialog();
const [definitions, setDefinitions] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
Expand Down Expand Up @@ -82,7 +84,11 @@ const CustomFieldsTable = () => {
};

const handleDelete = async (id) => {
if (!window.confirm('Delete this custom field definition? This will also remove this field and its value from all items that have it.')) return;
const ok = await showConfirm(
'Delete this custom field definition? This will also remove this field and its value from all items that have it.',
{ title: 'Delete custom field', danger: true, confirmLabel: 'Delete', cancelLabel: 'Cancel' }
);
if (!ok) return;
try {
setError(null);
await admin.deleteCustomFieldDefinition(id);
Expand Down
14 changes: 9 additions & 5 deletions milventory/src/components/Admin/LocationPreview.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useRef, useState, useEffect, useCallback } from 'react';
import { admin } from '../../api';
import { useBlockingDialog } from '../Common/BlockingDialogContext';

const LOCATION_TYPES = [
{ value: 'drawer', label: 'Drawer' },
Expand All @@ -12,6 +13,7 @@ const LOCATION_TYPES = [
];

const LocationPreview = ({ location, onClose, onDelete, leftPaneWidth, leftPaneCollapsed, onEditStart, onEditEnd, onPreviewUpdate, onEdgeDrag }) => {
const { showAlert, showConfirm } = useBlockingDialog();
const previewRef = useRef(null);
const [deleting, setDeleting] = useState(false);
const [editing, setEditing] = useState(false);
Expand Down Expand Up @@ -187,15 +189,17 @@ const LocationPreview = ({ location, onClose, onDelete, leftPaneWidth, leftPaneC
if (!location) return;

if (isProtected) {
alert(
await showAlert(
`This location is protected and is a permanent inventory location. ` +
`To delete it, you must edit the database directly to set protected = FALSE.`
`To delete it, you must edit the database directly to set protected = FALSE.`,
{ title: 'Protected location' }
);
return;
}

const confirmed = window.confirm(
`Are you sure you want to delete "${location.name}"? This action cannot be undone.`
const confirmed = await showConfirm(
`Are you sure you want to delete "${location.name}"? This action cannot be undone.`,
{ title: 'Delete location', danger: true, confirmLabel: 'Delete', cancelLabel: 'Cancel' }
);

if (!confirmed) return;
Expand All @@ -213,7 +217,7 @@ const LocationPreview = ({ location, onClose, onDelete, leftPaneWidth, leftPaneC
window.location.reload();
}, 300);
} catch (err) {
alert(`Failed to delete location: ${err.message}`);
await showAlert(`Failed to delete location: ${err.message}`, { title: 'Error' });
setDeleting(false);
}
};
Expand Down
39 changes: 39 additions & 0 deletions milventory/src/components/Common/BlockingDialog.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
.blocking-dialog-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.65);
backdrop-filter: blur(4px);
z-index: 11000;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
box-sizing: border-box;
}

.blocking-dialog.modal {
max-width: 420px;
width: 100%;
margin: 0;
}

.blocking-dialog-message {
margin: 0;
color: var(--text);
font-size: 0.9rem;
line-height: 1.5;
white-space: pre-wrap;
}

.blocking-dialog .modal-actions {
margin-top: 1rem;
}

.modal button.blocking-dialog-confirm-danger {
background: #c53030;
color: #fff;
}

.modal button.blocking-dialog-confirm-danger:hover {
opacity: 0.92;
}
136 changes: 136 additions & 0 deletions milventory/src/components/Common/BlockingDialogContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import React, {
createContext,
useContext,
useState,
useCallback,
useEffect,
} from 'react';
import './BlockingDialog.css';

const BlockingDialogContext = createContext(null);

/**
* App-wide blocking modal replacements for alert() / confirm().
* showAlert(message, { title?, confirmLabel? }) -> Promise<void>
* showConfirm(message, { title?, confirmLabel?, cancelLabel?, danger? }) -> Promise<boolean>
*/
export function BlockingDialogProvider({ children }) {
const [dialog, setDialog] = useState(null);

const showAlert = useCallback((message, options = {}) => {
const text = typeof message === 'string' ? message : String(message);
return new Promise((resolve) => {
setDialog({
kind: 'alert',
heading: options.title != null && options.title !== '' ? options.title : null,
message: text,
confirmLabel: options.confirmLabel || 'OK',
onConfirm: () => {
setDialog(null);
resolve();
},
});
});
}, []);

const showConfirm = useCallback((message, options = {}) => {
const text = typeof message === 'string' ? message : String(message);
return new Promise((resolve) => {
setDialog({
kind: 'confirm',
heading: options.title != null && options.title !== '' ? options.title : 'Confirm',
message: text,
confirmLabel: options.confirmLabel || 'OK',
cancelLabel: options.cancelLabel || 'Cancel',
danger: Boolean(options.danger),
onConfirm: () => {
setDialog(null);
resolve(true);
},
onCancel: () => {
setDialog(null);
resolve(false);
},
});
});
}, []);

useEffect(() => {
if (!dialog) return undefined;
const onKey = (e) => {
if (e.key === 'Escape') {
e.stopPropagation();
if (dialog.kind === 'confirm') dialog.onCancel();
else dialog.onConfirm();
}
};
document.addEventListener('keydown', onKey, true);
return () => document.removeEventListener('keydown', onKey, true);
}, [dialog]);

const handleOverlayMouseDown = (e) => {
if (e.target !== e.currentTarget) return;
if (dialog.kind === 'confirm') dialog.onCancel();
else dialog.onConfirm();
};

return (
<BlockingDialogContext.Provider value={{ showAlert, showConfirm }}>
{children}
{dialog && (
<div
className="blocking-dialog-overlay"
role="presentation"
onMouseDown={handleOverlayMouseDown}
>
<div
className="blocking-dialog modal"
role="alertdialog"
aria-modal="true"
aria-labelledby={dialog.heading ? 'blocking-dialog-heading' : undefined}
aria-label={!dialog.heading ? 'Notice' : undefined}
aria-describedby="blocking-dialog-desc"
onMouseDown={(e) => e.stopPropagation()}
>
{dialog.heading && (
<h3 id="blocking-dialog-heading">{dialog.heading}</h3>
)}
<p id="blocking-dialog-desc" className="blocking-dialog-message">
{dialog.message}
</p>
<div className="modal-actions">
{dialog.kind === 'confirm' && (
<button
type="button"
className="cancel"
onClick={dialog.onCancel}
>
{dialog.cancelLabel}
</button>
)}
<button
type="button"
className={
dialog.kind === 'confirm' && dialog.danger
? 'save blocking-dialog-confirm-danger'
: 'save'
}
onClick={dialog.onConfirm}
>
{dialog.confirmLabel}
</button>
</div>
</div>
</div>
)}
</BlockingDialogContext.Provider>
);
}

export function useBlockingDialog() {
const ctx = useContext(BlockingDialogContext);
if (!ctx) {
throw new Error('useBlockingDialog must be used within BlockingDialogProvider');
}
return ctx;
}
4 changes: 3 additions & 1 deletion milventory/src/components/History/HistoryTab.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React, { useState, useEffect } from 'react';
import { locationHistory } from '../../api';
import { useInventory } from '../../context/InventoryContext';
import { useBlockingDialog } from '../Common/BlockingDialogContext';

const HistoryTab = () => {
const { showAlert } = useBlockingDialog();
const [history, setHistory] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
Expand Down Expand Up @@ -41,7 +43,7 @@ const HistoryTab = () => {
await reloadSupplyLocations();
}
} catch (err) {
alert(err.message || 'Failed to undo action');
await showAlert(err.message || 'Failed to undo action', { title: 'Undo failed' });
} finally {
setUndoing(prev => {
const next = new Set(prev);
Expand Down
4 changes: 2 additions & 2 deletions milventory/src/components/History/HistoryTableRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,15 +130,15 @@ const HistoryTableRow = ({ entry, onUndo, index = 0, isAdmin = false }) => {

// Determine if entry can be undone
// Note: Undone entries are deleted entirely from the database, so we don't need to check undone status
// For users: only last 5 items can be undone
// For users: only the first row (most recent on this page) can be undone
// For admins: all items can be undone
const baseCanUndo = entry.historyType === 'location'
? entry.action_type !== 'CASCADED_SUBTRACT'
: entry.can_undo !== false;

const canUndo = isAdmin
? baseCanUndo // Admins can undo all items
: baseCanUndo && index < 5; // Users can only undo last 5 items
: baseCanUndo && index < 1; // Users can only undo the top row

return (
<tr className="history-row">
Expand Down
19 changes: 11 additions & 8 deletions milventory/src/components/Master/MasterCreateModal.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useState, useEffect, useRef } from 'react';
import { useInventory } from '../../context/InventoryContext';
import { getCategories, getTeams, api } from '../../api';
import { useBlockingDialog } from '../Common/BlockingDialogContext';

// Levenshtein distance for fuzzy search
const levenshteinDistance = (str1, str2) => {
Expand Down Expand Up @@ -171,6 +172,7 @@ const TagDropdown = ({ placeholder, selectedItems, availableItems, onSelect, onR

const MasterCreateModal = ({ isOpen, onClose }) => {
const { createMasterItem, masterInventoryItems } = useInventory();
const { showAlert } = useBlockingDialog();

const [name, setName] = useState('');
const [description, setDescription] = useState('');
Expand Down Expand Up @@ -252,7 +254,7 @@ const MasterCreateModal = ({ isOpen, onClose }) => {
}
}, [isOpen]);

const handleImageChange = (e) => {
const handleImageChange = async (e) => {
const file = e.target.files[0];
if (!file) {
setImage(null);
Expand All @@ -261,13 +263,13 @@ const MasterCreateModal = ({ isOpen, onClose }) => {
}

if (file.size > 10 * 1024 * 1024) {
alert('Image file size must be less than 10MB');
await showAlert('Image file size must be less than 10MB');
e.target.value = '';
return;
}

if (!file.type.startsWith('image/')) {
alert('Please select an image file');
await showAlert('Please select an image file');
e.target.value = '';
return;
}
Expand All @@ -279,8 +281,9 @@ const MasterCreateModal = ({ isOpen, onClose }) => {
setImagePreview(base64Data);
};
reader.onerror = () => {
alert('Error reading image file');
e.target.value = '';
showAlert('Error reading image file').then(() => {
e.target.value = '';
});
};
reader.readAsDataURL(file);
};
Expand All @@ -290,14 +293,14 @@ const MasterCreateModal = ({ isOpen, onClose }) => {
setImagePreview(null);
};

const handleSave = () => {
const handleSave = async () => {
if (name.trim()) {
if (masterInventoryItems.has(name.trim())) {
alert('An item with this name already exists. Please use a different name.');
await showAlert('An item with this name already exists. Please use a different name.');
return;
}
if (!areNumberCustomFieldsValid(customFields, customFieldDefinitions)) {
alert('Please enter a valid number in all number fields (or leave them empty).');
await showAlert('Please enter a valid number in all number fields (or leave them empty).');
return;
}

Expand Down
Loading