fix: split collaboration request controllers

هذا الالتزام موجود في:
boutmoun123
2026-05-31 17:05:58 +03:00
الأصل 49e132909e
التزام 1973b8b904
10 ملفات معدلة مع 402 إضافات و18 حذوفات

عرض الملف

@@ -1,14 +1,16 @@
"use client";
import { useCallback, useEffect, useRef, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { NoPermissionState } from "@/components/auth/no-permission-state";
import { useSuperAdminSession } from "@/components/auth/session-context";
import { PageHeader } from "@/components/dashboard/page-header";
import { MediaInspector, getMediaHealth } from "@/components/dashboard/media-inspector";
import { PaginationControls } from "@/components/dashboard/pagination-controls";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Drawer } from "@/components/ui/drawer";
import { EmptyState } from "@/components/ui/empty-state";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
@@ -89,6 +91,7 @@ export default function ContentPage() {
const [bulkAction, setBulkAction] = useState<BulkAction>("flag");
const [bulkReason, setBulkReason] = useState("");
const [bulkLoading, setBulkLoading] = useState(false);
const [selectedPost, setSelectedPost] = useState<ApiPost | null>(null);
const { toast } = useToast();
const filtersRef = useRef({ query, postType, visibility, moderationStatus });
@@ -165,6 +168,13 @@ export default function ContentPage() {
const posts = getItems(postsResponse) as ApiPost[];
const comments = getItems(commentsResponse) as ApiComment[];
const bulkIds = bulkTarget === "post" ? selectedPostIds : selectedCommentIds;
const mediaSummary = useMemo(() => {
const mediaPosts = posts.filter((post) => ["image", "video", "audio"].includes(post.postType ?? ""));
const unhealthy = mediaPosts.filter((post) => getMediaHealth(post).problems.length > 0);
const hlsReady = posts.filter((post) => post.postType === "video" && post.hlsUrl).length;
const thumbnailsReady = posts.filter((post) => post.thumbnailUrl || post.thumbnailVariants?.thumbnail).length;
return { mediaPosts: mediaPosts.length, unhealthy: unhealthy.length, hlsReady, thumbnailsReady };
}, [posts]);
const updatePostStatus = async (postId: string, status: ModerationStatus) => {
const reason = promptReason(`post ${status}`);
@@ -328,6 +338,33 @@ export default function ContentPage() {
</CardContent>
</Card>
<section className="grid gap-4 md:grid-cols-4">
<Card>
<CardHeader>
<CardTitle>Media posts</CardTitle>
</CardHeader>
<CardContent className="text-3xl font-bold text-foreground">{mediaSummary.mediaPosts}</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Needs media review</CardTitle>
</CardHeader>
<CardContent className="text-3xl font-bold text-foreground">{mediaSummary.unhealthy}</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>HLS ready videos</CardTitle>
</CardHeader>
<CardContent className="text-3xl font-bold text-foreground">{mediaSummary.hlsReady}</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Thumbnails ready</CardTitle>
</CardHeader>
<CardContent className="text-3xl font-bold text-foreground">{mediaSummary.thumbnailsReady}</CardContent>
</Card>
</section>
{canUseBulkActions ? (
<Card>
<CardHeader>
@@ -420,7 +457,14 @@ export default function ContentPage() {
) : null}
<TableCell className="max-w-[240px] truncate">{post.content || "-"}</TableCell>
<TableCell>{getUserLabel(getPostAuthor(post))}</TableCell>
<TableCell>{post.postType ?? "-"}</TableCell>
<TableCell>
<div className="space-y-1">
<div>{post.postType ?? "-"}</div>
<Badge variant={getMediaHealth(post).problems.length ? "warning" : "muted"}>
{getMediaHealth(post).problems.length ? "media check" : "ok"}
</Badge>
</div>
</TableCell>
<TableCell>
<Badge
variant={
@@ -438,6 +482,9 @@ export default function ContentPage() {
{post.likesCount ?? 0}/{post.commentsCount ?? 0}/{post.shareCount ?? 0}
</TableCell>
<TableCell className="flex flex-wrap gap-2">
<Button size="sm" variant="outline" onClick={() => setSelectedPost(post)}>
Details
</Button>
<Button size="sm" variant="outline" onClick={() => void updatePostStatus(post._id, "active")}>
Activate
</Button>
@@ -546,6 +593,36 @@ export default function ContentPage() {
</CardContent>
</Card>
</section>
<Drawer
open={Boolean(selectedPost)}
onClose={() => setSelectedPost(null)}
title="Post media details"
description="Preview the actual media, inspect generated URLs, and review moderation state."
side="right"
widthClassName="w-full sm:w-[92vw] sm:max-w-3xl"
>
{selectedPost ? (
<div className="space-y-4">
<MediaInspector post={selectedPost} />
<Card className="border-border/70 bg-secondary/20">
<CardHeader>
<CardTitle>Post metadata</CardTitle>
</CardHeader>
<CardContent className="grid gap-3 text-sm md:grid-cols-2">
<div>Author: {getUserLabel(getPostAuthor(selectedPost))}</div>
<div>Type: {selectedPost.postType ?? "-"}</div>
<div>Status: {selectedPost.moderationStatus ?? "active"}</div>
<div>Processing: {selectedPost.processingStatus ?? "-"}</div>
<div>Visibility: {selectedPost.visibility ?? "-"}</div>
<div>Created: {formatDateTime(selectedPost.createdAt)}</div>
<div>Likes: {selectedPost.likesCount ?? 0}</div>
<div>Comments: {selectedPost.commentsCount ?? 0}</div>
</CardContent>
</Card>
</div>
) : null}
</Drawer>
</div>
);
}