working on onboarding flow

This commit is contained in:
dimitar committed 2025-12-13 06:26:23 +01:00
1 parent 868eaa5e3d
commit d3a36b6103
19 files changed
+2062 -393

No files matched your search

+68 -13
View File
@@ -16,13 +16,12 @@ function getTimeAgo(date: Date): string {
const diffHours = Math.floor(diffMins / 60);
const diffDays = Math.floor(diffHours / 24);
if (diffMins < 1) return 'just now';
if (diffMins < 1) return "just now";
if (diffMins < 60) return `${diffMins}m ago`;
if (diffHours < 24) return `${diffHours}h ago`;
return `${diffDays}d ago`;
}
interface User {
id: string;
email: string;
@@ -30,6 +29,7 @@ interface User {
lastName: string;
role: string;
phone?: string;
gymId?: string;
createdAt: Date;
isCheckedIn?: boolean;
checkInTime?: Date;
@@ -61,6 +61,31 @@ export function UserGrid({
}: UserGridProps) {
const [selectedUsers, setSelectedUsers] = useState<User[]>([]);
const [searchQuery, setSearchQuery] = useState<string>("");
const [gymNames, setGymNames] = useState<Record<string, string>>({});
React.useEffect(() => {
let isMounted = true;
(async () => {
try {
const res = await fetch("/api/gyms");
const data = await res.json();
if (isMounted && Array.isArray(data)) {
const map: Record<string, string> = {};
for (const g of data) {
if (g && g.id) {
map[g.id] = g.name || g.id;
}
}
setGymNames(map);
}
} catch (e) {
// silently fail; we'll show gymId if name not available
}
})();
return () => {
isMounted = false;
};
}, []);
const columnDefs: ColDef<User>[] = useMemo(
() => [
@@ -95,16 +120,34 @@ export function UserGrid({
roleColors[params.value as keyof typeof roleColors] ||
"bg-gray-100 text-gray-800";
const label = params.value === 'superAdmin' ? 'Super Admin' : params.value.charAt(0).toUpperCase() + params.value.slice(1);
const label =
params.value === "superAdmin"
? "Super Admin"
: params.value.charAt(0).toUpperCase() + params.value.slice(1);
return (
<span className={`px-2 py-1 rounded-full text-xs font-medium ${colorClass}`}>
<span
className={`px-2 py-1 rounded-full text-xs font-medium ${colorClass}`}
>
{label}
</span>
);
},
minWidth: 120,
},
{
headerName: "Gym",
field: "gymId",
filter: "agTextColumnFilter",
sortable: true,
minWidth: 160,
valueFormatter: (params: any) => {
const gymId = params.value;
if (!gymId) return "None";
return gymNames[gymId] || gymId;
},
},
{
headerName: "Phone",
field: "phone",
@@ -119,7 +162,8 @@ export function UserGrid({
filter: "agTextColumnFilter",
sortable: true,
cellRenderer: (params: any) => {
if (!params.value || params.value === "N/A") return <span className="text-gray-400">N/A</span>;
if (!params.value || params.value === "N/A")
return <span className="text-gray-400">N/A</span>;
const membershipColors = {
vip: "bg-yellow-100 text-yellow-800 border-yellow-200",
@@ -130,10 +174,13 @@ export function UserGrid({
membershipColors[params.value as keyof typeof membershipColors] ||
"bg-gray-100 text-gray-800";
const label = params.value.charAt(0).toUpperCase() + params.value.slice(1);
const label =
params.value.charAt(0).toUpperCase() + params.value.slice(1);
return (
<span className={`px-2 py-1 rounded-full text-xs font-medium border ${colorClass}`}>
<span
className={`px-2 py-1 rounded-full text-xs font-medium border ${colorClass}`}
>
{label}
</span>
);
@@ -146,7 +193,8 @@ export function UserGrid({
filter: "agTextColumnFilter",
sortable: true,
cellRenderer: (params: any) => {
if (!params.value || params.value === "N/A") return <span className="text-gray-400">N/A</span>;
if (!params.value || params.value === "N/A")
return <span className="text-gray-400">N/A</span>;
const statusColors = {
active: "bg-green-100 text-green-800",
@@ -158,10 +206,13 @@ export function UserGrid({
statusColors[params.value as keyof typeof statusColors] ||
"bg-gray-100 text-gray-800";
const label = params.value.charAt(0).toUpperCase() + params.value.slice(1);
const label =
params.value.charAt(0).toUpperCase() + params.value.slice(1);
return (
<span className={`px-2 py-1 rounded-full text-xs font-medium ${colorClass}`}>
<span
className={`px-2 py-1 rounded-full text-xs font-medium ${colorClass}`}
>
{label}
</span>
);
@@ -178,8 +229,10 @@ export function UserGrid({
return <span className="text-gray-400">—</span>;
}
const checkInTime = params.data.checkInTime ? new Date(params.data.checkInTime) : null;
const timeAgo = checkInTime ? getTimeAgo(checkInTime) : '';
const checkInTime = params.data.checkInTime
? new Date(params.data.checkInTime)
: null;
const timeAgo = checkInTime ? getTimeAgo(checkInTime) : "";
return (
<span className="px-2 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800 border border-green-200">
@@ -231,7 +284,9 @@ export function UserGrid({
rowSelection: "multiple" as const,
onSelectionChanged: () => {
const selectedNodes = gridRef.current?.api.getSelectedNodes();
const selectedData = selectedNodes?.map((node) => node.data).filter((u): u is User => !!u) || [];
const selectedData =
selectedNodes?.map((node) => node.data).filter((u): u is User => !!u) ||
[];
setSelectedUsers(selectedData);
if (selectedData.length === 1 && onUserSelect) {
onUserSelect(selectedData[0]);