Compare commits
No commits in common. "ai-dev" and "master" have entirely different histories.
@ -7,189 +7,146 @@ import LayoutAuthenticated from '../layouts/Authenticated'
|
|||||||
import SectionMain from '../components/SectionMain'
|
import SectionMain from '../components/SectionMain'
|
||||||
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'
|
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'
|
||||||
import BaseIcon from "../components/BaseIcon";
|
import BaseIcon from "../components/BaseIcon";
|
||||||
import BaseButton from "../components/BaseButton";
|
|
||||||
import { getPageTitle } from '../config'
|
import { getPageTitle } from '../config'
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
import { hasPermission } from "../helpers/userPermissions";
|
import { hasPermission } from "../helpers/userPermissions";
|
||||||
import { fetchWidgets } from '../stores/roles/rolesSlice';
|
import { fetchWidgets } from '../stores/roles/rolesSlice';
|
||||||
import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator';
|
import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator';
|
||||||
import { SmartWidget } from '../components/SmartWidget/SmartWidget';
|
import { SmartWidget } from '../components/SmartWidget/SmartWidget';
|
||||||
|
|
||||||
import { useAppDispatch, useAppSelector } from '../stores/hooks';
|
import { useAppDispatch, useAppSelector } from '../stores/hooks';
|
||||||
|
|
||||||
const EntityCard = ({ entity, label, count, iconPath, iconsColor, corners, cardsStyle }: any) => (
|
|
||||||
<Link href={`/${entity}/${entity}-list`}>
|
|
||||||
<div className={`${corners !== 'rounded-full' ? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6 hover:shadow-lg transition-all duration-200 transform hover:-translate-y-1`}>
|
|
||||||
<div className="flex justify-between align-center">
|
|
||||||
<div>
|
|
||||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400 font-medium">
|
|
||||||
{label}
|
|
||||||
</div>
|
|
||||||
<div className="text-3xl leading-tight font-bold mt-1">
|
|
||||||
{count}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<BaseIcon
|
|
||||||
className={`${iconsColor}`}
|
|
||||||
w="w-12"
|
|
||||||
h="h-12"
|
|
||||||
size={40}
|
|
||||||
path={iconPath || icon.mdiTable}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const iconsColor = useAppSelector((state) => state.style.iconsColor);
|
const iconsColor = useAppSelector((state) => state.style.iconsColor);
|
||||||
const corners = useAppSelector((state) => state.style.corners);
|
const corners = useAppSelector((state) => state.style.corners);
|
||||||
const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
|
const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
|
||||||
|
|
||||||
const loadingMessage = '...';
|
const loadingMessage = 'Loading...';
|
||||||
|
|
||||||
const [counts, setCounts] = React.useState<Record<string, any>>({});
|
|
||||||
|
const [users, setUsers] = React.useState(loadingMessage);
|
||||||
|
const [roles, setRoles] = React.useState(loadingMessage);
|
||||||
|
const [permissions, setPermissions] = React.useState(loadingMessage);
|
||||||
|
const [tenants, setTenants] = React.useState(loadingMessage);
|
||||||
|
const [user_roles, setUser_roles] = React.useState(loadingMessage);
|
||||||
|
const [modules, setModules] = React.useState(loadingMessage);
|
||||||
|
const [tenant_modules, setTenant_modules] = React.useState(loadingMessage);
|
||||||
|
const [plans, setPlans] = React.useState(loadingMessage);
|
||||||
|
const [subscriptions, setSubscriptions] = React.useState(loadingMessage);
|
||||||
|
const [activation_codes, setActivation_codes] = React.useState(loadingMessage);
|
||||||
|
const [licenses, setLicenses] = React.useState(loadingMessage);
|
||||||
|
const [audit_logs, setAudit_logs] = React.useState(loadingMessage);
|
||||||
|
const [products, setProducts] = React.useState(loadingMessage);
|
||||||
|
const [stock_movements, setStock_movements] = React.useState(loadingMessage);
|
||||||
|
const [warehouses, setWarehouses] = React.useState(loadingMessage);
|
||||||
|
const [warehouse_locations, setWarehouse_locations] = React.useState(loadingMessage);
|
||||||
|
const [customers, setCustomers] = React.useState(loadingMessage);
|
||||||
|
const [suppliers, setSuppliers] = React.useState(loadingMessage);
|
||||||
|
const [invoices, setInvoices] = React.useState(loadingMessage);
|
||||||
|
const [invoice_items, setInvoice_items] = React.useState(loadingMessage);
|
||||||
|
const [payments, setPayments] = React.useState(loadingMessage);
|
||||||
|
const [expenses, setExpenses] = React.useState(loadingMessage);
|
||||||
|
const [equipment_assets, setEquipment_assets] = React.useState(loadingMessage);
|
||||||
|
const [orders, setOrders] = React.useState(loadingMessage);
|
||||||
|
const [order_items, setOrder_items] = React.useState(loadingMessage);
|
||||||
|
|
||||||
|
|
||||||
|
const [widgetsRole, setWidgetsRole] = React.useState({
|
||||||
|
role: { value: '', label: '' },
|
||||||
|
});
|
||||||
const { currentUser } = useAppSelector((state) => state.auth);
|
const { currentUser } = useAppSelector((state) => state.auth);
|
||||||
const { isFetchingQuery } = useAppSelector((state) => state.openAi);
|
const { isFetchingQuery } = useAppSelector((state) => state.openAi);
|
||||||
|
|
||||||
const { rolesWidgets, loading } = useAppSelector((state) => state.roles);
|
const { rolesWidgets, loading } = useAppSelector((state) => state.roles);
|
||||||
const [widgetsRole, setWidgetsRole] = React.useState({ role: { value: '', label: '' } });
|
|
||||||
|
|
||||||
const entities = [
|
|
||||||
{ key: 'products', label: 'Products', icon: icon.mdiPackageVariant, module: 'Inventory & Storage' },
|
|
||||||
{ key: 'stock_movements', label: 'Stock Movements', icon: icon.mdiSwapHorizontal, module: 'Inventory & Storage' },
|
|
||||||
{ key: 'warehouses', label: 'Warehouses', icon: icon.mdiWarehouse, module: 'Inventory & Storage' },
|
|
||||||
{ key: 'warehouse_locations', label: 'Warehouse Locations', icon: icon.mdiMapMarkerRadius, module: 'Inventory & Storage' },
|
|
||||||
{ key: 'suppliers', label: 'Suppliers', icon: icon.mdiTruckDelivery, module: 'Inventory & Storage' },
|
|
||||||
|
|
||||||
{ key: 'invoices', label: 'Invoices', icon: icon.mdiInvoice, module: 'Accounting' },
|
const organizationId = currentUser?.tenants?.id;
|
||||||
{ key: 'payments', label: 'Payments', icon: icon.mdiCreditCard, module: 'Accounting' },
|
|
||||||
{ key: 'expenses', label: 'Expenses', icon: icon.mdiClipboardList, module: 'Accounting' },
|
|
||||||
{ key: 'customers', label: 'Customers', icon: icon.mdiAccountCircle, module: 'Accounting' },
|
|
||||||
|
|
||||||
{ key: 'equipment_assets', label: 'Equipment Assets', icon: icon.mdiWrench, module: 'Assets & Orders' },
|
|
||||||
{ key: 'orders', label: 'Orders', icon: icon.mdiTruck, module: 'Assets & Orders' },
|
|
||||||
|
|
||||||
{ key: 'users', label: 'Users', icon: icon.mdiAccountGroup, module: 'Administration' },
|
|
||||||
{ key: 'subscriptions', label: 'Subscriptions', icon: icon.mdiCalendarClock, module: 'Administration' },
|
|
||||||
{ key: 'activation_codes', label: 'Activation Codes', icon: icon.mdiKey, module: 'Administration' },
|
|
||||||
];
|
|
||||||
|
|
||||||
async function loadData() {
|
async function loadData() {
|
||||||
const requests = entities.map((entity) => {
|
const entities = ['users','roles','permissions','tenants','user_roles','modules','tenant_modules','plans','subscriptions','activation_codes','licenses','audit_logs','products','stock_movements','warehouses','warehouse_locations','customers','suppliers','invoices','invoice_items','payments','expenses','equipment_assets','orders','order_items',];
|
||||||
if(hasPermission(currentUser, `READ_${entity.key.toUpperCase()}`)) {
|
const fns = [setUsers,setRoles,setPermissions,setTenants,setUser_roles,setModules,setTenant_modules,setPlans,setSubscriptions,setActivation_codes,setLicenses,setAudit_logs,setProducts,setStock_movements,setWarehouses,setWarehouse_locations,setCustomers,setSuppliers,setInvoices,setInvoice_items,setPayments,setExpenses,setEquipment_assets,setOrders,setOrder_items,];
|
||||||
return axios.get(`/${entity.key.toLowerCase()}/count`);
|
|
||||||
|
const requests = entities.map((entity, index) => {
|
||||||
|
|
||||||
|
if(hasPermission(currentUser, `READ_${entity.toUpperCase()}`)) {
|
||||||
|
return axios.get(`/${entity.toLowerCase()}/count`);
|
||||||
|
} else {
|
||||||
|
fns[index](null);
|
||||||
|
return Promise.resolve({data: {count: null}});
|
||||||
}
|
}
|
||||||
return Promise.resolve({ data: { count: null } });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const results = await Promise.allSettled(requests);
|
Promise.allSettled(requests).then((results) => {
|
||||||
const newCounts: Record<string, any> = {};
|
|
||||||
results.forEach((result, i) => {
|
results.forEach((result, i) => {
|
||||||
if (result.status === 'fulfilled') {
|
if (result.status === 'fulfilled') {
|
||||||
newCounts[entities[i].key] = (result.value as any).data.count;
|
fns[i](result.value.data.count);
|
||||||
} else {
|
} else {
|
||||||
newCounts[entities[i].key] = 'Error';
|
fns[i](result.reason.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setCounts(newCounts);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getWidgets(roleId) {
|
||||||
|
await dispatch(fetchWidgets(roleId));
|
||||||
|
}
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!currentUser) return;
|
if (!currentUser) return;
|
||||||
loadData();
|
loadData().then();
|
||||||
setWidgetsRole({ role: { value: currentUser?.app_role?.id, label: currentUser?.app_role?.name } });
|
setWidgetsRole({ role: { value: currentUser?.app_role?.id, label: currentUser?.app_role?.name } });
|
||||||
}, [currentUser]);
|
}, [currentUser]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!currentUser || !widgetsRole?.role?.value) return;
|
if (!currentUser || !widgetsRole?.role?.value) return;
|
||||||
dispatch(fetchWidgets(widgetsRole?.role?.value));
|
getWidgets(widgetsRole?.role?.value || '').then();
|
||||||
}, [widgetsRole?.role?.value]);
|
}, [widgetsRole?.role?.value]);
|
||||||
|
|
||||||
const renderModuleGroup = (moduleName: string) => {
|
|
||||||
const moduleEntities = entities.filter(e => e.module === moduleName && hasPermission(currentUser, `READ_${e.key.toUpperCase()}`));
|
|
||||||
if (moduleEntities.length === 0) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={moduleName} className="mb-10">
|
|
||||||
<h3 className="text-xl font-bold mb-6 text-gray-700 dark:text-gray-300 flex items-center">
|
|
||||||
<span className="w-1.5 h-6 bg-indigo-600 mr-3 rounded-full"></span>
|
|
||||||
{moduleName}
|
|
||||||
</h3>
|
|
||||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3 xl:grid-cols-4">
|
|
||||||
{moduleEntities.map(e => (
|
|
||||||
<EntityCard
|
|
||||||
key={e.key}
|
|
||||||
entity={e.key}
|
|
||||||
label={e.label}
|
|
||||||
count={counts[e.key] ?? loadingMessage}
|
|
||||||
iconPath={e.icon}
|
|
||||||
iconsColor={iconsColor}
|
|
||||||
corners={corners}
|
|
||||||
cardsStyle={cardsStyle}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('Overview')}</title>
|
<title>
|
||||||
|
{getPageTitle('Overview')}
|
||||||
|
</title>
|
||||||
</Head>
|
</Head>
|
||||||
<SectionMain>
|
<SectionMain>
|
||||||
<SectionTitleLineWithButton
|
<SectionTitleLineWithButton
|
||||||
icon={icon.mdiChartTimelineVariant}
|
icon={icon.mdiChartTimelineVariant}
|
||||||
title='Enterprise Dashboard'
|
title='Overview'
|
||||||
main
|
main>
|
||||||
>
|
|
||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
|
|
||||||
{/* Subscription Status Widget */}
|
{hasPermission(currentUser, 'CREATE_ROLES') && <WidgetCreator
|
||||||
<div className={`mb-10 p-6 bg-indigo-50 border border-indigo-200 rounded-3xl flex flex-col md:flex-row items-center justify-between shadow-sm`}>
|
|
||||||
<div className="flex items-center mb-4 md:mb-0">
|
|
||||||
<div className="w-14 h-14 bg-indigo-100 text-indigo-600 rounded-2xl flex items-center justify-center mr-5 shadow-inner">
|
|
||||||
<BaseIcon path={icon.mdiCheckDecagram} size={32} />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 className="text-indigo-900 font-bold text-lg leading-tight">DIGIHELP-IMS Premium Active</h4>
|
|
||||||
<p className="text-indigo-700 text-sm mt-0.5">Your enterprise subscription is valid until 2026-12-31</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex space-x-3">
|
|
||||||
<BaseButton label="Renew" color="info" small className="rounded-xl px-6" />
|
|
||||||
<BaseButton label="View Plan" outline color="info" small className="rounded-xl px-6" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{hasPermission(currentUser, 'CREATE_ROLES') && (
|
|
||||||
<WidgetCreator
|
|
||||||
currentUser={currentUser}
|
currentUser={currentUser}
|
||||||
isFetchingQuery={isFetchingQuery}
|
isFetchingQuery={isFetchingQuery}
|
||||||
setWidgetsRole={setWidgetsRole}
|
setWidgetsRole={setWidgetsRole}
|
||||||
widgetsRole={widgetsRole}
|
widgetsRole={widgetsRole}
|
||||||
/>
|
/>}
|
||||||
|
{!!rolesWidgets.length &&
|
||||||
|
hasPermission(currentUser, 'CREATE_ROLES') && (
|
||||||
|
<p className=' text-gray-500 dark:text-gray-400 mb-4'>
|
||||||
|
{`${widgetsRole?.role?.label || 'Users'}'s widgets`}
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Dynamic AI Widgets */}
|
<div className='grid grid-cols-1 gap-6 lg:grid-cols-4 mb-6 grid-flow-dense'>
|
||||||
{(rolesWidgets.length > 0 || isFetchingQuery || loading) && (
|
|
||||||
<div className="mb-10">
|
|
||||||
<h3 className="text-xl font-bold mb-6 text-gray-700 dark:text-gray-300 flex items-center">
|
|
||||||
<span className="w-1.5 h-6 bg-violet-600 mr-3 rounded-full"></span>
|
|
||||||
AI Insights & Reports
|
|
||||||
</h3>
|
|
||||||
<div className='grid grid-cols-1 gap-6 lg:grid-cols-4'>
|
|
||||||
{(isFetchingQuery || loading) && (
|
{(isFetchingQuery || loading) && (
|
||||||
<div className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 text-lg leading-tight text-gray-500 flex items-center ${cardsStyle} dark:border-dark-700 p-6 shadow-sm`}>
|
<div className={` ${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 text-lg leading-tight text-gray-500 flex items-center ${cardsStyle} dark:border-dark-700 p-6`}>
|
||||||
<BaseIcon className={`${iconsColor} animate-spin mr-5`} w='w-12' h='h-12' size={32} path={icon.mdiLoading} />
|
<BaseIcon
|
||||||
Generating Insights...
|
className={`${iconsColor} animate-spin mr-5`}
|
||||||
|
w='w-16'
|
||||||
|
h='h-16'
|
||||||
|
size={48}
|
||||||
|
path={icon.mdiLoading}
|
||||||
|
/>{' '}
|
||||||
|
Loading widgets...
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{rolesWidgets.map((widget) => (
|
|
||||||
|
{ rolesWidgets &&
|
||||||
|
rolesWidgets.map((widget) => (
|
||||||
<SmartWidget
|
<SmartWidget
|
||||||
key={widget.id}
|
key={widget.id}
|
||||||
userId={currentUser?.id}
|
userId={currentUser?.id}
|
||||||
@ -199,12 +156,714 @@ const Dashboard = () => {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{!!rolesWidgets.length && <hr className='my-6 text-midnightBlueTheme-mainBG ' />}
|
||||||
|
|
||||||
|
<div id="dashboard" className='grid grid-cols-1 gap-6 lg:grid-cols-3 mb-6'>
|
||||||
|
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_USERS') && <Link href={'/users/users-list'}>
|
||||||
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Users
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{users}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={icon.mdiAccountGroup || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
{/* Grouped Entity Cards */}
|
{hasPermission(currentUser, 'READ_ROLES') && <Link href={'/roles/roles-list'}>
|
||||||
{['Inventory & Storage', 'Accounting', 'Assets & Orders', 'Administration'].map(renderModuleGroup)}
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Roles
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{roles}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={icon.mdiShieldAccountVariantOutline || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_PERMISSIONS') && <Link href={'/permissions/permissions-list'}>
|
||||||
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Permissions
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{permissions}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={icon.mdiShieldAccountOutline || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_TENANTS') && <Link href={'/tenants/tenants-list'}>
|
||||||
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Tenants
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{tenants}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={icon.mdiTable || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_USER_ROLES') && <Link href={'/user_roles/user_roles-list'}>
|
||||||
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
User roles
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{user_roles}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={'mdiAccountMultipleCheck' in icon ? icon['mdiAccountMultipleCheck' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_MODULES') && <Link href={'/modules/modules-list'}>
|
||||||
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Modules
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{modules}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={'mdiPuzzle' in icon ? icon['mdiPuzzle' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_TENANT_MODULES') && <Link href={'/tenant_modules/tenant_modules-list'}>
|
||||||
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Tenant modules
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{tenant_modules}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={'mdiToggleSwitch' in icon ? icon['mdiToggleSwitch' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_PLANS') && <Link href={'/plans/plans-list'}>
|
||||||
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Plans
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{plans}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={'mdiFinance' in icon ? icon['mdiFinance' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_SUBSCRIPTIONS') && <Link href={'/subscriptions/subscriptions-list'}>
|
||||||
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Subscriptions
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{subscriptions}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={'mdiCalendarClock' in icon ? icon['mdiCalendarClock' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_ACTIVATION_CODES') && <Link href={'/activation_codes/activation_codes-list'}>
|
||||||
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Activation codes
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{activation_codes}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={'mdiKey' in icon ? icon['mdiKey' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_LICENSES') && <Link href={'/licenses/licenses-list'}>
|
||||||
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Licenses
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{licenses}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={'mdiCertificate' in icon ? icon['mdiCertificate' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_AUDIT_LOGS') && <Link href={'/audit_logs/audit_logs-list'}>
|
||||||
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Audit logs
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{audit_logs}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={'mdiHistory' in icon ? icon['mdiHistory' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_PRODUCTS') && <Link href={'/products/products-list'}>
|
||||||
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Products
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{products}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={'mdiPackageVariant' in icon ? icon['mdiPackageVariant' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_STOCK_MOVEMENTS') && <Link href={'/stock_movements/stock_movements-list'}>
|
||||||
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Stock movements
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{stock_movements}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={'mdiSwapHorizontal' in icon ? icon['mdiSwapHorizontal' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_WAREHOUSES') && <Link href={'/warehouses/warehouses-list'}>
|
||||||
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Warehouses
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{warehouses}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={'mdiWarehouse' in icon ? icon['mdiWarehouse' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_WAREHOUSE_LOCATIONS') && <Link href={'/warehouse_locations/warehouse_locations-list'}>
|
||||||
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Warehouse locations
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{warehouse_locations}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={'mdiMapMarkerRadius' in icon ? icon['mdiMapMarkerRadius' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_CUSTOMERS') && <Link href={'/customers/customers-list'}>
|
||||||
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Customers
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{customers}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={'mdiAccountCircle' in icon ? icon['mdiAccountCircle' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_SUPPLIERS') && <Link href={'/suppliers/suppliers-list'}>
|
||||||
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Suppliers
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{suppliers}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={'mdiTruckDelivery' in icon ? icon['mdiTruckDelivery' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_INVOICES') && <Link href={'/invoices/invoices-list'}>
|
||||||
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Invoices
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{invoices}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={'mdiFileInvoice' in icon ? icon['mdiFileInvoice' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_INVOICE_ITEMS') && <Link href={'/invoice_items/invoice_items-list'}>
|
||||||
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Invoice items
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{invoice_items}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={'mdiReceipt' in icon ? icon['mdiReceipt' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_PAYMENTS') && <Link href={'/payments/payments-list'}>
|
||||||
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Payments
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{payments}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={'mdiCreditCard' in icon ? icon['mdiCreditCard' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_EXPENSES') && <Link href={'/expenses/expenses-list'}>
|
||||||
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Expenses
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{expenses}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={'mdiClipboardList' in icon ? icon['mdiClipboardList' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_EQUIPMENT_ASSETS') && <Link href={'/equipment_assets/equipment_assets-list'}>
|
||||||
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Equipment assets
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{equipment_assets}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={'mdiScrewdriverWrench' in icon ? icon['mdiScrewdriverWrench' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_ORDERS') && <Link href={'/orders/orders-list'}>
|
||||||
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Orders
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{orders}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={'mdiTruck' in icon ? icon['mdiTruck' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'READ_ORDER_ITEMS') && <Link href={'/order_items/order_items-list'}>
|
||||||
|
<div
|
||||||
|
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between align-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||||
|
Order items
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl leading-tight font-semibold">
|
||||||
|
{order_items}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<BaseIcon
|
||||||
|
className={`${iconsColor}`}
|
||||||
|
w="w-16"
|
||||||
|
h="h-16"
|
||||||
|
size={48}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
path={'mdiFormatListBulleted' in icon ? icon['mdiFormatListBulleted' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,192 +1,166 @@
|
|||||||
import React from 'react';
|
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import BaseButton from '../components/BaseButton';
|
import BaseButton from '../components/BaseButton';
|
||||||
|
import CardBox from '../components/CardBox';
|
||||||
|
import SectionFullScreen from '../components/SectionFullScreen';
|
||||||
import LayoutGuest from '../layouts/Guest';
|
import LayoutGuest from '../layouts/Guest';
|
||||||
|
import BaseDivider from '../components/BaseDivider';
|
||||||
|
import BaseButtons from '../components/BaseButtons';
|
||||||
import { getPageTitle } from '../config';
|
import { getPageTitle } from '../config';
|
||||||
import { mdiPackageVariant, mdiWarehouse, mdiInvoice, mdiTruck, mdiWrench } from '@mdi/js';
|
import { useAppSelector } from '../stores/hooks';
|
||||||
import BaseIcon from '../components/BaseIcon';
|
import CardBoxComponentTitle from "../components/CardBoxComponentTitle";
|
||||||
|
import { getPexelsImage, getPexelsVideo } from '../helpers/pexels';
|
||||||
|
|
||||||
const features = [
|
|
||||||
{
|
|
||||||
title: 'Inventory Management',
|
|
||||||
description: 'Track stock levels, products, and movements in real-time.',
|
|
||||||
icon: mdiPackageVariant,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Storage & Warehouses',
|
|
||||||
description: 'Manage multiple warehouses and locations with ease.',
|
|
||||||
icon: mdiWarehouse,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Accounting & Invoices',
|
|
||||||
description: 'Complete billing system with payments and expense tracking.',
|
|
||||||
icon: mdiInvoice,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Shipping & Orders',
|
|
||||||
description: 'Handle customer orders and shipping workflows efficiently.',
|
|
||||||
icon: mdiTruck,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Asset Management',
|
|
||||||
description: 'Track equipment and assets across your organization.',
|
|
||||||
icon: mdiWrench,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function LandingPage() {
|
export default function Starter() {
|
||||||
|
const [illustrationImage, setIllustrationImage] = useState({
|
||||||
|
src: undefined,
|
||||||
|
photographer: undefined,
|
||||||
|
photographer_url: undefined,
|
||||||
|
})
|
||||||
|
const [illustrationVideo, setIllustrationVideo] = useState({video_files: []})
|
||||||
|
const [contentType, setContentType] = useState('image');
|
||||||
|
const [contentPosition, setContentPosition] = useState('right');
|
||||||
|
const textColor = useAppSelector((state) => state.style.linkColor);
|
||||||
|
|
||||||
|
const title = 'DIGIHELP-IMS'
|
||||||
|
|
||||||
|
// Fetch Pexels image/video
|
||||||
|
useEffect(() => {
|
||||||
|
async function fetchData() {
|
||||||
|
const image = await getPexelsImage();
|
||||||
|
const video = await getPexelsVideo();
|
||||||
|
setIllustrationImage(image);
|
||||||
|
setIllustrationVideo(video);
|
||||||
|
}
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const imageBlock = (image) => (
|
||||||
|
<div
|
||||||
|
className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'
|
||||||
|
style={{
|
||||||
|
backgroundImage: `${
|
||||||
|
image
|
||||||
|
? `url(${image?.src?.original})`
|
||||||
|
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
|
||||||
|
}`,
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'left center',
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='flex justify-center w-full bg-blue-300/20'>
|
||||||
|
<a
|
||||||
|
className='text-[8px]'
|
||||||
|
href={image?.photographer_url}
|
||||||
|
target='_blank'
|
||||||
|
rel='noreferrer'
|
||||||
|
>
|
||||||
|
Photo by {image?.photographer} on Pexels
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const videoBlock = (video) => {
|
||||||
|
if (video?.video_files?.length > 0) {
|
||||||
return (
|
return (
|
||||||
<div className="bg-white text-gray-900 font-sans">
|
<div className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'>
|
||||||
|
<video
|
||||||
|
className='absolute top-0 left-0 w-full h-full object-cover'
|
||||||
|
autoPlay
|
||||||
|
loop
|
||||||
|
muted
|
||||||
|
>
|
||||||
|
<source src={video?.video_files[0]?.link} type='video/mp4'/>
|
||||||
|
Your browser does not support the video tag.
|
||||||
|
</video>
|
||||||
|
<div className='flex justify-center w-full bg-blue-300/20 z-10'>
|
||||||
|
<a
|
||||||
|
className='text-[8px]'
|
||||||
|
href={video?.user?.url}
|
||||||
|
target='_blank'
|
||||||
|
rel='noreferrer'
|
||||||
|
>
|
||||||
|
Video by {video.user.name} on Pexels
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
contentPosition === 'background'
|
||||||
|
? {
|
||||||
|
backgroundImage: `${
|
||||||
|
illustrationImage
|
||||||
|
? `url(${illustrationImage.src?.original})`
|
||||||
|
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
|
||||||
|
}`,
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'left center',
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
}
|
||||||
|
: {}
|
||||||
|
}
|
||||||
|
>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{getPageTitle('DIGIHELP-IMS - Integrated Management Suite')}</title>
|
<title>{getPageTitle('Starter Page')}</title>
|
||||||
<meta name="description" content="Next-generation SaaS for Inventory, Storage, Accounting, and Enterprise Management." />
|
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
{/* Hero Section */}
|
<SectionFullScreen bg='violet'>
|
||||||
<section className="relative py-20 overflow-hidden bg-gradient-to-br from-indigo-900 via-violet-800 to-indigo-900 text-white">
|
<div
|
||||||
<div className="absolute top-0 left-0 w-full h-full opacity-10">
|
className={`flex ${
|
||||||
<div className="absolute top-10 left-10 w-64 h-64 bg-white rounded-full blur-3xl"></div>
|
contentPosition === 'right' ? 'flex-row-reverse' : 'flex-row'
|
||||||
<div className="absolute bottom-10 right-10 w-96 h-96 bg-indigo-300 rounded-full blur-3xl"></div>
|
} min-h-screen w-full`}
|
||||||
|
>
|
||||||
|
{contentType === 'image' && contentPosition !== 'background'
|
||||||
|
? imageBlock(illustrationImage)
|
||||||
|
: null}
|
||||||
|
{contentType === 'video' && contentPosition !== 'background'
|
||||||
|
? videoBlock(illustrationVideo)
|
||||||
|
: null}
|
||||||
|
<div className='flex items-center justify-center flex-col space-y-4 w-full lg:w-full'>
|
||||||
|
<CardBox className='w-full md:w-3/5 lg:w-2/3'>
|
||||||
|
<CardBoxComponentTitle title="Welcome to your DIGIHELP-IMS app!"/>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<p className='text-center '>This is a React.js/Node.js app generated by the <a className={`${textColor}`} href="https://flatlogic.com/generator">Flatlogic Web App Generator</a></p>
|
||||||
|
<p className='text-center '>For guides and documentation please check
|
||||||
|
your local README.md and the <a className={`${textColor}`} href="https://flatlogic.com/documentation">Flatlogic documentation</a></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="container mx-auto px-6 relative z-10 text-center">
|
<BaseButtons>
|
||||||
<h1 className="text-5xl md:text-7xl font-extrabold mb-6 tracking-tight animate-fade-in">
|
|
||||||
DIGIHELP-IMS
|
|
||||||
</h1>
|
|
||||||
<p className="text-xl md:text-2xl mb-10 text-indigo-100 max-w-2xl mx-auto leading-relaxed">
|
|
||||||
The Integrated Management Suite for Modern Enterprise.
|
|
||||||
Streamline Inventory, Accounting, and Logistics in one unified platform.
|
|
||||||
</p>
|
|
||||||
<div className="flex flex-col md:flex-row justify-center space-y-4 md:space-y-0 md:space-x-6">
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
href="/register"
|
href='/login'
|
||||||
label="Start Free Trial"
|
label='Login'
|
||||||
color="white"
|
color='info'
|
||||||
className="px-10 py-4 text-lg font-bold rounded-xl shadow-2xl hover:scale-105 transition-all duration-300"
|
className='w-full'
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
|
||||||
href="/login"
|
|
||||||
label="Sign In"
|
|
||||||
outline
|
|
||||||
color="white"
|
|
||||||
className="px-10 py-4 text-lg font-bold rounded-xl border-2 hover:bg-white/10 transition-all duration-300"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Features Section */}
|
</BaseButtons>
|
||||||
<section className="py-24 bg-gray-50">
|
</CardBox>
|
||||||
<div className="container mx-auto px-6">
|
</div>
|
||||||
<div className="text-center mb-20">
|
</div>
|
||||||
<h2 className="text-3xl md:text-4xl font-bold mb-4 text-gray-900">Comprehensive Enterprise Modules</h2>
|
</SectionFullScreen>
|
||||||
<div className="w-20 h-1 bg-indigo-600 mx-auto mb-6"></div>
|
<div className='bg-black text-white flex flex-col text-center justify-center md:flex-row'>
|
||||||
<p className="text-gray-600 max-w-2xl mx-auto text-lg">
|
<p className='py-6 text-sm'>© 2026 <span>{title}</span>. All rights reserved</p>
|
||||||
Everything you need to run your business operations smoothly and efficiently.
|
<Link className='py-6 ml-4 text-sm' href='/privacy-policy/'>
|
||||||
</p>
|
Privacy Policy
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10">
|
|
||||||
{features.map((feature, index) => (
|
|
||||||
<div key={index} className="bg-white p-10 rounded-3xl shadow-sm border border-gray-100 hover:shadow-2xl hover:-translate-y-2 transition-all duration-300 group">
|
|
||||||
<div className="w-16 h-16 bg-indigo-50 text-indigo-600 rounded-2xl flex items-center justify-center mb-8 group-hover:bg-indigo-600 group-hover:text-white transition-colors duration-300">
|
|
||||||
<BaseIcon path={feature.icon} size={36} />
|
|
||||||
</div>
|
|
||||||
<h3 className="text-2xl font-bold mb-4">{feature.title}</h3>
|
|
||||||
<p className="text-gray-600 leading-relaxed text-lg">{feature.description}</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* How It Works Section */}
|
|
||||||
<section className="py-24 bg-indigo-900 text-white">
|
|
||||||
<div className="container mx-auto px-6">
|
|
||||||
<div className="text-center mb-16">
|
|
||||||
<h2 className="text-3xl md:text-4xl font-bold mb-4">How It Works</h2>
|
|
||||||
<p className="text-indigo-200">Scale your business in three simple steps.</p>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-12 text-center">
|
|
||||||
{[
|
|
||||||
{ step: '01', title: 'Register Tenant', desc: 'Create your organization account and subdomain.' },
|
|
||||||
{ step: '02', title: 'Activate Modules', desc: 'Choose the modules your business needs to grow.' },
|
|
||||||
{ step: '03', title: 'Start Scaling', desc: 'Onboard your team and automate your workflows.' },
|
|
||||||
].map((item, idx) => (
|
|
||||||
<div key={idx} className="relative">
|
|
||||||
<div className="text-6xl font-black text-white/10 absolute -top-10 left-1/2 -translate-x-1/2 z-0">{item.step}</div>
|
|
||||||
<h3 className="text-2xl font-bold mb-4 relative z-10">{item.title}</h3>
|
|
||||||
<p className="text-indigo-200 relative z-10">{item.desc}</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Pricing Section */}
|
|
||||||
<section className="py-24 bg-white">
|
|
||||||
<div className="container mx-auto px-6">
|
|
||||||
<div className="text-center mb-16">
|
|
||||||
<h2 className="text-3xl md:text-4xl font-bold mb-4 text-gray-900 text-center">Flexible Subscription Plans</h2>
|
|
||||||
<p className="text-gray-600">Choose a plan that fits your business scale.</p>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
|
||||||
{['Weekly', 'Monthly', 'Yearly', 'Lifetime'].map((plan) => (
|
|
||||||
<div key={plan} className={`p-8 rounded-3xl border ${plan === 'Yearly' ? 'border-indigo-600 bg-indigo-50 ring-4 ring-indigo-600 ring-opacity-10' : 'border-gray-200'} hover:shadow-xl transition-all duration-300`}>
|
|
||||||
<h3 className="text-lg font-semibold text-gray-500 mb-2 uppercase tracking-widest">{plan}</h3>
|
|
||||||
<div className="text-4xl font-bold mb-6 text-indigo-600">
|
|
||||||
{plan === 'Lifetime' ? 'Contact' : '$XX'}
|
|
||||||
</div>
|
|
||||||
<ul className="mb-8 space-y-3 text-gray-600 text-sm">
|
|
||||||
<li>✓ All Modules Included</li>
|
|
||||||
<li>✓ Unlimited Users</li>
|
|
||||||
<li>✓ Priority Support</li>
|
|
||||||
<li>✓ 99.9% Uptime SLA</li>
|
|
||||||
</ul>
|
|
||||||
<BaseButton label="Get Started" color={plan === 'Yearly' ? 'info' : 'white'} outline={plan !== 'Yearly'} className="w-full rounded-xl py-3 font-bold" href="/register" />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Footer */}
|
|
||||||
<footer className="bg-gray-900 text-white py-16 border-t border-gray-800">
|
|
||||||
<div className="container mx-auto px-6">
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-12 mb-12">
|
|
||||||
<div>
|
|
||||||
<h3 className="text-2xl font-bold mb-6 text-indigo-400">DIGIHELP-IMS</h3>
|
|
||||||
<p className="text-gray-400 leading-relaxed">
|
|
||||||
Next-generation enterprise resource planning and management suite.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 className="font-bold mb-6 uppercase tracking-wider text-gray-500 text-sm">Product</h4>
|
|
||||||
<ul className="space-y-4 text-gray-400">
|
|
||||||
<li><Link href="/login" className="hover:text-white transition-colors">Dashboard</Link></li>
|
|
||||||
<li><Link href="/register" className="hover:text-white transition-colors">Pricing</Link></li>
|
|
||||||
<li><Link href="/modules" className="hover:text-white transition-colors">Modules</Link></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 className="font-bold mb-6 uppercase tracking-wider text-gray-500 text-sm">Legal</h4>
|
|
||||||
<ul className="space-y-4 text-gray-400">
|
|
||||||
<li><Link href="/privacy-policy" className="hover:text-white transition-colors">Privacy Policy</Link></li>
|
|
||||||
<li><Link href="/terms-of-use" className="hover:text-white transition-colors">Terms of Service</Link></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="pt-12 border-t border-gray-800 text-center text-gray-500 text-sm">
|
|
||||||
<p>© 2026 DIGIHELP-IMS. All rights reserved.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
LandingPage.getLayout = function getLayout(page: ReactElement) {
|
Starter.getLayout = function getLayout(page: ReactElement) {
|
||||||
return <LayoutGuest>{page}</LayoutGuest>;
|
return <LayoutGuest>{page}</LayoutGuest>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user