Project structure changes ad new files added in dashboard folders
This commit is contained in:
14
components/common/button.tsx
Normal file
14
components/common/button.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { ButtonProps } from "@/types/types";
|
||||
|
||||
|
||||
export default function Button({
|
||||
children,
|
||||
onClick,
|
||||
className,
|
||||
}: ButtonProps) {
|
||||
return (
|
||||
<button onClick={onClick} className={className}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
14
components/common/cardHeader.tsx
Normal file
14
components/common/cardHeader.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
|
||||
export function CardHeader({ title, subtitle }: { title: string; subtitle?: string }) {
|
||||
return (
|
||||
<div className="flex justify-between items-baseline mb-2 border-b border-[#162942]/50 pb-3">
|
||||
<h3 className="text-white text-base font-semibold tracking-wide">{title}</h3>
|
||||
{subtitle && (
|
||||
<span className="text-[#4e6178] text-xs font-medium uppercase tracking-wider">
|
||||
{subtitle}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
9
components/common/cardWrapper.tsx
Normal file
9
components/common/cardWrapper.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
export function Card({ children, className = "" }:
|
||||
{ children: React.ReactNode; className?: string }) {
|
||||
return (
|
||||
<div className={`bg-[#0a1526] border border-[#162942] rounded-xl p-3
|
||||
shadow-xl flex flex-col justify-between ${className}`}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
66
components/common/dashStatCard.tsx
Normal file
66
components/common/dashStatCard.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { StatCardProps } from "@/types/types";
|
||||
import { colorMap } from "@/constants/constant";
|
||||
|
||||
export default function StatCard({
|
||||
title,
|
||||
value,
|
||||
variant = "progress",
|
||||
subtitle,
|
||||
progress,
|
||||
color = "teal",
|
||||
icon: Icon,
|
||||
badgeText,
|
||||
badgeClassName = "text-slate-500 font-mono uppercase",
|
||||
iconContainerClassName = "bg-[#11233a] border-[#1b2c3f]/50 text-cyan-400",
|
||||
isActiveCard = false,
|
||||
}: StatCardProps) {
|
||||
|
||||
// Base structural classes matched exactly to your dark UI design
|
||||
const cardBorder = isActiveCard ? "border-[#0d9488]" : "border-[#142538]";
|
||||
|
||||
return (
|
||||
<div className={`relative rounded-2xl border ${cardBorder} bg-[#091624] p-5 flex flex-col justify-between min-h-[142px] w-full transition-all duration-300`}>
|
||||
|
||||
{/* --- TOP HEADER SECTION --- */}
|
||||
<div className="flex items-center justify-between w-full">
|
||||
{variant === "icon" && Icon ? (
|
||||
<div className={`flex h-9 w-9 items-center justify-center rounded-lg border ${iconContainerClassName}`}>
|
||||
<Icon size={18} />
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-xs font-bold uppercase tracking-wider text-slate-400/80">
|
||||
{title}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{badgeText && (
|
||||
<span className={`text-[10px] font-semibold tracking-wider ${badgeClassName}`}>
|
||||
{badgeText}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* --- BODY STATS SECTION --- */}
|
||||
<div className="mt-4 flex-grow flex flex-col justify-end">
|
||||
<div className="text-3xl font-extrabold text-white tracking-tight leading-none">
|
||||
{value}
|
||||
</div>
|
||||
|
||||
{/* Subtitle / Bottom Label text arrangement layout swap */}
|
||||
<p className="text-xs font-semibold text-slate-400/70 mt-2">
|
||||
{variant === "icon" ? title : subtitle}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* --- PROGRESS LAYER (Only renders if variant="progress") --- */}
|
||||
{variant === "progress" && progress !== undefined && (
|
||||
<div className="w-full bg-[#162a45] h-1.5 rounded-full mt-3 overflow-hidden">
|
||||
<div
|
||||
className={`h-1.5 rounded-full ${colorMap[color] || 'bg-teal-500'} transition-all duration-500`}
|
||||
style={{ width: `${progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
55
components/common/dataTable.tsx
Normal file
55
components/common/dataTable.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import React from "react";
|
||||
|
||||
type TableProps = {
|
||||
headers: string[];
|
||||
rows: React.ReactNode[][];
|
||||
};
|
||||
|
||||
export default function Table({ headers, rows }: TableProps) {
|
||||
return (
|
||||
<div className="w-full overflow-hidden rounded-2xl border border-slate-800 bg-[#0B1727] shadow-xl">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full border-collapse text-[12px]">
|
||||
<thead>
|
||||
<tr className="border-b border-slate-800 uppercase tracking-[0.12em] text-[10px] text-slate-500">
|
||||
{headers.map((header, index) => (
|
||||
<th
|
||||
key={index}
|
||||
className={`px-5 py-4 font-semibold whitespace-nowrap ${
|
||||
index === headers.length - 1
|
||||
? "text-center"
|
||||
: "text-left"
|
||||
}`}
|
||||
>
|
||||
{header}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody className="divide-y divide-slate-800/70">
|
||||
{rows.map((row, rowIndex) => (
|
||||
<tr
|
||||
key={rowIndex}
|
||||
className="transition-colors duration-200 hover:bg-slate-800/30"
|
||||
>
|
||||
{row.map((cell, cellIndex) => (
|
||||
<td
|
||||
key={cellIndex}
|
||||
className={`px-5 py-4 text-[12px] text-slate-300 whitespace-nowrap ${
|
||||
cellIndex === row.length - 1
|
||||
? "text-center"
|
||||
: "text-left"
|
||||
}`}
|
||||
>
|
||||
{cell}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
25
components/common/formInput.tsx
Normal file
25
components/common/formInput.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
import { FormProps } from "@/types/types";
|
||||
export default function FormInput({
|
||||
label,
|
||||
value,
|
||||
type = "text",
|
||||
placeholder="string"
|
||||
}: FormProps) {
|
||||
return (
|
||||
<div>
|
||||
<label className="mb-2 block text-sm text-slate-400">
|
||||
{label}
|
||||
</label>
|
||||
|
||||
<input
|
||||
type={type}
|
||||
defaultValue={value}
|
||||
placeholder={placeholder}
|
||||
|
||||
className="h-10 w-full rounded-xl border border-slate-700 text-white px-5"
|
||||
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
components/common/infoBanner.tsx
Normal file
22
components/common/infoBanner.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
|
||||
import {InfoBannerProps} from "@/types/types";
|
||||
|
||||
export default function InfoBanner({
|
||||
boldText,
|
||||
normalText,
|
||||
}: InfoBannerProps) {
|
||||
return (
|
||||
<div className="w-full
|
||||
rounded-xl border border-l-4 border-[#142538]
|
||||
border-l-[#10b981] bg-[#091624]
|
||||
p-4 flex items-center shadow-sm">
|
||||
|
||||
<p className="text-sm font-medium text-slate-300 tracking-wide leading-relaxed">
|
||||
{boldText && <strong className="text-white font-bold mr-1.5">{boldText}</strong>}
|
||||
{normalText}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
44
components/common/roundedFilters.tsx
Normal file
44
components/common/roundedFilters.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { FilterTabsProps } from "@/types/types";
|
||||
|
||||
export default function FilterTabs({
|
||||
options = [],
|
||||
activeFilter,
|
||||
onChange,
|
||||
className = "",
|
||||
}: FilterTabsProps) {
|
||||
if (!Array.isArray(options) || options.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`flex items-center gap-2.5 overflow-x-auto no-scrollbar py-1 ${className}`}>
|
||||
{options.map((filter) => {
|
||||
const isActive = activeFilter === filter.label;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={filter.label}
|
||||
type="button"
|
||||
onClick={() => onChange(filter.label)}
|
||||
className={`flex items-center gap-2 px-3.5 py-1.5 rounded-full text-xs font-medium transition-all whitespace-nowrap cursor-pointer ${
|
||||
isActive
|
||||
? "bg-teal-950/40 text-teal-400 border border-teal-500/50"
|
||||
: "bg-[#0b1727] text-slate-400 border border-slate-800/80 hover:text-slate-200"
|
||||
}`}
|
||||
>
|
||||
<span>{filter.label}</span>
|
||||
{filter.count !== undefined && (
|
||||
<span
|
||||
className={`font-mono text-[11px] ${
|
||||
isActive ? "text-teal-400 font-bold" : "text-slate-400"
|
||||
}`}
|
||||
>
|
||||
{filter.count}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
31
components/common/tabs.tsx
Normal file
31
components/common/tabs.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { NavigationTabsProps } from "@/types/types";
|
||||
|
||||
|
||||
|
||||
|
||||
export default function NavigationTabs({
|
||||
tabs,
|
||||
activeTab,
|
||||
onTabChange,
|
||||
className = '',
|
||||
}: NavigationTabsProps) {
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center gap-8 border-b border-slate-800 text-sm font-semibold overflow-x-auto no-scrollbar ${className}`}
|
||||
>
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab}
|
||||
onClick={() => onTabChange(tab)}
|
||||
className={`pb-3 relative transition-colors whitespace-nowrap ${
|
||||
activeTab === tab
|
||||
? 'text-teal-400 border-b-2 border-teal-400 font-bold'
|
||||
: 'text-slate-400 hover:text-slate-200'
|
||||
}`}
|
||||
>
|
||||
{tab}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user