style: clean up code with prettier and eslint

This commit is contained in:
openhands committed 2026-07-12 20:31:05 +02:00
1 parent 60278b9e71
commit fb8e77bb68
378 files changed
+22407 -20689

No files matched your search

@@ -1,108 +1,102 @@
'use client'
"use client";
import { ImageIcon, Loader2, Search, X } from 'lucide-react'
import { useCallback, useEffect, useRef, useState } from 'react'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog'
import { Input } from '@/components/ui/input'
import { cn } from '@/lib/utils'
import { ImageIcon, Loader2, Search, X } from "lucide-react";
import { useCallback, useEffect, useRef, useState } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
interface CatalogImagePickerProps {
value: string
onChange: (name: string) => void
value: string;
onChange: (name: string) => void;
/** Filter: 'header' | 'teaser' | 'all' */
type?: 'header' | 'teaser' | 'all'
disabled?: boolean
label?: string
type?: "header" | "teaser" | "all";
disabled?: boolean;
label?: string;
}
const PAGE_SIZE = 80
const PAGE_SIZE = 80;
export function CatalogImagePicker({
value,
onChange,
type = 'all',
type = "all",
disabled,
label,
}: CatalogImagePickerProps) {
const [open, setOpen] = useState(false)
const [search, setSearch] = useState('')
const [debounced, setDebounced] = useState('')
const [images, setImages] = useState<string[]>([])
const [total, setTotal] = useState(0)
const [loading, setLoading] = useState(false)
const [offset, setOffset] = useState(0)
const [hasMore, setHasMore] = useState(true)
const scrollRef = useRef<HTMLDivElement>(null)
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");
const [debounced, setDebounced] = useState("");
const [images, setImages] = useState<string[]>([]);
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(false);
const [offset, setOffset] = useState(0);
const [hasMore, setHasMore] = useState(true);
const scrollRef = useRef<HTMLDivElement>(null);
// Debounce search
useEffect(() => {
const t = setTimeout(() => setDebounced(search.trim()), 250)
return () => clearTimeout(t)
}, [search])
const t = setTimeout(() => setDebounced(search.trim()), 250);
return () => clearTimeout(t);
}, [search]);
const fetchImages = useCallback(
async (nextOffset: number, replace: boolean) => {
setLoading(true)
setLoading(true);
try {
const params = new URLSearchParams({
limit: String(PAGE_SIZE),
offset: String(nextOffset),
type,
})
if (debounced) params.set('search', debounced)
const res = await fetch(`/api/admin/catalog/images?${params}`)
if (!res.ok) throw new Error()
const data = await res.json()
const fetched: string[] = data.images ?? []
setTotal(data.total ?? 0)
setHasMore(nextOffset + fetched.length < (data.total ?? 0))
setImages((prev) => (replace ? fetched : [...prev, ...fetched]))
setOffset(nextOffset + fetched.length)
});
if (debounced) params.set("search", debounced);
const res = await fetch(`/api/admin/catalog/images?${params}`);
if (!res.ok) throw new Error();
const data = await res.json();
const fetched: string[] = data.images ?? [];
setTotal(data.total ?? 0);
setHasMore(nextOffset + fetched.length < (data.total ?? 0));
setImages((prev) => (replace ? fetched : [...prev, ...fetched]));
setOffset(nextOffset + fetched.length);
} catch {
// ignore
} finally {
setLoading(false)
setLoading(false);
}
},
[debounced, type],
)
);
// Reset and fetch on open / search change
// biome-ignore lint/correctness/useExhaustiveDependencies: explicitly chosen here, see surrounding code
useEffect(() => {
if (!open) return
setImages([])
setOffset(0)
setHasMore(true)
fetchImages(0, true)
if (scrollRef.current) scrollRef.current.scrollTop = 0
}, [open, debounced, fetchImages])
if (!open) return;
setImages([]);
setOffset(0);
setHasMore(true);
fetchImages(0, true);
if (scrollRef.current) scrollRef.current.scrollTop = 0;
}, [open, debounced, fetchImages]);
const handleScroll = useCallback(() => {
const el = scrollRef.current
if (!el || loading || !hasMore) return
const el = scrollRef.current;
if (!el || loading || !hasMore) return;
if (el.scrollTop + el.clientHeight >= el.scrollHeight - 300) {
fetchImages(offset, false)
fetchImages(offset, false);
}
}, [loading, hasMore, offset, fetchImages])
}, [loading, hasMore, offset, fetchImages]);
const handleSelect = (name: string) => {
onChange(name)
setOpen(false)
}
onChange(name);
setOpen(false);
};
const handleClear = () => {
onChange('')
setOpen(false)
}
onChange("");
setOpen(false);
};
return (
<Dialog open={open} onOpenChange={setOpen}>
@@ -122,27 +116,20 @@ export function CatalogImagePicker({
)}
<div className="min-w-0 text-left">
<p className="text-xs font-medium truncate">
{label ||
(type === 'header'
? 'Headline image'
: type === 'teaser'
? 'Teaser image'
: 'Image')}
</p>
<p className="text-[11px] text-muted-foreground truncate font-mono">
{value || 'None selected'}
{label || (type === "header" ? "Headline image" : type === "teaser" ? "Teaser image" : "Image")}
</p>
<p className="text-[11px] text-muted-foreground truncate font-mono">{value || "None selected"}</p>
</div>
</Button>
</DialogTrigger>
<DialogContent className="max-w-4xl">
<DialogHeader>
<DialogTitle>
{type === 'header'
? 'Select Headline Image'
: type === 'teaser'
? 'Select Teaser Image'
: 'Select Catalog Image'}
{type === "header"
? "Select Headline Image"
: type === "teaser"
? "Select Teaser Image"
: "Select Catalog Image"}
</DialogTitle>
</DialogHeader>
@@ -159,7 +146,7 @@ export function CatalogImagePicker({
{search && (
<button
type="button"
onClick={() => setSearch('')}
onClick={() => setSearch("")}
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
>
<X className="h-4 w-4" />
@@ -189,8 +176,8 @@ export function CatalogImagePicker({
type="button"
onClick={() => handleSelect(name)}
className={cn(
'group relative flex flex-col overflow-hidden rounded-md border bg-card transition-colors hover:border-primary hover:shadow-sm',
value === name && 'border-primary ring-2 ring-primary/30',
"group relative flex flex-col overflow-hidden rounded-md border bg-card transition-colors hover:border-primary hover:shadow-sm",
value === name && "border-primary ring-2 ring-primary/30",
)}
title={name}
>
@@ -213,17 +200,17 @@ export function CatalogImagePicker({
</div>
</DialogContent>
</Dialog>
)
);
}
// ── Thumbnail with gif/png fallback ─────────────────────────────
function CatalogImageThumb({ name, size = 60 }: { name: string; size?: number }) {
const [error, setError] = useState(0)
const [error, setError] = useState(0);
// Reset error state when name changes
// biome-ignore lint/correctness/useExhaustiveDependencies: explicitly chosen here, see surrounding code
useEffect(() => setError(0), [name])
useEffect(() => setError(0), [name]);
if (!name || error >= 2) {
return (
@@ -233,20 +220,19 @@ function CatalogImageThumb({ name, size = 60 }: { name: string; size?: number })
>
<ImageIcon className="h-4 w-4 text-muted-foreground" />
</div>
)
);
}
// Try .png first, fallback to .gif
const src =
error === 0 ? `/swf/c_images/catalogue/${name}.png` : `/swf/c_images/catalogue/${name}.gif`
const src = error === 0 ? `/swf/c_images/catalogue/${name}.png` : `/swf/c_images/catalogue/${name}.gif`;
return (
<img
src={src}
alt={name}
className="object-contain"
style={{ imageRendering: 'pixelated', maxWidth: size, maxHeight: size * 0.75 }}
style={{ imageRendering: "pixelated", maxWidth: size, maxHeight: size * 0.75 }}
onError={() => setError((e) => e + 1)}
/>
)
);
}