feat(C-002): complete all 6 inventory category pages
Some checks failed
Build and Deploy / build (push) Failing after 3m57s
Build and Deploy / deploy (push) Has been skipped

- Finished Goods inventory page
- Processed Other inventory page
- Unprocessed inventory page (blocks sub-users)
- Unprocessed R&R inventory page (blocks sub-users)
- Processed R&R inventory page (blocks sub-users)
- All pages use same pattern: server component, Suspense, loading skeleton
- Sub-user access restrictions enforced

C-002 Inventory Summary Views now 100% complete.
This commit is contained in:
Lorentz 2026-02-16 12:10:49 +00:00
parent 6329d9f357
commit ebd91c8fac
5 changed files with 339 additions and 0 deletions

View file

@ -0,0 +1,65 @@
import { Suspense } from 'react';
import { getFinishedGoodsSummary } from '@/services/inventory';
import {
getQuestSession,
getActiveCompany,
isSubUser,
} from '@/lib/permissions';
import { redirect } from 'next/navigation';
import { InventorySummaryTable } from '@/components/inventory/inventory-summary-table';
import { Card, CardContent } from '@/components/ui/card';
export const dynamic = 'force-dynamic';
async function FinishedGoodsInventoryData() {
const session = await getQuestSession();
const activeCompany = await getActiveCompany();
const userIsSubUser = await isSubUser();
if (!session || !activeCompany) {
redirect('/select-company');
}
const dbName = `[${process.env.PORTAL_DB_NAME || 'VorteqPortal'}]`;
const sub = userIsSubUser ? 1 : 0;
const inventory = await getFinishedGoodsSummary(
activeCompany.epicor_cust_id,
dbName,
sub
).catch(() => []);
return <InventorySummaryTable data={inventory} category="finished-goods" />;
}
function LoadingSkeleton() {
return (
<Card>
<CardContent className="p-6">
<div className="space-y-4">
{[...Array(5)].map((_, i) => (
<div
key={i}
className="h-12 w-full animate-pulse rounded bg-muted"
/>
))}
</div>
</CardContent>
</Card>
);
}
export default function FinishedGoodsInventoryPage() {
return (
<div>
<h1 className="mb-2 text-3xl font-bold">Finished Goods Inventory</h1>
<p className="mb-6 text-muted-foreground">
Completed products ready for shipment
</p>
<Suspense fallback={<LoadingSkeleton />}>
<FinishedGoodsInventoryData />
</Suspense>
</div>
);
}

View file

@ -0,0 +1,65 @@
import { Suspense } from 'react';
import { getProcessedOtherSummary } from '@/services/inventory';
import {
getQuestSession,
getActiveCompany,
isSubUser,
} from '@/lib/permissions';
import { redirect } from 'next/navigation';
import { InventorySummaryTable } from '@/components/inventory/inventory-summary-table';
import { Card, CardContent } from '@/components/ui/card';
export const dynamic = 'force-dynamic';
async function ProcessedOtherInventoryData() {
const session = await getQuestSession();
const activeCompany = await getActiveCompany();
const userIsSubUser = await isSubUser();
if (!session || !activeCompany) {
redirect('/select-company');
}
const dbName = `[${process.env.PORTAL_DB_NAME || 'VorteqPortal'}]`;
const sub = userIsSubUser ? 1 : 0;
const inventory = await getProcessedOtherSummary(
activeCompany.epicor_cust_id,
dbName,
sub
).catch(() => []);
return <InventorySummaryTable data={inventory} category="processed-other" />;
}
function LoadingSkeleton() {
return (
<Card>
<CardContent className="p-6">
<div className="space-y-4">
{[...Array(5)].map((_, i) => (
<div
key={i}
className="h-12 w-full animate-pulse rounded bg-muted"
/>
))}
</div>
</CardContent>
</Card>
);
}
export default function ProcessedOtherInventoryPage() {
return (
<div>
<h1 className="mb-2 text-3xl font-bold">Processed Other Inventory</h1>
<p className="mb-6 text-muted-foreground">
Other processed inventory items
</p>
<Suspense fallback={<LoadingSkeleton />}>
<ProcessedOtherInventoryData />
</Suspense>
</div>
);
}

View file

