feat: Add date processed filter to Identify component and API

This commit introduces a new date processed filter in the Identify component, allowing users to filter faces based on the date they were processed. The API has been updated to support this new parameter, ensuring seamless integration with the frontend. Additionally, the date filters for date taken have been renamed for clarity. Documentation has been updated to reflect these changes.
This commit is contained in:
tanyar09 2025-11-11 13:05:19 -05:00
parent 17aeb5b823
commit 85dd6a68b3
4 changed files with 46 additions and 18 deletions

View File

@ -152,6 +152,7 @@ export const facesApi = {
date_to?: string
date_taken_from?: string
date_taken_to?: string
date_processed?: string
date_processed_from?: string
date_processed_to?: string
sort_by?: 'quality' | 'date_taken' | 'date_added'

View File

@ -18,6 +18,7 @@ export default function Identify() {
const [sortDir, setSortDir] = useState<SortDir>('desc')
const [dateFrom, setDateFrom] = useState<string>('')
const [dateTo, setDateTo] = useState<string>('')
const [dateProcessed, setDateProcessed] = useState<string>('')
const [currentIdx, setCurrentIdx] = useState(0)
const currentFace = faces[currentIdx]
@ -74,8 +75,9 @@ export default function Identify() {
page: 1,
page_size: pageSize,
min_quality: minQuality,
date_from: dateFrom || undefined,
date_to: dateTo || undefined,
date_taken_from: dateFrom || undefined,
date_taken_to: dateTo || undefined,
date_processed: dateProcessed || undefined,
sort_by: sortBy,
sort_dir: sortDir,
tag_names: selectedTags.length > 0 ? selectedTags.join(', ') : undefined,
@ -248,6 +250,7 @@ export default function Identify() {
if (settings.sortDir !== undefined) setSortDir(settings.sortDir)
if (settings.dateFrom !== undefined) setDateFrom(settings.dateFrom)
if (settings.dateTo !== undefined) setDateTo(settings.dateTo)
if (settings.dateProcessed !== undefined) setDateProcessed(settings.dateProcessed)
if (settings.uniqueFacesOnly !== undefined) setUniqueFacesOnly(settings.uniqueFacesOnly)
if (settings.compareEnabled !== undefined) setCompareEnabled(settings.compareEnabled)
if (settings.selectedTags !== undefined) setSelectedTags(settings.selectedTags)
@ -271,6 +274,7 @@ export default function Identify() {
sortDir,
dateFrom,
dateTo,
dateProcessed,
uniqueFacesOnly,
compareEnabled,
selectedTags,
@ -279,7 +283,7 @@ export default function Identify() {
} catch (error) {
console.error('Error saving settings to localStorage:', error)
}
}, [pageSize, minQuality, sortBy, sortDir, dateFrom, dateTo, uniqueFacesOnly, compareEnabled, selectedTags, settingsLoaded])
}, [pageSize, minQuality, sortBy, sortDir, dateFrom, dateTo, dateProcessed, uniqueFacesOnly, compareEnabled, selectedTags, settingsLoaded])
// Initial load on mount (after settings are loaded)
useEffect(() => {
@ -521,18 +525,12 @@ export default function Identify() {
<div className="p-4">
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-sm font-medium text-gray-700">Min Quality</label>
<input type="range" min={0} max={1} step={0.05} value={minQuality}
onChange={(e) => setMinQuality(parseFloat(e.target.value))} className="w-full" />
<div className="text-xs text-gray-500">{(minQuality * 100).toFixed(0)}%</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">Date From</label>
<label className="block text-sm font-medium text-gray-700">Date Taken From</label>
<input type="date" value={dateFrom} onChange={(e) => setDateFrom(e.target.value)}
className="mt-1 block w-full border rounded px-2 py-1" />
</div>
<div>
<label className="block text-sm font-medium text-gray-700">Date To</label>
<label className="block text-sm font-medium text-gray-700">Date Taken To</label>
<input type="date" value={dateTo} onChange={(e) => setDateTo(e.target.value)}
className="mt-1 block w-full border rounded px-2 py-1" />
</div>
@ -553,6 +551,17 @@ export default function Identify() {
<option value="asc">Asc</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">Date Processed On</label>
<input type="date" value={dateProcessed} onChange={(e) => setDateProcessed(e.target.value)}
className="mt-1 block w-full border rounded px-2 py-1" />
</div>
<div>
<label className="block text-sm font-medium text-gray-700">Min Quality</label>
<input type="range" min={0} max={1} step={0.05} value={minQuality}
onChange={(e) => setMinQuality(parseFloat(e.target.value))} className="w-full" />
<div className="text-xs text-gray-500">{(minQuality * 100).toFixed(0)}%</div>
</div>
</div>
<div className="mt-4 pt-3 border-t">
<div className="flex items-center gap-2 mb-2">

View File

@ -100,10 +100,11 @@ def process_faces(request: ProcessFacesRequest) -> ProcessFacesResponse:
@router.get("/unidentified", response_model=UnidentifiedFacesResponse)
def get_unidentified_faces(
page: int = Query(1, ge=1),
page_size: int = Query(50, ge=1, le=200),
page_size: int = Query(50, ge=1, le=2000),
min_quality: float = Query(0.0, ge=0.0, le=1.0),
date_from: str | None = Query(None),
date_to: str | None = Query(None),
date_taken_from: str | None = Query(None, description="Filter by date taken (from)"),
date_taken_to: str | None = Query(None, description="Filter by date taken (to)"),
date_processed: str | None = Query(None, description="Filter by date processed (exact date)"),
sort_by: str = Query("quality"),
sort_dir: str = Query("desc"),
tag_names: str | None = Query(None, description="Comma-separated tag names for filtering"),
@ -113,8 +114,20 @@ def get_unidentified_faces(
"""Get unidentified faces with filters and pagination."""
from datetime import date as _date
df = _date.fromisoformat(date_from) if date_from else None
dt = _date.fromisoformat(date_to) if date_to else None
try:
dtf = _date.fromisoformat(date_taken_from) if date_taken_from and date_taken_from.strip() else None
except (ValueError, AttributeError, TypeError):
dtf = None
try:
dtt = _date.fromisoformat(date_taken_to) if date_taken_to and date_taken_to.strip() else None
except (ValueError, AttributeError, TypeError):
dtt = None
try:
dp = _date.fromisoformat(date_processed) if date_processed and date_processed.strip() else None
except (ValueError, AttributeError, TypeError):
dp = None
# Parse tag names
tag_names_list = None
@ -126,8 +139,9 @@ def get_unidentified_faces(
page=page,
page_size=page_size,
min_quality=min_quality,
date_from=df,
date_to=dt,
date_taken_from=dtf,
date_taken_to=dtt,
date_processed=dp,
sort_by=sort_by,
sort_dir=sort_dir,
tag_names=tag_names_list,

View File

@ -1200,6 +1200,7 @@ def list_unidentified_faces(
date_to: Optional[date] = None,
date_taken_from: Optional[date] = None,
date_taken_to: Optional[date] = None,
date_processed: Optional[date] = None,
date_processed_from: Optional[date] = None,
date_processed_to: Optional[date] = None,
sort_by: str = "quality",
@ -1263,6 +1264,9 @@ def list_unidentified_faces(
query = query.filter(Photo.date_taken <= date_taken_to)
# Date processed filters (uses photo.date_added)
if date_processed is not None:
# Filter by exact date processed
query = query.filter(func.date(Photo.date_added) == date_processed)
if date_processed_from is not None:
query = query.filter(func.date(Photo.date_added) >= date_processed_from)
if date_processed_to is not None: