Styling Fixes
This commit is contained in:
parent
4774f15f3f
commit
cb46826770
@ -1,6 +1,11 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
|
||||||
|
|
||||||
|
interface CountResponse {
|
||||||
|
lastUpdateTime: string;
|
||||||
|
counts: PackageStats;
|
||||||
|
}
|
||||||
|
|
||||||
interface PackageStats {
|
interface PackageStats {
|
||||||
stale: number;
|
stale: number;
|
||||||
missing: number;
|
missing: number;
|
||||||
@ -18,16 +23,21 @@ const Home: React.FC = () => {
|
|||||||
const fetchStats = async () => {
|
const fetchStats = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/counts');
|
const response = await fetch('/api/counts');
|
||||||
const data: PackageStats = await response.json();
|
const data: CountResponse = await response.json();
|
||||||
setStats(data);
|
const counts = data.counts;
|
||||||
setLastUpdated(new Date().toLocaleString('en-GB', {
|
setStats(counts);
|
||||||
|
const dt = new Date(data.lastUpdateTime);
|
||||||
|
const dateLocale = dt.toLocaleDateString(navigator.language, {
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
month: '2-digit',
|
month: '2-digit',
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
|
});
|
||||||
|
const timeLocale = dt.toLocaleTimeString(navigator.language, {
|
||||||
hour: '2-digit',
|
hour: '2-digit',
|
||||||
minute: '2-digit',
|
minute: '2-digit'
|
||||||
second: '2-digit'
|
});
|
||||||
}));
|
|
||||||
|
setLastUpdated(`${timeLocale} on ${dateLocale}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch stats:', error);
|
console.error('Failed to fetch stats:', error);
|
||||||
}
|
}
|
||||||
|
@ -38,17 +38,8 @@ interface Package {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CountsData {
|
|
||||||
stale: number;
|
|
||||||
missing: number;
|
|
||||||
built: number;
|
|
||||||
error: number;
|
|
||||||
queued: number;
|
|
||||||
building: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Packages: React.FC = () => {
|
const Packages: React.FC = () => {
|
||||||
const { getParam } = useSearchParams();
|
const { getParam, setParam } = useSearchParams();
|
||||||
const [packages, setPackages] = useState<Package[]>([]);
|
const [packages, setPackages] = useState<Package[]>([]);
|
||||||
const [totalCount, setTotalCount] = useState(0);
|
const [totalCount, setTotalCount] = useState(0);
|
||||||
const [currentPage, setCurrentPage] = useState(
|
const [currentPage, setCurrentPage] = useState(
|
||||||
@ -65,21 +56,12 @@ const Packages: React.FC = () => {
|
|||||||
}, [isMobile]);
|
}, [isMobile]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchTotalCount();
|
|
||||||
fetchPackages();
|
fetchPackages();
|
||||||
|
setParam("page", currentPage.toString());
|
||||||
|
setParam("search", search);
|
||||||
|
setParam("filter", filter);
|
||||||
}, [currentPage, search, filter, pageSize]);
|
}, [currentPage, search, filter, pageSize]);
|
||||||
|
|
||||||
const fetchTotalCount = async () => {
|
|
||||||
try {
|
|
||||||
const response = await fetch("/api/counts");
|
|
||||||
const data: CountsData = await response.json();
|
|
||||||
const total = Object.values(data).reduce((acc, curr) => acc + curr, 0);
|
|
||||||
setTotalCount(total);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching total count:", error);
|
|
||||||
setTotalCount(0); // Set to 0 if there's an error
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchPackages = async () => {
|
const fetchPackages = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@ -89,8 +71,10 @@ const Packages: React.FC = () => {
|
|||||||
`/api/packages?page=${currentPage}&pageSize=${pageSize}&search=${search}&filter=${filterParam}`
|
`/api/packages?page=${currentPage}&pageSize=${pageSize}&search=${search}&filter=${filterParam}`
|
||||||
);
|
);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
setPackages(Object.values(data));
|
setTotalCount(data.total);
|
||||||
|
setPackages(Object.values(data.packages));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
setTotalCount(0);
|
||||||
console.error("Error fetching packages:", error);
|
console.error("Error fetching packages:", error);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@ -98,13 +82,24 @@ const Packages: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleFilterChange = (value: string) => {
|
const handleFilterChange = (value: string) => {
|
||||||
setFilter(value === "All" ? "" : value);
|
const newFilter = value === "All" ? "" : value;
|
||||||
|
setFilter(newFilter);
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
|
setParam("filter", newFilter);
|
||||||
|
setParam("page", "1");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setSearch(e.target.value);
|
const newSearch = e.target.value;
|
||||||
|
setSearch(newSearch);
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
|
setParam("search", newSearch);
|
||||||
|
setParam("page", "1");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePageChange = (page: number) => {
|
||||||
|
setCurrentPage(page);
|
||||||
|
setParam("page", page.toString());
|
||||||
};
|
};
|
||||||
|
|
||||||
const totalPages = Math.ceil(totalCount / pageSize);
|
const totalPages = Math.ceil(totalCount / pageSize);
|
||||||
@ -145,154 +140,151 @@ const Packages: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Table */}
|
{/* Table */}
|
||||||
<div className="flex-grow overflow-hidden">
|
<div className="flex-grow overflow-hidden">
|
||||||
<div className="container mx-0 px-0 h-full">
|
<div className="container mx-0 px-0 h-full">
|
||||||
<div className="h-full">
|
<div className="h-full">
|
||||||
<Table className="">
|
<Table className="w-full">
|
||||||
<TableHeader className="top-0 bg-background z-10 hidden md:table-header-group">
|
<TableHeader className="top-0 bg-background z-10 hidden md:table-header-group">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead className="w-1/2 text-left">Name</TableHead>
|
<TableHead className="w-1/2 text-left">Name</TableHead>
|
||||||
<TableHead className="w-1/4 text-left">Version</TableHead>
|
<TableHead className="w-1/4 text-left">Version</TableHead>
|
||||||
<TableHead className="w-1/4 text-left">Status</TableHead>
|
<TableHead className="w-1/4 text-left">Status</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{loading ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={3} className="text-center">
|
||||||
|
Loading...
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : (
|
||||||
|
packages.map((pkg) => {
|
||||||
|
const firstPackage = Object.values(pkg.Packages)[0];
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
key={pkg.Name}
|
||||||
|
className="md:table-row border-b last:border-b-0"
|
||||||
|
>
|
||||||
|
<TableCell className="w-full md:w-1/2 py-2 md:py-4">
|
||||||
|
<div className="flex flex-col h-full md:h-auto">
|
||||||
|
<div className="flex justify-between items-start mb-2 md:mb-0">
|
||||||
|
<span className="font-medium truncate mr-2">{pkg.Name}</span>
|
||||||
|
<span className="text-sm md:hidden truncate">{firstPackage.Status}</span>
|
||||||
|
</div>
|
||||||
|
<div className="md:hidden text-sm text-muted-foreground text-center">
|
||||||
|
<span className="truncate">{firstPackage.Version}</span>
|
||||||
|
{firstPackage.NewVersion && (
|
||||||
|
<span className="truncate block">→ {firstPackage.NewVersion}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="hidden md:table-cell md:w-1/4 py-4 text-left">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="truncate">{firstPackage.Version}</span>
|
||||||
|
{firstPackage.NewVersion && (
|
||||||
|
<span className="text-sm text-muted-foreground truncate">
|
||||||
|
→ {firstPackage.NewVersion}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="hidden md:table-cell md:w-1/4 py-2 md:py-4 text-left">
|
||||||
|
<span className="truncate">{firstPackage.Status}</span>
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
);
|
||||||
<TableBody>
|
})
|
||||||
{loading ? (
|
)}
|
||||||
<TableRow>
|
</TableBody>
|
||||||
<TableCell colSpan={3} className="text-center">
|
</Table>
|
||||||
Loading...
|
</div>
|
||||||
</TableCell>
|
</div>
|
||||||
</TableRow>
|
</div>
|
||||||
) : (
|
|
||||||
packages.map((pkg) => {
|
|
||||||
const firstPackage = Object.values(pkg.Packages)[0];
|
|
||||||
return (
|
|
||||||
<TableRow
|
|
||||||
key={pkg.Name}
|
|
||||||
className="flex justify-between flex-row md:table-row"
|
|
||||||
>
|
|
||||||
<TableCell className="w-full md:w-1/2 py-2 md:py-4">
|
|
||||||
<div className="flex flex-col justify-center">
|
|
||||||
<div className="font-medium truncate md:text-left flex flex-row justify-between">
|
|
||||||
<div className="flex w-1/2">{pkg.Name}</div>
|
|
||||||
<div className="md:hidden flex w/2">
|
|
||||||
{firstPackage.Status}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="md:hidden text-sm text-muted-foreground truncate">
|
|
||||||
{firstPackage.Version}
|
|
||||||
{firstPackage.NewVersion && (
|
|
||||||
<> → {firstPackage.NewVersion}</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="hidden md:table-cell md:w-1/4 py-4 md:text-left">
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<div className="text-foreground truncate">
|
|
||||||
{firstPackage.Version}
|
|
||||||
</div>
|
|
||||||
{firstPackage.NewVersion && (
|
|
||||||
<span className="text-sm text-muted-foreground truncate">
|
|
||||||
→ {firstPackage.NewVersion}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="hidden md:block md:w-1/4 py-2 md:py-4 text-right md:text-left">
|
|
||||||
{firstPackage.Status}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Sticky Footer with Pagination */}
|
{/* Sticky Footer with Pagination */}
|
||||||
<div className="sticky bottom-0 w-full bg-background border-t border-border py-4">
|
<div className="sticky bottom-0 w-full bg-background border-t border-border py-4">
|
||||||
<div className="container mx-auto">
|
<div className="container mx-auto px-2">
|
||||||
<Pagination>
|
<Pagination>
|
||||||
<PaginationContent>
|
<PaginationContent className="flex flex-wrap justify-center items-center gap-1">
|
||||||
<PaginationItem>
|
<PaginationItem>
|
||||||
<PaginationPrevious
|
<PaginationPrevious
|
||||||
size="default"
|
size="default"
|
||||||
onClick={() =>
|
onClick={() => handlePageChange(Math.max(currentPage - 1, 1))}
|
||||||
setCurrentPage((prev) => Math.max(prev - 1, 1))
|
/>
|
||||||
}
|
</PaginationItem>
|
||||||
/>
|
|
||||||
</PaginationItem>
|
|
||||||
|
|
||||||
{currentPage > 1 && (
|
{currentPage > 2 && (
|
||||||
<PaginationItem>
|
<PaginationItem className="hidden sm:inline-block">
|
||||||
<PaginationLink
|
<PaginationLink size="default" onClick={() => handlePageChange(1)}>
|
||||||
size="default"
|
1
|
||||||
onClick={() => setCurrentPage(1)}
|
</PaginationLink>
|
||||||
>
|
</PaginationItem>
|
||||||
1
|
)}
|
||||||
</PaginationLink>
|
|
||||||
</PaginationItem>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentPage > 3 && <PaginationEllipsis />}
|
{currentPage > 3 && (
|
||||||
|
<PaginationItem className="hidden sm:inline-flex items-center">
|
||||||
|
<PaginationEllipsis />
|
||||||
|
</PaginationItem>
|
||||||
|
)}
|
||||||
|
|
||||||
{currentPage > 2 && (
|
{currentPage > 1 && (
|
||||||
<PaginationItem>
|
<PaginationItem>
|
||||||
<PaginationLink
|
<PaginationLink
|
||||||
size="default"
|
size="default"
|
||||||
onClick={() => setCurrentPage(currentPage - 1)}
|
onClick={() => handlePageChange(currentPage - 1)}
|
||||||
>
|
>
|
||||||
{currentPage - 1}
|
{currentPage - 1}
|
||||||
</PaginationLink>
|
</PaginationLink>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<PaginationItem>
|
<PaginationItem>
|
||||||
<PaginationLink size="default" isActive>
|
<PaginationLink size="default" isActive>
|
||||||
{currentPage}
|
{currentPage}
|
||||||
</PaginationLink>
|
</PaginationLink>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
|
|
||||||
{currentPage < totalPages - 1 && (
|
{currentPage < totalPages && (
|
||||||
<PaginationItem>
|
<PaginationItem>
|
||||||
<PaginationLink
|
<PaginationLink
|
||||||
size="default"
|
size="default"
|
||||||
onClick={() => setCurrentPage(currentPage + 1)}
|
onClick={() => handlePageChange(currentPage + 1)}
|
||||||
>
|
>
|
||||||
{currentPage + 1}
|
{currentPage + 1}
|
||||||
</PaginationLink>
|
</PaginationLink>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{currentPage < totalPages - 2 && <PaginationEllipsis />}
|
{currentPage < totalPages - 2 && (
|
||||||
|
<PaginationItem className="hidden sm:inline-flex items-center">
|
||||||
|
<PaginationEllipsis />
|
||||||
|
</PaginationItem>
|
||||||
|
)}
|
||||||
|
|
||||||
{currentPage < totalPages && (
|
{currentPage < totalPages - 1 && (
|
||||||
<PaginationItem>
|
<PaginationItem className="hidden sm:inline-block">
|
||||||
<PaginationLink
|
<PaginationLink
|
||||||
size="default"
|
size="default"
|
||||||
onClick={() => setCurrentPage(totalPages)}
|
onClick={() => handlePageChange(totalPages)}
|
||||||
>
|
>
|
||||||
{totalPages}
|
{totalPages}
|
||||||
</PaginationLink>
|
</PaginationLink>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<PaginationItem>
|
<PaginationItem>
|
||||||
<PaginationNext
|
<PaginationNext
|
||||||
size="default"
|
size="default"
|
||||||
onClick={() =>
|
onClick={() => handlePageChange(Math.min(currentPage + 1, totalPages))}
|
||||||
setCurrentPage((prev) => Math.min(prev + 1, totalPages))
|
/>
|
||||||
}
|
</PaginationItem>
|
||||||
/>
|
</PaginationContent>
|
||||||
</PaginationItem>
|
</Pagination>
|
||||||
</PaginationContent>
|
</div>
|
||||||
</Pagination>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user