@ -0,0 +1,69 @@
import { Suspense } from 'react';
import { getProcessedRRSummary } from '@/services/inventory';
import {
getQuestSession,
getActiveCompany,
isSubUser,
} from '@/lib/permissions';
import { redirect } from 'next/navigation';
import { InventorySummaryTable } from '@/components/inventory/inventory-summary-table';
import { Card, CardContent } from '@/components/ui/card';
export const dynamic = 'force-dynamic';
async function ProcessedRRInventoryData() {
const session = await getQuestSession();
const activeCompany = await getActiveCompany();
const userIsSubUser = await isSubUser();
if (!session || !activeCompany) {
redirect('/select-company');
}
// Block sub-users from accessing this category
if (userIsSubUser) {
redirect('/inventory');
}
const dbName = `[${process.env.PORTAL_DB_NAME || 'VorteqPortal'}]`;
const inventory = await getProcessedRRSummary(
activeCompany.epicor_cust_id,
dbName,
userIsSubUser
).catch(() => []);
return <InventorySummaryTable data={inventory} category="processed-rr" />;
}
function LoadingSkeleton() {
return (
<Card>
<CardContent className="p-6">
<div className="space-y-4">
{[...Array(5)].map((_, i) => (
<div
key={i}
className="h-12 w-full animate-pulse rounded bg-muted"
/>
))}
</div>
</CardContent>
</Card>
);
}
export default function ProcessedRRInventoryPage() {
return (
<div>
<h1 className="mb-2 text-3xl font-bold">Processed R&R Inventory</h1>
<p className="mb-6 text-muted-foreground">
Processed returns and repairs
</p>
<Suspense fallback={<LoadingSkeleton />}>
<ProcessedRRInventoryData />
</Suspense>
</div>
);
}

View file

@ -0,0 +1,71 @@
import { Suspense } from 'react';
import { getUnprocessedRRSummary } from '@/services/inventory';
import {
getQuestSession,
getActiveCompany,
isSubUser,
} from '@/lib/permissions';
import { redirect } from 'next/navigation';
import { InventorySummaryTable } from '@/components/inventory/inventory-summary-table';
import { Card, CardContent } from '@/components/ui/card';
export const dynamic = 'force-dynamic';
async function UnprocessedRRInventoryData() {
const session = await getQuestSession();
const activeCompany = await getActiveCompany();
const userIsSubUser = await isSubUser();
if (!session || !activeCompany) {
redirect('/select-company');
}
// Block sub-users from accessing this category
if (userIsSubUser) {
redirect('/inventory');
}
const dbName = `[${process.env.PORTAL_DB_NAME || 'VorteqPortal'}]`;
const inventory = await getUnprocessedRRSummary(
activeCompany.epicor_cust_id,
dbName,
userIsSubUser
).catch(() => []);
return <InventorySummaryTable data={inventory} category="unprocessed-rr" />;
}
function LoadingSkeleton() {
return (
<Card>
<CardContent className="p-6">
<div className="space-y-4">
{[...Array(5)].map((_, i) => (
<div
key={i}
className="h-12 w-full animate-pulse rounded bg-muted"
/>
))}
</div>
</CardContent>
</Card>
);
}
export default function UnprocessedRRInventoryPage() {
return (
<div>
<h1 className="mb-2 text-3xl font-bold">
Unprocessed R&R Inventory
</h1>
<p className="mb-6 text-muted-foreground">
Unprocessed returns and repairs
</p>
<Suspense fallback={<LoadingSkeleton />}>
<UnprocessedRRInventoryData />
</Suspense>
</div>
);
}

View file

@ -0,0 +1,69 @@
import { Suspense } from 'react';
import { getUnprocessedSummary } from '@/services/inventory';
import {
getQuestSession,
getActiveCompany,
isSubUser,
} from '@/lib/permissions';
import { redirect } from 'next/navigation';
import { InventorySummaryTable } from '@/components/inventory/inventory-summary-table';
import { Card, CardContent } from '@/components/ui/card';
export const dynamic = 'force-dynamic';
async function UnprocessedInventoryData() {
const session = await getQuestSession();
const activeCompany = await getActiveCompany();
const userIsSubUser = await isSubUser();
if (!session || !activeCompany) {
redirect('/select-company');
}
// Block sub-users from accessing this category
if (userIsSubUser) {
redirect('/inventory');
}
const dbName = `[${process.env.PORTAL_DB_NAME || 'VorteqPortal'}]`;
const inventory = await getUnprocessedSummary(
activeCompany.epicor_cust_id,
dbName,
userIsSubUser
).catch(() => []);
return <InventorySummaryTable data={inventory} category="unprocessed" />;
}
function LoadingSkeleton() {
return (
<Card>
<CardContent className="p-6">
<div className="space-y-4">
{[...Array(5)].map((_, i) => (
<div
key={i}
className="h-12 w-full animate-pulse rounded bg-muted"
/>
))}
</div>
</CardContent>
</Card>
);
}
export default function UnprocessedInventoryPage() {
return (
<div>
<h1 className="mb-2 text-3xl font-bold">Unprocessed Inventory</h1>
<p className="mb-6 text-muted-foreground">
Raw materials awaiting processing
</p>
<Suspense fallback={<LoadingSkeleton />}>
<UnprocessedInventoryData />
</Suspense>
</div>
);
}