Skip to content
Merged
14 changes: 8 additions & 6 deletions frontend/src/pages/Inventory.editor-mode.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ describe('Inventory editor mode inline controls', () => {
);
});

it('hides the org add flow when org permissions do not include edit/admin', async () => {
it('hides the add button for view-only org users but still shows org inventory', async () => {
mockGetUserOrganizations.mockResolvedValue([
{
id: 1,
Expand All @@ -502,15 +502,17 @@ describe('Inventory editor mode inline controls', () => {
fireEvent.click(await screen.findByText('Test Org'));

await waitFor(() =>
expect(
screen.getByText(
'You do not have permission to add items to this organization.',
),
).toBeInTheDocument(),
expect(screen.getByText('Test Org')).toBeInTheDocument(),
);
Comment thread
GitAddRemote marked this conversation as resolved.

expect(
screen.queryByRole('button', { name: 'Add org item' }),
).not.toBeInTheDocument();
expect(
screen.queryByText(
'You do not have permission to add items to this organization.',
),
).not.toBeInTheDocument();
});

it('handles org add conflicts by loading the existing item and merging quantities', async () => {
Expand Down
61 changes: 49 additions & 12 deletions frontend/src/pages/Inventory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ const InventoryPage = () => {
const [orgOptions, setOrgOptions] = useState<{ id: number; name: string }[]>(
[],
);
const [allOrgOptions, setAllOrgOptions] = useState<
{ id: number; name: string }[]
>([]);
const orgsLoaded = useRef(false);
const [selectedOrgId, setSelectedOrgId] = useState<number | null>(() =>
readStoredOrgId(),
);
Expand Down Expand Up @@ -173,6 +177,7 @@ const InventoryPage = () => {
const [orgPermissionsError, setOrgPermissionsError] = useState<string | null>(
null,
);
const permissionsFetchedForOrgId = useRef<number | null>(null);

const [filters, setFilters] = useState({
search: '',
Expand Down Expand Up @@ -263,7 +268,7 @@ const InventoryPage = () => {
}, [density]);

useEffect(() => {
if (orgOptions.length === 0 || selectedOrgId === null) return;
if (!orgsLoaded.current || selectedOrgId === null) return;
const isValidOrg = orgOptions.some((org) => org.id === selectedOrgId);
if (!isValidOrg) {
setSelectedOrgId(null);
Expand Down Expand Up @@ -465,6 +470,14 @@ const InventoryPage = () => {
[],
);

const canViewOrgInventory = useMemo(
() =>
orgPermissions.includes(OrgPermission.CAN_VIEW_ORG_INVENTORY) ||
orgPermissions.includes(OrgPermission.CAN_EDIT_ORG_INVENTORY) ||
orgPermissions.includes(OrgPermission.CAN_ADMIN_ORG_INVENTORY),
[orgPermissions],
);

const canManageOrgInventory = useMemo(
() =>
orgPermissions.includes(OrgPermission.CAN_EDIT_ORG_INVENTORY) ||
Expand Down Expand Up @@ -517,7 +530,28 @@ const InventoryPage = () => {
id: entry.organization?.id ?? entry.organizationId,
name: entry.organization?.name ?? `Org #${entry.organizationId}`,
}));
setOrgOptions(mapped);
setAllOrgOptions(mapped);
const viewableOrgs = (
await Promise.all(
mapped.map(async (org) => {
try {
const perms = await permissionsService.getUserPermissions(
userId,
org.id,
);
const canView =
perms.includes(OrgPermission.CAN_VIEW_ORG_INVENTORY) ||
perms.includes(OrgPermission.CAN_EDIT_ORG_INVENTORY) ||
perms.includes(OrgPermission.CAN_ADMIN_ORG_INVENTORY);
return canView ? org : null;
} catch {
return null;
}
}),
)
).filter((org): org is { id: number; name: string } => org !== null);
setOrgOptions(viewableOrgs);
Comment thread
GitAddRemote marked this conversation as resolved.
Comment thread
GitAddRemote marked this conversation as resolved.
orgsLoaded.current = true;
} catch (err) {
console.error('Error loading organizations', err);
}
Expand Down Expand Up @@ -588,6 +622,13 @@ const InventoryPage = () => {
const offset = page * rowsPerPage;

if (isOrgMode && selectedOrgId) {
if (orgPermissionsLoading || permissionsFetchedForOrgId.current !== selectedOrgId || !canViewOrgInventory) {
setItems([]);
setTotalCount(0);
if (initialLoading) setInitialLoading(false);
setRefreshing(false);
return;
}
Comment thread
GitAddRemote marked this conversation as resolved.
const data = await inventoryService.getOrgInventory(selectedOrgId, {
gameId: GAME_ID,
search: debouncedSearch || undefined,
Expand Down Expand Up @@ -653,6 +694,8 @@ const InventoryPage = () => {
user,
isOrgMode,
selectedOrgId,
orgPermissionsLoading,
canViewOrgInventory,
filters.categoryId,
filters.locationId,
filters.sharedOnly,
Expand Down Expand Up @@ -879,16 +922,19 @@ const InventoryPage = () => {
if (viewMode !== 'org' || !user?.userId || !selectedOrgId) {
setOrgPermissions([]);
setOrgPermissionsError(null);
permissionsFetchedForOrgId.current = null;
return;
}
let isMounted = true;
permissionsFetchedForOrgId.current = null;
setOrgPermissionsLoading(true);
permissionsService
.getUserPermissions(user.userId, selectedOrgId)
.then((permissions) => {
if (isMounted) {
setOrgPermissions(permissions);
setOrgPermissionsError(null);
permissionsFetchedForOrgId.current = selectedOrgId;
}
})
.catch((err) => {
Expand Down Expand Up @@ -1809,7 +1855,7 @@ const InventoryPage = () => {
)
}
>
{orgOptions.map((org) => (
{allOrgOptions.map((org) => (
<MenuItem key={org.id} value={org.id}>
{org.name}
</MenuItem>
Expand Down Expand Up @@ -2171,15 +2217,6 @@ const InventoryPage = () => {
autoFocusSearch
disabled={inventoryBusy}
/>
{viewMode === 'org' &&
selectedOrgId &&
!canManageOrgInventory &&
!orgPermissionsLoading && (
<Alert severity="info" sx={{ mt: 2 }}>
You do not have permission to add items to this
organization.
</Alert>
)}
{orgPermissionsError && (
Comment thread
GitAddRemote marked this conversation as resolved.
<Alert severity="warning" sx={{ mt: 2 }}>
{orgPermissionsError}
Expand Down
Loading