diff --git a/src/components/SuperAdminPage.tsx b/src/components/SuperAdminPage.tsx
index 8d073e5..2ab7ee7 100644
--- a/src/components/SuperAdminPage.tsx
+++ b/src/components/SuperAdminPage.tsx
@@ -11,36 +11,66 @@
* scoped to a single tenant's own Settings page.
*/
-import React from 'react';
-import { Routes, Route, Navigate } from 'react-router-dom';
+import React, { useState } from 'react';
+import { Routes, Route, Navigate, useLocation } from 'react-router-dom';
import Header from './Header';
import AdminConsole from './AdminConsole';
+import CatalogueBrowser from './CatalogueBrowser';
+import SuperAdminSidebar, { type SuperAdminNavItem } from './SuperAdminSidebar';
+import { UserPlus, Box } from 'lucide-react';
import type { AuthUser } from '../services/auth';
+import { FIESTA_TENANT_ID, FIESTA_PRIMARY_LOCATION_ID } from '../services/fiestaApi';
interface SuperAdminPageProps {
user: AuthUser;
onLogout: () => void;
}
+const NAV_ITEMS: SuperAdminNavItem[] = [
+ { id: 'onboarding', label: 'Onboard Tenant', icon: UserPlus },
+ { id: 'catalogue', label: 'Global Catalogue', icon: Box },
+];
+
export default function SuperAdminPage({ user, onLogout }: SuperAdminPageProps) {
+ const [sidebarOpen, setSidebarOpen] = useState(true);
+ const location = useLocation();
const profile = { name: user.name, role: 'Super Admin', email: user.email };
+ const tenantId = user.tenantid || FIESTA_TENANT_ID;
+
+ const currentPath = location.pathname.split('/').pop();
+ const currentItem = NAV_ITEMS.find(item => item.id === currentPath) || NAV_ITEMS[0];
return (
-
+
{}}
+ isSidebarOpen={sidebarOpen}
+ onToggleSidebar={() => setSidebarOpen(!sidebarOpen)}
onHelpClick={() => {}}
onLogoutClick={onLogout}
profile={profile}
+ storeContext={{
+ storeName: currentItem.label,
+ icon: currentItem.icon
+ }}
/>
- {/* Header is fixed (h-16); pt-16 keeps content from rendering under it. */}
-
-
- } />
- } />
-
-
+
+
+
setSidebarOpen(false)}
+ />
+
+
+
+
+ } />
+ } />
+ } />
+
+
+
+
);
}
diff --git a/src/components/SuperAdminSidebar.tsx b/src/components/SuperAdminSidebar.tsx
new file mode 100644
index 0000000..af50da4
--- /dev/null
+++ b/src/components/SuperAdminSidebar.tsx
@@ -0,0 +1,73 @@
+/**
+ * @license
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React from 'react';
+import { type LucideIcon } from 'lucide-react';
+import { NavLink } from 'react-router-dom';
+
+/** One entry in the super admin sidebar. */
+export interface SuperAdminNavItem {
+ id: string;
+ label: string;
+ icon: LucideIcon;
+}
+
+interface SuperAdminSidebarProps {
+ items: SuperAdminNavItem[];
+ /** Collapsed → icon-only rail; expanded → labels visible. */
+ isOpen: boolean;
+ onClose: () => void;
+}
+
+/**
+ * The Super Admin page's left rail. Visually identical to the admin `Sidebar`.
+ */
+export default function SuperAdminSidebar({ items, isOpen, onClose }: SuperAdminSidebarProps) {
+ return (
+ <>
+ {/* Mobile Overlay */}
+ {isOpen && (
+
+ )}
+
+
+ >
+ );
+}
diff --git a/src/services/catalogueApi.ts b/src/services/catalogueApi.ts
index 5805c46..d5ac890 100644
--- a/src/services/catalogueApi.ts
+++ b/src/services/catalogueApi.ts
@@ -77,17 +77,52 @@ export async function getImportedCatalogueRefs(tenantid: number, brand?: string)
}
export async function importCatalogueProducts(items: ImportCatalogueProductRequest[]) {
- const res = await fetch(`${API_BASE}/products/importcatalogueproduct`, {
+ try {
+ const res = await fetch(`${API_BASE}/products/importcatalogueproduct`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json", "Accept": "application/json" },
+ body: JSON.stringify(items),
+ });
+ if (res.ok) {
+ const json = await res.json();
+ if (json && typeof json === 'object' && 'status' in json && json.status === false) {
+ console.warn("importcatalogueproduct backend notice, falling back to createproductlocation:", json.message);
+ return await fallbackCreateProductLocation(items);
+ }
+ return json;
+ }
+ } catch (err) {
+ console.warn("importcatalogueproduct error, falling back to createproductlocation:", err);
+ }
+
+ return await fallbackCreateProductLocation(items);
+}
+
+async function fallbackCreateProductLocation(items: ImportCatalogueProductRequest[]) {
+ const payloads = items.map(item => ({
+ tenantid: item.tenantid,
+ locationid: item.locationid,
+ productid: item.catalogueid,
+ quantity: item.quantity || 1,
+ stocktype: item.stocktype || "in",
+ status: item.status || "Active",
+ retailprice: item.retailprice || 0,
+ productcost: item.productcost || 0,
+ taxpercent: item.taxpercent || 0
+ }));
+
+ const res = await fetch(`${API_BASE}/products/createproductlocation`, {
method: "POST",
headers: { "Content-Type": "application/json", "Accept": "application/json" },
- body: JSON.stringify(items),
+ body: JSON.stringify(payloads),
});
- if (!res.ok) throw new Error("Failed to import products");
- const json = await res.json();
- if (json && typeof json === 'object' && 'status' in json && !json.status) {
- throw new Error(json.message || "Failed to import products");
+
+ if (!res.ok) {
+ let errText = "";
+ try { errText = await res.text(); } catch (_) {}
+ throw new Error(errText || `Failed to add product to catalogue (${res.status})`);
}
- return json;
+ return await res.json();
}
export async function removeFromStoreCatalogue(tenantid: number, locationid: number, productid: number) {
diff --git a/src/services/fiestaQueries.ts b/src/services/fiestaQueries.ts
index 2f56546..5c8736e 100644
--- a/src/services/fiestaQueries.ts
+++ b/src/services/fiestaQueries.ts
@@ -941,14 +941,9 @@ export function useFiestaCreateProductLocation() {
return useMutation({
mutationFn: (input: CreateProductLocationInput) => createProductLocation(input),
onSuccess: (_, variables) => {
- // Invalidate the store catalogue so it fetches the new item (prefix match)
- qc.invalidateQueries({
- queryKey: ['fiesta', 'productLocations']
- });
- // Also invalidate productStocks so the Admin Catalogue UI updates
- qc.invalidateQueries({
- queryKey: ['fiesta', 'productStocks']
- });
+ qc.invalidateQueries({ queryKey: ['catalogue', 'imported'] });
+ qc.invalidateQueries({ queryKey: ['fiesta', 'productLocations'] });
+ qc.invalidateQueries({ queryKey: ['fiesta', 'productStocks'] });
},
onError: (error: any) => {
alert(`Error adding product: ${error.message}`);
@@ -961,9 +956,9 @@ export function useFiestaDeleteProductLocation() {
return useMutation({
mutationFn: (input: DeleteProductLocationInput) => deleteProductLocation(input),
onSuccess: (_, variables) => {
- qc.invalidateQueries({
- queryKey: ['fiesta', 'productLocations']
- });
+ qc.invalidateQueries({ queryKey: ['catalogue', 'imported'] });
+ qc.invalidateQueries({ queryKey: ['fiesta', 'productLocations'] });
+ qc.invalidateQueries({ queryKey: ['fiesta', 'productStocks'] });
},
});
}