Merge branch 'dev-anang' of https://gitlab.com/hanifsalafi/mediahub_redesign
This commit is contained in:
commit
e69d21baa3
|
|
@ -10,7 +10,7 @@ import { Label } from "@/components/ui/label";
|
|||
import ExternalDraggingevent from "./dragging-events";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Plus } from "lucide-react";
|
||||
import { Book, Plus } from "lucide-react";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { EventContentArg } from "@fullcalendar/core";
|
||||
import EventModal from "./event-modal";
|
||||
|
|
@ -25,6 +25,13 @@ import {
|
|||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
|
||||
const wait = () => new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
interface CalendarViewProps {
|
||||
|
|
@ -103,7 +110,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
const [selectedEventDate, setSelectedEventDate] = useState<Date | null>(null);
|
||||
const roleId = Number(getCookiesDecrypt("urie")) || 0;
|
||||
const [apiEvents, setApiEvents] = useState<CalendarEvent[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [Isloading, setLoading] = useState<boolean>(false);
|
||||
const [draggableInitialized, setDraggableInitialized] =
|
||||
useState<boolean>(false);
|
||||
const t = useTranslations("CalendarApp");
|
||||
|
|
@ -116,6 +123,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
const TODAY = dayjs().format("yyyy-MM-dd");
|
||||
const INITIAL_YEAR = dayjs().format("YYYY");
|
||||
const INITIAL_MONTH = dayjs().format("M");
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const [selectedYear, setSelectedYear] = useState(new Date());
|
||||
const [selectedMonth, setSelectedMonth] = useState(
|
||||
|
|
@ -503,13 +511,44 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
);
|
||||
};
|
||||
|
||||
const getModalContent = (type: "terkirim" | "diterima") => (
|
||||
<div className="overflow-x-auto overflow-y-auto ">
|
||||
{Isloading ? (
|
||||
<p>Loading...</p>
|
||||
) : (
|
||||
<table className="w-full border-collapse border border-gray-300">
|
||||
<thead>
|
||||
<tr className="bg-gray-100 border-b">
|
||||
<th className="px-4 py-2 text-left">No</th>
|
||||
<th className="px-4 py-2 text-left">Ticket Number</th>
|
||||
<th className="px-4 py-2 text-left">Date and Time</th>
|
||||
<th className="px-4 py-2 text-left">Title</th>
|
||||
<th className="px-4 py-2 text-left">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr key={""} className="border-b">
|
||||
<td className="px-4 py-2">{"1"}</td>
|
||||
<td className="px-4 py-2">{"MIA - 001"}</td>
|
||||
<td className="px-4 py-2">{"23/01/2025 13:00"}</td>
|
||||
<td className="px-4 py-2">{"Daily Issue 25 Januari 2025 "}</td>
|
||||
<td className="px-4 py-2">
|
||||
{type === "terkirim" ? "Completed" : "Completed"}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-12 gap-6 divide-x divide-border">
|
||||
<Card className="col-span-12 lg:col-span-4 2xl:col-span-3 pb-5">
|
||||
<CardContent className="p-0">
|
||||
<CardHeader className="border-none mb-2 pt-5">
|
||||
{roleId == 11 || roleId == 12 ? (
|
||||
{roleId == 11 || roleId == 2 || roleId == 12 ? (
|
||||
<Button
|
||||
onClick={handleDateClick}
|
||||
className="dark:bg-background dark:text-foreground"
|
||||
|
|
@ -520,6 +559,24 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
) : (
|
||||
""
|
||||
)}
|
||||
<div>
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{roleId == 2 ? (
|
||||
<Button className="dark:bg-background dark:text-foreground ml-2">
|
||||
<Book className="w-4 h-4 me-1" />
|
||||
Hasil Pantauan BAG PA
|
||||
</Button>
|
||||
) : null}
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px] overflow-y-auto max-h-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Hasil Pantauan</DialogTitle>
|
||||
</DialogHeader>
|
||||
{getModalContent("terkirim")}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<div className="px-3">
|
||||
|
|
|
|||
|
|
@ -1118,6 +1118,14 @@ const EventModal = ({
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{detailData && (
|
||||
<div>
|
||||
<Button variant={"outline"} color="primary">
|
||||
Kirim Ke Mabes
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-wrap gap-2 mt-10">
|
||||
<Button
|
||||
style={
|
||||
|
|
|
|||
|
|
@ -25,15 +25,23 @@ import {
|
|||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Search } from "lucide-react";
|
||||
import { ChevronDown, Search } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||
import { paginationBlog } from "@/service/blog/blog";
|
||||
import { getBlogCategory, paginationBlog } from "@/service/blog/blog";
|
||||
import { ticketingPagination } from "@/service/ticketing/ticketing";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import TablePagination from "@/components/table/table-pagination";
|
||||
import columns from "./columns";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { listEnableCategory } from "@/service/content/content";
|
||||
|
||||
const BlogTable = () => {
|
||||
const router = useRouter();
|
||||
|
|
@ -56,6 +64,12 @@ const BlogTable = () => {
|
|||
const [totalPage, setTotalPage] = React.useState(1);
|
||||
const [limit, setLimit] = React.useState(10);
|
||||
const [search, setSearch] = React.useState<string>("");
|
||||
const [categories, setCategories] = React.useState<any[]>([]);
|
||||
const [selectedCategories, setSelectedCategories] = React.useState<number[]>(
|
||||
[]
|
||||
);
|
||||
const [categoryFilter, setCategoryFilter] = React.useState<string>("");
|
||||
const [statusFilter, setStatusFilter] = React.useState<any[]>([]);
|
||||
|
||||
const table = useReactTable({
|
||||
data: dataTable,
|
||||
|
|
@ -87,11 +101,18 @@ const BlogTable = () => {
|
|||
|
||||
React.useEffect(() => {
|
||||
fetchData();
|
||||
}, [page, limit, search]);
|
||||
getCategories();
|
||||
}, [categoryFilter, statusFilter, page, limit, search]);
|
||||
|
||||
async function fetchData() {
|
||||
try {
|
||||
const res = await paginationBlog(limit, page - 1, search);
|
||||
const res = await paginationBlog(
|
||||
limit,
|
||||
page - 1,
|
||||
search,
|
||||
categoryFilter,
|
||||
statusFilter
|
||||
);
|
||||
const data = res?.data?.data;
|
||||
const contentData = data?.content;
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
|
|
@ -108,6 +129,40 @@ const BlogTable = () => {
|
|||
}
|
||||
}
|
||||
|
||||
async function getCategories() {
|
||||
const category = await getBlogCategory();
|
||||
const resCategory = category?.data?.data?.content;
|
||||
setCategories(resCategory || []);
|
||||
}
|
||||
|
||||
const handleCheckboxChange = (categoryId: number) => {
|
||||
setSelectedCategories(
|
||||
(prev: any) =>
|
||||
prev.includes(categoryId)
|
||||
? prev.filter((id: any) => id !== categoryId) // Hapus jika sudah dipilih
|
||||
: [...prev, categoryId] // Tambahkan jika belum dipilih
|
||||
);
|
||||
|
||||
// Perbarui filter kategori
|
||||
setCategoryFilter((prev) => {
|
||||
const updatedCategories = prev.split(",").filter(Boolean).map(Number);
|
||||
|
||||
const newCategories = updatedCategories.includes(categoryId)
|
||||
? updatedCategories.filter((id) => id !== categoryId)
|
||||
: [...updatedCategories, categoryId];
|
||||
|
||||
return newCategories.join(",");
|
||||
});
|
||||
};
|
||||
|
||||
function handleStatusCheckboxChange(value: any) {
|
||||
setStatusFilter((prev: any) =>
|
||||
prev.includes(value)
|
||||
? prev.filter((status: any) => status !== value)
|
||||
: [...prev, value]
|
||||
);
|
||||
}
|
||||
|
||||
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearch(e.target.value); // Perbarui state search
|
||||
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
|
||||
|
|
@ -130,17 +185,134 @@ const BlogTable = () => {
|
|||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
<div className="flex-none">
|
||||
<Input
|
||||
placeholder="Filter Status..."
|
||||
value={
|
||||
(table.getColumn("status")?.getFilterValue() as string) ?? ""
|
||||
}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
table.getColumn("status")?.setFilterValue(event.target.value)
|
||||
}
|
||||
className="max-w-sm "
|
||||
/>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<div className="flex items-center py-4">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="ml-auto" size="md">
|
||||
Filter <ChevronDown />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
className="w-64 h-[200px] overflow-y-auto"
|
||||
>
|
||||
<div className="flex flex-row justify-between my-1 mx-1">
|
||||
<p>Filter</p>
|
||||
{/* <p
|
||||
className="text-blue-600 cursor-pointer"
|
||||
onClick={fetchData}
|
||||
>
|
||||
Simpan
|
||||
</p> */}
|
||||
</div>
|
||||
<Label className="ml-2">Kategori</Label>
|
||||
{categories.length > 0 ? (
|
||||
categories.map((category) => (
|
||||
<div
|
||||
key={category.id}
|
||||
className="flex items-center px-4 py-1"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`category-${category.id}`}
|
||||
className="mr-2"
|
||||
checked={selectedCategories.includes(category.id)}
|
||||
onChange={() => handleCheckboxChange(category.id)}
|
||||
/>
|
||||
<label
|
||||
htmlFor={`category-${category.id}`}
|
||||
className="text-sm"
|
||||
>
|
||||
{category.name}
|
||||
</label>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<p className="text-sm text-gray-500 px-4 py-2">
|
||||
No categories found.
|
||||
</p>
|
||||
)}
|
||||
<Label className="ml-2 mt-2">Status</Label>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-2"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(1)}
|
||||
onChange={() => handleStatusCheckboxChange(1)}
|
||||
/>
|
||||
<label htmlFor="status-2" className="text-sm">
|
||||
Menunggu Review
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-2"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(2)}
|
||||
onChange={() => handleStatusCheckboxChange(2)}
|
||||
/>
|
||||
<label htmlFor="status-2" className="text-sm">
|
||||
Diterima
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-3"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(3)}
|
||||
onChange={() => handleStatusCheckboxChange(3)}
|
||||
/>
|
||||
<label htmlFor="status-3" className="text-sm">
|
||||
Minta Update
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-4"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(4)}
|
||||
onChange={() => handleStatusCheckboxChange(4)}
|
||||
/>
|
||||
<label htmlFor="status-4" className="text-sm">
|
||||
Ditolak
|
||||
</label>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<div className="flex items-center py-4">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="ml-auto" size="md">
|
||||
Columns <ChevronDown />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{table
|
||||
.getAllColumns()
|
||||
.filter((column) => column.getCanHide())
|
||||
.map((column) => {
|
||||
return (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={column.id}
|
||||
className="capitalize"
|
||||
checked={column.getIsVisible()}
|
||||
onCheckedChange={(value) =>
|
||||
column.toggleVisibility(!!value)
|
||||
}
|
||||
>
|
||||
{column.id}
|
||||
</DropdownMenuCheckboxItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Table className="overflow-hidden mt-3">
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ const TableAudio = () => {
|
|||
[]
|
||||
);
|
||||
const [categoryFilter, setCategoryFilter] = React.useState<string>("");
|
||||
const [statusFilter, setStatusFilter] = React.useState([]);
|
||||
const [statusFilter, setStatusFilter] = React.useState<any[]>([]);
|
||||
const [startDate, setStartDate] = React.useState("");
|
||||
const [endDate, setEndDate] = React.useState("");
|
||||
const [filterByCreator, setFilterByCreator] = React.useState("");
|
||||
|
|
@ -131,7 +131,7 @@ const TableAudio = () => {
|
|||
React.useEffect(() => {
|
||||
fetchData();
|
||||
getCategories();
|
||||
}, [categoryFilter, page, limit, search, startDate, endDate]);
|
||||
}, [categoryFilter, statusFilter, page, limit, search, startDate, endDate]);
|
||||
|
||||
async function getCategories() {
|
||||
const category = await listEnableCategory("4");
|
||||
|
|
@ -175,9 +175,7 @@ const TableAudio = () => {
|
|||
isForSelf,
|
||||
!isForSelf,
|
||||
categoryFilter,
|
||||
statusFilter?.sort().join(",").includes("1")
|
||||
? "1,2"
|
||||
: statusFilter?.sort().join(","),
|
||||
statusFilter,
|
||||
statusFilter?.sort().join(",").includes("1") ? userLevelId : "",
|
||||
filterByCreator,
|
||||
filterBySource,
|
||||
|
|
@ -214,6 +212,14 @@ const TableAudio = () => {
|
|||
fetchData(); // Panggil ulang data dengan filter baru
|
||||
};
|
||||
|
||||
function handleStatusCheckboxChange(value: any) {
|
||||
setStatusFilter((prev: any) =>
|
||||
prev.includes(value)
|
||||
? prev.filter((status: any) => status !== value)
|
||||
: [...prev, value]
|
||||
);
|
||||
}
|
||||
|
||||
const handleSearchFilterByCreator = (
|
||||
e: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
|
|
@ -324,22 +330,54 @@ const TableAudio = () => {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div className="mx-2 my-1">
|
||||
<Label>Status</Label>
|
||||
<Input
|
||||
placeholder="Filter Status..."
|
||||
value={
|
||||
(table
|
||||
.getColumn("statusName")
|
||||
?.getFilterValue() as string) ?? ""
|
||||
}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
table
|
||||
.getColumn("statusName")
|
||||
?.setFilterValue(event.target.value)
|
||||
}
|
||||
className="max-w-sm "
|
||||
<Label className="ml-2 mt-2">Status</Label>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-2"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(1)}
|
||||
onChange={() => handleStatusCheckboxChange(1)}
|
||||
/>
|
||||
<label htmlFor="status-2" className="text-sm">
|
||||
Menunggu Review
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-2"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(2)}
|
||||
onChange={() => handleStatusCheckboxChange(2)}
|
||||
/>
|
||||
<label htmlFor="status-2" className="text-sm">
|
||||
Diterima
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-3"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(3)}
|
||||
onChange={() => handleStatusCheckboxChange(3)}
|
||||
/>
|
||||
<label htmlFor="status-3" className="text-sm">
|
||||
Minta Update
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-4"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(4)}
|
||||
onChange={() => handleStatusCheckboxChange(4)}
|
||||
/>
|
||||
<label htmlFor="status-4" className="text-sm">
|
||||
Ditolak
|
||||
</label>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const ReactTableAudioPage = () => {
|
|||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
<Card className="py-4 px-3">
|
||||
<div className="flex flex-row justify-between items-center px-5">
|
||||
<div className="flex flex-wrap justify-between items-center px-5">
|
||||
<div className="flex flex-row items-center text-xl font-medium text-default-900 gap-2">
|
||||
<div>
|
||||
<Icon icon="icon-park-outline:check-one" />
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ const TableImage = () => {
|
|||
[]
|
||||
);
|
||||
const [categoryFilter, setCategoryFilter] = React.useState<string>("");
|
||||
const [statusFilter, setStatusFilter] = React.useState([]);
|
||||
const [statusFilter, setStatusFilter] = React.useState<any[]>([]);
|
||||
const [startDate, setStartDate] = React.useState("");
|
||||
const [endDate, setEndDate] = React.useState("");
|
||||
const [filterByCreator, setFilterByCreator] = React.useState("");
|
||||
|
|
@ -137,7 +137,7 @@ const TableImage = () => {
|
|||
// Panggil fetchData saat filter kategori berubah
|
||||
fetchData();
|
||||
getCategories();
|
||||
}, [categoryFilter, page, limit, search, startDate, endDate]);
|
||||
}, [categoryFilter, statusFilter, page, limit, search, startDate, endDate]);
|
||||
|
||||
async function getCategories() {
|
||||
const category = await listEnableCategory("1");
|
||||
|
|
@ -181,9 +181,7 @@ const TableImage = () => {
|
|||
isForSelf,
|
||||
!isForSelf,
|
||||
categoryFilter,
|
||||
statusFilter?.sort().join(",").includes("1")
|
||||
? "1,2"
|
||||
: statusFilter?.sort().join(","),
|
||||
statusFilter,
|
||||
statusFilter?.sort().join(",").includes("1") ? userLevelId : "",
|
||||
filterByCreator,
|
||||
filterBySource,
|
||||
|
|
@ -220,6 +218,14 @@ const TableImage = () => {
|
|||
fetchData(); // Panggil ulang data dengan filter baru
|
||||
};
|
||||
|
||||
function handleStatusCheckboxChange(value: any) {
|
||||
setStatusFilter((prev: any) =>
|
||||
prev.includes(value)
|
||||
? prev.filter((status: any) => status !== value)
|
||||
: [...prev, value]
|
||||
);
|
||||
}
|
||||
|
||||
const handleSearchFilterByCreator = (
|
||||
e: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
|
|
@ -330,22 +336,54 @@ const TableImage = () => {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div className="mx-2 my-1">
|
||||
<Label>Status</Label>
|
||||
<Input
|
||||
placeholder="Filter Status..."
|
||||
value={
|
||||
(table
|
||||
.getColumn("statusName")
|
||||
?.getFilterValue() as string) ?? ""
|
||||
}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
table
|
||||
.getColumn("statusName")
|
||||
?.setFilterValue(event.target.value)
|
||||
}
|
||||
className="max-w-sm "
|
||||
<Label className="ml-2 mt-2">Status</Label>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-2"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(1)}
|
||||
onChange={() => handleStatusCheckboxChange(1)}
|
||||
/>
|
||||
<label htmlFor="status-2" className="text-sm">
|
||||
Menunggu Review
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-2"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(2)}
|
||||
onChange={() => handleStatusCheckboxChange(2)}
|
||||
/>
|
||||
<label htmlFor="status-2" className="text-sm">
|
||||
Diterima
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-3"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(3)}
|
||||
onChange={() => handleStatusCheckboxChange(3)}
|
||||
/>
|
||||
<label htmlFor="status-3" className="text-sm">
|
||||
Minta Update
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-4"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(4)}
|
||||
onChange={() => handleStatusCheckboxChange(4)}
|
||||
/>
|
||||
<label htmlFor="status-4" className="text-sm">
|
||||
Ditolak
|
||||
</label>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const ReactTableImagePage = () => {
|
|||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
<Card className="py-4 px-3">
|
||||
<div className="flex flex-row justify-between items-center px-5">
|
||||
<div className="flex flex-wrap justify-between items-center px-5">
|
||||
<div className="flex flex-row items-center text-xl font-medium text-default-900 gap-2">
|
||||
<div>
|
||||
<Icon icon="icon-park-outline:check-one" />
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ const TableTeks = () => {
|
|||
[]
|
||||
);
|
||||
const [categoryFilter, setCategoryFilter] = React.useState<string>("");
|
||||
const [statusFilter, setStatusFilter] = React.useState([]);
|
||||
const [statusFilter, setStatusFilter] = React.useState<any[]>([]);
|
||||
const [startDate, setStartDate] = React.useState("");
|
||||
const [endDate, setEndDate] = React.useState("");
|
||||
const [filterByCreator, setFilterByCreator] = React.useState("");
|
||||
|
|
@ -130,7 +130,7 @@ const TableTeks = () => {
|
|||
React.useEffect(() => {
|
||||
fetchData();
|
||||
getCategories();
|
||||
}, [categoryFilter, page, limit, search, startDate, endDate]);
|
||||
}, [categoryFilter, statusFilter, page, limit, search, startDate, endDate]);
|
||||
|
||||
async function getCategories() {
|
||||
const category = await listEnableCategory("3");
|
||||
|
|
@ -174,9 +174,7 @@ const TableTeks = () => {
|
|||
isForSelf,
|
||||
!isForSelf,
|
||||
categoryFilter,
|
||||
statusFilter?.sort().join(",").includes("1")
|
||||
? "1,2"
|
||||
: statusFilter?.sort().join(","),
|
||||
statusFilter,
|
||||
statusFilter?.sort().join(",").includes("1") ? userLevelId : "",
|
||||
filterByCreator,
|
||||
filterBySource,
|
||||
|
|
@ -213,6 +211,14 @@ const TableTeks = () => {
|
|||
fetchData(); // Panggil ulang data dengan filter baru
|
||||
};
|
||||
|
||||
function handleStatusCheckboxChange(value: any) {
|
||||
setStatusFilter((prev: any) =>
|
||||
prev.includes(value)
|
||||
? prev.filter((status: any) => status !== value)
|
||||
: [...prev, value]
|
||||
);
|
||||
}
|
||||
|
||||
const handleSearchFilterByCreator = (
|
||||
e: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
|
|
@ -323,22 +329,54 @@ const TableTeks = () => {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div className="mx-2 my-1">
|
||||
<Label>Status</Label>
|
||||
<Input
|
||||
placeholder="Filter Status..."
|
||||
value={
|
||||
(table
|
||||
.getColumn("statusName")
|
||||
?.getFilterValue() as string) ?? ""
|
||||
}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
table
|
||||
.getColumn("statusName")
|
||||
?.setFilterValue(event.target.value)
|
||||
}
|
||||
className="max-w-sm "
|
||||
<Label className="ml-2 mt-2">Status</Label>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-2"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(1)}
|
||||
onChange={() => handleStatusCheckboxChange(1)}
|
||||
/>
|
||||
<label htmlFor="status-2" className="text-sm">
|
||||
Menunggu Review
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-2"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(2)}
|
||||
onChange={() => handleStatusCheckboxChange(2)}
|
||||
/>
|
||||
<label htmlFor="status-2" className="text-sm">
|
||||
Diterima
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-3"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(3)}
|
||||
onChange={() => handleStatusCheckboxChange(3)}
|
||||
/>
|
||||
<label htmlFor="status-3" className="text-sm">
|
||||
Minta Update
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-4"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(4)}
|
||||
onChange={() => handleStatusCheckboxChange(4)}
|
||||
/>
|
||||
<label htmlFor="status-4" className="text-sm">
|
||||
Ditolak
|
||||
</label>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const ReactTableTeksPage = () => {
|
|||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
<Card className="py-4 px-3">
|
||||
<div className="flex flex-row justify-between items-center px-5">
|
||||
<div className="flex flex-wrap justify-between items-center px-5">
|
||||
<div className="flex flex-row items-center text-xl font-medium text-default-900 gap-2">
|
||||
<div>
|
||||
<Icon icon="icon-park-outline:check-one" />
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ const TableVideo = () => {
|
|||
[]
|
||||
);
|
||||
const [categoryFilter, setCategoryFilter] = React.useState<string>("");
|
||||
const [statusFilter, setStatusFilter] = React.useState([]);
|
||||
const [statusFilter, setStatusFilter] = React.useState<any[]>([]);
|
||||
const [startDate, setStartDate] = React.useState("");
|
||||
const [endDate, setEndDate] = React.useState("");
|
||||
const [filterByCreator, setFilterByCreator] = React.useState("");
|
||||
|
|
@ -130,7 +130,7 @@ const TableVideo = () => {
|
|||
React.useEffect(() => {
|
||||
fetchData();
|
||||
getCategories();
|
||||
}, [categoryFilter, page, limit, search, startDate, endDate]);
|
||||
}, [categoryFilter, statusFilter, page, limit, search, startDate, endDate]);
|
||||
|
||||
async function getCategories() {
|
||||
const category = await listEnableCategory("2");
|
||||
|
|
@ -174,9 +174,7 @@ const TableVideo = () => {
|
|||
isForSelf,
|
||||
!isForSelf,
|
||||
categoryFilter,
|
||||
statusFilter?.sort().join(",").includes("1")
|
||||
? "1,2"
|
||||
: statusFilter?.sort().join(","),
|
||||
statusFilter,
|
||||
statusFilter?.sort().join(",").includes("1") ? userLevelId : "",
|
||||
filterByCreator,
|
||||
filterBySource,
|
||||
|
|
@ -213,6 +211,14 @@ const TableVideo = () => {
|
|||
fetchData(); // Panggil ulang data dengan filter baru
|
||||
};
|
||||
|
||||
function handleStatusCheckboxChange(value: any) {
|
||||
setStatusFilter((prev: any) =>
|
||||
prev.includes(value)
|
||||
? prev.filter((status: any) => status !== value)
|
||||
: [...prev, value]
|
||||
);
|
||||
}
|
||||
|
||||
const handleSearchFilterByCreator = (
|
||||
e: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
|
|
@ -324,22 +330,54 @@ const TableVideo = () => {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div className="mx-2 my-1">
|
||||
<Label>Status</Label>
|
||||
<Input
|
||||
placeholder="Filter Status..."
|
||||
value={
|
||||
(table
|
||||
.getColumn("statusName")
|
||||
?.getFilterValue() as string) ?? ""
|
||||
}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
table
|
||||
.getColumn("statusName")
|
||||
?.setFilterValue(event.target.value)
|
||||
}
|
||||
className="max-w-sm "
|
||||
<Label className="ml-2 mt-2">Status</Label>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-2"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(1)}
|
||||
onChange={() => handleStatusCheckboxChange(1)}
|
||||
/>
|
||||
<label htmlFor="status-2" className="text-sm">
|
||||
Menunggu Review
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-2"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(2)}
|
||||
onChange={() => handleStatusCheckboxChange(2)}
|
||||
/>
|
||||
<label htmlFor="status-2" className="text-sm">
|
||||
Diterima
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-3"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(3)}
|
||||
onChange={() => handleStatusCheckboxChange(3)}
|
||||
/>
|
||||
<label htmlFor="status-3" className="text-sm">
|
||||
Minta Update
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-4"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(4)}
|
||||
onChange={() => handleStatusCheckboxChange(4)}
|
||||
/>
|
||||
<label htmlFor="status-4" className="text-sm">
|
||||
Ditolak
|
||||
</label>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const ReactTableVideoPage = () => {
|
|||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
<Card className="py-4 px-3">
|
||||
<div className="flex flex-row justify-between items-center px-5">
|
||||
<div className="flex flex-wrap justify-between items-center px-5">
|
||||
<div className="flex flex-row items-center text-xl font-medium text-default-900 gap-2">
|
||||
<div>
|
||||
<Icon icon="icon-park-outline:check-one" />
|
||||
|
|
|
|||
|
|
@ -176,8 +176,8 @@ const TaskTable = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center px-5">
|
||||
<div>
|
||||
<div className="flex flex-col sm:flex-row lg:flex-row justify-between lg:items-center px-5">
|
||||
<div className="mb-3 sm:mb-0 lg-mb-0">
|
||||
<InputGroup merged>
|
||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||
<Search className=" h-4 w-4 dark:text-white" />
|
||||
|
|
@ -185,7 +185,7 @@ const TaskTable = () => {
|
|||
<Input
|
||||
type="text"
|
||||
placeholder="Search Judul..."
|
||||
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white w-full"
|
||||
value={search}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ const TaskPage = () => {
|
|||
<Card>
|
||||
<CardHeader className="border-b border-solid border-default-200 mb-6">
|
||||
<CardTitle>
|
||||
<div className="flex items-center">
|
||||
<div className="flex flex-col sm:flex-row lg:flex-row lg:items-center">
|
||||
<div className="flex-1 text-xl font-medium text-default-900">
|
||||
Table Penugasan
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,129 @@
|
|||
"use client";
|
||||
import { Link } from "@/components/navigation";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "@/components/ui/carousel";
|
||||
import { listCuratedContent } from "@/service/curated-content/curated-content";
|
||||
import { getListContent } from "@/service/landing/landing";
|
||||
import {
|
||||
formatDateToIndonesian,
|
||||
generateLocalizedPath,
|
||||
textEllipsis,
|
||||
} from "@/utils/globals";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { useParams, usePathname, useRouter } from "next/navigation";
|
||||
import React, { Component, useEffect, useState } from "react";
|
||||
|
||||
const AudioAll = () => {
|
||||
const [audioData, setAudioData] = useState<any>();
|
||||
const [displayAudio, setDisplayAudio] = useState<any[]>([]);
|
||||
const [page, setPage] = useState(1);
|
||||
const [limit, setLimit] = React.useState(10);
|
||||
const [search, setSearch] = React.useState("");
|
||||
|
||||
useEffect(() => {
|
||||
initFetch();
|
||||
}, [page, limit, search]);
|
||||
|
||||
useEffect(() => {
|
||||
if (audioData?.length > 0) {
|
||||
shuffleAndSetVideos();
|
||||
const interval = setInterval(shuffleAndSetVideos, 5000);
|
||||
return () => clearInterval(interval); // Cleanup interval on unmount
|
||||
}
|
||||
}, [audioData]);
|
||||
|
||||
const initFetch = async () => {
|
||||
const response = await listCuratedContent(search, limit, page - 1, 4, "1");
|
||||
console.log(response);
|
||||
|
||||
const data = response?.data?.data;
|
||||
const contentData = data?.content;
|
||||
setAudioData(contentData);
|
||||
};
|
||||
|
||||
const shuffleAndSetVideos = () => {
|
||||
const shuffled = shuffleArray([...audioData]);
|
||||
setDisplayAudio(shuffled.slice(0, 3));
|
||||
};
|
||||
|
||||
const shuffleArray = (array: any[]) => {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
return array;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-3 px-5">
|
||||
<div className=" grid grid-cols-1 gap-6 ">
|
||||
{displayAudio?.map((audio: any) => (
|
||||
<Link
|
||||
href={`/shared/curated-content//giat-routine/audio/detail/${audio.id}`}
|
||||
key={audio?.id}
|
||||
className="flex flex-col sm:flex-row items-center hover:scale-100 transition-transform duration-300 bg-white dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full"
|
||||
>
|
||||
<div className="flex items-center justify-center bg-red-500 text-white rounded-lg w-16 h-16">
|
||||
<svg
|
||||
width="32"
|
||||
height="34"
|
||||
viewBox="0 0 32 34"
|
||||
fill="null"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M23.404 0.452014C23.7033 0.35857 24.0204 0.336816 24.3297 0.388509C24.639 0.440203 24.9318 0.563895 25.1845 0.749599C25.4371 0.935304 25.6426 1.17782 25.7843 1.45756C25.9259 1.73731 25.9998 2.04644 26 2.36001V14.414C25.3462 14.2296 24.6766 14.1064 24 14.046V8.36001L10 12.736V27C10 28.1264 9.6197 29.2197 8.92071 30.1029C8.22172 30.9861 7.24499 31.6075 6.14877 31.8663C5.05255 32.125 3.90107 32.0061 2.88089 31.5287C1.86071 31.0514 1.03159 30.2435 0.52787 29.2361C0.024152 28.2286 -0.124656 27.0806 0.105556 25.9781C0.335768 24.8755 0.931513 23.883 1.79627 23.1613C2.66102 22.4396 3.74413 22.031 4.87009 22.0017C5.99606 21.9724 7.09893 22.3242 8.00001 23V6.73601C7.99982 6.30956 8.13596 5.8942 8.38854 5.55059C8.64112 5.20698 8.99692 4.9531 9.40401 4.82601L23.404 0.452014ZM10 10.64L24 6.26601V2.36001L10 6.73601V10.64ZM5.00001 24C4.20436 24 3.44129 24.3161 2.87869 24.8787C2.31608 25.4413 2.00001 26.2044 2.00001 27C2.00001 27.7957 2.31608 28.5587 2.87869 29.1213C3.44129 29.6839 4.20436 30 5.00001 30C5.79566 30 6.55872 29.6839 7.12133 29.1213C7.68394 28.5587 8.00001 27.7957 8.00001 27C8.00001 26.2044 7.68394 25.4413 7.12133 24.8787C6.55872 24.3161 5.79566 24 5.00001 24ZM32 25C32 27.387 31.0518 29.6761 29.364 31.364C27.6761 33.0518 25.387 34 23 34C20.6131 34 18.3239 33.0518 16.636 31.364C14.9482 29.6761 14 27.387 14 25C14 22.6131 14.9482 20.3239 16.636 18.6361C18.3239 16.9482 20.6131 16 23 16C25.387 16 27.6761 16.9482 29.364 18.6361C31.0518 20.3239 32 22.6131 32 25ZM27.47 24.128L21.482 20.828C21.3298 20.7443 21.1583 20.7016 20.9846 20.7043C20.8108 20.707 20.6408 20.7549 20.4912 20.8433C20.3416 20.9317 20.2176 21.0576 20.1315 21.2086C20.0453 21.3595 20 21.5302 20 21.704V28.304C20 28.4778 20.0453 28.6486 20.1315 28.7995C20.2176 28.9504 20.3416 29.0763 20.4912 29.1647C20.6408 29.2531 20.8108 29.301 20.9846 29.3037C21.1583 29.3064 21.3298 29.2638 21.482 29.18L27.47 25.88C27.6268 25.7937 27.7575 25.6669 27.8486 25.5128C27.9397 25.3587 27.9877 25.183 27.9877 25.004C27.9877 24.825 27.9397 24.6493 27.8486 24.4952C27.7575 24.3412 27.6268 24.2143 27.47 24.128Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col flex-1">
|
||||
<div className="text-gray-500 dark:text-gray-400 flex flex-row text-sm">
|
||||
{formatDateToIndonesian(new Date(audio?.createdAt))}{" "}
|
||||
{audio?.timezone ? audio?.timezone : "WIB"} |{" "}
|
||||
<Icon icon="formkit:eye" width="15" height="15" /> 518
|
||||
</div>
|
||||
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm">
|
||||
{audio?.title}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<div className="mt-2">
|
||||
<img src="/assets/wave.svg" className="w-80" />
|
||||
</div>
|
||||
<div className="flex flex-row items-center justify-center text-gray-500 dark:text-gray-400">
|
||||
<img
|
||||
src="/assets/audio-icon.png"
|
||||
alt="#"
|
||||
className="flex items-center justify-center"
|
||||
/>
|
||||
<div className="flex mx-2 items-center justify-center">
|
||||
{audio?.duration}
|
||||
</div>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fill="#f00"
|
||||
d="M7.707 10.293a1 1 0 1 0-1.414 1.414l3 3a1 1 0 0 0 1.414 0l3-3a1 1 0 0 0-1.414-1.414L11 11.586V6h5a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h5v5.586zM9 4a1 1 0 0 1 2 0v2H9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AudioAll;
|
||||
|
|
@ -23,6 +23,7 @@ import AudioSliderPage from "../../audio/audio";
|
|||
import TeksSliderPage from "../../document/teks";
|
||||
import ImageSliderPage from "../../image/image";
|
||||
import VideoSliderPage from "../../video/audio-visual";
|
||||
import AudioAll from "./audio";
|
||||
|
||||
const AudioAllPage = () => {
|
||||
return (
|
||||
|
|
@ -56,7 +57,7 @@ const AudioAllPage = () => {
|
|||
<Label className="text-base">Audio</Label>
|
||||
</div>
|
||||
<div className="px-5 my-5">
|
||||
<AudioSliderPage />
|
||||
<AudioAll />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
textEllipsis,
|
||||
} from "@/utils/globals";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { hasData } from "jquery";
|
||||
import { useParams, usePathname, useRouter } from "next/navigation";
|
||||
import React, { Component, useEffect, useState } from "react";
|
||||
|
||||
|
|
@ -24,18 +25,18 @@ const AudioSliderPage = () => {
|
|||
const [page, setPage] = useState(1);
|
||||
const [limit, setLimit] = React.useState(10);
|
||||
const [search, setSearch] = React.useState("");
|
||||
|
||||
const [hasData, setHasData] = useState(false);
|
||||
useEffect(() => {
|
||||
initFetch();
|
||||
}, [page, limit, search]);
|
||||
|
||||
useEffect(() => {
|
||||
if (audioData?.length > 0) {
|
||||
shuffleAndSetVideos();
|
||||
const interval = setInterval(shuffleAndSetVideos, 5000);
|
||||
return () => clearInterval(interval); // Cleanup interval on unmount
|
||||
}
|
||||
}, [audioData]);
|
||||
// useEffect(() => {
|
||||
// if (audioData?.length > 0) {
|
||||
// shuffleAndSetVideos();
|
||||
// const interval = setInterval(shuffleAndSetVideos, 5000);
|
||||
// return () => clearInterval(interval); // Cleanup interval on unmount
|
||||
// }
|
||||
// }, [audioData]);
|
||||
|
||||
const initFetch = async () => {
|
||||
const response = await listCuratedContent(search, limit, page - 1, 4, "1");
|
||||
|
|
@ -43,13 +44,14 @@ const AudioSliderPage = () => {
|
|||
|
||||
const data = response?.data?.data;
|
||||
const contentData = data?.content;
|
||||
setAudioData(contentData);
|
||||
setHasData(displayAudio && displayAudio.length > 0);
|
||||
setDisplayAudio(contentData);
|
||||
};
|
||||
|
||||
const shuffleAndSetVideos = () => {
|
||||
const shuffled = shuffleArray([...audioData]);
|
||||
setDisplayAudio(shuffled.slice(0, 3));
|
||||
};
|
||||
// const shuffleAndSetVideos = () => {
|
||||
// const shuffled = shuffleArray([...audioData]);
|
||||
// setDisplayAudio(shuffled.slice(0, 3));
|
||||
// };
|
||||
|
||||
const shuffleArray = (array: any[]) => {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
|
|
@ -60,69 +62,46 @@ const AudioSliderPage = () => {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="mx-3 px-5">
|
||||
<div className=" grid grid-cols-1 gap-6 ">
|
||||
{displayAudio?.map((audio: any) => (
|
||||
<Link
|
||||
href={`/shared/curated-content//giat-routine/audio/detail/${audio.id}`}
|
||||
key={audio?.id}
|
||||
className="flex flex-col sm:flex-row items-center hover:scale-100 transition-transform duration-300 bg-white dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full"
|
||||
>
|
||||
<div className="flex items-center justify-center bg-red-500 text-white rounded-lg w-16 h-16">
|
||||
<svg
|
||||
width="32"
|
||||
height="34"
|
||||
viewBox="0 0 32 34"
|
||||
fill="null"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M23.404 0.452014C23.7033 0.35857 24.0204 0.336816 24.3297 0.388509C24.639 0.440203 24.9318 0.563895 25.1845 0.749599C25.4371 0.935304 25.6426 1.17782 25.7843 1.45756C25.9259 1.73731 25.9998 2.04644 26 2.36001V14.414C25.3462 14.2296 24.6766 14.1064 24 14.046V8.36001L10 12.736V27C10 28.1264 9.6197 29.2197 8.92071 30.1029C8.22172 30.9861 7.24499 31.6075 6.14877 31.8663C5.05255 32.125 3.90107 32.0061 2.88089 31.5287C1.86071 31.0514 1.03159 30.2435 0.52787 29.2361C0.024152 28.2286 -0.124656 27.0806 0.105556 25.9781C0.335768 24.8755 0.931513 23.883 1.79627 23.1613C2.66102 22.4396 3.74413 22.031 4.87009 22.0017C5.99606 21.9724 7.09893 22.3242 8.00001 23V6.73601C7.99982 6.30956 8.13596 5.8942 8.38854 5.55059C8.64112 5.20698 8.99692 4.9531 9.40401 4.82601L23.404 0.452014ZM10 10.64L24 6.26601V2.36001L10 6.73601V10.64ZM5.00001 24C4.20436 24 3.44129 24.3161 2.87869 24.8787C2.31608 25.4413 2.00001 26.2044 2.00001 27C2.00001 27.7957 2.31608 28.5587 2.87869 29.1213C3.44129 29.6839 4.20436 30 5.00001 30C5.79566 30 6.55872 29.6839 7.12133 29.1213C7.68394 28.5587 8.00001 27.7957 8.00001 27C8.00001 26.2044 7.68394 25.4413 7.12133 24.8787C6.55872 24.3161 5.79566 24 5.00001 24ZM32 25C32 27.387 31.0518 29.6761 29.364 31.364C27.6761 33.0518 25.387 34 23 34C20.6131 34 18.3239 33.0518 16.636 31.364C14.9482 29.6761 14 27.387 14 25C14 22.6131 14.9482 20.3239 16.636 18.6361C18.3239 16.9482 20.6131 16 23 16C25.387 16 27.6761 16.9482 29.364 18.6361C31.0518 20.3239 32 22.6131 32 25ZM27.47 24.128L21.482 20.828C21.3298 20.7443 21.1583 20.7016 20.9846 20.7043C20.8108 20.707 20.6408 20.7549 20.4912 20.8433C20.3416 20.9317 20.2176 21.0576 20.1315 21.2086C20.0453 21.3595 20 21.5302 20 21.704V28.304C20 28.4778 20.0453 28.6486 20.1315 28.7995C20.2176 28.9504 20.3416 29.0763 20.4912 29.1647C20.6408 29.2531 20.8108 29.301 20.9846 29.3037C21.1583 29.3064 21.3298 29.2638 21.482 29.18L27.47 25.88C27.6268 25.7937 27.7575 25.6669 27.8486 25.5128C27.9397 25.3587 27.9877 25.183 27.9877 25.004C27.9877 24.825 27.9397 24.6493 27.8486 24.4952C27.7575 24.3412 27.6268 24.2143 27.47 24.128Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col flex-1">
|
||||
<div className="text-gray-500 dark:text-gray-400 flex flex-row text-sm">
|
||||
{formatDateToIndonesian(new Date(audio?.createdAt))}{" "}
|
||||
{audio?.timezone ? audio?.timezone : "WIB"} |{" "}
|
||||
<Icon icon="formkit:eye" width="15" height="15" /> 518
|
||||
</div>
|
||||
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm">
|
||||
{audio?.title}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<div className="mt-2">
|
||||
<img src="/assets/wave.svg" className="w-80" />
|
||||
</div>
|
||||
<div className="flex flex-row items-center justify-center text-gray-500 dark:text-gray-400">
|
||||
<img
|
||||
src="/assets/audio-icon.png"
|
||||
alt="#"
|
||||
className="flex items-center justify-center"
|
||||
/>
|
||||
<div className="flex mx-2 items-center justify-center">
|
||||
{audio?.duration}
|
||||
</div>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
<Carousel className="w-full pr-3">
|
||||
<CarouselContent>
|
||||
{Array.from({ length: 5 }).map((_, index) => (
|
||||
<CarouselItem key={index}>
|
||||
<div className="p-1 flex flex-row md:basis-1/2 lg:basis-1/2 gap-3">
|
||||
{displayAudio?.map((audio: any) => (
|
||||
<Link
|
||||
href={`/shared/curated-content//giat-routine/audio/detail/${audio.id}`}
|
||||
key={audio?.id}
|
||||
className="flex flex-col sm:flex-row items-center hover:scale-100 transition-transform duration-300 bg-white dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full"
|
||||
>
|
||||
<path
|
||||
fill="#f00"
|
||||
d="M7.707 10.293a1 1 0 1 0-1.414 1.414l3 3a1 1 0 0 0 1.414 0l3-3a1 1 0 0 0-1.414-1.414L11 11.586V6h5a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h5v5.586zM9 4a1 1 0 0 1 2 0v2H9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="flex items-center justify-center bg-red-500 text-white rounded-lg w-12 h-12">
|
||||
<svg
|
||||
width="28"
|
||||
height="28"
|
||||
viewBox="0 0 32 34"
|
||||
fill="null"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M23.404 0.452014C23.7033 0.35857 24.0204 0.336816 24.3297 0.388509C24.639 0.440203 24.9318 0.563895 25.1845 0.749599C25.4371 0.935304 25.6426 1.17782 25.7843 1.45756C25.9259 1.73731 25.9998 2.04644 26 2.36001V14.414C25.3462 14.2296 24.6766 14.1064 24 14.046V8.36001L10 12.736V27C10 28.1264 9.6197 29.2197 8.92071 30.1029C8.22172 30.9861 7.24499 31.6075 6.14877 31.8663C5.05255 32.125 3.90107 32.0061 2.88089 31.5287C1.86071 31.0514 1.03159 30.2435 0.52787 29.2361C0.024152 28.2286 -0.124656 27.0806 0.105556 25.9781C0.335768 24.8755 0.931513 23.883 1.79627 23.1613C2.66102 22.4396 3.74413 22.031 4.87009 22.0017C5.99606 21.9724 7.09893 22.3242 8.00001 23V6.73601C7.99982 6.30956 8.13596 5.8942 8.38854 5.55059C8.64112 5.20698 8.99692 4.9531 9.40401 4.82601L23.404 0.452014ZM10 10.64L24 6.26601V2.36001L10 6.73601V10.64ZM5.00001 24C4.20436 24 3.44129 24.3161 2.87869 24.8787C2.31608 25.4413 2.00001 26.2044 2.00001 27C2.00001 27.7957 2.31608 28.5587 2.87869 29.1213C3.44129 29.6839 4.20436 30 5.00001 30C5.79566 30 6.55872 29.6839 7.12133 29.1213C7.68394 28.5587 8.00001 27.7957 8.00001 27C8.00001 26.2044 7.68394 25.4413 7.12133 24.8787C6.55872 24.3161 5.79566 24 5.00001 24ZM32 25C32 27.387 31.0518 29.6761 29.364 31.364C27.6761 33.0518 25.387 34 23 34C20.6131 34 18.3239 33.0518 16.636 31.364C14.9482 29.6761 14 27.387 14 25C14 22.6131 14.9482 20.3239 16.636 18.6361C18.3239 16.9482 20.6131 16 23 16C25.387 16 27.6761 16.9482 29.364 18.6361C31.0518 20.3239 32 22.6131 32 25ZM27.47 24.128L21.482 20.828C21.3298 20.7443 21.1583 20.7016 20.9846 20.7043C20.8108 20.707 20.6408 20.7549 20.4912 20.8433C20.3416 20.9317 20.2176 21.0576 20.1315 21.2086C20.0453 21.3595 20 21.5302 20 21.704V28.304C20 28.4778 20.0453 28.6486 20.1315 28.7995C20.2176 28.9504 20.3416 29.0763 20.4912 29.1647C20.6408 29.2531 20.8108 29.301 20.9846 29.3037C21.1583 29.3064 21.3298 29.2638 21.482 29.18L27.47 25.88C27.6268 25.7937 27.7575 25.6669 27.8486 25.5128C27.9397 25.3587 27.9877 25.183 27.9877 25.004C27.9877 24.825 27.9397 24.6493 27.8486 24.4952C27.7575 24.3412 27.6268 24.2143 27.47 24.128Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col flex-1">
|
||||
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm">
|
||||
{audio?.title}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</Link>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -21,8 +21,20 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|||
import Cookies from "js-cookie";
|
||||
import { postBlog } from "@/service/blog/blog";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { DotSquare, InboxIcon, PaperclipIcon, SmileIcon } from "lucide-react";
|
||||
import { detailMedia } from "@/service/curated-content/curated-content";
|
||||
import {
|
||||
DotSquare,
|
||||
InboxIcon,
|
||||
Music,
|
||||
PaperclipIcon,
|
||||
SmileIcon,
|
||||
TrashIcon,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
deleteMediaCurationMessage,
|
||||
detailMedia,
|
||||
getMediaCurationMessage,
|
||||
saveMediaCurationMessage,
|
||||
} from "@/service/curated-content/curated-content";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import "swiper/css";
|
||||
import "swiper/css/free-mode";
|
||||
|
|
@ -33,6 +45,15 @@ import "swiper/css";
|
|||
import "swiper/css/navigation";
|
||||
import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
|
||||
import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
||||
import WavesurferPlayer from "@wavesurfer/react";
|
||||
import WaveSurfer from "wavesurfer.js";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { htmlToString } from "@/utils/globals";
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
import { loading } from "@/lib/swal";
|
||||
import { formatDate } from "@fullcalendar/core/index.js";
|
||||
|
||||
const detailSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -69,6 +90,13 @@ export type curationDetail = {
|
|||
userGroupId: number;
|
||||
};
|
||||
};
|
||||
publishedFor: string; // ID for selected radio button
|
||||
publishedForObject: {
|
||||
id: number;
|
||||
name: string;
|
||||
isInternal: boolean;
|
||||
code: string;
|
||||
}[];
|
||||
tags: string;
|
||||
provinceId: string;
|
||||
is_active: string;
|
||||
|
|
@ -119,10 +147,14 @@ export default function DetailAudio() {
|
|||
// files: [],
|
||||
// fileType: null,
|
||||
// });
|
||||
const userLevelNumber = getCookiesDecrypt("ulne");
|
||||
const userId = getCookiesDecrypt("uie");
|
||||
const [detail, setDetail] = useState<curationDetail>();
|
||||
const [refresh] = useState(false);
|
||||
const [detailThumb, setDetailThumb] = useState<any>([]);
|
||||
const [detailAudio, setDetailAudio] = useState<any>([]);
|
||||
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
||||
const [selectedValue, setSelectedValue] = useState<string>("");
|
||||
|
||||
const {
|
||||
control,
|
||||
|
|
@ -136,11 +168,30 @@ export default function DetailAudio() {
|
|||
const [commentsData, setCommentsData] = useState(initialComments);
|
||||
const [replyText, setReplyText] = useState("");
|
||||
const [replyingTo, setReplyingTo] = useState<number | null>(null);
|
||||
const [selectedFileId, setSelectedFileId] = useState(null);
|
||||
const [listData, setListData] = useState([]);
|
||||
const [message, setMessage] = useState("");
|
||||
|
||||
const [wavesurfer, setWavesurfer] = useState<WaveSurfer>();
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
|
||||
const handleReply = (commentId: number) => {
|
||||
setReplyingTo(commentId);
|
||||
};
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setMessage(e.target.value);
|
||||
};
|
||||
|
||||
const onReady = (ws: any) => {
|
||||
setWavesurfer(ws);
|
||||
setIsPlaying(false);
|
||||
};
|
||||
|
||||
const onPlayPause = () => {
|
||||
wavesurfer && wavesurfer.playPause();
|
||||
};
|
||||
|
||||
const addReply = (commentId: number) => {
|
||||
if (replyText.trim()) {
|
||||
const newCommentData = commentsData.map((comment: any) => {
|
||||
|
|
@ -166,23 +217,131 @@ export default function DetailAudio() {
|
|||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
// loading();
|
||||
const response = await getMediaCurationMessage(selectedFileId);
|
||||
console.log("data", response?.data?.data);
|
||||
console.log("userLvl", userLevelNumber);
|
||||
setListData(response?.data?.data);
|
||||
close();
|
||||
}
|
||||
|
||||
initState();
|
||||
}, [selectedFileId]);
|
||||
|
||||
const postData = async () => {
|
||||
if (message?.length > 1 && selectedFileId) {
|
||||
try {
|
||||
const data = {
|
||||
mediaUploadFileId: selectedFileId,
|
||||
message,
|
||||
parentId: null,
|
||||
};
|
||||
|
||||
const response = await saveMediaCurationMessage(data);
|
||||
console.log("Komentar terkirim:", response);
|
||||
|
||||
const responseGet = await getMediaCurationMessage(selectedFileId);
|
||||
setListData(responseGet?.data?.data);
|
||||
|
||||
setMessage("");
|
||||
} catch (error) {
|
||||
console.error("Error posting comment:", error);
|
||||
}
|
||||
} else {
|
||||
console.log("Pesan atau file ID tidak valid.");
|
||||
}
|
||||
};
|
||||
|
||||
const sendReplyData = async (parentId: number) => {
|
||||
const inputElement = document.querySelector(
|
||||
`#input-comment-${parentId}`
|
||||
) as HTMLTextAreaElement;
|
||||
|
||||
if (inputElement?.value?.length > 1 && selectedFileId) {
|
||||
loading();
|
||||
const data = {
|
||||
mediaUploadFileId: selectedFileId,
|
||||
message: inputElement.value,
|
||||
parentId,
|
||||
};
|
||||
|
||||
console.log("Sending reply:", data);
|
||||
const response = await saveMediaCurationMessage(data);
|
||||
console.log(response);
|
||||
|
||||
const responseGet = await getMediaCurationMessage(selectedFileId);
|
||||
console.log("Updated comments:", responseGet?.data?.data);
|
||||
setListData(responseGet?.data?.data);
|
||||
|
||||
inputElement.value = "";
|
||||
close();
|
||||
setReplyingTo(null);
|
||||
}
|
||||
};
|
||||
|
||||
async function deleteDataSuggestion(dataId: any) {
|
||||
loading();
|
||||
const response = await deleteMediaCurationMessage(dataId);
|
||||
console.log(response);
|
||||
const responseGet = await getMediaCurationMessage(selectedFileId);
|
||||
console.log(responseGet?.data?.data);
|
||||
setListData(responseGet?.data?.data);
|
||||
close();
|
||||
}
|
||||
|
||||
const deleteData = (dataId: any) => {
|
||||
deleteDataSuggestion(dataId);
|
||||
console.log(dataId);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
const response = await detailMedia(id);
|
||||
const details = response?.data?.data;
|
||||
|
||||
setDetail(details);
|
||||
const filesData = details.files || [];
|
||||
const fileUrls = filesData.map((file: { thumbnailFileUrl: string }) =>
|
||||
file.thumbnailFileUrl ? file.thumbnailFileUrl : "default-image.jpg"
|
||||
const fileUrls = filesData.map((file: any) => ({
|
||||
id: file.id,
|
||||
secondaryUrl: file.secondaryUrl || "default-image.jpg",
|
||||
placements: file.placements || "",
|
||||
}));
|
||||
setDetail(details);
|
||||
setSelectedValue(details?.publishedFor || "");
|
||||
setSelectedFileId(details?.files[0]?.id);
|
||||
setDetailAudio(fileUrls);
|
||||
const fileUrlsThumbnail = filesData.map(
|
||||
(file: { thumbnailFileUrl: string; placements: string }) => ({
|
||||
thumbnailFileUrl: file.thumbnailFileUrl
|
||||
? file.thumbnailFileUrl
|
||||
: "default-image.jpg",
|
||||
placements: file.placements || "",
|
||||
})
|
||||
);
|
||||
setDetailThumb(fileUrls);
|
||||
|
||||
setDetailThumb(fileUrlsThumbnail);
|
||||
// setDetailThumb(fileUrls);
|
||||
}
|
||||
}
|
||||
initState();
|
||||
}, [id, refresh]);
|
||||
|
||||
const handleFileClick = async (fileId: any) => {
|
||||
setSelectedFileId(fileId);
|
||||
try {
|
||||
const response = await getMediaCurationMessage(fileId);
|
||||
console.log("Data komentar:", response?.data?.data);
|
||||
setListData(response?.data?.data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching comments:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleValueChange = (value: string) => {
|
||||
setSelectedValue(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex gap-10">
|
||||
{detail !== undefined ? (
|
||||
|
|
@ -190,8 +349,8 @@ export default function DetailAudio() {
|
|||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Kurasi Detail</p>
|
||||
<CardContent className="border rounded-md">
|
||||
<div className="flex flex-row gap-10">
|
||||
<div className="w-6/12">
|
||||
<div className="flex flex-col sm:flex-row lg:flex-row lg:gap-10">
|
||||
<div className="w-full lg:w-6/12">
|
||||
<div className="gap-5 mb-5">
|
||||
<div className="space-y-2 py-3">
|
||||
<Label>Judul</Label>
|
||||
|
|
@ -242,7 +401,7 @@ export default function DetailAudio() {
|
|||
name="description"
|
||||
render={({ field }) => (
|
||||
<Textarea
|
||||
value={detail.description}
|
||||
value={htmlToString(detail.description)}
|
||||
onChange={field.onChange}
|
||||
placeholder="Enter Meta"
|
||||
cols={5}
|
||||
|
|
@ -258,29 +417,30 @@ export default function DetailAudio() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-6/12">
|
||||
<div className="w-full lg:w-6/12">
|
||||
<div className="gap-5 mb-5">
|
||||
<div className="mt-5">
|
||||
<Label>Jenis Penugasan</Label>
|
||||
<RadioGroup
|
||||
// value={type} // State yang dipetakan ke value RadioGroup
|
||||
// onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
|
||||
value={selectedValue} // Set selected value
|
||||
onValueChange={handleValueChange} // Update state on change
|
||||
className="flex flex-wrap gap-3"
|
||||
>
|
||||
{/* Static list of radio buttons */}
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="1" id="publication" />
|
||||
<RadioGroupItem value="5" id="umum" />
|
||||
<Label htmlFor="umum">Umum</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="2" id="amplification" />
|
||||
<RadioGroupItem value="8" id="ksp" />
|
||||
<Label htmlFor="ksp">Ksp</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="3" id="contra" />
|
||||
<RadioGroupItem value="6" id="journalist" />
|
||||
<Label htmlFor="journalist">Journalist</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="4" id="contra" />
|
||||
<RadioGroupItem value="7" id="polri" />
|
||||
<Label htmlFor="polri">Polri</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
|
|
@ -288,9 +448,16 @@ export default function DetailAudio() {
|
|||
<div className=" py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<p className="border rounded-md text-blue-600 px-2 py-2">
|
||||
{detail?.tags}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{detail?.tags?.split(",").map((tag, index) => (
|
||||
<Badge
|
||||
key={index}
|
||||
className="border rounded-md px-2 py-2"
|
||||
>
|
||||
{tag.trim()}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className=" py-3">
|
||||
|
|
@ -315,8 +482,8 @@ export default function DetailAudio() {
|
|||
</div>
|
||||
</CardContent>
|
||||
<CardContent className="border rounded-md mt-5">
|
||||
<div className="flex flex-row gap-5">
|
||||
<div className="w-6/12 border px-3 mt-3 rounded-md">
|
||||
<div className="flex flex-col lg:flex-row gap-5">
|
||||
<div className="w-full lg:w-6/12 border px-3 mt-3 rounded-md">
|
||||
<div className="gap-5 mb-5">
|
||||
<div className="space-y-2 py-3">
|
||||
<Label className="text-xl text-black">File Media</Label>
|
||||
|
|
@ -327,40 +494,62 @@ export default function DetailAudio() {
|
|||
navigation={false}
|
||||
className="w-full"
|
||||
>
|
||||
{detailThumb?.map((data: any) => (
|
||||
<SwiperSlide key={data.id}>
|
||||
<audio
|
||||
className="w-full"
|
||||
src={data}
|
||||
controls
|
||||
title={`Audio ${data.id}`}
|
||||
{detailAudio?.map((data: any) => (
|
||||
<SwiperSlide
|
||||
key={data.id}
|
||||
onClick={() => handleFileClick(data.id)}
|
||||
>
|
||||
<WavesurferPlayer
|
||||
height={500}
|
||||
waveColor="red"
|
||||
url={data.secondaryUrl}
|
||||
onReady={onReady}
|
||||
key={`File ID: ${data.id}`}
|
||||
onPlay={() => setIsPlaying(true)}
|
||||
onPause={() => setIsPlaying(false)}
|
||||
/>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
<div className="mt-2">
|
||||
<Button
|
||||
size="sm"
|
||||
type="button"
|
||||
onClick={onPlayPause}
|
||||
disabled={isPlaying}
|
||||
className={`flex items-center gap-2 ${
|
||||
isPlaying
|
||||
? "bg-gray-300 cursor-not-allowed"
|
||||
: "bg-primary text-white"
|
||||
} p-2 rounded`}
|
||||
>
|
||||
{isPlaying ? "Pause" : "Play"}
|
||||
<Icon
|
||||
icon={
|
||||
isPlaying
|
||||
? "carbon:pause-outline"
|
||||
: "famicons:play-sharp"
|
||||
}
|
||||
className="h-5 w-5"
|
||||
/>
|
||||
</Button>
|
||||
<div className=" mt-2 ">
|
||||
<Swiper
|
||||
onSwiper={setThumbsSwiper}
|
||||
slidesPerView={6}
|
||||
spaceBetween={8}
|
||||
pagination={{
|
||||
clickable: true,
|
||||
}}
|
||||
pagination={{ clickable: true }}
|
||||
modules={[Pagination, Thumbs]}
|
||||
>
|
||||
{detailThumb?.map((data: any) => (
|
||||
<SwiperSlide key={data.id}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-sm text-gray-700">
|
||||
Audio {data.id}
|
||||
</span>
|
||||
<audio
|
||||
className="h-[24px] w-[80px]"
|
||||
src={data}
|
||||
controls
|
||||
title={`Audio ${data.id}`}
|
||||
/>
|
||||
</div>
|
||||
{detailAudio?.map((data: any) => (
|
||||
<SwiperSlide
|
||||
key={data.id}
|
||||
onClick={() => handleFileClick(data.id)}
|
||||
>
|
||||
<img
|
||||
className="object-fill h-full w-full rounded-md"
|
||||
src={"/assets/music-icon.jpg"}
|
||||
alt={`File ID: ${data.id}`}
|
||||
/>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
|
|
@ -369,7 +558,7 @@ export default function DetailAudio() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-6/12 border px-3 rounded-md mt-3">
|
||||
<div className="w-full lg:w-6/12 border px-3 rounded-md mt-3">
|
||||
<div className="gap-5 mb-5">
|
||||
<div className="mt-5">
|
||||
<Label className="text-xl text-black">
|
||||
|
|
@ -390,35 +579,33 @@ export default function DetailAudio() {
|
|||
src={data.thumbnailUrl} // Assuming `thumbnailUrl` is the property that contains the URL for the thumbnail image
|
||||
alt={`Thumbnail ${index}`}
|
||||
/> */}
|
||||
<img
|
||||
className="object-cover w-36 h-32"
|
||||
src={data}
|
||||
alt={`Article ${data.id}`}
|
||||
/>
|
||||
<div className="flex flex-row gap-3 items-center">
|
||||
<label className="text-blue-500 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value="Nasional"
|
||||
<Music className="object-cover w-32 h-32" />
|
||||
<div className="flex flex-wrap lg:flex-row gap-3 items-center">
|
||||
{/* Mabes Checkbox */}
|
||||
<label className=" cursor-pointer flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={data.placements === "mabes"} // Automatically checks if placement matches
|
||||
disabled // To reflect read-only behavior
|
||||
/>
|
||||
Nasional
|
||||
<span>Nasional</span>
|
||||
</label>
|
||||
<label className="text-blue-500 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value="Wilayah"
|
||||
|
||||
{/* Polda Checkbox */}
|
||||
<label className=" cursor-pointer flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={data.placements === "polda"} // Automatically checks if placement matches
|
||||
disabled
|
||||
/>
|
||||
Wilayah
|
||||
<span>Wilayah</span>
|
||||
</label>
|
||||
<label className="text-blue-500 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value="International"
|
||||
|
||||
{/* International Checkbox */}
|
||||
<label className=" cursor-pointer flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={data.placements === "international"} // Automatically checks if placement matches
|
||||
disabled
|
||||
/>
|
||||
International
|
||||
<span>International</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -432,110 +619,184 @@ export default function DetailAudio() {
|
|||
<div className="gap-5 mb-5">
|
||||
<div className="mt-5">
|
||||
<Label className="text-xl text-black">Berikan Komentar</Label>
|
||||
<div className="flex items-start gap-3">
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="/images/avatar/avatar-1.png"
|
||||
alt="@shadcn"
|
||||
/>
|
||||
</Avatar>
|
||||
|
||||
<textarea
|
||||
className="flex-grow p-2 border rounded-lg focus:outline-none focus:border-yellow-400"
|
||||
placeholder="Tuliskan komentar Anda di sini.."
|
||||
></textarea>
|
||||
<div className="mt-4 border p-4 rounded bg-gray-50">
|
||||
<Textarea
|
||||
placeholder="Tulis tanggapan Anda di sini..."
|
||||
value={message}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<div className="flex justify-end mt-3">
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => postData()}
|
||||
type="button"
|
||||
>
|
||||
Kirim Komentar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center mt-2 gap-3">
|
||||
<button className="flex items-center text-gray-600 hover:text-gray-800">
|
||||
<PaperclipIcon className="w-5 h-5" />
|
||||
Lampirkan
|
||||
</button>
|
||||
<button className="flex items-center text-gray-600 hover:text-gray-800">
|
||||
<SmileIcon className="w-5 h-5" />
|
||||
Emoticon
|
||||
</button>
|
||||
<button className="ml-auto px-4 py-1 bg-yellow-500 text-white rounded-lg hover:bg-yellow-600">
|
||||
Kirim
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-5">
|
||||
<Label className="text-xl text-black">Komentar</Label>
|
||||
{commentsData.map((comment) => (
|
||||
<div
|
||||
key={comment.id}
|
||||
className="flex items-start gap-3 mt-2"
|
||||
>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src={comment.avatar}
|
||||
alt={`@${comment.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{comment.username}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{comment.date}
|
||||
</span>
|
||||
<p className="text-gray-800 mt-1">{comment.text}</p>
|
||||
<div
|
||||
className="flex items-center mt-1 text-blue-500 cursor-pointer"
|
||||
onClick={() => handleReply(comment.id)}
|
||||
>
|
||||
<DotSquare className="w-4 h-4" />
|
||||
<span className="ml-1">Balas</span>
|
||||
{listData?.map((item: any) => (
|
||||
<div key={item.id} className="flex flex-col gap-3 mt-2 ">
|
||||
<div className="flex flex-row gap-3">
|
||||
<Avatar className="mt-2">
|
||||
<AvatarImage
|
||||
src={"/assets/avatar-profile.png"}
|
||||
alt={`@${item.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{item.messageFrom.fullname}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{formatDate(item.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-800 mt-1">{item.message}</p>
|
||||
<div className="flex flex-row gap-2">
|
||||
{/* <div
|
||||
className="flex items-center mt-1 text-blue-500 cursor-pointer"
|
||||
onClick={() => handleReply(item.id)}
|
||||
>
|
||||
<DotSquare className="w-4 h-4" />
|
||||
<span className="ml-1">Balas</span>
|
||||
</div> */}
|
||||
<div
|
||||
className="flex items-center mt-1 text-red-500 cursor-pointer"
|
||||
onClick={() => deleteData(item.id)}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<span className="ml-1">Delete</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{comment.replies.length > 0 && (
|
||||
<div className="ml-8 mt-2">
|
||||
{comment.replies.map((reply: any, index: any) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-start gap-3 mt-1"
|
||||
>
|
||||
<Avatar>
|
||||
</div>
|
||||
{replyingTo === item.id && (
|
||||
<div className="ml-10 mt-2">
|
||||
<textarea
|
||||
id={`input-comment-${item.id}`}
|
||||
className="w-full p-2 border rounded"
|
||||
placeholder="Masukkan tanggapan anda"
|
||||
/>
|
||||
<button
|
||||
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
|
||||
onClick={() => sendReplyData(item.id)}
|
||||
>
|
||||
Kirim
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{item.children?.length > 0 && (
|
||||
<div className="ml-10 mt-2 flex flex-col">
|
||||
{item.children.map((child: any) => (
|
||||
<div
|
||||
key={child.id}
|
||||
className="flex flex-col gap-3 mt-2"
|
||||
>
|
||||
<div className="flex flex-row gap-3">
|
||||
<Avatar className="mt-2">
|
||||
<AvatarImage
|
||||
src="https://github.com/shadcn.png"
|
||||
alt={`@${reply.username}`}
|
||||
src={"/assets/avatar-profile.png"}
|
||||
alt={`@${child.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{reply.username}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{reply.date}
|
||||
</span>
|
||||
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{item.messageFrom.fullname}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{formatDate(item.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-800 mt-1">
|
||||
{reply.text}
|
||||
{child.message}
|
||||
</p>
|
||||
<div className="flex flex-row gap-2">
|
||||
{/* <div
|
||||
className="flex items-center mt-1 text-blue-500 cursor-pointer"
|
||||
onClick={() => handleReply(child.id)}
|
||||
>
|
||||
<DotSquare className="w-4 h-4" />
|
||||
<span className="ml-1">Balas</span>
|
||||
</div> */}
|
||||
<div
|
||||
className="flex items-center mt-1 text-red-500 cursor-pointer"
|
||||
onClick={() => deleteData(child.id)}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<span className="ml-1">Delete</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{replyingTo === child.id && (
|
||||
<div className="ml-10 mt-2">
|
||||
<textarea
|
||||
id={`input-comment-${child.id}`}
|
||||
className="w-full p-2 border rounded"
|
||||
placeholder="Masukkan tanggapan anda"
|
||||
/>
|
||||
<button
|
||||
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
|
||||
onClick={() => sendReplyData(child.id)}
|
||||
>
|
||||
Kirim
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{child.children?.length > 0 && (
|
||||
<div className="ml-10 mt-2 flex flex-col mb-3">
|
||||
{child.children.map((child2: any) => (
|
||||
<div
|
||||
key={child2.id}
|
||||
className="flex flex-col gap-3 mt-2"
|
||||
>
|
||||
<div className="flex flex-row gap-3 ">
|
||||
<Avatar className="mt-2">
|
||||
<AvatarImage
|
||||
src={"/assets/avatar-profile.png"}
|
||||
alt={`@${child2.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{item.messageFrom.fullname}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{formatDate(item.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-800 mt-1">
|
||||
{child2.message}
|
||||
</p>
|
||||
<div className="flex flex-row gap-2">
|
||||
<div
|
||||
className="flex items-center mt-1 text-red-500 cursor-pointer"
|
||||
onClick={() =>
|
||||
deleteData(child2.id)
|
||||
}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<span className="ml-1">
|
||||
Delete
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{replyingTo !== null && (
|
||||
<div className="mt-4">
|
||||
<textarea
|
||||
className="w-full p-2 border rounded-md"
|
||||
rows={3}
|
||||
placeholder="Tulis balasan..."
|
||||
value={replyText}
|
||||
onChange={(e) => setReplyText(e.target.value)}
|
||||
></textarea>
|
||||
<button
|
||||
className="mt-2 bg-yellow-500 text-white px-4 py-2 rounded-md"
|
||||
onClick={() => addReply(replyingTo)}
|
||||
>
|
||||
Kirim
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import AudioSliderPage from "../../audio/audio";
|
|||
import TeksSliderPage from "../../document/teks";
|
||||
import ImageSliderPage from "../../image/image";
|
||||
import VideoSliderPage from "../../video/audio-visual";
|
||||
import TeksAll from "./teks";
|
||||
|
||||
const DocumentAllPage = () => {
|
||||
return (
|
||||
|
|
@ -56,7 +57,7 @@ const DocumentAllPage = () => {
|
|||
<Label className="text-base">Teks</Label>
|
||||
</div>
|
||||
<div className="px-5 my-5">
|
||||
<TeksSliderPage />
|
||||
<TeksAll />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,116 @@
|
|||
"use client";
|
||||
import { Link } from "@/components/navigation";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "@/components/ui/carousel";
|
||||
import { listCuratedContent } from "@/service/curated-content/curated-content";
|
||||
import { getListContent } from "@/service/landing/landing";
|
||||
import {
|
||||
formatDateToIndonesian,
|
||||
generateLocalizedPath,
|
||||
textEllipsis,
|
||||
} from "@/utils/globals";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { useParams, usePathname, useRouter } from "next/navigation";
|
||||
import React, { Component, useEffect, useState } from "react";
|
||||
|
||||
const TeksAll = () => {
|
||||
const [documentData, setDocumentData] = useState<any>();
|
||||
const [displayDocument, setDisplayDocument] = useState<any[]>([]);
|
||||
const [page, setPage] = useState(1);
|
||||
const [limit, setLimit] = React.useState(10);
|
||||
const [search, setSearch] = React.useState("");
|
||||
|
||||
useEffect(() => {
|
||||
initFetch();
|
||||
}, [page, limit, search]);
|
||||
|
||||
useEffect(() => {
|
||||
if (documentData?.length > 0) {
|
||||
shuffleAndSetVideos();
|
||||
const interval = setInterval(shuffleAndSetVideos, 5000);
|
||||
return () => clearInterval(interval); // Cleanup interval on unmount
|
||||
}
|
||||
}, [documentData]);
|
||||
|
||||
const initFetch = async () => {
|
||||
const response = await listCuratedContent(search, limit, page - 1, 3, "1");
|
||||
console.log(response);
|
||||
|
||||
const data = response?.data?.data;
|
||||
const contentData = data?.content;
|
||||
setDocumentData(contentData);
|
||||
};
|
||||
const shuffleAndSetVideos = () => {
|
||||
const shuffled = shuffleArray([...documentData]);
|
||||
setDisplayDocument(shuffled.slice(0, 2));
|
||||
};
|
||||
|
||||
const shuffleArray = (array: any[]) => {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
return array;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-3 px-5">
|
||||
<div className=" grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{displayDocument?.map((document: any) => (
|
||||
<Link
|
||||
href={`/shared/curated-content/giat-routine/document/detail/${document.id}`}
|
||||
key={document?.id}
|
||||
className="flex flex-col bg-yellow-500 sm:flex-row items-center dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full"
|
||||
>
|
||||
<div className="flex items-center justify-center rounded-lg w-16 h-16">
|
||||
<svg
|
||||
width="28"
|
||||
height="34"
|
||||
viewBox="0 0 28 34"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5.6665 17.4167C5.6665 17.0851 5.7982 16.7672 6.03262 16.5328C6.26704 16.2984 6.58498 16.1667 6.9165 16.1667C7.24802 16.1667 7.56597 16.2984 7.80039 16.5328C8.03481 16.7672 8.1665 17.0851 8.1665 17.4167C8.1665 17.7482 8.03481 18.0661 7.80039 18.3005C7.56597 18.535 7.24802 18.6667 6.9165 18.6667C6.58498 18.6667 6.26704 18.535 6.03262 18.3005C5.7982 18.0661 5.6665 17.7482 5.6665 17.4167ZM6.9165 21.1667C6.58498 21.1667 6.26704 21.2984 6.03262 21.5328C5.7982 21.7672 5.6665 22.0851 5.6665 22.4167C5.6665 22.7482 5.7982 23.0661 6.03262 23.3005C6.26704 23.535 6.58498 23.6667 6.9165 23.6667C7.24802 23.6667 7.56597 23.535 7.80039 23.3005C8.03481 23.0661 8.1665 22.7482 8.1665 22.4167C8.1665 22.0851 8.03481 21.7672 7.80039 21.5328C7.56597 21.2984 7.24802 21.1667 6.9165 21.1667ZM5.6665 27.4167C5.6665 27.0851 5.7982 26.7672 6.03262 26.5328C6.26704 26.2984 6.58498 26.1667 6.9165 26.1667C7.24802 26.1667 7.56597 26.2984 7.80039 26.5328C8.03481 26.7672 8.1665 27.0851 8.1665 27.4167C8.1665 27.7482 8.03481 28.0661 7.80039 28.3005C7.56597 28.535 7.24802 28.6667 6.9165 28.6667C6.58498 28.6667 6.26704 28.535 6.03262 28.3005C5.7982 28.0661 5.6665 27.7482 5.6665 27.4167ZM11.9165 16.1667C11.585 16.1667 11.267 16.2984 11.0326 16.5328C10.7982 16.7672 10.6665 17.0851 10.6665 17.4167C10.6665 17.7482 10.7982 18.0661 11.0326 18.3005C11.267 18.535 11.585 18.6667 11.9165 18.6667H21.0832C21.4147 18.6667 21.7326 18.535 21.9671 18.3005C22.2015 18.0661 22.3332 17.7482 22.3332 17.4167C22.3332 17.0851 22.2015 16.7672 21.9671 16.5328C21.7326 16.2984 21.4147 16.1667 21.0832 16.1667H11.9165ZM10.6665 22.4167C10.6665 22.0851 10.7982 21.7672 11.0326 21.5328C11.267 21.2984 11.585 21.1667 11.9165 21.1667H21.0832C21.4147 21.1667 21.7326 21.2984 21.9671 21.5328C22.2015 21.7672 22.3332 22.0851 22.3332 22.4167C22.3332 22.7482 22.2015 23.0661 21.9671 23.3005C21.7326 23.535 21.4147 23.6667 21.0832 23.6667H11.9165C11.585 23.6667 11.267 23.535 11.0326 23.3005C10.7982 23.0661 10.6665 22.7482 10.6665 22.4167ZM11.9165 26.1667C11.585 26.1667 11.267 26.2984 11.0326 26.5328C10.7982 26.7672 10.6665 27.0851 10.6665 27.4167C10.6665 27.7482 10.7982 28.0661 11.0326 28.3005C11.267 28.535 11.585 28.6667 11.9165 28.6667H21.0832C21.4147 28.6667 21.7326 28.535 21.9671 28.3005C22.2015 28.0661 22.3332 27.7482 22.3332 27.4167C22.3332 27.0851 22.2015 26.7672 21.9671 26.5328C21.7326 26.2984 21.4147 26.1667 21.0832 26.1667H11.9165ZM26.3565 11.0233L16.6415 1.31C16.6157 1.28605 16.5885 1.26378 16.5598 1.24333C16.5392 1.22742 16.5192 1.21074 16.4998 1.19333C16.3852 1.08512 16.2632 0.984882 16.1348 0.893332C16.0922 0.865802 16.0476 0.841298 16.0015 0.819999L15.9215 0.779999L15.8382 0.731666C15.7482 0.679999 15.6565 0.626665 15.5615 0.586665C15.2296 0.454104 14.8783 0.376423 14.5215 0.356665C14.4885 0.354519 14.4557 0.350625 14.4232 0.344999C14.3779 0.338012 14.3323 0.334114 14.2865 0.333332H3.99984C3.11578 0.333332 2.26794 0.684521 1.64281 1.30964C1.01769 1.93476 0.666504 2.78261 0.666504 3.66667V30.3333C0.666504 31.2174 1.01769 32.0652 1.64281 32.6904C2.26794 33.3155 3.11578 33.6667 3.99984 33.6667H23.9998C24.8839 33.6667 25.7317 33.3155 26.3569 32.6904C26.982 32.0652 27.3332 31.2174 27.3332 30.3333V13.38C27.333 12.496 26.9817 11.6483 26.3565 11.0233ZM24.8332 30.3333C24.8332 30.5543 24.7454 30.7663 24.5891 30.9226C24.4328 31.0789 24.2208 31.1667 23.9998 31.1667H3.99984C3.77882 31.1667 3.56686 31.0789 3.41058 30.9226C3.2543 30.7663 3.1665 30.5543 3.1665 30.3333V3.66667C3.1665 3.44565 3.2543 3.23369 3.41058 3.07741C3.56686 2.92113 3.77882 2.83333 3.99984 2.83333H13.9998V10.3333C13.9998 11.2174 14.351 12.0652 14.9761 12.6904C15.6013 13.3155 16.4491 13.6667 17.3332 13.6667H24.8332V30.3333ZM16.4998 4.70166L22.9632 11.1667H17.3332C17.1122 11.1667 16.9002 11.0789 16.7439 10.9226C16.5876 10.7663 16.4998 10.5543 16.4998 10.3333V4.70166Z"
|
||||
fill="black"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col flex-1">
|
||||
<div className="text-gray-500 dark:text-gray-400 flex flex-row text-sm">
|
||||
{formatDateToIndonesian(new Date(document?.createdAt))}{" "}
|
||||
{document?.timezone ? document?.timezone : "WIB"} |{" "}
|
||||
<Icon icon="formkit:eye" width="15" height="15" /> 518
|
||||
</div>
|
||||
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm">
|
||||
{document?.title}
|
||||
</div>
|
||||
<div className="flex gap-2 items-center text-sm text-red-500 dark:text-red-500">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
fill="#f00"
|
||||
d="M224 30v256h-64l96 128l96-128h-64V30zM32 434v48h448v-48z"
|
||||
/>
|
||||
</svg>
|
||||
Download Dokumen
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TeksAll;
|
||||
|
|
@ -21,8 +21,19 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|||
import Cookies from "js-cookie";
|
||||
import { postBlog } from "@/service/blog/blog";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { DotSquare, InboxIcon, PaperclipIcon, SmileIcon } from "lucide-react";
|
||||
import { detailMedia } from "@/service/curated-content/curated-content";
|
||||
import {
|
||||
DotSquare,
|
||||
InboxIcon,
|
||||
PaperclipIcon,
|
||||
SmileIcon,
|
||||
TrashIcon,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
deleteMediaCurationMessage,
|
||||
detailMedia,
|
||||
getMediaCurationMessage,
|
||||
saveMediaCurationMessage,
|
||||
} from "@/service/curated-content/curated-content";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import "swiper/css";
|
||||
import "swiper/css/free-mode";
|
||||
|
|
@ -35,6 +46,11 @@ import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
|
|||
import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
||||
import JoditEditor from "jodit-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { htmlToString } from "@/utils/globals";
|
||||
import { loading } from "@/lib/swal";
|
||||
import { formatDate } from "@fullcalendar/core/index.js";
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
|
||||
const detailSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -51,6 +67,13 @@ type Category = {
|
|||
categoryName: string;
|
||||
};
|
||||
|
||||
type PublishedForObject = {
|
||||
id: number;
|
||||
name: string;
|
||||
isInternal: boolean;
|
||||
code: string;
|
||||
};
|
||||
|
||||
export type curationDetail = {
|
||||
id: number;
|
||||
title: string;
|
||||
|
|
@ -71,6 +94,13 @@ export type curationDetail = {
|
|||
userGroupId: number;
|
||||
};
|
||||
};
|
||||
publishedFor: string; // ID for selected radio button
|
||||
publishedForObject: {
|
||||
id: number;
|
||||
name: string;
|
||||
isInternal: boolean;
|
||||
code: string;
|
||||
}[];
|
||||
tags: string;
|
||||
provinceId: string;
|
||||
is_active: string;
|
||||
|
|
@ -109,7 +139,8 @@ export default function DetailDocument() {
|
|||
console.log(id);
|
||||
const editor = useRef(null);
|
||||
type DetailSchema = z.infer<typeof detailSchema>;
|
||||
|
||||
const userLevelNumber = getCookiesDecrypt("ulne");
|
||||
const userId = getCookiesDecrypt("uie");
|
||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||
const taskId = Cookies.get("taskId");
|
||||
const scheduleId = Cookies.get("scheduleId");
|
||||
|
|
@ -125,6 +156,7 @@ export default function DetailDocument() {
|
|||
const [refresh] = useState(false);
|
||||
const [detailThumb, setDetailThumb] = useState<any>([]);
|
||||
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
||||
const [selectedValue, setSelectedValue] = useState<string>("");
|
||||
|
||||
const {
|
||||
control,
|
||||
|
|
@ -138,11 +170,18 @@ export default function DetailDocument() {
|
|||
const [commentsData, setCommentsData] = useState(initialComments);
|
||||
const [replyText, setReplyText] = useState("");
|
||||
const [replyingTo, setReplyingTo] = useState<number | null>(null);
|
||||
const [selectedFileId, setSelectedFileId] = useState(null);
|
||||
const [listData, setListData] = useState([]);
|
||||
const [message, setMessage] = useState("");
|
||||
|
||||
const handleReply = (commentId: number) => {
|
||||
setReplyingTo(commentId);
|
||||
};
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setMessage(e.target.value);
|
||||
};
|
||||
|
||||
const addReply = (commentId: number) => {
|
||||
if (replyText.trim()) {
|
||||
const newCommentData = commentsData.map((comment: any) => {
|
||||
|
|
@ -168,6 +207,85 @@ export default function DetailDocument() {
|
|||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
// loading();
|
||||
const response = await getMediaCurationMessage(selectedFileId);
|
||||
console.log("data", response?.data?.data);
|
||||
console.log("userLvl", userLevelNumber);
|
||||
setListData(response?.data?.data);
|
||||
close();
|
||||
}
|
||||
|
||||
initState();
|
||||
}, [selectedFileId]);
|
||||
|
||||
const postData = async () => {
|
||||
if (message?.length > 1 && selectedFileId) {
|
||||
try {
|
||||
const data = {
|
||||
mediaUploadFileId: selectedFileId,
|
||||
message,
|
||||
parentId: null,
|
||||
};
|
||||
|
||||
const response = await saveMediaCurationMessage(data);
|
||||
console.log("Komentar terkirim:", response);
|
||||
|
||||
const responseGet = await getMediaCurationMessage(selectedFileId);
|
||||
setListData(responseGet?.data?.data);
|
||||
|
||||
setMessage("");
|
||||
} catch (error) {
|
||||
console.error("Error posting comment:", error);
|
||||
}
|
||||
} else {
|
||||
console.log("Pesan atau file ID tidak valid.");
|
||||
}
|
||||
};
|
||||
|
||||
const sendReplyData = async (parentId: number) => {
|
||||
const inputElement = document.querySelector(
|
||||
`#input-comment-${parentId}`
|
||||
) as HTMLTextAreaElement;
|
||||
|
||||
if (inputElement?.value?.length > 1 && selectedFileId) {
|
||||
loading();
|
||||
const data = {
|
||||
mediaUploadFileId: selectedFileId,
|
||||
message: inputElement.value,
|
||||
parentId,
|
||||
};
|
||||
|
||||
console.log("Sending reply:", data);
|
||||
const response = await saveMediaCurationMessage(data);
|
||||
console.log(response);
|
||||
|
||||
const responseGet = await getMediaCurationMessage(selectedFileId);
|
||||
console.log("Updated comments:", responseGet?.data?.data);
|
||||
setListData(responseGet?.data?.data);
|
||||
|
||||
inputElement.value = "";
|
||||
close();
|
||||
setReplyingTo(null);
|
||||
}
|
||||
};
|
||||
|
||||
async function deleteDataSuggestion(dataId: any) {
|
||||
loading();
|
||||
const response = await deleteMediaCurationMessage(dataId);
|
||||
console.log(response);
|
||||
const responseGet = await getMediaCurationMessage(selectedFileId);
|
||||
console.log(responseGet?.data?.data);
|
||||
setListData(responseGet?.data?.data);
|
||||
close();
|
||||
}
|
||||
|
||||
const deleteData = (dataId: any) => {
|
||||
deleteDataSuggestion(dataId);
|
||||
console.log(dataId);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
|
|
@ -175,16 +293,37 @@ export default function DetailDocument() {
|
|||
const details = response?.data?.data;
|
||||
|
||||
setDetail(details);
|
||||
setSelectedValue(details?.publishedFor || "");
|
||||
setSelectedFileId(details?.files[0]?.id);
|
||||
const filesData = details.files || [];
|
||||
const fileUrls = filesData.map((file: { thumbnailFileUrl: string }) =>
|
||||
file.thumbnailFileUrl ? file.thumbnailFileUrl : "default-image.jpg"
|
||||
);
|
||||
const fileUrls = filesData.map((file: any) => ({
|
||||
id: file.id,
|
||||
url: file.secondaryUrl || "default-image.jpg",
|
||||
format: file.format,
|
||||
fileName: file.fileName,
|
||||
placements: file.placements || "",
|
||||
}));
|
||||
setDetailThumb(fileUrls);
|
||||
}
|
||||
}
|
||||
initState();
|
||||
}, [id, refresh]);
|
||||
|
||||
const handleFileClick = async (fileId: any) => {
|
||||
setSelectedFileId(fileId);
|
||||
try {
|
||||
const response = await getMediaCurationMessage(fileId);
|
||||
console.log("Data komentar:", response?.data?.data);
|
||||
setListData(response?.data?.data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching comments:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleValueChange = (value: string) => {
|
||||
setSelectedValue(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex gap-10">
|
||||
{detail !== undefined ? (
|
||||
|
|
@ -192,8 +331,8 @@ export default function DetailDocument() {
|
|||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Kurasi Detail</p>
|
||||
<CardContent className="border rounded-md">
|
||||
<div className="flex flex-row gap-10">
|
||||
<div className="w-6/12">
|
||||
<div className="flex flex-col sm:flex-row lg:flex-row lg:gap-10">
|
||||
<div className="w-full lg:w-6/12">
|
||||
<div className="gap-5 mb-5">
|
||||
<div className="space-y-2 py-3">
|
||||
<Label>Judul</Label>
|
||||
|
|
@ -244,7 +383,7 @@ export default function DetailDocument() {
|
|||
name="description"
|
||||
render={({ field }) => (
|
||||
<Textarea
|
||||
value={detail.description}
|
||||
value={htmlToString(detail.description)}
|
||||
onChange={field.onChange}
|
||||
placeholder="Enter Meta"
|
||||
cols={5}
|
||||
|
|
@ -265,24 +404,25 @@ export default function DetailDocument() {
|
|||
<div className="mt-5">
|
||||
<Label>Jenis Penugasan</Label>
|
||||
<RadioGroup
|
||||
// value={type} // State yang dipetakan ke value RadioGroup
|
||||
// onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
|
||||
value={selectedValue} // Set selected value
|
||||
onValueChange={handleValueChange} // Update state on change
|
||||
className="flex flex-wrap gap-3"
|
||||
>
|
||||
{/* Static list of radio buttons */}
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="1" id="publication" />
|
||||
<RadioGroupItem value="5" id="umum" />
|
||||
<Label htmlFor="umum">Umum</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="2" id="amplification" />
|
||||
<RadioGroupItem value="8" id="ksp" />
|
||||
<Label htmlFor="ksp">Ksp</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="3" id="contra" />
|
||||
<RadioGroupItem value="6" id="journalist" />
|
||||
<Label htmlFor="journalist">Journalist</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="4" id="contra" />
|
||||
<RadioGroupItem value="7" id="polri" />
|
||||
<Label htmlFor="polri">Polri</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
|
|
@ -324,29 +464,101 @@ export default function DetailDocument() {
|
|||
</div>
|
||||
</CardContent>
|
||||
<CardContent className="border rounded-md mt-5">
|
||||
<div className="flex flex-row gap-5">
|
||||
<div className="w-6/12 border px-3 mt-3 rounded-md">
|
||||
<div className="flex flex-col lg:flex-row gap-5">
|
||||
<div className="w-full lg:w-6/12 border px-3 mt-3 rounded-md">
|
||||
<div className="gap-5 mb-5">
|
||||
<div className="space-y-2 py-3">
|
||||
<Label className="text-xl text-black">File Media</Label>
|
||||
<div className="w-full ">
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<JoditEditor
|
||||
ref={editor}
|
||||
value={detail?.description}
|
||||
onChange={onChange}
|
||||
className="dark:text-black"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Swiper
|
||||
thumbs={{ swiper: thumbsSwiper }}
|
||||
modules={[FreeMode, Navigation, Thumbs]}
|
||||
navigation={false}
|
||||
className="w-full"
|
||||
>
|
||||
{detailThumb?.map((data: any) => (
|
||||
<SwiperSlide
|
||||
key={data.id}
|
||||
onClick={() => handleFileClick(data.id)}
|
||||
>
|
||||
{[".jpg", ".jpeg", ".png", ".webp"].includes(
|
||||
data.format
|
||||
) ? (
|
||||
// Menampilkan gambar
|
||||
<img
|
||||
className="object-fill h-full w-full rounded-md"
|
||||
src={data.url}
|
||||
alt={data.fileName || "File"}
|
||||
/>
|
||||
) : data.format === ".pdf" ? (
|
||||
// Menampilkan PDF menggunakan iframe
|
||||
<iframe
|
||||
className="w-full h-96 rounded-md"
|
||||
src={data.url}
|
||||
title={data.fileName || "PDF File"}
|
||||
/>
|
||||
) : [".docx", ".ppt", ".pptx"].includes(
|
||||
data.format
|
||||
) ? (
|
||||
// Menampilkan file dokumen menggunakan Office Viewer
|
||||
<iframe
|
||||
className="w-full h-96 rounded-md"
|
||||
src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
|
||||
data.url
|
||||
)}`}
|
||||
title={data.fileName || "Document"}
|
||||
/>
|
||||
) : (
|
||||
// Menampilkan link jika format tidak dikenali
|
||||
<a
|
||||
href={data.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block text-blue-500 underline"
|
||||
>
|
||||
View {data.fileName || "File"}
|
||||
</a>
|
||||
)}
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
<div className="mt-2">
|
||||
<Swiper
|
||||
onSwiper={setThumbsSwiper}
|
||||
slidesPerView={6}
|
||||
spaceBetween={8}
|
||||
pagination={{ clickable: true }}
|
||||
modules={[Pagination, Thumbs]}
|
||||
>
|
||||
{detailThumb?.map((data: any) => (
|
||||
<SwiperSlide
|
||||
key={data.id}
|
||||
onClick={() => handleFileClick(data.id)}
|
||||
>
|
||||
{[".jpg", ".jpeg", ".png", ".webp"].includes(
|
||||
data.format
|
||||
) ? (
|
||||
<img
|
||||
className="object-cover h-[60px] w-[80px]"
|
||||
src={"/assets/docx-icon.jpg"}
|
||||
alt={data.fileName}
|
||||
/>
|
||||
) : (
|
||||
<div className="h-[60px] w-[80px] flex items-center justify-center bg-gray-200 text-sm text-center text-gray-700 rounded-md">
|
||||
{data?.format
|
||||
?.replace(".", "")
|
||||
.toUpperCase()}
|
||||
</div>
|
||||
)}
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-6/12 border px-3 rounded-md mt-3">
|
||||
<div className="w-full lg:w-6/12 border px-3 rounded-md mt-3">
|
||||
<div className="gap-5 mb-5">
|
||||
<div className="mt-5">
|
||||
<Label className="text-xl text-black">
|
||||
|
|
@ -357,45 +569,42 @@ export default function DetailDocument() {
|
|||
<p>Penempatan</p>
|
||||
</div>
|
||||
<p className="bg-black h-1 w-full rounded-lg"></p>
|
||||
{detailThumb?.map((data: any) => (
|
||||
{detailThumb.map((data: any, index: number) => (
|
||||
<div
|
||||
key={data.id}
|
||||
key={index}
|
||||
className="flex items-center gap-3 mt-2"
|
||||
>
|
||||
{/* <img
|
||||
className="object-cover w-20 h-20"
|
||||
src={data.thumbnailUrl} // Assuming `thumbnailUrl` is the property that contains the URL for the thumbnail image
|
||||
alt={`Thumbnail ${index}`}
|
||||
/> */}
|
||||
<img
|
||||
className="object-cover w-36 h-32"
|
||||
src={data}
|
||||
alt={` ${data.id}`}
|
||||
className="object-cover h-[60px] w-[80px]"
|
||||
src={"/assets/docx-icon.jpg"}
|
||||
alt={data.fileName}
|
||||
/>
|
||||
<div className="flex flex-row gap-3 items-center">
|
||||
<label className="text-blue-500 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value="Nasional"
|
||||
<div className="flex flex-wrap lg:flex-row gap-3 items-center">
|
||||
{/* Mabes Checkbox */}
|
||||
<label className=" cursor-pointer flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={data.placements === "mabes"} // Automatically checks if placement matches
|
||||
disabled // To reflect read-only behavior
|
||||
/>
|
||||
Nasional
|
||||
<span>Nasional</span>
|
||||
</label>
|
||||
<label className="text-blue-500 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value="Wilayah"
|
||||
|
||||
{/* Polda Checkbox */}
|
||||
<label className=" cursor-pointer flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={data.placements === "polda"} // Automatically checks if placement matches
|
||||
disabled
|
||||
/>
|
||||
Wilayah
|
||||
<span>Wilayah</span>
|
||||
</label>
|
||||
<label className="text-blue-500 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value="International"
|
||||
|
||||
{/* International Checkbox */}
|
||||
<label className=" cursor-pointer flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={data.placements === "international"} // Automatically checks if placement matches
|
||||
disabled
|
||||
/>
|
||||
International
|
||||
<span>International</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -409,110 +618,184 @@ export default function DetailDocument() {
|
|||
<div className="gap-5 mb-5">
|
||||
<div className="mt-5">
|
||||
<Label className="text-xl text-black">Berikan Komentar</Label>
|
||||
<div className="flex items-start gap-3">
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="/images/avatar/avatar-1.png"
|
||||
alt="@shadcn"
|
||||
/>
|
||||
</Avatar>
|
||||
|
||||
<textarea
|
||||
className="flex-grow p-2 border rounded-lg focus:outline-none focus:border-yellow-400"
|
||||
placeholder="Tuliskan komentar Anda di sini.."
|
||||
></textarea>
|
||||
<div className="mt-4 border p-4 rounded bg-gray-50">
|
||||
<Textarea
|
||||
placeholder="Tulis tanggapan Anda di sini..."
|
||||
value={message}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<div className="flex justify-end mt-3">
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => postData()}
|
||||
type="button"
|
||||
>
|
||||
Kirim Komentar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center mt-2 gap-3">
|
||||
<button className="flex items-center text-gray-600 hover:text-gray-800">
|
||||
<PaperclipIcon className="w-5 h-5" />
|
||||
Lampirkan
|
||||
</button>
|
||||
<button className="flex items-center text-gray-600 hover:text-gray-800">
|
||||
<SmileIcon className="w-5 h-5" />
|
||||
Emoticon
|
||||
</button>
|
||||
<button className="ml-auto px-4 py-1 bg-yellow-500 text-white rounded-lg hover:bg-yellow-600">
|
||||
Kirim
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-5">
|
||||
<Label className="text-xl text-black">Komentar</Label>
|
||||
{commentsData.map((comment) => (
|
||||
<div
|
||||
key={comment.id}
|
||||
className="flex items-start gap-3 mt-2"
|
||||
>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src={comment.avatar}
|
||||
alt={`@${comment.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{comment.username}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{comment.date}
|
||||
</span>
|
||||
<p className="text-gray-800 mt-1">{comment.text}</p>
|
||||
<div
|
||||
className="flex items-center mt-1 text-blue-500 cursor-pointer"
|
||||
onClick={() => handleReply(comment.id)}
|
||||
>
|
||||
<DotSquare className="w-4 h-4" />
|
||||
<span className="ml-1">Balas</span>
|
||||
{listData?.map((item: any) => (
|
||||
<div key={item.id} className="flex flex-col gap-3 mt-2 ">
|
||||
<div className="flex flex-row gap-3">
|
||||
<Avatar className="mt-2">
|
||||
<AvatarImage
|
||||
src={"/assets/avatar-profile.png"}
|
||||
alt={`@${item.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{item.messageFrom.fullname}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{formatDate(item.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-800 mt-1">{item.message}</p>
|
||||
<div className="flex flex-row gap-2">
|
||||
{/* <div
|
||||
className="flex items-center mt-1 text-blue-500 cursor-pointer"
|
||||
onClick={() => handleReply(item.id)}
|
||||
>
|
||||
<DotSquare className="w-4 h-4" />
|
||||
<span className="ml-1">Balas</span>
|
||||
</div> */}
|
||||
<div
|
||||
className="flex items-center mt-1 text-red-500 cursor-pointer"
|
||||
onClick={() => deleteData(item.id)}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<span className="ml-1">Delete</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{comment.replies.length > 0 && (
|
||||
<div className="ml-8 mt-2">
|
||||
{comment.replies.map((reply: any, index: any) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-start gap-3 mt-1"
|
||||
>
|
||||
<Avatar>
|
||||
</div>
|
||||
{replyingTo === item.id && (
|
||||
<div className="ml-10 mt-2">
|
||||
<textarea
|
||||
id={`input-comment-${item.id}`}
|
||||
className="w-full p-2 border rounded"
|
||||
placeholder="Masukkan tanggapan anda"
|
||||
/>
|
||||
<button
|
||||
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
|
||||
onClick={() => sendReplyData(item.id)}
|
||||
>
|
||||
Kirim
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{item.children?.length > 0 && (
|
||||
<div className="ml-10 mt-2 flex flex-col">
|
||||
{item.children.map((child: any) => (
|
||||
<div
|
||||
key={child.id}
|
||||
className="flex flex-col gap-3 mt-2"
|
||||
>
|
||||
<div className="flex flex-row gap-3">
|
||||
<Avatar className="mt-2">
|
||||
<AvatarImage
|
||||
src="https://github.com/shadcn.png"
|
||||
alt={`@${reply.username}`}
|
||||
src={"/assets/avatar-profile.png"}
|
||||
alt={`@${child.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{reply.username}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{reply.date}
|
||||
</span>
|
||||
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{item.messageFrom.fullname}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{formatDate(item.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-800 mt-1">
|
||||
{reply.text}
|
||||
{child.message}
|
||||
</p>
|
||||
<div className="flex flex-row gap-2">
|
||||
{/* <div
|
||||
className="flex items-center mt-1 text-blue-500 cursor-pointer"
|
||||
onClick={() => handleReply(child.id)}
|
||||
>
|
||||
<DotSquare className="w-4 h-4" />
|
||||
<span className="ml-1">Balas</span>
|
||||
</div> */}
|
||||
<div
|
||||
className="flex items-center mt-1 text-red-500 cursor-pointer"
|
||||
onClick={() => deleteData(child.id)}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<span className="ml-1">Delete</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{replyingTo === child.id && (
|
||||
<div className="ml-10 mt-2">
|
||||
<textarea
|
||||
id={`input-comment-${child.id}`}
|
||||
className="w-full p-2 border rounded"
|
||||
placeholder="Masukkan tanggapan anda"
|
||||
/>
|
||||
<button
|
||||
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
|
||||
onClick={() => sendReplyData(child.id)}
|
||||
>
|
||||
Kirim
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{child.children?.length > 0 && (
|
||||
<div className="ml-10 mt-2 flex flex-col mb-3">
|
||||
{child.children.map((child2: any) => (
|
||||
<div
|
||||
key={child2.id}
|
||||
className="flex flex-col gap-3 mt-2"
|
||||
>
|
||||
<div className="flex flex-row gap-3 ">
|
||||
<Avatar className="mt-2">
|
||||
<AvatarImage
|
||||
src={"/assets/avatar-profile.png"}
|
||||
alt={`@${child2.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{item.messageFrom.fullname}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{formatDate(item.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-800 mt-1">
|
||||
{child2.message}
|
||||
</p>
|
||||
<div className="flex flex-row gap-2">
|
||||
<div
|
||||
className="flex items-center mt-1 text-red-500 cursor-pointer"
|
||||
onClick={() =>
|
||||
deleteData(child2.id)
|
||||
}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<span className="ml-1">
|
||||
Delete
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{replyingTo !== null && (
|
||||
<div className="mt-4">
|
||||
<textarea
|
||||
className="w-full p-2 border rounded-md"
|
||||
rows={3}
|
||||
placeholder="Tulis balasan..."
|
||||
value={replyText}
|
||||
onChange={(e) => setReplyText(e.target.value)}
|
||||
></textarea>
|
||||
<button
|
||||
className="mt-2 bg-yellow-500 text-white px-4 py-2 rounded-md"
|
||||
onClick={() => addReply(replyingTo)}
|
||||
>
|
||||
Kirim
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "@/components/ui/carousel";
|
||||
import { listCuratedContent } from "@/service/curated-content/curated-content";
|
||||
import { getListContent } from "@/service/landing/landing";
|
||||
import {
|
||||
formatDateToIndonesian,
|
||||
|
|
@ -21,94 +22,102 @@ const TeksSliderPage = () => {
|
|||
const [documentData, setDocumentData] = useState<any>();
|
||||
const [displayDocument, setDisplayDocument] = useState<any[]>([]);
|
||||
const [page, setPage] = useState(1);
|
||||
const [limit, setLimit] = React.useState(10);
|
||||
const [search, setSearch] = React.useState("");
|
||||
const [hasData, setHasData] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
initFetch();
|
||||
}, []);
|
||||
}, [page, limit, search]);
|
||||
|
||||
useEffect(() => {
|
||||
if (documentData?.length > 0) {
|
||||
shuffleAndSetVideos();
|
||||
const interval = setInterval(shuffleAndSetVideos, 5000);
|
||||
return () => clearInterval(interval); // Cleanup interval on unmount
|
||||
}
|
||||
}, [documentData]);
|
||||
// useEffect(() => {
|
||||
// if (documentData?.length > 0) {
|
||||
// shuffleAndSetVideos();
|
||||
// const interval = setInterval(shuffleAndSetVideos, 5000);
|
||||
// return () => clearInterval(interval); // Cleanup interval on unmount
|
||||
// }
|
||||
// }, [documentData]);
|
||||
|
||||
const initFetch = async () => {
|
||||
const response = await getListContent({
|
||||
page: page - 1,
|
||||
size: 12,
|
||||
sortBy: "createdAt",
|
||||
contentTypeId: "3",
|
||||
});
|
||||
const response = await listCuratedContent(search, limit, page - 1, 3, "1");
|
||||
console.log(response);
|
||||
setDocumentData(response?.data?.data?.content);
|
||||
};
|
||||
const shuffleAndSetVideos = () => {
|
||||
const shuffled = shuffleArray([...documentData]);
|
||||
setDisplayDocument(shuffled.slice(0, 2));
|
||||
};
|
||||
|
||||
const shuffleArray = (array: any[]) => {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
return array;
|
||||
const data = response?.data?.data;
|
||||
const contentData = data?.content;
|
||||
setHasData(displayDocument && displayDocument.length > 0);
|
||||
setDisplayDocument(contentData);
|
||||
};
|
||||
// const shuffleAndSetVideos = () => {
|
||||
// const shuffled = shuffleArray([...documentData]);
|
||||
// setDisplayDocument(shuffled.slice(0, 2));
|
||||
// };
|
||||
|
||||
// const shuffleArray = (array: any[]) => {
|
||||
// for (let i = array.length - 1; i > 0; i--) {
|
||||
// const j = Math.floor(Math.random() * (i + 1));
|
||||
// [array[i], array[j]] = [array[j], array[i]];
|
||||
// }
|
||||
// return array;
|
||||
// };
|
||||
|
||||
return (
|
||||
<div className="mx-3 px-5">
|
||||
<div className=" grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{displayDocument?.map((document: any) => (
|
||||
<Link
|
||||
href={`/shared/curated-content/giat-routine/document/detail/${document.id}`}
|
||||
key={document?.id}
|
||||
className="flex flex-col bg-yellow-500 sm:flex-row items-center dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full"
|
||||
>
|
||||
<div className="flex items-center justify-center rounded-lg w-16 h-16">
|
||||
<svg
|
||||
width="28"
|
||||
height="34"
|
||||
viewBox="0 0 28 34"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
<Carousel className="w-full pr-3">
|
||||
<CarouselContent>
|
||||
<CarouselItem>
|
||||
<div className="p-1 flex flex-row md:basis-1/2 lg:basis-1/3 gap-3">
|
||||
{displayDocument?.map((document: any) => (
|
||||
<Link
|
||||
href={`/shared/curated-content/giat-routine/document/detail/${document.id}`}
|
||||
key={document?.id}
|
||||
className="flex flex-col bg-yellow-500 sm:flex-row items-center dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full"
|
||||
>
|
||||
<path
|
||||
d="M5.6665 17.4167C5.6665 17.0851 5.7982 16.7672 6.03262 16.5328C6.26704 16.2984 6.58498 16.1667 6.9165 16.1667C7.24802 16.1667 7.56597 16.2984 7.80039 16.5328C8.03481 16.7672 8.1665 17.0851 8.1665 17.4167C8.1665 17.7482 8.03481 18.0661 7.80039 18.3005C7.56597 18.535 7.24802 18.6667 6.9165 18.6667C6.58498 18.6667 6.26704 18.535 6.03262 18.3005C5.7982 18.0661 5.6665 17.7482 5.6665 17.4167ZM6.9165 21.1667C6.58498 21.1667 6.26704 21.2984 6.03262 21.5328C5.7982 21.7672 5.6665 22.0851 5.6665 22.4167C5.6665 22.7482 5.7982 23.0661 6.03262 23.3005C6.26704 23.535 6.58498 23.6667 6.9165 23.6667C7.24802 23.6667 7.56597 23.535 7.80039 23.3005C8.03481 23.0661 8.1665 22.7482 8.1665 22.4167C8.1665 22.0851 8.03481 21.7672 7.80039 21.5328C7.56597 21.2984 7.24802 21.1667 6.9165 21.1667ZM5.6665 27.4167C5.6665 27.0851 5.7982 26.7672 6.03262 26.5328C6.26704 26.2984 6.58498 26.1667 6.9165 26.1667C7.24802 26.1667 7.56597 26.2984 7.80039 26.5328C8.03481 26.7672 8.1665 27.0851 8.1665 27.4167C8.1665 27.7482 8.03481 28.0661 7.80039 28.3005C7.56597 28.535 7.24802 28.6667 6.9165 28.6667C6.58498 28.6667 6.26704 28.535 6.03262 28.3005C5.7982 28.0661 5.6665 27.7482 5.6665 27.4167ZM11.9165 16.1667C11.585 16.1667 11.267 16.2984 11.0326 16.5328C10.7982 16.7672 10.6665 17.0851 10.6665 17.4167C10.6665 17.7482 10.7982 18.0661 11.0326 18.3005C11.267 18.535 11.585 18.6667 11.9165 18.6667H21.0832C21.4147 18.6667 21.7326 18.535 21.9671 18.3005C22.2015 18.0661 22.3332 17.7482 22.3332 17.4167C22.3332 17.0851 22.2015 16.7672 21.9671 16.5328C21.7326 16.2984 21.4147 16.1667 21.0832 16.1667H11.9165ZM10.6665 22.4167C10.6665 22.0851 10.7982 21.7672 11.0326 21.5328C11.267 21.2984 11.585 21.1667 11.9165 21.1667H21.0832C21.4147 21.1667 21.7326 21.2984 21.9671 21.5328C22.2015 21.7672 22.3332 22.0851 22.3332 22.4167C22.3332 22.7482 22.2015 23.0661 21.9671 23.3005C21.7326 23.535 21.4147 23.6667 21.0832 23.6667H11.9165C11.585 23.6667 11.267 23.535 11.0326 23.3005C10.7982 23.0661 10.6665 22.7482 10.6665 22.4167ZM11.9165 26.1667C11.585 26.1667 11.267 26.2984 11.0326 26.5328C10.7982 26.7672 10.6665 27.0851 10.6665 27.4167C10.6665 27.7482 10.7982 28.0661 11.0326 28.3005C11.267 28.535 11.585 28.6667 11.9165 28.6667H21.0832C21.4147 28.6667 21.7326 28.535 21.9671 28.3005C22.2015 28.0661 22.3332 27.7482 22.3332 27.4167C22.3332 27.0851 22.2015 26.7672 21.9671 26.5328C21.7326 26.2984 21.4147 26.1667 21.0832 26.1667H11.9165ZM26.3565 11.0233L16.6415 1.31C16.6157 1.28605 16.5885 1.26378 16.5598 1.24333C16.5392 1.22742 16.5192 1.21074 16.4998 1.19333C16.3852 1.08512 16.2632 0.984882 16.1348 0.893332C16.0922 0.865802 16.0476 0.841298 16.0015 0.819999L15.9215 0.779999L15.8382 0.731666C15.7482 0.679999 15.6565 0.626665 15.5615 0.586665C15.2296 0.454104 14.8783 0.376423 14.5215 0.356665C14.4885 0.354519 14.4557 0.350625 14.4232 0.344999C14.3779 0.338012 14.3323 0.334114 14.2865 0.333332H3.99984C3.11578 0.333332 2.26794 0.684521 1.64281 1.30964C1.01769 1.93476 0.666504 2.78261 0.666504 3.66667V30.3333C0.666504 31.2174 1.01769 32.0652 1.64281 32.6904C2.26794 33.3155 3.11578 33.6667 3.99984 33.6667H23.9998C24.8839 33.6667 25.7317 33.3155 26.3569 32.6904C26.982 32.0652 27.3332 31.2174 27.3332 30.3333V13.38C27.333 12.496 26.9817 11.6483 26.3565 11.0233ZM24.8332 30.3333C24.8332 30.5543 24.7454 30.7663 24.5891 30.9226C24.4328 31.0789 24.2208 31.1667 23.9998 31.1667H3.99984C3.77882 31.1667 3.56686 31.0789 3.41058 30.9226C3.2543 30.7663 3.1665 30.5543 3.1665 30.3333V3.66667C3.1665 3.44565 3.2543 3.23369 3.41058 3.07741C3.56686 2.92113 3.77882 2.83333 3.99984 2.83333H13.9998V10.3333C13.9998 11.2174 14.351 12.0652 14.9761 12.6904C15.6013 13.3155 16.4491 13.6667 17.3332 13.6667H24.8332V30.3333ZM16.4998 4.70166L22.9632 11.1667H17.3332C17.1122 11.1667 16.9002 11.0789 16.7439 10.9226C16.5876 10.7663 16.4998 10.5543 16.4998 10.3333V4.70166Z"
|
||||
fill="black"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="flex items-center justify-center rounded-lg w-16 h-16">
|
||||
<svg
|
||||
width="28"
|
||||
height="34"
|
||||
viewBox="0 0 28 34"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5.6665 17.4167C5.6665 17.0851 5.7982 16.7672 6.03262 16.5328C6.26704 16.2984 6.58498 16.1667 6.9165 16.1667C7.24802 16.1667 7.56597 16.2984 7.80039 16.5328C8.03481 16.7672 8.1665 17.0851 8.1665 17.4167C8.1665 17.7482 8.03481 18.0661 7.80039 18.3005C7.56597 18.535 7.24802 18.6667 6.9165 18.6667C6.58498 18.6667 6.26704 18.535 6.03262 18.3005C5.7982 18.0661 5.6665 17.7482 5.6665 17.4167ZM6.9165 21.1667C6.58498 21.1667 6.26704 21.2984 6.03262 21.5328C5.7982 21.7672 5.6665 22.0851 5.6665 22.4167C5.6665 22.7482 5.7982 23.0661 6.03262 23.3005C6.26704 23.535 6.58498 23.6667 6.9165 23.6667C7.24802 23.6667 7.56597 23.535 7.80039 23.3005C8.03481 23.0661 8.1665 22.7482 8.1665 22.4167C8.1665 22.0851 8.03481 21.7672 7.80039 21.5328C7.56597 21.2984 7.24802 21.1667 6.9165 21.1667ZM5.6665 27.4167C5.6665 27.0851 5.7982 26.7672 6.03262 26.5328C6.26704 26.2984 6.58498 26.1667 6.9165 26.1667C7.24802 26.1667 7.56597 26.2984 7.80039 26.5328C8.03481 26.7672 8.1665 27.0851 8.1665 27.4167C8.1665 27.7482 8.03481 28.0661 7.80039 28.3005C7.56597 28.535 7.24802 28.6667 6.9165 28.6667C6.58498 28.6667 6.26704 28.535 6.03262 28.3005C5.7982 28.0661 5.6665 27.7482 5.6665 27.4167ZM11.9165 16.1667C11.585 16.1667 11.267 16.2984 11.0326 16.5328C10.7982 16.7672 10.6665 17.0851 10.6665 17.4167C10.6665 17.7482 10.7982 18.0661 11.0326 18.3005C11.267 18.535 11.585 18.6667 11.9165 18.6667H21.0832C21.4147 18.6667 21.7326 18.535 21.9671 18.3005C22.2015 18.0661 22.3332 17.7482 22.3332 17.4167C22.3332 17.0851 22.2015 16.7672 21.9671 16.5328C21.7326 16.2984 21.4147 16.1667 21.0832 16.1667H11.9165ZM10.6665 22.4167C10.6665 22.0851 10.7982 21.7672 11.0326 21.5328C11.267 21.2984 11.585 21.1667 11.9165 21.1667H21.0832C21.4147 21.1667 21.7326 21.2984 21.9671 21.5328C22.2015 21.7672 22.3332 22.0851 22.3332 22.4167C22.3332 22.7482 22.2015 23.0661 21.9671 23.3005C21.7326 23.535 21.4147 23.6667 21.0832 23.6667H11.9165C11.585 23.6667 11.267 23.535 11.0326 23.3005C10.7982 23.0661 10.6665 22.7482 10.6665 22.4167ZM11.9165 26.1667C11.585 26.1667 11.267 26.2984 11.0326 26.5328C10.7982 26.7672 10.6665 27.0851 10.6665 27.4167C10.6665 27.7482 10.7982 28.0661 11.0326 28.3005C11.267 28.535 11.585 28.6667 11.9165 28.6667H21.0832C21.4147 28.6667 21.7326 28.535 21.9671 28.3005C22.2015 28.0661 22.3332 27.7482 22.3332 27.4167C22.3332 27.0851 22.2015 26.7672 21.9671 26.5328C21.7326 26.2984 21.4147 26.1667 21.0832 26.1667H11.9165ZM26.3565 11.0233L16.6415 1.31C16.6157 1.28605 16.5885 1.26378 16.5598 1.24333C16.5392 1.22742 16.5192 1.21074 16.4998 1.19333C16.3852 1.08512 16.2632 0.984882 16.1348 0.893332C16.0922 0.865802 16.0476 0.841298 16.0015 0.819999L15.9215 0.779999L15.8382 0.731666C15.7482 0.679999 15.6565 0.626665 15.5615 0.586665C15.2296 0.454104 14.8783 0.376423 14.5215 0.356665C14.4885 0.354519 14.4557 0.350625 14.4232 0.344999C14.3779 0.338012 14.3323 0.334114 14.2865 0.333332H3.99984C3.11578 0.333332 2.26794 0.684521 1.64281 1.30964C1.01769 1.93476 0.666504 2.78261 0.666504 3.66667V30.3333C0.666504 31.2174 1.01769 32.0652 1.64281 32.6904C2.26794 33.3155 3.11578 33.6667 3.99984 33.6667H23.9998C24.8839 33.6667 25.7317 33.3155 26.3569 32.6904C26.982 32.0652 27.3332 31.2174 27.3332 30.3333V13.38C27.333 12.496 26.9817 11.6483 26.3565 11.0233ZM24.8332 30.3333C24.8332 30.5543 24.7454 30.7663 24.5891 30.9226C24.4328 31.0789 24.2208 31.1667 23.9998 31.1667H3.99984C3.77882 31.1667 3.56686 31.0789 3.41058 30.9226C3.2543 30.7663 3.1665 30.5543 3.1665 30.3333V3.66667C3.1665 3.44565 3.2543 3.23369 3.41058 3.07741C3.56686 2.92113 3.77882 2.83333 3.99984 2.83333H13.9998V10.3333C13.9998 11.2174 14.351 12.0652 14.9761 12.6904C15.6013 13.3155 16.4491 13.6667 17.3332 13.6667H24.8332V30.3333ZM16.4998 4.70166L22.9632 11.1667H17.3332C17.1122 11.1667 16.9002 11.0789 16.7439 10.9226C16.5876 10.7663 16.4998 10.5543 16.4998 10.3333V4.70166Z"
|
||||
fill="black"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col flex-1">
|
||||
<div className="text-gray-500 dark:text-gray-400 flex flex-row text-sm">
|
||||
{formatDateToIndonesian(new Date(document?.createdAt))}{" "}
|
||||
{document?.timezone ? document?.timezone : "WIB"} |{" "}
|
||||
<Icon icon="formkit:eye" width="15" height="15" /> 518
|
||||
</div>
|
||||
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm">
|
||||
{document?.title}
|
||||
</div>
|
||||
<div className="flex gap-2 items-center text-sm text-red-500 dark:text-red-500">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
fill="#f00"
|
||||
d="M224 30v256h-64l96 128l96-128h-64V30zM32 434v48h448v-48z"
|
||||
/>
|
||||
</svg>
|
||||
Download Dokumen
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col flex-1">
|
||||
<div className="text-gray-500 dark:text-gray-400 flex flex-row text-sm">
|
||||
{formatDateToIndonesian(new Date(document?.createdAt))}{" "}
|
||||
{document?.timezone ? document?.timezone : "WIB"} |{" "}
|
||||
<Icon icon="formkit:eye" width="15" height="15" /> 518
|
||||
</div>
|
||||
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm">
|
||||
{document?.title}
|
||||
</div>
|
||||
<div className="flex gap-2 items-center text-sm text-red-500 dark:text-red-500">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
fill="#f00"
|
||||
d="M224 30v256h-64l96 128l96-128h-64V30zM32 434v48h448v-48z"
|
||||
/>
|
||||
</svg>
|
||||
Download Dokumen
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</CarouselItem>
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ import {
|
|||
} from "@/service/task";
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
import { close, loading } from "@/lib/swal";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { htmlToString } from "@/utils/globals";
|
||||
|
||||
const detailSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -107,6 +109,13 @@ export type curationDetail = {
|
|||
userGroupId: number;
|
||||
};
|
||||
};
|
||||
publishedFor: string; // ID for selected radio button
|
||||
publishedForObject: {
|
||||
id: number;
|
||||
name: string;
|
||||
isInternal: boolean;
|
||||
code: string;
|
||||
}[];
|
||||
tags: string;
|
||||
provinceId: string;
|
||||
is_active: string;
|
||||
|
|
@ -173,6 +182,7 @@ export default function DetailImage() {
|
|||
const [commentsData, setCommentsData] = useState(initialComments);
|
||||
const [replyText, setReplyText] = useState("");
|
||||
const [replyingTo, setReplyingTo] = useState<number | null>(null);
|
||||
const [selectedValue, setSelectedValue] = useState<string>("");
|
||||
|
||||
const handleReply = (commentId: number) => {
|
||||
setReplyingTo(commentId);
|
||||
|
|
@ -272,11 +282,13 @@ export default function DetailImage() {
|
|||
const details = response?.data?.data;
|
||||
|
||||
setDetail(details);
|
||||
setSelectedValue(details?.publishedFor || "");
|
||||
setSelectedFileId(details?.files[0]?.id);
|
||||
const filesData = details.files || [];
|
||||
const fileUrls = filesData.map((file: any) => ({
|
||||
id: file.id,
|
||||
thumbnailFileUrl: file.thumbnailFileUrl || "default-image.jpg",
|
||||
placements: file.placements || "",
|
||||
}));
|
||||
setDetailThumb(fileUrls);
|
||||
}
|
||||
|
|
@ -294,6 +306,9 @@ export default function DetailImage() {
|
|||
console.error("Error fetching comments:", error);
|
||||
}
|
||||
};
|
||||
const handleValueChange = (value: string) => {
|
||||
setSelectedValue(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex gap-10">
|
||||
|
|
@ -302,8 +317,8 @@ export default function DetailImage() {
|
|||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Kurasi Detail</p>
|
||||
<CardContent className="border rounded-md">
|
||||
<div className="flex flex-row gap-10">
|
||||
<div className="w-6/12">
|
||||
<div className="flex flex-col sm:flex-row lg:flex-row lg:gap-10">
|
||||
<div className="w-full lg:w-6/12">
|
||||
<div className="gap-5 mb-5">
|
||||
<div className="space-y-2 py-3">
|
||||
<Label>Judul</Label>
|
||||
|
|
@ -354,7 +369,7 @@ export default function DetailImage() {
|
|||
name="description"
|
||||
render={({ field }) => (
|
||||
<Textarea
|
||||
value={detail.description}
|
||||
value={htmlToString(detail.description)}
|
||||
onChange={field.onChange}
|
||||
placeholder="Enter Meta"
|
||||
cols={5}
|
||||
|
|
@ -370,29 +385,30 @@ export default function DetailImage() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-6/12">
|
||||
<div className="w-full lg:w-6/12">
|
||||
<div className="gap-5 mb-5">
|
||||
<div className="mt-5">
|
||||
<Label>Jenis Penugasan</Label>
|
||||
<RadioGroup
|
||||
// value={type} // State yang dipetakan ke value RadioGroup
|
||||
// onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
|
||||
value={selectedValue} // Set selected value
|
||||
onValueChange={handleValueChange} // Update state on change
|
||||
className="flex flex-wrap gap-3"
|
||||
>
|
||||
{/* Static list of radio buttons */}
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="1" id="publication" />
|
||||
<RadioGroupItem value="5" id="umum" />
|
||||
<Label htmlFor="umum">Umum</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="2" id="amplification" />
|
||||
<RadioGroupItem value="8" id="ksp" />
|
||||
<Label htmlFor="ksp">Ksp</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="3" id="contra" />
|
||||
<RadioGroupItem value="6" id="journalist" />
|
||||
<Label htmlFor="journalist">Journalist</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="4" id="contra" />
|
||||
<RadioGroupItem value="7" id="polri" />
|
||||
<Label htmlFor="polri">Polri</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
|
|
@ -434,8 +450,8 @@ export default function DetailImage() {
|
|||
</div>
|
||||
</CardContent>
|
||||
<CardContent className="border rounded-md mt-5">
|
||||
<div className="flex flex-row gap-5">
|
||||
<div className="w-6/12 border px-3 mt-3 rounded-md">
|
||||
<div className="flex flex-col lg:flex-row gap-5">
|
||||
<div className="w-full lg:w-6/12 border px-3 mt-3 rounded-md">
|
||||
<div className="gap-5 mb-5">
|
||||
<div className="space-y-2 py-3">
|
||||
<Label className="text-xl text-black">File Media</Label>
|
||||
|
|
@ -452,7 +468,7 @@ export default function DetailImage() {
|
|||
onClick={() => handleFileClick(data.id)}
|
||||
>
|
||||
<img
|
||||
className="object-fill h-full w-full"
|
||||
className="object-fill h-full w-full rounded-md"
|
||||
src={data.thumbnailFileUrl}
|
||||
alt={`File ID: ${data.id}`}
|
||||
/>
|
||||
|
|
@ -476,7 +492,7 @@ export default function DetailImage() {
|
|||
onClick={() => handleFileClick(data.id)}
|
||||
>
|
||||
<img
|
||||
className="object-fill h-full w-full"
|
||||
className="object-fill h-full w-full rounded-md"
|
||||
src={data.thumbnailFileUrl}
|
||||
alt={`File ID: ${data.id}`}
|
||||
/>
|
||||
|
|
@ -488,7 +504,7 @@ export default function DetailImage() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-6/12 border px-3 rounded-md mt-3">
|
||||
<div className="w-full lg:w-6/12 border px-3 rounded-md mt-3">
|
||||
<div className="gap-5 mb-5">
|
||||
<div className="mt-5">
|
||||
<Label className="text-xl text-black">
|
||||
|
|
@ -510,34 +526,36 @@ export default function DetailImage() {
|
|||
alt={`Thumbnail ${index}`}
|
||||
/> */}
|
||||
<img
|
||||
className="object-cover w-36 h-32"
|
||||
src={data}
|
||||
className="object-cover h-20 w-20 lg:w-36 lg:h-32 rounded-md"
|
||||
src={data.thumbnailFileUrl}
|
||||
alt={`Article ${data.id}`}
|
||||
/>
|
||||
<div className="flex flex-row gap-3 items-center">
|
||||
<label className="text-blue-500 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value="Nasional"
|
||||
<div className="flex flex-wrap lg:flex-row gap-3 items-center">
|
||||
{/* Mabes Checkbox */}
|
||||
<label className=" cursor-pointer flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={data.placements === "mabes"} // Automatically checks if placement matches
|
||||
disabled // To reflect read-only behavior
|
||||
/>
|
||||
Nasional
|
||||
<span>Nasional</span>
|
||||
</label>
|
||||
<label className="text-blue-500 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value="Wilayah"
|
||||
|
||||
{/* Polda Checkbox */}
|
||||
<label className=" cursor-pointer flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={data.placements === "polda"} // Automatically checks if placement matches
|
||||
disabled
|
||||
/>
|
||||
Wilayah
|
||||
<span>Wilayah</span>
|
||||
</label>
|
||||
<label className="text-blue-500 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value="International"
|
||||
|
||||
{/* International Checkbox */}
|
||||
<label className=" cursor-pointer flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={data.placements === "international"} // Automatically checks if placement matches
|
||||
disabled
|
||||
/>
|
||||
International
|
||||
<span>International</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -43,13 +43,13 @@ const ImageSliderPage = () => {
|
|||
fetchData();
|
||||
}, [page, limit, search]);
|
||||
|
||||
useEffect(() => {
|
||||
if (imageData?.length > 0) {
|
||||
shuffleAndSetVideos();
|
||||
const interval = setInterval(shuffleAndSetVideos, 5000);
|
||||
return () => clearInterval(interval); // Cleanup interval on unmount
|
||||
}
|
||||
}, [imageData]);
|
||||
// useEffect(() => {
|
||||
// if (imageData?.length > 0) {
|
||||
// shuffleAndSetVideos();
|
||||
// const interval = setInterval(shuffleAndSetVideos, 5000);
|
||||
// return () => clearInterval(interval); // Cleanup interval on unmount
|
||||
// }
|
||||
// }, [imageData]);
|
||||
|
||||
const fetchData = async () => {
|
||||
const response = await listCuratedContent(search, limit, page - 1, 1, "1");
|
||||
|
|
@ -60,60 +60,73 @@ const ImageSliderPage = () => {
|
|||
setImageData(contentData);
|
||||
};
|
||||
|
||||
const shuffleAndSetVideos = () => {
|
||||
const shuffled = shuffleArray([...imageData]);
|
||||
setDisplayImage(shuffled.slice(0, 3));
|
||||
};
|
||||
// const shuffleAndSetVideos = () => {
|
||||
// const shuffled = shuffleArray([...imageData]);
|
||||
// setDisplayImage(shuffled.slice(0, 3));
|
||||
// };
|
||||
|
||||
const shuffleArray = (array: any[]) => {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
return array;
|
||||
};
|
||||
// const shuffleArray = (array: any[]) => {
|
||||
// for (let i = array.length - 1; i > 0; i--) {
|
||||
// const j = Math.floor(Math.random() * (i + 1));
|
||||
// [array[i], array[j]] = [array[j], array[i]];
|
||||
// }
|
||||
// return array;
|
||||
// };
|
||||
return (
|
||||
<div className="mx-3 px-5">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{displayImage?.map((image: any) => (
|
||||
<Card
|
||||
key={image?.id}
|
||||
className="hover:sc ale-110 transition-transform duration-300"
|
||||
>
|
||||
<Link
|
||||
href={`/shared/curated-content//giat-routine/image/detail/${image.id}`}
|
||||
>
|
||||
<CardContent className="flex flex-col text-xs lg:text-sm w-full p-0">
|
||||
<img
|
||||
src={image?.thumbnailLink}
|
||||
className="h-60 object-cover items-center justify-center cursor-pointer rounded-lg"
|
||||
/>
|
||||
<div className="flex flex-row items-center gap-2 text-[10px] mx-2">
|
||||
{formatDateToIndonesian(new Date(image?.createdAt))}{" "}
|
||||
{image?.timezone ? image?.timezone : "WIB"}|{" "}
|
||||
<Icon icon="formkit:eye" width="15" height="15" />
|
||||
{image?.clickCount}{" "}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 20 20"
|
||||
<Carousel className="w-full pr-3">
|
||||
<CarouselContent>
|
||||
{Array.from({ length: 5 }).map((_, index) => (
|
||||
<CarouselItem key={index}>
|
||||
<div className="p-1 flex flex-row md:basis-1/2 lg:basis-1/2 gap-3">
|
||||
{imageData?.map((image: any) => (
|
||||
<Card
|
||||
key={image?.id}
|
||||
className="hover:sc ale-110 transition-transform duration-300"
|
||||
>
|
||||
<Link
|
||||
href={`/shared/curated-content//giat-routine/image/detail/${image.id}`}
|
||||
>
|
||||
<path
|
||||
fill="#f00"
|
||||
d="M7.707 10.293a1 1 0 1 0-1.414 1.414l3 3a1 1 0 0 0 1.414 0l3-3a1 1 0 0 0-1.414-1.414L11 11.586V6h5a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h5v5.586zM9 4a1 1 0 0 1 2 0v2H9z"
|
||||
/>
|
||||
</svg>{" "}
|
||||
</div>
|
||||
<div className="font-semibold pr-3 pb-3 mx-2 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible w-full">
|
||||
{image?.title}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Link>
|
||||
</Card>
|
||||
<CardContent className="flex flex-col text-xs lg:text-sm w-full p-0">
|
||||
<img
|
||||
src={image?.thumbnailLink}
|
||||
className="h-60 object-cover items-center justify-center cursor-pointer rounded-lg"
|
||||
/>
|
||||
<div className="flex flex-row items-center gap-2 text-[10px] mx-2">
|
||||
{formatDateToIndonesian(new Date(image?.createdAt))}{" "}
|
||||
{image?.timezone ? image?.timezone : "WIB"}|{" "}
|
||||
<Icon icon="formkit:eye" width="15" height="15" />
|
||||
{image?.clickCount}{" "}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fill="#f00"
|
||||
d="M7.707 10.293a1 1 0 1 0-1.414 1.414l3 3a1 1 0 0 0 1.414 0l3-3a1 1 0 0 0-1.414-1.414L11 11.586V6h5a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h5v5.586zM9 4a1 1 0 0 1 2 0v2H9z"
|
||||
/>
|
||||
</svg>{" "}
|
||||
</div>
|
||||
<div className="font-semibold pr-3 pb-3 mx-2 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible w-full">
|
||||
{image?.title}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Link>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
// <div className="mx-3 px-5">
|
||||
// <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
|
||||
// </div>
|
||||
// </div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
"use client";
|
||||
import { Link } from "@/components/navigation";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "@/components/ui/carousel";
|
||||
import { listCuratedContent } from "@/service/curated-content/curated-content";
|
||||
import { getListContent } from "@/service/landing/landing";
|
||||
import { formatDateToIndonesian } from "@/utils/globals";
|
||||
|
|
@ -19,13 +26,13 @@ const VideoSliderPage = () => {
|
|||
fetchData();
|
||||
}, [page, limit, search]);
|
||||
|
||||
useEffect(() => {
|
||||
if (allVideoData?.length > 0) {
|
||||
shuffleAndSetVideos();
|
||||
const interval = setInterval(shuffleAndSetVideos, 5000);
|
||||
return () => clearInterval(interval); // Cleanup interval on unmount
|
||||
}
|
||||
}, [allVideoData]);
|
||||
// useEffect(() => {
|
||||
// if (allVideoData?.length > 0) {
|
||||
// shuffleAndSetVideos();
|
||||
// const interval = setInterval(shuffleAndSetVideos, 5000);
|
||||
// return () => clearInterval(interval); // Cleanup interval on unmount
|
||||
// }
|
||||
// }, [allVideoData]);
|
||||
|
||||
const fetchData = async () => {
|
||||
const response = await listCuratedContent(search, limit, page - 1, 2, "1");
|
||||
|
|
@ -36,61 +43,69 @@ const VideoSliderPage = () => {
|
|||
setAllVideoData(contentData);
|
||||
};
|
||||
|
||||
const shuffleAndSetVideos = () => {
|
||||
const shuffled = shuffleArray([...allVideoData]);
|
||||
setDisplayVideos(shuffled.slice(0, 3));
|
||||
};
|
||||
// const shuffleAndSetVideos = () => {
|
||||
// const shuffled = shuffleArray([...allVideoData]);
|
||||
// setDisplayVideos(shuffled.slice(0, 3));
|
||||
// };
|
||||
|
||||
const shuffleArray = (array: any[]) => {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
return array;
|
||||
};
|
||||
// const shuffleArray = (array: any[]) => {
|
||||
// for (let i = array.length - 1; i > 0; i--) {
|
||||
// const j = Math.floor(Math.random() * (i + 1));
|
||||
// [array[i], array[j]] = [array[j], array[i]];
|
||||
// }
|
||||
// return array;
|
||||
// };
|
||||
|
||||
return (
|
||||
<div className="mx-3 px-5">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{displayVideos.map((video: any) => (
|
||||
<Card
|
||||
key={video?.id}
|
||||
className="hover:scale-110 transition-transform duration-300"
|
||||
>
|
||||
<Link
|
||||
href={`/shared/curated-content/giat-routine/video/detail/${video.id}`}
|
||||
>
|
||||
<CardContent className="flex flex-col text-xs lg:text-sm w-full p-0">
|
||||
<img
|
||||
src={video?.thumbnailLink}
|
||||
className="h-60 object-cover items-center justify-center cursor-pointer rounded-lg"
|
||||
/>
|
||||
<div className="flex flex-row items-center gap-2 text-[10px] mx-2">
|
||||
{formatDateToIndonesian(new Date(video?.createdAt))}{" "}
|
||||
{video?.timezone || "WIB"} |
|
||||
<Icon icon="formkit:eye" width="15" height="15" />
|
||||
{video?.clickCount}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 20 20"
|
||||
<Carousel className="w-full pr-3">
|
||||
<CarouselContent>
|
||||
{Array.from({ length: 5 }).map((_, index) => (
|
||||
<CarouselItem key={index}>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{allVideoData.map((video: any) => (
|
||||
<Card
|
||||
key={video?.id}
|
||||
className="hover:scale-110 transition-transform duration-300"
|
||||
>
|
||||
<Link
|
||||
href={`/shared/curated-content/giat-routine/video/detail/${video.id}`}
|
||||
>
|
||||
<path
|
||||
fill="#f00"
|
||||
d="M7.707 10.293a1 1 0 1 0-1.414 1.414l3 3a1 1 0 0 0 1.414 0l3-3a1 1 0 0 0-1.414-1.414L11 11.586V6h5a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h5v5.586zM9 4a1 1 0 0 1 2 0v2H9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="font-semibold pr-3 pb-3 mx-2 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible w-full">
|
||||
{video?.title}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Link>
|
||||
</Card>
|
||||
<CardContent className="flex flex-col text-xs lg:text-sm w-full p-0">
|
||||
<img
|
||||
src={video?.thumbnailLink}
|
||||
className="h-60 object-cover items-center justify-center cursor-pointer rounded-lg"
|
||||
/>
|
||||
<div className="flex flex-row items-center gap-2 text-[10px] mx-2">
|
||||
{formatDateToIndonesian(new Date(video?.createdAt))}{" "}
|
||||
{video?.timezone || "WIB"} |
|
||||
<Icon icon="formkit:eye" width="15" height="15" />
|
||||
{video?.clickCount}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fill="#f00"
|
||||
d="M7.707 10.293a1 1 0 1 0-1.414 1.414l3 3a1 1 0 0 0 1.414 0l3-3a1 1 0 0 0-1.414-1.414L11 11.586V6h5a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h5v5.586zM9 4a1 1 0 0 1 2 0v2H9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="font-semibold pr-3 pb-3 mx-2 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible w-full">
|
||||
{video?.title}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Link>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -21,8 +21,19 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|||
import Cookies from "js-cookie";
|
||||
import { postBlog } from "@/service/blog/blog";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { DotSquare, InboxIcon, PaperclipIcon, SmileIcon } from "lucide-react";
|
||||
import { detailMedia } from "@/service/curated-content/curated-content";
|
||||
import {
|
||||
DotSquare,
|
||||
InboxIcon,
|
||||
PaperclipIcon,
|
||||
SmileIcon,
|
||||
TrashIcon,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
deleteMediaCurationMessage,
|
||||
detailMedia,
|
||||
getMediaCurationMessage,
|
||||
saveMediaCurationMessage,
|
||||
} from "@/service/curated-content/curated-content";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import "swiper/css";
|
||||
import "swiper/css/free-mode";
|
||||
|
|
@ -34,6 +45,11 @@ import "swiper/css/navigation";
|
|||
import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
|
||||
import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
import { formatDate } from "@fullcalendar/core/index.js";
|
||||
import { loading } from "@/lib/swal";
|
||||
import { htmlToString } from "@/utils/globals";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
|
||||
const detailSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -70,6 +86,13 @@ export type curationDetail = {
|
|||
userGroupId: number;
|
||||
};
|
||||
};
|
||||
publishedFor: string; // ID for selected radio button
|
||||
publishedForObject: {
|
||||
id: number;
|
||||
name: string;
|
||||
isInternal: boolean;
|
||||
code: string;
|
||||
}[];
|
||||
tags: string;
|
||||
provinceId: string;
|
||||
is_active: string;
|
||||
|
|
@ -108,7 +131,7 @@ export default function DetailImage() {
|
|||
console.log(id);
|
||||
const editor = useRef(null);
|
||||
type DetailSchema = z.infer<typeof detailSchema>;
|
||||
|
||||
const userLevelNumber = getCookiesDecrypt("ulne");
|
||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||
const taskId = Cookies.get("taskId");
|
||||
const scheduleId = Cookies.get("scheduleId");
|
||||
|
|
@ -125,6 +148,7 @@ export default function DetailImage() {
|
|||
const [detailVideo, setDetailVideo] = useState<any>([]);
|
||||
const [detailThumb, setDetailThumb] = useState<any>([]);
|
||||
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
||||
const [selectedValue, setSelectedValue] = useState<string>("");
|
||||
|
||||
const {
|
||||
control,
|
||||
|
|
@ -138,59 +162,156 @@ export default function DetailImage() {
|
|||
const [commentsData, setCommentsData] = useState(initialComments);
|
||||
const [replyText, setReplyText] = useState("");
|
||||
const [replyingTo, setReplyingTo] = useState<number | null>(null);
|
||||
const [selectedFileId, setSelectedFileId] = useState(null);
|
||||
const [listData, setListData] = useState([]);
|
||||
const [message, setMessage] = useState("");
|
||||
|
||||
const handleReply = (commentId: number) => {
|
||||
setReplyingTo(commentId);
|
||||
};
|
||||
|
||||
const addReply = (commentId: number) => {
|
||||
if (replyText.trim()) {
|
||||
const newCommentData = commentsData.map((comment: any) => {
|
||||
if (comment.id === commentId) {
|
||||
return {
|
||||
...comment,
|
||||
replies: [
|
||||
...comment.replies,
|
||||
{
|
||||
text: replyText,
|
||||
username: "You",
|
||||
date: new Date().toLocaleString(),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return comment;
|
||||
});
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
// loading();
|
||||
const response = await getMediaCurationMessage(selectedFileId);
|
||||
console.log("data", response?.data?.data);
|
||||
console.log("userLvl", userLevelNumber);
|
||||
setListData(response?.data?.data);
|
||||
close();
|
||||
}
|
||||
|
||||
setCommentsData(newCommentData);
|
||||
setReplyText("");
|
||||
initState();
|
||||
}, [selectedFileId]);
|
||||
|
||||
const postData = async () => {
|
||||
if (message?.length > 1 && selectedFileId) {
|
||||
try {
|
||||
const data = {
|
||||
mediaUploadFileId: selectedFileId,
|
||||
message,
|
||||
parentId: null,
|
||||
};
|
||||
|
||||
const response = await saveMediaCurationMessage(data);
|
||||
console.log("Komentar terkirim:", response);
|
||||
|
||||
const responseGet = await getMediaCurationMessage(selectedFileId);
|
||||
setListData(responseGet?.data?.data);
|
||||
|
||||
setMessage("");
|
||||
} catch (error) {
|
||||
console.error("Error posting comment:", error);
|
||||
}
|
||||
} else {
|
||||
console.log("Pesan atau file ID tidak valid.");
|
||||
}
|
||||
};
|
||||
|
||||
const sendReplyData = async (parentId: number) => {
|
||||
const inputElement = document.querySelector(
|
||||
`#input-comment-${parentId}`
|
||||
) as HTMLTextAreaElement;
|
||||
|
||||
if (inputElement?.value?.length > 1 && selectedFileId) {
|
||||
loading();
|
||||
const data = {
|
||||
mediaUploadFileId: selectedFileId,
|
||||
message: inputElement.value,
|
||||
parentId,
|
||||
};
|
||||
|
||||
console.log("Sending reply:", data);
|
||||
const response = await saveMediaCurationMessage(data);
|
||||
console.log(response);
|
||||
|
||||
const responseGet = await getMediaCurationMessage(selectedFileId);
|
||||
console.log("Updated comments:", responseGet?.data?.data);
|
||||
setListData(responseGet?.data?.data);
|
||||
|
||||
inputElement.value = "";
|
||||
close();
|
||||
setReplyingTo(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setMessage(e.target.value);
|
||||
};
|
||||
|
||||
// useEffect(() => {
|
||||
// async function initState() {
|
||||
// // loading();
|
||||
// const response = await getMediaCurationMessage(selectedFileId);
|
||||
// console.log("data", response?.data?.data);
|
||||
// console.log("userLvl", userLevelNumber);
|
||||
// setListData(response?.data?.data);
|
||||
// close();
|
||||
// }
|
||||
|
||||
// initState();
|
||||
// }, [selectedFileId]);
|
||||
|
||||
async function deleteDataSuggestion(dataId: any) {
|
||||
loading();
|
||||
const response = await deleteMediaCurationMessage(dataId);
|
||||
console.log(response);
|
||||
const responseGet = await getMediaCurationMessage(selectedFileId);
|
||||
console.log(responseGet?.data?.data);
|
||||
setListData(responseGet?.data?.data);
|
||||
close();
|
||||
}
|
||||
|
||||
const deleteData = (dataId: any) => {
|
||||
deleteDataSuggestion(dataId);
|
||||
console.log(dataId);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
const response = await detailMedia(id);
|
||||
const details = response?.data?.data;
|
||||
|
||||
setDetail(details);
|
||||
const filesData = details.files || [];
|
||||
const fileUrls = filesData.map((file: { url: string }) =>
|
||||
file.url ? file.url : "default-image.jpg"
|
||||
);
|
||||
const fileUrls = filesData.map((file: any) => ({
|
||||
id: file.id,
|
||||
url: file.url || "default-image.jpg",
|
||||
placements: file.placements || "",
|
||||
}));
|
||||
setDetail(details);
|
||||
setSelectedValue(details?.publishedFor || "");
|
||||
setSelectedFileId(details?.files[0]?.id);
|
||||
setDetailVideo(fileUrls);
|
||||
const filesDataThumbnail = details.files || [];
|
||||
const fileUrlsThumbnail = filesDataThumbnail.map(
|
||||
(file: { thumbnailFileUrl: string }) =>
|
||||
file.thumbnailFileUrl ? file.thumbnailFileUrl : "default-image.jpg"
|
||||
const fileUrlsThumbnail = filesData.map(
|
||||
(file: { thumbnailFileUrl: string; placements: string }) => ({
|
||||
thumbnailFileUrl: file.thumbnailFileUrl
|
||||
? file.thumbnailFileUrl
|
||||
: "default-image.jpg",
|
||||
placements: file.placements || "",
|
||||
})
|
||||
);
|
||||
|
||||
setDetailThumb(fileUrlsThumbnail);
|
||||
}
|
||||
}
|
||||
initState();
|
||||
}, [id, refresh]);
|
||||
|
||||
const handleFileClick = async (fileId: any) => {
|
||||
setSelectedFileId(fileId);
|
||||
try {
|
||||
const response = await getMediaCurationMessage(fileId);
|
||||
console.log("Data komentar:", response?.data?.data);
|
||||
setListData(response?.data?.data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching comments:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleValueChange = (value: string) => {
|
||||
setSelectedValue(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex gap-10">
|
||||
{detail !== undefined ? (
|
||||
|
|
@ -198,8 +319,8 @@ export default function DetailImage() {
|
|||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Kurasi Detail</p>
|
||||
<CardContent className="border rounded-md">
|
||||
<div className="flex flex-row gap-10">
|
||||
<div className="w-6/12">
|
||||
<div className="flex flex-col sm:flex-row lg:flex-row lg:gap-10">
|
||||
<div className="w-full lg:w-6/12">
|
||||
<div className="gap-5 mb-5">
|
||||
<div className="space-y-2 py-3">
|
||||
<Label>Judul</Label>
|
||||
|
|
@ -250,7 +371,7 @@ export default function DetailImage() {
|
|||
name="description"
|
||||
render={({ field }) => (
|
||||
<Textarea
|
||||
value={detail.description}
|
||||
value={htmlToString(detail.description)}
|
||||
onChange={field.onChange}
|
||||
placeholder="Enter Meta"
|
||||
cols={5}
|
||||
|
|
@ -266,29 +387,30 @@ export default function DetailImage() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-6/12">
|
||||
<div className="w-full lg:w-6/12">
|
||||
<div className="gap-5 mb-5">
|
||||
<div className="mt-5">
|
||||
<Label>Jenis Penugasan</Label>
|
||||
<RadioGroup
|
||||
// value={type} // State yang dipetakan ke value RadioGroup
|
||||
// onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
|
||||
value={selectedValue} // Set selected value
|
||||
onValueChange={handleValueChange} // Update state on change
|
||||
className="flex flex-wrap gap-3"
|
||||
>
|
||||
{/* Static list of radio buttons */}
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="1" id="publication" />
|
||||
<RadioGroupItem value="5" id="umum" />
|
||||
<Label htmlFor="umum">Umum</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="2" id="amplification" />
|
||||
<RadioGroupItem value="8" id="ksp" />
|
||||
<Label htmlFor="ksp">Ksp</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="3" id="contra" />
|
||||
<RadioGroupItem value="6" id="journalist" />
|
||||
<Label htmlFor="journalist">Journalist</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="4" id="contra" />
|
||||
<RadioGroupItem value="7" id="polri" />
|
||||
<Label htmlFor="polri">Polri</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
|
|
@ -330,8 +452,8 @@ export default function DetailImage() {
|
|||
</div>
|
||||
</CardContent>
|
||||
<CardContent className="border rounded-md mt-5">
|
||||
<div className="flex flex-row gap-5">
|
||||
<div className="w-6/12 border px-3 mt-3 rounded-md">
|
||||
<div className="flex flex-col lg:flex-row gap-5">
|
||||
<div className="w-full lg:w-6/12 border px-3 mt-3 rounded-md">
|
||||
<div className="gap-5 mb-5">
|
||||
<div className="space-y-2 py-3">
|
||||
<Label className="text-xl text-black">File Media</Label>
|
||||
|
|
@ -343,10 +465,13 @@ export default function DetailImage() {
|
|||
className="w-full"
|
||||
>
|
||||
{detailVideo?.map((data: any) => (
|
||||
<SwiperSlide key={data.id}>
|
||||
<SwiperSlide
|
||||
key={data.id}
|
||||
onClick={() => handleFileClick(data.id)}
|
||||
>
|
||||
<video
|
||||
className="object-fill h-full w-full"
|
||||
src={data}
|
||||
className="object-fill h-full w-full rounded-md"
|
||||
src={data.url}
|
||||
controls
|
||||
title={`Video ${data.id}`} // Mengganti alt dengan title
|
||||
/>
|
||||
|
|
@ -364,10 +489,13 @@ export default function DetailImage() {
|
|||
modules={[Pagination, Thumbs]}
|
||||
>
|
||||
{detailVideo?.map((data: any) => (
|
||||
<SwiperSlide key={data.id}>
|
||||
<SwiperSlide
|
||||
key={data.id}
|
||||
onClick={() => handleFileClick(data.id)}
|
||||
>
|
||||
<video
|
||||
className="object-cover h-[60px] w-[80px]"
|
||||
src={data}
|
||||
className="object-cover h-[60px] w-[80px] rounded-md"
|
||||
src={data.url}
|
||||
muted
|
||||
title={`Video ${data.id}`} // Mengganti alt dengan title
|
||||
/>
|
||||
|
|
@ -379,7 +507,7 @@ export default function DetailImage() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-6/12 border px-3 rounded-md mt-3">
|
||||
<div className="w-full lg:w-6/12 border px-3 rounded-md mt-3">
|
||||
<div className="gap-5 mb-5">
|
||||
<div className="mt-5">
|
||||
<Label className="text-xl text-black">
|
||||
|
|
@ -401,34 +529,36 @@ export default function DetailImage() {
|
|||
alt={`Thumbnail ${index}`}
|
||||
/> */}
|
||||
<img
|
||||
className="object-cover w-36 h-32"
|
||||
src={data}
|
||||
className="object-cover w-20 h-20 lg:w-32 lg:h-32"
|
||||
src={"/assets/video-icon.webp"}
|
||||
alt={` ${data.id}`}
|
||||
/>
|
||||
<div className="flex flex-row gap-3 items-center">
|
||||
<label className="text-blue-500 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value="Nasional"
|
||||
<div className="flex flex-wrap lg:flex-row gap-3 items-center">
|
||||
{/* Mabes Checkbox */}
|
||||
<label className=" cursor-pointer flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={data.placements === "mabes"} // Automatically checks if placement matches
|
||||
disabled // To reflect read-only behavior
|
||||
/>
|
||||
Nasional
|
||||
<span>Nasional</span>
|
||||
</label>
|
||||
<label className="text-blue-500 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value="Wilayah"
|
||||
|
||||
{/* Polda Checkbox */}
|
||||
<label className=" cursor-pointer flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={data.placements === "polda"} // Automatically checks if placement matches
|
||||
disabled
|
||||
/>
|
||||
Wilayah
|
||||
<span>Wilayah</span>
|
||||
</label>
|
||||
<label className="text-blue-500 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value="International"
|
||||
|
||||
{/* International Checkbox */}
|
||||
<label className=" cursor-pointer flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={data.placements === "international"} // Automatically checks if placement matches
|
||||
disabled
|
||||
/>
|
||||
International
|
||||
<span>International</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -440,112 +570,186 @@ export default function DetailImage() {
|
|||
</CardContent>
|
||||
<CardContent>
|
||||
<div className="gap-5 mb-5">
|
||||
<div className="mt-5">
|
||||
<Label className="text-xl text-black">Berikan Komentar</Label>
|
||||
<div className="flex items-start gap-3">
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="/images/avatar/avatar-1.png"
|
||||
alt="@shadcn"
|
||||
/>
|
||||
</Avatar>
|
||||
|
||||
<textarea
|
||||
className="flex-grow p-2 border rounded-lg focus:outline-none focus:border-yellow-400"
|
||||
placeholder="Tuliskan komentar Anda di sini.."
|
||||
></textarea>
|
||||
</div>
|
||||
<div className="flex items-center mt-2 gap-3">
|
||||
<button className="flex items-center text-gray-600 hover:text-gray-800">
|
||||
<PaperclipIcon className="w-5 h-5" />
|
||||
Lampirkan
|
||||
</button>
|
||||
<button className="flex items-center text-gray-600 hover:text-gray-800">
|
||||
<SmileIcon className="w-5 h-5" />
|
||||
Emoticon
|
||||
</button>
|
||||
<button className="ml-auto px-4 py-1 bg-yellow-500 text-white rounded-lg hover:bg-yellow-600">
|
||||
Kirim
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-5">
|
||||
<Label className="text-xl text-black">Komentar</Label>
|
||||
{commentsData.map((comment) => (
|
||||
<div
|
||||
key={comment.id}
|
||||
className="flex items-start gap-3 mt-2"
|
||||
>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src={comment.avatar}
|
||||
alt={`@${comment.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{comment.username}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{comment.date}
|
||||
</span>
|
||||
<p className="text-gray-800 mt-1">{comment.text}</p>
|
||||
<div
|
||||
className="flex items-center mt-1 text-blue-500 cursor-pointer"
|
||||
onClick={() => handleReply(comment.id)}
|
||||
>
|
||||
<DotSquare className="w-4 h-4" />
|
||||
<span className="ml-1">Balas</span>
|
||||
<div className="mt-4 border p-4 rounded bg-gray-50">
|
||||
<Textarea
|
||||
placeholder="Tulis tanggapan Anda di sini..."
|
||||
value={message}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<div className="flex justify-end mt-3">
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => postData()}
|
||||
type="button"
|
||||
>
|
||||
Kirim Komentar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{listData?.map((item: any) => (
|
||||
<div key={item.id} className="flex flex-col gap-3 mt-2 ">
|
||||
<div className="flex flex-row gap-3">
|
||||
<Avatar className="mt-2">
|
||||
<AvatarImage
|
||||
src={"/assets/avatar-profile.png"}
|
||||
alt={`@${item.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{item.messageFrom.fullname}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{formatDate(item.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-800 mt-1">{item.message}</p>
|
||||
<div className="flex flex-row gap-2">
|
||||
{/* <div
|
||||
className="flex items-center mt-1 text-blue-500 cursor-pointer"
|
||||
onClick={() => handleReply(item.id)}
|
||||
>
|
||||
<DotSquare className="w-4 h-4" />
|
||||
<span className="ml-1">Balas</span>
|
||||
</div> */}
|
||||
<div
|
||||
className="flex items-center mt-1 text-red-500 cursor-pointer"
|
||||
onClick={() => deleteData(item.id)}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<span className="ml-1">Delete</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{comment.replies.length > 0 && (
|
||||
<div className="ml-8 mt-2">
|
||||
{comment.replies.map((reply: any, index: any) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-start gap-3 mt-1"
|
||||
>
|
||||
<Avatar>
|
||||
</div>
|
||||
{replyingTo === item.id && (
|
||||
<div className="ml-10 mt-2">
|
||||
<textarea
|
||||
id={`input-comment-${item.id}`}
|
||||
className="w-full p-2 border rounded"
|
||||
placeholder="Masukkan tanggapan anda"
|
||||
/>
|
||||
<button
|
||||
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
|
||||
onClick={() => sendReplyData(item.id)}
|
||||
>
|
||||
Kirim
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{item.children?.length > 0 && (
|
||||
<div className="ml-10 mt-2 flex flex-col">
|
||||
{item.children.map((child: any) => (
|
||||
<div
|
||||
key={child.id}
|
||||
className="flex flex-col gap-3 mt-2"
|
||||
>
|
||||
<div className="flex flex-row gap-3">
|
||||
<Avatar className="mt-2">
|
||||
<AvatarImage
|
||||
src="https://github.com/shadcn.png"
|
||||
alt={`@${reply.username}`}
|
||||
src={"/assets/avatar-profile.png"}
|
||||
alt={`@${child.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{reply.username}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{reply.date}
|
||||
</span>
|
||||
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{item.messageFrom.fullname}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{formatDate(item.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-800 mt-1">
|
||||
{reply.text}
|
||||
{child.message}
|
||||
</p>
|
||||
<div className="flex flex-row gap-2">
|
||||
{/* <div
|
||||
className="flex items-center mt-1 text-blue-500 cursor-pointer"
|
||||
onClick={() => handleReply(child.id)}
|
||||
>
|
||||
<DotSquare className="w-4 h-4" />
|
||||
<span className="ml-1">Balas</span>
|
||||
</div> */}
|
||||
<div
|
||||
className="flex items-center mt-1 text-red-500 cursor-pointer"
|
||||
onClick={() => deleteData(child.id)}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<span className="ml-1">Delete</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{replyingTo === child.id && (
|
||||
<div className="ml-10 mt-2">
|
||||
<textarea
|
||||
id={`input-comment-${child.id}`}
|
||||
className="w-full p-2 border rounded"
|
||||
placeholder="Masukkan tanggapan anda"
|
||||
/>
|
||||
<button
|
||||
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
|
||||
onClick={() => sendReplyData(child.id)}
|
||||
>
|
||||
Kirim
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{child.children?.length > 0 && (
|
||||
<div className="ml-10 mt-2 flex flex-col mb-3">
|
||||
{child.children.map((child2: any) => (
|
||||
<div
|
||||
key={child2.id}
|
||||
className="flex flex-col gap-3 mt-2"
|
||||
>
|
||||
<div className="flex flex-row gap-3 ">
|
||||
<Avatar className="mt-2">
|
||||
<AvatarImage
|
||||
src={"/assets/avatar-profile.png"}
|
||||
alt={`@${child2.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{item.messageFrom.fullname}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{formatDate(item.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-800 mt-1">
|
||||
{child2.message}
|
||||
</p>
|
||||
<div className="flex flex-row gap-2">
|
||||
<div
|
||||
className="flex items-center mt-1 text-red-500 cursor-pointer"
|
||||
onClick={() =>
|
||||
deleteData(child2.id)
|
||||
}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<span className="ml-1">
|
||||
Delete
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{replyingTo !== null && (
|
||||
<div className="mt-4">
|
||||
<textarea
|
||||
className="w-full p-2 border rounded-md"
|
||||
rows={3}
|
||||
placeholder="Tulis balasan..."
|
||||
value={replyText}
|
||||
onChange={(e) => setReplyText(e.target.value)}
|
||||
></textarea>
|
||||
<button
|
||||
className="mt-2 bg-yellow-500 text-white px-4 py-2 rounded-md"
|
||||
onClick={() => addReply(replyingTo)}
|
||||
>
|
||||
Kirim
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Rows, Search, UploadIcon } from "lucide-react";
|
||||
import { ArrowRight, Rows, Search, UploadIcon } from "lucide-react";
|
||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
|
@ -76,65 +76,76 @@ const CuratedContentPage = () => {
|
|||
<div className="flex justify-between items-center mx-3">
|
||||
<Label className="text-base">Audio Visual</Label>
|
||||
|
||||
<Label>
|
||||
{" "}
|
||||
<Link
|
||||
href={
|
||||
"/shared/curated-content/giat-routine/video/all"
|
||||
}
|
||||
>
|
||||
Lihat Semua
|
||||
</Link>
|
||||
</Label>
|
||||
<div className="flex items-center">
|
||||
<Label>
|
||||
{" "}
|
||||
<Link
|
||||
href={
|
||||
"/shared/curated-content/giat-routine/video/all"
|
||||
}
|
||||
>
|
||||
Lihat Semua
|
||||
</Link>
|
||||
</Label>
|
||||
<ArrowRight size={18} className="text-slate-600" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-5 my-5">
|
||||
<VideoSliderPage />
|
||||
</div>
|
||||
<div className="flex justify-between items-center mx-3">
|
||||
<Label className="text-base">Audio</Label>
|
||||
|
||||
<Label>
|
||||
{" "}
|
||||
<Link
|
||||
href={
|
||||
"/shared/curated-content/giat-routine/audio/all"
|
||||
}
|
||||
>
|
||||
Lihat Semua
|
||||
</Link>
|
||||
</Label>
|
||||
<div className="flex items-center">
|
||||
<Label>
|
||||
{" "}
|
||||
<Link
|
||||
href={
|
||||
"/shared/curated-content/giat-routine/audio/all"
|
||||
}
|
||||
>
|
||||
Lihat Semua
|
||||
</Link>
|
||||
</Label>
|
||||
<ArrowRight size={18} className="text-slate-600" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-5 my-5">
|
||||
<AudioSliderPage />
|
||||
</div>
|
||||
<div className="flex justify-between items-center mx-3">
|
||||
<Label className="text-base">Foto</Label>
|
||||
|
||||
<Label>
|
||||
{" "}
|
||||
<Link
|
||||
href={
|
||||
"/shared/curated-content/giat-routine/image/all"
|
||||
}
|
||||
>
|
||||
Lihat Semua
|
||||
</Link>
|
||||
</Label>
|
||||
<div className="flex items-center">
|
||||
<Label>
|
||||
{" "}
|
||||
<Link
|
||||
href={
|
||||
"/shared/curated-content/giat-routine/image/all"
|
||||
}
|
||||
>
|
||||
Lihat Semua
|
||||
</Link>
|
||||
</Label>
|
||||
<ArrowRight size={18} className="text-slate-600" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-5 my-5">
|
||||
<ImageSliderPage />
|
||||
</div>
|
||||
<div className="flex justify-between items-center mx-3">
|
||||
<Label className="text-base">Teks</Label>
|
||||
<Label>
|
||||
<Link
|
||||
href={
|
||||
"/shared/curated-content/giat-routine/document/all"
|
||||
}
|
||||
>
|
||||
Lihat Semua
|
||||
</Link>
|
||||
</Label>
|
||||
<div className="flex items-center">
|
||||
<Label>
|
||||
{" "}
|
||||
<Link
|
||||
href={
|
||||
"/shared/curated-content/giat-routine/document/all"
|
||||
}
|
||||
>
|
||||
Lihat Semua
|
||||
</Link>
|
||||
</Label>
|
||||
<ArrowRight size={18} className="text-slate-600" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-5 my-5">
|
||||
<TeksSliderPage />
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ export default function FormBlogDetail() {
|
|||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{detail !== undefined ? (
|
||||
<div className="flex lg:flex-row gap-10">
|
||||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Detail Indeks</p>
|
||||
|
|
@ -302,7 +302,7 @@ export default function FormBlogDetail() {
|
|||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div className="w-4/12">
|
||||
<div className="w-full lg:w-4/12">
|
||||
<Card className=" h-[650px]">
|
||||
<div className="px-3 py-3">
|
||||
<Label htmlFor="fileInput">Gambar Utama</Label>
|
||||
|
|
|
|||
|
|
@ -267,7 +267,7 @@ export default function FormBlogUpdate() {
|
|||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{detail !== undefined ? (
|
||||
<div className="flex lg:flex-row gap-10">
|
||||
<div className="flex flex-col lg:flex-row gap-3 sm:gap-0 lg:gap-3">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Update Indeks</p>
|
||||
|
|
@ -362,8 +362,8 @@ export default function FormBlogUpdate() {
|
|||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div className="w-4/12">
|
||||
<Card className=" h-[700px]">
|
||||
<div className="w-full lg:w-4/12">
|
||||
<Card className="h-[600px] sm:h-[850px] lg:h-[700px]">
|
||||
<div className="px-3 py-3">
|
||||
<Label htmlFor="fileInput">Gambar Utama</Label>
|
||||
<input
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ export default function FormBlog() {
|
|||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="flex lg:flex-row gap-10">
|
||||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Indeks</p>
|
||||
|
|
@ -358,7 +358,7 @@ export default function FormBlog() {
|
|||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div className="w-4/12">
|
||||
<div className="w-full lg:w-4/12">
|
||||
<Card className=" h-[600px]">
|
||||
<div className="px-3 py-3">
|
||||
<label htmlFor="fileInput">Gambar Utama</label>
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import {
|
|||
} from "@/service/content/content";
|
||||
import { detailMedia } from "@/service/curated-content/curated-content";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { MailIcon } from "lucide-react";
|
||||
import { MailIcon, Music } from "lucide-react";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import "swiper/css";
|
||||
import "swiper/css/free-mode";
|
||||
|
|
@ -138,25 +138,23 @@ export default function FormAudioDetail() {
|
|||
const [selectedTarget, setSelectedTarget] = useState("");
|
||||
const [files, setFiles] = useState<FileType[]>([]);
|
||||
const [rejectedFiles, setRejectedFiles] = useState<number[]>([]);
|
||||
const [isMabesApprover, setIsMabesApprover] = useState(false);
|
||||
const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
|
||||
const [audioPlaying, setAudioPlaying] = useState<any>(null);
|
||||
const [filePlacements, setFilePlacements] = useState<string[][]>([]);
|
||||
|
||||
const waveSurferRef = useRef<any>(null);
|
||||
|
||||
|
||||
const [wavesurfer, setWavesurfer] = useState<WaveSurfer>();
|
||||
const [isPlaying, setIsPlaying] = useState(false)
|
||||
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
|
||||
const onReady = (ws: any) => {
|
||||
setWavesurfer(ws)
|
||||
setWavesurfer(ws);
|
||||
setIsPlaying(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onPlayPause = () => {
|
||||
wavesurfer && wavesurfer.playPause();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
let fileTypeId = "4";
|
||||
|
||||
|
|
@ -187,7 +185,7 @@ export default function FormAudioDetail() {
|
|||
userLevelId == "216" &&
|
||||
roleId == "3"
|
||||
) {
|
||||
setIsMabesApprover(true);
|
||||
setIsUserMabesApprover(true);
|
||||
}
|
||||
}, [userLevelId, roleId]);
|
||||
|
||||
|
|
@ -230,6 +228,14 @@ export default function FormAudioDetail() {
|
|||
}
|
||||
};
|
||||
|
||||
const setupPlacementCheck = (length: number) => {
|
||||
const temp = [];
|
||||
for (let i = 0; i < length; i++) {
|
||||
temp.push([]);
|
||||
}
|
||||
setFilePlacements(temp);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
|
|
@ -244,6 +250,7 @@ export default function FormAudioDetail() {
|
|||
names: details?.files[0]?.fileName,
|
||||
format: details?.files[0]?.format,
|
||||
});
|
||||
setupPlacementCheck(details?.files?.length);
|
||||
|
||||
if (details.publishedForObject) {
|
||||
const publisherIds = details.publishedForObject.map(
|
||||
|
|
@ -281,9 +288,16 @@ export default function FormAudioDetail() {
|
|||
}, [refresh, setValue]);
|
||||
|
||||
const actionApproval = (e: string) => {
|
||||
const temp = [];
|
||||
for (const element of detail.files) {
|
||||
temp.push([]);
|
||||
}
|
||||
setFilePlacements(temp);
|
||||
setStatus(e);
|
||||
setModalOpen(true);
|
||||
setFiles(detail.files);
|
||||
|
||||
setDescription("");
|
||||
setModalOpen(true);
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
|
|
@ -308,13 +322,26 @@ export default function FormAudioDetail() {
|
|||
}
|
||||
};
|
||||
|
||||
const getPlacement = () => {
|
||||
console.log("getPlaa", filePlacements);
|
||||
const temp = [];
|
||||
for (let i = 0; i < filePlacements?.length; i++) {
|
||||
if (filePlacements[i].length !== 0) {
|
||||
const now = filePlacements[i].filter((a) => a !== "all");
|
||||
const data = { mediaFileId: files[i].id, placements: now.join(",") };
|
||||
temp.push(data);
|
||||
}
|
||||
}
|
||||
return temp;
|
||||
};
|
||||
|
||||
async function save() {
|
||||
const data = {
|
||||
mediaUploadId: id,
|
||||
statusId: status,
|
||||
message: description,
|
||||
files: [],
|
||||
// files: isMabesApprover ? getPlacement() : [],
|
||||
// files: [],
|
||||
files: isUserMabesApprover ? getPlacement() : [],
|
||||
};
|
||||
|
||||
loading();
|
||||
|
|
@ -337,6 +364,7 @@ export default function FormAudioDetail() {
|
|||
}
|
||||
|
||||
close();
|
||||
submitApprovalSuccesss();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -348,6 +376,40 @@ export default function FormAudioDetail() {
|
|||
rejects.push(id);
|
||||
setRejectedFiles(rejects);
|
||||
}
|
||||
|
||||
const setupPlacement = (
|
||||
index: number,
|
||||
placement: string,
|
||||
checked: boolean
|
||||
) => {
|
||||
let temp = [...filePlacements];
|
||||
if (checked) {
|
||||
if (placement === "all") {
|
||||
temp[index] = ["all", "mabes", "polda", "international"];
|
||||
} else {
|
||||
const now = temp[index];
|
||||
now.push(placement);
|
||||
if (now.length === 3 && !now.includes("all")) {
|
||||
now.push("all");
|
||||
}
|
||||
temp[index] = now;
|
||||
}
|
||||
} else {
|
||||
if (placement === "all") {
|
||||
temp[index] = [];
|
||||
} else {
|
||||
const now = temp[index].filter((a) => a !== placement);
|
||||
console.log("now", now);
|
||||
temp[index] = now;
|
||||
if (now.length === 3 && now.includes("all")) {
|
||||
const newData = now.filter((b) => b !== "all");
|
||||
temp[index] = newData;
|
||||
}
|
||||
}
|
||||
}
|
||||
setFilePlacements(temp);
|
||||
};
|
||||
|
||||
const handleMain = (
|
||||
type: string,
|
||||
url: string,
|
||||
|
|
@ -380,14 +442,14 @@ export default function FormAudioDetail() {
|
|||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then(() => {
|
||||
router.push("/in/contributor/content/image");
|
||||
router.push("/in/contributor/content/audio");
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<form>
|
||||
{detail !== undefined ? (
|
||||
<div className="flex lg:flex-row gap-10">
|
||||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Konten Audio</p>
|
||||
|
|
@ -469,14 +531,34 @@ export default function FormAudioDetail() {
|
|||
/>
|
||||
</div>
|
||||
))}
|
||||
<p onClick={onPlayPause}>{isPlaying ? "Pause" : "Play"}</p>
|
||||
<Button
|
||||
size="sm"
|
||||
type="button"
|
||||
onClick={onPlayPause}
|
||||
disabled={isPlaying}
|
||||
className={`flex items-center gap-2 ${
|
||||
isPlaying
|
||||
? "bg-gray-300 cursor-not-allowed"
|
||||
: "bg-primary text-white"
|
||||
} p-2 rounded`}
|
||||
>
|
||||
{isPlaying ? "Pause" : "Play"}
|
||||
<Icon
|
||||
icon={
|
||||
isPlaying
|
||||
? "carbon:pause-outline"
|
||||
: "famicons:play-sharp"
|
||||
}
|
||||
className="h-5 w-5"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div className="w-4/12">
|
||||
<Card className=" h-[800px]">
|
||||
<div className="w-full lg:w-4/12">
|
||||
<Card className=" h-[600px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Kreator</Label>
|
||||
|
|
@ -609,7 +691,7 @@ export default function FormAudioDetail() {
|
|||
<DialogTitle>Berikan Komentar</DialogTitle>
|
||||
</DialogHeader>
|
||||
{status == "2"
|
||||
? files?.map((file) => (
|
||||
? files?.map((file, index) => (
|
||||
<div
|
||||
key={file.id}
|
||||
className="flex flex-row gap-2 items-center"
|
||||
|
|
@ -618,49 +700,92 @@ export default function FormAudioDetail() {
|
|||
<div className="flex flex-col gap-2 w-full">
|
||||
<div className="flex justify-between text-sm">
|
||||
{file.fileName}
|
||||
<a>
|
||||
<a
|
||||
onClick={() =>
|
||||
handleDeleteFileApproval(file.id)
|
||||
}
|
||||
>
|
||||
<Icon icon="humbleicons:times" color="red" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex flex-row gap-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="terms" />
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Semua
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="terms" />
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Nasional
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="terms" />
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Wilayah
|
||||
</label>
|
||||
</div>
|
||||
{isUserMabesApprover && (
|
||||
<div className="flex flex-row gap-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="terms"
|
||||
value="all"
|
||||
checked={filePlacements[index]?.includes(
|
||||
"all"
|
||||
)}
|
||||
onCheckedChange={(e) =>
|
||||
setupPlacement(index, "all", Boolean(e))
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Semua
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="terms"
|
||||
checked={filePlacements[index]?.includes(
|
||||
"mabes"
|
||||
)}
|
||||
onCheckedChange={(e) =>
|
||||
setupPlacement(index, "mabes", Boolean(e))
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Nasional
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="terms"
|
||||
checked={filePlacements[index]?.includes(
|
||||
"polda"
|
||||
)}
|
||||
onCheckedChange={(e) =>
|
||||
setupPlacement(index, "polda", Boolean(e))
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Wilayah
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="terms" />
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Internasional
|
||||
</label>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="terms"
|
||||
checked={filePlacements[index]?.includes(
|
||||
"international"
|
||||
)}
|
||||
onCheckedChange={(e) =>
|
||||
setupPlacement(
|
||||
index,
|
||||
"international",
|
||||
Boolean(e)
|
||||
)
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Internasional
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
|
|
@ -743,14 +868,16 @@ export default function FormAudioDetail() {
|
|||
<Button
|
||||
type="button"
|
||||
color="primary"
|
||||
onClick={submitApprovalSuccesss}
|
||||
onClick={() => submit()}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="destructive"
|
||||
onClick={() => setModalOpen(false)}
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -533,7 +533,7 @@ export default function FormAudio() {
|
|||
const csrfToken = resCsrf?.data?.token;
|
||||
console.log("CSRF TOKEN : ", csrfToken);
|
||||
const headers = {
|
||||
"X-XSRF-TOKEN": csrfToken
|
||||
"X-XSRF-TOKEN": csrfToken,
|
||||
};
|
||||
|
||||
const upload = new Upload(file, {
|
||||
|
|
@ -549,8 +549,8 @@ export default function FormAudio() {
|
|||
isWatermark: "false", // hardcode
|
||||
},
|
||||
onBeforeRequest: function (req) {
|
||||
var xhr = req.getUnderlyingObject()
|
||||
xhr.withCredentials = true
|
||||
var xhr = req.getUnderlyingObject();
|
||||
xhr.withCredentials = true;
|
||||
},
|
||||
onError: async (e: any) => {
|
||||
console.log("Error upload :", e);
|
||||
|
|
@ -685,7 +685,7 @@ export default function FormAudio() {
|
|||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="flex lg:flex-row gap-10">
|
||||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Konten Audio</p>
|
||||
|
|
@ -1026,11 +1026,9 @@ export default function FormAudio() {
|
|||
</Fragment>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
</div>
|
||||
</Card>
|
||||
<div className="w-4/12">
|
||||
<div className="w-full lg:w-4/12">
|
||||
<Card className=" h-[500px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
|
|
|
|||
|
|
@ -587,7 +587,7 @@ export default function FormAudioUpdate() {
|
|||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{detail !== undefined ? (
|
||||
<div className="flex lg:flex-row gap-10">
|
||||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Konten Audio</p>
|
||||
|
|
@ -713,7 +713,7 @@ export default function FormAudioUpdate() {
|
|||
alt={file.fileName}
|
||||
className="w-16 h-16 object-cover rounded-md mr-4"
|
||||
/>
|
||||
<div className="flex flex-row gap-3 items-center ">
|
||||
<div className="flex flex-wrap gap-3 items-center ">
|
||||
<div className="flex-grow">
|
||||
<p className="font-medium">{file.fileName}</p>
|
||||
<a
|
||||
|
|
@ -808,7 +808,7 @@ export default function FormAudioUpdate() {
|
|||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div className="w-4/12">
|
||||
<div className="w-full lg:w-4/12">
|
||||
<Card className=" h-[800px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@ export default function FormImageDetail() {
|
|||
for (let i = 0; i < filePlacements?.length; i++) {
|
||||
if (filePlacements[i].length !== 0) {
|
||||
const now = filePlacements[i].filter((a) => a !== "all");
|
||||
const data = { mediaFileId: files[i].id, placement: now.join(",") };
|
||||
const data = { mediaFileId: files[i].id, placements: now.join(",") };
|
||||
temp.push(data);
|
||||
}
|
||||
}
|
||||
|
|
@ -409,7 +409,7 @@ export default function FormImageDetail() {
|
|||
return (
|
||||
<form>
|
||||
{detail !== undefined ? (
|
||||
<div className="flex lg:flex-row gap-10">
|
||||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Konten Foto</p>
|
||||
|
|
@ -520,7 +520,7 @@ export default function FormImageDetail() {
|
|||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div className="w-4/12">
|
||||
<div className="w-full lg:w-4/12">
|
||||
<Card className=" h-[900px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
|
|
|
|||
|
|
@ -695,7 +695,7 @@ export default function FormImage() {
|
|||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="flex lg:flex-row gap-10">
|
||||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Konten Foto</p>
|
||||
|
|
@ -1039,7 +1039,7 @@ export default function FormImage() {
|
|||
{/* Submit Button */}
|
||||
</div>
|
||||
</Card>
|
||||
<div className="w-4/12">
|
||||
<div className="w-full lg:w-4/12">
|
||||
<Card className=" h-[500px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
|
|
|
|||
|
|
@ -625,7 +625,7 @@ export default function FormImageUpdate() {
|
|||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{detail !== undefined ? (
|
||||
<div className="flex lg:flex-row gap-10">
|
||||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Konten Foto</p>
|
||||
|
|
@ -752,7 +752,7 @@ export default function FormImageUpdate() {
|
|||
alt={file.fileName}
|
||||
className="w-16 h-16 object-cover rounded-md mr-4"
|
||||
/>
|
||||
<div className="flex flex-row gap-3 items-center ">
|
||||
<div className="flex flex-wrap gap-3 items-center ">
|
||||
<div className="flex-grow">
|
||||
<p className="font-medium">{file.fileName}</p>
|
||||
<a
|
||||
|
|
@ -847,8 +847,8 @@ export default function FormImageUpdate() {
|
|||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div className="w-4/12">
|
||||
<Card className=" h-[800px]">
|
||||
<div className="w-full lg:w-4/12">
|
||||
<Card className="h-[900px] md:h-[1100px] lg:h-[800px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Kreator</Label>
|
||||
|
|
|
|||
|
|
@ -355,10 +355,8 @@ export default function FormConvertSPIT() {
|
|||
const handleCheckboxChange = (id: string): void => {
|
||||
if (id === "all") {
|
||||
if (publishedFor.includes("all")) {
|
||||
// Uncheck all checkboxes
|
||||
setPublishedFor([]);
|
||||
} else {
|
||||
// Select all checkboxes
|
||||
setPublishedFor(
|
||||
options
|
||||
.filter((opt: any) => opt.id !== "all")
|
||||
|
|
@ -370,7 +368,6 @@ export default function FormConvertSPIT() {
|
|||
? publishedFor.filter((item) => item !== id)
|
||||
: [...publishedFor, id];
|
||||
|
||||
// Remove "all" if any checkbox is unchecked
|
||||
if (publishedFor.includes("all") && id !== "all") {
|
||||
setPublishedFor(updatedPublishedFor.filter((item) => item !== "all"));
|
||||
} else {
|
||||
|
|
@ -416,10 +413,10 @@ export default function FormConvertSPIT() {
|
|||
description,
|
||||
htmlDescription: description,
|
||||
tags: "siap",
|
||||
categoryId: 1,
|
||||
categoryId: selectedCategoryId,
|
||||
publishedFor: publishedFor.join(","),
|
||||
creator: data.contentCreator,
|
||||
files: isUserMabesApprover ? getPlacement() : [], // Include placement data
|
||||
files: isUserMabesApprover ? getPlacement() : [],
|
||||
};
|
||||
|
||||
const response = await convertSPIT(requestData);
|
||||
|
|
@ -544,7 +541,7 @@ export default function FormConvertSPIT() {
|
|||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{detail !== undefined ? (
|
||||
<div className="flex lg:flex-row gap-10">
|
||||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Konten Foto</p>
|
||||
|
|
@ -698,7 +695,7 @@ export default function FormConvertSPIT() {
|
|||
</RadioGroup>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-xl text-black">File Media</Label>
|
||||
<Label className="text-xl">File Media</Label>
|
||||
<div className="w-full ">
|
||||
<Swiper
|
||||
thumbs={{ swiper: thumbsSwiper }}
|
||||
|
|
@ -709,7 +706,7 @@ export default function FormConvertSPIT() {
|
|||
{detailThumb?.map((data: any) => (
|
||||
<SwiperSlide key={data.id}>
|
||||
<img
|
||||
className="object-fill h-full w-full"
|
||||
className="object-fill h-full w-full rounded-md"
|
||||
src={data}
|
||||
alt={` ${data.id}`}
|
||||
/>
|
||||
|
|
@ -719,7 +716,7 @@ export default function FormConvertSPIT() {
|
|||
<div className=" mt-2 ">
|
||||
<Swiper
|
||||
onSwiper={setThumbsSwiper}
|
||||
slidesPerView={6}
|
||||
slidesPerView={8}
|
||||
spaceBetween={8}
|
||||
pagination={{
|
||||
clickable: true,
|
||||
|
|
@ -730,7 +727,7 @@ export default function FormConvertSPIT() {
|
|||
{detailThumb?.map((data: any) => (
|
||||
<SwiperSlide key={data.id}>
|
||||
<img
|
||||
className="object-cover h-[60px] w-[80px]"
|
||||
className="object-cover h-[60px] w-[80px] rounded-md"
|
||||
src={data}
|
||||
alt={` ${data.id}`}
|
||||
/>
|
||||
|
|
@ -761,7 +758,7 @@ export default function FormConvertSPIT() {
|
|||
</a> */}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row gap-2">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="terms"
|
||||
|
|
@ -833,7 +830,7 @@ export default function FormConvertSPIT() {
|
|||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div className="w-4/12">
|
||||
<div className="w-full lg:w-4/12">
|
||||
<Card className=" h-[800px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
|
|
|
|||
|
|
@ -138,6 +138,9 @@ export default function FormTeksDetail() {
|
|||
const [rejectedFiles, setRejectedFiles] = useState<number[]>([]);
|
||||
const [isMabesApprover, setIsMabesApprover] = useState(false);
|
||||
|
||||
const [filePlacements, setFilePlacements] = useState<string[][]>([]);
|
||||
const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
|
||||
|
||||
let fileTypeId = "3";
|
||||
|
||||
const {
|
||||
|
|
@ -167,7 +170,7 @@ export default function FormTeksDetail() {
|
|||
userLevelId == "216" &&
|
||||
roleId == "3"
|
||||
) {
|
||||
setIsMabesApprover(true);
|
||||
setIsUserMabesApprover(true);
|
||||
}
|
||||
}, [userLevelId, roleId]);
|
||||
|
||||
|
|
@ -210,6 +213,14 @@ export default function FormTeksDetail() {
|
|||
}
|
||||
};
|
||||
|
||||
const setupPlacementCheck = (length: number) => {
|
||||
const temp = [];
|
||||
for (let i = 0; i < length; i++) {
|
||||
temp.push([]);
|
||||
}
|
||||
setFilePlacements(temp);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
|
|
@ -255,9 +266,16 @@ export default function FormTeksDetail() {
|
|||
}, [refresh, setValue]);
|
||||
|
||||
const actionApproval = (e: string) => {
|
||||
const temp = [];
|
||||
for (const element of detail.files) {
|
||||
temp.push([]);
|
||||
}
|
||||
setFilePlacements(temp);
|
||||
setStatus(e);
|
||||
setModalOpen(true);
|
||||
setFiles(detail.files);
|
||||
|
||||
setDescription("");
|
||||
setModalOpen(true);
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
|
|
@ -266,29 +284,42 @@ export default function FormTeksDetail() {
|
|||
Number(status) == 2 ||
|
||||
Number(status) == 4
|
||||
) {
|
||||
MySwal.fire({
|
||||
title: "Simpan Approval",
|
||||
text: "",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "Simpan",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
save();
|
||||
}
|
||||
});
|
||||
save();
|
||||
// MySwal.fire({
|
||||
// title: "Simpan Approval",
|
||||
// text: "",
|
||||
// icon: "warning",
|
||||
// showCancelButton: true,
|
||||
// cancelButtonColor: "#d33",
|
||||
// confirmButtonColor: "#3085d6",
|
||||
// confirmButtonText: "Simpan",
|
||||
// }).then((result) => {
|
||||
// if (result.isConfirmed) {
|
||||
|
||||
// }
|
||||
// });
|
||||
}
|
||||
};
|
||||
|
||||
const getPlacement = () => {
|
||||
console.log("getPlaa", filePlacements);
|
||||
const temp = [];
|
||||
for (let i = 0; i < filePlacements?.length; i++) {
|
||||
if (filePlacements[i].length !== 0) {
|
||||
const now = filePlacements[i].filter((a) => a !== "all");
|
||||
const data = { mediaFileId: files[i].id, placements: now.join(",") };
|
||||
temp.push(data);
|
||||
}
|
||||
}
|
||||
return temp;
|
||||
};
|
||||
|
||||
async function save() {
|
||||
const data = {
|
||||
mediaUploadId: id,
|
||||
statusId: status,
|
||||
message: description,
|
||||
files: [],
|
||||
// files: isMabesApprover ? getPlacement() : [],
|
||||
files: isUserMabesApprover ? getPlacement() : [],
|
||||
};
|
||||
|
||||
loading();
|
||||
|
|
@ -311,10 +342,44 @@ export default function FormTeksDetail() {
|
|||
}
|
||||
|
||||
close();
|
||||
submitApprovalSuccesss();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const setupPlacement = (
|
||||
index: number,
|
||||
placement: string,
|
||||
checked: boolean
|
||||
) => {
|
||||
let temp = [...filePlacements];
|
||||
if (checked) {
|
||||
if (placement === "all") {
|
||||
temp[index] = ["all", "mabes", "polda", "international"];
|
||||
} else {
|
||||
const now = temp[index];
|
||||
now.push(placement);
|
||||
if (now.length === 3 && !now.includes("all")) {
|
||||
now.push("all");
|
||||
}
|
||||
temp[index] = now;
|
||||
}
|
||||
} else {
|
||||
if (placement === "all") {
|
||||
temp[index] = [];
|
||||
} else {
|
||||
const now = temp[index].filter((a) => a !== placement);
|
||||
console.log("now", now);
|
||||
temp[index] = now;
|
||||
if (now.length === 3 && now.includes("all")) {
|
||||
const newData = now.filter((b) => b !== "all");
|
||||
temp[index] = newData;
|
||||
}
|
||||
}
|
||||
}
|
||||
setFilePlacements(temp);
|
||||
};
|
||||
|
||||
function handleDeleteFileApproval(id: number) {
|
||||
const selectedFiles = files.filter((file) => file.id != id);
|
||||
setFiles(selectedFiles);
|
||||
|
|
@ -353,7 +418,7 @@ export default function FormTeksDetail() {
|
|||
return (
|
||||
<form>
|
||||
{detail !== undefined ? (
|
||||
<div className="flex lg:flex-row gap-10">
|
||||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">
|
||||
|
|
@ -470,10 +535,10 @@ export default function FormTeksDetail() {
|
|||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
<div className="mt-2">
|
||||
<div className="mt-2 ">
|
||||
<Swiper
|
||||
onSwiper={setThumbsSwiper}
|
||||
slidesPerView={6}
|
||||
slidesPerView={8}
|
||||
spaceBetween={8}
|
||||
pagination={{ clickable: true }}
|
||||
modules={[Pagination, Thumbs]}
|
||||
|
|
@ -501,7 +566,7 @@ export default function FormTeksDetail() {
|
|||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div className="w-4/12">
|
||||
<div className="w-full lg:w-4/12">
|
||||
<Card className=" h-[800px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
|
|
@ -644,7 +709,7 @@ export default function FormTeksDetail() {
|
|||
<DialogTitle>Berikan Komentar</DialogTitle>
|
||||
</DialogHeader>
|
||||
{status == "2"
|
||||
? files?.map((file) => (
|
||||
? files?.map((file, index) => (
|
||||
<div
|
||||
key={file.id}
|
||||
className="flex flex-row gap-2 items-center"
|
||||
|
|
@ -653,49 +718,92 @@ export default function FormTeksDetail() {
|
|||
<div className="flex flex-col gap-2 w-full">
|
||||
<div className="flex justify-between text-sm">
|
||||
{file.fileName}
|
||||
<a>
|
||||
<a
|
||||
onClick={() =>
|
||||
handleDeleteFileApproval(file.id)
|
||||
}
|
||||
>
|
||||
<Icon icon="humbleicons:times" color="red" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex flex-row gap-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="terms" />
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Semua
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="terms" />
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Nasional
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="terms" />
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Wilayah
|
||||
</label>
|
||||
</div>
|
||||
{isUserMabesApprover && (
|
||||
<div className="flex flex-row gap-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="terms"
|
||||
value="all"
|
||||
checked={filePlacements[index]?.includes(
|
||||
"all"
|
||||
)}
|
||||
onCheckedChange={(e) =>
|
||||
setupPlacement(index, "all", Boolean(e))
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Semua
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="terms"
|
||||
checked={filePlacements[index]?.includes(
|
||||
"mabes"
|
||||
)}
|
||||
onCheckedChange={(e) =>
|
||||
setupPlacement(index, "mabes", Boolean(e))
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Nasional
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="terms"
|
||||
checked={filePlacements[index]?.includes(
|
||||
"polda"
|
||||
)}
|
||||
onCheckedChange={(e) =>
|
||||
setupPlacement(index, "polda", Boolean(e))
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Wilayah
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="terms" />
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Internasional
|
||||
</label>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="terms"
|
||||
checked={filePlacements[index]?.includes(
|
||||
"international"
|
||||
)}
|
||||
onCheckedChange={(e) =>
|
||||
setupPlacement(
|
||||
index,
|
||||
"international",
|
||||
Boolean(e)
|
||||
)
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Internasional
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
|
|
@ -778,14 +886,16 @@ export default function FormTeksDetail() {
|
|||
<Button
|
||||
type="button"
|
||||
color="primary"
|
||||
onClick={submitApprovalSuccesss}
|
||||
onClick={() => submit()}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="destructive"
|
||||
onClick={() => setModalOpen(false)}
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -532,7 +532,7 @@ export default function FormTeks() {
|
|||
const csrfToken = resCsrf?.data?.token;
|
||||
console.log("CSRF TOKEN : ", csrfToken);
|
||||
const headers = {
|
||||
"X-XSRF-TOKEN": csrfToken
|
||||
"X-XSRF-TOKEN": csrfToken,
|
||||
};
|
||||
|
||||
const upload = new Upload(file, {
|
||||
|
|
@ -548,8 +548,8 @@ export default function FormTeks() {
|
|||
isWatermark: "false", // hardcode
|
||||
},
|
||||
onBeforeRequest: function (req) {
|
||||
var xhr = req.getUnderlyingObject()
|
||||
xhr.withCredentials = true
|
||||
var xhr = req.getUnderlyingObject();
|
||||
xhr.withCredentials = true;
|
||||
},
|
||||
onError: async (e: any) => {
|
||||
console.log("Error upload :", e);
|
||||
|
|
@ -684,7 +684,7 @@ export default function FormTeks() {
|
|||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="flex lg:flex-row gap-10">
|
||||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Konten Teks</p>
|
||||
|
|
@ -1028,7 +1028,7 @@ export default function FormTeks() {
|
|||
{/* Submit Button */}
|
||||
</div>
|
||||
</Card>
|
||||
<div className="w-4/12">
|
||||
<div className="w-full lg:w-4/12">
|
||||
<Card className=" h-[500px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
|
|
|
|||
|
|
@ -552,7 +552,7 @@ export default function FormTeksUpdate() {
|
|||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{detail !== undefined ? (
|
||||
<div className="flex lg:flex-row gap-10">
|
||||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">
|
||||
|
|
@ -679,7 +679,7 @@ export default function FormTeksUpdate() {
|
|||
alt={file.fileName}
|
||||
className="w-16 h-16 object-cover rounded-md mr-4"
|
||||
/>
|
||||
<div className="flex flex-row gap-3 items-center ">
|
||||
<div className="flex flex-wrap gap-3 items-center ">
|
||||
<div className="flex-grow">
|
||||
<p className="font-medium">{file.fileName}</p>
|
||||
<a
|
||||
|
|
@ -774,7 +774,7 @@ export default function FormTeksUpdate() {
|
|||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div className="w-4/12">
|
||||
<div className="w-full lg:w-4/12">
|
||||
<Card className=" h-[800px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
|
|
|
|||
|
|
@ -329,7 +329,7 @@ export default function FormVideoDetail() {
|
|||
for (let i = 0; i < filePlacements?.length; i++) {
|
||||
if (filePlacements[i].length !== 0) {
|
||||
const now = filePlacements[i].filter((a) => a !== "all");
|
||||
const data = { mediaFileId: files[i].id, placement: now.join(",") };
|
||||
const data = { mediaFileId: files[i].id, placements: now.join(",") };
|
||||
temp.push(data);
|
||||
}
|
||||
}
|
||||
|
|
@ -409,7 +409,7 @@ export default function FormVideoDetail() {
|
|||
return (
|
||||
<form>
|
||||
{detail !== undefined ? (
|
||||
<div className="flex lg:flex-row gap-10">
|
||||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">
|
||||
|
|
@ -524,7 +524,7 @@ export default function FormVideoDetail() {
|
|||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div className="w-4/12">
|
||||
<div className="w-full lg:w-4/12">
|
||||
<Card className=" h-[800px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
|
|
|
|||
|
|
@ -683,12 +683,11 @@ export default function FormVideo() {
|
|||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="flex lg:flex-row gap-10">
|
||||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Konten Video</p>
|
||||
<div className="gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2 py-3">
|
||||
<Label>Judul</Label>
|
||||
<Controller
|
||||
|
|
@ -1027,7 +1026,7 @@ export default function FormVideo() {
|
|||
{/* Submit Button */}
|
||||
</div>
|
||||
</Card>
|
||||
<div className="w-4/12">
|
||||
<div className="w-full lg:w-4/12">
|
||||
<Card className=" h-[800px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
|
|
|
|||
|
|
@ -606,7 +606,7 @@ export default function FormVideoUpdate() {
|
|||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{detail !== undefined ? (
|
||||
<div className="flex lg:flex-row gap-10">
|
||||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">
|
||||
|
|
@ -734,7 +734,7 @@ export default function FormVideoUpdate() {
|
|||
alt={file.fileName}
|
||||
className="w-16 h-16 object-cover rounded-md mr-4"
|
||||
/>
|
||||
<div className="flex flex-row gap-3 items-center ">
|
||||
<div className="flex flex-wrap gap-3 items-center ">
|
||||
<div className="flex-grow">
|
||||
<p className="font-medium">{file.fileName}</p>
|
||||
<a
|
||||
|
|
@ -829,7 +829,7 @@ export default function FormVideoUpdate() {
|
|||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div className="w-4/12">
|
||||
<div className="w-full lg:w-4/12">
|
||||
<Card className=" h-[800px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
|
|
|
|||
|
|
@ -46,6 +46,11 @@ import {
|
|||
} from "@/components/ui/dialog";
|
||||
import dynamic from "next/dynamic";
|
||||
import Cookies from "js-cookie";
|
||||
import FileUploader from "../shared/file-uploader";
|
||||
import { AudioRecorder } from "react-audio-voice-recorder";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
import { Upload } from "tus-js-client";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
|
||||
const contestSchema = z.object({
|
||||
theme: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -88,6 +93,10 @@ const CustomEditor = dynamic(
|
|||
{ ssr: false }
|
||||
);
|
||||
|
||||
interface FileWithPreview extends File {
|
||||
preview: string;
|
||||
}
|
||||
|
||||
export default function FormContestDetail() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -105,6 +114,18 @@ export default function FormContestDetail() {
|
|||
const [checkedLevels, setCheckedLevels] = useState(new Set());
|
||||
const [expandedPolda, setExpandedPolda] = useState([{}]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [audioFile, setAudioFile] = useState<File | null>(null);
|
||||
const [imageFiles, setImageFiles] = useState<FileWithPreview[]>([]);
|
||||
const [videoFiles, setVideoFiles] = useState<FileWithPreview[]>([]);
|
||||
const [textFiles, setTextFiles] = useState<FileWithPreview[]>([]);
|
||||
const [audioFiles, setAudioFiles] = useState<FileWithPreview[]>([]);
|
||||
const [isImageUploadFinish, setIsImageUploadFinish] = useState(false);
|
||||
const [isVideoUploadFinish, setIsVideoUploadFinish] = useState(false);
|
||||
const [isTextUploadFinish, setIsTextUploadFinish] = useState(false);
|
||||
const [isAudioUploadFinish, setIsAudioUploadFinish] = useState(false);
|
||||
const [isRecording, setIsRecording] = useState(false);
|
||||
const [timer, setTimer] = useState<number>(120);
|
||||
const [links, setLinks] = useState<string[]>([""]);
|
||||
|
||||
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
|
||||
|
||||
|
|
@ -221,13 +242,6 @@ export default function FormContestDetail() {
|
|||
return Array.from(checkedLevels).join(","); // Mengonversi Set ke string
|
||||
};
|
||||
|
||||
const toggleExpand = (poldaId: any) => {
|
||||
setExpandedPolda((prev: any) => ({
|
||||
...prev,
|
||||
[poldaId]: !prev[poldaId],
|
||||
}));
|
||||
};
|
||||
|
||||
const save = async (data: ContestSchema) => {
|
||||
const fileTypeMapping = {
|
||||
all: "1",
|
||||
|
|
@ -276,24 +290,60 @@ export default function FormContestDetail() {
|
|||
fileTypeOutput: selectedOutputs,
|
||||
};
|
||||
|
||||
if (id != undefined) {
|
||||
requestData.id = id;
|
||||
}
|
||||
// if (id != undefined) {
|
||||
// requestData.id = id;
|
||||
// }
|
||||
|
||||
const response = await postCreateContest(requestData);
|
||||
|
||||
console.log("Form Data Submitted:", requestData);
|
||||
console.log("response", response);
|
||||
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Data berhasil disimpan.",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then(() => {
|
||||
router.push("/en/shared/contest");
|
||||
const id = response?.data?.data.id;
|
||||
loading();
|
||||
if (imageFiles?.length == 0) {
|
||||
setIsImageUploadFinish(true);
|
||||
}
|
||||
imageFiles?.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(index, String(id), item, "1", "0");
|
||||
});
|
||||
|
||||
if (videoFiles?.length == 0) {
|
||||
setIsVideoUploadFinish(true);
|
||||
}
|
||||
videoFiles?.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(index, String(id), item, "2", "0");
|
||||
});
|
||||
|
||||
if (textFiles?.length == 0) {
|
||||
setIsTextUploadFinish(true);
|
||||
}
|
||||
textFiles?.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(index, String(id), item, "3", "0");
|
||||
});
|
||||
|
||||
if (audioFiles?.length == 0) {
|
||||
setIsAudioUploadFinish(true);
|
||||
}
|
||||
audioFiles.map(async (item: FileWithPreview, index: number) => {
|
||||
await uploadResumableFile(
|
||||
index,
|
||||
String(id),
|
||||
item, // Use .file to access the actual File object
|
||||
"4",
|
||||
"0" // Optional: Replace with actual duration if available
|
||||
);
|
||||
});
|
||||
|
||||
// MySwal.fire({
|
||||
// title: "Sukses",
|
||||
// text: "Data berhasil disimpan.",
|
||||
// icon: "success",
|
||||
// confirmButtonColor: "#3085d6",
|
||||
// confirmButtonText: "OK",
|
||||
// }).then(() => {
|
||||
// router.push("/en/shared/contest");
|
||||
// });
|
||||
};
|
||||
|
||||
const onSubmit = (data: ContestSchema) => {
|
||||
|
|
@ -312,6 +362,179 @@ export default function FormContestDetail() {
|
|||
});
|
||||
};
|
||||
|
||||
const toggleExpand = (poldaId: any) => {
|
||||
setExpandedPolda((prev: any) => ({
|
||||
...prev,
|
||||
[poldaId]: !prev[poldaId],
|
||||
}));
|
||||
};
|
||||
|
||||
const onRecordingStart = () => {
|
||||
setIsRecording(true);
|
||||
|
||||
const countdown = setInterval(() => {
|
||||
setTimer((prevTimer) => {
|
||||
if (prevTimer <= 1) {
|
||||
clearInterval(countdown);
|
||||
return 0;
|
||||
}
|
||||
return prevTimer - 1;
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
setTimeout(() => {
|
||||
if (isRecording) {
|
||||
handleStopRecording();
|
||||
}
|
||||
}, 120000);
|
||||
};
|
||||
|
||||
const handleStopRecording = () => {
|
||||
setIsRecording(false);
|
||||
setTimer(120); // Reset the timer to 2 minutes for the next recording
|
||||
};
|
||||
|
||||
const addAudioElement = (blob: Blob) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const audio = document.createElement("audio");
|
||||
audio.src = url;
|
||||
audio.controls = true;
|
||||
document.body.appendChild(audio);
|
||||
|
||||
// Convert Blob to File and add preview
|
||||
const fileWithPreview: FileWithPreview = Object.assign(
|
||||
new File([blob], "voiceNote.webm", { type: "audio/webm" }),
|
||||
{ preview: url }
|
||||
);
|
||||
|
||||
// Add to state
|
||||
setAudioFile(fileWithPreview);
|
||||
setAudioFiles((prev) => [...prev, fileWithPreview]);
|
||||
};
|
||||
|
||||
const handleDeleteAudio = (index: number) => {
|
||||
setAudioFiles((prev) => prev.filter((_, idx) => idx !== index));
|
||||
};
|
||||
|
||||
async function uploadResumableFile(
|
||||
idx: number,
|
||||
id: string,
|
||||
file: any,
|
||||
fileTypeId: string,
|
||||
duration: string
|
||||
) {
|
||||
console.log(idx, id, file, fileTypeId, duration);
|
||||
|
||||
const resCsrf = await getCsrfToken();
|
||||
const csrfToken = resCsrf?.data?.token;
|
||||
console.log("CSRF TOKEN : ", csrfToken);
|
||||
const headers = {
|
||||
"X-XSRF-TOKEN": csrfToken,
|
||||
};
|
||||
|
||||
const upload = new Upload(file, {
|
||||
endpoint: `${process.env.NEXT_PUBLIC_API}/contest/file/upload`,
|
||||
headers: headers,
|
||||
retryDelays: [0, 3000, 6000, 12_000, 24_000],
|
||||
chunkSize: 20_000,
|
||||
metadata: {
|
||||
assignmentId: id,
|
||||
filename: file.name,
|
||||
contentType: file.type,
|
||||
fileTypeId: fileTypeId,
|
||||
duration,
|
||||
},
|
||||
onBeforeRequest: function (req) {
|
||||
var xhr = req.getUnderlyingObject();
|
||||
xhr.withCredentials = true;
|
||||
},
|
||||
onError: async (e: any) => {
|
||||
console.log("Error upload :", e);
|
||||
error(e);
|
||||
},
|
||||
onChunkComplete: (
|
||||
chunkSize: any,
|
||||
bytesAccepted: any,
|
||||
bytesTotal: any
|
||||
) => {
|
||||
// const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
|
||||
// progressInfo[idx].percentage = uploadPersen;
|
||||
// counterUpdateProgress++;
|
||||
// console.log(counterUpdateProgress);
|
||||
// setProgressList(progressInfo);
|
||||
// setCounterProgress(counterUpdateProgress);
|
||||
},
|
||||
onSuccess: async () => {
|
||||
// uploadPersen = 100;
|
||||
// progressInfo[idx].percentage = 100;
|
||||
// counterUpdateProgress++;
|
||||
// setCounterProgress(counterUpdateProgress);
|
||||
successTodo();
|
||||
if (fileTypeId == "1") {
|
||||
setIsImageUploadFinish(true);
|
||||
} else if (fileTypeId == "2") {
|
||||
setIsVideoUploadFinish(true);
|
||||
}
|
||||
if (fileTypeId == "3") {
|
||||
setIsTextUploadFinish(true);
|
||||
}
|
||||
if (fileTypeId == "4") {
|
||||
setIsAudioUploadFinish(true);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
upload.start();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
successTodo();
|
||||
}, [
|
||||
isImageUploadFinish,
|
||||
isVideoUploadFinish,
|
||||
isAudioUploadFinish,
|
||||
isTextUploadFinish,
|
||||
]);
|
||||
|
||||
function successTodo() {
|
||||
if (
|
||||
isImageUploadFinish &&
|
||||
isVideoUploadFinish &&
|
||||
isAudioUploadFinish &&
|
||||
isTextUploadFinish
|
||||
) {
|
||||
successSubmit("/in/shared/contest");
|
||||
}
|
||||
}
|
||||
|
||||
const successSubmit = (redirect: string) => {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Data berhasil disimpan.",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then(() => {
|
||||
router.push(redirect);
|
||||
});
|
||||
};
|
||||
|
||||
const handleLinkChange = (index: number, value: string) => {
|
||||
const updatedLinks = [...links];
|
||||
updatedLinks[index] = value;
|
||||
setLinks(updatedLinks);
|
||||
};
|
||||
|
||||
const handleAddRow = () => {
|
||||
setLinks([...links, ""]);
|
||||
};
|
||||
|
||||
// Remove a specific link row
|
||||
const handleRemoveRow = (index: number) => {
|
||||
const updatedLinks = links.filter((_: any, i: any) => i !== index);
|
||||
setLinks(updatedLinks);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<div className="px-6 py-6">
|
||||
|
|
@ -557,6 +780,119 @@ export default function FormContestDetail() {
|
|||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-1.5 mt-5">
|
||||
<Label htmlFor="attachments">Lampiran</Label>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<Label>Video</Label>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"mp4/*": [],
|
||||
"mov/*": [],
|
||||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .mp4 atau .mov."
|
||||
onDrop={(files) => setVideoFiles(files)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Foto</Label>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"image/*": [],
|
||||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .png, .jpg, atau .jpeg."
|
||||
onDrop={(files) => setImageFiles(files)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Teks</Label>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"pdf/*": [],
|
||||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .pdf."
|
||||
onDrop={(files) => setTextFiles(files)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Audio</Label>
|
||||
<AudioRecorder
|
||||
onRecordingComplete={addAudioElement}
|
||||
audioTrackConstraints={{
|
||||
noiseSuppression: true,
|
||||
echoCancellation: true,
|
||||
}}
|
||||
downloadOnSavePress={true}
|
||||
downloadFileExtension="webm"
|
||||
/>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"mp3/*": [],
|
||||
"wav/*": [],
|
||||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .mp3 atau .wav."
|
||||
onDrop={(files) =>
|
||||
setAudioFiles((prev) => [...prev, ...files])
|
||||
}
|
||||
className="mt-2"
|
||||
/>
|
||||
</div>
|
||||
{audioFiles?.map((audio: any, idx: any) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="flex flex-row justify-between items-center"
|
||||
>
|
||||
<p>Voice Note</p>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => handleDeleteAudio(idx)}
|
||||
size="sm"
|
||||
color="destructive"
|
||||
>
|
||||
X
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
|
||||
{/* Display remaining time */}
|
||||
<div className="mt-4">
|
||||
<h2 className="text-lg font-bold">Link Berita</h2>
|
||||
{links.map((link, index) => (
|
||||
<div key={index} className="flex items-center gap-2 mt-2">
|
||||
<input
|
||||
type="url"
|
||||
className="border rounded p-2 w-full"
|
||||
placeholder={`Masukkan link berita ${index + 1}`}
|
||||
value={link}
|
||||
onChange={(e) =>
|
||||
handleLinkChange(index, e.target.value)
|
||||
}
|
||||
/>
|
||||
{links.length > 1 && (
|
||||
<button
|
||||
type="button"
|
||||
className="bg-red-500 text-white px-3 py-1 rounded"
|
||||
onClick={() => handleRemoveRow(index)}
|
||||
>
|
||||
Hapus
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
className="mt-2 bg-blue-500 text-white px-4 py-2 rounded"
|
||||
onClick={handleAddRow}
|
||||
>
|
||||
Tambah Link
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import {
|
|||
DotSquare,
|
||||
ImageIcon,
|
||||
Music,
|
||||
Search,
|
||||
TrashIcon,
|
||||
VideoIcon,
|
||||
} from "lucide-react";
|
||||
|
|
@ -63,6 +64,7 @@ import Image from "next/image";
|
|||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import WavesurferPlayer from "@wavesurfer/react";
|
||||
import WaveSurfer from "wavesurfer.js";
|
||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||
|
||||
const taskSchema = z.object({
|
||||
uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -242,6 +244,7 @@ export default function FormTaskDetail() {
|
|||
const [audioFile, setAudioFile] = useState<File | null>(null);
|
||||
const [isRecording, setIsRecording] = useState(false);
|
||||
const [timer, setTimer] = useState<number>(120);
|
||||
const [search, setSearch] = React.useState<string>("");
|
||||
|
||||
const [wavesurfer, setWavesurfer] = useState<WaveSurfer>();
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
|
|
@ -749,14 +752,19 @@ export default function FormTaskDetail() {
|
|||
|
||||
const handleRemoveFile = (id: number) => {};
|
||||
|
||||
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearch(e.target.value); // Perbarui state search
|
||||
// getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
{detail !== undefined ? (
|
||||
<div className="px-6 py-6">
|
||||
<div className="flex flex-row justify-between">
|
||||
<div className="flex flex-col sm:flex-row lg:flex-row justify-between">
|
||||
<p className="text-lg font-semibold mb-3">Detail Penugasan</p>
|
||||
<div
|
||||
className="flex gap-3"
|
||||
className="flex gap-3"
|
||||
style={
|
||||
detail?.createdBy?.id === Number(userId)
|
||||
? {}
|
||||
|
|
@ -837,7 +845,7 @@ export default function FormTaskDetail() {
|
|||
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="flex flex-col sm:flex-row lg:flex-row sm:items-center lg:items-center">
|
||||
<div className="mt-6">
|
||||
<Label>Tujuan Pemilihan Tugas</Label>
|
||||
<Select
|
||||
|
|
@ -854,7 +862,7 @@ export default function FormTaskDetail() {
|
|||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3 mt-6 pt-5 ml-3">
|
||||
<div className="flex flex-wrap gap-3 mt-6 lg:pt-5 lg:ml-3">
|
||||
{Object.keys(unitSelection).map((key) => (
|
||||
<div className="flex items-center gap-2" key={key}>
|
||||
<Checkbox
|
||||
|
|
@ -873,7 +881,7 @@ export default function FormTaskDetail() {
|
|||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-6 pt-5 pl-3">
|
||||
<div className="mt-6 lg:pt-5 lg:pl-3">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="soft" size="sm" color="primary">
|
||||
|
|
@ -1308,8 +1316,8 @@ export default function FormTaskDetail() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-between gap-3 my-3">
|
||||
<div className="px-1">
|
||||
<div className="flex flex-wrap lg:flex-row justify-between gap-3 my-3">
|
||||
<div className="lg:px-1">
|
||||
{detail?.isDone !== true &&
|
||||
(Number(userLevelNumber) !== 3 ||
|
||||
Number(userLevelNumber) == 2) ? (
|
||||
|
|
@ -1317,7 +1325,7 @@ export default function FormTaskDetail() {
|
|||
color="primary"
|
||||
variant={"outline"}
|
||||
type="button"
|
||||
className="btn btn-outline-primary ml-3"
|
||||
className="btn btn-outline-primary lg:ml-3"
|
||||
onClick={() => handleForward()}
|
||||
>
|
||||
Forward
|
||||
|
|
@ -1326,9 +1334,10 @@ export default function FormTaskDetail() {
|
|||
""
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<div className="">
|
||||
<Button
|
||||
type="button"
|
||||
color="primary"
|
||||
variant={"outline"}
|
||||
onClick={handleToggleInput}
|
||||
|
|
@ -1338,7 +1347,8 @@ export default function FormTaskDetail() {
|
|||
</div>
|
||||
<div className="">
|
||||
<Button
|
||||
className="btn btn-primary ml-3 mr-3"
|
||||
type="button"
|
||||
className="btn btn-primary lg:mx-3"
|
||||
style={
|
||||
statusAcceptance ||
|
||||
detail?.createdBy?.id !== Number(userId)
|
||||
|
|
@ -1353,7 +1363,7 @@ export default function FormTaskDetail() {
|
|||
</Button>
|
||||
</div>
|
||||
<div
|
||||
className="task-response w-100 px-3 "
|
||||
className="task-response w-100 lg:px-3 "
|
||||
// style={
|
||||
// Number(detail?.createdBy?.id) == Number(userId)
|
||||
// ? {}
|
||||
|
|
@ -1374,26 +1384,130 @@ export default function FormTaskDetail() {
|
|||
</div>
|
||||
</div>
|
||||
{isTableResult && (
|
||||
<table className="w-full border-collapse border border-gray-300">
|
||||
<thead>
|
||||
<tr className="bg-gray-100 border-b">
|
||||
<th className="px-4 py-2 text-left">Judul</th>
|
||||
<th className="px-4 py-2 text-left">Konten</th>
|
||||
<th className="px-4 py-2 text-left">Kategory</th>
|
||||
<th className="px-4 py-2 text-left">Diupload Oleh</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{uploadResults.map((item) => (
|
||||
<tr key={item.id} className="border-b">
|
||||
<td className="px-4 py-2">{item.title}</td>
|
||||
<td className="px-4 py-2">{item.fileType.name}</td>
|
||||
<td className="px-4 py-2">{item.category.name}</td>
|
||||
<td className="px-4 py-2">{item.creatorName}</td>
|
||||
<div>
|
||||
<div className="flex flex-row items-center gap-2 my-2">
|
||||
<div className="mb-3 sm:mb-0 lg-mb-0">
|
||||
<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 w-full"
|
||||
value={search}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" size="md" color="primary">
|
||||
Filter Polda/Polres
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
Daftar Wilayah Polda dan Polres
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="grid grid-cols-2 gap-2 max-h-[400px] overflow-y-auto">
|
||||
{listDest.map((polda: any) => (
|
||||
<div key={polda.id} className="border p-2">
|
||||
<Label className="flex items-center">
|
||||
<Checkbox
|
||||
disabled
|
||||
checked={checkedLevels.has(polda.id)}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(polda.id)
|
||||
}
|
||||
className="mr-3"
|
||||
/>
|
||||
{polda.name}
|
||||
<button
|
||||
onClick={() => toggleExpand(polda.id)}
|
||||
className="ml-2 focus:outline-none"
|
||||
>
|
||||
{expandedPolda[polda.id] ? (
|
||||
<ChevronUp size={16} />
|
||||
) : (
|
||||
<ChevronDown size={16} />
|
||||
)}
|
||||
</button>
|
||||
</Label>
|
||||
{expandedPolda[polda.id] && (
|
||||
<div className="ml-6 mt-2">
|
||||
<Label className="block">
|
||||
<Checkbox
|
||||
disabled
|
||||
checked={polda?.subDestination?.every(
|
||||
(polres: any) =>
|
||||
checkedLevels.has(polres.id)
|
||||
)}
|
||||
onCheckedChange={(isChecked) => {
|
||||
const updatedLevels = new Set(
|
||||
checkedLevels
|
||||
);
|
||||
polda?.subDestination?.forEach(
|
||||
(polres: any) => {
|
||||
if (isChecked) {
|
||||
updatedLevels.add(polres.id);
|
||||
} else {
|
||||
updatedLevels.delete(polres.id);
|
||||
}
|
||||
}
|
||||
);
|
||||
setCheckedLevels(updatedLevels);
|
||||
}}
|
||||
className="mr-2"
|
||||
/>
|
||||
Pilih Semua Polres
|
||||
</Label>
|
||||
{polda?.subDestination?.map((polres: any) => (
|
||||
<Label key={polres.id} className="block mt-1">
|
||||
<Checkbox
|
||||
disabled
|
||||
checked={checkedLevels.has(polres.id)}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(polres.id)
|
||||
}
|
||||
className="mr-2"
|
||||
/>
|
||||
{polres.name}
|
||||
</Label>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
<table className="w-full border-collapse border border-gray-300">
|
||||
<thead>
|
||||
<tr className="bg-gray-100 border-b">
|
||||
<th className="px-4 py-2 text-left">Judul</th>
|
||||
<th className="px-4 py-2 text-left">Konten</th>
|
||||
<th className="px-4 py-2 text-left">Kategory</th>
|
||||
<th className="px-4 py-2 text-left">Diupload Oleh</th>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
{uploadResults.map((item) => (
|
||||
<tr key={item.id} className="border-b">
|
||||
<td className="px-4 py-2 text-blue-500">
|
||||
<a href="">{item.title}</a>
|
||||
</td>
|
||||
<td className="px-4 py-2">{item.fileType.name}</td>
|
||||
<td className="px-4 py-2">{item.category.name}</td>
|
||||
<td className="px-4 py-2">{item.creatorName}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
{showInput && (
|
||||
<>
|
||||
|
|
@ -1580,7 +1694,7 @@ export default function FormTaskDetail() {
|
|||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="flex justify-center mt-4">
|
||||
<div className="flex lg:justify-center mt-4">
|
||||
{detail?.createdBy?.id == Number(userId) &&
|
||||
detail?.isDone !== true ? (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -643,7 +643,7 @@ export default function FormTaskEdit() {
|
|||
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="flex flex-col sm:flex-row lg:flex-row sm:items-center lg:items-center">
|
||||
<div className="mt-6">
|
||||
<Label>Tujuan Pemilihan Tugas</Label>
|
||||
<Select
|
||||
|
|
@ -660,7 +660,7 @@ export default function FormTaskEdit() {
|
|||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3 mt-6 pt-5 ml-3">
|
||||
<div className="flex flex-wrap gap-3 mt-6 lg:pt-5 lg:ml-3">
|
||||
{Object.keys(unitSelection).map((key) => (
|
||||
<div className="flex items-center gap-2" key={key}>
|
||||
<Checkbox
|
||||
|
|
@ -678,7 +678,7 @@ export default function FormTaskEdit() {
|
|||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-6 pt-5 pl-3">
|
||||
<div className="mt-6 lg:pt-5 lg:pl-3">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="soft" size="sm" color="primary">
|
||||
|
|
|
|||
|
|
@ -502,7 +502,7 @@ export default function FormTask() {
|
|||
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="flex flex-col sm:flex-row lg:flex-row sm:items-center lg:items-center">
|
||||
<div className="mt-5">
|
||||
<Label>Tujuan Pemilihan Tugas</Label>
|
||||
<Select onValueChange={setSelectedTarget}>
|
||||
|
|
@ -516,7 +516,7 @@ export default function FormTask() {
|
|||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3 mt-5 pt-5 ml-3">
|
||||
<div className="flex flex-wrap gap-3 mt-5 lg:pt-5 lg:ml-3">
|
||||
{Object.keys(unitSelection).map((key) => (
|
||||
<div className="flex items-center gap-2" key={key}>
|
||||
<Checkbox
|
||||
|
|
@ -532,7 +532,7 @@ export default function FormTask() {
|
|||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-6 pt-5 pl-3">
|
||||
<div className="mt-6 lg:pt-5 lg:pl-3">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="soft" size="sm" color="primary">
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ const CarouselPrevious = React.forwardRef<
|
|||
variant={variant}
|
||||
size={size}
|
||||
className={cn(
|
||||
"absolute h-6 w-6 rounded-full text-white bg-black ml-3",
|
||||
"absolute h-6 w-6 rounded-full text-white bg-black -ml-6",
|
||||
orientation === "horizontal"
|
||||
? "-left-0 top-1/2 -translate-y-1/2"
|
||||
: "-top-0 left-1/2 -translate-x-1/2 rotate-90",
|
||||
|
|
@ -235,7 +235,7 @@ const CarouselNext = React.forwardRef<
|
|||
variant={variant}
|
||||
size={size}
|
||||
className={cn(
|
||||
"absolute h-6 w-6 rounded-full bg-black text-white mr-3",
|
||||
"absolute h-6 w-6 rounded-full bg-black text-white -mr-4",
|
||||
orientation === "horizontal"
|
||||
? "-right-0 top-1/2 -translate-y-1/2"
|
||||
: "-bottom-0 left-1/2 -translate-x-1/2 rotate-90",
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
|
|
@ -7,10 +7,12 @@ import {
|
|||
export async function paginationBlog(
|
||||
size: number,
|
||||
page: number,
|
||||
title: string = ""
|
||||
title: string = "",
|
||||
categoryFilter: any,
|
||||
statusFilter: any
|
||||
) {
|
||||
return await httpGetInterceptor(
|
||||
`blog/pagination?enablePage=1&page=${page}&size=${size}&title=${title}`
|
||||
`blog/pagination?enablePage=1&page=${page}&size=${size}&title=${title}&categoryId=${categoryFilter}&statusId=${statusFilter}`
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -36,3 +38,8 @@ export async function deleteBlog(id: any) {
|
|||
const url = `blog?id=${id}`;
|
||||
return httpDeleteInterceptor(url);
|
||||
}
|
||||
|
||||
export async function getBlogCategory() {
|
||||
const url = "blog/categories/enable";
|
||||
return httpGetInterceptor(url);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue