Skip to content

Commit

Permalink
Merge pull request #65 from varun-raj/feat-nov-release
Browse files Browse the repository at this point in the history
Feat: November 2024 release
  • Loading branch information
varun-raj authored Nov 23, 2024
2 parents 9488f8d + 34ff728 commit 2abad9a
Show file tree
Hide file tree
Showing 17 changed files with 285 additions and 41 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: ci
on:
push:
branches:
- main
- release

jobs:
release:
Expand Down
38 changes: 34 additions & 4 deletions src/components/albums/potential-albums/PotentialAlbumsDates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,27 @@ import React, { use, useEffect, useState } from "react";
import PotentialDateItem from "./PotentialDateItem";
import { usePotentialAlbumContext } from "@/contexts/PotentialAlbumContext";
import { useRouter } from "next/router";
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Button } from "@/components/ui/button";
import { ArrowDown, ArrowUp, ArrowUpDownIcon, SortAsc, SortDesc } from "lucide-react";

export default function PotentialAlbumsDates() {
const router = useRouter();
const { updateContext } = usePotentialAlbumContext();
const [dateRecords, setDateRecords] = React.useState<
IPotentialAlbumsDatesResponse[]
>([]);
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 listPotentialAlbumsDates({})
return listPotentialAlbumsDates({
sortBy: filters.sortBy,
sortOrder: filters.sortOrder,
})
.then(setDateRecords)
.catch(setErrorMessage)
.finally(() => setLoading(false));
Expand All @@ -31,15 +38,38 @@ export default function PotentialAlbumsDates() {
pathname: router.pathname,
query: { startDate: date },
})

};


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) => (
<PotentialDateItem
key={record.date}
Expand Down
128 changes: 128 additions & 0 deletions src/components/assets/assets-options/AssetOffsetDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import React, { useEffect, useState } from 'react'
import { IAsset } from '@/types/asset';
import { Dialog, DialogTitle, DialogHeader, DialogContent } from '@/components/ui/dialog';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { formatDate, offsetDate } from '@/helpers/date.helper';
import LazyImage from '@/components/ui/lazy-image';
import Image from 'next/image';
import { updateAssets } from '@/handlers/api/asset.handler';
import { useToast } from '@/components/ui/use-toast';

interface IProps {
assets: IAsset[];
open: boolean;
toggleOpen: (open: boolean) => void;
}

export default function AssetOffsetDialog({ assets: _assets, open, toggleOpen }: IProps) {
const { toast } = useToast();
const [assets, setAssets] = useState<IAsset[]>(_assets);
const [offsetData, setOffsetData] = useState<{ days: number, hours: number, minutes: number, seconds: number, years: number }>({
days: 0,
hours: 0,
minutes: 0,
seconds: 0,
years: 0
});
const [loading, setLoading] = useState(false);
const [assetStatus, setAssetStatus] = useState<Record<string, string>>({});

const handleChange = (key: keyof typeof offsetData, value: number) => {
setOffsetData({ ...offsetData, [key]: value });
}

const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log(offsetData);
setLoading(true);
const promises = assets.map(async (asset) => {
setAssetStatus({ [asset.id]: 'pending' });
await updateAssets({
ids: [asset.id],
dateTimeOriginal: offsetDate(asset.dateTimeOriginal, offsetData)
})
.then(() => {
setLoading(false);
toggleOpen(false);
})
.catch((error) => {
setLoading(false);
setAssetStatus({ [asset.id]: 'error' });
})
});
await Promise.all(promises);
setLoading(false);
toggleOpen(false);
toast({
title: 'Asset dates offset',
description: 'Asset dates have been offset',
})
}


useEffect(() => {
setAssets(_assets);
}, [_assets]);

return (
<Dialog open={open} onOpenChange={toggleOpen}>
<DialogContent className='max-w-[800px]'>
<DialogHeader>
<DialogTitle>Offset Asset Dates</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit} className='flex items-center gap-2'>
<div>
<Label>Years</Label>
<Input type="number" value={offsetData.years} onChange={(e) => handleChange("years", parseInt(e.target.value))} />
</div>
<div>
<Label>Days</Label>
<Input type="number" value={offsetData.days} onChange={(e) => handleChange("days", parseInt(e.target.value))} />
</div>
<div>
<Label>Hours</Label>
<Input type="number" value={offsetData.hours} onChange={(e) => handleChange("hours", parseInt(e.target.value))} />
</div>
<div>
<Label>Minutes</Label>
<Input type="number" value={offsetData.minutes} onChange={(e) => handleChange("minutes", parseInt(e.target.value))} />
</div>
<div>
<Label>Seconds</Label>
<Input type="number" value={offsetData.seconds} onChange={(e) => handleChange("seconds", parseInt(e.target.value))} />
</div>
<Button disabled={loading} type="submit">Offset Dates</Button>
</form>
<div className='grid grid-cols-3 gap-4 py-2 overflow-y-auto max-h-[500px]'>
{assets.map((asset) => (
<div key={asset.id} >
<div className='relative w-full h-full rounded-md border overflow-hidden' >
{assetStatus[asset.id] === 'pending' ? <div className='absolute text-xs text-center bottom-0 left-0 right-0 bg-blue-500/50 text-white p-2'>
<p className='text-white'>Offsetting...</p>
</div> : (
<div className='absolute text-xs text-center bottom-0 left-0 right-0 bg-green-500/50 text-white p-2'>{formatDate(offsetDate(asset.dateTimeOriginal, offsetData), 'PPpp')} </div>
)}
<Image
src={asset.previewUrl}
alt={asset.originalFileName}
width={300}
height={300}
objectPosition='cover'
style={{
objectFit: 'cover',
overflow: 'hidden',
height: '100%',
width: '100%'
}}
/>
<div className='absolute text-xs text-center top-0 left-0 right-0 bg-red-500/50 text-white p-2'>{formatDate(asset.dateTimeOriginal, 'PPpp')}</div>
</div>
</div>
))}
</div>
</DialogContent>
</Dialog>
)
}
31 changes: 31 additions & 0 deletions src/components/assets/assets-options/AssetsOptions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
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} />}
</>
)
}
2 changes: 1 addition & 1 deletion src/components/people/PeopleFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function PeopleFilters() {
query: {
...router.query,
...data,
page: undefined,
page: data.page || undefined,
},
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/people/PeopleList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export default function PeopleList() {
if (errorMessage) return <div>{errorMessage}</div>;

return (
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-1 p-2">
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-1 p-2">
{people.map((person) => (
<PersonItem person={person} key={person.id} onRemove={handleRemove}/>
))}
Expand Down
40 changes: 20 additions & 20 deletions src/components/people/PersonMergeDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,47 +103,47 @@ export function PersonMergeDropdown({
setSelectedPeople(selectedPeople.filter((p) => p.id !== value.id));
return;
}
if (selectedPeople.length >= 5) {
toast({
title: "Error",
description: "You can only merge 5 people at a time",
});
return;
} else {
setSelectedPeople([...selectedPeople, value]);
}
setSelectedPeople([...selectedPeople, value]);
if (primaryPerson.name.length === 0 && value.name.length > 0) {
setPrimaryPerson(value);
}

};



const handleMerge = () => {
if (selectedPeople.length === 0) {
return;
}

const personIds = selectedPeople.map((p) => p.id);
setMerging(true);
return mergePerson(person.id, personIds)
.then(() => {
onRemove?.(person);
})
.then(() => {

const mergeInBatches = async (ids: string[], index: number = 0) => {
if (index >= ids.length) {
setOpen(false);
toast({
title: "Success",
description: "People merged successfully",
});
})
.catch(() => {
setMerging(false);
return;
}

const batch = ids.slice(index, index + 5);
try {
await mergePerson(person.id, batch);
mergeInBatches(ids, index + 5);
} catch {
toast({
title: "Error",
description: "Failed to merge people",
});
})
.finally(() => {
setMerging(false);
});
}
};

mergeInBatches(personIds);
};

const handleRemove = (value: IPerson) => {
Expand Down
2 changes: 2 additions & 0 deletions src/handlers/api/album.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { IAsset } from "@/types/asset";
interface IPotentialAlbumsDatesFilters {
startDate?: string;
endDate?: string;
sortBy?: string;
sortOrder?: string;
}
export interface IPotentialAlbumsDatesResponse {
date: string;
Expand Down
1 change: 1 addition & 0 deletions src/handlers/api/asset.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export interface IUpdateAssetsParams {
ids: string[];
latitude?: number;
longitude?: number;
dateTimeOriginal?: string;
}

export const updateAssets = async (params: IUpdateAssetsParams) => {
Expand Down
20 changes: 19 additions & 1 deletion src/helpers/date.helper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { format, parse } from "date-fns"
import { addSeconds, addHours, addMinutes, format, parse, addYears } from "date-fns"

export const formatDate = (date: string, outputFormat?: string): string => {
return format(date, outputFormat || "PPP")
Expand All @@ -13,3 +13,21 @@ export const addDays = (date: Date, days: number): Date => {
result.setDate(result.getDate() + days);
return result;
}


export const offsetDate = (date: string, offset: {
years: number,
days: number,
hours: number,
minutes: number,
seconds: number
}): string => {
console.log(offset)
const parsedDate = new Date(date);
const result = addYears(parsedDate, offset.years || 0)
const result2 = addDays(result, offset.days || 0)
const result3 = addHours(result2, offset.hours || 0)
const result4 = addMinutes(result3, offset.minutes || 0)
const result5 = addSeconds(result4, offset.seconds || 0)
return result5.toISOString()
}
Loading

0 comments on commit 2abad9a

Please sign in to comment.