generated from fahricansecer/boilerplate-fe
Some checks failed
UI Deploy (Next-Auth Support) 🎨 / build-and-deploy (push) Has been cancelled
191 lines
7.8 KiB
TypeScript
191 lines
7.8 KiB
TypeScript
"use client";
|
|
|
|
import { Box, Table, Badge, HStack, IconButton, Button } from "@chakra-ui/react";
|
|
import { useState, useEffect, useCallback } from "react";
|
|
import { useSession } from "next-auth/react";
|
|
import { LuEye, LuPencil, LuTrash2, LuRefreshCw } from "react-icons/lu";
|
|
import { toaster } from "@/components/ui/feedback/toaster";
|
|
import { ContentPreviewDialog } from "./ContentPreviewDialog";
|
|
|
|
|
|
|
|
const getStatusColor = (status: string) => {
|
|
switch (status?.toLowerCase()) {
|
|
case "published": return "green";
|
|
case "draft": return "gray";
|
|
case "scheduled": return "blue";
|
|
case "review": return "orange";
|
|
default: return "gray";
|
|
}
|
|
};
|
|
|
|
export function ContentTable() {
|
|
const { data: session } = useSession();
|
|
const [selectedItem, setSelectedItem] = useState<any>(null);
|
|
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
|
|
const [contentList, setContentList] = useState<any[]>([]);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
const fetchContent = useCallback(async () => {
|
|
setIsLoading(true);
|
|
try {
|
|
const headers: HeadersInit = {};
|
|
if (session?.accessToken) {
|
|
headers['Authorization'] = `Bearer ${session.accessToken}`;
|
|
}
|
|
|
|
const res = await fetch('/api/backend/content', {
|
|
headers,
|
|
});
|
|
if (res.ok) {
|
|
const responseData = await res.json();
|
|
// Handle wrapped response from global interceptor: { success, data, message }
|
|
const items = responseData?.data || responseData;
|
|
setContentList(Array.isArray(items) ? items : []);
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to fetch content:", error);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [session]);
|
|
|
|
useEffect(() => {
|
|
fetchContent();
|
|
}, [fetchContent]);
|
|
|
|
const handleDelete = async (item: any) => {
|
|
if (!session?.accessToken) return;
|
|
try {
|
|
const res = await fetch(`/api/backend/content/${item.id}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Authorization': `Bearer ${session.accessToken}`
|
|
}
|
|
});
|
|
if (res.ok) {
|
|
toaster.create({ title: "Deleted", description: `"${item.title || 'Content'}" removed.`, type: "success" });
|
|
fetchContent();
|
|
} else {
|
|
toaster.create({ title: "Delete failed", type: "error" });
|
|
}
|
|
} catch {
|
|
toaster.create({ title: "Delete failed", type: "error" });
|
|
}
|
|
};
|
|
|
|
const handleAction = (action: string, item: any) => {
|
|
if (action === 'View') {
|
|
setSelectedItem(item);
|
|
setIsPreviewOpen(true);
|
|
return;
|
|
}
|
|
if (action === 'Edit') {
|
|
// Open the preview in edit mode
|
|
setSelectedItem(item);
|
|
setIsPreviewOpen(true);
|
|
return;
|
|
}
|
|
if (action === 'Delete') {
|
|
handleDelete(item);
|
|
return;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<HStack justify="flex-end" mb={3}>
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={fetchContent}
|
|
loading={isLoading}
|
|
>
|
|
<LuRefreshCw /> Refresh
|
|
</Button>
|
|
</HStack>
|
|
<Box borderWidth="1px" borderRadius="lg" overflow="hidden" bg="bg.panel">
|
|
<Table.Root striped>
|
|
<Table.Header>
|
|
<Table.Row>
|
|
<Table.ColumnHeader>Title</Table.ColumnHeader>
|
|
<Table.ColumnHeader>Platform</Table.ColumnHeader>
|
|
<Table.ColumnHeader>Status</Table.ColumnHeader>
|
|
<Table.ColumnHeader>Date</Table.ColumnHeader>
|
|
<Table.ColumnHeader textAlign="right">Actions</Table.ColumnHeader>
|
|
</Table.Row>
|
|
</Table.Header>
|
|
<Table.Body>
|
|
{isLoading ? (
|
|
<Table.Row>
|
|
<Table.Cell colSpan={5} textAlign="center" py={10}>
|
|
<HStack justify="center" gap={2}>
|
|
<LuRefreshCw className="animate-spin" />
|
|
<Box>Loading content...</Box>
|
|
</HStack>
|
|
</Table.Cell>
|
|
</Table.Row>
|
|
) : contentList.length === 0 ? (
|
|
<Table.Row>
|
|
<Table.Cell colSpan={5} textAlign="center" py={10}>
|
|
No content found. Start by generating some!
|
|
</Table.Cell>
|
|
</Table.Row>
|
|
) : contentList.map((item) => (
|
|
<Table.Row key={item.id}>
|
|
<Table.Cell fontWeight="medium">
|
|
{item.title || item.body?.substring(0, 60) + '...' || 'Untitled'}
|
|
</Table.Cell>
|
|
<Table.Cell>{item.type || item.platform}</Table.Cell>
|
|
<Table.Cell>
|
|
<Badge colorPalette={getStatusColor(item.status)} variant="solid">
|
|
{item.status}
|
|
</Badge>
|
|
</Table.Cell>
|
|
<Table.Cell>{new Date(item.createdAt || item.date).toLocaleDateString()}</Table.Cell>
|
|
<Table.Cell textAlign="right">
|
|
<HStack justify="flex-end" gap={2}>
|
|
<IconButton
|
|
variant="ghost"
|
|
size="sm"
|
|
aria-label="View"
|
|
onClick={() => handleAction('View', item)}
|
|
>
|
|
<LuEye />
|
|
</IconButton>
|
|
<IconButton
|
|
variant="ghost"
|
|
size="sm"
|
|
aria-label="Edit"
|
|
onClick={() => handleAction('Edit', item)}
|
|
>
|
|
<LuPencil />
|
|
</IconButton>
|
|
<IconButton
|
|
variant="ghost"
|
|
size="sm"
|
|
colorPalette="red"
|
|
aria-label="Delete"
|
|
onClick={() => handleAction('Delete', item)}
|
|
>
|
|
<LuTrash2 />
|
|
</IconButton>
|
|
</HStack>
|
|
</Table.Cell>
|
|
</Table.Row>
|
|
))}
|
|
</Table.Body>
|
|
</Table.Root>
|
|
</Box>
|
|
|
|
|
|
<ContentPreviewDialog
|
|
item={selectedItem}
|
|
open={isPreviewOpen}
|
|
onOpenChange={(e) => setIsPreviewOpen(e.open)}
|
|
onContentUpdated={fetchContent}
|
|
/>
|
|
</>
|
|
);
|
|
}
|