diff --git a/src/components/KitchenandInventory/KitchenReports/KitchenReports.jsx b/src/components/KitchenandInventory/KitchenReports/KitchenReports.jsx
new file mode 100644
index 00000000000..8d0169b3c3d
--- /dev/null
+++ b/src/components/KitchenandInventory/KitchenReports/KitchenReports.jsx
@@ -0,0 +1,92 @@
+import { useState } from 'react';
+import { useSelector } from 'react-redux';
+import styles from './KitchenReports.module.css';
+import { REPORT_TABS, DATE_RANGE_OPTIONS, EXPORT_ENDPOINTS_READY } from './kitchenReportsData';
+import useReportExport from './useReportExport';
+
+const EXPORT_UNAVAILABLE_MESSAGE =
+ 'Export is unavailable until the backend export endpoints are deployed.';
+
+export function KitchenReports() {
+ const darkMode = useSelector(state => state.theme.darkMode);
+ const dm = darkMode ? styles.dark : '';
+
+ const [activeTab, setActiveTab] = useState(REPORT_TABS[0].id);
+ const [dateRange, setDateRange] = useState(DATE_RANGE_OPTIONS[0].value);
+ const { exportReport, loadingFormat } = useReportExport();
+
+ const activeTabLabel = REPORT_TABS.find(tab => tab.id === activeTab)?.label;
+
+ return (
+
+
+
+
+
Reports & Analytics
+
Comprehensive insights on usage, spending, waste, and sustainability
+
+
+
+
+
+
+
+
+
+
+
+
+ {REPORT_TABS.map(tab => (
+
+ ))}
+
+
+
+ {activeTabLabel} content coming soon.
+
+
+
+ );
+}
+
+export default KitchenReports;
diff --git a/src/components/KitchenandInventory/KitchenReports/KitchenReports.module.css b/src/components/KitchenandInventory/KitchenReports/KitchenReports.module.css
new file mode 100644
index 00000000000..110b43921dc
--- /dev/null
+++ b/src/components/KitchenandInventory/KitchenReports/KitchenReports.module.css
@@ -0,0 +1,137 @@
+.pageWrapper {
+ min-height: 100vh;
+ width: 100%;
+ background-color: #fff;
+}
+
+.pageWrapper.dark {
+ background-color: #1a1816;
+}
+
+.container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 24px 16px;
+}
+
+.header {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ align-items: flex-start;
+ gap: 16px;
+ margin-bottom: 20px;
+}
+
+.titleBlock h1 {
+ font-size: 1.5rem;
+ font-weight: 700;
+ margin: 0 0 4px;
+ color: #1f2937;
+}
+
+.titleBlock p {
+ margin: 0;
+ font-size: 0.9rem;
+ color: #6b7280;
+}
+
+.dark .titleBlock h1 {
+ color: #f3f4f6;
+}
+
+.dark .titleBlock p {
+ color: #9ca3af;
+}
+
+.controls {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex-wrap: wrap;
+}
+
+.dateSelect {
+ padding: 8px 12px;
+ border-radius: 6px;
+ border: 1px solid #d1d5db;
+ background-color: #fff;
+ color: #1f2937;
+ font-size: 0.875rem;
+}
+
+.dark .dateSelect {
+ background-color: #2a2622;
+ border-color: #4b4642;
+ color: #f3f4f6;
+}
+
+.exportButton {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 8px 14px;
+ border-radius: 6px;
+ border: 1px solid #d1d5db;
+ background-color: #fff;
+ color: #1f2937;
+ font-size: 0.875rem;
+ cursor: pointer;
+}
+
+.exportButton:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+}
+
+.dark .exportButton {
+ background-color: #2a2622;
+ border-color: #4b4642;
+ color: #f3f4f6;
+}
+
+.tabs {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px;
+ border-bottom: 1px solid #e5e7eb;
+ margin-bottom: 20px;
+}
+
+.dark .tabs {
+ border-bottom-color: #3a3632;
+}
+
+.tabButton {
+ padding: 10px 16px;
+ border: none;
+ background: none;
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: #4b5563;
+ cursor: pointer;
+ border-bottom: 2px solid transparent;
+}
+
+.tabButton.active {
+ color: #047857;
+ border-bottom-color: #047857;
+}
+
+.dark .tabButton {
+ color: #9ca3af;
+}
+
+.dark .tabButton.active {
+ color: #34d399;
+ border-bottom-color: #34d399;
+}
+
+.tabPanel {
+ min-height: 200px;
+ color: #374151;
+}
+
+.dark .tabPanel {
+ color: #d1d5db;
+}
diff --git a/src/components/KitchenandInventory/KitchenReports/kitchenReportsData.js b/src/components/KitchenandInventory/KitchenReports/kitchenReportsData.js
new file mode 100644
index 00000000000..577f52df3ef
--- /dev/null
+++ b/src/components/KitchenandInventory/KitchenReports/kitchenReportsData.js
@@ -0,0 +1,16 @@
+export const REPORT_TABS = [
+ { id: 'overview', label: 'Overview' },
+ { id: 'sustainability', label: 'Sustainability Dashboard' },
+ { id: 'preservation', label: 'Preservation Efficiency' },
+ { id: 'forecast', label: 'Forecast Accuracy' },
+];
+
+export const DATE_RANGE_OPTIONS = [
+ { value: 'thisMonth', label: 'This Month' },
+ { value: 'lastMonth', label: 'Last Month' },
+ { value: 'last3Months', label: 'Last 3 Months' },
+ { value: 'thisYear', label: 'This Year' },
+];
+
+// Flip to true once the backend /kitchenandinventory/reports/export/* routes exist in HGNRest.
+export const EXPORT_ENDPOINTS_READY = false;
diff --git a/src/components/KitchenandInventory/KitchenReports/useReportExport.js b/src/components/KitchenandInventory/KitchenReports/useReportExport.js
new file mode 100644
index 00000000000..f50d86908e2
--- /dev/null
+++ b/src/components/KitchenandInventory/KitchenReports/useReportExport.js
@@ -0,0 +1,56 @@
+import { useCallback, useState } from 'react';
+import axios from 'axios';
+import { toast } from 'react-toastify';
+import { ENDPOINTS } from '~/utils/URL';
+
+const EXPORT_CONFIG = {
+ pdf: {
+ url: ENDPOINTS.KITCHEN_REPORTS_EXPORT_PDF,
+ mimeType: 'application/pdf',
+ fileName: 'report-overview.pdf',
+ },
+ csv: {
+ url: ENDPOINTS.KITCHEN_REPORTS_EXPORT_CSV,
+ mimeType: 'text/csv',
+ fileName: 'report-overview.csv',
+ },
+};
+
+export default function useReportExport() {
+ const [loadingFormat, setLoadingFormat] = useState(null);
+
+ const exportReport = useCallback(async (format, dateRange) => {
+ const config = EXPORT_CONFIG[format];
+ setLoadingFormat(format);
+
+ try {
+ const response = await axios.get(config.url(), {
+ params: { dateRange },
+ responseType: 'blob',
+ });
+
+ if (!response.data || response.data.size === 0) {
+ throw new Error('Received an empty report file.');
+ }
+
+ const blob = new Blob([response.data], { type: config.mimeType });
+ const downloadUrl = window.URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = downloadUrl;
+ link.download = config.fileName;
+ document.body.appendChild(link);
+ link.click();
+ link.remove();
+ setTimeout(() => window.URL.revokeObjectURL(downloadUrl), 100);
+
+ toast.success(`${format.toUpperCase()} report downloaded successfully!`);
+ } catch (error) {
+ const message = error?.message || `Failed to export ${format.toUpperCase()} report.`;
+ toast.error(`${message} Please try again.`);
+ } finally {
+ setLoadingFormat(null);
+ }
+ }, []);
+
+ return { exportReport, loadingFormat };
+}
diff --git a/src/routes.jsx b/src/routes.jsx
index df47fba3d99..3a90ee43292 100644
--- a/src/routes.jsx
+++ b/src/routes.jsx
@@ -181,6 +181,7 @@ import GardenManagement from './components/KitchenandInventory/GardenManagement/
import RecipesLandingPage from './components/KitchenandInventory/Recipes';
import KIDashboard from './components/KitchenandInventory/KIDashboard/KIDashboard';
+import KitchenReports from './components/KitchenandInventory/KitchenReports/KitchenReports';
import KIINVENTORY from './components/KitchenandInventory/KIInventory/KIInventory';
import KICalendar from './components/KitchenandInventory/KICalendar/KICalendar';
@@ -1129,6 +1130,7 @@ export default (
exact
component={GardenManagement}
/>
+
diff --git a/src/utils/URL.js b/src/utils/URL.js
index 9a183aef8c6..0c996f9d8c6 100644
--- a/src/utils/URL.js
+++ b/src/utils/URL.js
@@ -138,6 +138,8 @@ export const ENDPOINTS = {
WEEKLY_SUMMARIES_FILTER_REPLACE_INDIVIDUAL_CODES: `${APIEndpoint}/weeklySummariesFilters/replaceIndividualTeamcodes`,
SAVE_SUMMARY_RECEPIENTS: userid => `${APIEndpoint}/reports/recepients/${userid}`,
GET_SUMMARY_RECEPIENTS: () => `${APIEndpoint}/reports/getrecepients`,
+ KITCHEN_REPORTS_EXPORT_PDF: () => `${APIEndpoint}/kitchenandinventory/reports/export/pdf`,
+ KITCHEN_REPORTS_EXPORT_CSV: () => `${APIEndpoint}/kitchenandinventory/reports/export/csv`,
GET_CURRENT_WARNINGS: () => `${APIEndpoint}/currentWarnings`,
POST_NEW_WARNING: () => `${APIEndpoint}/currentWarnings`,
UPDATE_WARNING_DESCRIPTION: warningId => `${APIEndpoint}/currentWarnings/${warningId}`,