<LostStatus /> / <LostManager />

写真付き落とし物管理

拾得物の写真付き一覧掲載および返却ステータス管理。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 : string
updates : { name : string, place : string, reason : string, photo_path? : string }
管理者専用 : 編集理由を付与して既存の落とし物情報を更新
deleteLostItem(id, photoPath?)id : string
photoPath? : string
管理者専用 : 落とし物レコードおよび Storage の画像ファイルを削除

コード

クライアントサイド画像圧縮と EXIF 削除 src/lib/Misc/ImageUtils.ts

撮影された写真をブラウザ内で Canvas API を用いて最大 800×800px にリサイズ・JPEG 圧縮し、EXIF 位置情報などのメタデータを自動除去してアップロードします。

src/lib/Misc/ImageUtils.ts
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);
    };
  });
};

データベーススキーマ

lost_items
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())
);