写真付き落とし物管理
拾得物の写真付き一覧掲載および返却ステータス管理。Supabase Storage と連携した画像最適化アップロード、品名・場所での検索、返却完了時の非表示処理に対応。
使い方
参加者向け
import LostStatus from "@/features/lost/components/LostStatus";
<LostStatus />全体管理者向け
import LostManager from "@/features/lost/components/LostManager";
<LostManager />API src/features/lost/api.ts
落とし物一覧の取得はアプリ共通の DataContext.FetchedData.lostItems を通じて自動ポーリング・キャッシュ管理され、登録・編集・削除は以下の API を通じて行われます。更新時は自動でキャッシュが無効化されます。
| API 関数 | 引数 / ペイロード | 役割 |
|---|---|---|
postLostItem(item) | item : { name : string, place : string, photo_path? : string } | 品名・拾得場所・写真パスを登録し lost キャッシュを無効化 |
updateLostItem(id, updates) | id : stringupdates : { name : string, place : string, reason : string, photo_path? : string } | 管理者専用 : 編集理由を付与して既存の落とし物情報を更新 |
deleteLostItem(id, photoPath?) | id : stringphotoPath? : string | 管理者専用 : 落とし物レコードおよび Storage の画像ファイルを削除 |
コード
クライアントサイド画像圧縮と EXIF 削除 src/lib/Misc/ImageUtils.ts
撮影された写真をブラウザ内で Canvas API を用いて最大 800×800px にリサイズ・JPEG 圧縮し、EXIF 位置情報などのメタデータを自動除去してアップロードします。
export const compressImage = (file: File, maxDim = 800, quality = 0.5): Promise<Blob> => {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = URL.createObjectURL(file);
img.onload = () => {
const scale = Math.min(maxDim / img.width, maxDim / img.height, 1);
const canvas = document.createElement("canvas");
canvas.width = img.width * scale;
canvas.height = img.height * scale;
canvas.getContext("2d")?.drawImage(img, 0, 0, canvas.width, canvas.height);
canvas.toBlob((blob) => blob ? resolve(blob) : reject(), "image/jpeg", quality);
};
});
};データベーススキーマ
CREATE TABLE lost_items (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
name TEXT NOT NULL,
place TEXT NOT NULL,
photo_path TEXT,
is_returned BOOLEAN DEFAULT false,
created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now())
);