feat: added date and asset count sorting for missing location

Signed-off-by: Varun Raj <varun@skcript.com>
This commit is contained in:
Varun Raj
2024-11-24 11:25:12 +05:30
parent e3909847b6
commit c28b221325
5 changed files with 76 additions and 11 deletions
@@ -1,7 +1,9 @@
import { usePotentialAlbumContext } from "@/contexts/PotentialAlbumContext";
import { IPotentialAlbumsDatesResponse } from "@/handlers/api/album.handler";
import { formatDate } from "@/helpers/date.helper";
import { parseDate } from "@/helpers/date.helper";
import { cn } from "@/lib/utils";
import React from "react";
import React, { useMemo } from "react";
interface IProps {
record: IPotentialAlbumsDatesResponse;
@@ -9,6 +11,9 @@ interface IProps {
}
export default function PotentialDateItem({ record, onSelect }: IProps) {
const { startDate } = usePotentialAlbumContext()
const dateLabel = useMemo(() => {
return formatDate(parseDate(record.date, "yyyy-MM-dd").toISOString(), "do MMM yyyy")
}, [record.date])
return (
<div
role="button"
@@ -19,8 +24,8 @@ export default function PotentialDateItem({ record, onSelect }: IProps) {
startDate === record.date ? "bg-zinc-100 dark:bg-zinc-800 border-gray-300 dark:border-zinc-700" : "")
}
>
<p className="font-mono text-sm">{record.date}</p>
<p className="text-xs">{record.asset_count} Orphan Assets</p>
<p className="font-mono text-sm">{dateLabel}</p>
<p className="text-xs text-foreground/50">{record.asset_count} Orphan Assets</p>
</div>
);
}
@@ -1,8 +1,9 @@
import { useMissingLocationContext } from "@/contexts/MissingLocationContext";
import { IMissingLocationDatesResponse } from "@/handlers/api/asset.handler";
import { parseDate, formatDate } from "@/helpers/date.helper";
import { cn } from "@/lib/utils";
import React from "react";
import React, { useMemo } from "react";
interface IProps {
record: IMissingLocationDatesResponse;
@@ -10,6 +11,10 @@ interface IProps {
}
export default function MissingLocationDateItem({ record, onSelect }: IProps) {
const { startDate } = useMissingLocationContext()
const dateLabel = useMemo(() => {
if (!record.date) return "Unknown"
return formatDate(parseDate(record.date, "yyyy-MM-dd").toISOString(), "do MMM yyyy")
}, [record.date])
return (
<div
role="button"
@@ -20,8 +25,8 @@ export default function MissingLocationDateItem({ record, onSelect }: IProps) {
startDate === record.date ? "bg-zinc-100 dark:bg-zinc-800 border-gray-300 dark:border-zinc-700" : "")
}
>
<p className="font-mono text-sm">{record.date}</p>
<p className="text-xs">{record.asset_count} W/O Location</p>
<p className="font-mono text-sm">{dateLabel}</p>
<p className="text-xs text-foreground/50">{record.asset_count} Orphan Assets</p>
</div>
);
}
@@ -4,6 +4,9 @@ import { IMissingLocationDatesResponse, listMissingLocationDates } from "@/handl
import MissingLocationDateItem from "./MissingLocationDateItem";
import { useMissingLocationContext } from "@/contexts/MissingLocationContext";
import { useRouter } from "next/router";
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Button } from "@/components/ui/button";
import { SortAsc, SortDesc } from "lucide-react";
export default function MissingLocationDates() {
const { updateContext } = useMissingLocationContext();
@@ -11,12 +14,16 @@ export default function MissingLocationDates() {
const [dateRecords, setDateRecords] = React.useState<
IMissingLocationDatesResponse[]
>([]);
const [filters, setFilters] = useState<{ sortBy: string, sortOrder: string }>({ sortBy: "date", sortOrder: "desc" });
const [loading, setLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const fetchData = async () => {
return listMissingLocationDates({})
return listMissingLocationDates({
sortBy: filters.sortBy,
sortOrder: filters.sortOrder,
})
.then(setDateRecords)
.catch(setErrorMessage)
.finally(() => setLoading(false));
@@ -30,12 +37,37 @@ export default function MissingLocationDates() {
});
};
useEffect(() => {
fetchData();
}, []);
}, [filters]);
return (
<div className="overflow-y-auto min-w-[170px] py-4 max-h-[calc(100vh-60px)] min-h-[calc(100vh-60px)] dark:bg-zinc-900 bg-gray-200 flex flex-col gap-2 px-2">
<div className="overflow-y-auto min-w-[200px] py-4 max-h-[calc(100vh-60px)] min-h-[calc(100vh-60px)] dark:bg-zinc-900 bg-gray-200 flex flex-col gap-2 px-2">
<div className="flex justify-between items-center gap-2">
<Select
defaultValue={filters.sortBy}
onValueChange={(value) => setFilters({ ...filters, sortBy: value })}
>
<SelectTrigger className="!p-2">
<SelectValue placeholder="Sort by" />
</SelectTrigger>
<SelectContent defaultValue={filters.sortBy}>
<SelectGroup title="Sort by">
<SelectLabel>Sort by</SelectLabel>
<SelectItem value="date">Date</SelectItem>
<SelectItem value="asset_count">Asset Count</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<div>
<Button variant="default" size="sm" onClick={() => setFilters({ ...filters, sortOrder: filters.sortOrder === "asc" ? "desc" : "asc" })}>
{filters.sortOrder === "asc" ? <SortAsc size={16} /> : <SortDesc size={16} />}
</Button>
</div>
</div>
{dateRecords.map((record) => (
<MissingLocationDateItem
key={record.date}
+2
View File
@@ -12,6 +12,8 @@ import { IAsset } from "@/types/asset";
interface IMissingAssetAlbumsFilters {
startDate?: string;
endDate?: string;
sortBy?: string;
sortOrder?: string;
}
export interface IMissingLocationDatesResponse {
date: string;
+23 -2
View File
@@ -1,7 +1,9 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import { CHART_COLORS } from "@/config/constants/chart.constant";
import { db } from "@/config/db";
import { IMissingLocationDatesResponse } from "@/handlers/api/asset.handler";
import { getCurrentUser } from "@/handlers/serverUtils/user.utils";
import { parseDate } from "@/helpers/date.helper";
import { assets, exif } from "@/schema";
import { and, count, desc, eq, isNotNull, isNull, ne, sql } from "drizzle-orm";
import type { NextApiRequest, NextApiResponse } from "next";
@@ -11,7 +13,13 @@ export default async function handler(
res: NextApiResponse
) {
try {
const { sortBy = "date", sortOrder = "desc" } = req.query;
const currentUser = await getCurrentUser(req);
if (!currentUser) {
return res.status(401).json({
error: "Unauthorized",
});
}
const rows = await db
.select({
asset_count: desc(count(assets.id)),
@@ -22,12 +30,25 @@ export default async function handler(
.where(and(
isNull(exif.latitude),
isNotNull(assets.createdAt),
isNotNull(exif.dateTimeOriginal),
eq(assets.type, "VIDEO"),
eq(assets.ownerId, currentUser.id),
eq(assets.ownerId, currentUser.id),
eq(assets.isVisible, true),
))
.groupBy(sql`DATE(${exif.dateTimeOriginal})`)
.orderBy(desc(count(assets.id)));
.orderBy(desc(count(assets.id))) as IMissingLocationDatesResponse[];
if (sortBy === "date") {
rows.sort((a, b) => {
const aDate = parseDate(a.date, "yyyy-MM-dd");
const bDate = parseDate(b.date, "yyyy-MM-dd");
return sortOrder === "asc" ? aDate.getTime() - bDate.getTime() : bDate.getTime() - aDate.getTime();
});
} else if (sortBy === "asset_count") {
rows.sort((a, b) => {
return sortOrder === "asc" ? a.asset_count - b.asset_count : b.asset_count - a.asset_count;
});
}
return res.status(200).json(rows);
} catch (error: any) {
res.status(500).json({