Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions frontend/src/components/inventory/InventoryFiltersPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ interface FiltersPanelProps {
showAddButton: boolean;
totalCount: number;
itemCount: number;
autoFocusSearch?: boolean;
}

export const InventoryFiltersPanel = ({
Expand Down Expand Up @@ -84,6 +85,7 @@ export const InventoryFiltersPanel = ({
showAddButton,
totalCount,
itemCount,
autoFocusSearch = false,
}: FiltersPanelProps) => {
return (
<>
Expand All @@ -94,6 +96,7 @@ export const InventoryFiltersPanel = ({
label="Search by name, note, or location"
placeholder="Prospector, Lorville, armors..."
value={filters.search}
autoFocus={autoFocusSearch}
onChange={(e) => setFilters((prev) => ({ ...prev, search: e.target.value }))}
/>
</Grid>
Expand Down
23 changes: 18 additions & 5 deletions frontend/src/components/inventory/InventoryInlineRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ interface InventoryInlineRowProps {
inlineLocationInput: string;
locationEditing: boolean;
inlineSaving: boolean;
inlineSaved?: boolean;
inlineError?: string | null;
isDirty: boolean;
focusController: FocusController<string, 'location' | 'quantity' | 'save'>;
Expand All @@ -54,6 +55,7 @@ export const InventoryInlineRow = ({
inlineLocationInput,
locationEditing,
inlineSaving,
inlineSaved,
inlineError,
isDirty,
focusController,
Expand Down Expand Up @@ -279,11 +281,6 @@ export const InventoryInlineRow = ({
Large quantity entered - verify value.
</Typography>
)}
{inlineError && (
<Typography variant="caption" color="error">
{inlineError}
</Typography>
)}
</Stack>
<Stack
direction="row"
Expand All @@ -295,6 +292,22 @@ export const InventoryInlineRow = ({
flexWrap: 'nowrap',
}}
>
<Box sx={{ minHeight: 18, display: 'flex', alignItems: 'center' }}>
{inlineError && (
<Typography variant="caption" color="error">
{inlineError}
</Typography>
)}
{!inlineError && inlineSaved && (
<Chip
label="Saved"
size="small"
color="success"
variant="outlined"
sx={{ height: 18, fontSize: 11 }}
/>
)}
</Box>
{density === 'compact' && isDirty && (
<Chip
label="Unsaved"
Expand Down
51 changes: 41 additions & 10 deletions frontend/src/pages/Inventory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ const InventoryPage = () => {
Record<string, { locationId: number | ''; quantity: number | '' }>
>({});
const [inlineSaving, setInlineSaving] = useState<Set<string>>(new Set());
const [inlineSaved, setInlineSaved] = useState<Set<string>>(new Set());
const [inlineError, setInlineError] = useState<Record<string, string | null>>({});
const [allLocations, setAllLocations] = useState<{ id: number; name: string }[]>([]);
const [inlineLocationInputs, setInlineLocationInputs] = useState<Record<string, string>>({});
Expand Down Expand Up @@ -150,14 +151,6 @@ const InventoryPage = () => {
api?: string | null;
}>({});
const [newRowSaving, setNewRowSaving] = useState(false);
const itemGridTemplate = useMemo(
() =>
density === 'compact'
? { xs: '1fr', md: '2fr 1fr 0.8fr 0.8fr auto' }
: { xs: '1fr', md: '2fr 1fr 1fr 1fr auto' },
[density],
);

const debouncedSearch = useDebounce(filters.search, 350);
const debouncedCatalogSearch = useDebounce(catalogSearch, 350);
const debouncedNewItemSearch = useDebounce(newRowItemInput, 300);
Expand Down Expand Up @@ -962,6 +955,21 @@ const InventoryPage = () => {
const nextSaving = new Set(inlineSaving);
nextSaving.add(item.id);
setInlineSaving(nextSaving);
const prevItem =
items.find((entry) => entry.id === item.id) ??
({
...item,
} as InventoryRecord);
const updatedItem: InventoryRecord = {
...item,
locationId: parsedLocationId,
quantity: parsedQuantity,
locationName:
allLocations.find((loc) => loc.id === parsedLocationId)?.name || item.locationName,
};
setItems((prev) =>
prev.map((entry) => (entry.id === item.id ? updatedItem : entry)),
);

try {
if (viewMode === 'org' && selectedOrgId) {
Expand All @@ -975,10 +983,24 @@ const InventoryPage = () => {
quantity: parsedQuantity,
});
}
await fetchInventory();
setInlineSaved((prev) => {
const next = new Set(prev);
next.add(item.id.toString());
setTimeout(() => {
setInlineSaved((current) => {
const copy = new Set(current);
copy.delete(item.id.toString());
return copy;
});
}, 1200);
return next;
});
return true;
} catch (err) {
console.error('Inline save failed', err);
setItems((prev) =>
prev.map((entry) => (entry.id === item.id ? prevItem : entry)),
);
setInlineError((prev) => ({
...prev,
[item.id]: 'Unable to save. Please try again.',
Expand Down Expand Up @@ -1461,6 +1483,7 @@ const InventoryPage = () => {
'');
const saving = inlineSaving.has(item.id);
const errorText = inlineError[item.id];
const saved = inlineSaved.has(item.id.toString());

return (
<InventoryInlineRow
Expand All @@ -1472,6 +1495,7 @@ const InventoryPage = () => {
inlineLocationInput={inlineLocationValue}
locationEditing={Boolean(locationEditing[rowKey])}
inlineSaving={saving}
inlineSaved={saved}
inlineError={errorText}
isDirty={isDirty}
focusController={focusController}
Expand Down Expand Up @@ -1596,6 +1620,7 @@ const InventoryPage = () => {
showAddButton={viewMode === 'personal'}
totalCount={totalCount}
itemCount={items.length}
autoFocusSearch
/>
</CardContent>
</Card>
Expand Down Expand Up @@ -1650,7 +1675,13 @@ const InventoryPage = () => {
<Box
sx={{
display: 'grid',
gridTemplateColumns: itemGridTemplate,
gridTemplateColumns: {
xs: '1fr',
md:
density === 'compact'
? '2fr 1fr 1fr 1fr auto'
: '2fr 1fr 1fr 1fr auto',
},
alignItems: 'center',
color: 'text.secondary',
fontSize: 12,
Expand Down