feat: unified album creation and option to delete assets from potential albums
Signed-off-by: Varun Raj <varun@skcript.com>
This commit is contained in:
1 parent
098701b828
commit
72dcb928ef
9 files changed
+197
-185
No files matched your search
@@ -1,86 +0,0 @@
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../ui/dialog";
|
||||
import { Button } from "../ui/button";
|
||||
import { listAlbums } from "@/handlers/api/album.handler";
|
||||
import { IAlbum, IAlbumCreate } from "@/types/album";
|
||||
import { Input } from "../ui/input";
|
||||
import { Label } from "../ui/label";
|
||||
import { useToast } from "../ui/use-toast";
|
||||
|
||||
interface IProps {
|
||||
onCreated?: (album: IAlbum) => Promise<void>;
|
||||
onSubmit?: (data: IAlbumCreate) => Promise<any>;
|
||||
assetIds?: string[];
|
||||
}
|
||||
export default function AlbumCreateDialog({ onSubmit, assetIds }: IProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [formData, setFormData] = useState({
|
||||
albumName: "",
|
||||
});
|
||||
const { toast } = useToast();
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
if (!onSubmit) return;
|
||||
setLoading(true);
|
||||
onSubmit(formData)
|
||||
.then(() => {
|
||||
toast({
|
||||
title: "Album Created",
|
||||
description: "Album created successfully",
|
||||
});
|
||||
setFormData({ albumName: "" });
|
||||
setOpen(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to create album",
|
||||
variant: "destructive",
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button size={"sm"}>Create Album</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create Album</DialogTitle>
|
||||
<DialogDescription>
|
||||
Create and add photos to a new album
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<form onSubmit={handleSubmit} className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Album Name</Label>
|
||||
<Input
|
||||
placeholder="Album Name"
|
||||
onChange={(e) => {
|
||||
setFormData({ ...formData, albumName: e.target.value });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="self-end">
|
||||
<Button disabled={loading}>
|
||||
{loading ? "Creating Album" : "Create Album"}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -9,18 +9,28 @@ import {
|
||||
} from "../ui/dialog";
|
||||
import { Button } from "../ui/button";
|
||||
import { listAlbums } from "@/handlers/api/album.handler";
|
||||
import { IAlbum } from "@/types/album";
|
||||
import { IAlbum, IAlbumCreate } from "@/types/album";
|
||||
import { Input } from "../ui/input";
|
||||
import { usePotentialAlbumContext } from "@/contexts/PotentialAlbumContext";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
|
||||
import toast from "react-hot-toast";
|
||||
import { Label } from "../ui/label";
|
||||
|
||||
interface IProps {
|
||||
onSelected: (album: IAlbum) => Promise<void>;
|
||||
onCreated?: (album: IAlbum) => Promise<void>;
|
||||
onSubmit?: (data: IAlbumCreate) => Promise<any>;
|
||||
}
|
||||
export default function AlbumSelectorDialog({ onSelected }: IProps) {
|
||||
export default function AlbumSelectorDialog({ onSelected, onCreated, onSubmit }: IProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [albums, setAlbums] = useState<IAlbum[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
const [search, setSearch] = useState("");
|
||||
const { selectedIds, assets } = usePotentialAlbumContext();
|
||||
const [formData, setFormData] = useState({
|
||||
albumName: "",
|
||||
});
|
||||
|
||||
const fetchData = () => {
|
||||
return listAlbums()
|
||||
@@ -42,11 +52,32 @@ export default function AlbumSelectorDialog({ onSelected }: IProps) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const handleSubmit = useCallback((e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
console.log(formData);
|
||||
if (!onSubmit) return;
|
||||
setLoading(true);
|
||||
onSubmit(formData)
|
||||
.then(() => {
|
||||
toast.success("Album created successfully");
|
||||
setFormData({ albumName: "" });
|
||||
setOpen(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
toast.error("Failed to create album");
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, [onSubmit, formData]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (open && !albums.length) fetchData();
|
||||
}, [open]);
|
||||
|
||||
const renderContent = () => {
|
||||
const renderContent = useCallback(() => {
|
||||
if (loading) return <p>Loading...</p>;
|
||||
if (errorMessage) return <p>{errorMessage}</p>;
|
||||
return (
|
||||
@@ -73,12 +104,36 @@ export default function AlbumSelectorDialog({ onSelected }: IProps) {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}, [albums, filteredAlbums, handleSelect, loading, errorMessage]);
|
||||
|
||||
const renderCreateContent = useCallback(() => {
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="flex flex-col gap-4 max-h-[500px] min-h-[500px]">
|
||||
<p className="text-sm text-zinc-500 dark:text-zinc-400">
|
||||
Create a new album and add the selected assets to it
|
||||
</p>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Album Name</Label>
|
||||
<Input
|
||||
placeholder="Album Name"
|
||||
onChange={(e) => {
|
||||
setFormData({ ...formData, albumName: e.target.value });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="self-end">
|
||||
<Button disabled={loading} type="submit">
|
||||
{loading ? "Creating Album" : "Create Album"}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}, [loading, formData, handleSubmit]);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button size={"sm"}>Select Album</Button>
|
||||
<Button size={"sm"} disabled={!selectedIds.length}>Add to Album</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
@@ -87,7 +142,18 @@ export default function AlbumSelectorDialog({ onSelected }: IProps) {
|
||||
Select the albums you want to add the selected assets to
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{renderContent()}
|
||||
<Tabs defaultValue="albums" className="w-full">
|
||||
<TabsList className="w-full">
|
||||
<TabsTrigger className="w-full" value="albums">Albums</TabsTrigger>
|
||||
<TabsTrigger className="w-full" value="create">Create</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="albums">
|
||||
{renderContent()}
|
||||
</TabsContent>
|
||||
<TabsContent value="create">
|
||||
{renderCreateContent()}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { IAsset } from '@/types/asset';
|
||||
import { Dialog, DialogTitle, DialogHeader, DialogContent } from '@/components/ui/dialog';
|
||||
import { Dialog, DialogTitle, DialogHeader, DialogContent, DialogTrigger } from '@/components/ui/dialog';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -11,12 +11,12 @@ import { useToast } from '@/components/ui/use-toast';
|
||||
|
||||
interface IProps {
|
||||
assets: IAsset[];
|
||||
open: boolean;
|
||||
toggleOpen: (open: boolean) => void;
|
||||
onComplete: () => void;
|
||||
}
|
||||
|
||||
export default function AssetOffsetDialog({ assets: _assets, open, toggleOpen }: IProps) {
|
||||
export default function AssetOffsetDialog({ assets: _assets, onComplete }: IProps) {
|
||||
const { toast } = useToast();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [assets, setAssets] = useState<IAsset[]>(_assets);
|
||||
const [offsetData, setOffsetData] = useState<{ days: number, hours: number, minutes: number, seconds: number, years: number }>({
|
||||
days: 0,
|
||||
@@ -43,7 +43,7 @@ export default function AssetOffsetDialog({ assets: _assets, open, toggleOpen }:
|
||||
})
|
||||
.then(() => {
|
||||
setLoading(false);
|
||||
toggleOpen(false);
|
||||
setOpen(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
setLoading(false);
|
||||
@@ -52,7 +52,8 @@ export default function AssetOffsetDialog({ assets: _assets, open, toggleOpen }:
|
||||
});
|
||||
await Promise.all(promises);
|
||||
setLoading(false);
|
||||
toggleOpen(false);
|
||||
setOpen(false);
|
||||
onComplete();
|
||||
toast({
|
||||
title: 'Asset dates offset',
|
||||
description: 'Asset dates have been offset',
|
||||
@@ -65,12 +66,17 @@ export default function AssetOffsetDialog({ assets: _assets, open, toggleOpen }:
|
||||
}, [_assets]);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={toggleOpen}>
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant={"default"} size={"sm"} onClick={() => setOpen(true)}>
|
||||
Offset Dates
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className='max-w-[800px]'>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Offset Asset Dates</DialogTitle>
|
||||
</DialogHeader>
|
||||
<form onSubmit={handleSubmit} className='flex items-center gap-2'>
|
||||
<form onSubmit={handleSubmit} className='flex items-end gap-2'>
|
||||
<div>
|
||||
<Label>Years</Label>
|
||||
<Input type="number" value={offsetData.years} onChange={(e) => handleChange("years", parseInt(e.target.value))} />
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
|
||||
import { IAsset } from '@/types/asset';
|
||||
import { DotsVerticalIcon, HamburgerMenuIcon } from '@radix-ui/react-icons';
|
||||
import { Clock, PlusIcon } from 'lucide-react'
|
||||
import React, { useState } from 'react'
|
||||
import AssetOffsetDialog from './AssetOffsetDialog';
|
||||
|
||||
interface IProps {
|
||||
onAdd?: () => void;
|
||||
assets: IAsset[];
|
||||
}
|
||||
export default function AssetsOptions({ onAdd, assets }: IProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild className='cursor-pointer'>
|
||||
<HamburgerMenuIcon className="w-4 h-4" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem className='flex items-center gap-2' onSelect={() => setOpen(true)}>
|
||||
<Clock className="w-4 h-4" /> Offset Asset Dates
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
{open && <AssetOffsetDialog assets={assets} open={open} toggleOpen={setOpen} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,22 +1,15 @@
|
||||
import "yet-another-react-lightbox/styles.css";
|
||||
import { usePotentialAlbumContext } from "@/contexts/PotentialAlbumContext";
|
||||
import { listPotentialAlbumsAssets } from "@/handlers/api/album.handler";
|
||||
import { IAsset } from "@/types/asset";
|
||||
import React, { useEffect, useMemo, useState, MouseEvent } from "react";
|
||||
import { Gallery } from "react-grid-gallery";
|
||||
import Lightbox from "yet-another-react-lightbox";
|
||||
import Captions from "yet-another-react-lightbox/plugins/captions";
|
||||
import {
|
||||
ArrowUpRight,
|
||||
CalendarArrowDown,
|
||||
CalendarArrowUp,
|
||||
Hourglass,
|
||||
Link,
|
||||
} from "lucide-react";
|
||||
import { useMissingLocationContext } from "@/contexts/MissingLocationContext";
|
||||
import { listMissingLocationAssets } from "@/handlers/api/asset.handler";
|
||||
import { formatDate, parseDate } from "@/helpers/date.helper";
|
||||
import { addDays } from "date-fns";
|
||||
import { useConfig } from "@/contexts/ConfigContext";
|
||||
import LazyGridImage from "@/components/ui/lazy-grid-image";
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ interface FloatingBarProps {
|
||||
export default function FloatingBar({ children }: FloatingBarProps) {
|
||||
return (
|
||||
<div className='absolute bottom-10 left-0 right-0'>
|
||||
<div className='max-w-xl mx-auto py-2 px-2 rounded-lg flex justify-between items-center bg-white dark:bg-zinc-900 border-t border-zinc-200 dark:border-zinc-800 border'>
|
||||
<div className='max-w-2xl mx-auto py-2 px-2 rounded-lg flex justify-between items-center bg-white dark:bg-zinc-900 border-t border-zinc-200 dark:border-zinc-800 border'>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import AlbumCreateDialog from "@/components/albums/AlbumCreateDialog";
|
||||
import AlbumSelectorDialog from "@/components/albums/AlbumSelectorDialog";
|
||||
import PotentialAlbumsAssets from "@/components/albums/potential-albums/PotentialAlbumsAssets";
|
||||
import PotentialAlbumsDates from "@/components/albums/potential-albums/PotentialAlbumsDates";
|
||||
import AssetsOptions from "@/components/assets/assets-options/AssetsOptions";
|
||||
import AssetOffsetDialog from "@/components/assets/assets-options/AssetOffsetDialog";
|
||||
import PageLayout from "@/components/layouts/PageLayout";
|
||||
import FloatingBar from "@/components/shared/FloatingBar";
|
||||
import Header from "@/components/shared/Header";
|
||||
import { AlertDialog } from "@/components/ui/alert-dialog";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
@@ -13,6 +14,7 @@ import PotentialAlbumContext, {
|
||||
IPotentialAlbumConfig,
|
||||
} from "@/contexts/PotentialAlbumContext";
|
||||
import { addAssetToAlbum, createAlbum } from "@/handlers/api/album.handler";
|
||||
import { deleteAssets } from "@/handlers/api/asset.handler";
|
||||
import { IAlbum, IAlbumCreate } from "@/types/album";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useMemo } from "react";
|
||||
@@ -29,7 +31,7 @@ export default function PotentialAlbums() {
|
||||
assets: [],
|
||||
});
|
||||
|
||||
const selectedAssets = useMemo(() => config.assets.filter((a) => config.selectedIds.includes(a.id)), [config.assets, config.selectedIds]) ;
|
||||
const selectedAssets = useMemo(() => config.assets.filter((a) => config.selectedIds.includes(a.id)), [config.assets, config.selectedIds]);
|
||||
|
||||
const handleSelect = (album: IAlbum) => {
|
||||
return addAssetToAlbum(album.id, config.selectedIds)
|
||||
@@ -66,50 +68,46 @@ export default function PotentialAlbums() {
|
||||
role: "editor",
|
||||
userId: id,
|
||||
}]
|
||||
}).then(() => {
|
||||
toast({
|
||||
title: "Album created",
|
||||
description: "Album created successfully",
|
||||
});
|
||||
setConfig({ ...config, selectedIds: [], assets: config.assets.filter(a => !config.selectedIds.includes(a.id)) });
|
||||
})
|
||||
}
|
||||
|
||||
const handleDelete = () => {
|
||||
return deleteAssets(config.selectedIds)
|
||||
.then(() => {
|
||||
setConfig({ ...config, selectedIds: [], assets: config.assets.filter(a => !config.selectedIds.includes(a.id)) });
|
||||
})
|
||||
.then(() => {
|
||||
toast({
|
||||
title: "Assets deleted",
|
||||
description: "Assets deleted successfully",
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to delete assets",
|
||||
variant: "destructive",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const handleOffsetComplete = () => {
|
||||
setConfig({
|
||||
...config,
|
||||
selectedIds: [],
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<PageLayout className="!p-0 !mb-0">
|
||||
<PageLayout className="!p-0 !mb-0 relative">
|
||||
<Header
|
||||
leftComponent="Potential Albums"
|
||||
rightComponent={
|
||||
<div className="flex gap-2 items-center">
|
||||
<Badge variant={"outline"}>
|
||||
{config.selectedIds.length} Selected
|
||||
</Badge>
|
||||
{config.selectedIds.length && config.selectedIds.length === config.assets.length ? (
|
||||
<Button
|
||||
variant={"outline"}
|
||||
size={"sm"}
|
||||
onClick={() =>
|
||||
setConfig({
|
||||
...config,
|
||||
selectedIds: [],
|
||||
})
|
||||
}
|
||||
>
|
||||
Unselect all
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant={"outline"}
|
||||
size={"sm"}
|
||||
onClick={() =>
|
||||
setConfig({
|
||||
...config,
|
||||
selectedIds: config.assets.map((a) => a.id),
|
||||
})
|
||||
}
|
||||
>
|
||||
Select all
|
||||
</Button>
|
||||
)}
|
||||
<AlbumSelectorDialog onSelected={handleSelect} />
|
||||
<AlbumCreateDialog onSubmit={handleCreate} assetIds={config.assets.map((a) => a.id)} />
|
||||
<AssetsOptions assets={selectedAssets} onAdd={() => { }} />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<PotentialAlbumContext.Provider
|
||||
value={{
|
||||
@@ -122,6 +120,55 @@ export default function PotentialAlbums() {
|
||||
<PotentialAlbumsDates />
|
||||
<PotentialAlbumsAssets />
|
||||
</div>
|
||||
<FloatingBar>
|
||||
<div className="flex items-center gap-2 justify-between w-full">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{config.selectedIds.length} Selected
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
{config.selectedIds.length && config.selectedIds.length === config.assets.length ? (
|
||||
<Button
|
||||
variant={"outline"}
|
||||
size={"sm"}
|
||||
onClick={() =>
|
||||
setConfig({
|
||||
...config,
|
||||
selectedIds: [],
|
||||
})
|
||||
}
|
||||
>
|
||||
Unselect all
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant={"outline"}
|
||||
size={"sm"}
|
||||
onClick={() =>
|
||||
setConfig({
|
||||
...config,
|
||||
selectedIds: config.assets.map((a) => a.id),
|
||||
})
|
||||
}
|
||||
|
||||
>
|
||||
Select all
|
||||
</Button>
|
||||
)}
|
||||
<AlbumSelectorDialog onSelected={handleSelect} onSubmit={handleCreate}/>
|
||||
<AssetOffsetDialog assets={selectedAssets} onComplete={handleOffsetComplete} />
|
||||
<div className="h-[10px] w-[1px] bg-zinc-500 dark:bg-zinc-800"></div>
|
||||
|
||||
<AlertDialog
|
||||
title="Delete the selected assets?"
|
||||
description="This action will delete the selected assets and cannot be undone."
|
||||
onConfirm={handleDelete}>
|
||||
<Button variant={"destructive"} size={"sm"} >
|
||||
Delete
|
||||
</Button>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</div>
|
||||
</FloatingBar>
|
||||
</PotentialAlbumContext.Provider>
|
||||
</PageLayout>
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { CHART_COLORS } from "@/config/constants/chart.constant";
|
||||
import { db } from "@/config/db";
|
||||
import { getCurrentUser } from "@/handlers/serverUtils/user.utils";
|
||||
import { isFlipped } from "@/helpers/asset.helper";
|
||||
import { exif } from "@/schema";
|
||||
import { count, desc, isNotNull, ne, sql } from "drizzle-orm";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
@@ -23,7 +24,8 @@ const SELECT_ORPHAN_PHOTOS = (date: string, ownerId: string) =>
|
||||
a."deletedAt",
|
||||
e."exifImageWidth",
|
||||
e."exifImageHeight",
|
||||
e."dateTimeOriginal"
|
||||
e."dateTimeOriginal",
|
||||
e."orientation"
|
||||
FROM
|
||||
assets a
|
||||
LEFT JOIN
|
||||
@@ -47,7 +49,15 @@ export default async function handler(
|
||||
const currentUser = await getCurrentUser(req)
|
||||
const { startDate } = req.query as { startDate: string };
|
||||
const { rows } = await db.execute(SELECT_ORPHAN_PHOTOS(startDate, currentUser.id));
|
||||
return res.status(200).json(rows);
|
||||
|
||||
const cleanedRows = rows.map((row: any) => {
|
||||
return {
|
||||
...row,
|
||||
exifImageWidth: isFlipped(row.orientation || 0) ? row.exifImageHeight : row.exifImageWidth,
|
||||
exifImageHeight: isFlipped(row.orientation || 0) ? row.exifImageWidth : row.exifImageHeight,
|
||||
};
|
||||
});
|
||||
return res.status(200).json(cleanedRows);
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
error: error?.message,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import AssetsOptions from "@/components/assets/assets-options/AssetsOptions";
|
||||
import MissingLocationAssets from "@/components/assets/missing-location/MissingLocationAssets";
|
||||
import MissingLocationDates from "@/components/assets/missing-location/MissingLocationDates";
|
||||
import TagMissingLocationDialog from "@/components/assets/missing-location/TagMissingLocationDialog/TagMissingLocationDialog";
|
||||
@@ -18,6 +17,7 @@ import React, { useMemo } from "react";
|
||||
import FloatingBar from "@/components/shared/FloatingBar";
|
||||
import { Select, SelectValue, SelectTrigger, SelectContent, SelectItem } from "@/components/ui/select";
|
||||
import { AlertDialog } from "@/components/ui/alert-dialog";
|
||||
import AssetOffsetDialog from "@/components/assets/assets-options/AssetOffsetDialog";
|
||||
|
||||
export default function MissingLocations() {
|
||||
const { query, push } = useRouter();
|
||||
@@ -53,6 +53,13 @@ export default function MissingLocations() {
|
||||
})
|
||||
}
|
||||
|
||||
const handleOffsetComplete = () => {
|
||||
setConfig({
|
||||
...config,
|
||||
selectedIds: [],
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<PageLayout className="!p-0 !mb-0 relative">
|
||||
<Header
|
||||
@@ -78,7 +85,7 @@ export default function MissingLocations() {
|
||||
<SelectItem value="date">Date</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<AssetsOptions assets={selectedAssets} onAdd={() => { }} />
|
||||
<AssetOffsetDialog assets={selectedAssets} onComplete={handleOffsetComplete} />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
Reference in new issue
Block a user