refactor: update tenant dashboard UI with styled GradientStatCard components and refreshed layout header
This commit is contained in:
parent
2cfce21323
commit
d36c34aa9f
@ -87,7 +87,7 @@ export const DataTable = <T,>({
|
||||
return (
|
||||
<th
|
||||
key={column.key}
|
||||
className={`px-3 md:px-2 lg:px-4 xl:px-5 py-2 md:py-1 lg:py-2.5 xl:py-3 ${alignClass} text-[10px] md:text-[8px] lg:text-xs font-medium text-[#9aa6b2] uppercase`}
|
||||
className={`px-3 md:px-2 lg:px-4 xl:px-5 py-2 md:py-1 lg:py-2.5 xl:py-3 ${alignClass} text-[10px] md:text-[8px] lg:text-[13px] font-medium text-[#9aa6b2] uppercase`}
|
||||
>
|
||||
{column.label}
|
||||
</th>
|
||||
@ -97,7 +97,7 @@ export const DataTable = <T,>({
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colSpan={desktopColSpan} className="px-3 md:px-2 lg:px-4 xl:px-5 py-6 md:py-3 lg:py-7 xl:py-8 text-center text-xs md:text-xs lg:text-sm text-[#6b7280]">
|
||||
<td colSpan={desktopColSpan} className="px-3 md:px-2 lg:px-4 xl:px-5 py-6 md:py-3 lg:py-7 xl:py-8 text-center text-xs md:text-xs lg:text-[13px] text-[#6b7280]">
|
||||
{emptyMessage}
|
||||
</td>
|
||||
</tr>
|
||||
@ -134,7 +134,7 @@ export const DataTable = <T,>({
|
||||
return (
|
||||
<th
|
||||
key={column.key}
|
||||
className={`px-3 md:px-2 lg:px-4 xl:px-5 py-2 md:py-1 lg:py-2.5 xl:py-3 ${alignClass} text-[10px] md:text-[8px] lg:text-xs font-medium text-[#9aa6b2] uppercase`}
|
||||
className={`px-3 md:px-2 lg:px-4 xl:px-5 py-2 md:py-1 lg:py-2.5 xl:py-3 ${alignClass} text-[10px] md:text-[8px] lg:text-[13px] font-medium text-[#9aa6b2] uppercase`}
|
||||
>
|
||||
{column.label}
|
||||
</th>
|
||||
@ -175,7 +175,7 @@ export const DataTable = <T,>({
|
||||
? 'text-center'
|
||||
: 'text-left';
|
||||
return (
|
||||
<td key={column.key} className={`px-3 md:px-2 lg:px-4 xl:px-5 py-2.5 md:py-1.5 lg:py-3 xl:py-4 ${alignClass} text-xs md:text-[10px] lg:text-sm`}>
|
||||
<td key={column.key} className={`px-3 md:px-2 lg:px-4 xl:px-5 py-2.5 md:py-1.5 lg:py-3 xl:py-4 ${alignClass} text-xs md:text-[10px] lg:text-[13px]`}>
|
||||
{column.render ? column.render(item) : String((item as any)[column.key])}
|
||||
</td>
|
||||
);
|
||||
|
||||
@ -16,18 +16,18 @@ export const GradientStatCard: React.FC<GradientStatCardProps> = ({ icon: Icon,
|
||||
return (
|
||||
<div
|
||||
className="rounded-[8px] p-[1px] h-full"
|
||||
style={{
|
||||
style={{
|
||||
background: 'var(--Linear, linear-gradient(161deg, #084CC8 -1.15%, #75C044 44.29%, #FED314 89.74%))',
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col items-start gap-3 px-4 py-4 min-h-[108px] h-full w-full rounded-[7px] bg-[#F8FAFC]">
|
||||
<div className="flex items-start justify-between w-full">
|
||||
<div className="flex flex-col items-start gap-3 px-4 py-4 min-h-[108px] h-full w-full rounded-[7px] bg-white">
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<div className="text-[#94A3B8]">
|
||||
<Icon className="w-5 h-5 stroke-[1.8]" />
|
||||
</div>
|
||||
{badge && (
|
||||
<div className={cn(
|
||||
"px-2.5 py-1 rounded-full text-[10px] font-bold tracking-tight whitespace-nowrap",
|
||||
"px-2.5 py-1 rounded-full text-[12px] font-bold tracking-tight whitespace-nowrap",
|
||||
(badge.variant === 'success' || badge.variant === 'green') ? "bg-[#f1fffb] text-[#16c784]" :
|
||||
badge.variant === 'warning' ? "bg-[#fff5e5] text-[#fca004]" :
|
||||
badge.variant === 'info' ? "bg-[#f0f9ff] text-[#0ea5e9]" :
|
||||
@ -40,10 +40,10 @@ export const GradientStatCard: React.FC<GradientStatCardProps> = ({ icon: Icon,
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="text-[28px] leading-none font-bold text-[#1E293B] tracking-[-0.02em]">
|
||||
<h4 className="text-[24px] leading-none font-bold text-[#1E293B] tracking-[-0.02em]">
|
||||
{value}
|
||||
</h4>
|
||||
<p className="text-[11px] font-semibold text-[#64748B] uppercase tracking-wider mt-1">
|
||||
<p className="text-[12px] font-semibold text-[#64748B] uppercase tracking-wider mt-1">
|
||||
{label}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
Building2,
|
||||
BadgeCheck,
|
||||
GitBranch,
|
||||
Zap,
|
||||
} from "lucide-react";
|
||||
import { Layout } from "@/components/layout/Layout";
|
||||
import {
|
||||
@ -23,8 +24,14 @@ import {
|
||||
SuppliersTable,
|
||||
type Column,
|
||||
PrimaryButton,
|
||||
GradientStatCard,
|
||||
} from "@/components/shared";
|
||||
import { UsersTable, RolesTable, DepartmentsTable, type DepartmentsTableRef } from "@/components/superadmin";
|
||||
import {
|
||||
UsersTable,
|
||||
RolesTable,
|
||||
DepartmentsTable,
|
||||
type DepartmentsTableRef,
|
||||
} from "@/components/superadmin";
|
||||
import { Plus } from "lucide-react";
|
||||
import { tenantService } from "@/services/tenant-service";
|
||||
import { moduleService } from "@/services/module-service";
|
||||
@ -210,41 +217,52 @@ const TenantDetails = (): ReactElement => {
|
||||
]}
|
||||
pageHeader={{
|
||||
title: tenant.name,
|
||||
action: activeTab === "departments" ? (
|
||||
<PrimaryButton
|
||||
onClick={() => departmentsRef.current?.openNewModal()}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
<span>New Department</span>
|
||||
</PrimaryButton>
|
||||
) : undefined
|
||||
action:
|
||||
activeTab === "departments" ? (
|
||||
<PrimaryButton
|
||||
onClick={() => departmentsRef.current?.openNewModal()}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
<span>New Department</span>
|
||||
</PrimaryButton>
|
||||
) : undefined,
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-6">
|
||||
{/* Tenant Header Card */}
|
||||
<div className="bg-white border border-[rgba(0,0,0,0.08)] rounded-lg p-4 md:p-6">
|
||||
<div className="flex flex-col md:flex-row md:items-start md:justify-between gap-4">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-12 h-12 md:w-16 md:h-16 bg-[#f5f7fa] border border-[rgba(0,0,0,0.08)] rounded-lg flex items-center justify-center shrink-0">
|
||||
<span className="text-lg md:text-xl font-normal text-[#9aa6b2]">
|
||||
{getTenantInitials(tenant.name)}
|
||||
</span>
|
||||
<div className="flex items-start justify-between self-stretch rounded-[4px] border border-[#D1D5DB] bg-white px-3 py-4">
|
||||
{/* Left Section */}
|
||||
<div className="flex items-start gap-6">
|
||||
{/* Avatar */}
|
||||
<div className="flex h-16 w-16 shrink-0 items-center justify-center rounded-[8px] bg-[#F3F4F6]">
|
||||
<span className="text-[32px] font-semibold leading-none text-[#111827]">
|
||||
{getTenantInitials(tenant.name)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex flex-col gap-2">
|
||||
{/* Name + Status */}
|
||||
<div className="flex items-center gap-3">
|
||||
<h1 className="text-[30px] font-semibold leading-[36px] tracking-[-0.6px] text-[#111827]">
|
||||
{tenant.name}
|
||||
</h1>
|
||||
|
||||
<StatusBadge variant={getStatusVariant(tenant.status)}>
|
||||
{tenant.status}
|
||||
</StatusBadge>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<h1 className="text-xl md:text-2xl font-bold text-[#0f1724] tracking-[-0.48px] truncate">
|
||||
{tenant.name}
|
||||
</h1>
|
||||
<StatusBadge variant={getStatusVariant(tenant.status)}>
|
||||
{tenant.status}
|
||||
</StatusBadge>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-4 md:gap-6 text-sm font-normal text-[#6b7280]">
|
||||
|
||||
{/* Meta Information */}
|
||||
<div className="flex flex-col gap-2 text-sm text-[#6B7280]">
|
||||
{/* First Row */}
|
||||
<div className="flex flex-wrap items-center gap-6">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Hash className="w-4 h-4" />
|
||||
<span className="truncate">{tenant.slug}</span>
|
||||
<Hash className="h-4 w-4" />
|
||||
<span>ID: {tenant.slug}</span>
|
||||
</div>
|
||||
|
||||
{tenant.domain && (
|
||||
<a
|
||||
href={
|
||||
@ -254,31 +272,44 @@ const TenantDetails = (): ReactElement => {
|
||||
}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-1.5 text-[#6b7280] hover:text-[#112868] transition-colors"
|
||||
className="flex items-center gap-1.5 hover:text-[#112868] transition-colors"
|
||||
>
|
||||
<Globe className="w-4 h-4" />
|
||||
<span className="truncate">{tenant.domain}</span>
|
||||
<Globe className="h-4 w-4" />
|
||||
<span>{tenant.domain}</span>
|
||||
</a>
|
||||
)}
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Calendar className="w-4 h-4" />
|
||||
<span>Created {formatDate(tenant.created_at)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Second Row */}
|
||||
<div className="flex flex-wrap items-center gap-6">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Calendar className="h-4 w-4" />
|
||||
<span>Created: {formatDate(tenant.created_at)}</span>
|
||||
</div>
|
||||
|
||||
{/* {tenant.primary_contact && (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<User className="h-4 w-4" />
|
||||
<span>Admin: {tenant.primary_contact}</span>
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => navigate(`/tenants/${id}/edit`)}
|
||||
className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-[#112868] bg-white border border-[rgba(0,0,0,0.08)] rounded-md hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<Edit className="w-4 h-4" />
|
||||
<span>Edit Tenant</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Edit Button */}
|
||||
<button
|
||||
onClick={() => navigate(`/tenants/${id}/edit`)}
|
||||
className="flex items-center gap-2 rounded-md border border-[#D1D5DB] bg-white px-4 py-2 text-sm font-medium text-[#112868] transition-colors hover:bg-[#F9FAFB]"
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
<span>Edit Tenant</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="border border-[rgba(0,0,0,0.08)] rounded-lg bg-white">
|
||||
<div >
|
||||
<div className="border-b border-[rgba(0,0,0,0.08)] px-4 md:px-6">
|
||||
<div className="flex gap-1 overflow-x-auto">
|
||||
{tabs.map((tab) => (
|
||||
@ -310,7 +341,11 @@ const TenantDetails = (): ReactElement => {
|
||||
<RolesTable tenantId={id} compact={false} />
|
||||
)}
|
||||
{activeTab === "departments" && id && (
|
||||
<DepartmentsTable ref={departmentsRef} tenantId={id} compact={true} />
|
||||
<DepartmentsTable
|
||||
ref={departmentsRef}
|
||||
tenantId={id}
|
||||
compact={true}
|
||||
/>
|
||||
)}
|
||||
{activeTab === "designations" && id && (
|
||||
<DesignationsTable tenantId={id} compact={false} />
|
||||
@ -367,105 +402,85 @@ const OverviewTab = ({ tenant, stats }: OverviewTabProps): ReactElement => {
|
||||
<div className="flex flex-col gap-6">
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div className="bg-[#f5f7fa] border border-[rgba(0,0,0,0.08)] rounded-lg p-4">
|
||||
<div className="text-sm font-medium text-[#6b7280] mb-1">
|
||||
Total Users
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-[#0f1724]">
|
||||
{stats?.totalUsers || 0}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-[#f5f7fa] border border-[rgba(0,0,0,0.08)] rounded-lg p-4">
|
||||
<div className="text-sm font-medium text-[#6b7280] mb-1">
|
||||
Total Modules
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-[#0f1724]">
|
||||
{stats?.totalModules || 0}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-[#f5f7fa] border border-[rgba(0,0,0,0.08)] rounded-lg p-4">
|
||||
<div className="text-sm font-medium text-[#6b7280] mb-1">
|
||||
Active Modules
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-[#0f1724]">
|
||||
{stats?.activeModules || 0}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-[#f5f7fa] border border-[rgba(0,0,0,0.08)] rounded-lg p-4">
|
||||
<div className="text-sm font-medium text-[#6b7280] mb-1">
|
||||
Subscription Tier
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-[#0f1724] capitalize">
|
||||
{stats?.subscriptionTier || "N/A"}
|
||||
</div>
|
||||
</div>
|
||||
<GradientStatCard
|
||||
icon={Users}
|
||||
value={stats?.totalUsers || 0}
|
||||
label="Total Users"
|
||||
/>
|
||||
<GradientStatCard
|
||||
icon={Package}
|
||||
value={stats?.totalModules || 0}
|
||||
label="Total Modules"
|
||||
/>
|
||||
<GradientStatCard
|
||||
icon={Zap}
|
||||
value={stats?.activeModules || 0}
|
||||
label="Active Modules"
|
||||
badge={
|
||||
stats?.activeModules && stats.activeModules > 0
|
||||
? { text: "Running", variant: "success" }
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<GradientStatCard
|
||||
icon={CreditCard}
|
||||
value={stats?.subscriptionTier || "N/A"}
|
||||
label="Subscription Tier"
|
||||
badge={{ text: "Plan", variant: "info" }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* General Information */}
|
||||
<div className="bg-[#f5f7fa] border border-[rgba(0,0,0,0.08)] rounded-lg p-4 md:p-6">
|
||||
<h3 className="text-lg font-semibold text-[#0f1724] mb-4">
|
||||
General Information
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<div className="text-sm font-medium text-[#6b7280] mb-1">
|
||||
<div className="flex flex-col items-start gap-[10px] rounded-[8px] border border-[#D1D5DB] bg-white p-4">
|
||||
{/* Header Section */}
|
||||
<div className="flex w-full flex-col items-start border-b border-[#D1D5DB] pb-2">
|
||||
<h3 className="text-[20px] font-semibold leading-[28px] text-[#111827]">
|
||||
General Information
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{/* Content Section */}
|
||||
<div className="flex w-full flex-col items-start gap-[10px]">
|
||||
<div className="grid w-full grid-cols-1 gap-y-4 md:grid-cols-[180px_1fr]">
|
||||
{/* Tenant Name */}
|
||||
<div className="text-sm font-medium text-[#6B7280]">
|
||||
Tenant Name
|
||||
</div>
|
||||
<div className="text-sm font-normal text-[#0f1724]">
|
||||
<div className="text-sm font-normal text-[#111827]">
|
||||
{tenant.name}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-[#6b7280] mb-1">Slug</div>
|
||||
<div className="text-sm font-normal text-[#0f1724]">
|
||||
|
||||
{/* Slug */}
|
||||
<div className="text-sm font-medium text-[#6B7280]">Slug</div>
|
||||
<div className="text-sm font-normal text-[#111827]">
|
||||
{tenant.slug}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-[#6b7280] mb-1">
|
||||
Status
|
||||
|
||||
{/* Status */}
|
||||
<div className="text-sm font-medium text-[#6B7280]">Status</div>
|
||||
<div>
|
||||
<StatusBadge variant={getStatusVariant(tenant.status)}>
|
||||
{tenant.status}
|
||||
</StatusBadge>
|
||||
</div>
|
||||
<StatusBadge variant={getStatusVariant(tenant.status)}>
|
||||
{tenant.status}
|
||||
</StatusBadge>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-[#6b7280] mb-1">
|
||||
|
||||
{/* Subscription Tier */}
|
||||
<div className="text-sm font-medium text-[#6B7280]">
|
||||
Subscription Tier
|
||||
</div>
|
||||
<div className="text-sm font-normal text-[#0f1724] capitalize">
|
||||
<div className="text-sm font-normal text-[#111827] capitalize">
|
||||
{tenant.subscription_tier || "N/A"}
|
||||
</div>
|
||||
</div>
|
||||
{/* <div>
|
||||
<div className="text-sm font-medium text-[#6b7280] mb-1">
|
||||
Max Users
|
||||
</div>
|
||||
<div className="text-sm font-normal text-[#0f1724]">
|
||||
{tenant.max_users || "Unlimited"}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-[#6b7280] mb-1">
|
||||
Max Modules
|
||||
</div>
|
||||
<div className="text-sm font-normal text-[#0f1724]">
|
||||
{tenant.max_modules || "Unlimited"}
|
||||
</div>
|
||||
</div> */}
|
||||
<div>
|
||||
<div className="text-sm font-medium text-[#6b7280] mb-1">
|
||||
Created At
|
||||
</div>
|
||||
<div className="text-sm font-normal text-[#0f1724]">
|
||||
|
||||
{/* Created At */}
|
||||
<div className="text-sm font-medium text-[#6B7280]">Created At</div>
|
||||
<div className="text-sm font-normal text-[#111827]">
|
||||
{formatDate(tenant.created_at)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-[#6b7280] mb-1">
|
||||
Updated At
|
||||
</div>
|
||||
<div className="text-sm font-normal text-[#0f1724]">
|
||||
|
||||
{/* Updated At */}
|
||||
<div className="text-sm font-medium text-[#6B7280]">Updated At</div>
|
||||
<div className="text-sm font-normal text-[#111827]">
|
||||
{formatDate(tenant.updated_at)}
|
||||
</div>
|
||||
</div>
|
||||
@ -644,7 +659,6 @@ const LicenseTab = ({ tenant: _tenant }: LicenseTabProps): ReactElement => {
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// Billing Tab Component
|
||||
interface BillingTabProps {
|
||||
tenant: Tenant;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user