b318798ca7
- Remove approve/reject buttons and status badge from asset cards - Remove bulk approve/reject from gallery toolbar - Remove status filter dropdown (no longer relevant) - Add "Postiz" button on each image/video asset card for direct publishing - Keep bulk "Push to Postiz" for multi-select in toolbar - Update How This Works guide to remove approve/reject section Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
166 lines
4.9 KiB
TypeScript
166 lines
4.9 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState, useCallback } from "react";
|
|
import { AssetCard } from "@/components/asset-card";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
|
|
interface Asset {
|
|
id: string;
|
|
type: string;
|
|
platform?: string | null;
|
|
format?: string | null;
|
|
fileName: string;
|
|
filePath: string;
|
|
dimensions?: string | null;
|
|
metadata?: string | null;
|
|
status: string;
|
|
createdAt: string;
|
|
campaign?: { name: string };
|
|
parentAsset?: { id: string; fileName: string } | null;
|
|
}
|
|
|
|
interface AssetGalleryProps {
|
|
campaignId?: string;
|
|
onPushToPostiz?: (assetIds: string[]) => void;
|
|
}
|
|
|
|
export function AssetGallery({ campaignId, onPushToPostiz }: AssetGalleryProps) {
|
|
const [assets, setAssets] = useState<Asset[]>([]);
|
|
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
|
const [filters, setFilters] = useState({
|
|
platform: "all",
|
|
type: "all",
|
|
});
|
|
const [search, setSearch] = useState("");
|
|
const [sort, setSort] = useState("newest");
|
|
|
|
const fetchAssets = useCallback(() => {
|
|
const params = new URLSearchParams();
|
|
if (campaignId) params.set("campaignId", campaignId);
|
|
if (filters.platform !== "all") params.set("platform", filters.platform);
|
|
if (filters.type !== "all") params.set("type", filters.type);
|
|
if (search) params.set("search", search);
|
|
|
|
fetch(`/api/assets?${params}`)
|
|
.then((r) => r.json())
|
|
.then(setAssets)
|
|
.catch(() => {});
|
|
}, [campaignId, filters, search]);
|
|
|
|
useEffect(() => {
|
|
fetchAssets();
|
|
}, [fetchAssets]);
|
|
|
|
function toggleSelect(id: string) {
|
|
setSelectedIds((prev) => {
|
|
const next = new Set(prev);
|
|
if (next.has(id)) next.delete(id);
|
|
else next.add(id);
|
|
return next;
|
|
});
|
|
}
|
|
|
|
const sortedAssets = [...assets].sort((a, b) => {
|
|
switch (sort) {
|
|
case "oldest":
|
|
return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
|
case "name-asc":
|
|
return a.fileName.localeCompare(b.fileName);
|
|
case "name-desc":
|
|
return b.fileName.localeCompare(a.fileName);
|
|
case "platform":
|
|
return (a.platform || "").localeCompare(b.platform || "");
|
|
case "type":
|
|
return a.type.localeCompare(b.type);
|
|
case "newest":
|
|
default:
|
|
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
|
|
}
|
|
});
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
{/* Filters */}
|
|
<div className="flex flex-wrap gap-3 items-center">
|
|
<select
|
|
className="h-9 rounded-md border px-3 text-sm"
|
|
value={filters.platform}
|
|
onChange={(e) =>
|
|
setFilters((f) => ({ ...f, platform: e.target.value }))
|
|
}
|
|
>
|
|
<option value="all">All Platforms</option>
|
|
<option value="instagram">Instagram</option>
|
|
<option value="tiktok">TikTok</option>
|
|
<option value="nextdoor">Nextdoor</option>
|
|
</select>
|
|
|
|
<select
|
|
className="h-9 rounded-md border px-3 text-sm"
|
|
value={filters.type}
|
|
onChange={(e) =>
|
|
setFilters((f) => ({ ...f, type: e.target.value }))
|
|
}
|
|
>
|
|
<option value="all">All Types</option>
|
|
<option value="image">Images</option>
|
|
<option value="video">Videos</option>
|
|
<option value="copy">Copy</option>
|
|
<option value="script">Scripts</option>
|
|
</select>
|
|
|
|
<select
|
|
className="h-9 rounded-md border px-3 text-sm"
|
|
value={sort}
|
|
onChange={(e) => setSort(e.target.value)}
|
|
>
|
|
<option value="newest">Newest First</option>
|
|
<option value="oldest">Oldest First</option>
|
|
<option value="name-asc">Name A-Z</option>
|
|
<option value="name-desc">Name Z-A</option>
|
|
<option value="platform">Platform</option>
|
|
<option value="type">Type</option>
|
|
</select>
|
|
|
|
<Input
|
|
placeholder="Search..."
|
|
value={search}
|
|
onChange={(e) => setSearch(e.target.value)}
|
|
className="h-9 w-48"
|
|
/>
|
|
|
|
{selectedIds.size > 0 && onPushToPostiz && (
|
|
<div className="ml-auto">
|
|
<Button
|
|
size="sm"
|
|
onClick={() => onPushToPostiz(Array.from(selectedIds))}
|
|
>
|
|
Push to Postiz ({selectedIds.size})
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Grid */}
|
|
{sortedAssets.length === 0 ? (
|
|
<p className="text-center text-muted-foreground py-12">
|
|
No assets yet. Launch a pipeline to generate content.
|
|
</p>
|
|
) : (
|
|
<div className="grid gap-4 grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
|
|
{sortedAssets.map((asset) => (
|
|
<AssetCard
|
|
key={asset.id}
|
|
asset={asset}
|
|
selected={selectedIds.has(asset.id)}
|
|
onSelect={toggleSelect}
|
|
onPushToPostiz={onPushToPostiz}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|