feat:dashboard, agenda-setting
This commit is contained in:
parent
20b055a5ea
commit
2d58658333
|
|
@ -12,24 +12,60 @@ import { Calendar } from "@/components/ui/calendar";
|
|||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
import { Plus } from "lucide-react";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { CalendarEvent, CalendarCategory } from "./data";
|
||||
import { CalendarCategory } from "./data";
|
||||
import { EventContentArg } from "@fullcalendar/core";
|
||||
import EventModal from "./event-modal";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { getAgendaSettingsList } from "@/service/agenda-setting/agenda-setting";
|
||||
const wait = () => new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
interface CalendarViewProps {
|
||||
events: CalendarEvent[];
|
||||
categories: CalendarCategory[];
|
||||
}
|
||||
|
||||
export interface CalendarEvent {
|
||||
id: string;
|
||||
title: string;
|
||||
start: Date;
|
||||
end: Date;
|
||||
allDay: boolean;
|
||||
extendedProps: {
|
||||
calendar: string;
|
||||
description?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AgendaSettingsAPIResponse {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
agendaType: string;
|
||||
startDate: string; // API mengembalikan tanggal dalam bentuk string
|
||||
endDate: string;
|
||||
isActive: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
createdById: number | null;
|
||||
createdByName: string | null;
|
||||
}
|
||||
|
||||
interface APIResponse {
|
||||
error: boolean;
|
||||
message: any;
|
||||
data: AgendaSettingsAPIResponse[] | null; // `data` bisa berupa array atau null
|
||||
}
|
||||
|
||||
const CalendarView = ({ events, categories }: CalendarViewProps) => {
|
||||
const [selectedCategory, setSelectedCategory] = useState<string[] | null>(
|
||||
null
|
||||
);
|
||||
const [selectedEventDate, setSelectedEventDate] = useState<Date | null>(null);
|
||||
const [selectedEvent, setSelectedEvent] = useState<CalendarEvent | null>(
|
||||
null
|
||||
);
|
||||
// const [selectedEvent, setSelectedEvent] = useState<CalendarEvent | null>(
|
||||
// null
|
||||
// );
|
||||
|
||||
const [apiEvents, setApiEvents] = useState<CalendarEvent[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [draggableInitialized, setDraggableInitialized] =
|
||||
useState<boolean>(false);
|
||||
const t = useTranslations("CalendarApp");
|
||||
|
|
@ -48,6 +84,73 @@ const CalendarView = ({ events, categories }: CalendarViewProps) => {
|
|||
setSelectedCategory(categories?.map((c) => c.value));
|
||||
}, [events, categories]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("Fetched events from API:", apiEvents);
|
||||
}, [apiEvents]);
|
||||
|
||||
const filteredEvents = apiEvents?.filter((event) =>
|
||||
selectedCategory?.includes(event.extendedProps.calendar)
|
||||
);
|
||||
|
||||
const displayedEvents =
|
||||
filteredEvents?.length > 0 ? filteredEvents : apiEvents;
|
||||
|
||||
useEffect(() => {
|
||||
console.log("Filtered events based on category:", displayedEvents);
|
||||
}, [filteredEvents, apiEvents]);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedCategory(categories?.map((c) => c.value));
|
||||
}, [categories]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("Selected categories:", selectedCategory);
|
||||
}, [selectedCategory]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchAgendaEvents = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const selectedMonth = new Date(); // Replace with your logic for selected month
|
||||
const year = selectedMonth.getFullYear().toString();
|
||||
const month = (selectedMonth.getMonth() + 1).toString();
|
||||
const typeFilter = ""; // Replace with your type filter logic if needed
|
||||
|
||||
const response: APIResponse = await getAgendaSettingsList(
|
||||
year,
|
||||
month,
|
||||
typeFilter
|
||||
);
|
||||
|
||||
if (response.data && Array.isArray(response.data)) {
|
||||
// Transform API data to match CalendarEvent type
|
||||
const eventsFromAPI: CalendarEvent[] = response.data.map((item) => ({
|
||||
id: item.id.toString(),
|
||||
title: item.title,
|
||||
start: new Date(item.startDate),
|
||||
end: new Date(item.endDate),
|
||||
allDay: true, // Sesuaikan jika memang ada event sepanjang hari
|
||||
extendedProps: {
|
||||
calendar: item.agendaType,
|
||||
description: item.description,
|
||||
},
|
||||
}));
|
||||
setApiEvents(eventsFromAPI);
|
||||
} else {
|
||||
console.warn("No events found in API response.");
|
||||
setApiEvents([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch agenda settings:", error);
|
||||
setApiEvents([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchAgendaEvents();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const draggableEl = document.getElementById("external-events");
|
||||
|
||||
|
|
@ -80,23 +183,24 @@ const CalendarView = ({ events, categories }: CalendarViewProps) => {
|
|||
draggableEl?.removeEventListener("mousedown", initDraggable);
|
||||
};
|
||||
}, [dragEvents]);
|
||||
|
||||
// event click
|
||||
const handleEventClick = (arg: any) => {
|
||||
setSelectedEventDate(null);
|
||||
setSheetOpen(true);
|
||||
setSelectedEvent(arg);
|
||||
setApiEvents(arg);
|
||||
wait().then(() => (document.body.style.pointerEvents = "auto"));
|
||||
};
|
||||
// handle close modal
|
||||
const handleCloseModal = () => {
|
||||
setSheetOpen(false);
|
||||
setSelectedEvent(null);
|
||||
setApiEvents([]);
|
||||
setSelectedEventDate(null);
|
||||
};
|
||||
const handleDateClick = (arg: any) => {
|
||||
setSheetOpen(true);
|
||||
setSelectedEventDate(arg);
|
||||
setSelectedEvent(null);
|
||||
setApiEvents([]);
|
||||
wait().then(() => (document.body.style.pointerEvents = "auto"));
|
||||
};
|
||||
|
||||
|
|
@ -109,7 +213,7 @@ const CalendarView = ({ events, categories }: CalendarViewProps) => {
|
|||
};
|
||||
|
||||
const handleClassName = (arg: EventContentArg) => {
|
||||
if (arg.event.extendedProps.calendar === "national") {
|
||||
if (arg.event.extendedProps.calendar === "mabes") {
|
||||
return "primary";
|
||||
} else if (arg.event.extendedProps.calendar === "polda") {
|
||||
return "success";
|
||||
|
|
@ -122,10 +226,6 @@ const CalendarView = ({ events, categories }: CalendarViewProps) => {
|
|||
}
|
||||
};
|
||||
|
||||
const filteredEvents = events?.filter((event) =>
|
||||
selectedCategory?.includes(event.extendedProps.calendar)
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-12 gap-6 divide-x divide-border">
|
||||
|
|
@ -205,7 +305,7 @@ const CalendarView = ({ events, categories }: CalendarViewProps) => {
|
|||
center: "title",
|
||||
right: "dayGridMonth,timeGridWeek,timeGridDay,listWeek",
|
||||
}}
|
||||
events={filteredEvents}
|
||||
events={displayedEvents} // Use apiEvents here
|
||||
editable={true}
|
||||
rerenderDelay={10}
|
||||
eventDurationEditable={false}
|
||||
|
|
@ -226,7 +326,7 @@ const CalendarView = ({ events, categories }: CalendarViewProps) => {
|
|||
open={sheetOpen}
|
||||
onClose={handleCloseModal}
|
||||
categories={categories}
|
||||
event={selectedEvent}
|
||||
event={apiEvents}
|
||||
selectedDate={selectedEventDate}
|
||||
/>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -5,11 +5,12 @@ import { useTranslations } from "next-intl";
|
|||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { UploadIcon } from "lucide-react";
|
||||
import RecentActivity from "./routine-task/recent-activity";
|
||||
import CompanyTable from "./routine-task/routine-task-table";
|
||||
import RecentActivity from "./routine-task/components/recent-activity";
|
||||
import CompanyTable from "./routine-task/components/content-table";
|
||||
import TaskTable from "../task/components/task-table";
|
||||
import PressConferenceTable from "../schedule/press-release/components/pressrilis-table";
|
||||
import BlogTable from "../blog/components/blog-table";
|
||||
import ContentTable from "./routine-task/components/content-table";
|
||||
|
||||
const DashboardPage = () => {
|
||||
const t = useTranslations("AnalyticsDashboard");
|
||||
|
|
@ -94,7 +95,7 @@ const DashboardPage = () => {
|
|||
<DashboardDropdown />
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<CompanyTable />
|
||||
<ContentTable />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,174 @@
|
|||
import * as React from "react";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
|
||||
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuItem,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { format } from "date-fns";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
accessorKey: "no",
|
||||
header: "No",
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center gap-5">
|
||||
<div className="flex-1 text-start">
|
||||
<h4 className="text-sm font-medium text-default-600 whitespace-nowrap mb-1">
|
||||
{row.getValue("no")}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "title",
|
||||
header: "Title",
|
||||
cell: ({ row }: { row: { getValue: (key: string) => string } }) => {
|
||||
const title: string = row.getValue("title");
|
||||
return (
|
||||
<span className="">
|
||||
{title.length > 50 ? `${title.slice(0, 10)}...` : title}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "createdAt",
|
||||
header: "Upload Date",
|
||||
cell: ({ row }) => {
|
||||
const createdAt = row.getValue("createdAt") as
|
||||
| string
|
||||
| number
|
||||
| undefined;
|
||||
|
||||
const formattedDate =
|
||||
createdAt && !isNaN(new Date(createdAt).getTime())
|
||||
? format(new Date(createdAt), "dd-MM-yyyy HH:mm:ss")
|
||||
: "-";
|
||||
return <span className="whitespace-nowrap">{formattedDate}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "fileTypeName",
|
||||
header: "Type Content",
|
||||
cell: ({ row }: { row: { getValue: (key: string) => string } }) => {
|
||||
const title: string = row.getValue("fileTypeName");
|
||||
return (
|
||||
<span className="whitespace-nowrap">
|
||||
{title.length > 50 ? `${title.slice(0, 30)}...` : title}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
// {
|
||||
// accessorKey: "creatorGroup",
|
||||
// header: "Creator Group",
|
||||
// cell: ({ row }) => (
|
||||
// <span className="whitespace-nowrap">{row.getValue("creatorGroup")}</span>
|
||||
// ),
|
||||
// },
|
||||
// {
|
||||
// accessorKey: "creatorName",
|
||||
// header: "Sumber",
|
||||
// cell: ({ row }) => (
|
||||
// <span className="whitespace-nowrap">{row.getValue("creatorName")}</span>
|
||||
// ),
|
||||
// },
|
||||
// {
|
||||
// accessorKey: "publishedOn",
|
||||
// header: "Published",
|
||||
// cell: ({ row }) => {
|
||||
// const isPublish = row.original.isPublish;
|
||||
// const isPublishOnPolda = row.original.isPublishOnPolda;
|
||||
|
||||
// let displayText = "-";
|
||||
// if (isPublish && !isPublishOnPolda) {
|
||||
// displayText = "Mabes";
|
||||
// } else if (isPublish && isPublishOnPolda) {
|
||||
// displayText = "Mabes & Polda";
|
||||
// } else if (!isPublish && isPublishOnPolda) {
|
||||
// displayText = "Polda";
|
||||
// }
|
||||
|
||||
// return (
|
||||
// <div className="text-center whitespace-nowrap" title={displayText}>
|
||||
// {displayText}
|
||||
// </div>
|
||||
// );
|
||||
// },
|
||||
// },
|
||||
|
||||
{
|
||||
accessorKey: "statusName",
|
||||
header: "Status",
|
||||
cell: ({ row }) => {
|
||||
// Mendapatkan nilai statusName
|
||||
const statusName = row.getValue<string>("statusName");
|
||||
|
||||
// Mapping warna berdasarkan statusName
|
||||
const colorMapping: Record<string, string> = {
|
||||
"Menunggu Review": "text-orange-500 border-orange-500",
|
||||
Diterima: "text-green-500 border-green-500",
|
||||
"Minta Update": "text-blue-500 border-blue-500",
|
||||
Ditolak: "text-red-500 border-red-500",
|
||||
};
|
||||
|
||||
// Mendapatkan kelas warna dari mapping, default ke abu-abu jika tidak ditemukan
|
||||
const buttonClass =
|
||||
colorMapping[statusName] || "text-gray-500 border-gray-500";
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className={`btn btn-sm pill-btn ml-1 ${buttonClass}`}
|
||||
>
|
||||
{statusName || "Tidak Diketahui"}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
accessorKey: "action",
|
||||
header: "Actions",
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
|
||||
>
|
||||
<span className="sr-only">Open menu</span>
|
||||
<MoreVertical className="h-4 w-4 text-default-800" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="p-0" align="end">
|
||||
<a href="/en/task/detail/[id]">
|
||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||
<Eye className="w-4 h-4 me-1.5" />
|
||||
View
|
||||
</DropdownMenuItem>
|
||||
</a>
|
||||
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
||||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export default columns;
|
||||
|
|
@ -0,0 +1,271 @@
|
|||
"use client";
|
||||
import * as React from "react";
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
PaginationState,
|
||||
SortingState,
|
||||
VisibilityState,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Eye,
|
||||
MoreVertical,
|
||||
Search,
|
||||
SquarePen,
|
||||
Trash2,
|
||||
TrendingDown,
|
||||
TrendingUp,
|
||||
} from "lucide-react";
|
||||
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||
import { paginationBlog } from "@/service/blog/blog";
|
||||
import { ticketingPagination } from "@/service/ticketing/ticketing";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import TablePagination from "@/components/table/table-pagination";
|
||||
import columns from "./columns";
|
||||
import { listDataAll, listDataImage } from "@/service/content/content";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
|
||||
type StatusFilter = string[];
|
||||
|
||||
const ContentTable = () => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
||||
const [totalData, setTotalData] = React.useState<number>(1);
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
[]
|
||||
);
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
const [page, setPage] = React.useState(1);
|
||||
const [totalPage, setTotalPage] = React.useState(1);
|
||||
const [limit, setLimit] = React.useState(10);
|
||||
const [search, setSearch] = React.useState<string>("");
|
||||
const userId = getCookiesDecrypt("uie");
|
||||
const userLevelId = getCookiesDecrypt("ulie");
|
||||
|
||||
const [categories, setCategories] = React.useState<string[]>();
|
||||
const [categoryFilter, setCategoryFilter] = React.useState<string[]>([]);
|
||||
const [statusFilter, setStatusFilter] = React.useState<StatusFilter>([]);
|
||||
const [startDateString, setStartDateString] = React.useState<string>("");
|
||||
const [endDateString, setEndDateString] = React.useState<string>("");
|
||||
const [filterByCreator, setFilterByCreator] = React.useState<string>("");
|
||||
const [fileTypeFilter, setFileTypeFilter] = React.useState<string[]>([]);
|
||||
const [filterBySource, setFilterBySource] = React.useState<string>("");
|
||||
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
|
||||
const table = useReactTable({
|
||||
data: dataTable,
|
||||
columns,
|
||||
onSortingChange: setSorting,
|
||||
onColumnFiltersChange: setColumnFilters,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
onColumnVisibilityChange: setColumnVisibility,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
onPaginationChange: setPagination,
|
||||
state: {
|
||||
sorting,
|
||||
columnFilters,
|
||||
columnVisibility,
|
||||
rowSelection,
|
||||
pagination,
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const pageFromUrl = searchParams?.get("page");
|
||||
if (pageFromUrl) {
|
||||
setPage(Number(pageFromUrl));
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchData();
|
||||
}, [page, limit, fileTypeFilter, statusFilter]);
|
||||
|
||||
async function fetchData() {
|
||||
try {
|
||||
const isForSelf = Number(roleId) === 4;
|
||||
const res = await listDataAll(
|
||||
isForSelf,
|
||||
!isForSelf,
|
||||
page - 1,
|
||||
limit,
|
||||
search,
|
||||
fileTypeFilter.sort().join(","),
|
||||
categoryFilter.sort().join(","),
|
||||
statusFilter.sort().join(","),
|
||||
statusFilter.sort().join(",").includes("1") ? userLevelId : "",
|
||||
filterByCreator,
|
||||
filterBySource,
|
||||
startDateString,
|
||||
endDateString
|
||||
);
|
||||
const data = res.data?.data;
|
||||
const contentData = data?.content;
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * limit + index + 1;
|
||||
});
|
||||
|
||||
console.log("contentData : ", contentData);
|
||||
|
||||
setDataTable(contentData);
|
||||
setTotalData(data?.totalElements);
|
||||
setTotalPage(data?.totalPages);
|
||||
} catch (error) {
|
||||
console.error("Error fetching tasks:", error);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="flex justify-between items-center px-5">
|
||||
<div>
|
||||
<InputGroup merged>
|
||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||
<Search className="h-4 w-4 dark:text-white" />
|
||||
</InputGroupText>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search Judul..."
|
||||
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
{/* <Select
|
||||
onValueChange={(value) => {
|
||||
setStatusFilter([value]);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Select a Filter Status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>Status</SelectLabel>
|
||||
<SelectItem value="1">Menunggu Review</SelectItem>
|
||||
<SelectItem value="2">Diterima</SelectItem>
|
||||
<SelectItem value="3">Minta Update</SelectItem>
|
||||
<SelectItem value="4">Ditolak</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select> */}
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
setFileTypeFilter([value]);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Select a Filter" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>Filter</SelectLabel>
|
||||
<SelectItem value="1">Image</SelectItem>
|
||||
<SelectItem value="2">Video</SelectItem>
|
||||
<SelectItem value="3">Teks</SelectItem>
|
||||
<SelectItem value="4">Audio</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<Table className="overflow-hidden mt-3">
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id} className="bg-default-200">
|
||||
{headerGroup.headers.map((header) => (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
className="h-[75px]"
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination
|
||||
table={table}
|
||||
totalData={totalData}
|
||||
totalPage={totalPage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContentTable;
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
"use client";
|
||||
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
import { listDataAll } from "@/service/content/content";
|
||||
import {
|
||||
ColumnFiltersState,
|
||||
PaginationState,
|
||||
SortingState,
|
||||
VisibilityState,
|
||||
} from "@tanstack/react-table";
|
||||
import { DockIcon, ImageIcon, MicIcon, YoutubeIcon } from "lucide-react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import React from "react";
|
||||
import search from "../../../app/chat/components/search";
|
||||
|
||||
type StatusFilter = string[];
|
||||
|
||||
interface Counts {
|
||||
images: number;
|
||||
audiovisual: number;
|
||||
text: number;
|
||||
audio: number;
|
||||
}
|
||||
|
||||
const RecentActivity: React.FC = () => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
||||
const [totalData, setTotalData] = React.useState<number>(1);
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
[]
|
||||
);
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
const [page, setPage] = React.useState(1);
|
||||
const [totalPage, setTotalPage] = React.useState(1);
|
||||
const [limit, setLimit] = React.useState(10);
|
||||
const [search, setSearch] = React.useState<string>("");
|
||||
const userId = getCookiesDecrypt("uie");
|
||||
const userLevelId = getCookiesDecrypt("ulie");
|
||||
|
||||
const [categories, setCategories] = React.useState<string[]>();
|
||||
const [categoryFilter, setCategoryFilter] = React.useState<string[]>([]);
|
||||
const [statusFilter, setStatusFilter] = React.useState<StatusFilter>([]);
|
||||
const [startDateString, setStartDateString] = React.useState<string>("");
|
||||
const [endDateString, setEndDateString] = React.useState<string>("");
|
||||
const [filterByCreator, setFilterByCreator] = React.useState<string>("");
|
||||
const [fileTypeFilter, setFileTypeFilter] = React.useState<string[]>([]);
|
||||
const [filterBySource, setFilterBySource] = React.useState<string>("");
|
||||
|
||||
const [counts, setCounts] = React.useState<Counts>({
|
||||
images: 0,
|
||||
audiovisual: 0,
|
||||
text: 0,
|
||||
audio: 0,
|
||||
});
|
||||
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
|
||||
React.useEffect(() => {
|
||||
const pageFromUrl = searchParams?.get("page");
|
||||
if (pageFromUrl) {
|
||||
setPage(Number(pageFromUrl));
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchData();
|
||||
}, [page, limit]);
|
||||
|
||||
async function fetchData() {
|
||||
try {
|
||||
const isForSelf = Number(roleId) === 4;
|
||||
const res = await listDataAll(
|
||||
isForSelf,
|
||||
!isForSelf,
|
||||
page - 1,
|
||||
limit,
|
||||
search,
|
||||
fileTypeFilter.sort().join(","),
|
||||
categoryFilter.sort().join(","),
|
||||
statusFilter.sort().join(","),
|
||||
statusFilter.sort().join(",").includes("1") ? userLevelId : "",
|
||||
filterByCreator,
|
||||
filterBySource,
|
||||
startDateString,
|
||||
endDateString
|
||||
);
|
||||
const data = res.data?.data;
|
||||
const { content } = data || [];
|
||||
|
||||
// Calculate counts for each typeId
|
||||
const newCounts: Counts = {
|
||||
images: content.filter((item: any) => item.typeId === 1).length,
|
||||
audiovisual: content.filter((item: any) => item.typeId === 2).length,
|
||||
text: content.filter((item: any) => item.typeId === 3).length,
|
||||
audio: content.filter((item: any) => item.typeId === 4).length,
|
||||
};
|
||||
|
||||
setDataTable(content);
|
||||
setCounts(newCounts);
|
||||
} catch (error) {
|
||||
console.error("Error fetching tasks:", error);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-5">
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<ImageIcon size={40} className="text-blue-700" />
|
||||
<p className="text-xl">{counts.images} FOTO</p>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<YoutubeIcon size={40} className="text-blue-700" />
|
||||
<p className="text-xl">{counts.audiovisual} AUDIO VISUAL</p>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<DockIcon size={40} className="text-blue-700" />
|
||||
<p className="text-xl">{counts.text} TEXT</p>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<MicIcon size={40} className="text-blue-700" />
|
||||
<p className="text-xl">{counts.audio} AUDIO</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RecentActivity;
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import {
|
||||
DockIcon,
|
||||
ImageIcon,
|
||||
MicIcon,
|
||||
PaperclipIcon,
|
||||
TextIcon,
|
||||
VideoIcon,
|
||||
YoutubeIcon,
|
||||
} from "lucide-react";
|
||||
|
||||
const RecentActivity = () => {
|
||||
return (
|
||||
<div className="flex flex-col gap-5">
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<ImageIcon size={40} className="text-blue-700" />
|
||||
<p className="text-xl">0 FOTO</p>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<YoutubeIcon size={40} className="text-blue-700" />
|
||||
<p className="text-xl">0 AUDIO VISUAL</p>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<DockIcon size={40} className="text-blue-700" />
|
||||
<p className="text-xl">0 TEXT</p>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<MicIcon size={40} className="text-blue-700" />
|
||||
<p className="text-xl">0 AUDIO</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RecentActivity;
|
||||
|
|
@ -1,272 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
PaginationState,
|
||||
SortingState,
|
||||
VisibilityState,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { data } from "./data";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
Badge,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Eye,
|
||||
MoreVertical,
|
||||
SquarePen,
|
||||
Trash2,
|
||||
TrendingDown,
|
||||
TrendingUp,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
|
||||
export type CompanyData = {
|
||||
company: string;
|
||||
category: string;
|
||||
revenue: string;
|
||||
sales: number;
|
||||
status: string;
|
||||
up: boolean;
|
||||
};
|
||||
|
||||
export const columns: ColumnDef<CompanyData>[] = [
|
||||
{
|
||||
accessorKey: "company",
|
||||
header: "Judul",
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center gap-5">
|
||||
<div className="flex-none">
|
||||
<div className="w-8 h-8">
|
||||
<Avatar>
|
||||
<AvatarImage src={row.getValue("company")}></AvatarImage>
|
||||
<AvatarFallback>SC</AvatarFallback>
|
||||
</Avatar>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 text-start">
|
||||
<h4 className="text-sm font-medium text-default-600 whitespace-nowrap mb-1">
|
||||
Biffco Enterprises Ltd.
|
||||
</h4>
|
||||
<div className="text-xs font-normal text-default-600 ">
|
||||
Biffco@example.com
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "category",
|
||||
header: "Tanggal Unggah",
|
||||
cell: ({ row }) => (
|
||||
<span className="whitespace-nowrap">{row.getValue("category")}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "sales",
|
||||
header: "Tipe Konten",
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center gap-4">
|
||||
<span>{row.getValue("sales")}</span>
|
||||
{row?.original.up ? (
|
||||
<TrendingUp className="text-success w-4 h-4" />
|
||||
) : (
|
||||
<TrendingDown className="text-destructive w-4 h-4" />
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "status",
|
||||
header: "Status",
|
||||
cell: ({ row }) => {
|
||||
const statusColors: Record<CompanyData["status"], string> = {
|
||||
paid: "bg-success/20 text-success",
|
||||
due: "bg-warning/20 text-warning",
|
||||
canceled: "bg-destructive/20 text-destructive",
|
||||
};
|
||||
const status = row.getValue<CompanyData["status"]>("status");
|
||||
return (
|
||||
<Badge className={cn("rounded-full px-5", statusColors[status])}>
|
||||
{status}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
accessorKey: "action",
|
||||
header: "Actions",
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
|
||||
>
|
||||
<span className="sr-only">Open menu</span>
|
||||
<MoreVertical className="h-4 w-4 text-default-800" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="p-0" align="end">
|
||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||
<Eye className="w-4 h-4 me-1.5" />
|
||||
View
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||
<SquarePen className="w-4 h-4 me-1.5" />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
||||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const CompanyTable = () => {
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
[]
|
||||
);
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 6,
|
||||
});
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
onSortingChange: setSorting,
|
||||
onColumnFiltersChange: setColumnFilters,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
onColumnVisibilityChange: setColumnVisibility,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
onPaginationChange: setPagination,
|
||||
state: {
|
||||
sorting,
|
||||
columnFilters,
|
||||
columnVisibility,
|
||||
rowSelection,
|
||||
pagination,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="w-full overflow-x-auto">
|
||||
<Table className="overflow-hidden">
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id} className="bg-default-200">
|
||||
{headerGroup.headers.map((header) => (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
className="h-[75px]"
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div className="flex items-center justify-center py-4 gap-2 flex-none">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
className="w-8 h-8"
|
||||
>
|
||||
<ChevronLeft className="w-4 h-4" />
|
||||
</Button>
|
||||
{table.getPageOptions().map((page, pageIndex) => (
|
||||
<Button
|
||||
key={`basic-data-table-${pageIndex}`}
|
||||
onClick={() => table.setPageIndex(pageIndex)}
|
||||
size="icon"
|
||||
className="w-8 h-8"
|
||||
variant={
|
||||
table.getState().pagination.pageIndex === pageIndex
|
||||
? "default"
|
||||
: "outline"
|
||||
}
|
||||
>
|
||||
{page + 1}
|
||||
</Button>
|
||||
))}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
className="w-8 h-8"
|
||||
>
|
||||
<ChevronRight className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompanyTable;
|
||||
|
|
@ -1,6 +1,47 @@
|
|||
import { getAPIInterceptor, postAPIInterceptor } from "@/config/api";
|
||||
import { httpGetInterceptor } from "../http-config/http-interceptor-service";
|
||||
|
||||
// export async function listDataAll(
|
||||
// isForSelf,
|
||||
// isApproval,
|
||||
// page,
|
||||
// limit,
|
||||
// search,
|
||||
// fileTypeFilter,
|
||||
// statusFilter,
|
||||
// startDate,
|
||||
// endDate,
|
||||
// needApprovalFromLevel
|
||||
// ) {
|
||||
// const name = search || "";
|
||||
// const url = `media/list?title=${name}&enablePage=1&sortBy=createdAt&sort=desc&size=${limit}&page=${page}&isForSelf=${isForSelf}&isApproval=${isApproval}&typeId=${fileTypeFilter}&statusId=${statusFilter}&startDate=${
|
||||
// startDate == undefined ? "" : startDate
|
||||
// }&endDate=${
|
||||
// endDate == undefined ? "" : endDate
|
||||
// }&needApprovalFromLevel=${needApprovalFromLevel}`;
|
||||
// return getAPIInterceptor({ url });
|
||||
// }
|
||||
|
||||
export async function listDataAll(
|
||||
isForSelf: any,
|
||||
isApproval: any,
|
||||
page: any,
|
||||
limit: any,
|
||||
search: any,
|
||||
fileTypeFilter: any,
|
||||
statusFilter: any,
|
||||
needApprovalFromLevel: any,
|
||||
creator: any,
|
||||
source: any,
|
||||
startDate: any,
|
||||
endDate: any,
|
||||
title: string = search || ""
|
||||
) {
|
||||
return await getAPIInterceptor(
|
||||
`media/list?enablePage=1&sortBy=createdAt&sort=desc&size=${limit}&page=${page}&isForSelf=${isForSelf}&isApproval=${isApproval}&typeId=${fileTypeFilter}&statusId=${statusFilter}&needApprovalFromLevel=${needApprovalFromLevel}&title=${title}`
|
||||
);
|
||||
}
|
||||
|
||||
export async function listDataImage(
|
||||
limit: any,
|
||||
page: any,
|
||||
|
|
|
|||
Loading…
Reference in New Issue