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 { AuthProvider } from './auth/AuthProvider'
|
||||||
import { LoginPage } from './screens/LoginPage'
|
import { LoginPage } from './screens/LoginPage'
|
||||||
import { ConsoleLayout } from './screens/ConsoleLayout'
|
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() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@ -11,8 +14,17 @@ function App() {
|
|||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/login" element={<LoginPage />} />
|
<Route path="/login" element={<LoginPage />} />
|
||||||
<Route element={<ConsoleLayout />}>
|
<Route element={<ConsoleLayout />}>
|
||||||
<Route path="/:tab" element={<WorkflowPage />} />
|
<Route path="/orders" element={<OrdersPage />} />
|
||||||
<Route path="/:tab/:instanceId" element={<WorkflowPage />} />
|
<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>
|
||||||
<Route path="*" element={<Navigate to="/orders" replace />} />
|
<Route path="*" element={<Navigate to="/orders" replace />} />
|
||||||
</Routes>
|
</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 { onAuthErrorAll, orderBookingClient } from '../api/clients';
|
||||||
import { APP_ID } from '../api/config';
|
import { APP_ID } from '../api/config';
|
||||||
import { Button } from '../components/buttons';
|
import { Button } from '../components/buttons';
|
||||||
import { TABS } from './tabs';
|
import { SCREENS } from './tabs';
|
||||||
|
|
||||||
/** Auth-guarded shell: navy top bar + tab nav + routed <Outlet>. */
|
/** Auth-guarded shell: navy top bar + tab nav + routed <Outlet>. */
|
||||||
export function ConsoleLayout() {
|
export function ConsoleLayout() {
|
||||||
@ -25,12 +25,32 @@ export function ConsoleLayout() {
|
|||||||
if (!authed) return <Navigate to="/login" replace />;
|
if (!authed) return <Navigate to="/login" replace />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-app">
|
<div className="min-h-screen bg-app flex flex-col">
|
||||||
<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">
|
<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">
|
<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-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>
|
<span className="text-2xs text-on-navy-muted">Field Sales · Sandbox {APP_ID}</span>
|
||||||
</div>
|
</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">
|
<div className="flex items-center gap-3">
|
||||||
{user?.name && <span className="text-sm text-on-navy hidden sm:inline">{user.name}</span>}
|
{user?.name && <span className="text-sm text-on-navy hidden sm:inline">{user.name}</span>}
|
||||||
<Button
|
<Button
|
||||||
@ -47,29 +67,10 @@ export function ConsoleLayout() {
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main className="max-w-[1240px] mx-auto p-6 flex flex-col gap-5">
|
<main className="flex-1 overflow-y-auto p-6">
|
||||||
<nav className="flex gap-0.5 bg-slate-100 rounded-md p-[3px] w-fit">
|
<div className="max-w-[1240px] w-full mx-auto flex flex-col gap-5">
|
||||||
{TABS.map((t) => {
|
<Outlet />
|
||||||
const Icon = t.icon;
|
</div>
|
||||||
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>
|
|
||||||
|
|
||||||
<Outlet />
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</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,
|
type WiredDetailViewProps,
|
||||||
} from '../components/dv';
|
} from '../components/dv';
|
||||||
|
|
||||||
export type TabKey = 'orders' | 'calls' | 'stores' | 'daily';
|
export type ScreenKey = 'orders' | 'calls' | 'stores' | 'daily';
|
||||||
|
|
||||||
export interface TabDef {
|
export interface ScreenDef {
|
||||||
key: TabKey;
|
key: ScreenKey;
|
||||||
label: string;
|
label: string;
|
||||||
icon: LucideIcon;
|
icon: LucideIcon;
|
||||||
View: (p: WiredRecordViewProps) => React.JSX.Element;
|
View: (p: WiredRecordViewProps) => React.JSX.Element;
|
||||||
@ -26,13 +26,13 @@ export interface TabDef {
|
|||||||
noun: string;
|
noun: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TABS: TabDef[] = [
|
export const SCREENS: ScreenDef[] = [
|
||||||
{ key: 'orders', label: 'Orders', icon: ShoppingCart, View: OrdersView, Detail: OrderDetail, noun: 'Order' },
|
{ 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: 'calls', label: 'Calls', icon: Phone, View: CallsView, Detail: CallDetail, noun: 'Call' },
|
||||||
{ key: 'stores', label: 'Stores', icon: Store, View: StoresView, Detail: StoreDetail, noun: 'Store' },
|
{ 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' },
|
{ key: 'daily', label: 'Daily Logs', icon: ClipboardList, View: DailyLogsView, Detail: DailyLogDetail, noun: 'Daily Log' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function tabByKey(key: string | undefined): TabDef | undefined {
|
export function screenByKey(key: string | undefined): ScreenDef | undefined {
|
||||||
return TABS.find((t) => t.key === key);
|
return SCREENS.find((t) => t.key === key);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user