feat(C-002): complete all 6 inventory category pages
- 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:
parent
6329d9f357
commit
ebd91c8fac
5 changed files with 339 additions and 0 deletions
65
src/app/(portal)/inventory/finished-goods/page.tsx
Normal file
65
src/app/(portal)/inventory/finished-goods/page.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
65
src/app/(portal)/inventory/processed-other/page.tsx
Normal file
65
src/app/(portal)/inventory/processed-other/page.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
69
src/app/(portal)/inventory/processed-rr/page.tsx
Normal file
69
src/app/(portal)/inventory/processed-rr/page.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
71
src/app/(portal)/inventory/unprocessed-rr/page.tsx
Normal file
71
src/app/(portal)/inventory/unprocessed-rr/page.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
69
src/app/(portal)/inventory/unprocessed/page.tsx
Normal file
69
src/app/(portal)/inventory/unprocessed/page.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue