converted the tabs into screens and fixed the nav
This commit is contained in:
parent
d5e4ebee5d
commit
4990eceada
18
src/App.tsx
18
src/App.tsx
@ -2,7 +2,10 @@ import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'
|
||||
import { AuthProvider } from './auth/AuthProvider'
|
||||
import { LoginPage } from './screens/LoginPage'
|
||||
import { ConsoleLayout } from './screens/ConsoleLayout'
|
||||
import { WorkflowPage } from './screens/WorkflowPage'
|
||||
import { OrdersPage } from './screens/OrdersPage'
|
||||
import { CallsPage } from './screens/CallsPage'
|
||||
import { StoresPage } from './screens/StoresPage'
|
||||
import { DailyLogsPage } from './screens/DailyLogsPage'
|
||||
|
||||
function App() {
|
||||
return (
|
||||
@ -11,8 +14,17 @@ function App() {
|
||||
<Routes>
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route element={<ConsoleLayout />}>
|
||||
<Route path="/:tab" element={<WorkflowPage />} />
|
||||
<Route path="/:tab/:instanceId" element={<WorkflowPage />} />
|
||||
<Route path="/orders" element={<OrdersPage />} />
|
||||
<Route path="/orders/:instanceId" element={<OrdersPage />} />
|
||||
|
||||
<Route path="/calls" element={<CallsPage />} />
|
||||
<Route path="/calls/:instanceId" element={<CallsPage />} />
|
||||
|
||||
<Route path="/stores" element={<StoresPage />} />
|
||||
<Route path="/stores/:instanceId" element={<StoresPage />} />
|
||||
|
||||
<Route path="/daily" element={<DailyLogsPage />} />
|
||||
<Route path="/daily/:instanceId" element={<DailyLogsPage />} />
|
||||
</Route>
|
||||
<Route path="*" element={<Navigate to="/orders" replace />} />
|
||||
</Routes>
|
||||
|
||||
27
src/screens/CallsPage.tsx
Normal file
27
src/screens/CallsPage.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Modal } from '../components/reusable';
|
||||
import { CallsView } from '../components/rv';
|
||||
import { CallDetail } from '../components/dv';
|
||||
|
||||
export function CallsPage() {
|
||||
const { instanceId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<>
|
||||
<CallsView onRowClick={(row) => {
|
||||
const id = row.instance_id as number | string | undefined;
|
||||
if (id != null) navigate(`/calls/${id}`);
|
||||
}} />
|
||||
|
||||
<Modal
|
||||
open={instanceId != null}
|
||||
onClose={() => navigate(`/calls`)}
|
||||
title={instanceId != null ? `Call #${instanceId}` : undefined}
|
||||
width="lg"
|
||||
>
|
||||
{instanceId != null && <CallDetail instanceId={instanceId} />}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -6,7 +6,7 @@ import { useAuth } from '../auth/context';
|
||||
import { onAuthErrorAll, orderBookingClient } from '../api/clients';
|
||||
import { APP_ID } from '../api/config';
|
||||
import { Button } from '../components/buttons';
|
||||
import { TABS } from './tabs';
|
||||
import { SCREENS } from './tabs';
|
||||
|
||||
/** Auth-guarded shell: navy top bar + tab nav + routed <Outlet>. */
|
||||
export function ConsoleLayout() {
|
||||
@ -25,12 +25,32 @@ export function ConsoleLayout() {
|
||||
if (!authed) return <Navigate to="/login" replace />;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-app">
|
||||
<header className="sticky top-0 z-10 flex items-center justify-between gap-3 px-6 h-[60px] bg-navy-grad border-b border-border-navy">
|
||||
<div className="min-h-screen bg-app flex flex-col">
|
||||
<header className="sticky top-0 z-10 shrink-0 flex items-center justify-between gap-3 px-6 h-[60px] bg-navy-grad border-b border-border-navy">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-md font-extrabold text-on-navy tracking-[-0.01em] leading-none">Krishna Sales</span>
|
||||
<span className="text-2xs text-on-navy-muted">Field Sales · Sandbox {APP_ID}</span>
|
||||
</div>
|
||||
<nav className="flex items-center gap-1 h-full">
|
||||
{SCREENS.map((t) => {
|
||||
const Icon = t.icon;
|
||||
return (
|
||||
<NavLink
|
||||
key={t.key}
|
||||
to={`/${t.key}`}
|
||||
className={({ isActive }) =>
|
||||
cn(
|
||||
'flex items-center gap-1.5 no-underline font-sans text-sm font-medium px-3 py-1.5 rounded-md transition-all duration-150',
|
||||
isActive ? 'bg-white/10 text-white' : 'text-on-navy-muted hover:text-white hover:bg-white/5',
|
||||
)
|
||||
}
|
||||
>
|
||||
<Icon size={16} />
|
||||
{t.label}
|
||||
</NavLink>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
<div className="flex items-center gap-3">
|
||||
{user?.name && <span className="text-sm text-on-navy hidden sm:inline">{user.name}</span>}
|
||||
<Button
|
||||
@ -47,29 +67,10 @@ export function ConsoleLayout() {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="max-w-[1240px] mx-auto p-6 flex flex-col gap-5">
|
||||
<nav className="flex gap-0.5 bg-slate-100 rounded-md p-[3px] w-fit">
|
||||
{TABS.map((t) => {
|
||||
const Icon = t.icon;
|
||||
return (
|
||||
<NavLink
|
||||
key={t.key}
|
||||
to={`/${t.key}`}
|
||||
className={({ isActive }) =>
|
||||
cn(
|
||||
'inline-flex items-center gap-1.5 no-underline font-sans text-xs font-semibold px-3.5 py-2 rounded-sm transition-all duration-150',
|
||||
isActive ? 'bg-card text-strong shadow-xs' : 'bg-transparent text-muted hover:text-strong',
|
||||
)
|
||||
}
|
||||
>
|
||||
<Icon size={14} />
|
||||
{t.label}
|
||||
</NavLink>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
<main className="flex-1 overflow-y-auto p-6">
|
||||
<div className="max-w-[1240px] w-full mx-auto flex flex-col gap-5">
|
||||
<Outlet />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
27
src/screens/DailyLogsPage.tsx
Normal file
27
src/screens/DailyLogsPage.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Modal } from '../components/reusable';
|
||||
import { DailyLogsView } from '../components/rv';
|
||||
import { DailyLogDetail } from '../components/dv';
|
||||
|
||||
export function DailyLogsPage() {
|
||||
const { instanceId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<>
|
||||
<DailyLogsView onRowClick={(row) => {
|
||||
const id = row.instance_id as number | string | undefined;
|
||||
if (id != null) navigate(`/daily/${id}`);
|
||||
}} />
|
||||
|
||||
<Modal
|
||||
open={instanceId != null}
|
||||
onClose={() => navigate(`/daily`)}
|
||||
title={instanceId != null ? `Daily Log #${instanceId}` : undefined}
|
||||
width="lg"
|
||||
>
|
||||
{instanceId != null && <DailyLogDetail instanceId={instanceId} />}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
27
src/screens/OrdersPage.tsx
Normal file
27
src/screens/OrdersPage.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Modal } from '../components/reusable';
|
||||
import { OrdersView } from '../components/rv';
|
||||
import { OrderDetail } from '../components/dv';
|
||||
|
||||
export function OrdersPage() {
|
||||
const { instanceId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<>
|
||||
<OrdersView onRowClick={(row) => {
|
||||
const id = row.instance_id as number | string | undefined;
|
||||
if (id != null) navigate(`/orders/${id}`);
|
||||
}} />
|
||||
|
||||
<Modal
|
||||
open={instanceId != null}
|
||||
onClose={() => navigate(`/orders`)}
|
||||
title={instanceId != null ? `Order #${instanceId}` : undefined}
|
||||
width="lg"
|
||||
>
|
||||
{instanceId != null && <OrderDetail instanceId={instanceId} />}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
27
src/screens/StoresPage.tsx
Normal file
27
src/screens/StoresPage.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Modal } from '../components/reusable';
|
||||
import { StoresView } from '../components/rv';
|
||||
import { StoreDetail } from '../components/dv';
|
||||
|
||||
export function StoresPage() {
|
||||
const { instanceId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<>
|
||||
<StoresView onRowClick={(row) => {
|
||||
const id = row.instance_id as number | string | undefined;
|
||||
if (id != null) navigate(`/stores/${id}`);
|
||||
}} />
|
||||
|
||||
<Modal
|
||||
open={instanceId != null}
|
||||
onClose={() => navigate(`/stores`)}
|
||||
title={instanceId != null ? `Store #${instanceId}` : undefined}
|
||||
width="lg"
|
||||
>
|
||||
{instanceId != null && <StoreDetail instanceId={instanceId} />}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
import { Navigate, useNavigate, useParams } from 'react-router-dom';
|
||||
import { Modal } from '../components/reusable';
|
||||
import { tabByKey } from './tabs';
|
||||
|
||||
/** Record view for /:tab, with a deep-linkable detail modal at /:tab/:instanceId. */
|
||||
export function WorkflowPage() {
|
||||
const { tab, instanceId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const def = tabByKey(tab);
|
||||
|
||||
// Unknown tab → default to orders.
|
||||
if (!def) return <Navigate to="/orders" replace />;
|
||||
|
||||
const { View, Detail, noun, key } = def;
|
||||
|
||||
return (
|
||||
<>
|
||||
<View onRowClick={(row) => {
|
||||
const id = row.instance_id as number | string | undefined;
|
||||
if (id != null) navigate(`/${key}/${id}`);
|
||||
}} />
|
||||
|
||||
<Modal
|
||||
open={instanceId != null}
|
||||
onClose={() => navigate(`/${key}`)}
|
||||
title={instanceId != null ? `${noun} #${instanceId}` : undefined}
|
||||
width="lg"
|
||||
>
|
||||
{instanceId != null && <Detail instanceId={instanceId} />}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -14,10 +14,10 @@ import {
|
||||
type WiredDetailViewProps,
|
||||
} from '../components/dv';
|
||||
|
||||
export type TabKey = 'orders' | 'calls' | 'stores' | 'daily';
|
||||
export type ScreenKey = 'orders' | 'calls' | 'stores' | 'daily';
|
||||
|
||||
export interface TabDef {
|
||||
key: TabKey;
|
||||
export interface ScreenDef {
|
||||
key: ScreenKey;
|
||||
label: string;
|
||||
icon: LucideIcon;
|
||||
View: (p: WiredRecordViewProps) => React.JSX.Element;
|
||||
@ -26,13 +26,13 @@ export interface TabDef {
|
||||
noun: string;
|
||||
}
|
||||
|
||||
export const TABS: TabDef[] = [
|
||||
export const SCREENS: ScreenDef[] = [
|
||||
{ key: 'orders', label: 'Orders', icon: ShoppingCart, View: OrdersView, Detail: OrderDetail, noun: 'Order' },
|
||||
{ key: 'calls', label: 'Calls', icon: Phone, View: CallsView, Detail: CallDetail, noun: 'Call' },
|
||||
{ key: 'stores', label: 'Stores', icon: Store, View: StoresView, Detail: StoreDetail, noun: 'Store' },
|
||||
{ key: 'daily', label: 'Daily Logs', icon: ClipboardList, View: DailyLogsView, Detail: DailyLogDetail, noun: 'Daily Log' },
|
||||
];
|
||||
|
||||
export function tabByKey(key: string | undefined): TabDef | undefined {
|
||||
return TABS.find((t) => t.key === key);
|
||||
export function screenByKey(key: string | undefined): ScreenDef | undefined {
|
||||
return SCREENS.find((t) => t.key === key);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user