merge main
This commit is contained in:
commit
841859414d
|
|
@ -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, CheckSquare2, CheckSquare2Icon, 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 {
|
||||
|
|
@ -46,7 +53,6 @@ export type CalendarEvent = {
|
|||
};
|
||||
};
|
||||
|
||||
|
||||
const INITIAL_YEAR = dayjs().format("YYYY");
|
||||
const INITIAL_MONTH = dayjs().format("M");
|
||||
|
||||
|
|
@ -63,7 +69,7 @@ export interface AgendaSettingsAPIResponse {
|
|||
updatedAt: string;
|
||||
createdById: number | null;
|
||||
}
|
||||
|
||||
|
||||
interface YearlyData {
|
||||
january?: Event[];
|
||||
february?: Event[];
|
||||
|
|
@ -86,8 +92,9 @@ interface MonthCardProps {
|
|||
|
||||
interface ListItemProps {
|
||||
item: any;
|
||||
text: string
|
||||
text: string;
|
||||
createdBy: string;
|
||||
isPublish: boolean;
|
||||
bgColor: string;
|
||||
}
|
||||
|
||||
|
|
@ -104,7 +111,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");
|
||||
|
|
@ -117,10 +124,11 @@ 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(
|
||||
dayjs(new Date(Number(INITIAL_YEAR), Number(INITIAL_MONTH) - 1, 1)),
|
||||
dayjs(new Date(Number(INITIAL_YEAR), Number(INITIAL_MONTH) - 1, 1))
|
||||
);
|
||||
|
||||
const [dragEvents] = useState([
|
||||
|
|
@ -142,7 +150,11 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
|
||||
const getCalendarEvents = async () => {
|
||||
console.log("View : ", activeView);
|
||||
const res = await getAgendaSettingsList(selectedMonth?.format("YYYY") || INITIAL_YEAR, selectedMonth.format("M") || INITIAL_MONTH, "");
|
||||
const res = await getAgendaSettingsList(
|
||||
selectedMonth?.format("YYYY") || INITIAL_YEAR,
|
||||
selectedMonth.format("M") || INITIAL_MONTH,
|
||||
""
|
||||
);
|
||||
console.log("View : API Response:", res);
|
||||
|
||||
if (res?.error) {
|
||||
|
|
@ -175,13 +187,17 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
};
|
||||
|
||||
const getYearlyEvents = async () => {
|
||||
const res = await getAgendaSettingsList(selectedMonth?.format("YYYY") || INITIAL_YEAR, '', '');
|
||||
if (res?.error) {
|
||||
error(res.message);
|
||||
return false;
|
||||
}
|
||||
setYearlyData(res?.data?.data);
|
||||
}
|
||||
const res = await getAgendaSettingsList(
|
||||
selectedMonth?.format("YYYY") || INITIAL_YEAR,
|
||||
"",
|
||||
""
|
||||
);
|
||||
if (res?.error) {
|
||||
error(res.message);
|
||||
return false;
|
||||
}
|
||||
setYearlyData(res?.data?.data);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedCategory(categories?.map((c) => c.value));
|
||||
|
|
@ -218,21 +234,19 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
|
||||
if (response?.data && Array.isArray(response?.data)) {
|
||||
// Transform API data to match CalendarEvent type
|
||||
const eventsFromAPI: CalendarEvent[] = response?.data?.map(
|
||||
(item) => ({
|
||||
id: item.id.toString(),
|
||||
title: item.title,
|
||||
createBy: "Mabes Polri - Approver",
|
||||
createdByName: item.createdByName,
|
||||
start: new Date(item.startDate),
|
||||
end: new Date(item.endDate),
|
||||
allDay: true, // Sesuaikan jika memang ada event sepanjang hari
|
||||
extendedProps: {
|
||||
calendar: item.agendaType,
|
||||
description: item.description,
|
||||
},
|
||||
})
|
||||
);
|
||||
const eventsFromAPI: CalendarEvent[] = response?.data?.map((item) => ({
|
||||
id: item.id.toString(),
|
||||
title: item.title,
|
||||
createBy: "Mabes Polri - Approver",
|
||||
createdByName: item.createdByName,
|
||||
start: new Date(item.startDate),
|
||||
end: new Date(item.endDate),
|
||||
allDay: true, // Sesuaikan jika memang ada event sepanjang hari
|
||||
extendedProps: {
|
||||
calendar: item.agendaType,
|
||||
description: item.description,
|
||||
},
|
||||
}));
|
||||
|
||||
setApiEvents(eventsFromAPI);
|
||||
} else {
|
||||
|
|
@ -321,11 +335,17 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
|
||||
const renderEventContent = (eventInfo: any) => {
|
||||
const { title } = eventInfo.event;
|
||||
const { createdByName } = eventInfo.event.extendedProps; // Akses dari extendedProps
|
||||
const { isPublish } = eventInfo.event.extendedProps;
|
||||
const { createdByName } = eventInfo.event.extendedProps;
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className="ml-1">{title}</p>
|
||||
<div className="flex flex-row">
|
||||
{" "}
|
||||
{isPublish == true && <CheckSquare2 />}
|
||||
<p className="ml-1">{title}</p>
|
||||
</div>
|
||||
|
||||
<p className="ml-1 text-xs text-start mt-2">
|
||||
Created By : {createdByName}
|
||||
</p>
|
||||
|
|
@ -340,6 +360,8 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
return "bg-blue-400 border-none";
|
||||
} else if (arg.event.extendedProps.calendar === "polres") {
|
||||
return "bg-slate-400 border-none";
|
||||
} else if (arg.event.extendedProps.calendar === "satker") {
|
||||
return "bg-orange-500 border-none";
|
||||
} else if (arg.event.extendedProps.calendar === "international") {
|
||||
return "bg-green-400 border-none";
|
||||
} else {
|
||||
|
|
@ -359,29 +381,30 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
const handleViewChange = (viewType: string) => {
|
||||
console.log("Change view : ", viewType);
|
||||
setActiveView(viewType);
|
||||
};
|
||||
};
|
||||
|
||||
const months: Array<{ id: keyof YearlyData; label: string }> = [
|
||||
{ id: 'january', label: 'Januari' },
|
||||
{ id: 'february', label: 'Februari' },
|
||||
{ id: 'march', label: 'Maret' },
|
||||
{ id: 'april', label: 'April' },
|
||||
{ id: 'may', label: 'Mei' },
|
||||
{ id: 'june', label: 'Juni' },
|
||||
{ id: 'july', label: 'Juli' },
|
||||
{ id: 'august', label: 'Agustus' },
|
||||
{ id: 'september', label: 'September' },
|
||||
{ id: 'october', label: 'Oktober' },
|
||||
{ id: 'november', label: 'November' },
|
||||
{ id: 'december', label: 'Desember' }
|
||||
{ id: "january", label: "Januari" },
|
||||
{ id: "february", label: "Februari" },
|
||||
{ id: "march", label: "Maret" },
|
||||
{ id: "april", label: "April" },
|
||||
{ id: "may", label: "Mei" },
|
||||
{ id: "june", label: "Juni" },
|
||||
{ id: "july", label: "Juli" },
|
||||
{ id: "august", label: "Agustus" },
|
||||
{ id: "september", label: "September" },
|
||||
{ id: "october", label: "Oktober" },
|
||||
{ id: "november", label: "November" },
|
||||
{ id: "december", label: "Desember" },
|
||||
];
|
||||
|
||||
const getEventColor = (type: Event['type']): string => {
|
||||
const colors: Record<Event['type'], string> = {
|
||||
mabes: 'bg-yellow-500',
|
||||
polda: 'bg-blue-400',
|
||||
polres: 'bg-slate-400',
|
||||
international: 'bg-green-400'
|
||||
const getEventColor = (type: Event["type"]): string => {
|
||||
const colors: Record<Event["type"], string> = {
|
||||
mabes: "bg-yellow-500",
|
||||
polda: "bg-blue-400",
|
||||
polres: "bg-slate-400",
|
||||
satker: "bg-orange-500",
|
||||
international: "bg-green-400",
|
||||
};
|
||||
return colors[type];
|
||||
};
|
||||
|
|
@ -397,32 +420,42 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
allDay: true,
|
||||
extendedProps: {
|
||||
calendar: item.agendaType,
|
||||
description: item.description
|
||||
}
|
||||
description: item.description,
|
||||
},
|
||||
};
|
||||
const finalEvent: any = {
|
||||
event: formattedEvent
|
||||
}
|
||||
|
||||
event: formattedEvent,
|
||||
};
|
||||
|
||||
console.log("Event click custom : ", finalEvent);
|
||||
|
||||
setSelectedEventDate(null);
|
||||
setSheetOpen(true);
|
||||
setApiEvents(finalEvent);
|
||||
wait().then(() => (document.body.style.pointerEvents = "auto"));
|
||||
}
|
||||
};
|
||||
|
||||
const ListItem: React.FC<ListItemProps> = ({ item, text, createdBy, bgColor }) => (
|
||||
<div className={`w-full p-1 mb-2 rounded-md text-white text-sm ${bgColor}`} onClick={() => handleClickListItem(item)}>
|
||||
<p className="ml-1">{text}</p>
|
||||
<p className="ml-1 text-xs text-start mt-2">
|
||||
Created By: {createdBy}
|
||||
</p>
|
||||
const ListItem: React.FC<ListItemProps> = ({
|
||||
item,
|
||||
text,
|
||||
createdBy,
|
||||
isPublish,
|
||||
bgColor,
|
||||
}) => (
|
||||
<div
|
||||
className={`w-full p-1 mb-2 rounded-md text-white text-sm ${bgColor}`}
|
||||
onClick={() => handleClickListItem(item)}
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
{isPublish == true && <CheckSquare2Icon />}
|
||||
<p className="ml-1">{text}</p>
|
||||
</div>
|
||||
<p className="ml-1 text-xs text-start mt-2">Created By: {createdBy}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
const MonthCard: React.FC<MonthCardProps> = (props: any) => {
|
||||
const { monthId, label } = props;
|
||||
const { monthId, label } = props;
|
||||
const events: any = yearlyData?.[monthId];
|
||||
const displayedEvents = events?.slice(0, 3);
|
||||
const hasMoreEvents = events?.length > 3;
|
||||
|
|
@ -431,7 +464,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
<div className="py-3">
|
||||
<h4 className="font-bold text-center">{label}</h4>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="px-2">
|
||||
{events?.length === 0 ? (
|
||||
<div className="mt-1 py-2 rounded-lg bg-white border border-black">
|
||||
|
|
@ -440,15 +473,16 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
) : (
|
||||
<>
|
||||
{displayedEvents?.map((event: any, index: number) => (
|
||||
<ListItem
|
||||
<ListItem
|
||||
key={index}
|
||||
item={event}
|
||||
text={event.title}
|
||||
createdBy={event.createdByName}
|
||||
isPublish={event.isPublish}
|
||||
bgColor={getEventColor(event.agendaType)}
|
||||
/>
|
||||
))}
|
||||
|
||||
|
||||
{hasMoreEvents && (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
|
|
@ -462,16 +496,19 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
>
|
||||
<Card>
|
||||
<CardHeader className="bg-default p-2 rounded-t-lg">
|
||||
<CardTitle className="text-default-foreground text-base py-0">{label}</CardTitle>
|
||||
<CardTitle className="text-default-foreground text-base py-0">
|
||||
{label}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-2 text-sm">
|
||||
<div className="max-h-[300px] overflow-y-auto border rounded-md border-gray-300 p-2">
|
||||
{events.map((event: any, index: number) => (
|
||||
<ListItem
|
||||
<ListItem
|
||||
key={index}
|
||||
item={event}
|
||||
text={event.title}
|
||||
createdBy={event.createdByName}
|
||||
isPublish={event.isPublish}
|
||||
bgColor={getEventColor(event.agendaType)}
|
||||
/>
|
||||
))}
|
||||
|
|
@ -488,22 +525,72 @@ 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"
|
||||
>
|
||||
<Plus className="w-4 h-4 me-1" />
|
||||
{"Tambahkan Agenda baru"}
|
||||
</Button> :
|
||||
</Button>
|
||||
) : (
|
||||
""
|
||||
}
|
||||
)}
|
||||
<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">
|
||||
|
|
@ -536,7 +623,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
if (selectedCategory?.length === categories?.length) {
|
||||
setSelectedCategory([]);
|
||||
} else {
|
||||
setSelectedCategory(categories.map((c) => c.value));
|
||||
setSelectedCategory(categories?.map((c) => c.value));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
|
@ -597,35 +684,53 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
handleDateChange(info.view.currentStart, info.view.currentEnd);
|
||||
handleViewChange(info.view.type);
|
||||
}}
|
||||
viewClassNames={activeView === "listYear" ? "hide-calendar-grid" : ""}
|
||||
viewClassNames={
|
||||
activeView === "listYear" ? "hide-calendar-grid" : ""
|
||||
}
|
||||
/>
|
||||
|
||||
{activeView === "listYear" && (
|
||||
<div className="custom-ui">
|
||||
<div className="flex gap-1 mt-1">
|
||||
{months.slice(0, 3).map(month => (
|
||||
<MonthCard key={month.id} monthId={month.id} label={month.label} />
|
||||
{months.slice(0, 3).map((month) => (
|
||||
<MonthCard
|
||||
key={month.id}
|
||||
monthId={month.id}
|
||||
label={month.label}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Second Row */}
|
||||
<div className="flex gap-1 mt-1">
|
||||
{months.slice(3, 6).map(month => (
|
||||
<MonthCard key={month.id} monthId={month.id} label={month.label} />
|
||||
{months.slice(3, 6).map((month) => (
|
||||
<MonthCard
|
||||
key={month.id}
|
||||
monthId={month.id}
|
||||
label={month.label}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Third Row */}
|
||||
<div className="flex gap-1 mt-1">
|
||||
{months.slice(6, 9).map(month => (
|
||||
<MonthCard key={month.id} monthId={month.id} label={month.label} />
|
||||
{months.slice(6, 9).map((month) => (
|
||||
<MonthCard
|
||||
key={month.id}
|
||||
monthId={month.id}
|
||||
label={month.label}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Fourth Row */}
|
||||
<div className="flex gap-1 mt-1">
|
||||
{months.slice(9, 12).map(month => (
|
||||
<MonthCard key={month.id} monthId={month.id} label={month.label} />
|
||||
{months.slice(9, 12).map((month) => (
|
||||
<MonthCard
|
||||
key={month.id}
|
||||
monthId={month.id}
|
||||
label={month.label}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -125,7 +125,6 @@ export const categories = [
|
|||
{
|
||||
label: "Polda",
|
||||
value: "polda",
|
||||
|
||||
className:
|
||||
"data-[state=checked]:bg-blue-400 data-[state=checked]:ring-blue-400",
|
||||
},
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,17 +1,49 @@
|
|||
"use client"
|
||||
|
||||
import { getEvents, getCategories } from "./utils";
|
||||
import { calendarEvents, Category } from "./data";
|
||||
import CalendarView from "./calender-view";
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
import { useEffect, useState } from "react";
|
||||
import { CalendarCategory } from "./data";
|
||||
|
||||
const CalenderPage = () => {
|
||||
|
||||
const [categories, setCategories] = useState<CalendarCategory[]>([]);
|
||||
const userLevelNumber = Number(getCookiesDecrypt("ulne")) || 0;
|
||||
const userLevelId = Number(getCookiesDecrypt("ulie")) || 0;
|
||||
const userParentLevelId = Number(getCookiesDecrypt("uplie")) || 0;
|
||||
|
||||
useEffect(() => {
|
||||
initData();
|
||||
|
||||
async function initData() {
|
||||
const events = await getEvents();
|
||||
const categories = await getCategories();
|
||||
let valueShowed : string[] = [];
|
||||
if (userLevelNumber == 1) {
|
||||
valueShowed = ['mabes', 'polda', 'polres', 'satker', 'international'];
|
||||
} else if (userLevelNumber == 2 && userLevelId != 761) {
|
||||
valueShowed = ['polda', 'polres'];
|
||||
} else if ((userLevelNumber == 2 && userLevelId == 761) || (userLevelNumber == 3 && userParentLevelId == 761)) {
|
||||
valueShowed = ['satker'];
|
||||
} else if (userLevelNumber == 3 && userParentLevelId != 761) {
|
||||
valueShowed = ['polres'];
|
||||
}
|
||||
|
||||
const formattedCategories = categories.filter((category: Category) => valueShowed.includes(category.value))
|
||||
.map((category: Category) => ({
|
||||
...category,
|
||||
activeClass: "",
|
||||
}));
|
||||
console.log(formattedCategories);
|
||||
setCategories(formattedCategories);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const CalenderPage = async () => {
|
||||
const events = await getEvents();
|
||||
const categories = await getCategories();
|
||||
const formattedCategories = categories.map((category: Category) => ({
|
||||
...category,
|
||||
activeClass: "",
|
||||
}));
|
||||
return (
|
||||
<div>
|
||||
<CalendarView categories={formattedCategories} />
|
||||
{categories && <CalendarView categories={categories} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,204 @@
|
|||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getUserLevelForAssignments } from "@/service/task";
|
||||
|
||||
const FormSchema = z.object({
|
||||
items: z.array(z.string()).refine((value) => value.some((item) => item), {
|
||||
message: "Required",
|
||||
}),
|
||||
});
|
||||
|
||||
interface UnitType {
|
||||
id: number;
|
||||
name: string;
|
||||
subDestination: { id: number; name: string }[] | null;
|
||||
}
|
||||
|
||||
export function UnitMapping(props: {
|
||||
unit: "Polda" | "Satker" | "Polres";
|
||||
sendDataToParent: (data: string[]) => void;
|
||||
isDetail: boolean;
|
||||
initData?: string[];
|
||||
}) {
|
||||
const { unit, sendDataToParent, isDetail } = props;
|
||||
const [unitList, setUnitList] = useState<UnitType[]>([]);
|
||||
const [satkerList, setSatkerList] = useState<{ id: number; name: string }[]>(
|
||||
[]
|
||||
);
|
||||
const [polresList, setPolresList] = useState<{ id: number; name: string }[]>(
|
||||
[]
|
||||
);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
items: props.initData ? props.initData : [],
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
const response = await getUserLevelForAssignments();
|
||||
setupUnit(response?.data?.data.list);
|
||||
console.log("list", response?.data?.data.list);
|
||||
}
|
||||
|
||||
initState();
|
||||
}, []);
|
||||
|
||||
const unitType = form.watch("items");
|
||||
|
||||
const isAllUnitChecked = unitList.every((item) =>
|
||||
unitType?.includes(String(item.id))
|
||||
);
|
||||
const isAllSatkerChecked = satkerList.every((item) =>
|
||||
unitType?.includes(String(item.id))
|
||||
);
|
||||
const isAllPolresChecked = polresList.every((item) =>
|
||||
unitType?.includes(String(item.id))
|
||||
);
|
||||
|
||||
const setupUnit = (data: UnitType[]) => {
|
||||
const temp = data.filter((a) => a.name.includes("POLDA"));
|
||||
const temp2 = data.filter((a) => a.name.includes("SATKER"));
|
||||
const temp3 = temp.flatMap((item) => item.subDestination || []);
|
||||
setUnitList(temp);
|
||||
setSatkerList(temp2[0]?.subDestination || []);
|
||||
setPolresList(temp3);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
sendDataToParent(form.getValues("items"));
|
||||
}, [unitType]);
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<a
|
||||
onClick={() => setIsOpen(true)}
|
||||
className="text-primary cursor-pointer text-xs mr-3"
|
||||
>
|
||||
Pilih {unit}
|
||||
</a>
|
||||
</DialogTrigger>
|
||||
<DialogContent size="md" className="h-[500px] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{unit}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
<form className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<Checkbox
|
||||
id={`all-${unit}`}
|
||||
checked={
|
||||
unit === "Polda"
|
||||
? isAllUnitChecked
|
||||
: unit === "Satker"
|
||||
? isAllSatkerChecked
|
||||
: isAllPolresChecked
|
||||
}
|
||||
disabled={isDetail}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
form.setValue(
|
||||
"items",
|
||||
unit === "Polda"
|
||||
? unitList.map((item) => String(item.id))
|
||||
: unit === "Satker"
|
||||
? satkerList.map((item) => String(item.id))
|
||||
: polresList.map((item) => String(item.id))
|
||||
);
|
||||
} else {
|
||||
form.setValue("items", []);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="all" className="text-sm text-black uppercase">
|
||||
SEMUA {unit}
|
||||
</label>
|
||||
</div>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="items"
|
||||
render={() => (
|
||||
<FormItem
|
||||
className={`grid grid-cols-${
|
||||
unit === "Polda" ? "2" : unit === "Satker" ? "3" : "4"
|
||||
}`}
|
||||
>
|
||||
{(unit === "Polda"
|
||||
? unitList
|
||||
: unit === "Satker"
|
||||
? satkerList
|
||||
: polresList
|
||||
)?.map((item: any) => (
|
||||
<FormField
|
||||
key={item.id}
|
||||
control={form.control}
|
||||
name="items"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem
|
||||
key={String(item.id)}
|
||||
className="flex flex-row items-center space-x-3 space-y-0"
|
||||
>
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
disabled={isDetail}
|
||||
checked={field.value?.includes(String(item.id))}
|
||||
onCheckedChange={(checked) => {
|
||||
return checked
|
||||
? field.onChange([
|
||||
...field.value,
|
||||
String(item.id),
|
||||
])
|
||||
: field.onChange(
|
||||
field.value?.filter(
|
||||
(value) => value !== String(item.id)
|
||||
)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<p className="text-sm text-black">{item.name}</p>
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -12,7 +12,11 @@ import {
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { format } from "date-fns";
|
||||
import { Link } from "@/components/navigation";
|
||||
import { Link, useRouter } from "@/components/navigation";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { deleteBlog } from "@/service/blog/blog";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
|
|
@ -84,6 +88,48 @@ const columns: ColumnDef<any>[] = [
|
|||
header: "Actions",
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const router = useRouter();
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
async function deleteProcess(id: any) {
|
||||
loading();
|
||||
const resDelete = await deleteBlog(id);
|
||||
|
||||
if (resDelete?.error) {
|
||||
error(resDelete.message);
|
||||
return false;
|
||||
}
|
||||
success();
|
||||
}
|
||||
|
||||
function success() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const handleDeleteBlog = (id: any) => {
|
||||
MySwal.fire({
|
||||
title: "Hapus Data",
|
||||
text: "",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Hapus",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
deleteProcess(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -108,7 +154,10 @@ const columns: ColumnDef<any>[] = [
|
|||
Edit
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDeleteBlog(row.original.id)}
|
||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||
>
|
||||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ import { Button } from "@/components/ui/button";
|
|||
import { Badge } from "@/components/ui/badge";
|
||||
import { format } from "date-fns";
|
||||
import { Link } from "@/components/navigation";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { deleteMedia } from "@/service/content/content";
|
||||
import { error } from "@/lib/swal";
|
||||
import Swal from "sweetalert2";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
|
|
@ -64,17 +68,19 @@ const columns: ColumnDef<any>[] = [
|
|||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "creatorGroup",
|
||||
accessorKey: "creatorName",
|
||||
header: "Creator Group",
|
||||
cell: ({ row }) => (
|
||||
<span className="whitespace-nowrap">{row.getValue("creatorGroup")}</span>
|
||||
<span className="whitespace-nowrap">{row.getValue("creatorName")}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "creatorName",
|
||||
accessorKey: "creatorGroupLevelName",
|
||||
header: "Sumber",
|
||||
cell: ({ row }) => (
|
||||
<span className="whitespace-nowrap">{row.getValue("creatorName")}</span>
|
||||
<span className="whitespace-nowrap">
|
||||
{row.getValue("creatorGroupLevelName")}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
|
@ -135,6 +141,51 @@ const columns: ColumnDef<any>[] = [
|
|||
header: "Actions",
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
async function doDelete(id: any) {
|
||||
// loading();
|
||||
const data = {
|
||||
id,
|
||||
};
|
||||
|
||||
const response = await deleteMedia(data);
|
||||
|
||||
if (response?.error) {
|
||||
error(response.message);
|
||||
return false;
|
||||
}
|
||||
success();
|
||||
}
|
||||
|
||||
function success() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const handleDeleteMedia = (id: any) => {
|
||||
MySwal.fire({
|
||||
title: "Hapus Data",
|
||||
text: "",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Hapus",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
doDelete(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -159,7 +210,10 @@ const columns: ColumnDef<any>[] = [
|
|||
Edit
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDeleteMedia(row.original.id)}
|
||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||
>
|
||||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import {
|
|||
} from "@/components/ui/table";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Eye,
|
||||
|
|
@ -39,6 +40,7 @@ import {
|
|||
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
|
|
@ -55,7 +57,10 @@ import {
|
|||
listDataAudio,
|
||||
listDataImage,
|
||||
listDataVideo,
|
||||
listEnableCategory,
|
||||
} from "@/service/content/content";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { format } from "date-fns";
|
||||
|
||||
const TableAudio = () => {
|
||||
const router = useRouter();
|
||||
|
|
@ -81,13 +86,17 @@ const TableAudio = () => {
|
|||
const userId = getCookiesDecrypt("uie");
|
||||
const userLevelId = getCookiesDecrypt("ulie");
|
||||
|
||||
const [categories, setCategories] = React.useState();
|
||||
const [categoryFilter, setCategoryFilter] = React.useState([]);
|
||||
const [statusFilter, setStatusFilter] = React.useState([]);
|
||||
const [startDateString, setStartDateString] = React.useState("");
|
||||
const [endDateString, setEndDateString] = React.useState("");
|
||||
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 [startDate, setStartDate] = React.useState("");
|
||||
const [endDate, setEndDate] = React.useState("");
|
||||
const [filterByCreator, setFilterByCreator] = React.useState("");
|
||||
const [filterBySource, setFilterBySource] = React.useState("");
|
||||
const [filterByCreatorGroup, setFilterByCreatorGroup] = React.useState("");
|
||||
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
|
||||
|
|
@ -121,35 +130,67 @@ const TableAudio = () => {
|
|||
|
||||
React.useEffect(() => {
|
||||
fetchData();
|
||||
}, [page, limit, search]);
|
||||
getCategories();
|
||||
}, [categoryFilter, statusFilter, page, limit, search, startDate, endDate]);
|
||||
|
||||
async function getCategories() {
|
||||
const category = await listEnableCategory("4");
|
||||
const resCategory = category?.data?.data?.content;
|
||||
setCategories(resCategory || []);
|
||||
}
|
||||
|
||||
// Fungsi menangani perubahan checkbox
|
||||
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(",");
|
||||
});
|
||||
};
|
||||
|
||||
async function fetchData() {
|
||||
const formattedStartDate = startDate
|
||||
? format(new Date(startDate), "yyyy-MM-dd")
|
||||
: "";
|
||||
const formattedEndDate = endDate
|
||||
? format(new Date(endDate), "yyyy-MM-dd")
|
||||
: "";
|
||||
try {
|
||||
const isForSelf = Number(roleId) == 4;
|
||||
const isForSelf = Number(roleId) === 4;
|
||||
const res = await listDataAudio(
|
||||
limit,
|
||||
page - 1,
|
||||
isForSelf,
|
||||
!isForSelf,
|
||||
categoryFilter?.sort().join(","),
|
||||
statusFilter?.sort().join(",").includes("1")
|
||||
? "1,2"
|
||||
: statusFilter?.sort().join(","),
|
||||
categoryFilter,
|
||||
statusFilter,
|
||||
statusFilter?.sort().join(",").includes("1") ? userLevelId : "",
|
||||
filterByCreator,
|
||||
filterBySource,
|
||||
startDateString,
|
||||
endDateString,
|
||||
search
|
||||
formattedStartDate, // Pastikan format sesuai
|
||||
formattedEndDate, // Pastikan format sesuai
|
||||
search,
|
||||
filterByCreatorGroup
|
||||
);
|
||||
|
||||
const data = res?.data?.data;
|
||||
const contentData = data?.content;
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * limit + index + 1;
|
||||
});
|
||||
|
||||
console.log("contentData : ", contentData);
|
||||
|
||||
setDataTable(contentData);
|
||||
setTotalData(data?.totalElements);
|
||||
setTotalPage(data?.totalPages);
|
||||
|
|
@ -157,11 +198,36 @@ const TableAudio = () => {
|
|||
console.error("Error fetching tasks:", error);
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearch(e.target.value); // Perbarui state search
|
||||
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
|
||||
};
|
||||
|
||||
const handleSearchFilterBySource = (
|
||||
e: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
const value = e.target.value;
|
||||
setFilterBySource(value); // Perbarui state filter
|
||||
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>
|
||||
) => {
|
||||
const value = e.target.value;
|
||||
setFilterByCreator(value); // Perbarui state filter
|
||||
fetchData(); // Panggil ulang data dengan filter baru
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="flex justify-between items-center px-5">
|
||||
|
|
@ -179,17 +245,171 @@ const TableAudio = () => {
|
|||
/>
|
||||
</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>
|
||||
)}
|
||||
<div className="mx-2 my-1">
|
||||
<Label>Tanggal Awal</Label>
|
||||
<Input
|
||||
type="date"
|
||||
value={startDate}
|
||||
onChange={(e) => setStartDate(e.target.value)}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="mx-2 my-1">
|
||||
<Label>Tanggal Akhir</Label>
|
||||
<Input
|
||||
type="date"
|
||||
value={endDate}
|
||||
onChange={(e) => setEndDate(e.target.value)}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="mx-2 my-1">
|
||||
<Label>Kreator</Label>
|
||||
<Input
|
||||
placeholder="Filter Status..."
|
||||
value={filterByCreator}
|
||||
onChange={handleSearchFilterByCreator}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="mx-2 my-1">
|
||||
<Label>Sumber</Label>
|
||||
<Input
|
||||
placeholder="Filter Status..."
|
||||
value={filterBySource}
|
||||
onChange={handleSearchFilterBySource}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -13,6 +13,14 @@ import { Button } from "@/components/ui/button";
|
|||
import { Badge } from "@/components/ui/badge";
|
||||
import { format } from "date-fns";
|
||||
import { Link } from "@/components/navigation";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { deleteMedia } from "@/service/content/content";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
|
|
@ -64,17 +72,19 @@ const columns: ColumnDef<any>[] = [
|
|||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "creatorGroup",
|
||||
accessorKey: "creatorName",
|
||||
header: "Creator Group",
|
||||
cell: ({ row }) => (
|
||||
<span className="whitespace-nowrap">{row.getValue("creatorGroup")}</span>
|
||||
<span className="whitespace-nowrap">{row.getValue("creatorName")}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "creatorName",
|
||||
accessorKey: "creatorGroupLevelName",
|
||||
header: "Sumber",
|
||||
cell: ({ row }) => (
|
||||
<span className="whitespace-nowrap">{row.getValue("creatorName")}</span>
|
||||
<span className="whitespace-nowrap">
|
||||
{row.getValue("creatorGroupLevelName")}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
|
@ -131,6 +141,52 @@ const columns: ColumnDef<any>[] = [
|
|||
header: "Actions",
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const router = useRouter();
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
async function doDelete(id: any) {
|
||||
// loading();
|
||||
const data = {
|
||||
id,
|
||||
};
|
||||
|
||||
const response = await deleteMedia(data);
|
||||
|
||||
if (response?.error) {
|
||||
error(response.message);
|
||||
return false;
|
||||
}
|
||||
success();
|
||||
}
|
||||
|
||||
function success() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const handleDeleteMedia = (id: any) => {
|
||||
MySwal.fire({
|
||||
title: "Hapus Data",
|
||||
text: "",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Hapus",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
doDelete(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -155,7 +211,10 @@ const columns: ColumnDef<any>[] = [
|
|||
Edit
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDeleteMedia(row.original.id)}
|
||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||
>
|
||||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
|
|
|
|||
|
|
@ -53,12 +53,24 @@ import { Badge } from "@/components/ui/badge";
|
|||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import TablePagination from "@/components/table/table-pagination";
|
||||
import columns from "./columns";
|
||||
import { listDataImage } from "@/service/content/content";
|
||||
import {
|
||||
deleteMedia,
|
||||
listDataImage,
|
||||
listEnableCategory,
|
||||
} from "@/service/content/content";
|
||||
import { loading } from "@/config/swal";
|
||||
|
||||
import { toast } from "sonner";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { error } from "@/lib/swal";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { format } from "date-fns";
|
||||
|
||||
const TableImage = () => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const MySwal = withReactContent(Swal);
|
||||
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
||||
const [totalData, setTotalData] = React.useState<number>(1);
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
|
|
@ -79,13 +91,17 @@ const TableImage = () => {
|
|||
const userId = getCookiesDecrypt("uie");
|
||||
const userLevelId = getCookiesDecrypt("ulie");
|
||||
|
||||
const [categories, setCategories] = React.useState();
|
||||
const [categoryFilter, setCategoryFilter] = React.useState([]);
|
||||
const [statusFilter, setStatusFilter] = React.useState([]);
|
||||
const [startDateString, setStartDateString] = React.useState("");
|
||||
const [endDateString, setEndDateString] = React.useState("");
|
||||
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 [startDate, setStartDate] = React.useState("");
|
||||
const [endDate, setEndDate] = React.useState("");
|
||||
const [filterByCreator, setFilterByCreator] = React.useState("");
|
||||
const [filterBySource, setFilterBySource] = React.useState("");
|
||||
const [filterByCreatorGroup, setFilterByCreatorGroup] = React.useState("");
|
||||
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
|
||||
|
|
@ -118,36 +134,69 @@ const TableImage = () => {
|
|||
}, [searchParams]);
|
||||
|
||||
React.useEffect(() => {
|
||||
// Panggil fetchData saat filter kategori berubah
|
||||
fetchData();
|
||||
}, [page, limit, search]);
|
||||
getCategories();
|
||||
}, [categoryFilter, statusFilter, page, limit, search, startDate, endDate]);
|
||||
|
||||
async function getCategories() {
|
||||
const category = await listEnableCategory("1");
|
||||
const resCategory = category?.data?.data?.content;
|
||||
setCategories(resCategory || []);
|
||||
}
|
||||
|
||||
// Fungsi menangani perubahan checkbox
|
||||
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(",");
|
||||
});
|
||||
};
|
||||
|
||||
async function fetchData() {
|
||||
const formattedStartDate = startDate
|
||||
? format(new Date(startDate), "yyyy-MM-dd")
|
||||
: "";
|
||||
const formattedEndDate = endDate
|
||||
? format(new Date(endDate), "yyyy-MM-dd")
|
||||
: "";
|
||||
try {
|
||||
const isForSelf = Number(roleId) == 4;
|
||||
const isForSelf = Number(roleId) === 4;
|
||||
const res = await listDataImage(
|
||||
limit,
|
||||
page - 1,
|
||||
isForSelf,
|
||||
!isForSelf,
|
||||
categoryFilter?.sort().join(","),
|
||||
statusFilter?.sort().join(",").includes("1")
|
||||
? "1,2"
|
||||
: statusFilter?.sort().join(","),
|
||||
categoryFilter,
|
||||
statusFilter,
|
||||
statusFilter?.sort().join(",").includes("1") ? userLevelId : "",
|
||||
filterByCreator,
|
||||
filterBySource,
|
||||
startDateString,
|
||||
endDateString,
|
||||
search
|
||||
formattedStartDate, // Pastikan format sesuai
|
||||
formattedEndDate, // Pastikan format sesuai
|
||||
search,
|
||||
filterByCreatorGroup
|
||||
);
|
||||
|
||||
const data = res?.data?.data;
|
||||
const contentData = data?.content;
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * limit + index + 1;
|
||||
});
|
||||
|
||||
console.log("contentData : ", contentData);
|
||||
|
||||
setDataTable(contentData);
|
||||
setTotalData(data?.totalElements);
|
||||
setTotalPage(data?.totalPages);
|
||||
|
|
@ -161,6 +210,30 @@ const TableImage = () => {
|
|||
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
|
||||
};
|
||||
|
||||
const handleSearchFilterBySource = (
|
||||
e: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
const value = e.target.value;
|
||||
setFilterBySource(value); // Perbarui state filter
|
||||
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>
|
||||
) => {
|
||||
const value = e.target.value;
|
||||
setFilterByCreator(value); // Perbarui state filter
|
||||
fetchData(); // Panggil ulang data dengan filter baru
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="flex justify-between items-center px-5">
|
||||
|
|
@ -179,16 +252,142 @@ const TableImage = () => {
|
|||
</InputGroup>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<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 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>
|
||||
)}
|
||||
<div className="mx-2 my-1">
|
||||
<Label>Tanggal Awal</Label>
|
||||
<Input
|
||||
type="date"
|
||||
value={startDate}
|
||||
onChange={(e) => setStartDate(e.target.value)}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="mx-2 my-1">
|
||||
<Label>Tanggal Akhir</Label>
|
||||
<Input
|
||||
type="date"
|
||||
value={endDate}
|
||||
onChange={(e) => setEndDate(e.target.value)}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="mx-2 my-1">
|
||||
<Label>Kreator</Label>
|
||||
<Input
|
||||
placeholder="Filter Status..."
|
||||
value={filterByCreator}
|
||||
onChange={handleSearchFilterByCreator}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="mx-2 my-1">
|
||||
<Label>Sumber</Label>
|
||||
<Input
|
||||
placeholder="Filter Status..."
|
||||
value={filterBySource}
|
||||
onChange={handleSearchFilterBySource}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ const columns: ColumnDef<any>[] = [
|
|||
<div>
|
||||
<Button
|
||||
size="sm"
|
||||
color={isPublish ? "success" : "warning"} // Hijau untuk diterima, oranye untuk menunggu review
|
||||
color={isPublish ? "success" : "warning"}
|
||||
variant="outline"
|
||||
className={`btn btn-sm ${
|
||||
isPublish ? "btn-outline-success" : "btn-outline-warning"
|
||||
|
|
|
|||
|
|
@ -170,10 +170,10 @@ const TableSPIT = () => {
|
|||
<Input
|
||||
placeholder="Filter Status..."
|
||||
value={
|
||||
(table.getColumn("status")?.getFilterValue() as string) ?? ""
|
||||
(table.getColumn("isPublish")?.getFilterValue() as string) ?? ""
|
||||
}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
table.getColumn("status")?.setFilterValue(event.target.value)
|
||||
table.getColumn("isPublish")?.setFilterValue(event.target.value)
|
||||
}
|
||||
className="max-w-sm "
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ import { Button } from "@/components/ui/button";
|
|||
import { Badge } from "@/components/ui/badge";
|
||||
import { format } from "date-fns";
|
||||
import { Link } from "@/components/navigation";
|
||||
import { error } from "@/lib/swal";
|
||||
import { deleteMedia } from "@/service/content/content";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import Swal from "sweetalert2";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
|
|
@ -64,17 +68,19 @@ const columns: ColumnDef<any>[] = [
|
|||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "creatorGroup",
|
||||
accessorKey: "creatorName",
|
||||
header: "Creator Group",
|
||||
cell: ({ row }) => (
|
||||
<span className="whitespace-nowrap">{row.getValue("creatorGroup")}</span>
|
||||
<span className="whitespace-nowrap">{row.getValue("creatorName")}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "creatorName",
|
||||
accessorKey: "creatorGroupLevelName",
|
||||
header: "Sumber",
|
||||
cell: ({ row }) => (
|
||||
<span className="whitespace-nowrap">{row.getValue("creatorName")}</span>
|
||||
<span className="whitespace-nowrap">
|
||||
{row.getValue("creatorGroupLevelName")}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
|
@ -136,6 +142,51 @@ const columns: ColumnDef<any>[] = [
|
|||
header: "Actions",
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
async function doDelete(id: any) {
|
||||
// loading();
|
||||
const data = {
|
||||
id,
|
||||
};
|
||||
|
||||
const response = await deleteMedia(data);
|
||||
|
||||
if (response?.error) {
|
||||
error(response.message);
|
||||
return false;
|
||||
}
|
||||
success();
|
||||
}
|
||||
|
||||
function success() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const handleDeleteMedia = (id: any) => {
|
||||
MySwal.fire({
|
||||
title: "Hapus Data",
|
||||
text: "",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Hapus",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
doDelete(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -160,7 +211,10 @@ const columns: ColumnDef<any>[] = [
|
|||
Edit
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDeleteMedia(row.original.id)}
|
||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||
>
|
||||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import {
|
|||
} from "@/components/ui/table";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Eye,
|
||||
|
|
@ -39,6 +40,7 @@ import {
|
|||
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
|
|
@ -51,7 +53,13 @@ import { Badge } from "@/components/ui/badge";
|
|||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import TablePagination from "@/components/table/table-pagination";
|
||||
import columns from "./columns";
|
||||
import { listDataImage, listDataTeks } from "@/service/content/content";
|
||||
import {
|
||||
listDataImage,
|
||||
listDataTeks,
|
||||
listEnableCategory,
|
||||
} from "@/service/content/content";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { format } from "date-fns";
|
||||
|
||||
const TableTeks = () => {
|
||||
const router = useRouter();
|
||||
|
|
@ -77,13 +85,17 @@ const TableTeks = () => {
|
|||
const userId = getCookiesDecrypt("uie");
|
||||
const userLevelId = getCookiesDecrypt("ulie");
|
||||
|
||||
const [categories, setCategories] = React.useState();
|
||||
const [categoryFilter, setCategoryFilter] = React.useState([]);
|
||||
const [statusFilter, setStatusFilter] = React.useState([]);
|
||||
const [startDateString, setStartDateString] = React.useState("");
|
||||
const [endDateString, setEndDateString] = React.useState("");
|
||||
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 [startDate, setStartDate] = React.useState("");
|
||||
const [endDate, setEndDate] = React.useState("");
|
||||
const [filterByCreator, setFilterByCreator] = React.useState("");
|
||||
const [filterBySource, setFilterBySource] = React.useState("");
|
||||
const [filterByCreatorGroup, setFilterByCreatorGroup] = React.useState("");
|
||||
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
|
||||
|
|
@ -117,35 +129,67 @@ const TableTeks = () => {
|
|||
|
||||
React.useEffect(() => {
|
||||
fetchData();
|
||||
}, [page, limit, search]);
|
||||
getCategories();
|
||||
}, [categoryFilter, statusFilter, page, limit, search, startDate, endDate]);
|
||||
|
||||
async function getCategories() {
|
||||
const category = await listEnableCategory("3");
|
||||
const resCategory = category?.data?.data?.content;
|
||||
setCategories(resCategory || []);
|
||||
}
|
||||
|
||||
// Fungsi menangani perubahan checkbox
|
||||
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(",");
|
||||
});
|
||||
};
|
||||
|
||||
async function fetchData() {
|
||||
const formattedStartDate = startDate
|
||||
? format(new Date(startDate), "yyyy-MM-dd")
|
||||
: "";
|
||||
const formattedEndDate = endDate
|
||||
? format(new Date(endDate), "yyyy-MM-dd")
|
||||
: "";
|
||||
try {
|
||||
const isForSelf = Number(roleId) == 4;
|
||||
const isForSelf = Number(roleId) === 4;
|
||||
const res = await listDataTeks(
|
||||
limit,
|
||||
page - 1,
|
||||
isForSelf,
|
||||
!isForSelf,
|
||||
categoryFilter?.sort().join(","),
|
||||
statusFilter?.sort().join(",").includes("1")
|
||||
? "1,2"
|
||||
: statusFilter?.sort().join(","),
|
||||
categoryFilter,
|
||||
statusFilter,
|
||||
statusFilter?.sort().join(",").includes("1") ? userLevelId : "",
|
||||
filterByCreator,
|
||||
filterBySource,
|
||||
startDateString,
|
||||
endDateString,
|
||||
search
|
||||
formattedStartDate, // Pastikan format sesuai
|
||||
formattedEndDate, // Pastikan format sesuai
|
||||
search,
|
||||
filterByCreatorGroup
|
||||
);
|
||||
|
||||
const data = res?.data?.data;
|
||||
const contentData = data?.content;
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * limit + index + 1;
|
||||
});
|
||||
|
||||
console.log("contentData : ", contentData);
|
||||
|
||||
setDataTable(contentData);
|
||||
setTotalData(data?.totalElements);
|
||||
setTotalPage(data?.totalPages);
|
||||
|
|
@ -159,6 +203,30 @@ const TableTeks = () => {
|
|||
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
|
||||
};
|
||||
|
||||
const handleSearchFilterBySource = (
|
||||
e: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
const value = e.target.value;
|
||||
setFilterBySource(value); // Perbarui state filter
|
||||
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>
|
||||
) => {
|
||||
const value = e.target.value;
|
||||
setFilterByCreator(value); // Perbarui state filter
|
||||
fetchData(); // Panggil ulang data dengan filter baru
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="flex justify-between items-center px-5">
|
||||
|
|
@ -176,17 +244,171 @@ const TableTeks = () => {
|
|||
/>
|
||||
</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>
|
||||
)}
|
||||
<div className="mx-2 my-1">
|
||||
<Label>Tanggal Awal</Label>
|
||||
<Input
|
||||
type="date"
|
||||
value={startDate}
|
||||
onChange={(e) => setStartDate(e.target.value)}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="mx-2 my-1">
|
||||
<Label>Tanggal Akhir</Label>
|
||||
<Input
|
||||
type="date"
|
||||
value={endDate}
|
||||
onChange={(e) => setEndDate(e.target.value)}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="mx-2 my-1">
|
||||
<Label>Kreator</Label>
|
||||
<Input
|
||||
placeholder="Filter Status..."
|
||||
value={filterByCreator}
|
||||
onChange={handleSearchFilterByCreator}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="mx-2 my-1">
|
||||
<Label>Sumber</Label>
|
||||
<Input
|
||||
placeholder="Filter Status..."
|
||||
value={filterBySource}
|
||||
onChange={handleSearchFilterBySource}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ import { Button } from "@/components/ui/button";
|
|||
import { Badge } from "@/components/ui/badge";
|
||||
import { format } from "date-fns";
|
||||
import { Link } from "@/components/navigation";
|
||||
import { deleteMedia } from "@/service/content/content";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { error } from "@/lib/swal";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
|
|
@ -64,17 +68,19 @@ const columns: ColumnDef<any>[] = [
|
|||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "creatorGroup",
|
||||
accessorKey: "creatorName",
|
||||
header: "Creator Group",
|
||||
cell: ({ row }) => (
|
||||
<span className="whitespace-nowrap">{row.getValue("creatorGroup")}</span>
|
||||
<span className="whitespace-nowrap">{row.getValue("creatorName")}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "creatorName",
|
||||
accessorKey: "creatorGroupLevelName",
|
||||
header: "Sumber",
|
||||
cell: ({ row }) => (
|
||||
<span className="whitespace-nowrap">{row.getValue("creatorName")}</span>
|
||||
<span className="whitespace-nowrap">
|
||||
{row.getValue("creatorGroupLevelName")}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
|
@ -110,13 +116,10 @@ const columns: ColumnDef<any>[] = [
|
|||
"menunggu review": "bg-orange-100 text-orange-600",
|
||||
};
|
||||
|
||||
// Mengambil `statusName` dari data API
|
||||
const status = row.getValue("statusName") as string;
|
||||
const statusName = status?.toLocaleLowerCase(); // Ubah ke huruf kecil
|
||||
|
||||
// Gunakan `statusName` untuk pencocokan
|
||||
const statusName = status?.toLocaleLowerCase();
|
||||
const statusStyles =
|
||||
statusColors[statusName] || "bg-gray-100 text-gray-600";
|
||||
statusColors[statusName] || "bg-red-200 text-red-600";
|
||||
|
||||
return (
|
||||
<Badge
|
||||
|
|
@ -136,6 +139,51 @@ const columns: ColumnDef<any>[] = [
|
|||
header: "Actions",
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
async function doDelete(id: any) {
|
||||
// loading();
|
||||
const data = {
|
||||
id,
|
||||
};
|
||||
|
||||
const response = await deleteMedia(data);
|
||||
|
||||
if (response?.error) {
|
||||
error(response.message);
|
||||
return false;
|
||||
}
|
||||
success();
|
||||
}
|
||||
|
||||
function success() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const handleDeleteMedia = (id: any) => {
|
||||
MySwal.fire({
|
||||
title: "Hapus Data",
|
||||
text: "",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Hapus",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
doDelete(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -160,7 +208,10 @@ const columns: ColumnDef<any>[] = [
|
|||
Edit
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDeleteMedia(row.original.id)}
|
||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||
>
|
||||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import {
|
|||
} from "@/components/ui/table";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Eye,
|
||||
|
|
@ -39,6 +40,7 @@ import {
|
|||
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
|
|
@ -51,9 +53,15 @@ import { Badge } from "@/components/ui/badge";
|
|||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import TablePagination from "@/components/table/table-pagination";
|
||||
import columns from "./columns";
|
||||
import { listDataImage, listDataVideo } from "@/service/content/content";
|
||||
import {
|
||||
listDataImage,
|
||||
listDataVideo,
|
||||
listEnableCategory,
|
||||
} from "@/service/content/content";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { format } from "date-fns";
|
||||
|
||||
const TableImage = () => {
|
||||
const TableVideo = () => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
|
|
@ -77,13 +85,17 @@ const TableImage = () => {
|
|||
const userId = getCookiesDecrypt("uie");
|
||||
const userLevelId = getCookiesDecrypt("ulie");
|
||||
|
||||
const [categories, setCategories] = React.useState();
|
||||
const [categoryFilter, setCategoryFilter] = React.useState([]);
|
||||
const [statusFilter, setStatusFilter] = React.useState([]);
|
||||
const [startDateString, setStartDateString] = React.useState("");
|
||||
const [endDateString, setEndDateString] = React.useState("");
|
||||
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 [startDate, setStartDate] = React.useState("");
|
||||
const [endDate, setEndDate] = React.useState("");
|
||||
const [filterByCreator, setFilterByCreator] = React.useState("");
|
||||
const [filterBySource, setFilterBySource] = React.useState("");
|
||||
const [filterByCreatorGroup, setFilterByCreatorGroup] = React.useState("");
|
||||
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
|
||||
|
|
@ -117,35 +129,67 @@ const TableImage = () => {
|
|||
|
||||
React.useEffect(() => {
|
||||
fetchData();
|
||||
}, [page, limit, search]);
|
||||
getCategories();
|
||||
}, [categoryFilter, statusFilter, page, limit, search, startDate, endDate]);
|
||||
|
||||
async function getCategories() {
|
||||
const category = await listEnableCategory("2");
|
||||
const resCategory = category?.data?.data?.content;
|
||||
setCategories(resCategory || []);
|
||||
}
|
||||
|
||||
// Fungsi menangani perubahan checkbox
|
||||
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(",");
|
||||
});
|
||||
};
|
||||
|
||||
async function fetchData() {
|
||||
const formattedStartDate = startDate
|
||||
? format(new Date(startDate), "yyyy-MM-dd")
|
||||
: "";
|
||||
const formattedEndDate = endDate
|
||||
? format(new Date(endDate), "yyyy-MM-dd")
|
||||
: "";
|
||||
try {
|
||||
const isForSelf = Number(roleId) == 4;
|
||||
const isForSelf = Number(roleId) === 4;
|
||||
const res = await listDataVideo(
|
||||
limit,
|
||||
page - 1,
|
||||
isForSelf,
|
||||
!isForSelf,
|
||||
categoryFilter?.sort().join(","),
|
||||
statusFilter?.sort().join(",").includes("1")
|
||||
? "1,2"
|
||||
: statusFilter?.sort().join(","),
|
||||
categoryFilter,
|
||||
statusFilter,
|
||||
statusFilter?.sort().join(",").includes("1") ? userLevelId : "",
|
||||
filterByCreator,
|
||||
filterBySource,
|
||||
startDateString,
|
||||
endDateString,
|
||||
search
|
||||
formattedStartDate, // Pastikan format sesuai
|
||||
formattedEndDate, // Pastikan format sesuai
|
||||
search,
|
||||
filterByCreatorGroup
|
||||
);
|
||||
|
||||
const data = res?.data?.data;
|
||||
const contentData = data?.content;
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * limit + index + 1;
|
||||
});
|
||||
|
||||
console.log("contentData : ", contentData);
|
||||
|
||||
setDataTable(contentData);
|
||||
setTotalData(data?.totalElements);
|
||||
setTotalPage(data?.totalPages);
|
||||
|
|
@ -159,6 +203,30 @@ const TableImage = () => {
|
|||
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
|
||||
};
|
||||
|
||||
const handleSearchFilterBySource = (
|
||||
e: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
const value = e.target.value;
|
||||
setFilterBySource(value); // Perbarui state filter
|
||||
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>
|
||||
) => {
|
||||
const value = e.target.value;
|
||||
setFilterByCreator(value); // Perbarui state filter
|
||||
fetchData(); // Panggil ulang data dengan filter baru
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="flex justify-between items-center px-5">
|
||||
|
|
@ -176,17 +244,172 @@ const TableImage = () => {
|
|||
/>
|
||||
</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>
|
||||
)}
|
||||
<div className="mx-2 my-1">
|
||||
<Label>Tanggal Awal</Label>
|
||||
<Input
|
||||
type="date"
|
||||
value={startDate}
|
||||
onChange={(e) => setStartDate(e.target.value)}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="mx-2 my-1">
|
||||
<Label>Tanggal Akhir</Label>
|
||||
<Input
|
||||
type="date"
|
||||
value={endDate}
|
||||
onChange={(e) => setEndDate(e.target.value)}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="mx-2 my-1">
|
||||
<Label>Kreator</Label>
|
||||
<Input
|
||||
placeholder="Filter Status..."
|
||||
value={filterByCreator}
|
||||
onChange={handleSearchFilterByCreator}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="mx-2 my-1">
|
||||
<Label>Sumber</Label>
|
||||
<Input
|
||||
placeholder="Filter Status..."
|
||||
value={filterBySource}
|
||||
onChange={handleSearchFilterBySource}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
|
|
@ -239,4 +462,4 @@ const TableImage = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export default TableImage;
|
||||
export default TableVideo;
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -13,6 +13,13 @@ import { Button } from "@/components/ui/button";
|
|||
import { Badge } from "@/components/ui/badge";
|
||||
import { format } from "date-fns";
|
||||
import { Link } from "@/components/navigation";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { deleteCategory } from "@/service/settings/settings";
|
||||
import { deleteTask } from "@/service/task";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import Swal from "sweetalert2";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
|
|
@ -115,6 +122,49 @@ const columns: ColumnDef<any>[] = [
|
|||
header: "Actions",
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const router = useRouter();
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
async function deleteProcess(id: any) {
|
||||
loading();
|
||||
const resDelete = await deleteTask(id);
|
||||
|
||||
if (resDelete?.error) {
|
||||
error(resDelete.message);
|
||||
return false;
|
||||
}
|
||||
success();
|
||||
}
|
||||
|
||||
function success() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const TaskDelete = (id: any) => {
|
||||
MySwal.fire({
|
||||
title: "Hapus Data",
|
||||
text: "",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Hapus",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
deleteProcess(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -139,7 +189,10 @@ const columns: ColumnDef<any>[] = [
|
|||
Edit
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
||||
<DropdownMenuItem
|
||||
onClick={() => TaskDelete(row.original.id)}
|
||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||
>
|
||||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -12,14 +12,14 @@ import { Button } from "@/components/ui/button";
|
|||
import InternalTable from "./internal/components/internal-table";
|
||||
|
||||
const CommunicationPage = () => {
|
||||
const [tab, setTab] = useState("Komunikasi");
|
||||
const [tab, setTab] = useState("Pertanyaan Internal");
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
||||
<div className="flex justify-between py-3">
|
||||
<p className="text-lg">{tab}</p>
|
||||
{tab === "Komunikasi" && (
|
||||
{tab === "Pertanyaan Internal" && (
|
||||
<Link href="/shared/communication/internal/create">
|
||||
<Button color="primary" size="md">
|
||||
<PlusIcon />
|
||||
|
|
@ -39,15 +39,15 @@ const CommunicationPage = () => {
|
|||
<div className="flex flex-row gap-1 border-2 rounded-md w-fit mb-5">
|
||||
<Button
|
||||
rounded="md"
|
||||
onClick={() => setTab("Komunikasi")}
|
||||
onClick={() => setTab("Pertanyaan Internal")}
|
||||
className={` hover:text-white
|
||||
${
|
||||
tab === "Komunikasi"
|
||||
tab === "Pertanyaan Internal"
|
||||
? "bg-black text-white "
|
||||
: "bg-white text-black "
|
||||
}`}
|
||||
>
|
||||
Komunikasi
|
||||
Pertanyaan Internal
|
||||
</Button>
|
||||
<Button
|
||||
rounded="md"
|
||||
|
|
@ -74,7 +74,7 @@ const CommunicationPage = () => {
|
|||
Kolaborasi
|
||||
</Button>
|
||||
</div>
|
||||
{tab === "Komunikasi" && <InternalTable />}
|
||||
{tab === "Pertanyaan Internal" && <InternalTable />}
|
||||
{tab === "Eskalasi" && <EscalationTable />}
|
||||
{tab === "Kolaborasi" && <CollaborationTable />}
|
||||
</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;
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
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 { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "@/components/ui/carousel";
|
||||
import { Link } from "@/components/navigation";
|
||||
import { formatDateToIndonesian, generateLocalizedPath } from "@/utils/globals";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { locale } from "dayjs";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getListContent } from "@/service/landing/landing";
|
||||
import ContestTable from "../../../../contest/components/contest-table";
|
||||
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 (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="my-3">
|
||||
<Tabs defaultValue="giat-routine" className="w-full">
|
||||
<Card className="py-3 px-2 my-4 h-20 flex items-center">
|
||||
<p className="text-lg font-semibold ml-2">Konten Audio</p>
|
||||
</Card>
|
||||
<TabsContent value="giat-routine">
|
||||
<div className="grid grid-cols-12 gap-5">
|
||||
<div className="lg:col-span-12 col-span-12">
|
||||
<Card>
|
||||
<div className="flex justify-between items-center py-4 px-5">
|
||||
<div>
|
||||
<InputGroup merged>
|
||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||
<Search className=" h-4 w-4 dark:text-white" />
|
||||
</InputGroupText>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search Judul..."
|
||||
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-5 pb-3">
|
||||
<div className="flex justify-between items-center mx-3">
|
||||
<Label className="text-base">Audio</Label>
|
||||
</div>
|
||||
<div className="px-5 my-5">
|
||||
<AudioAll />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AudioAllPage;
|
||||
|
|
@ -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,
|
||||
|
|
@ -14,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";
|
||||
|
||||
|
|
@ -21,34 +23,35 @@ const AudioSliderPage = () => {
|
|||
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("");
|
||||
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 getListContent({
|
||||
page: page - 1,
|
||||
size: 12,
|
||||
sortBy: "createdAt",
|
||||
contentTypeId: "4",
|
||||
});
|
||||
const response = await listCuratedContent(search, limit, page - 1, 4, "1");
|
||||
console.log(response);
|
||||
setAudioData(response?.data?.data?.content);
|
||||
|
||||
const data = response?.data?.data;
|
||||
const contentData = data?.content;
|
||||
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--) {
|
||||
|
|
@ -59,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-110 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>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
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 { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "@/components/ui/carousel";
|
||||
import { Link } from "@/components/navigation";
|
||||
import { formatDateToIndonesian, generateLocalizedPath } from "@/utils/globals";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { locale } from "dayjs";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getListContent } from "@/service/landing/landing";
|
||||
import ContestTable from "../../../../contest/components/contest-table";
|
||||
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 (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="my-3">
|
||||
<Tabs defaultValue="giat-routine" className="w-full">
|
||||
<Card className="py-3 px-2 my-4 h-20 flex items-center">
|
||||
<p className="text-lg font-semibold ml-2">Konten Teks</p>
|
||||
</Card>
|
||||
<TabsContent value="giat-routine">
|
||||
<div className="grid grid-cols-12 gap-5">
|
||||
<div className="lg:col-span-12 col-span-12">
|
||||
<Card>
|
||||
<div className="flex justify-between items-center py-4 px-5">
|
||||
<div>
|
||||
<InputGroup merged>
|
||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||
<Search className=" h-4 w-4 dark:text-white" />
|
||||
</InputGroupText>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search Judul..."
|
||||
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-5 pb-3">
|
||||
<div className="flex justify-between items-center mx-3">
|
||||
<Label className="text-base">Teks</Label>
|
||||
</div>
|
||||
<div className="px-5 my-5">
|
||||
<TeksAll />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="giat-penugasan">
|
||||
<div className="grid grid-cols-12 gap-5">
|
||||
<div className="lg:col-span-12 col-span-12">
|
||||
<Card>
|
||||
<div className="flex justify-between items-center py-4 px-5">
|
||||
<div>
|
||||
<InputGroup merged>
|
||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||
<Search className=" h-4 w-4 dark:text-white" />
|
||||
</InputGroupText>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search Judul..."
|
||||
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-5 pb-3">
|
||||
<Label>Audio Visual</Label>
|
||||
<div className="px-5 my-5">
|
||||
<VideoSliderPage />
|
||||
</div>
|
||||
<Label>Audio</Label>
|
||||
<div className="px-5 my-5">
|
||||
<AudioSliderPage />
|
||||
</div>
|
||||
<Label>Foto</Label>
|
||||
<div className="px-5 my-5">
|
||||
<ImageSliderPage />
|
||||
</div>
|
||||
<Label>Teks</Label>
|
||||
<div className="px-5 my-5">
|
||||
<TeksSliderPage />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="contest">
|
||||
<Card>
|
||||
<div className="py-3">
|
||||
<ContestTable />
|
||||
</div>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DocumentAllPage;
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
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 { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "@/components/ui/carousel";
|
||||
import { Link } from "@/components/navigation";
|
||||
import { formatDateToIndonesian, generateLocalizedPath } from "@/utils/globals";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { locale } from "dayjs";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getListContent } from "@/service/landing/landing";
|
||||
import ContestTable from "../../../../contest/components/contest-table";
|
||||
import AudioSliderPage from "../../audio/audio";
|
||||
import TeksSliderPage from "../../document/teks";
|
||||
import ImageSliderPage from "../../image/image";
|
||||
import VideoSliderPage from "../../video/audio-visual";
|
||||
|
||||
const ImageAllPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="my-3">
|
||||
<Tabs defaultValue="giat-routine" className="w-full">
|
||||
<Card className="py-3 px-2 my-4 h-20 flex items-center">
|
||||
<p className="text-lg font-semibold ml-2">Konten Image</p>
|
||||
</Card>
|
||||
<TabsContent value="giat-routine">
|
||||
<div className="grid grid-cols-12 gap-5">
|
||||
<div className="lg:col-span-12 col-span-12">
|
||||
<Card>
|
||||
<div className="flex justify-between items-center py-4 px-5">
|
||||
<div>
|
||||
<InputGroup merged>
|
||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||
<Search className=" h-4 w-4 dark:text-white" />
|
||||
</InputGroupText>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search Judul..."
|
||||
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-5 pb-3">
|
||||
<div className="flex justify-between items-center mx-3">
|
||||
<Label className="text-base">Image</Label>
|
||||
</div>
|
||||
<div className="px-5 my-5">
|
||||
<ImageSliderPage />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImageAllPage;
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
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 { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "@/components/ui/carousel";
|
||||
import { Link } from "@/components/navigation";
|
||||
import { formatDateToIndonesian, generateLocalizedPath } from "@/utils/globals";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { locale } from "dayjs";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getListContent } from "@/service/landing/landing";
|
||||
import ContestTable from "../../../../contest/components/contest-table";
|
||||
import AudioSliderPage from "../../audio/audio";
|
||||
import TeksSliderPage from "../../document/teks";
|
||||
import ImageSliderPage from "../../image/image";
|
||||
import VideoSliderPage from "../audio-visual";
|
||||
|
||||
const VideoAllPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="my-3">
|
||||
<Tabs defaultValue="giat-routine" className="w-full">
|
||||
<Card className="py-3 px-2 my-4 h-20 flex items-center">
|
||||
<p className="text-lg font-semibold ml-2">Konten Video</p>
|
||||
</Card>
|
||||
<TabsContent value="giat-routine">
|
||||
<div className="grid grid-cols-12 gap-5">
|
||||
<div className="lg:col-span-12 col-span-12">
|
||||
<Card>
|
||||
<div className="flex justify-between items-center py-4 px-5">
|
||||
<div>
|
||||
<InputGroup merged>
|
||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||
<Search className=" h-4 w-4 dark:text-white" />
|
||||
</InputGroupText>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search Judul..."
|
||||
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-5 pb-3">
|
||||
<div className="flex justify-between items-center mx-3">
|
||||
<Label className="text-base">Audio Visual</Label>
|
||||
</div>
|
||||
<div className="px-5 my-5">
|
||||
<VideoSliderPage />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="giat-penugasan">
|
||||
<div className="grid grid-cols-12 gap-5">
|
||||
<div className="lg:col-span-12 col-span-12">
|
||||
<Card>
|
||||
<div className="flex justify-between items-center py-4 px-5">
|
||||
<div>
|
||||
<InputGroup merged>
|
||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||
<Search className=" h-4 w-4 dark:text-white" />
|
||||
</InputGroupText>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search Judul..."
|
||||
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-5 pb-3">
|
||||
<Label>Audio Visual</Label>
|
||||
<div className="px-5 my-5">
|
||||
<VideoSliderPage />
|
||||
</div>
|
||||
<Label>Audio</Label>
|
||||
<div className="px-5 my-5">
|
||||
<AudioSliderPage />
|
||||
</div>
|
||||
<Label>Foto</Label>
|
||||
<div className="px-5 my-5">
|
||||
<ImageSliderPage />
|
||||
</div>
|
||||
<Label>Teks</Label>
|
||||
<div className="px-5 my-5">
|
||||
<TeksSliderPage />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="contest">
|
||||
<Card>
|
||||
<div className="py-3">
|
||||
<ContestTable />
|
||||
</div>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VideoAllPage;
|
||||
|
|
@ -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,16 +26,16 @@ 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, 1, "2");
|
||||
const response = await listCuratedContent(search, limit, page - 1, 2, "1");
|
||||
console.log(response);
|
||||
|
||||
const data = response?.data?.data;
|
||||
|
|
@ -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 { 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";
|
||||
|
|
@ -73,19 +73,80 @@ const CuratedContentPage = () => {
|
|||
</div>
|
||||
</div>
|
||||
<div className="ml-5 pb-3">
|
||||
<Label>Audio Visual</Label>
|
||||
<div className="flex justify-between items-center mx-3">
|
||||
<Label className="text-base">Audio Visual</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>
|
||||
<Label>Audio</Label>
|
||||
<div className="flex justify-between items-center mx-3">
|
||||
<Label className="text-base">Audio</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>
|
||||
<Label>Foto</Label>
|
||||
<div className="flex justify-between items-center mx-3">
|
||||
<Label className="text-base">Foto</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>
|
||||
<Label>Teks</Label>
|
||||
<div className="flex justify-between items-center mx-3">
|
||||
<Label className="text-base">Teks</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 />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,24 +1,42 @@
|
|||
"use client";
|
||||
|
||||
import { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import NewContent from "@/components/landing-page/new-content";
|
||||
import { Link, useRouter } from "@/i18n/routing";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { BarWave } from "react-cssfx-loading";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { checkWishlistStatus, deleteWishlist, getDetail, saveWishlist } from "@/service/landing/landing";
|
||||
import { checkWishlistStatus, createPublicSuggestion, deletePublicSuggestion, deleteWishlist, getDetail, getPublicSuggestionList, saveWishlist } from "@/service/landing/landing";
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
import { close, error, loading, successCallback } from "@/config/swal";
|
||||
import { close, error, loading, successCallback, warning } from "@/config/swal";
|
||||
import { sendMediaUploadToEmail } from "@/service/media-tracking/media-tracking";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import Swal from "sweetalert2";
|
||||
import { checkMaliciousText, getPublicLocaleTimestamp } from "@/utils/globals";
|
||||
import parse from "html-react-parser";
|
||||
import $ from "jquery";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const formWaveSurferOptions = (ref: any) => ({
|
||||
container: ref,
|
||||
waveColor: "#eee",
|
||||
progressColor: "OrangeRed",
|
||||
cursorColor: "OrangeRed",
|
||||
barWidth: 3,
|
||||
barRadius: 3,
|
||||
responsive: true,
|
||||
height: 150, // If true, normalize by the maximum peak instead of 1.0.
|
||||
normalize: true, // Use the PeakCache to improve rendering speed of large waveforms.
|
||||
partialRender: true,
|
||||
});
|
||||
|
||||
const DetailAudio = () => {
|
||||
const [selectedSize, setSelectedSize] = useState<string>("L");
|
||||
const [selectedTab, setSelectedTab] = useState("video");
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const params = useParams();
|
||||
|
|
@ -31,8 +49,6 @@ const DetailAudio = () => {
|
|||
const [downloadProgress, setDownloadProgress] = useState(0);
|
||||
const [isFromSPIT, setIsFromSPIT] = useState(false);
|
||||
const [main, setMain] = useState<any>();
|
||||
const [resolutionSelected, setResolutionSelected] = useState("720");
|
||||
const [imageSizeSelected, setImageSizeSelected] = useState("l");
|
||||
const userId = getCookiesDecrypt("uie");
|
||||
const [emailShareList, setEmailShareList] = useState<any>();
|
||||
const [emailShareInput, setEmailShareInput] = useState<any>();
|
||||
|
|
@ -42,8 +58,18 @@ const DetailAudio = () => {
|
|||
const [width, setWidth] = useState<any>();
|
||||
const [content, setContent] = useState<any>([]);
|
||||
const userRoleId = getCookiesDecrypt("urie");
|
||||
const [playing, setPlaying] = useState(false);
|
||||
const wavesurfer = useRef<any>(null);
|
||||
const waveformRef = useRef(null);
|
||||
const [audioLoaded, setAudioLoaded] = useState(false);
|
||||
const [volume, setVolume] = useState<any>(0.5);
|
||||
const [message, setMessage] = useState("");
|
||||
const [listSuggestion, setListSuggestion] = useState<any>();
|
||||
const MySwal = withReactContent(Swal);
|
||||
const [visibleInput, setVisibleInput] = useState<string | null>(null);
|
||||
const t = useTranslations("LandingPage");
|
||||
|
||||
let typeString = "video";
|
||||
let typeString = "audio";
|
||||
|
||||
useEffect(() => {
|
||||
initFetch();
|
||||
|
|
@ -53,9 +79,12 @@ const DetailAudio = () => {
|
|||
const initFetch = async () => {
|
||||
const response = await getDetail(String(slug));
|
||||
console.log("detailAudio", response);
|
||||
const responseGet = await getPublicSuggestionList(slug?.split("-")?.[0]);
|
||||
|
||||
setIsFromSPIT(response?.data?.data?.isFromSPIT);
|
||||
setWidth(window.innerWidth);
|
||||
setContent(response?.data.data);
|
||||
setContent(response?.data?.data);
|
||||
setListSuggestion(responseGet.data?.data);
|
||||
setMain({
|
||||
id: response?.data?.data?.files[0]?.id,
|
||||
type: response?.data?.data?.fileType.name,
|
||||
|
|
@ -224,11 +253,8 @@ const DetailAudio = () => {
|
|||
};
|
||||
|
||||
const sizes = [
|
||||
{ label: "XL", value: "3198 x 1798 px" },
|
||||
{ label: "L", value: "2399 x 1349 px" },
|
||||
{ label: "M", value: "1599 x 899 px" },
|
||||
{ label: "S", value: "1066 x 599 px" },
|
||||
{ label: "XS", value: "800 x 450 px" },
|
||||
{ label: "MP3", value: "... MB" },
|
||||
{ label: "WAV", value: "... MB" },
|
||||
];
|
||||
|
||||
const handleShare = (type: any, url: any) => {
|
||||
|
|
@ -268,6 +294,11 @@ const DetailAudio = () => {
|
|||
}
|
||||
}
|
||||
|
||||
const handlePlayPause = () => {
|
||||
setPlaying(!playing);
|
||||
wavesurfer?.current?.playPause();
|
||||
};
|
||||
|
||||
const handleEmailList = (e: any) => {
|
||||
const arrayEmail: any = [];
|
||||
for (let i = 0; i < emailShareList?.length; i += 1) {
|
||||
|
|
@ -284,6 +315,240 @@ const DetailAudio = () => {
|
|||
return false;
|
||||
};
|
||||
|
||||
// useEffect(() => {
|
||||
// function initState() {
|
||||
// if (typeString == "audio" && main?.url != undefined) {
|
||||
// const init = async () => {
|
||||
// const { default: WaveSurfer } = await import("wavesurfer.js");
|
||||
|
||||
// setPlaying(false);
|
||||
|
||||
// const formatTime = function (time: any) {
|
||||
// return [
|
||||
// Math.floor((time % 3600) / 60),
|
||||
// // minutes
|
||||
// `00${Math.floor(time % 60)}`.slice(-2), // seconds
|
||||
// ].join(":");
|
||||
// };
|
||||
// console.log("AUDIO", main?.url);
|
||||
|
||||
// const options = formWaveSurferOptions(waveformRef.current);
|
||||
|
||||
// wavesurfer.current = WaveSurfer.create(options);
|
||||
|
||||
// wavesurfer.current.load(main?.url);
|
||||
|
||||
// wavesurfer.current.on("ready", () => {
|
||||
// // https://wavesurfer-js.org/docs/methods.html
|
||||
// // wavesurfer.current.playPause();
|
||||
// // setPlaying(true);
|
||||
// setAudioLoaded(true);
|
||||
// // make sure object stillavailable when file loaded
|
||||
// if (wavesurfer.current) {
|
||||
// wavesurfer.current.setVolume(volume);
|
||||
// let volumeNow = volume;
|
||||
// setVolume(volumeNow);
|
||||
// }
|
||||
|
||||
// $(".waveform__duration").text(formatTime(wavesurfer.current.getDuration()));
|
||||
// });
|
||||
|
||||
// // Show current time
|
||||
// wavesurfer.current.on("audioprocess", () => {
|
||||
// $(".waveform__counter").text(formatTime(wavesurfer.current.getCurrentTime()));
|
||||
// });
|
||||
|
||||
// wavesurfer.current.on("finish", () => {
|
||||
// setPlaying(false);
|
||||
// });
|
||||
// };
|
||||
|
||||
// init();
|
||||
// // Removes events, elements and disconnects Web Audio nodes.
|
||||
// // when component unmount
|
||||
|
||||
// return () => wavesurfer?.current?.destroy();
|
||||
// }
|
||||
// }
|
||||
|
||||
// initState();
|
||||
// }, [main?.url]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeString === "audio" && main?.url) {
|
||||
const init = async () => {
|
||||
const { default: WaveSurfer } = await import("wavesurfer.js");
|
||||
|
||||
if (wavesurfer.current) {
|
||||
wavesurfer.current.destroy(); // 🔥 Hapus instance lama sebelum membuat yang baru
|
||||
}
|
||||
|
||||
setPlaying(false);
|
||||
|
||||
const formatTime = function (time: any) {
|
||||
return [
|
||||
Math.floor((time % 3600) / 60),
|
||||
// minutes
|
||||
`00${Math.floor(time % 60)}`.slice(-2), // seconds
|
||||
].join(":");
|
||||
};
|
||||
|
||||
const options = formWaveSurferOptions(waveformRef.current);
|
||||
wavesurfer.current = WaveSurfer.create(options);
|
||||
wavesurfer.current.load(main?.url);
|
||||
|
||||
wavesurfer.current.on("ready", () => {
|
||||
setAudioLoaded(true);
|
||||
wavesurfer.current.setVolume(volume);
|
||||
$(".waveform__duration").text(formatTime(wavesurfer.current.getDuration()));
|
||||
});
|
||||
|
||||
wavesurfer.current.on("audioprocess", () => {
|
||||
$(".waveform__counter").text(formatTime(wavesurfer.current.getCurrentTime()));
|
||||
});
|
||||
|
||||
wavesurfer.current.on("finish", () => {
|
||||
setPlaying(false);
|
||||
});
|
||||
};
|
||||
|
||||
init();
|
||||
|
||||
return () => {
|
||||
if (wavesurfer.current) {
|
||||
wavesurfer.current.destroy(); // 🔥 Hapus saat unmount
|
||||
}
|
||||
};
|
||||
}
|
||||
}, [main?.url]);
|
||||
|
||||
const onVolumeChange = (e: any) => {
|
||||
const { target } = e;
|
||||
const newVolume = +target?.value;
|
||||
|
||||
if (newVolume) {
|
||||
setVolume(newVolume);
|
||||
wavesurfer?.current?.setVolume(newVolume || 1);
|
||||
}
|
||||
};
|
||||
|
||||
async function sendSuggestionChild(parentId: any) {
|
||||
const inputElement = document.querySelector(`#input-comment-${parentId}`) as HTMLInputElement;
|
||||
|
||||
if (inputElement && inputElement.value.length > 3) {
|
||||
loading();
|
||||
const data = {
|
||||
mediaUploadId: slug?.split("-")?.[0],
|
||||
message: inputElement.value,
|
||||
parentId,
|
||||
};
|
||||
|
||||
console.log(data);
|
||||
const response = await createPublicSuggestion(data);
|
||||
console.log(response);
|
||||
const responseGet: any = await getPublicSuggestionList(slug?.split("-")?.[0]);
|
||||
console.log(responseGet.data?.data);
|
||||
setListSuggestion(responseGet.data?.data);
|
||||
|
||||
// Reset input field
|
||||
inputElement.value = "";
|
||||
|
||||
// document.querySelector("#comment-id-" + parentId)?.style.display = "none";
|
||||
|
||||
close();
|
||||
}
|
||||
}
|
||||
async function deleteDataSuggestion(dataId: any) {
|
||||
loading();
|
||||
const response = await deletePublicSuggestion(dataId);
|
||||
console.log(response);
|
||||
const responseGet = await getPublicSuggestionList(slug.split("-")?.[0]);
|
||||
console.log(responseGet.data?.data);
|
||||
setListSuggestion(responseGet.data?.data);
|
||||
close();
|
||||
}
|
||||
const deleteData = (dataId: any) => {
|
||||
MySwal.fire({
|
||||
title: "Delete Comment",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Delete",
|
||||
}).then((result: any) => {
|
||||
if (result.isConfirmed) {
|
||||
deleteDataSuggestion(dataId);
|
||||
console.log(dataId);
|
||||
}
|
||||
});
|
||||
};
|
||||
const showInput = (e: any) => {
|
||||
console.log(document.querySelector(`#${e}`)?.classList);
|
||||
document.querySelector(`#${e}`)?.classList.toggle("none");
|
||||
setVisibleInput(visibleInput === e ? null : e);
|
||||
};
|
||||
|
||||
function addDefaultProfile(ev: any) {
|
||||
ev.target.src = "/assets/avatar-profile.png";
|
||||
}
|
||||
|
||||
async function sendSuggestionParent() {
|
||||
if (message?.length > 3) {
|
||||
loading();
|
||||
const data = {
|
||||
mediaUploadId: slug?.split("-")?.[0],
|
||||
message,
|
||||
parentId: null,
|
||||
};
|
||||
|
||||
const response = await createPublicSuggestion(data);
|
||||
|
||||
console.log(response);
|
||||
setMessage("");
|
||||
|
||||
const responseGet = await getPublicSuggestionList(slug?.split("-")?.[0]);
|
||||
console.log(responseGet?.data?.data);
|
||||
setListSuggestion(responseGet?.data?.data);
|
||||
|
||||
// Hapus nilai semua input secara manual jika perlu
|
||||
const inputs = document.querySelectorAll("input");
|
||||
inputs.forEach((input) => {
|
||||
input.value = "";
|
||||
});
|
||||
|
||||
close();
|
||||
}
|
||||
}
|
||||
const getInputValue = (e: any) => {
|
||||
const message = e.target.value;
|
||||
console.log(message);
|
||||
setMessage(message);
|
||||
};
|
||||
const postData = () => {
|
||||
const checkMessage = checkMaliciousText(message);
|
||||
if (checkMessage == "") {
|
||||
if (Number(userRoleId) < 1 || userRoleId == undefined) {
|
||||
router.push("/auth");
|
||||
} else {
|
||||
sendSuggestionParent();
|
||||
}
|
||||
} else {
|
||||
warning(checkMessage);
|
||||
}
|
||||
};
|
||||
const postDataChild = (id: any) => {
|
||||
const checkMessage = checkMaliciousText(message);
|
||||
if (checkMessage == "") {
|
||||
if (Number(userRoleId) < 1 || userRoleId == undefined) {
|
||||
router.push("/auth");
|
||||
} else {
|
||||
sendSuggestionChild(id);
|
||||
}
|
||||
} else {
|
||||
warning(checkMessage);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="min-h-screen px-4 md:px-24 py-4">
|
||||
|
|
@ -291,25 +556,62 @@ const DetailAudio = () => {
|
|||
<div className="rounded-md overflow-hidden md:flex">
|
||||
{/* Bagian Kiri */}
|
||||
<div className="md:w-3/4">
|
||||
<div className="relative flex justify-center">
|
||||
<img src="/assets/audio-btn.png" />
|
||||
<BarWave color="#ffc831" width="60px" height="25px" duration="2s" />
|
||||
<div className="absolute top-4 left-4"></div>
|
||||
<div className="relative flex flex-row">
|
||||
<button onClick={handlePlayPause}>
|
||||
<img src={`/assets/${playing ? "stop-icon.png" : "play-icon.png"}`} />
|
||||
</button>
|
||||
<div
|
||||
style={
|
||||
audioLoaded
|
||||
? {
|
||||
display: "none",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>
|
||||
<BarWave color="#ffc831" width="60px" height="25px" duration="2s" className="flex ml-5 mt-[50px] lg:ml-[200px]" />
|
||||
</div>
|
||||
<div id="waveform" ref={waveformRef} className="w-[350px] lg:w-[700px] ml-6 lg:ml-12"></div>
|
||||
</div>
|
||||
<div
|
||||
className="flex flex-row mt-2"
|
||||
style={
|
||||
audioLoaded
|
||||
? {}
|
||||
: {
|
||||
display: "none !important",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Icon icon="fa:volume-up" />
|
||||
{/* <Slider onChange={onVolumeChange} defaultValue={volume} /> */}
|
||||
<input type="range" id="volume" name="volume" min="0.01" max="1" step=".025" onChange={onVolumeChange} defaultValue={volume} />
|
||||
</div>
|
||||
|
||||
{/* Footer Informasi */}
|
||||
<div className="p-4 text-sm text-gray-500 flex justify-between items-center border-t mt-4">
|
||||
<p className="flex flex-row items-center">
|
||||
oleh <span className="font-semibold text-black">{detailDataAudio?.uploadedBy?.userLevel?.name}</span> | Diupdate pada {detailDataAudio?.updatedAt} WIB |
|
||||
<Icon icon="formkit:eye" width="15" height="15" />
|
||||
|
||||
{detailDataAudio?.clickCount}
|
||||
</p>
|
||||
<p>Kreator: {detailDataAudio?.creatorName}</p>
|
||||
<div className="text-gray-500 flex flex-col lg:flex-row justify-between items-center border-t mt-4">
|
||||
<div className="flex flex-col lg:flex-row items-center mt-3 lg:justify-between">
|
||||
<p className="text-xs lg:text-sm">
|
||||
{t("by")} <span className="font-semibold text-black">{detailDataAudio?.uploadedBy?.userLevel?.name}</span>
|
||||
</p>
|
||||
<p className="text-xs lg:text-sm">
|
||||
| {t("updatedOn")} {detailDataAudio?.updatedAt} WIB |
|
||||
</p>
|
||||
<p className="text-xs lg:text-sm flex justify-center items-center">
|
||||
<Icon icon="formkit:eye" width="15" height="15" />
|
||||
{detailDataAudio?.clickCount}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<p className="flex text-end text-xs lg:text-sm font-semibold">
|
||||
{t("creator")} {detailDataAudio?.creatorName}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/* Keterangan */}
|
||||
<div className="md:w-3/4">
|
||||
<h1 className="flex flex-row font-bold text-2xl mx-5 my-8">{detailDataAudio?.title}</h1>
|
||||
<div className="font-light text-justify" dangerouslySetInnerHTML={{ __html: detailDataAudio?.htmlDescription }} />
|
||||
<div className="w-full">
|
||||
<h1 className="flex flex-row font-bold text-lg lg:text-2xl my-8">{detailDataAudio?.title}</h1>
|
||||
<div className="font-light text-justify mb-5 lg:mb-0" dangerouslySetInnerHTML={{ __html: detailDataAudio?.htmlDescription }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -318,12 +620,12 @@ const DetailAudio = () => {
|
|||
{isSaved ? (
|
||||
<a onClick={() => handleDeleteWishlist()} className="flex flex-col mb-3 items-center justify-center cursor-pointer">
|
||||
<Icon icon="material-symbols:bookmark" width={40} />
|
||||
<p className="text-base lg:text-lg">Hapus</p>
|
||||
<p className="text-base lg:text-lg">{t("delete")}</p>
|
||||
</a>
|
||||
) : (
|
||||
<a onClick={() => doBookmark()} className="flex flex-col mb-3 items-center justify-center cursor-pointer">
|
||||
<Icon icon="material-symbols:bookmark-outline" width={40} />
|
||||
<p className="text-base lg:text-lg">Simpan</p>
|
||||
<p className="text-base lg:text-lg">{t("save")}</p>
|
||||
</a>
|
||||
)}
|
||||
|
||||
|
|
@ -345,7 +647,7 @@ const DetailAudio = () => {
|
|||
<div className="border-t border-black my-4"></div>
|
||||
|
||||
{/* Opsi Ukuran Foto */}
|
||||
<h4 className="flex text-lg justify-center items-center font-semibold my-3">Opsi Ukuran Audio</h4>
|
||||
<h4 className="flex text-lg justify-center items-center font-semibold my-3">{t("audioSize")}</h4>
|
||||
|
||||
<div className="border-t border-black my-4"></div>
|
||||
|
||||
|
|
@ -367,7 +669,7 @@ const DetailAudio = () => {
|
|||
<div className="mt-4">
|
||||
<label className="flex items-center space-x-2 text-sm">
|
||||
<input type="checkbox" className="text-red-600 focus:ring-red-600" onChange={() => setIsDownloadAll(!isDownloadAll)} />
|
||||
<span>Download Semua File?</span>
|
||||
<span>{t("downloadAll")}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
|
@ -376,12 +678,12 @@ const DetailAudio = () => {
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||
<path fill="white" d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z" />
|
||||
</svg>
|
||||
Download
|
||||
{t("download")}
|
||||
</button>
|
||||
|
||||
{/* Tombol Bagikan */}
|
||||
<div className="flex flex-row py-3">
|
||||
<p className="text-base font-semibold">Bagikan</p>
|
||||
<p className="text-base font-semibold">{t("share")}</p>
|
||||
<a className="ml-8 cursor-pointer" onClick={() => handleShare("fb", `https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}"e=${content?.title}`)}>
|
||||
<Icon icon="brandico:facebook" height="20" className="px-auto text-red-600 text-center" />
|
||||
</a>
|
||||
|
|
@ -399,13 +701,13 @@ const DetailAudio = () => {
|
|||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<div className="flex flex-col">
|
||||
<h1 className="mb-2">Share Ke Email</h1>
|
||||
<h1 className="mb-2">{t("shareTo")}</h1>
|
||||
<div className="flex flex-col mb-2">
|
||||
<p className="text-base font-semibold mb-1">Email Tujuan :</p>
|
||||
<Input value={emailShareInput} onChange={(event) => setEmailShareInput(event.target.value)} onKeyPress={handleEmailList} type="email" placeholder="Tekan Enter untuk input Email" />
|
||||
<p className="text-base font-semibold mb-1">{t("destinationEmail")}</p>
|
||||
<Input value={emailShareInput} onChange={(event) => setEmailShareInput(event.target.value)} onKeyPress={handleEmailList} type="email" placeholder={t("pressEnter")} />
|
||||
</div>
|
||||
<Button className="bg-blue-500 text-white p-2 w-fit rounded-lg" onClick={() => shareToEmail()}>
|
||||
Kirim
|
||||
{t("send")}
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
|
|
@ -417,10 +719,183 @@ const DetailAudio = () => {
|
|||
|
||||
<div className="w-full mb-8">
|
||||
{/* Comment */}
|
||||
<div className="flex flex-col my-16 gap-5 p-10 bg-gray-300">
|
||||
<p className="flex items-start text-lg">Berikan Komentar</p>
|
||||
<Textarea placeholder="Type your comments here." className="flex items-start justify-center" />
|
||||
<button className="flex items-start bg-[#bb3523] rounded-lg w-fit px-4 py-1">Kirim</button>
|
||||
<div className="flex flex-col my-16 p-4 lg:p-10 bg-[#f7f7f7]">
|
||||
<div className="gap-5 flex flex-col px-4 lg:px-14">
|
||||
<p className="flex items-start text-lg">{t("comment")}</p>
|
||||
<Textarea placeholder="Type your comments here." className="flex w-full pb-12" onChange={getInputValue} />
|
||||
<button onClick={() => postData()} className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-4 py-1">
|
||||
{t("send")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="border-b-2 border-slate-300 mt-4 w-auto"></div>
|
||||
|
||||
<div>
|
||||
{listSuggestion?.map((data: any) => (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row mt-2 px-4 lg:px-14">
|
||||
<img src={data?.suggestionFrom?.profilePictureUrl} className="h-12 lg:h-16 w-12 lg:w-16 mr-2" onError={addDefaultProfile} alt="" />
|
||||
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
|
||||
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
|
||||
{Number(data.suggestionFrom?.roleId) == 2 || Number(data.suggestionFrom?.roleId) == 3 || Number(data.suggestionFrom?.roleId) == 4 ? "HUMAS POLRI" : data.suggestionFrom?.fullname}
|
||||
{getPublicLocaleTimestamp(new Date(data.createdAt))}
|
||||
</p>
|
||||
<p className="text-slate-500 text-[13px] lg:text-sm mb-4">{data?.message}</p>
|
||||
<div>
|
||||
<a
|
||||
style={
|
||||
Number(data.suggestionFrom?.id) == Number(userId)
|
||||
? {
|
||||
display: "none",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
onClick={() => showInput(`comment-id-${data.id}`)}
|
||||
className="mr-2"
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("reply")}</small>
|
||||
</a>
|
||||
{Number(data.suggestionFrom?.id) == Number(userId) || Number(userRoleId) == 2 ? (
|
||||
<a onClick={() => deleteData(data.id)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("delete")}</small>
|
||||
</a>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{visibleInput === `comment-id-${data.id}` && (
|
||||
<div id={`comment-id-${data.id}`} className="px-4 pl-[72px] lg:px-14 lg:pl-32 mt-2 ">
|
||||
<Textarea id={`input-comment-${data.id}`} className="p-4 focus:outline-none focus:border-sky-500" placeholder={t("enterReply")} />
|
||||
<div className="flex flex-row gap-3">
|
||||
<a onClick={() => postDataChild(data.id)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 mt-2 cursor-pointer">{t("send")}</small>
|
||||
</a>
|
||||
<a onClick={() => showInput(`comment-id-${data.id}`)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg mt-2 w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("cancel")}</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data.children.length > 0
|
||||
? data.children?.map((child1: any) => (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-16 lg:pl-32">
|
||||
<img src={child1.suggestionFrom?.profilePictureUrl} onError={addDefaultProfile} alt="" className="h-10 lg:h-16 w-10 lg:w-16 mr-2" />
|
||||
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
|
||||
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
|
||||
{" "}
|
||||
<b>{Number(child1.suggestionFrom?.roleId) == 2 || Number(child1.suggestionFrom?.roleId) == 3 || Number(child1.suggestionFrom?.roleId) == 4 ? "HUMAS POLRI" : child1.suggestionFrom?.fullname}</b>{" "}
|
||||
{getPublicLocaleTimestamp(new Date(child1.createdAt))}
|
||||
</p>
|
||||
<p className="text-slate-500 text-[13px] lg:text-sm mb-4">{parse(String(child1?.message))}</p>
|
||||
<div>
|
||||
<a
|
||||
style={
|
||||
Number(child1.suggestionFrom?.id) == Number(userId)
|
||||
? {
|
||||
display: "none",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
onClick={() => showInput(`comment-id-${child1.id}`)}
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("reply")}</small>
|
||||
</a>
|
||||
<a
|
||||
style={
|
||||
Number(child1.suggestionFrom?.id) == Number(userId)
|
||||
? {}
|
||||
: {
|
||||
display: "none",
|
||||
}
|
||||
}
|
||||
onClick={() => deleteData(child1.id)}
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("delete")}</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{visibleInput === `comment-id-${child1.id}` && (
|
||||
<div id={`comment-id-${child1.id}`} className="px-4 lg:px-14 pl-28 lg:pl-[200px]">
|
||||
<Textarea name="" className="mt-2 " id={`input-comment-${child1.id}`} placeholder={t("enterReply")} />
|
||||
<div className="flex flex-row mt-2 gap-3">
|
||||
<a onClick={() => postDataChild(child1.id)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("send")}</small>
|
||||
</a>
|
||||
<a onClick={() => showInput(`comment-id-${child1.id}`)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("cancel")}</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{child1.children.length > 0
|
||||
? child1.children?.map((child2: any) => (
|
||||
<div className="">
|
||||
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-28 lg:pl-48">
|
||||
<img src={child2.suggestionFrom?.profilePictureUrl} className="h-9 lg:h-16 w-9 lg:w-16 mr-2" onError={addDefaultProfile} alt="" />
|
||||
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
|
||||
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
|
||||
{" "}
|
||||
<b>{Number(child2.suggestionFrom?.roleId) == 2 || Number(child2.suggestionFrom?.roleId) == 3 || Number(child2.suggestionFrom?.roleId) == 4 ? "HUMAS POLRI" : child2.suggestionFrom?.fullname}</b>{" "}
|
||||
{getPublicLocaleTimestamp(new Date(child2.createdAt))}
|
||||
</p>
|
||||
<p className="text-slate-500 text-sm mb-4">{parse(String(child2?.message))}</p>
|
||||
<div>
|
||||
<a
|
||||
style={
|
||||
Number(child2.suggestionFrom?.id) == Number(userId)
|
||||
? {
|
||||
display: "none",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
onClick={() => showInput(`comment-id-${child2.id}`)}
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("reply")}</small>
|
||||
</a>
|
||||
<a
|
||||
style={
|
||||
Number(child2.suggestionFrom?.id) == Number(userId)
|
||||
? {}
|
||||
: {
|
||||
display: "none",
|
||||
}
|
||||
}
|
||||
onClick={() => deleteData(child2.id)}
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("delete")}</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{visibleInput === `comment-id-${child2.id}` && (
|
||||
<div id={`comment-id-${child2.id}`} className="px-4 lg:px-14 pl-40 lg:pl-[265px]">
|
||||
<Textarea name="" id={`input-comment-${child2.id}`} className="my-2" placeholder="Masukkan balasan anda" />
|
||||
<div className="flex flex-row gap-3">
|
||||
<a onClick={() => postDataChild(child2.id)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("send")}</small>
|
||||
</a>
|
||||
<a onClick={() => showInput(`comment-id-${child2.id}`)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("cancel")}</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
: ""}
|
||||
</div>
|
||||
))
|
||||
: ""}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Konten Serupa */}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,19 @@
|
|||
"use client";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { formatDateToIndonesian, getOnlyDate, getOnlyMonthAndYear } from "@/utils/globals";
|
||||
import { formatDateToIndonesian, getOnlyDate, getOnlyMonthAndYear, secondToTimes } from "@/utils/globals";
|
||||
import { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||
import { getUserLevelListByParent, listCategory, listData, listDataRegional } from "@/service/landing/landing";
|
||||
import { ColumnDef, ColumnFiltersState, PaginationState, SortingState, VisibilityState, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
|
||||
import LandingPagination from "@/components/landing-page/pagination";
|
||||
import { Reveal } from "@/components/landing-page/Reveal";
|
||||
import { Link, useRouter } from "@/i18n/routing";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import ReactDatePicker from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import { close, loading } from "@/config/swal";
|
||||
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
|
|
@ -70,9 +68,18 @@ const FilterPage = () => {
|
|||
const [categories, setCategories] = useState([]);
|
||||
const [userLevels, setUserLevels] = useState([]);
|
||||
const t = useTranslations("FilterPage");
|
||||
const [isLoading, setIsLoading] = useState<any>(true);
|
||||
|
||||
// const [startDate, endDate] = dateRange;
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 3000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
const pageFromUrl = searchParams?.get("page");
|
||||
if (pageFromUrl) {
|
||||
|
|
@ -155,7 +162,8 @@ const FilterPage = () => {
|
|||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") : "",
|
||||
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : ""
|
||||
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : "",
|
||||
locale == "en" ? true : false
|
||||
);
|
||||
close();
|
||||
// setGetTotalPage(response?.data?.data?.totalPages);
|
||||
|
|
@ -187,7 +195,8 @@ const FilterPage = () => {
|
|||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") : "",
|
||||
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : ""
|
||||
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : "",
|
||||
locale == "en" ? true : false
|
||||
);
|
||||
close();
|
||||
// setGetTotalPage(response?.data?.data?.totalPages);
|
||||
|
|
@ -243,7 +252,7 @@ const FilterPage = () => {
|
|||
const format = formatFilter == undefined ? "" : formatFilter?.join(",");
|
||||
loading();
|
||||
const response = await listDataRegional(
|
||||
"1",
|
||||
"4",
|
||||
name,
|
||||
filter,
|
||||
format,
|
||||
|
|
@ -425,7 +434,7 @@ const FilterPage = () => {
|
|||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">{t("categories")}</h3>
|
||||
<ul className="mt-2 space-y-2">
|
||||
{categories.map((category: any) => (
|
||||
{categories?.map((category: any) => (
|
||||
<li key={category?.id}>
|
||||
<label className="inline-flex items-center" htmlFor={`${category.id}`}>
|
||||
<Checkbox id={`${category.id}`} value={category.id} checked={categoryFilter.includes(String(category.id))} onCheckedChange={(e) => handleCategoryFilter(Boolean(e), category.id)} />
|
||||
|
|
@ -464,60 +473,86 @@ const FilterPage = () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Konten Kanan */}
|
||||
<Reveal>
|
||||
<div className="flex-1">
|
||||
<div className="flex flex-col items-end mb-4">
|
||||
<h2 className="text-lg font-semibold">{t("sortBy")} </h2>
|
||||
<select defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"} onChange={(e) => handleSorting(e)} className="border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500">
|
||||
<option value="latest">{t("latest")}</option>
|
||||
<option value="popular">{t("mostPopular")}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{audioData?.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{audioData?.map((image: any) => (
|
||||
<Carousel className="w-full max-w-7xl mx-auto">
|
||||
<CarouselContent>
|
||||
{audioData?.map((audio: any) => (
|
||||
<CarouselItem key={audio?.id} className="md:basis-1/2 lg:basis-1/3">
|
||||
<div className="flex flex-row gap-6">
|
||||
<Link href={`/audio/detail/${audio?.slug}`} className="flex flex-col sm:flex-row items-center 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-8 lg: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" /> {audio?.clickCount}{" "}
|
||||
</div>
|
||||
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">{audio?.title}</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="flex items-center justify-center text-black">
|
||||
<img src="/assets/empty-data.png" alt="empty" className="h-60 w-60 my-4" />
|
||||
</p>
|
||||
)}
|
||||
|
||||
<LandingPagination table={table} totalData={totalData} totalPage={totalPage} />
|
||||
{/* Right */}
|
||||
<div className="w-full lg:w-[75%]">
|
||||
<div className="flex flex-col items-end mb-4">
|
||||
<h2 className="text-lg font-semibold">{t("sortBy")} </h2>
|
||||
<select defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"} onChange={(e) => handleSorting(e)} className="border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500">
|
||||
<option value="latest">{t("latest")}</option>
|
||||
<option value="popular">{t("mostPopular")}</option>
|
||||
</select>
|
||||
</div>
|
||||
</Reveal>
|
||||
|
||||
{isLoading ? (
|
||||
<>
|
||||
<div className="flex flex-col space-y-3 w-full justify-center items-center gap-3">
|
||||
<Skeleton className="h-[100px] w-full rounded-xl" />
|
||||
<Skeleton className="h-[100px] w-full rounded-xl" />
|
||||
<Skeleton className="h-[100px] w-full rounded-xl" />
|
||||
<Skeleton className="h-[100px] w-full rounded-xl" />
|
||||
<Skeleton className="h-[100px] w-full rounded-xl" />
|
||||
<Skeleton className="h-[100px] w-full rounded-xl" />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{audioData?.length > 0 ? (
|
||||
<div className="flex flex-col gap-3">
|
||||
{audioData?.map((audio: any) => (
|
||||
<div key={audio?.id}>
|
||||
<Link href={`/audio/detail/${audio?.slug}`} className="flex flex-row items-center bg-white dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full">
|
||||
<div className="flex justify-center lg:justify-between w-full gap-2 lg:gap-6">
|
||||
<div className="flex flex-row gap-3">
|
||||
<div className="flex items-center justify-center bg-red-500 text-white rounded-lg w-16 h-full lg: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">
|
||||
<div className="flex flex-col lg:flex-row lg:gap-2">
|
||||
<div className="text-gray-500 dark:text-gray-400 flex flex-row text-sm items-center">
|
||||
{formatDateToIndonesian(new Date(audio?.createdAt))} {audio?.timezone ? audio?.timezone : "WIB"}
|
||||
</div>
|
||||
<div className="text-gray-500 dark:text-gray-400 flex flex-row text-sm items-center">
|
||||
|
|
||||
<Icon icon="formkit:eye" width="15" height="15" />
|
||||
{audio?.clickCount}{" "}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">{audio?.title}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:flex-row items-center place-content-end gap-3">
|
||||
<img src="/assets/wave.svg" alt="wave" className="h-5 lg:h-16" />
|
||||
<svg width="17" height="20" 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="black"
|
||||
/>
|
||||
</svg>
|
||||
<p className="text-xs lg:text-sm text-center"> {audio?.duration ? secondToTimes(Number(audio?.duration)) : "00:00:00"}</p>
|
||||
<Icon icon="ph:download-fill" className="text-red-500" fontSize={20} />
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="flex items-center justify-center text-black">
|
||||
<img src="/assets/empty-data.png" alt="empty" className="h-60 w-60 my-4" />
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<LandingPagination table={table} totalData={totalData} totalPage={totalPage} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -18,10 +18,11 @@ import { Input } from "@/components/ui/input";
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { sendMediaUploadToEmail } from "@/service/media-tracking/media-tracking";
|
||||
import ImageBlurry from "@/components/ui/image-blurry";
|
||||
import Image from "next/image";
|
||||
|
||||
const Galery = (props: any) => {
|
||||
const [profile, setProfile] = useState<any>();
|
||||
const [selectedTab, setSelectedTab] = useState("video");
|
||||
const [selectedTab, setSelectedTab] = useState("image");
|
||||
const router = useRouter();
|
||||
const MySwal = withReactContent(Swal);
|
||||
const searchParams = useSearchParams();
|
||||
|
|
@ -275,9 +276,9 @@ const Galery = (props: any) => {
|
|||
return (
|
||||
<>
|
||||
<HeaderManagement />
|
||||
<div className="flex flex-row">
|
||||
<div className="flex flex-col lg:flex-row">
|
||||
<SidebarManagement />
|
||||
<div className="w-2/3 p-12">
|
||||
<div className="w-full lg:w-2/3 p-8 lg:p-12">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold mb-3">Galeri Saya</h1>
|
||||
</div>
|
||||
|
|
@ -318,7 +319,7 @@ const Galery = (props: any) => {
|
|||
<div className="">
|
||||
{selectedTab == "video" ? (
|
||||
contentVideo?.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{contentVideo?.map((video: any) => (
|
||||
<Card key={video?.id}>
|
||||
<CardContent className="flex flex-col bg-black dark:bg-white w-full rounded-lg p-0">
|
||||
|
|
@ -336,11 +337,15 @@ const Galery = (props: any) => {
|
|||
<Icon className="text-white ml-1" fontSize={25} icon="tabler:dots" />
|
||||
</a>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-40">
|
||||
<PopoverContent className="w-52">
|
||||
<Link href={`/content-management/rewrite/create/${video?.mediaUpload?.id}`} className="flex flex-row hover:text-red-800 gap-2">
|
||||
<Icon icon="jam:write" fontSize={25} />
|
||||
<p className="text-base font-semibold mb-2">Content Rewrite</p>
|
||||
</Link>
|
||||
<div className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button className="w-full flex items-center gap-2">
|
||||
<button className="w-full flex items-center gap-3">
|
||||
<Icon icon="oi:share" fontSize={20} />
|
||||
<p className="text-base font-semibold mb-3">Bagikan</p>
|
||||
</button>
|
||||
|
|
@ -359,7 +364,7 @@ const Galery = (props: any) => {
|
|||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<a onClick={() => handleDelete(video?.id)} className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
|
||||
<a onClick={() => handleDelete(video?.id)} className="flex items-center gap-3 hover:text-red-800 w-full rounded-lg">
|
||||
<Icon icon="fa:trash" fontSize={20} />
|
||||
<p className="text-base font-semibold">Hapus</p>
|
||||
</a>
|
||||
|
|
@ -372,7 +377,7 @@ const Galery = (props: any) => {
|
|||
</div>
|
||||
) : (
|
||||
<p className="flex items-center justify-center">
|
||||
<img src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
<Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
</p>
|
||||
)
|
||||
) : selectedTab == "audio" ? (
|
||||
|
|
@ -414,11 +419,15 @@ const Galery = (props: any) => {
|
|||
<Icon className="text-white ml-1" fontSize={25} icon="tabler:dots" />
|
||||
</a>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-40">
|
||||
<PopoverContent className="w-52">
|
||||
<Link href={`/content-management/rewrite/create/${audio?.mediaUpload?.id}`} className="flex flex-row hover:text-red-800 gap-2">
|
||||
<Icon icon="jam:write" fontSize={25} />
|
||||
<p className="text-base font-semibold mb-2">Content Rewrite</p>
|
||||
</Link>
|
||||
<div className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button className="w-full flex items-center gap-2">
|
||||
<button className="w-full flex items-center gap-3">
|
||||
<Icon icon="oi:share" fontSize={20} />
|
||||
<p className="text-base font-semibold mb-3">Bagikan</p>
|
||||
</button>
|
||||
|
|
@ -437,7 +446,7 @@ const Galery = (props: any) => {
|
|||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<a onClick={() => handleDelete(audio?.id)} className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
|
||||
<a onClick={() => handleDelete(audio?.id)} className="flex items-center gap-3 hover:text-red-800 w-full rounded-lg">
|
||||
<Icon icon="fa:trash" fontSize={20} />
|
||||
<p className="text-base font-semibold">Hapus</p>
|
||||
</a>
|
||||
|
|
@ -448,7 +457,7 @@ const Galery = (props: any) => {
|
|||
</div>
|
||||
) : (
|
||||
<p className="flex items-center justify-center">
|
||||
<img src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
<Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
</p>
|
||||
)
|
||||
) : selectedTab == "image" ? (
|
||||
|
|
@ -470,11 +479,15 @@ const Galery = (props: any) => {
|
|||
<Icon className="text-white ml-1" fontSize={25} icon="tabler:dots" />
|
||||
</a>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-40">
|
||||
<PopoverContent className="w-52">
|
||||
<Link href={`/content-management/rewrite/create/${image?.mediaUpload?.id}`} className="flex flex-row hover:text-red-800 gap-2">
|
||||
<Icon icon="jam:write" fontSize={25} />
|
||||
<p className="text-base font-semibold mb-2">Content Rewrite</p>
|
||||
</Link>
|
||||
<div className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button className="w-full flex items-center gap-2">
|
||||
<button className="w-full flex items-center gap-3">
|
||||
<Icon icon="oi:share" fontSize={20} />
|
||||
<p className="text-base font-semibold mb-3">Bagikan</p>
|
||||
</button>
|
||||
|
|
@ -493,7 +506,7 @@ const Galery = (props: any) => {
|
|||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<a onClick={() => handleDelete(image?.id)} className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
|
||||
<a onClick={() => handleDelete(image?.id)} className="flex items-center gap-3 hover:text-red-800 w-full rounded-lg">
|
||||
<Icon icon="fa:trash" fontSize={20} />
|
||||
<p className="text-base font-semibold">Hapus</p>
|
||||
</a>
|
||||
|
|
@ -505,7 +518,7 @@ const Galery = (props: any) => {
|
|||
</div>
|
||||
) : (
|
||||
<p className="flex items-center justify-center">
|
||||
<img src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
<Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
</p>
|
||||
)
|
||||
) : contentDocument.length > 0 ? (
|
||||
|
|
@ -538,11 +551,15 @@ const Galery = (props: any) => {
|
|||
<Icon className="text-white ml-1" fontSize={25} icon="tabler:dots" />
|
||||
</a>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-40">
|
||||
<PopoverContent className="w-52">
|
||||
<Link href={`/content-management/rewrite/create/${document?.mediaUpload?.id}`} className="flex flex-row hover:text-red-800 gap-2">
|
||||
<Icon icon="jam:write" fontSize={25} />
|
||||
<p className="text-base font-semibold mb-2">Content Rewrite</p>
|
||||
</Link>
|
||||
<div className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button className="w-full flex items-center gap-2">
|
||||
<button className="w-full flex items-center gap-3">
|
||||
<Icon icon="oi:share" fontSize={20} />
|
||||
<p className="text-base font-semibold mb-3">Bagikan</p>
|
||||
</button>
|
||||
|
|
@ -561,7 +578,7 @@ const Galery = (props: any) => {
|
|||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<a onClick={() => handleDelete(document?.id)} className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
|
||||
<a onClick={() => handleDelete(document?.id)} className="flex items-center gap-3 hover:text-red-800 w-full rounded-lg">
|
||||
<Icon icon="fa:trash" fontSize={20} />
|
||||
<p className="text-base font-semibold">Hapus</p>
|
||||
</a>
|
||||
|
|
@ -572,7 +589,7 @@ const Galery = (props: any) => {
|
|||
</div>
|
||||
) : (
|
||||
<p className="flex items-center justify-center">
|
||||
<img src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
<Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -20,10 +20,11 @@ import { Input } from "@/components/ui/input";
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { sendMediaUploadToEmail } from "@/service/media-tracking/media-tracking";
|
||||
import ImageBlurry from "@/components/ui/image-blurry";
|
||||
import Image from "next/image";
|
||||
|
||||
const Galery = (props: any) => {
|
||||
const [profile, setProfile] = useState<any>();
|
||||
const [selectedTab, setSelectedTab] = useState("video");
|
||||
const [selectedTab, setSelectedTab] = useState("image");
|
||||
const router = useRouter();
|
||||
const MySwal = withReactContent(Swal);
|
||||
const searchParams = useSearchParams();
|
||||
|
|
@ -281,9 +282,9 @@ const Galery = (props: any) => {
|
|||
return (
|
||||
<>
|
||||
<HeaderManagement />
|
||||
<div className="flex flex-row">
|
||||
<div className="flex flex-col lg:flex-row">
|
||||
<SidebarManagement />
|
||||
<div className="w-2/3 p-12">
|
||||
<div className="w-full lg:w-2/3 p-8 lg:p-12">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold">Galeri {profile?.institute?.name}</h1>
|
||||
</div>
|
||||
|
|
@ -324,7 +325,7 @@ const Galery = (props: any) => {
|
|||
<div className="px-2">
|
||||
{selectedTab == "video" ? (
|
||||
contentVideo?.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{contentVideo?.map((video: any) => (
|
||||
<Card key={video?.id}>
|
||||
<CardContent className="flex flex-col bg-black dark:bg-white w-full rounded-lg p-0">
|
||||
|
|
@ -342,13 +343,21 @@ const Galery = (props: any) => {
|
|||
<Icon className="text-white ml-1" fontSize={25} icon="tabler:dots" />
|
||||
</a>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-40">
|
||||
<PopoverContent className="w-52">
|
||||
<div onClick={() => handleSaveWishlist(video?.mediaUpload?.id)} className="cursor-pointer flex flex-row gap-2 hover:text-red-800">
|
||||
<Icon icon="material-symbols:bookmark-outline" fontSize={25} />
|
||||
<p className="text-base font-semibold mb-2">Simpan</p>
|
||||
</div>
|
||||
<Link href={`/content-management/rewrite/create/${video?.mediaUpload?.id}`} className="flex flex-row hover:text-red-800 gap-2">
|
||||
<Icon icon="jam:write" fontSize={25} />
|
||||
<p className="text-base font-semibold mb-2">Content Rewrite</p>
|
||||
</Link>
|
||||
<div className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button className="w-full flex items-center gap-2">
|
||||
<button className="w-full flex flex-row items-center gap-3">
|
||||
<Icon icon="oi:share" fontSize={20} />
|
||||
<p className="text-base items-center font-semibold mb-3">Bagikan</p>
|
||||
<p className="text-base font-semibold mb-1">Bagikan</p>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
|
|
@ -365,10 +374,6 @@ const Galery = (props: any) => {
|
|||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<a onClick={() => handleDelete(video?.id)} className="flex items-center gap-2 hover:text-red-800 w-full rounded-lg">
|
||||
<Icon icon="fa:trash" fontSize={20} />
|
||||
<p className="text-base font-semibold">Hapus</p>
|
||||
</a>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
|
@ -378,7 +383,7 @@ const Galery = (props: any) => {
|
|||
</div>
|
||||
) : (
|
||||
<p className="flex items-center justify-center">
|
||||
<img src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
<Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
</p>
|
||||
)
|
||||
) : selectedTab == "audio" ? (
|
||||
|
|
@ -418,13 +423,21 @@ const Galery = (props: any) => {
|
|||
<Icon className="text-white ml-1" fontSize={25} icon="tabler:dots" />
|
||||
</a>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-40">
|
||||
<PopoverContent className="w-52">
|
||||
<div onClick={() => handleSaveWishlist(audio?.mediaUpload?.id)} className="cursor-pointer flex flex-row gap-2 hover:text-red-800">
|
||||
<Icon icon="material-symbols:bookmark-outline" fontSize={25} />
|
||||
<p className="text-base font-semibold mb-2">Simpan</p>
|
||||
</div>
|
||||
<Link href={`/content-management/rewrite/create/${audio?.mediaUpload?.id}`} className="flex flex-row hover:text-red-800 gap-2">
|
||||
<Icon icon="jam:write" fontSize={25} />
|
||||
<p className="text-base font-semibold mb-2">Content Rewrite</p>
|
||||
</Link>
|
||||
<div className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button className="w-full flex items-center gap-2">
|
||||
<Icon icon="oi:share" fontSize={20} />
|
||||
<p className="text-base font-semibold mb-3">Bagikan</p>
|
||||
<p className="text-base font-semibold mb-2">Bagikan</p>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
|
|
@ -441,10 +454,6 @@ const Galery = (props: any) => {
|
|||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<a onClick={() => handleDelete(audio?.id)} className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
|
||||
<Icon icon="fa:trash" fontSize={20} />
|
||||
<p className="text-base font-semibold">Hapus</p>
|
||||
</a>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
|
@ -452,7 +461,7 @@ const Galery = (props: any) => {
|
|||
</div>
|
||||
) : (
|
||||
<p className="flex items-center justify-center">
|
||||
<img src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
<Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
</p>
|
||||
)
|
||||
) : selectedTab == "image" ? (
|
||||
|
|
@ -474,13 +483,21 @@ const Galery = (props: any) => {
|
|||
<Icon className="text-white ml-1" fontSize={25} icon="tabler:dots" />
|
||||
</a>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-40">
|
||||
<PopoverContent className="w-52">
|
||||
<div onClick={() => handleSaveWishlist(image?.mediaUpload?.id)} className="cursor-pointer flex flex-row gap-2 hover:text-red-800">
|
||||
<Icon icon="material-symbols:bookmark-outline" fontSize={25} />
|
||||
<p className="text-base font-semibold mb-2">Simpan</p>
|
||||
</div>
|
||||
<Link href={`/content-management/rewrite/create/${image?.mediaUpload?.id}`} className="flex flex-row hover:text-red-800 gap-2">
|
||||
<Icon icon="jam:write" fontSize={25} />
|
||||
<p className="text-base font-semibold mb-2">Content Rewrite</p>
|
||||
</Link>
|
||||
<div className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button className="w-full flex items-center gap-2">
|
||||
<Icon icon="oi:share" fontSize={20} />
|
||||
<p className="text-base font-semibold mb-3">Bagikan</p>
|
||||
<p className="text-base font-semibold mb-2">Bagikan</p>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
|
|
@ -497,10 +514,6 @@ const Galery = (props: any) => {
|
|||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<a onClick={() => handleDelete(image?.id)} className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
|
||||
<Icon icon="fa:trash" fontSize={20} />
|
||||
<p className="text-base font-semibold">Hapus</p>
|
||||
</a>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</CardContent>
|
||||
|
|
@ -509,7 +522,7 @@ const Galery = (props: any) => {
|
|||
</div>
|
||||
) : (
|
||||
<p className="flex items-center justify-center">
|
||||
<img src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
<Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
</p>
|
||||
)
|
||||
) : contentDocument.length > 0 ? (
|
||||
|
|
@ -542,13 +555,21 @@ const Galery = (props: any) => {
|
|||
<Icon className="text-white ml-1" fontSize={25} icon="tabler:dots" />
|
||||
</a>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-40">
|
||||
<PopoverContent className="w-52">
|
||||
<div onClick={() => handleSaveWishlist(document?.mediaUpload?.id)} className="cursor-pointer flex flex-row gap-2 hover:text-red-800">
|
||||
<Icon icon="material-symbols:bookmark-outline" fontSize={25} />
|
||||
<p className="text-base font-semibold mb-2">Simpan</p>
|
||||
</div>
|
||||
<Link href={`/content-management/rewrite/create/${document?.mediaUpload?.id}`} className="flex flex-row hover:text-red-800 gap-2">
|
||||
<Icon icon="jam:write" fontSize={25} />
|
||||
<p className="text-base font-semibold mb-2">Content Rewrite</p>
|
||||
</Link>
|
||||
<div className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button className="w-full flex items-center gap-2">
|
||||
<Icon icon="oi:share" fontSize={20} />
|
||||
<p className="text-base font-semibold mb-3">Bagikan</p>
|
||||
<p className="text-base font-semibold mb-2">Bagikan</p>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
|
|
@ -565,10 +586,6 @@ const Galery = (props: any) => {
|
|||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<a onClick={() => handleDelete(document?.id)} className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
|
||||
<Icon icon="fa:trash" fontSize={20} />
|
||||
<p className="text-base font-semibold">Hapus</p>
|
||||
</a>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
|
@ -576,7 +593,7 @@ const Galery = (props: any) => {
|
|||
</div>
|
||||
) : (
|
||||
<p className="flex items-center justify-center">
|
||||
<img src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
<Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -289,21 +289,20 @@ const page = (props: any) => {
|
|||
return (
|
||||
<>
|
||||
<HeaderManagement />
|
||||
|
||||
<div className="flex flex-row">
|
||||
<div className="flex flex-col lg:flex-row">
|
||||
<SidebarManagement />
|
||||
<div className="w-2/3 p-12">
|
||||
<div className="w-full lg:w-2/3 p-8 lg:p-12">
|
||||
<div className="flex flex-col">
|
||||
<div className="text-xl font-bold mb-5">Content Rewrite</div>
|
||||
<div className="p-8 border border-black rounded-lg">
|
||||
<form method="POST" onSubmit={handleSubmit(onSubmit)}>
|
||||
{content && (
|
||||
<div className="flex flex-col gap-2 ">
|
||||
<div className="flex flex-row gap-2">
|
||||
<div className="flex flex-col lg:flex-row gap-2">
|
||||
<div className="gap-1 flex flex-col mb-3">
|
||||
<p className="font-semibold">Bahasa</p>
|
||||
<Select value={selectedLanguage} onValueChange={setSelectedLanguage}>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectTrigger className="w-full lg:w-[180px]">
|
||||
<SelectValue placeholder="Pilih Bahasa" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
|
@ -318,7 +317,7 @@ const page = (props: any) => {
|
|||
<div className="gap-1 flex flex-col mb-3">
|
||||
<p className="font-semibold">Context Type</p>
|
||||
<Select value={selectedContextType} onValueChange={setSelectedContextType}>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectTrigger className="w-full lg:w-[180px]">
|
||||
<SelectValue placeholder="Select Context" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
|
@ -335,7 +334,7 @@ const page = (props: any) => {
|
|||
<div className="gap-1 flex flex-col mb-3">
|
||||
<p className="font-semibold">Writing Style</p>
|
||||
<Select value={selectedWritingStyle} onValueChange={setSelectedWritingStyle}>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectTrigger className="w-full lg:w-[180px]">
|
||||
<SelectValue placeholder="Select Writing" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
|
@ -353,7 +352,7 @@ const page = (props: any) => {
|
|||
<div className="gap-1 flex flex-col mb-3">
|
||||
<p className="font-semibold">Article Size</p>
|
||||
<Select value={selectedSize} onValueChange={setSelectedSize}>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectTrigger className="w-full lg:w-[180px]">
|
||||
<SelectValue placeholder="Select Size" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
|
@ -396,7 +395,7 @@ const page = (props: any) => {
|
|||
/>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<a onClick={handleGenerateArtikel} className="text-blue-500 cursor-pointer hover:bg-blue-700 hover:text-white border border-blue-500 rounded-md p-2">
|
||||
<a onClick={handleGenerateArtikel} className="text-blue-500 cursor-pointer hover:bg-blue-700 hover:text-white border border-blue-500 rounded-md p-2 text-sm lg:text-base">
|
||||
Generate Artikel
|
||||
</a>
|
||||
{isGeneratedArticle && (
|
||||
|
|
@ -441,11 +440,11 @@ const page = (props: any) => {
|
|||
onClick={() => {
|
||||
router.back();
|
||||
}}
|
||||
className="border border-blue-400 hover:bg-blue-400 hover:text-white text-blue-400"
|
||||
className="border border-blue-400 hover:bg-blue-400 hover:text-white text-blue-400 text-sm lg:text-base"
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
<Button type="submit" className="border border-blue-500 bg-blue-500 text-white">
|
||||
<Button type="submit" className="border border-blue-500 bg-blue-500 text-sm lg:text-base text-white">
|
||||
Simpan
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -194,9 +194,9 @@ const page = () => {
|
|||
return (
|
||||
<>
|
||||
<HeaderManagement />
|
||||
<div className="flex flex-row">
|
||||
<div className="flex flex-col lg:flex-row">
|
||||
<SidebarManagement />
|
||||
<div className="w-2/3 p-12">
|
||||
<div className="w-full lg:w-2/3 p-8 lg:p-12">
|
||||
<div className="flex flex-col">
|
||||
<div className="text-xl font-bold mb-5">Detail Content Rewrite</div>
|
||||
<div className="p-8 border border-black rounded-lg">
|
||||
|
|
@ -218,8 +218,8 @@ const page = () => {
|
|||
// onChange={(e) => setSelectedTitle(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-between gap-3 lg:flex-row">
|
||||
<div className="w-1/2">
|
||||
<div className="flex flex-col justify-between gap-3 lg:flex-row">
|
||||
<div className="w-full lg:w-1/2">
|
||||
<div className="mb-1">
|
||||
<p className="font-semibold">Main Keyword</p>
|
||||
</div>
|
||||
|
|
@ -227,7 +227,7 @@ const page = () => {
|
|||
<Input type="text" className="border mb-3 w-full rounded-md p-2 border-black" id="mainKeyword" name="mainKeyword" placeholder="Masukan Main Keyword disini!" defaultValue={content?.mainKeyword} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-1/2">
|
||||
<div className="w-full lg:w-1/2">
|
||||
<div className="mb-1">
|
||||
<label htmlFor="title" className="font-semibold">
|
||||
Additional Keyword{" "}
|
||||
|
|
@ -247,8 +247,8 @@ const page = () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 justify-between lg:flex-row">
|
||||
<div className="w-1/2">
|
||||
<div className="flex gap-3 justify-between flex-col lg:flex-row">
|
||||
<div className="w-full lg:w-1/2">
|
||||
<div className="font-semibold mb-1">
|
||||
<label htmlFor="metaTitle">Meta Title</label>
|
||||
</div>
|
||||
|
|
@ -264,7 +264,7 @@ const page = () => {
|
|||
defaultValue={content?.metaTitle}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-1/2">
|
||||
<div className="w-full lg:w-1/2">
|
||||
<div className="font-semibold mb-1">
|
||||
<label htmlFor="metaDescription">Meta Description</label>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { Input } from "@/components/ui/input";
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { sendMediaUploadToEmail } from "@/service/media-tracking/media-tracking";
|
||||
import ImageBlurry from "@/components/ui/image-blurry";
|
||||
import Image from "next/image";
|
||||
|
||||
const page = () => {
|
||||
const [, setProfile] = useState();
|
||||
|
|
@ -197,9 +198,9 @@ const page = () => {
|
|||
return (
|
||||
<>
|
||||
<HeaderManagement />
|
||||
<div className="flex flex-row">
|
||||
<div className="flex flex-col lg:flex-row">
|
||||
<SidebarManagement />
|
||||
<div className="w-2/3 p-12">
|
||||
<div className="w-full lg:w-2/3 p-8 lg:p-12">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold mb-4">Galeri Content Rewrite</h1>
|
||||
</div>
|
||||
|
|
@ -213,7 +214,7 @@ const page = () => {
|
|||
<Link href={`/content-management/rewrite/detail/${image.id}`}>
|
||||
{/* <img src={image?.thumbnailUrl} className="h-40 object-cover items-center justify-center cursor-pointer rounded-lg place-self-center" /> */}
|
||||
<div className="img-container h-60 bg-[#e9e9e9] cursor-pointer rounded-lg">
|
||||
<ImageBlurry src={image?.thumbnailUrl} alt={image?.title} style={{ objectFit: "contain", width: "100%", height: "100%" }} />
|
||||
<ImageBlurry src={image?.thumbnailUrl} alt={image?.title} style={{ objectFit: "contain", width: "100%", height: "100%" }} />
|
||||
</div>
|
||||
</Link>
|
||||
<div className="font-semibold p-4 text-white text-xs lg:text-sm dark:text-black truncate w-full">{image?.title}</div>
|
||||
|
|
@ -259,7 +260,7 @@ const page = () => {
|
|||
</div>
|
||||
) : (
|
||||
<p className="flex items-center justify-center">
|
||||
<img src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
<Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,13 +4,15 @@ import HeaderManagement from "@/components/landing-page/header-management";
|
|||
import SidebarManagement from "@/components/landing-page/sidebar-management";
|
||||
import { close, error, loading } from "@/config/swal";
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
import { getInfoProfile, getUsersTeams } from "@/service/landing/landing";
|
||||
import { getInfoProfile, getUsersTeams, saveUserReports } from "@/service/landing/landing";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import Swal from "sweetalert2";
|
||||
import { saveUserReports } from "@/service/content/content";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import toast from "react-hot-toast";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { ToastAction } from "@/components/ui/toast";
|
||||
|
||||
const page = () => {
|
||||
const [user, setUser] = useState<any>();
|
||||
|
|
@ -19,6 +21,8 @@ const page = () => {
|
|||
const [userSelected, setUserSelected] = useState();
|
||||
const [reportMessage, setReportMessage] = useState<string>();
|
||||
const MySwal = withReactContent(Swal);
|
||||
const { toast } = useToast();
|
||||
const [reportMessageOpen, setReportMessageOpen] = useState(false);
|
||||
|
||||
// const launchModal = (user: any) => {
|
||||
// setUserSelected(user);
|
||||
|
|
@ -41,6 +45,8 @@ const page = () => {
|
|||
}
|
||||
|
||||
async function save() {
|
||||
setReportMessageOpen(false);
|
||||
|
||||
loading();
|
||||
const data = {
|
||||
userId: user?.id,
|
||||
|
|
@ -50,13 +56,24 @@ const page = () => {
|
|||
const response = await saveUserReports(data);
|
||||
|
||||
if (response?.error) {
|
||||
error(response?.message);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Uh oh! Something went wrong.",
|
||||
});
|
||||
// error(response?.message);
|
||||
return false;
|
||||
}
|
||||
|
||||
close();
|
||||
|
||||
successSubmit();
|
||||
toast({
|
||||
title: "Success !!",
|
||||
});
|
||||
|
||||
// toast.success("SUKSES !!", {
|
||||
// position: "top-right",
|
||||
// });
|
||||
// successSubmit();
|
||||
}
|
||||
|
||||
function successSubmit() {
|
||||
|
|
@ -97,7 +114,7 @@ const page = () => {
|
|||
return (
|
||||
<>
|
||||
<HeaderManagement />
|
||||
<div className="flex flex-row">
|
||||
<div className="flex flex-col lg:flex-row">
|
||||
<SidebarManagement />
|
||||
<div className="w-2/3 p-12">
|
||||
<div className="flex flex-col">
|
||||
|
|
@ -136,9 +153,9 @@ const page = () => {
|
|||
</div>
|
||||
<div className="border-b border-black w-full"></div>
|
||||
<div className="place-items-end">
|
||||
<Dialog>
|
||||
<Dialog open={reportMessageOpen} onOpenChange={setReportMessageOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" className="bg-red-500 rounded-md">
|
||||
<Button variant="outline" className="bg-red-500 rounded-md text-white hover:bg-white hover:text-red-500">
|
||||
Report
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
|
@ -150,13 +167,28 @@ const page = () => {
|
|||
<div className="border-b border-black w-full"></div>
|
||||
<div className="flex flex-col">
|
||||
<h1 className="text-red-600 mb-2">Alasan Report Akun</h1>
|
||||
<textarea id="formControlTextarea1" rows={4} className="border border-black font-light" onChange={(e) => setReportMessage(e.target.value)} />
|
||||
<textarea id="formControlTextarea1" rows={4} className="border border-black font-light" onChange={(e: any) => setReportMessage(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button className="bg-red-500 text-white" type="submit" onClick={() => onSubmit()}>
|
||||
Kirim
|
||||
</Button>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="bg-red-500 text-white" type="submit">
|
||||
Kirim
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<h1>Simpan Data?</h1>
|
||||
<DialogFooter>
|
||||
<DialogClose>
|
||||
<Button onClick={save}>Simpan</Button>
|
||||
</DialogClose>
|
||||
<DialogClose>
|
||||
<Button>Cancel</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
|
|
|||
|
|
@ -6,14 +6,19 @@ import { Icon } from "@iconify/react/dist/iconify.js";
|
|||
import NewContent from "@/components/landing-page/new-content";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
import { checkWishlistStatus, deleteWishlist, getDetail, saveWishlist } from "@/service/landing/landing";
|
||||
import { close, error, loading, successCallback } from "@/config/swal";
|
||||
import { checkWishlistStatus, createPublicSuggestion, deletePublicSuggestion, deleteWishlist, getDetail, getPublicSuggestionList, saveWishlist } from "@/service/landing/landing";
|
||||
import { close, error, loading, successCallback, warning } from "@/config/swal";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { Link, useRouter } from "@/i18n/routing";
|
||||
import { sendMediaUploadToEmail } from "@/service/media-tracking/media-tracking";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { checkMaliciousText, getPublicLocaleTimestamp } from "@/utils/globals";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import Swal from "sweetalert2";
|
||||
import parse from "html-react-parser";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const DetailDocument = () => {
|
||||
const [selectedSize, setSelectedSize] = useState<string>("L");
|
||||
|
|
@ -42,8 +47,13 @@ const DetailDocument = () => {
|
|||
const [width, setWidth] = useState<any>();
|
||||
const [content, setContent] = useState<any>([]);
|
||||
const userRoleId = getCookiesDecrypt("urie");
|
||||
const [message, setMessage] = useState("");
|
||||
const [listSuggestion, setListSuggestion] = useState<any>();
|
||||
const MySwal = withReactContent(Swal);
|
||||
const [visibleInput, setVisibleInput] = useState<string | null>(null);
|
||||
const t = useTranslations("LandingPage");
|
||||
|
||||
let typeString = "video";
|
||||
let typeString = "document";
|
||||
|
||||
useEffect(() => {
|
||||
initFetch();
|
||||
|
|
@ -53,9 +63,12 @@ const DetailDocument = () => {
|
|||
const initFetch = async () => {
|
||||
const response = await getDetail(String(slug));
|
||||
console.log("detailDocument", response);
|
||||
const responseGet = await getPublicSuggestionList(slug?.split("-")?.[0]);
|
||||
|
||||
setIsFromSPIT(response?.data?.data?.isFromSPIT);
|
||||
setWidth(window.innerWidth);
|
||||
setContent(response?.data.data);
|
||||
setListSuggestion(responseGet.data?.data);
|
||||
setMain({
|
||||
id: response?.data?.data?.files[0]?.id,
|
||||
type: response?.data?.data?.fileType.name,
|
||||
|
|
@ -128,11 +141,9 @@ const DetailDocument = () => {
|
|||
};
|
||||
|
||||
const sizes = [
|
||||
{ label: "XL", value: "3198 x 1798 px" },
|
||||
{ label: "L", value: "2399 x 1349 px" },
|
||||
{ label: "M", value: "1599 x 899 px" },
|
||||
{ label: "S", value: "1066 x 599 px" },
|
||||
{ label: "XS", value: "800 x 450 px" },
|
||||
{ label: "DOC", value: "...KB" },
|
||||
{ label: "PPT", value: "...KB" },
|
||||
{ label: "PDF", value: "...KB" },
|
||||
];
|
||||
|
||||
async function sendActivityLog(activityTypeId: number) {
|
||||
|
|
@ -284,6 +295,123 @@ const DetailDocument = () => {
|
|||
return false;
|
||||
};
|
||||
|
||||
async function sendSuggestionChild(parentId: any) {
|
||||
const inputElement = document.querySelector(`#input-comment-${parentId}`) as HTMLInputElement;
|
||||
|
||||
if (inputElement && inputElement.value.length > 3) {
|
||||
loading();
|
||||
const data = {
|
||||
mediaUploadId: slug?.split("-")?.[0],
|
||||
message: inputElement.value,
|
||||
parentId,
|
||||
};
|
||||
|
||||
console.log(data);
|
||||
const response = await createPublicSuggestion(data);
|
||||
console.log(response);
|
||||
const responseGet: any = await getPublicSuggestionList(slug?.split("-")?.[0]);
|
||||
console.log(responseGet.data?.data);
|
||||
setListSuggestion(responseGet.data?.data);
|
||||
|
||||
// Reset input field
|
||||
inputElement.value = "";
|
||||
|
||||
// document.querySelector("#comment-id-" + parentId)?.style.display = "none";
|
||||
|
||||
close();
|
||||
}
|
||||
}
|
||||
async function deleteDataSuggestion(dataId: any) {
|
||||
loading();
|
||||
const response = await deletePublicSuggestion(dataId);
|
||||
console.log(response);
|
||||
const responseGet = await getPublicSuggestionList(slug.split("-")?.[0]);
|
||||
console.log(responseGet.data?.data);
|
||||
setListSuggestion(responseGet.data?.data);
|
||||
close();
|
||||
}
|
||||
const deleteData = (dataId: any) => {
|
||||
MySwal.fire({
|
||||
title: "Delete Comment",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Delete",
|
||||
}).then((result: any) => {
|
||||
if (result.isConfirmed) {
|
||||
deleteDataSuggestion(dataId);
|
||||
console.log(dataId);
|
||||
}
|
||||
});
|
||||
};
|
||||
const showInput = (e: any) => {
|
||||
console.log(document.querySelector(`#${e}`)?.classList);
|
||||
document.querySelector(`#${e}`)?.classList.toggle("none");
|
||||
setVisibleInput(visibleInput === e ? null : e);
|
||||
};
|
||||
|
||||
function addDefaultProfile(ev: any) {
|
||||
ev.target.src = "/assets/avatar-profile.png";
|
||||
}
|
||||
|
||||
async function sendSuggestionParent() {
|
||||
if (message?.length > 3) {
|
||||
loading();
|
||||
const data = {
|
||||
mediaUploadId: slug?.split("-")?.[0],
|
||||
message,
|
||||
parentId: null,
|
||||
};
|
||||
|
||||
const response = await createPublicSuggestion(data);
|
||||
|
||||
console.log(response);
|
||||
setMessage("");
|
||||
|
||||
const responseGet = await getPublicSuggestionList(slug?.split("-")?.[0]);
|
||||
console.log(responseGet?.data?.data);
|
||||
setListSuggestion(responseGet?.data?.data);
|
||||
|
||||
// Hapus nilai semua input secara manual jika perlu
|
||||
const inputs = document.querySelectorAll("input");
|
||||
inputs.forEach((input) => {
|
||||
input.value = "";
|
||||
});
|
||||
|
||||
close();
|
||||
}
|
||||
}
|
||||
const getInputValue = (e: any) => {
|
||||
const message = e.target.value;
|
||||
console.log(message);
|
||||
setMessage(message);
|
||||
};
|
||||
const postData = () => {
|
||||
const checkMessage = checkMaliciousText(message);
|
||||
if (checkMessage == "") {
|
||||
if (Number(userRoleId) < 1 || userRoleId == undefined) {
|
||||
router.push("/auth");
|
||||
} else {
|
||||
sendSuggestionParent();
|
||||
}
|
||||
} else {
|
||||
warning(checkMessage);
|
||||
}
|
||||
};
|
||||
const postDataChild = (id: any) => {
|
||||
const checkMessage = checkMaliciousText(message);
|
||||
if (checkMessage == "") {
|
||||
if (Number(userRoleId) < 1 || userRoleId == undefined) {
|
||||
router.push("/auth");
|
||||
} else {
|
||||
sendSuggestionChild(id);
|
||||
}
|
||||
} else {
|
||||
warning(checkMessage);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="px-4 md:px-24 py-4">
|
||||
|
|
@ -295,19 +423,37 @@ const DetailDocument = () => {
|
|||
<div className="absolute top-4 left-4"></div>
|
||||
</div>
|
||||
{/* Footer Informasi */}
|
||||
<div className="text-sm text-gray-500 flex justify-between items-center border-t mt-4">
|
||||
<p className="flex flex-row items-center mt-3">
|
||||
<div className="text-gray-500 flex flex-col lg:flex-row justify-between items-center border-t mt-4">
|
||||
{/* <p className="flex flex-row items-center mt-3">
|
||||
oleh <span className="font-semibold text-black">{detailDataDocument?.uploadedBy?.userLevel?.name}</span> | Diupdate pada {detailDataDocument?.updatedAt} WIB |
|
||||
<Icon icon="formkit:eye" width="15" height="15" />
|
||||
|
||||
{detailDataDocument?.clickCount}
|
||||
</p>
|
||||
<p className="mt-3">Kreator: {detailDataDocument?.creatorName}</p>
|
||||
<p className="mt-3">Kreator: {detailDataDocument?.creatorName}</p> */}
|
||||
<div className="flex flex-col lg:flex-row items-center mt-3 lg:justify-between">
|
||||
<p className="text-xs lg:text-sm">
|
||||
{t("by")} <span className="font-semibold text-black">{detailDataDocument?.uploadedBy?.userLevel?.name}</span>
|
||||
</p>
|
||||
<p className="text-xs lg:text-sm">
|
||||
| {t("updatedOn")} {detailDataDocument?.updatedAt} WIB |
|
||||
</p>
|
||||
<p className="text-xs lg:text-sm flex justify-center items-center">
|
||||
<Icon icon="formkit:eye" width="15" height="15" />
|
||||
{detailDataDocument?.clickCount}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<p className="flex text-end text-xs lg:text-sm font-semibold">
|
||||
{t("creator")}
|
||||
{detailDataDocument?.creatorName}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Keterangan */}
|
||||
<div className="md:w-3/4">
|
||||
<h1 className="flex flex-row font-bold text-2xl my-8">{detailDataDocument?.title}</h1>
|
||||
<div className="">
|
||||
<h1 className="flex flex-row font-bold text-lg lg:text-2xl my-8 text-justify">{detailDataDocument?.title}</h1>
|
||||
<div dangerouslySetInnerHTML={{ __html: detailDataDocument?.htmlDescription }} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -317,12 +463,12 @@ const DetailDocument = () => {
|
|||
{isSaved ? (
|
||||
<a onClick={() => handleDeleteWishlist()} className="flex flex-col mb-3 items-center justify-center cursor-pointer">
|
||||
<Icon icon="material-symbols:bookmark" width={40} />
|
||||
<p className="text-base lg:text-lg">Hapus</p>
|
||||
<p className="text-base lg:text-lg">{t("delete")}</p>
|
||||
</a>
|
||||
) : (
|
||||
<a onClick={() => doBookmark()} className="flex flex-col mb-3 items-center justify-center cursor-pointer">
|
||||
<Icon icon="material-symbols:bookmark-outline" width={40} />
|
||||
<p className="text-base lg:text-lg">Simpan</p>
|
||||
<p className="text-base lg:text-lg">{t("save")}</p>
|
||||
</a>
|
||||
)}
|
||||
|
||||
|
|
@ -344,7 +490,7 @@ const DetailDocument = () => {
|
|||
<div className="border-t border-black my-4"></div>
|
||||
|
||||
{/* Opsi Ukuran Foto */}
|
||||
<h4 className="flex text-lg justify-center items-center font-semibold my-3">Opsi Ukuran Foto</h4>
|
||||
<h4 className="flex text-lg justify-center items-center font-semibold my-3">{t("docSize")}</h4>
|
||||
<div className="border-t border-black my-4"></div>
|
||||
<div className="space-y-2">
|
||||
{sizes.map((size: any) => (
|
||||
|
|
@ -364,7 +510,7 @@ const DetailDocument = () => {
|
|||
<div className="mt-4">
|
||||
<label className="flex items-center space-x-2 text-sm">
|
||||
<input type="checkbox" className="text-red-600 focus:ring-red-600" onChange={() => setIsDownloadAll(!isDownloadAll)} />
|
||||
<span>Download Semua File?</span>
|
||||
<span>{t("downloadAll")}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
|
@ -373,12 +519,12 @@ const DetailDocument = () => {
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||
<path fill="white" d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z" />
|
||||
</svg>
|
||||
Download
|
||||
{t("download")}
|
||||
</button>
|
||||
|
||||
{/* Tombol Bagikan */}
|
||||
<div className="flex flex-row py-3">
|
||||
<p className="text-base font-semibold">Bagikan</p>
|
||||
<p className="text-base font-semibold">{t("share")}</p>
|
||||
<a className="ml-8 cursor-pointer" onClick={() => handleShare("fb", `https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}"e=${content?.title}`)}>
|
||||
<Icon icon="brandico:facebook" height="20" className="px-auto text-red-600 text-center" />
|
||||
</a>
|
||||
|
|
@ -396,13 +542,13 @@ const DetailDocument = () => {
|
|||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<div className="flex flex-col">
|
||||
<h1 className="mb-2">Share Ke Email</h1>
|
||||
<h1 className="mb-2">{t("shareTo")}</h1>
|
||||
<div className="flex flex-col mb-2">
|
||||
<p className="text-base font-semibold mb-1">Email Tujuan :</p>
|
||||
<Input value={emailShareInput} onChange={(event) => setEmailShareInput(event.target.value)} onKeyPress={handleEmailList} type="email" placeholder="Tekan Enter untuk input Email" />
|
||||
<p className="text-base font-semibold mb-1">{t("destinationEmail")}</p>
|
||||
<Input value={emailShareInput} onChange={(event) => setEmailShareInput(event.target.value)} onKeyPress={handleEmailList} type="email" placeholder={t("pressEnter")} />
|
||||
</div>
|
||||
<Button className="bg-blue-500 text-white p-2 w-fit rounded-lg" onClick={() => shareToEmail()}>
|
||||
Kirim
|
||||
{t("send")}
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
|
|
@ -414,11 +560,182 @@ const DetailDocument = () => {
|
|||
|
||||
<div className="w-full mb-8">
|
||||
{/* Comment */}
|
||||
<div className="flex flex-col my-16 p-10 bg-[#f7f7f7]">
|
||||
<div className="gap-5 flex flex-col px-4 lg:px-16">
|
||||
<p className="flex items-start text-lg">Berikan Komentar</p>
|
||||
<Textarea placeholder="Type your comments here." className="flex w-full" />
|
||||
<button className="flex items-start bg-[#bb3523] rounded-lg text-white w-fit px-4 py-1">Kirim</button>
|
||||
<div className="flex flex-col my-16 p-4 lg:p-10 bg-[#f7f7f7]">
|
||||
<div className="gap-5 flex flex-col px-4 lg:px-14">
|
||||
<p className="flex items-start text-lg">{t("comment")}</p>
|
||||
<Textarea placeholder="Type your comments here." className="flex w-full pb-12" onChange={getInputValue} />
|
||||
<button onClick={() => postData()} className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-4 py-1">
|
||||
{t("send")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="border-b-2 border-slate-300 mt-4 w-auto"></div>
|
||||
|
||||
<div>
|
||||
{listSuggestion?.map((data: any) => (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row mt-2 px-4 lg:px-14">
|
||||
<img src={data?.suggestionFrom?.profilePictureUrl} className="h-12 lg:h-16 w-12 lg:w-16 mr-2" onError={addDefaultProfile} alt="" />
|
||||
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
|
||||
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
|
||||
{Number(data.suggestionFrom?.roleId) == 2 || Number(data.suggestionFrom?.roleId) == 3 || Number(data.suggestionFrom?.roleId) == 4 ? "HUMAS POLRI" : data.suggestionFrom?.fullname}
|
||||
{getPublicLocaleTimestamp(new Date(data.createdAt))}
|
||||
</p>
|
||||
<p className="text-slate-500 text-[13px] lg:text-sm mb-4">{data?.message}</p>
|
||||
<div>
|
||||
<a
|
||||
style={
|
||||
Number(data.suggestionFrom?.id) == Number(userId)
|
||||
? {
|
||||
display: "none",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
onClick={() => showInput(`comment-id-${data.id}`)}
|
||||
className="mr-2"
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("reply")}</small>
|
||||
</a>
|
||||
{Number(data.suggestionFrom?.id) == Number(userId) || Number(userRoleId) == 2 ? (
|
||||
<a onClick={() => deleteData(data.id)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("delete")}</small>
|
||||
</a>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{visibleInput === `comment-id-${data.id}` && (
|
||||
<div id={`comment-id-${data.id}`} className="px-4 pl-[72px] lg:px-14 lg:pl-32 mt-2 ">
|
||||
<Textarea id={`input-comment-${data.id}`} className="p-4 focus:outline-none focus:border-sky-500" placeholder={t("enterReply")} />
|
||||
<div className="flex flex-row gap-3">
|
||||
<a onClick={() => postDataChild(data.id)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 mt-2 cursor-pointer">{t("send")}</small>
|
||||
</a>
|
||||
<a onClick={() => showInput(`comment-id-${data.id}`)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg mt-2 w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("cancel")}</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data.children.length > 0
|
||||
? data.children?.map((child1: any) => (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-16 lg:pl-32">
|
||||
<img src={child1.suggestionFrom?.profilePictureUrl} onError={addDefaultProfile} alt="" className="h-10 lg:h-16 w-10 lg:w-16 mr-2" />
|
||||
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
|
||||
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
|
||||
{" "}
|
||||
<b>{Number(child1.suggestionFrom?.roleId) == 2 || Number(child1.suggestionFrom?.roleId) == 3 || Number(child1.suggestionFrom?.roleId) == 4 ? "HUMAS POLRI" : child1.suggestionFrom?.fullname}</b>{" "}
|
||||
{getPublicLocaleTimestamp(new Date(child1.createdAt))}
|
||||
</p>
|
||||
<p className="text-slate-500 text-[13px] lg:text-sm mb-4">{parse(String(child1?.message))}</p>
|
||||
<div>
|
||||
<a
|
||||
style={
|
||||
Number(child1.suggestionFrom?.id) == Number(userId)
|
||||
? {
|
||||
display: "none",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
onClick={() => showInput(`comment-id-${child1.id}`)}
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("reply")}</small>
|
||||
</a>
|
||||
<a
|
||||
style={
|
||||
Number(child1.suggestionFrom?.id) == Number(userId)
|
||||
? {}
|
||||
: {
|
||||
display: "none",
|
||||
}
|
||||
}
|
||||
onClick={() => deleteData(child1.id)}
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("delete")}</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{visibleInput === `comment-id-${child1.id}` && (
|
||||
<div id={`comment-id-${child1.id}`} className="px-4 lg:px-14 pl-28 lg:pl-[200px]">
|
||||
<Textarea name="" className="mt-2 " id={`input-comment-${child1.id}`} placeholder={t("enterReply")} />
|
||||
<div className="flex flex-row mt-2 gap-3">
|
||||
<a onClick={() => postDataChild(child1.id)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("send")}</small>
|
||||
</a>
|
||||
<a onClick={() => showInput(`comment-id-${child1.id}`)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("cancel")}</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{child1.children.length > 0
|
||||
? child1.children?.map((child2: any) => (
|
||||
<div className="">
|
||||
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-28 lg:pl-48">
|
||||
<img src={child2.suggestionFrom?.profilePictureUrl} className="h-9 lg:h-16 w-9 lg:w-16 mr-2" onError={addDefaultProfile} alt="" />
|
||||
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
|
||||
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
|
||||
{" "}
|
||||
<b>{Number(child2.suggestionFrom?.roleId) == 2 || Number(child2.suggestionFrom?.roleId) == 3 || Number(child2.suggestionFrom?.roleId) == 4 ? "HUMAS POLRI" : child2.suggestionFrom?.fullname}</b>{" "}
|
||||
{getPublicLocaleTimestamp(new Date(child2.createdAt))}
|
||||
</p>
|
||||
<p className="text-slate-500 text-sm mb-4">{parse(String(child2?.message))}</p>
|
||||
<div>
|
||||
<a
|
||||
style={
|
||||
Number(child2.suggestionFrom?.id) == Number(userId)
|
||||
? {
|
||||
display: "none",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
onClick={() => showInput(`comment-id-${child2.id}`)}
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("reply")}</small>
|
||||
</a>
|
||||
<a
|
||||
style={
|
||||
Number(child2.suggestionFrom?.id) == Number(userId)
|
||||
? {}
|
||||
: {
|
||||
display: "none",
|
||||
}
|
||||
}
|
||||
onClick={() => deleteData(child2.id)}
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("delete")}</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{visibleInput === `comment-id-${child2.id}` && (
|
||||
<div id={`comment-id-${child2.id}`} className="px-4 lg:px-14 pl-40 lg:pl-[265px]">
|
||||
<Textarea name="" id={`input-comment-${child2.id}`} className="my-2" placeholder={t("enterReply")} />
|
||||
<div className="flex flex-row gap-3">
|
||||
<a onClick={() => postDataChild(child2.id)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("send")}</small>
|
||||
</a>
|
||||
<a onClick={() => showInput(`comment-id-${child2.id}`)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("cancel")}</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
: ""}
|
||||
</div>
|
||||
))
|
||||
: ""}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import ReactDatePicker from "react-datepicker";
|
|||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import { close, loading } from "@/config/swal";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
|
|
@ -68,9 +69,18 @@ const FilterPage = () => {
|
|||
const [handleClose, setHandleClose] = useState(false);
|
||||
const [categories, setCategories] = useState([]);
|
||||
const [userLevels, setUserLevels] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState<any>(true);
|
||||
|
||||
// const [startDate, endDate] = dateRange;
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 3000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
const pageFromUrl = searchParams?.get("page");
|
||||
if (pageFromUrl) {
|
||||
|
|
@ -153,7 +163,8 @@ const FilterPage = () => {
|
|||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") : "",
|
||||
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : ""
|
||||
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : "",
|
||||
locale == "en" ? true : false
|
||||
);
|
||||
close();
|
||||
// setGetTotalPage(response?.data?.data?.totalPages);
|
||||
|
|
@ -185,7 +196,8 @@ const FilterPage = () => {
|
|||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") : "",
|
||||
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : ""
|
||||
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : "",
|
||||
locale == "en" ? true : false
|
||||
);
|
||||
close();
|
||||
// setGetTotalPage(response?.data?.data?.totalPages);
|
||||
|
|
@ -282,20 +294,20 @@ const FilterPage = () => {
|
|||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
initFetch();
|
||||
}, [page]);
|
||||
|
||||
const initFetch = async () => {
|
||||
const response = await getListContent({ page: page - 1, size: 6, sortBy: "createdAt", contentTypeId: "3", isInt: locale == "en" ? true : false });
|
||||
console.log(response);
|
||||
setDocumentData(response?.data?.data?.content);
|
||||
const data = response?.data?.data;
|
||||
const contentData = data?.content;
|
||||
setDocumentData(contentData);
|
||||
setTotalData(data?.totalElements);
|
||||
setTotalPage(data?.totalPages);
|
||||
};
|
||||
// useEffect(() => {
|
||||
// initFetch();
|
||||
// }, [page]);
|
||||
|
||||
// const initFetch = async () => {
|
||||
// const response = await getListContent({ page: page - 1, size: 6, sortBy: "createdAt", contentTypeId: "3", isInt: locale == "en" ? true : false });
|
||||
// console.log(response);
|
||||
// setDocumentData(response?.data?.data?.content);
|
||||
// const data = response?.data?.data;
|
||||
// const contentData = data?.content;
|
||||
// setDocumentData(contentData);
|
||||
// setTotalData(data?.totalElements);
|
||||
// setTotalPage(data?.totalPages);
|
||||
// };
|
||||
|
||||
function getSelectedCategory() {
|
||||
const filter = [];
|
||||
|
|
@ -382,7 +394,7 @@ const FilterPage = () => {
|
|||
|
||||
{/* Left */}
|
||||
<div className="flex flex-col lg:flex-row gap-6 p-4">
|
||||
<div className="lg:w-[25%] w-full bg-[#f7f7f7] dark:bg-black p-4 rounded-lg shadow-md">
|
||||
<div className="lg:w-[45%] w-full bg-[#f7f7f7] dark:bg-black p-4 rounded-lg shadow-md">
|
||||
<h2 className="text-lg font-semibold mb-4 flex items-center gap-1">
|
||||
<Icon icon="stash:filter-light" fontSize={30} />
|
||||
Filter
|
||||
|
|
@ -495,7 +507,7 @@ const FilterPage = () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Konten Kanan */}
|
||||
{/* Right */}
|
||||
<Reveal>
|
||||
<div className="flex-1">
|
||||
<div className="flex flex-col items-end mb-4">
|
||||
|
|
@ -506,37 +518,56 @@ const FilterPage = () => {
|
|||
</select>
|
||||
</div>
|
||||
|
||||
{documentData?.length > 0 ? (
|
||||
<div className=" grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{documentData?.map((document: any) => (
|
||||
<Link href={`/document/detail/${document?.slug}`} 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 gap-2">
|
||||
<div className="text-gray-500 dark:text-gray-400 flex flex-row items-center gap-2 text-xs">
|
||||
{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-xs 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>
|
||||
))}
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col lg:flex-row space-y-3 w-full justify-center items-center gap-3">
|
||||
<Skeleton className="h-[100px] w-[500px] rounded-xl" />
|
||||
<Skeleton className="h-[100px] w-[500px] rounded-xl" />
|
||||
</div>
|
||||
<div className="flex flex-col lg:flex-row space-y-3 w-full justify-center items-center gap-3">
|
||||
<Skeleton className="h-[100px] w-[500px] rounded-xl" />
|
||||
<Skeleton className="h-[100px] w-[500px] rounded-xl" />
|
||||
</div>
|
||||
<div className="flex flex-col lg:flex-row space-y-3 w-full justify-center items-center gap-3">
|
||||
<Skeleton className="h-[100px] w-[500px] rounded-xl" />
|
||||
<Skeleton className="h-[100px] w-[500px] rounded-xl" />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="flex items-center justify-center">
|
||||
<img src="/assets/empty-data.png" alt="empty" className="h-60 w-60 my-4" />
|
||||
</p>
|
||||
<>
|
||||
{documentData?.length > 0 ? (
|
||||
<div className=" grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{documentData?.map((document: any) => (
|
||||
<Link href={`/document/detail/${document?.slug}`} 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 gap-2">
|
||||
<div className="text-gray-500 dark:text-gray-400 flex flex-row items-center gap-2 text-xs">
|
||||
{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-xs 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>
|
||||
) : (
|
||||
<p className="flex items-center justify-center">
|
||||
<img src="/assets/empty-data.png" alt="empty" className="h-60 w-60 my-4" />
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<LandingPagination table={table} totalData={totalData} totalPage={totalPage} />
|
||||
|
|
|
|||
|
|
@ -54,18 +54,17 @@ const FeedbackForm: React.FC = () => {
|
|||
feedbackId: any;
|
||||
score: any;
|
||||
};
|
||||
|
||||
|
||||
const [userFeedbacks, setUserFeedbacks] = useState<Feedback[]>([]);
|
||||
const [hasMounted, setHasMounted] = useState(false);
|
||||
|
||||
const handleRatingChange = (id: any, score: any) => {
|
||||
const listData = [...userFeedbacks];
|
||||
const dataIdx = userFeedbacks.findIndex((o) => o.feedbackId === id);
|
||||
|
||||
|
||||
console.log("idx :", dataIdx);
|
||||
console.log("Before :", listData);
|
||||
|
||||
|
||||
if (dataIdx !== -1) {
|
||||
console.log("update");
|
||||
listData[dataIdx].score = score;
|
||||
|
|
@ -74,10 +73,10 @@ const FeedbackForm: React.FC = () => {
|
|||
feedbackId: id,
|
||||
score,
|
||||
};
|
||||
|
||||
|
||||
listData.push(data);
|
||||
}
|
||||
|
||||
|
||||
setUserFeedbacks(listData);
|
||||
console.log("After :", userFeedbacks);
|
||||
};
|
||||
|
|
@ -126,7 +125,7 @@ const FeedbackForm: React.FC = () => {
|
|||
<Rating label="Silakan berikan rating Anda terkait dengan tampilan website MediaHUB Polri" onRate={(rating) => handleRatingChange("appearance", rating)} />
|
||||
<Rating label="Silakan berikan rating Anda terkait dengan konten MediaHUB Polri" onRate={(rating) => handleRatingChange("content", rating)} />
|
||||
<div className="flex justify-center">
|
||||
<button onClick={handleSubmit} className="w-fit lg:w-32 bg-[#2F80ED] text-white py-2 px-4 gap-4 rounded-md hover:bg-blue-600 transition">
|
||||
<button onClick={handleSubmit} className="w-fit lg:w-32 bg-[#2F80ED] text-white py-2 px-4 gap-4 rounded-md hover:bg-blue-600 transition text-sm lg:text-base">
|
||||
Kirim
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,15 +7,23 @@ import { Icon } from "@iconify/react/dist/iconify.js";
|
|||
import NewContent from "@/components/landing-page/new-content";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
import { close, error, loading, successCallback } from "@/config/swal";
|
||||
import { checkWishlistStatus, deleteWishlist, getDetail, saveWishlist } from "@/service/landing/landing";
|
||||
import { close, error, loading, successCallback, warning } from "@/config/swal";
|
||||
import { checkWishlistStatus, createPublicSuggestion, deletePublicSuggestion, deleteWishlist, getDetail, getPublicSuggestionList, saveWishlist } from "@/service/landing/landing";
|
||||
import { Link, useRouter } from "@/i18n/routing";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { sendMediaUploadToEmail } from "@/service/media-tracking/media-tracking";
|
||||
import { checkMaliciousText, getPublicLocaleTimestamp } from "@/utils/globals";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import Swal from "sweetalert2";
|
||||
import parse from "html-react-parser";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Image from "next/image";
|
||||
|
||||
const DetailInfo = () => {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const [selectedSize, setSelectedSize] = useState<string>("L");
|
||||
const [selectedTab, setSelectedTab] = useState("video");
|
||||
const router = useRouter();
|
||||
|
|
@ -39,11 +47,22 @@ const DetailInfo = () => {
|
|||
const [emailShareInput, setEmailShareInput] = useState<any>();
|
||||
const [emailMessageInput, setEmailMessageInput] = useState();
|
||||
const searchParams = useSearchParams();
|
||||
const id = searchParams?.get("id");
|
||||
const [width, setWidth] = useState<any>();
|
||||
const userRoleId = getCookiesDecrypt("urie");
|
||||
const [message, setMessage] = useState("");
|
||||
const [listSuggestion, setListSuggestion] = useState<any>();
|
||||
const [isLoading, setIsLoading] = useState<any>(true);
|
||||
const [visibleInput, setVisibleInput] = useState(null);
|
||||
let typeString = "image";
|
||||
const t = useTranslations("LandingPage");
|
||||
|
||||
let typeString = "video";
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 3000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
initFetch();
|
||||
|
|
@ -53,9 +72,12 @@ const DetailInfo = () => {
|
|||
const initFetch = async () => {
|
||||
const response = await getDetail(String(slug));
|
||||
console.log("detailImage", response);
|
||||
const responseGet = await getPublicSuggestionList(slug?.split("-")?.[0]);
|
||||
|
||||
setIsFromSPIT(response?.data?.data?.isFromSPIT);
|
||||
setWidth(window.innerWidth);
|
||||
setContent(response?.data.data);
|
||||
setListSuggestion(responseGet.data?.data);
|
||||
setMain({
|
||||
id: response?.data?.data?.files[0]?.id,
|
||||
type: response?.data?.data?.fileType.name,
|
||||
|
|
@ -255,7 +277,7 @@ const DetailInfo = () => {
|
|||
router.push("/auth/login");
|
||||
} else {
|
||||
const data = {
|
||||
mediaUploadId: id?.split("-")?.[0],
|
||||
mediaUploadId: slug?.split("-")?.[0],
|
||||
email: emailShareList || [emailShareInput],
|
||||
message: emailMessageInput,
|
||||
url: window.location.href,
|
||||
|
|
@ -287,6 +309,129 @@ const DetailInfo = () => {
|
|||
return false;
|
||||
};
|
||||
|
||||
function addDefaultProfile(ev: any) {
|
||||
ev.target.src = "/assets/avatar-profile.png";
|
||||
}
|
||||
|
||||
const showInput = (e: any) => {
|
||||
console.log(document.querySelector(`#${e}`)?.classList);
|
||||
document.querySelector(`#${e}`)?.classList.toggle("none");
|
||||
setVisibleInput(visibleInput === e ? null : e);
|
||||
};
|
||||
|
||||
const getInputValue = (e: any) => {
|
||||
const message = e.target.value;
|
||||
console.log(message);
|
||||
setMessage(message);
|
||||
};
|
||||
|
||||
async function sendSuggestionParent() {
|
||||
if (message?.length > 3) {
|
||||
loading();
|
||||
const data = {
|
||||
mediaUploadId: slug?.split("-")?.[0],
|
||||
message,
|
||||
parentId: null,
|
||||
};
|
||||
|
||||
const response = await createPublicSuggestion(data);
|
||||
|
||||
console.log(response);
|
||||
setMessage("");
|
||||
|
||||
const responseGet = await getPublicSuggestionList(slug?.split("-")?.[0]);
|
||||
console.log(responseGet?.data?.data);
|
||||
setListSuggestion(responseGet?.data?.data);
|
||||
|
||||
// Hapus nilai semua input secara manual jika perlu
|
||||
const inputs = document.querySelectorAll("input");
|
||||
inputs.forEach((input) => {
|
||||
input.value = "";
|
||||
});
|
||||
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
async function sendSuggestionChild(parentId: any) {
|
||||
const inputElement = document.querySelector(`#input-comment-${parentId}`) as HTMLInputElement;
|
||||
|
||||
if (inputElement && inputElement.value.length > 3) {
|
||||
loading();
|
||||
const data = {
|
||||
mediaUploadId: slug?.split("-")?.[0],
|
||||
message: inputElement.value,
|
||||
parentId,
|
||||
};
|
||||
|
||||
console.log(data);
|
||||
const response = await createPublicSuggestion(data);
|
||||
console.log(response);
|
||||
const responseGet: any = await getPublicSuggestionList(slug?.split("-")?.[0]);
|
||||
console.log(responseGet.data?.data);
|
||||
setListSuggestion(responseGet.data?.data);
|
||||
|
||||
// Reset input field
|
||||
inputElement.value = "";
|
||||
|
||||
// document.querySelector("#comment-id-" + parentId)?.style.display = "none";
|
||||
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
const postData = () => {
|
||||
const checkMessage = checkMaliciousText(message);
|
||||
if (checkMessage == "") {
|
||||
if (Number(userRoleId) < 1 || userRoleId == undefined) {
|
||||
router.push("/auth");
|
||||
} else {
|
||||
sendSuggestionParent();
|
||||
}
|
||||
} else {
|
||||
warning(checkMessage);
|
||||
}
|
||||
};
|
||||
|
||||
const postDataChild = (id: any) => {
|
||||
const checkMessage = checkMaliciousText(message);
|
||||
if (checkMessage == "") {
|
||||
if (Number(userRoleId) < 1 || userRoleId == undefined) {
|
||||
router.push("/auth");
|
||||
} else {
|
||||
sendSuggestionChild(id);
|
||||
}
|
||||
} else {
|
||||
warning(checkMessage);
|
||||
}
|
||||
};
|
||||
|
||||
async function deleteDataSuggestion(dataId: any) {
|
||||
loading();
|
||||
const response = await deletePublicSuggestion(dataId);
|
||||
console.log(response);
|
||||
const responseGet = await getPublicSuggestionList(slug.split("-")?.[0]);
|
||||
console.log(responseGet.data?.data);
|
||||
setListSuggestion(responseGet.data?.data);
|
||||
close();
|
||||
}
|
||||
|
||||
const deleteData = (dataId: any) => {
|
||||
MySwal.fire({
|
||||
title: "Delete Comment",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Delete",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
deleteDataSuggestion(dataId);
|
||||
console.log(dataId);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="min-h-screen px-4 md:px-24 py-4">
|
||||
|
|
@ -294,34 +439,61 @@ const DetailInfo = () => {
|
|||
{/* Bagian Kiri */}
|
||||
<div className="md:w-3/4">
|
||||
{/* Gambar Besar */}
|
||||
<div className="relative">
|
||||
<img src={detailDataImage?.files[selectedImage]?.url} alt="Main" className="rounded-lg w-auto h-fit" />
|
||||
<div className="absolute top-4 left-4"></div>
|
||||
</div>
|
||||
{isLoading ? (
|
||||
<div className="relative">
|
||||
<Skeleton className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-[900px]" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative">
|
||||
<Image width={2560} height={1440} src={detailDataImage?.files[selectedImage]?.url} alt="Main" className="rounded-lg w-auto h-fit" />
|
||||
<div className="absolute top-4 left-4"></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Gambar bawah Kecil */}
|
||||
<div className="py-4 flex flex-row gap-3">
|
||||
{detailDataImage?.files?.map((file: any, index: number) => (
|
||||
<a onClick={() => setSelectedImage(index)} key={file?.id}>
|
||||
<img src={file?.url} className="w-[120px] h-[90px] object-cover rounded-md cursor-pointer hover:ring-2 hover:ring-red-600" />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
{isLoading ? (
|
||||
<div className="py-4 flex flex-row gap-3">
|
||||
<Skeleton className="rounded-lg w-[120px] h-[90px]" />
|
||||
<Skeleton className="rounded-lg w-[120px] h-[90px]" />
|
||||
<Skeleton className="rounded-lg w-[120px] h-[90px]" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-4 flex flex-row gap-3">
|
||||
{detailDataImage?.files?.map((file: any, index: number) => (
|
||||
<a onClick={() => setSelectedImage(index)} key={file?.id}>
|
||||
<Image width={1920} height={1080} alt="image-small" src={file?.url} className="w-[120px] h-[90px] object-cover rounded-md cursor-pointer hover:ring-2 hover:ring-red-600" />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Footer Informasi */}
|
||||
<div className="text-sm text-gray-500 flex justify-between items-center border-t mt-4">
|
||||
<div className="flex flex-row items-center mt-3 justify-between">
|
||||
oleh <span className="font-semibold text-black">{detailDataImage?.uploadedBy?.userLevel?.name}</span> | Diupdate pada {detailDataImage?.updatedAt} WIB |
|
||||
<Icon icon="formkit:eye" width="15" height="15" />
|
||||
{detailDataImage?.clickCount}
|
||||
<p className="flex text-end">Kreator: {detailDataImage?.creatorName}</p>
|
||||
<div className="text-gray-500 flex flex-col lg:flex-row justify-between items-center border-t mt-4">
|
||||
<div className="flex flex-col lg:flex-row items-center mt-3 lg:justify-between">
|
||||
<p className="text-xs lg:text-sm">
|
||||
{t("by")} <span className="font-semibold text-black">{detailDataImage?.uploadedBy?.userLevel?.name}</span>
|
||||
</p>
|
||||
<p className="text-xs lg:text-sm">
|
||||
| {t("updatedOn")}
|
||||
{detailDataImage?.updatedAt} WIB |
|
||||
</p>
|
||||
<p className="text-xs lg:text-sm flex justify-center items-center">
|
||||
<Icon icon="formkit:eye" width="15" height="15" />
|
||||
{detailDataImage?.clickCount}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<p className="flex text-end text-xs lg:text-sm font-semibold">
|
||||
{t("creator")}
|
||||
{detailDataImage?.creatorName}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Keterangan */}
|
||||
<div className="w-full">
|
||||
<h1 className="flex flex-row font-bold text-2xl my-8">{detailDataImage?.title}</h1>
|
||||
<div className="font-light text-justify" dangerouslySetInnerHTML={{ __html: detailDataImage?.htmlDescription }} />
|
||||
<h1 className="flex flex-row font-bold text-lg lg:text-2xl my-8">{detailDataImage?.title}</h1>
|
||||
<div className="font-light text-justify mb-5 lg:mb-0" dangerouslySetInnerHTML={{ __html: detailDataImage?.htmlDescription }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -330,12 +502,12 @@ const DetailInfo = () => {
|
|||
{isSaved ? (
|
||||
<a onClick={() => handleDeleteWishlist()} className="flex flex-col mb-3 items-center justify-center cursor-pointer">
|
||||
<Icon icon="material-symbols:bookmark" width={40} />
|
||||
<p className="text-base lg:text-lg">Hapus</p>
|
||||
<p className="text-base lg:text-lg">{t("delete")}</p>
|
||||
</a>
|
||||
) : (
|
||||
<a onClick={() => doBookmark()} className="flex flex-col mb-3 items-center justify-center cursor-pointer">
|
||||
<Icon icon="material-symbols:bookmark-outline" width={40} />
|
||||
<p className="text-base lg:text-lg">Simpan</p>
|
||||
<p className="text-base lg:text-lg">{t("save")}</p>
|
||||
</a>
|
||||
)}
|
||||
|
||||
|
|
@ -357,7 +529,7 @@ const DetailInfo = () => {
|
|||
<div className="border-t border-black my-4"></div>
|
||||
|
||||
{/* Opsi Ukuran Foto */}
|
||||
<h4 className="flex text-lg justify-center items-center font-semibold my-3">Opsi Ukuran Foto</h4>
|
||||
<h4 className="flex text-lg justify-center items-center font-semibold my-3">{t("imageSize")}</h4>
|
||||
<div className="border-t border-black my-4"></div>
|
||||
|
||||
<div className="space-y-2">
|
||||
|
|
@ -378,7 +550,7 @@ const DetailInfo = () => {
|
|||
<div className="mt-4">
|
||||
<label className="flex items-center space-x-2 text-sm">
|
||||
<input type="checkbox" className="text-red-600 focus:ring-red-600" onChange={() => setIsDownloadAll(!isDownloadAll)} />
|
||||
<span>Download Semua File?</span>
|
||||
<span>{t("downloadAll")}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
|
@ -387,12 +559,12 @@ const DetailInfo = () => {
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||
<path fill="white" d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z" />
|
||||
</svg>
|
||||
Download
|
||||
{t("download")}
|
||||
</button>
|
||||
|
||||
{/* Tombol Bagikan */}
|
||||
<div className="flex flex-row py-3">
|
||||
<p className="text-base font-semibold">Bagikan</p>
|
||||
<div className="flex flex-row mt-5 justify-center">
|
||||
<p className="text-base font-semibold">{t("share")}</p>
|
||||
<a className="ml-8 cursor-pointer" onClick={() => handleShare("fb", `https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}"e=${content?.title}`)}>
|
||||
<Icon icon="brandico:facebook" height="20" className="px-auto text-red-600 text-center" />
|
||||
</a>
|
||||
|
|
@ -410,13 +582,13 @@ const DetailInfo = () => {
|
|||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<div className="flex flex-col">
|
||||
<h1 className="mb-2">Share Ke Email</h1>
|
||||
<h1 className="mb-2">{t("shareTo")}</h1>
|
||||
<div className="flex flex-col mb-2">
|
||||
<p className="text-base font-semibold mb-1">Email Tujuan :</p>
|
||||
<Input value={emailShareInput} onChange={(event) => setEmailShareInput(event.target.value)} onKeyPress={handleEmailList} type="email" placeholder="Tekan Enter untuk input Email" />
|
||||
<p className="text-base font-semibold mb-1">{t("destinationEmail")}</p>
|
||||
<Input value={emailShareInput} onChange={(event) => setEmailShareInput(event.target.value)} onKeyPress={handleEmailList} type="email" placeholder={t("pressEnter")} />
|
||||
</div>
|
||||
<Button className="bg-blue-500 text-white p-2 w-fit rounded-lg" onClick={() => shareToEmail()}>
|
||||
Kirim
|
||||
{t("send")}
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
|
|
@ -428,11 +600,182 @@ const DetailInfo = () => {
|
|||
|
||||
<div className="w-full mb-8">
|
||||
{/* Comment */}
|
||||
<div className="flex flex-col my-16 p-10 bg-[#f7f7f7]">
|
||||
<div className="flex flex-col my-16 p-4 lg:p-10 bg-[#f7f7f7]">
|
||||
<div className="gap-5 flex flex-col px-4 lg:px-14">
|
||||
<p className="flex items-start text-lg">Berikan Komentar</p>
|
||||
<Textarea placeholder="Type your comments here." className="flex w-full" />
|
||||
<button className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-4 py-1">Kirim</button>
|
||||
<p className="flex items-start text-lg">{t("comment")}</p>
|
||||
<Textarea placeholder={t("leaveComment")} className="flex w-full pb-12" onChange={getInputValue} />
|
||||
<button onClick={() => postData()} className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-4 py-1">
|
||||
{t("send")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="border-b-2 border-slate-300 mt-4 w-auto"></div>
|
||||
|
||||
<div>
|
||||
{listSuggestion?.map((data: any) => (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row mt-2 px-4 lg:px-14">
|
||||
<Image width={1080} height={1080} src={data?.suggestionFrom?.profilePictureUrl} className="h-12 lg:h-16 w-12 lg:w-16 mr-2" onError={addDefaultProfile} alt="" />
|
||||
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
|
||||
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
|
||||
{Number(data.suggestionFrom?.roleId) == 2 || Number(data.suggestionFrom?.roleId) == 3 || Number(data.suggestionFrom?.roleId) == 4 ? "HUMAS POLRI" : data.suggestionFrom?.fullname}
|
||||
{getPublicLocaleTimestamp(new Date(data.createdAt))}
|
||||
</p>
|
||||
<p className="text-slate-500 text-[13px] lg:text-sm mb-4">{data?.message}</p>
|
||||
<div>
|
||||
<a
|
||||
style={
|
||||
Number(data.suggestionFrom?.id) == Number(userId)
|
||||
? {
|
||||
display: "none",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
onClick={() => showInput(`comment-id-${data.id}`)}
|
||||
className="mr-2"
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("reply")}</small>
|
||||
</a>
|
||||
{Number(data.suggestionFrom?.id) == Number(userId) || Number(userRoleId) == 2 ? (
|
||||
<a onClick={() => deleteData(data.id)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("delete")}</small>
|
||||
</a>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{visibleInput === `comment-id-${data.id}` && (
|
||||
<div id={`comment-id-${data.id}`} className="px-4 pl-[72px] lg:px-14 lg:pl-32 mt-2 ">
|
||||
<Textarea id={`input-comment-${data.id}`} className="p-4 focus:outline-none focus:border-sky-500" placeholder={t("enterReply")} />
|
||||
<div className="flex flex-row gap-3">
|
||||
<a onClick={() => postDataChild(data.id)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 mt-2 cursor-pointer">{t("send")}</small>
|
||||
</a>
|
||||
<a onClick={() => showInput(`comment-id-${data.id}`)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg mt-2 w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("cancel")}</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data.children.length > 0
|
||||
? data.children?.map((child1: any) => (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-16 lg:pl-32">
|
||||
<Image width={1080} height={1080} src={child1.suggestionFrom?.profilePictureUrl} onError={addDefaultProfile} alt="" className="h-10 lg:h-16 w-10 lg:w-16 mr-2" />
|
||||
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
|
||||
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
|
||||
{" "}
|
||||
<b>{Number(child1.suggestionFrom?.roleId) == 2 || Number(child1.suggestionFrom?.roleId) == 3 || Number(child1.suggestionFrom?.roleId) == 4 ? "HUMAS POLRI" : child1.suggestionFrom?.fullname}</b>{" "}
|
||||
{getPublicLocaleTimestamp(new Date(child1.createdAt))}
|
||||
</p>
|
||||
<p className="text-slate-500 text-[13px] lg:text-sm mb-4">{parse(String(child1?.message))}</p>
|
||||
<div>
|
||||
<a
|
||||
style={
|
||||
Number(child1.suggestionFrom?.id) == Number(userId)
|
||||
? {
|
||||
display: "none",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
onClick={() => showInput(`comment-id-${child1.id}`)}
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("reply")}</small>
|
||||
</a>
|
||||
<a
|
||||
style={
|
||||
Number(child1.suggestionFrom?.id) == Number(userId)
|
||||
? {}
|
||||
: {
|
||||
display: "none",
|
||||
}
|
||||
}
|
||||
onClick={() => deleteData(child1.id)}
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("delete")}</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{visibleInput === `comment-id-${child1.id}` && (
|
||||
<div id={`comment-id-${child1.id}`} className="px-4 lg:px-14 pl-28 lg:pl-[200px]">
|
||||
<Textarea name="" className="mt-2 " id={`input-comment-${child1.id}`} placeholder={t("enterReply")} />
|
||||
<div className="flex flex-row mt-2 gap-3">
|
||||
<a onClick={() => postDataChild(child1.id)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("send")}</small>
|
||||
</a>
|
||||
<a onClick={() => showInput(`comment-id-${child1.id}`)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("cancel")}</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{child1.children.length > 0
|
||||
? child1.children?.map((child2: any) => (
|
||||
<div className="">
|
||||
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-28 lg:pl-48">
|
||||
<Image width={1080} height={1080} src={child2.suggestionFrom?.profilePictureUrl} className="h-9 lg:h-16 w-9 lg:w-16 mr-2" onError={addDefaultProfile} alt="" />
|
||||
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
|
||||
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
|
||||
{" "}
|
||||
<b>{Number(child2.suggestionFrom?.roleId) == 2 || Number(child2.suggestionFrom?.roleId) == 3 || Number(child2.suggestionFrom?.roleId) == 4 ? "HUMAS POLRI" : child2.suggestionFrom?.fullname}</b>{" "}
|
||||
{getPublicLocaleTimestamp(new Date(child2.createdAt))}
|
||||
</p>
|
||||
<p className="text-slate-500 text-sm mb-4">{parse(String(child2?.message))}</p>
|
||||
<div>
|
||||
<a
|
||||
style={
|
||||
Number(child2.suggestionFrom?.id) == Number(userId)
|
||||
? {
|
||||
display: "none",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
onClick={() => showInput(`comment-id-${child2.id}`)}
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("reply")}</small>
|
||||
</a>
|
||||
<a
|
||||
style={
|
||||
Number(child2.suggestionFrom?.id) == Number(userId)
|
||||
? {}
|
||||
: {
|
||||
display: "none",
|
||||
}
|
||||
}
|
||||
onClick={() => deleteData(child2.id)}
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("delete")}</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{visibleInput === `comment-id-${child2.id}` && (
|
||||
<div id={`comment-id-${child2.id}`} className="px-4 lg:px-14 pl-40 lg:pl-[265px]">
|
||||
<Textarea name="" id={`input-comment-${child2.id}`} className="my-2" placeholder="Masukkan balasan anda" />
|
||||
<div className="flex flex-row gap-3">
|
||||
<a onClick={() => postDataChild(child2.id)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("send")}</small>
|
||||
</a>
|
||||
<a onClick={() => showInput(`comment-id-${child2.id}`)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("cancel")}</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
: ""}
|
||||
</div>
|
||||
))
|
||||
: ""}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ import "react-datepicker/dist/react-datepicker.css";
|
|||
import { close, loading } from "@/config/swal";
|
||||
import { useTranslations } from "next-intl";
|
||||
import ImageBlurry from "@/components/ui/image-blurry";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import Image from "next/image";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
|
|
@ -31,6 +33,7 @@ const FilterPage = () => {
|
|||
const params = useParams();
|
||||
const searchParams = useSearchParams();
|
||||
const locale = params?.locale;
|
||||
const [isLoading, setIsLoading] = useState<any>(true);
|
||||
const [imageData, setImageData] = useState<any>();
|
||||
const [totalData, setTotalData] = React.useState<number>(1);
|
||||
const [totalPage, setTotalPage] = React.useState<number>(1);
|
||||
|
|
@ -73,6 +76,14 @@ const FilterPage = () => {
|
|||
|
||||
// const [startDate, endDate] = dateRange;
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 3000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
const pageFromUrl = searchParams?.get("page");
|
||||
if (pageFromUrl) {
|
||||
|
|
@ -371,7 +382,7 @@ const FilterPage = () => {
|
|||
|
||||
{/* Left */}
|
||||
<div className="flex flex-col lg:flex-row gap-6 p-4">
|
||||
<div className="lg:w-96 h-fit w-full bg-[#f7f7f7] dark:bg-black p-4 rounded-lg shadow-md">
|
||||
<div className="lg:w-[30%] h-fit w-full bg-[#f7f7f7] dark:bg-black p-4 rounded-lg shadow-md">
|
||||
<h2 className="text-lg font-semibold mb-4 flex items-center gap-1">
|
||||
<Icon icon="stash:filter-light" fontSize={30} />
|
||||
Filter
|
||||
|
|
@ -472,7 +483,7 @@ const FilterPage = () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Konten Kanan */}
|
||||
{/* Right */}
|
||||
<div className="w-full">
|
||||
<Reveal>
|
||||
<div className="w-full">
|
||||
|
|
@ -484,38 +495,54 @@ const FilterPage = () => {
|
|||
</select>
|
||||
</div>
|
||||
|
||||
{imageData?.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{imageData?.map((image: any) => (
|
||||
<Card key={image?.id} className="hover:scale-105 transition-transform duration-300">
|
||||
<CardContent className="flex flex-col text-xs lg:text-sm w-full p-0">
|
||||
<Link href={`/image/detail/${image?.slug}`}>
|
||||
{/* <img src={image?.thumbnailLink} className="h-60 object-cover items-center justify-center cursor-pointer rounded-lg" /> */}
|
||||
<div className="img-container h-60 bg-[#e9e9e9] cursor-pointer rounded-lg">
|
||||
<ImageBlurry src={image?.thumbnailLink} alt={image?.title} style={{ objectFit: "contain", width: "100%", height: "100%" }} />
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-2 text-[10px] mx-2 mt-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>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col lg:flex-row space-y-3 w-full justify-center items-center gap-3">
|
||||
<Skeleton className="h-[200px] w-[350px] rounded-xl" />
|
||||
<Skeleton className="h-[200px] w-[350px] rounded-xl" />
|
||||
<Skeleton className="h-[200px] w-[350px] rounded-xl" />
|
||||
</div>
|
||||
<div className="flex flex-col lg:flex-row space-y-3 w-full justify-center items-center gap-3">
|
||||
<Skeleton className="h-[200px] w-[350px] rounded-xl" />
|
||||
<Skeleton className="h-[200px] w-[350px] rounded-xl" />
|
||||
<Skeleton className="h-[200px] w-[350px] rounded-xl" />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="flex items-center justify-center text-black">
|
||||
<img src="/assets/empty-data.png" alt="empty" className="h-60 w-60 my-4" />
|
||||
</p>
|
||||
<>
|
||||
{imageData?.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{imageData?.map((image: any) => (
|
||||
<Card key={image?.id} className="hover:scale-105 transition-transform duration-300">
|
||||
<CardContent className="flex flex-col text-xs lg:text-sm w-full p-0">
|
||||
<Link href={`/image/detail/${image?.slug}`}>
|
||||
{/* <img src={image?.thumbnailLink} className="h-60 object-cover items-center justify-center cursor-pointer rounded-lg" /> */}
|
||||
<div className="img-container h-60 bg-[#e9e9e9] cursor-pointer rounded-lg">
|
||||
<ImageBlurry src={image?.thumbnailLink} alt={image?.title} style={{ objectFit: "contain", width: "100%", height: "100%" }} />
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-2 text-[10px] mx-2 mt-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>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="flex items-center justify-center text-black">
|
||||
<Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-60 w-60 my-4" />
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{totalData > 1 && <LandingPagination table={table} totalData={totalData} totalPage={totalPage} />}
|
||||
</div>
|
||||
</Reveal>
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ const InboxSection = () => {
|
|||
<div className="flex flex-col justify-center items-center gap-3">
|
||||
<div className="flex justify-center">
|
||||
<div className="flex flex-row gap-10 items-center justify-center">
|
||||
<div>
|
||||
<div className="">
|
||||
<p className="bg-[#bb3523] py-1 px-3 rounded-full">Pesan Masuk</p>
|
||||
</div>
|
||||
<Link href={`/inbox/update`}>Update</Link>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,32 @@
|
|||
"use client";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { useParams, usePathname, useRouter } from "next/navigation";
|
||||
import { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { getDetailIndeks, publicDetailBlog } from "@/service/landing/landing";
|
||||
import { Link, useRouter } from "@/i18n/routing";
|
||||
import { deleteBlogComments, getBlogComments, getDetailIndeks, getPublicSuggestionList, postBlogComments, publicDetailBlog } from "@/service/landing/landing";
|
||||
import { formatDateToIndonesian } from "@/utils/globals";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
import { close, loading } from "@/config/swal";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Image from "next/image";
|
||||
|
||||
const IndeksDetail = () => {
|
||||
const searchParams = useSearchParams();
|
||||
const id = searchParams?.get("id");
|
||||
const [indeksData, setIndeksData] = useState<any>();
|
||||
const params = useParams();
|
||||
const slug = params?.slug;
|
||||
const [relatedPost, setRelatedPost] = useState<any>();
|
||||
const [indexData, setIndexData] = useState<any>();
|
||||
const [message, setMessage] = useState("");
|
||||
const [messageChild, setMessageChild] = useState("");
|
||||
const userId = getCookiesDecrypt("uie");
|
||||
const userRoleId = getCookiesDecrypt("urie");
|
||||
const router: any = useRouter();
|
||||
const [listComments, setListComments] = useState([]);
|
||||
const t = useTranslations("LandingPage");
|
||||
|
||||
useEffect(() => {
|
||||
initFetch();
|
||||
|
|
@ -21,12 +35,121 @@ const IndeksDetail = () => {
|
|||
const initFetch = async () => {
|
||||
const response = await getDetailIndeks();
|
||||
console.log(response);
|
||||
setRelatedPost(response?.data?.data?.content);
|
||||
setIndexData(response?.data?.data?.content);
|
||||
};
|
||||
const detailFetch = async () => {
|
||||
const response = await publicDetailBlog(slug);
|
||||
console.log(response);
|
||||
setIndeksData(response?.data?.data);
|
||||
getDataComment(response?.data?.data?.id);
|
||||
};
|
||||
|
||||
async function getDataComment(id?: any) {
|
||||
const response = await getBlogComments(id || indeksData?.id);
|
||||
console.log(response.data?.data);
|
||||
setListComments(response.data?.data);
|
||||
}
|
||||
|
||||
const showInput = (e: any) => {
|
||||
console.log(document.querySelector(`#${e}`)?.classList);
|
||||
document.querySelector(`#${e}`)?.classList.toggle("hidden");
|
||||
};
|
||||
|
||||
async function sendCommentParent() {
|
||||
if (message?.length > 3) {
|
||||
loading();
|
||||
const data = {
|
||||
blogId: indeksData?.id,
|
||||
message: message,
|
||||
parentId: null,
|
||||
};
|
||||
|
||||
const response = await postBlogComments(data);
|
||||
|
||||
console.log(response);
|
||||
setMessage("");
|
||||
getDataComment();
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
// async function sendCommentChild(parentId: any) {
|
||||
// if (messageChild.length > 3) {
|
||||
// loading();
|
||||
// const data = {
|
||||
// blogId: indeksData?.id,
|
||||
// message: messageChild,
|
||||
// parentId,
|
||||
// };
|
||||
|
||||
// console.log(data);
|
||||
// const response = await postBlogComments(data);
|
||||
// console.log(response);
|
||||
// const responseGet = await getPublicSuggestionList(slug);
|
||||
// console.log(responseGet.data?.data);
|
||||
// setMessageChild("");
|
||||
// // $(":input").val("");
|
||||
// close();
|
||||
// }
|
||||
// }
|
||||
|
||||
async function sendCommentChild(parentId: any) {
|
||||
const inputMsg = document.querySelector(`#input-comment-${parentId}`) as HTMLInputElement;
|
||||
|
||||
if (inputMsg && inputMsg.value.length > 3) {
|
||||
loading();
|
||||
const data = {
|
||||
blogId: indeksData?.id,
|
||||
message: inputMsg.value,
|
||||
parentId,
|
||||
};
|
||||
|
||||
console.log(data);
|
||||
const response = await postBlogComments(data);
|
||||
console.log(response);
|
||||
const responseGet = await getPublicSuggestionList(slug);
|
||||
console.log(responseGet.data?.data);
|
||||
getDataComment();
|
||||
inputMsg.value = "";
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteDataComment(dataId: any) {
|
||||
loading();
|
||||
const response = await deleteBlogComments(dataId);
|
||||
console.log(response);
|
||||
getDataComment();
|
||||
close();
|
||||
}
|
||||
|
||||
const getInputValue = (e: any) => {
|
||||
const message = e.target.value;
|
||||
console.log(message);
|
||||
setMessage(message);
|
||||
getDataComment();
|
||||
};
|
||||
|
||||
const postData = () => {
|
||||
if (Number(userRoleId) < 1) {
|
||||
router.push("/auth");
|
||||
} else {
|
||||
sendCommentParent();
|
||||
}
|
||||
getDataComment();
|
||||
};
|
||||
|
||||
const postDataChild = (id: any) => {
|
||||
if (Number(userRoleId) < 1) {
|
||||
router.push("/auth");
|
||||
} else {
|
||||
sendCommentChild(id);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteData = (dataId: any) => {
|
||||
deleteDataComment(dataId);
|
||||
console.log(dataId);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -34,45 +157,179 @@ const IndeksDetail = () => {
|
|||
<div className="p-4 lg:px-60 lg:p-12">
|
||||
{/* Judul */}
|
||||
<div className="flex flex-col mb-5">
|
||||
<h1 className="text-lg mb-2">Indeks / Detail</h1>
|
||||
<h1 className="flex flex-row font-bold text-center text-2xl">{indeksData?.title}</h1>
|
||||
<h1 className="text-base lg:text-lg mb-2">Index / Detail</h1>
|
||||
<h1 className="flex flex-row font-bold text-center text-lg lg:text-2xl">{indeksData?.title}</h1>
|
||||
</div>
|
||||
{/* Gambar Utama */}
|
||||
<div className="flex items-center justify-center">
|
||||
<img src={indeksData?.thumbnailLink} alt="Main" className="h-[550px] w-full rounded-lg" />
|
||||
<Image width={2560} height={1440} src={indeksData?.thumbnailLink} alt="Main" className="h-fit lg:h-[550px] w-full rounded-lg" />
|
||||
</div>
|
||||
{/* Footer Informasi */}
|
||||
<div className="text-sm text-gray-500 flex justify-between items-center border-t mt-4">
|
||||
<div className="flex flex-row items-center mt-3 justify-between">
|
||||
oleh <span className="font-semibold text-black">{indeksData?.uploaderName}</span> | Diupdate pada {indeksData?.createdAt} WIB
|
||||
<div className="text-gray-500 flex border-t mt-4">
|
||||
<div className="flex mt-2">
|
||||
<p className="text-xs lg:text-sm mb-2">
|
||||
{t("by")} <span className="font-semibold text-black">{indeksData?.uploaderName}</span> | {t("updatedOn")} {indeksData?.createdAt} WIB
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Keterangan */}
|
||||
<div className="w-auto">
|
||||
<p className="font-light text-justify" dangerouslySetInnerHTML={{ __html: indeksData?.description }} />
|
||||
<p className="font-light text-base lg:text-lg text-justify" dangerouslySetInnerHTML={{ __html: indeksData?.description }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Comment */}
|
||||
<div className="w-full">
|
||||
<div className="flex flex-col py-5 p-10 bg-[#f7f7f7]">
|
||||
<div className="flex flex-col py-5 p-0 lg:p-10 bg-[#f7f7f7]">
|
||||
<div className="gap-5 flex flex-col px-4 lg:px-16">
|
||||
<p className="flex items-start text-lg">Berikan Komentar</p>
|
||||
<Textarea placeholder="Type your comments here." className="flex w-full" />
|
||||
<button className="flex items-start bg-[#bb3523] text-white rounded-lg w-fit px-4 py-1">Kirim</button>
|
||||
<p className="flex items-start text-bases lg:text-lg">{t("comment")}</p>
|
||||
<Textarea placeholder="Type your comments here." className="flex w-full" onChange={getInputValue} value={message} />
|
||||
<button className="flex items-start bg-[#bb3523] text-white rounded-lg text-sm lg:text-base w-fit px-3 lg:px-4 py-1" onClick={() => postData()}>
|
||||
{t("send")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="border-b-2 border-slate-300 mt-4 w-auto"></div>
|
||||
|
||||
<div>
|
||||
{listComments?.map((data: any) => (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row mt-2 px-4 lg:px-14">
|
||||
<Image width={512} height={512} className="h-10 lg:h-20 w-10 lg:w-20" src="/assets/img/user-avatar-yellow.svg" alt="#" />
|
||||
<div className="border border-slate-300 w-full p-4 bg-white gap-1">
|
||||
<p className="flex justify-between text-sm text-slate-500 lg:text-base border-b-2 border-slate-200 mb-2">
|
||||
<b>{Number(data.commentFrom?.roleId) == 2 || Number(data.commentFrom?.roleId) == 3 || Number(data.commentFrom?.roleId) == 4 ? "HUMAS POLRI" : data.commentFrom?.fullname}</b>
|
||||
{`${new Date(data.createdAt).getDate()}/${new Date(data.createdAt).getMonth() + 1}/${new Date(data.createdAt).getFullYear()} ${new Date(data.createdAt).getHours()}:${new Date(data.createdAt).getMinutes()}`}
|
||||
</p>
|
||||
<p className="text-slate-500 text-sm lg:text-base mb-4">{data.message}</p>
|
||||
<div className="gap-3">
|
||||
<a href="javascript:void(0)" className="text-xs lg:text-sm mr-2 bg-blue-500 text-white py-1 px-2 hover:bg-blue-300 hover:text-black rounded-md" onClick={() => showInput(`comment-id-${data.id}`)}>
|
||||
{t("reply")}
|
||||
</a>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
style={
|
||||
data.commentFrom?.id == userId
|
||||
? {}
|
||||
: {
|
||||
display: "none",
|
||||
}
|
||||
}
|
||||
className="text-xs lg:text-sm bg-red-500 text-white py-1 px-2 hover:bg-red-300 hover:text-black rounded-md"
|
||||
onClick={() => deleteData(data.id)}
|
||||
>
|
||||
{t("delete")}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row px-4 pl-[55px] lg:px-14 lg:pl-[135px] mt-2" id={`comment-id-${data.id}`}>
|
||||
<Input type="text" id={`input-comment-${data.id}`} className="p-4 focus:outline-none focus:border-sky-500" placeholder={t("enterReply")} />
|
||||
<a href="javascript:void(0)" className="flex py-1 px-2 rounded-md items-center ml-2 bg-[#f7b357] text-white text-sm lg:text-base" onClick={() => postDataChild(data.id)}>
|
||||
{t("send")}
|
||||
</a>
|
||||
</div>
|
||||
{data.children.length > 0
|
||||
? data.children?.map((child1: any) => (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-12 lg:pl-32">
|
||||
<Image width={512} height={512} className="h-10 lg:h-20 w-10 lg:w-20" src="/assets/img/user-avatar-yellow.svg" alt="#" />
|
||||
<div className="border border-slate-300 w-full p-4 bg-white gap-1">
|
||||
<p className="flex justify-between text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
|
||||
<b>{Number(child1.commentFrom?.roleId) == 2 || Number(child1.commentFrom?.roleId) == 3 || Number(child1.commentFrom?.roleId) == 4 ? "HUMAS POLRI" : child1.commentFrom?.fullname}</b>
|
||||
{`${new Date(child1.createdAt).getDate()}/${new Date(child1.createdAt).getMonth() + 1}/${new Date(child1.createdAt).getFullYear()} ${new Date(child1.createdAt).getHours()}:${new Date(
|
||||
child1.createdAt
|
||||
).getMinutes()}`}
|
||||
</p>
|
||||
<p className="text-slate-500 text-sm mb-4">{child1.message}</p>
|
||||
<div className="gap-3">
|
||||
<a href="javascript:void(0)" className="mr-2 text-xs lg:text-sm bg-blue-500 text-white py-1 px-2 hover:bg-blue-300 hover:text-black rounded-md" onClick={() => showInput(`comment-id-${child1.id}`)}>
|
||||
{t("reply")}
|
||||
</a>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
className="text-xs lg:text-sm bg-red-500 text-white py-1 px-2 hover:bg-red-300 hover:text-black rounded-md"
|
||||
style={
|
||||
Number(child1.commentFrom?.id) == Number(userId)
|
||||
? {}
|
||||
: {
|
||||
display: "none",
|
||||
}
|
||||
}
|
||||
onClick={() => deleteData(child1.id)}
|
||||
>
|
||||
{t("delete")}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-center px-4 pl-[87px] lg:px-14 lg:pl-[205px] mt-2" id={`comment-id-${child1.id}`}>
|
||||
<Input type="text" id={`input-comment-${child1.id}`} className="p-4 focus:outline-none focus:border-sky-500" placeholder={t("enterReply")} />
|
||||
<a href="javascript:void(0)" className="flex text-sm lg:text-base py-1 px-2 rounded-md items-center ml-2 bg-[#f7b357] text-white" onClick={() => postDataChild(child1.id)}>
|
||||
{t("send")}
|
||||
</a>
|
||||
</div>
|
||||
{child1.children.length > 0
|
||||
? child1.children?.map((child2: any) => (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-20 lg:pl-48">
|
||||
<Image width={512} height={512} className="h-10 lg:h-20 w-10 lg:w-20" src="/assets/img/user-avatar-yellow.svg" alt="#" />
|
||||
<div className="border border-slate-300 w-full p-4 bg-white gap-2">
|
||||
<p className="flex justify-between text-slate-500 text-xs lg:text-base border-b-2 border-slate-200 mb-2">
|
||||
<b>{Number(child2.commentFrom?.roleId) == 2 || Number(child2.commentFrom?.roleId) == 3 || Number(child2.commentFrom?.roleId) == 4 ? "HUMAS POLRI" : child2.commentFrom?.fullname}</b>
|
||||
{`${new Date(child2.createdAt).getDate()}/${new Date(child2.createdAt).getMonth() + 1}/${new Date(child2.createdAt).getFullYear()} ${new Date(child2.createdAt).getHours()}:${new Date(
|
||||
child2.createdAt
|
||||
).getMinutes()}`}
|
||||
</p>
|
||||
<p className="text-slate-500 text-sm mb-4">{child2.message}</p>
|
||||
<div>
|
||||
<a href="javascript:void(0)" className="mr-2 text-xs lg:text-sm bg-blue-500 text-white py-1 px-2 hover:bg-blue-300 hover:text-black rounded-md" onClick={() => showInput("comment-id-" + child2.id)}>
|
||||
{t("reply")}
|
||||
</a>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
className="text-xs lg:text-sm bg-red-500 text-white py-1 px-2 hover:bg-red-300 hover:text-black rounded-md"
|
||||
style={
|
||||
Number(child2.commentFrom?.id) == Number(userId)
|
||||
? {}
|
||||
: {
|
||||
display: "none",
|
||||
}
|
||||
}
|
||||
onClick={() => deleteData(child2.id)}
|
||||
>
|
||||
{t("delete")}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row px-4 pl-[120px] lg:px-14 lg:pl-[270px] mt-2" id={`comment-id-${child2.id}`}>
|
||||
<Input type="text" id={`comment-id-${child2.id}`} className="p-4 focus:outline-none focus:border-sky-500" placeholder={t("enterReply")} />
|
||||
<a href="javascript:void(0)" className="flex text-sm lg:text-base py-1 px-2 rounded-md items-center ml-2 bg-[#f7b357] text-white" onClick={() => postDataChild(child1.id)}>
|
||||
{t("send")}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
: ""}
|
||||
</div>
|
||||
))
|
||||
: ""}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Konten Serupa */}
|
||||
<div className="space-x-5 flex flex-col px-4 lg:px-16 py-16 gap-5">
|
||||
<h1 className="font-bold text-base lg:text-xl px-4 lg:px-8">Post Terkait</h1>
|
||||
<h1 className="font-bold text-base lg:text-xl px-4 lg:px-8"> {t("relatedPosts")}</h1>
|
||||
<Carousel>
|
||||
<CarouselContent className="w-full max-w-7xl">
|
||||
{relatedPost?.map((relate: any) => (
|
||||
{indexData?.map((relate: any) => (
|
||||
<CarouselItem key={relate?.id} className="md:basis-1/2 lg:basis-1/3">
|
||||
<Link href={`/indeks/detail/${relate?.slug}`} className="relative group overflow-hidden shadow-md hover:shadow-lg">
|
||||
<img src={relate?.thumbnailLink} className="w-full rounded-lg h-40 lg:h-60 object-cover group-hover:scale-100 transition-transform duration-300" />
|
||||
<Image alt="" width={2560} height={1440} src={relate?.thumbnailLink} className="w-full rounded-lg h-40 lg:h-60 object-cover group-hover:scale-100 transition-transform duration-300" />
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-gray-600 border-l-4 border-[#bb3523] rounded-lg backdrop-blur-sm text-white p-2">
|
||||
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-sm px-4 py-1">{relate?.categoryName}</span>
|
||||
<h1 className="text-sm lg:text-lg mb-2 font-semibold h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">{relate?.title}</h1>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
"use client";
|
||||
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { getIndeksData } from "@/service/landing/landing";
|
||||
import { formatDateToIndonesian } from "@/utils/globals";
|
||||
import Image from "next/image";
|
||||
import { usePathname } from "next/navigation";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
const Indeks: React.FC = () => {
|
||||
const pathname = usePathname();
|
||||
const [isLoading, setIsLoading] = useState<any>(true);
|
||||
const [indeksData, setIndeksData] = useState<any>();
|
||||
let count: number = 0;
|
||||
useEffect(() => {
|
||||
|
|
@ -20,6 +23,14 @@ const Indeks: React.FC = () => {
|
|||
}
|
||||
}, [indeksData]);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 3000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
initFetch();
|
||||
}, []);
|
||||
|
|
@ -33,81 +44,124 @@ const Indeks: React.FC = () => {
|
|||
<div className="px-4 lg:px-14">
|
||||
{/* Hero Left */}
|
||||
<div className="flex flex-col lg:flex-row items-start gap-8 px-4 lg:px-10 py-4 mx-auto">
|
||||
<div className="lg:w-[60%] w-full lg:h-full">
|
||||
{indeksData?.map(
|
||||
(indeks: any, index: number) =>
|
||||
index == count && (
|
||||
<div key={indeks?.id} className="relative h-[310px] lg:h-[435px]">
|
||||
<img src={indeks?.thumbnailLink} alt="image" className="w-full h-[310px] lg:h-[435px] rounded-lg" />
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-transparent backdrop-blur-sm text-white p-4 rounded-b-lg">
|
||||
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-sm px-4 py-1">{indeks?.categoryName}</span>
|
||||
<Link href={`/indeks/detail/${indeks?.slug}`}>
|
||||
<h2 className="text-2xl font-bold mt-2">{indeks?.title}</h2>
|
||||
</Link>
|
||||
<p className="text-xs flex flex-row items-center gap-1 mt-1 ml-2">
|
||||
{formatDateToIndonesian(new Date(indeks?.createdAt))} {indeks?.timezone ? indeks?.timezone : "WIB"}|{" "}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
|
||||
/>
|
||||
</svg>{" "}
|
||||
{indeks?.clickCount}
|
||||
</p>
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col space-y-3 mx-auto w-full lg:w-2/3">
|
||||
<Skeleton className="h-[310px] lg:h-[420px] rounded-xl" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="lg:w-[60%] w-full lg:h-full">
|
||||
{indeksData?.map(
|
||||
(indeks: any, index: number) =>
|
||||
index == count && (
|
||||
<div key={indeks?.id} className="relative h-[310px] lg:h-[435px]">
|
||||
<Image width={2560} height={1440} src={indeks?.thumbnailLink} alt="image" className="w-full h-[310px] lg:h-[435px] rounded-lg object-cover" />
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-transparent backdrop-blur-sm text-white p-4 rounded-b-lg">
|
||||
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-xs px-2 py-1">{indeks?.categoryName}</span>
|
||||
<Link href={`/indeks/detail/${indeks?.slug}`}>
|
||||
<h2 className="text-2xl font-bold mt-2">{indeks?.title}</h2>
|
||||
</Link>
|
||||
<p className="text-xs flex flex-row items-center gap-1 mt-1">
|
||||
{formatDateToIndonesian(new Date(indeks?.createdAt))} {indeks?.timezone ? indeks?.timezone : "WIB"} | {" "}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
|
||||
/>
|
||||
</svg>{" "}
|
||||
{indeks?.clickCount}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Hero Right */}
|
||||
<div className="lg:w-[40%] w-full space-y-2">
|
||||
{indeksData?.map(
|
||||
(indeksRight: any, index: number) =>
|
||||
(index == count + 1 || index == count + 2) && (
|
||||
<div key={indeksRight?.id} className="relative h-[310px] lg:h-[215px]">
|
||||
<img src={indeksRight?.thumbnailLink} alt="image" className="w-full h-[310px] lg:h-[215px] rounded-lg " />
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-transparent backdrop-blur-sm text-white p-4 rounded-b-lg">
|
||||
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-sm px-4 py-1">{indeksRight?.categoryName}</span>
|
||||
<Link href={`/indeks/detail/${indeksRight?.slug}`}>
|
||||
<h2 className="text-xl font-bold mt-2">{indeksRight?.title}</h2>
|
||||
</Link>
|
||||
<p className="text-xs flex flex-row items-center gap-1 mt-1 ml-2">
|
||||
{formatDateToIndonesian(new Date(indeksRight?.createdAt))} {indeksRight?.timezone ? indeksRight?.timezone : "WIB"}|{" "}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
|
||||
/>
|
||||
</svg>{" "}
|
||||
{indeksRight?.clickCount}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
{isLoading ? (
|
||||
<>
|
||||
<div className="flex flex-col space-y-3 w-full justify-center items-center gap-3">
|
||||
<Skeleton className="h-[200px] w-full lg:w-[400px] rounded-xl" />
|
||||
<Skeleton className="h-[200px] w-full lg:w-[400px] rounded-xl" />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{indeksData?.map(
|
||||
(indeksRight: any, index: number) =>
|
||||
(index == count + 1 || index == count + 2) && (
|
||||
<div key={indeksRight?.id} className="relative h-[310px] lg:h-[215px]">
|
||||
<Image width={1920} height={1080} src={indeksRight?.thumbnailLink} alt="image" className="w-full h-[310px] lg:h-[215px] rounded-lg " />
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-transparent backdrop-blur-sm text-white p-4 rounded-b-lg">
|
||||
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-xs px-2 py-1">{indeksRight?.categoryName}</span>
|
||||
<Link href={`/indeks/detail/${indeksRight?.slug}`}>
|
||||
<h2 className="text-xl font-bold mt-2">{indeksRight?.title}</h2>
|
||||
</Link>
|
||||
<p className="text-xs flex flex-row items-center gap-1 mt-1 ml-2">
|
||||
{formatDateToIndonesian(new Date(indeksRight?.createdAt))} {indeksRight?.timezone ? indeksRight?.timezone : "WIB"}|{" "}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
|
||||
/>
|
||||
</svg>{" "}
|
||||
{indeksRight?.clickCount}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom */}
|
||||
<div className="px-4 lg:px-7 py-4">
|
||||
<div className="flex flex-col gap-4">
|
||||
{indeksData?.map(
|
||||
(indeksBottom: any, index: number) =>
|
||||
index < 3 && (
|
||||
<div key={indeksBottom?.id} className="flex flex-col md:flex-row items-start p-4 gap-4">
|
||||
<img src={indeksBottom?.thumbnailLink} alt="" className="h-40 object-cover rounded-lg w-full lg:w-full lg:h-[300px]" />
|
||||
<div className="flex flex-col justify-between w-full">
|
||||
<p className="text-sm">{indeksBottom?.date}</p>
|
||||
<Link href={`/indeks/detail/${indeksBottom?.slug}`} className="text-2xl font-semibold text-gray-800">
|
||||
{indeksBottom?.title}
|
||||
</Link>
|
||||
<p className="text-sm text-gray-600 mt-2">{indeksBottom?.description}</p>
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col space-y-3 w-full gap-10">
|
||||
<div className="flex flex-col lg:flex-row gap-5">
|
||||
<Skeleton className="h-[300px] w-full lg:w-[500px] rounded-lg" />
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-[250px]" />
|
||||
<Skeleton className="h-4 w-[200px]" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col lg:flex-row gap-5">
|
||||
<Skeleton className="h-[300px] w-full lg:w-[500px] rounded-lg" />
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-[250px]" />
|
||||
<Skeleton className="h-4 w-[200px]" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col lg:flex-row gap-5">
|
||||
<Skeleton className="h-[300px] w-full lg:w-[500px] rounded-lg" />
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-[250px]" />
|
||||
<Skeleton className="h-4 w-[200px]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-4">
|
||||
{indeksData?.map(
|
||||
(indeksBottom: any, index: number) =>
|
||||
index < 3 && (
|
||||
<div key={indeksBottom?.id} className="flex flex-col md:flex-row items-start p-4 gap-4">
|
||||
<Image width={2560} height={1440} src={indeksBottom?.thumbnailLink} alt="" className="h-40 object-cover rounded-lg w-full lg:w-full lg:h-[300px]" />
|
||||
<div className="flex flex-col justify-between w-full">
|
||||
<p className="text-sm">{indeksBottom?.date}</p>
|
||||
<Link href={`/indeks/detail/${indeksBottom?.slug}`} className="text-2xl font-semibold text-gray-800">
|
||||
{indeksBottom?.title}
|
||||
</Link>
|
||||
<p className="text-sm text-gray-600 mt-2">{indeksBottom?.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"use client";
|
||||
import { Link } from "@/components/navigation";
|
||||
import Image from "next/image";
|
||||
import React, { useState } from "react";
|
||||
|
||||
const ChangeProfile: React.FC = () => {
|
||||
|
|
@ -36,55 +37,29 @@ const ChangeProfile: React.FC = () => {
|
|||
|
||||
<div className="flex justify-center gap-4 mb-8">
|
||||
<Link href={"/profile"}>
|
||||
<button className="border border-red-700 text-red-700 px-4 py-2 rounded">
|
||||
User Profile
|
||||
</button>
|
||||
<button className="border border-red-700 text-red-700 px-4 py-2 rounded">User Profile</button>
|
||||
</Link>
|
||||
<Link href={"/profile/change-profile"}>
|
||||
<button className="bg-red-700 text-white px-4 py-2 rounded">
|
||||
Ubah Foto
|
||||
</button>
|
||||
<button className="bg-red-700 text-white px-4 py-2 rounded">Ubah Foto</button>
|
||||
</Link>
|
||||
<Link href={"/profile/change-password"}>
|
||||
<button className="border border-red-700 text-red-700 px-4 py-2 rounded">
|
||||
Ubah Password
|
||||
</button>
|
||||
<button className="border border-red-700 text-red-700 px-4 py-2 rounded">Ubah Password</button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="w-80 mx-auto bg-gray-900 text-white shadow-md p-8 rounded">
|
||||
<div className="flex justify-center mb-6">
|
||||
<div className="w-40 h-40 rounded-full border border-gray-300 flex items-center justify-center">
|
||||
{selectedImage ? (
|
||||
<img
|
||||
src={URL.createObjectURL(selectedImage)}
|
||||
alt="Preview"
|
||||
className="w-full h-full rounded-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<span className="text-gray-500">No Image</span>
|
||||
)}
|
||||
{selectedImage ? <Image width={1920} height={1080} src={URL.createObjectURL(selectedImage)} alt="Preview" className="w-full h-full rounded-full object-cover" /> : <span className="text-gray-500">No Image</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center gap-4">
|
||||
<input
|
||||
type="file"
|
||||
id="upload"
|
||||
accept="image/*"
|
||||
className="hidden"
|
||||
onChange={handleImageChange}
|
||||
/>
|
||||
<label
|
||||
htmlFor="upload"
|
||||
className="bg-blue-600 text-white px-4 py-2 rounded cursor-pointer"
|
||||
>
|
||||
<input type="file" id="upload" accept="image/*" className="hidden" onChange={handleImageChange} />
|
||||
<label htmlFor="upload" className="bg-blue-600 text-white px-4 py-2 rounded cursor-pointer">
|
||||
Ganti Foto
|
||||
</label>
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
className="border border-red-700 text-red-700 px-4 py-2 rounded"
|
||||
>
|
||||
<button onClick={handleDelete} className="border border-red-700 text-red-700 px-4 py-2 rounded">
|
||||
Hapus Foto
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,19 +2,20 @@
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Popover, PopoverArrow, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { format } from "date-fns";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { detailSchedule, listSchedule, listScheduleNextPublic, listSchedulePrevPublic, listScheduleTodayPublic } from "@/service/schedule/schedule";
|
||||
import { useRouter } from "@/i18n/routing";
|
||||
import { detailSchedule, listSchedule, listScheduleNextPublic, listSchedulePrevPublic, listScheduleTodayPublic, searchSchedules } from "@/service/schedule/schedule";
|
||||
import { usePathname, useRouter } from "@/i18n/routing";
|
||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog";
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
|
||||
import { close, loading } from "@/config/swal";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
||||
const timeList = [
|
||||
{
|
||||
|
|
@ -115,8 +116,57 @@ const timeList = [
|
|||
},
|
||||
];
|
||||
|
||||
const city = [
|
||||
{
|
||||
key: 1,
|
||||
id: "metro-jaya",
|
||||
name: "Polda Metro Jaya",
|
||||
},
|
||||
{
|
||||
key: 2,
|
||||
id: "jawa-barat",
|
||||
name: "Polda Jawa Barat",
|
||||
},
|
||||
{
|
||||
key: 3,
|
||||
id: "banten",
|
||||
name: "Polda Banten",
|
||||
},
|
||||
{
|
||||
key: 4,
|
||||
id: "jawa-tengah",
|
||||
name: "Polda Jawa Tengah",
|
||||
},
|
||||
{
|
||||
key: 5,
|
||||
id: "daerah-istimewa-yogyakarta",
|
||||
name: "Polda D.I Yogyakarta",
|
||||
},
|
||||
{
|
||||
key: 6,
|
||||
id: "jawa-timur",
|
||||
name: "Polda Jawa Timur",
|
||||
},
|
||||
{
|
||||
key: 7,
|
||||
id: "aceh",
|
||||
name: "Polda Aceh",
|
||||
},
|
||||
{
|
||||
key: 8,
|
||||
id: "sumatera-utara",
|
||||
name: "Polda Sumatera Utara",
|
||||
},
|
||||
{
|
||||
key: 9,
|
||||
id: "sumatera-barat",
|
||||
name: "Polda Sumatera Barat",
|
||||
},
|
||||
];
|
||||
|
||||
const Schedule = (props: any) => {
|
||||
const router = useRouter();
|
||||
const asPath = usePathname();
|
||||
const [startDate, setStartDate] = useState<Date | undefined>(new Date());
|
||||
const [dateAWeek, setDateAWeek] = useState<string[]>([]);
|
||||
const [todayList, setTodayList] = useState([]);
|
||||
|
|
@ -128,6 +178,31 @@ const Schedule = (props: any) => {
|
|||
const [content, setContent] = useState();
|
||||
const { id } = props;
|
||||
const t = useTranslations("LandingPage");
|
||||
const [search, setSearch] = useState<any>();
|
||||
const [scheduleSearch, setScheduleSearch] = useState<any>();
|
||||
let typingTimer: any;
|
||||
const doneTypingInterval = 1500;
|
||||
const [regionFilter, setRegionFilter] = useState<any>([]);
|
||||
const [regionName, setRegionName] = useState<any>([]);
|
||||
const isPolda = asPath.includes("/polda");
|
||||
|
||||
async function doneTyping() {
|
||||
if (search?.length > 2) {
|
||||
const resSchedule = await searchSchedules(search);
|
||||
setScheduleSearch(resSchedule.data?.data);
|
||||
} else {
|
||||
setScheduleSearch([]);
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeyUp = () => {
|
||||
clearTimeout(typingTimer);
|
||||
typingTimer = setTimeout(doneTyping, doneTypingInterval);
|
||||
};
|
||||
|
||||
const handleKeyDown = () => {
|
||||
clearTimeout(typingTimer);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function getDataSchedule() {
|
||||
|
|
@ -139,6 +214,23 @@ const Schedule = (props: any) => {
|
|||
getDataSchedule();
|
||||
}, [id]);
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
const group = isPolda ? asPath.split("/")[2] : regionFilter?.join(",");
|
||||
const resSchedule = await listSchedule(group);
|
||||
setSchedules(resSchedule.data?.data);
|
||||
console.log(resSchedule);
|
||||
setDateAWeek(dateList);
|
||||
getDataByDate(); // let today = new Date();
|
||||
// if (!dateList.includes(today.getDate())){
|
||||
// changeNextWeek();
|
||||
// }
|
||||
}
|
||||
|
||||
initState();
|
||||
console.log("Filter ::", regionFilter);
|
||||
}, [regionName]);
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
getDataByDate();
|
||||
|
|
@ -279,6 +371,57 @@ const Schedule = (props: any) => {
|
|||
setOpenDialog(true);
|
||||
};
|
||||
|
||||
const handleRegionFilter = (e: any) => {
|
||||
let regions = [...regionFilter];
|
||||
|
||||
if (e.target.checked) {
|
||||
regions = [...regionFilter, e.target.value];
|
||||
} else {
|
||||
regions.splice(regionFilter.indexOf(e.target.value), 1);
|
||||
}
|
||||
|
||||
console.log(regions);
|
||||
setRegionFilter(regions);
|
||||
};
|
||||
|
||||
const getListDataCity = () => {
|
||||
let filteredReg: any = [];
|
||||
let regName = "";
|
||||
|
||||
for (const element of regionFilter) {
|
||||
for (const element_ of city) {
|
||||
if (element == element_.id) {
|
||||
regName = element_.name;
|
||||
filteredReg = [
|
||||
...filteredReg,
|
||||
{
|
||||
id: element_.id,
|
||||
key: element_.key,
|
||||
name: regName,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(filteredReg);
|
||||
setRegionName(filteredReg);
|
||||
filteredReg = [];
|
||||
};
|
||||
|
||||
const doFilter = () => {
|
||||
getListDataCity();
|
||||
};
|
||||
|
||||
const deleteFilterhandler = (filterId: any) => {
|
||||
console.log("hapus", filterId);
|
||||
const deletedReg = regionName.filter((list: any) => list.id !== filterId);
|
||||
const filtered = regionFilter.filter((list: any) => list !== filterId);
|
||||
|
||||
setRegionName(deletedReg);
|
||||
setRegionFilter(filtered);
|
||||
};
|
||||
|
||||
function setItemSchedule(id: string, date: string) {
|
||||
const itemFound: any = schedules?.filter((s: any) => s.dateInRange.includes(date) && s.timeIndex.split(",").includes(id));
|
||||
|
||||
|
|
@ -358,8 +501,8 @@ const Schedule = (props: any) => {
|
|||
</Popover>
|
||||
|
||||
<div className="container relative py-4">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<a className="text-black flex flex-row w-fit gap-2 py-4 items-center cursor-pointer">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||
<path fill="#000" d="M20 3H4a1 1 0 0 0-1 1v2.227l.008.223a3 3 0 0 0 .772 1.795L8 12.886V21a1 1 0 0 0 1.316.949l6-2l.108-.043A1 1 0 0 0 16 19v-6.586l4.121-4.12A3 3 0 0 0 21 6.171V4a1 1 0 0 0-1-1" />
|
||||
|
|
@ -369,66 +512,49 @@ const Schedule = (props: any) => {
|
|||
<path fill="currentColor" fill-rule="evenodd" d="m6 7l6 6l6-6l2 2l-8 8l-8-8z" />
|
||||
</svg>
|
||||
</a>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="flex p-0 rounded-md">
|
||||
<DropdownMenuItem className="flex flex-col items-center justify-between gap-1.5 p-2 border-b text-default-600 rounded-none">
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="start" className="flex p-0 rounded-md w-fit">
|
||||
<div className="flex flex-col items-center justify-between gap-1.5 p-2 border-b text-default-600 rounded-none">
|
||||
<div className="gap-6 flex flex-row justify-end">
|
||||
<div> Filter</div>
|
||||
<button className="text-blue-400">Simpan</button>
|
||||
<p className="font-semibold">Filter</p>
|
||||
<button className="text-blue-400" onClick={doFilter}>
|
||||
{t("save")}
|
||||
</button>
|
||||
</div>
|
||||
<div className="border w-full border-t border-slate-500"></div>
|
||||
<div className="border w-full border-t border-slate-500"></div>
|
||||
<div className="overflow-y-auto flex flex-col gap-2 h-[200px] ">
|
||||
<p>Region Filter</p>
|
||||
<div className="mt-2 gap-2 flex flex-row">
|
||||
<Checkbox id="terms" />
|
||||
<p>POLDA METRO JAYA</p>
|
||||
</div>
|
||||
<div className="mt-2 gap-2 flex flex-row">
|
||||
<Checkbox id="terms" />
|
||||
<p>POLDA METRO JAYA</p>
|
||||
</div>
|
||||
<div className="mt-2 gap-2 flex flex-row">
|
||||
<Checkbox id="terms" />
|
||||
<p>POLDA METRO JAYA</p>
|
||||
</div>
|
||||
<div className="mt-2 gap-2 flex flex-row">
|
||||
<Checkbox id="terms" />
|
||||
<p>POLDA METRO JAYA</p>
|
||||
</div>
|
||||
<div className="mt-2 gap-2 flex flex-row">
|
||||
<Checkbox id="terms" />
|
||||
<p>POLDA METRO JAYA</p>
|
||||
</div>
|
||||
<div className="mt-2 gap-2 flex flex-row">
|
||||
<Checkbox id="terms" />
|
||||
<p>POLDA METRO JAYA</p>
|
||||
</div>
|
||||
<div className="mt-2 gap-2 flex flex-row">
|
||||
<Checkbox id="terms" />
|
||||
<p>POLDA METRO JAYA</p>
|
||||
</div>
|
||||
<div className="mt-2 gap-2 flex flex-row">
|
||||
<Checkbox id="terms" />
|
||||
<p>POLDA METRO JAYA</p>
|
||||
</div>
|
||||
<div className="mt-2 gap-2 flex flex-row">
|
||||
<Checkbox id="terms" />
|
||||
<p>POLDA METRO JAYA</p>
|
||||
</div>
|
||||
<p className="text-center font-semibold">Region Filter</p>
|
||||
{city?.map((list) => (
|
||||
<div className="mt-2 gap-2 flex flex-row">
|
||||
{" "}
|
||||
<input type="checkbox" className="" id={`filterCategory-${list.key}`} value={list.id} checked={regionFilter?.includes(list.id)} onChange={handleRegionFilter} />
|
||||
<p>{list?.name}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<div className="flex flex-col lg:flex-row gap-3">
|
||||
{regionName?.map((list: any) => (
|
||||
<div className="text-left">
|
||||
<button onClick={() => deleteFilterhandler(list.id)} key={list.key} id={list.id} className="text-black bg-yellow-300 w-fit p-3 flex justify-center items-center rounded-lg">
|
||||
{list.name}
|
||||
<Icon icon="icon-park-outline:delete-two" className="items-center" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col lg:flex-row gap-6">
|
||||
<div className="h-[500px] overflow-y-auto w-3/4 ">
|
||||
<div className="h-[500px] overflow-y-auto md:overflow-y-auto w-full lg:w-3/4 ">
|
||||
<div className="container-fluid relative">
|
||||
<div className="grid grid-cols-1 mt-8">
|
||||
<div className="relative block bg-white dark:bg-slate-900">
|
||||
<div className="relative block bg-white w-max dark:bg-slate-900">
|
||||
<table className="w-full text-sm text-start">
|
||||
<thead className="text-md">
|
||||
<tr className="h-full">
|
||||
<th className="text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[120px]">Time Table</th>
|
||||
<th className="text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[120px]">{t("timeTable")}</th>
|
||||
<th className="text-center border border-r-0 border-gray-100 dark:border-gray-700 py-6 w-[20px]">
|
||||
<a onClick={() => changePrevWeek()} className="cursor-pointer h-fit self-center bottom-0">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="40px" height="40px" viewBox="0 0 24 24">
|
||||
|
|
@ -444,23 +570,28 @@ const Schedule = (props: any) => {
|
|||
</a>{" "} */}
|
||||
<div className="flex flex-col ">
|
||||
<p className="text-2xl">{dateAWeek[0]?.split("-")[2]}</p>
|
||||
<p>Monday</p>
|
||||
<p>{t("monday")}</p>
|
||||
</div>
|
||||
</th>
|
||||
<th className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${new Date().toISOString().slice(0, 10) == dateAWeek[1] ? "bg-[#BE0106] text-white rounded-lg" : ""}`}>
|
||||
<div className="text-2xl">{dateAWeek[1]?.split("-")[2]}</div>Tuesday
|
||||
<div className="text-2xl">{dateAWeek[1]?.split("-")[2]}</div>
|
||||
{t("tuesday")}
|
||||
</th>
|
||||
<th className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${new Date().toISOString().slice(0, 10) == dateAWeek[2] ? "bg-[#BE0106] text-white rounded-lg" : ""}`}>
|
||||
<div className="text-2xl">{dateAWeek[2]?.split("-")[2]}</div>Wednesday
|
||||
<div className="text-2xl">{dateAWeek[2]?.split("-")[2]}</div>
|
||||
{t("wednesday")}
|
||||
</th>
|
||||
<th className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${new Date().toISOString().slice(0, 10) == dateAWeek[3] ? "bg-[#BE0106] text-white rounded-lg" : ""}`}>
|
||||
<div className="text-2xl">{dateAWeek[3]?.split("-")[2]}</div>Thursday
|
||||
<div className="text-2xl">{dateAWeek[3]?.split("-")[2]}</div>
|
||||
{t("thursday")}
|
||||
</th>
|
||||
<th className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${new Date().toISOString().slice(0, 10) == dateAWeek[4] ? "bg-[#BE0106] text-white rounded-lg" : ""}`}>
|
||||
<div className="text-2xl">{dateAWeek[4]?.split("-")[2]}</div>Friday
|
||||
<div className="text-2xl">{dateAWeek[4]?.split("-")[2]}</div>
|
||||
{t("friday")}
|
||||
</th>
|
||||
<th className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${new Date().toISOString().slice(0, 10) == dateAWeek[5] ? "bg-[#BE0106] text-white rounded-lg" : ""}`}>
|
||||
<div className="text-2xl">{dateAWeek[5]?.split("-")[2]}</div>Saturday
|
||||
<div className="text-2xl">{dateAWeek[5]?.split("-")[2]}</div>
|
||||
{t("saturday")}
|
||||
</th>
|
||||
<th
|
||||
onClick={() => changeNextWeek()}
|
||||
|
|
@ -470,7 +601,7 @@ const Schedule = (props: any) => {
|
|||
>
|
||||
<div className="flex flex-col ">
|
||||
<p className="text-2xl">{dateAWeek[6]?.split("-")[2]}</p>
|
||||
<p>Sunday</p>
|
||||
<p>{t("sunday")}</p>
|
||||
</div>
|
||||
{/* <a className="cursor-pointer h-fit p-0 m-0 self-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 24 24">
|
||||
|
|
@ -512,9 +643,16 @@ const Schedule = (props: any) => {
|
|||
</div>
|
||||
|
||||
{/* komponen Kanan */}
|
||||
<div className="w-1/4 flex flex-col gap-6">
|
||||
<div className="w-full lg:w-1/4 flex flex-col gap-6">
|
||||
<div className="relative text-gray-600 dark:text-white">
|
||||
<input type="text" placeholder={t("titleSchedule")} className="pl-8 pr-4 py-1 w-full border rounded-full text-sm focus:outline-none" />
|
||||
<input
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
onKeyUp={handleKeyUp}
|
||||
onKeyDown={handleKeyDown}
|
||||
type="text"
|
||||
placeholder={t("titleSchedule")}
|
||||
className="pl-8 pr-4 py-1 w-full border rounded-full text-sm focus:outline-none"
|
||||
/>
|
||||
<span className="absolute left-2 top-1/2 transform -translate-y-1/2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
|
|
@ -557,10 +695,10 @@ const Schedule = (props: any) => {
|
|||
</CollapsibleContent>
|
||||
))}
|
||||
</Collapsible> */}
|
||||
|
||||
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>Jadwal Hari ini</AccordionTrigger>
|
||||
<AccordionTrigger>{t("todaySchedule")}</AccordionTrigger>
|
||||
{todayList?.map((list: any) => (
|
||||
<AccordionContent key={list?.id} className="flex flex-row gap-3">
|
||||
<div className="border-l-4 border-red-700 pl-1 h-fit font-bold text-lg">{new Date(list.startDate).getDate()}</div>
|
||||
|
|
@ -574,7 +712,7 @@ const Schedule = (props: any) => {
|
|||
<Icon icon="bxs:map" width={40} />
|
||||
{list?.address}
|
||||
</p>
|
||||
<p>Pembicara :</p>
|
||||
<p>{t("speaker")}</p>
|
||||
<p className="flex flex-row items-center gap-2">
|
||||
<Icon icon="ic:round-person" />
|
||||
{list?.speakerTitle} {list?.speakerName}
|
||||
|
|
@ -585,7 +723,7 @@ const Schedule = (props: any) => {
|
|||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="item-2">
|
||||
<AccordionTrigger>Jadwal Sebelumnya</AccordionTrigger>
|
||||
<AccordionTrigger>{t("previousSchedule")}</AccordionTrigger>
|
||||
{prevdayList?.map((list: any) => (
|
||||
<AccordionContent key={list?.id} className="flex flex-row gap-3">
|
||||
<div className="border-l-4 border-red-700 pl-1 h-fit font-bold text-lg">{new Date(list.startDate).getDate()}</div>
|
||||
|
|
@ -599,7 +737,7 @@ const Schedule = (props: any) => {
|
|||
<Icon icon="bxs:map" width={40} />
|
||||
{list?.address}
|
||||
</p>
|
||||
<p>Pembicara :</p>
|
||||
<p>{t("speaker")}</p>
|
||||
<p className="flex flex-row items-center gap-2">
|
||||
<Icon icon="ic:round-person" />
|
||||
{list?.speakerTitle} {list?.speakerName}
|
||||
|
|
@ -610,7 +748,7 @@ const Schedule = (props: any) => {
|
|||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="item-3">
|
||||
<AccordionTrigger>Jadwal Selanjutnya</AccordionTrigger>
|
||||
<AccordionTrigger>{t("nextSchedule")}</AccordionTrigger>
|
||||
{nextdayList?.map((list: any) => (
|
||||
<AccordionContent key={list?.id} className="flex flex-row gap-3">
|
||||
<div className="border-l-4 border-red-700 pl-1 h-fit font-bold text-lg">{new Date(list.startDate).getDate()}</div>
|
||||
|
|
@ -624,7 +762,7 @@ const Schedule = (props: any) => {
|
|||
<Icon icon="bxs:map" width={40} />
|
||||
{list?.address}
|
||||
</p>
|
||||
<p>Pembicara :</p>
|
||||
<p>{t("speaker")}</p>
|
||||
<p className="flex flex-row items-center gap-2">
|
||||
<Icon icon="ic:round-person" />
|
||||
{list?.speakerTitle} {list?.speakerName}
|
||||
|
|
|
|||
|
|
@ -3,18 +3,24 @@
|
|||
import { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { checkWishlistStatus, deleteWishlist, getDetail, saveWishlist } from "@/service/landing/landing";
|
||||
import { checkWishlistStatus, createPublicSuggestion, deletePublicSuggestion, deleteWishlist, getDetail, getPublicSuggestionList, saveWishlist } from "@/service/landing/landing";
|
||||
import VideoPlayer from "@/utils/video-player";
|
||||
import NewContent from "@/components/landing-page/new-content";
|
||||
import { Link, useRouter } from "@/i18n/routing";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
import { close, error, loading, successCallback } from "@/config/swal";
|
||||
import { close, error, loading, successCallback, warning } from "@/config/swal";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { sendMediaUploadToEmail } from "@/service/media-tracking/media-tracking";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { checkMaliciousText, getPublicLocaleTimestamp } from "@/utils/globals";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import Swal from "sweetalert2";
|
||||
import parse from "html-react-parser";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Image from "next/image";
|
||||
|
||||
const DetailVideo = () => {
|
||||
const [selectedSize, setSelectedSize] = useState<string>("L");
|
||||
|
|
@ -41,7 +47,11 @@ const DetailVideo = () => {
|
|||
const [width, setWidth] = useState<any>();
|
||||
const [content, setContent] = useState<any>([]);
|
||||
const userRoleId = getCookiesDecrypt("urie");
|
||||
|
||||
const [message, setMessage] = useState("");
|
||||
const [listSuggestion, setListSuggestion] = useState<any>();
|
||||
const [visibleInput, setVisibleInput] = useState(null);
|
||||
const MySwal = withReactContent(Swal);
|
||||
const t = useTranslations("LandingPage");
|
||||
let typeString = "video";
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -52,9 +62,12 @@ const DetailVideo = () => {
|
|||
const initFetch = async () => {
|
||||
const response = await getDetail(String(slug));
|
||||
console.log("detailVideo", response);
|
||||
const responseGet = await getPublicSuggestionList(slug?.split("-")?.[0]);
|
||||
|
||||
setIsFromSPIT(response?.data?.data?.isFromSPIT);
|
||||
setWidth(window.innerWidth);
|
||||
setContent(response?.data.data);
|
||||
setListSuggestion(responseGet.data?.data);
|
||||
setMain({
|
||||
id: response?.data?.data?.files[0]?.id,
|
||||
type: response?.data?.data?.fileType.name,
|
||||
|
|
@ -284,6 +297,122 @@ const DetailVideo = () => {
|
|||
return false;
|
||||
};
|
||||
|
||||
async function sendSuggestionChild(parentId: any) {
|
||||
const inputElement = document.querySelector(`#input-comment-${parentId}`) as HTMLInputElement;
|
||||
|
||||
if (inputElement && inputElement.value.length > 3) {
|
||||
loading();
|
||||
const data = {
|
||||
mediaUploadId: slug?.split("-")?.[0],
|
||||
message: inputElement.value,
|
||||
parentId,
|
||||
};
|
||||
|
||||
console.log(data);
|
||||
const response = await createPublicSuggestion(data);
|
||||
console.log(response);
|
||||
const responseGet: any = await getPublicSuggestionList(slug?.split("-")?.[0]);
|
||||
console.log(responseGet.data?.data);
|
||||
setListSuggestion(responseGet.data?.data);
|
||||
|
||||
// Reset input field
|
||||
inputElement.value = "";
|
||||
|
||||
// document.querySelector("#comment-id-" + parentId)?.style.display = "none";
|
||||
|
||||
close();
|
||||
}
|
||||
}
|
||||
async function deleteDataSuggestion(dataId: any) {
|
||||
loading();
|
||||
const response = await deletePublicSuggestion(dataId);
|
||||
console.log(response);
|
||||
const responseGet = await getPublicSuggestionList(slug.split("-")?.[0]);
|
||||
console.log(responseGet.data?.data);
|
||||
setListSuggestion(responseGet.data?.data);
|
||||
close();
|
||||
}
|
||||
const deleteData = (dataId: any) => {
|
||||
MySwal.fire({
|
||||
title: "Delete Comment",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Delete",
|
||||
}).then((result: any) => {
|
||||
if (result.isConfirmed) {
|
||||
deleteDataSuggestion(dataId);
|
||||
console.log(dataId);
|
||||
}
|
||||
});
|
||||
};
|
||||
const showInput = (e: any) => {
|
||||
console.log(document.querySelector(`#${e}`)?.classList);
|
||||
document.querySelector(`#${e}`)?.classList.toggle("none");
|
||||
setVisibleInput(visibleInput === e ? null : e);
|
||||
};
|
||||
function addDefaultProfile(ev: any) {
|
||||
ev.target.src = "/assets/avatar-profile.png";
|
||||
}
|
||||
|
||||
async function sendSuggestionParent() {
|
||||
if (message?.length > 3) {
|
||||
loading();
|
||||
const data = {
|
||||
mediaUploadId: slug?.split("-")?.[0],
|
||||
message,
|
||||
parentId: null,
|
||||
};
|
||||
|
||||
const response = await createPublicSuggestion(data);
|
||||
|
||||
console.log(response);
|
||||
setMessage("");
|
||||
|
||||
const responseGet = await getPublicSuggestionList(slug?.split("-")?.[0]);
|
||||
console.log(responseGet?.data?.data);
|
||||
setListSuggestion(responseGet?.data?.data);
|
||||
|
||||
// Hapus nilai semua input secara manual jika perlu
|
||||
const inputs = document.querySelectorAll("input");
|
||||
inputs.forEach((input) => {
|
||||
input.value = "";
|
||||
});
|
||||
|
||||
close();
|
||||
}
|
||||
}
|
||||
const getInputValue = (e: any) => {
|
||||
const message = e.target.value;
|
||||
console.log(message);
|
||||
setMessage(message);
|
||||
};
|
||||
const postData = () => {
|
||||
const checkMessage = checkMaliciousText(message);
|
||||
if (checkMessage == "") {
|
||||
if (Number(userRoleId) < 1 || userRoleId == undefined) {
|
||||
router.push("/auth");
|
||||
} else {
|
||||
sendSuggestionParent();
|
||||
}
|
||||
} else {
|
||||
warning(checkMessage);
|
||||
}
|
||||
};
|
||||
const postDataChild = (id: any) => {
|
||||
const checkMessage = checkMaliciousText(message);
|
||||
if (checkMessage == "") {
|
||||
if (Number(userRoleId) < 1 || userRoleId == undefined) {
|
||||
router.push("/auth");
|
||||
} else {
|
||||
sendSuggestionChild(id);
|
||||
}
|
||||
} else {
|
||||
warning(checkMessage);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="px-4 md:px-24 py-4">
|
||||
|
|
@ -297,8 +426,8 @@ const DetailVideo = () => {
|
|||
</div>
|
||||
|
||||
{/* Footer Informasi */}
|
||||
<div className="text-sm text-gray-500 flex justify-between items-center border-t mt-4">
|
||||
<p className="flex flex-row items-center mt-3">
|
||||
<div className="text-gray-500 flex flex-col lg:flex-row justify-between items-center border-t mt-4">
|
||||
{/* <p className="flex flex-row items-center mt-3">
|
||||
oleh
|
||||
<span className="font-semibold text-black">{detailDataVideo?.uploadedBy?.userLevel?.name}</span>
|
||||
| Diupdate pada {detailDataVideo?.updatedAt} WIB |
|
||||
|
|
@ -306,14 +435,33 @@ const DetailVideo = () => {
|
|||
|
||||
{detailDataVideo?.clickCount}
|
||||
</p>
|
||||
<p className="mt-3">Kreator: {detailDataVideo?.creatorName}</p>
|
||||
<p className="mt-3">Kreator: {detailDataVideo?.creatorName}</p> */}
|
||||
<div className="flex flex-col lg:flex-row items-center mt-3 lg:justify-between">
|
||||
<p className="text-xs lg:text-sm">
|
||||
{t("by")} <span className="font-semibold text-black">{detailDataVideo?.uploadedBy?.userLevel?.name}</span>
|
||||
</p>
|
||||
<p className="text-xs lg:text-sm">
|
||||
| {t("updatedOn")}
|
||||
{detailDataVideo?.updatedAt} WIB |
|
||||
</p>
|
||||
<p className="text-xs lg:text-sm flex justify-center items-center">
|
||||
<Icon icon="formkit:eye" width="15" height="15" />
|
||||
{detailDataVideo?.clickCount}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<p className="flex text-end text-xs lg:text-sm font-semibold">
|
||||
{t("creator")}
|
||||
{detailDataVideo?.creatorName}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Keterangan */}
|
||||
<div className="w-full">
|
||||
<h1 className="flex flex-row font-bold text-2xl my-8">{detailDataVideo?.title}</h1>
|
||||
<h1 className="flex flex-row font-bold text-lg lg:text-2xl my-8">{detailDataVideo?.title}</h1>
|
||||
<div
|
||||
className="font-light text-justify"
|
||||
className="font-light text-justify mb-5 lg:mb-0"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: detailDataVideo?.htmlDescription,
|
||||
}}
|
||||
|
|
@ -326,12 +474,12 @@ const DetailVideo = () => {
|
|||
{isSaved ? (
|
||||
<a onClick={() => handleDeleteWishlist()} className="flex flex-col mb-3 items-center justify-center cursor-pointer">
|
||||
<Icon icon="material-symbols:bookmark" width={40} />
|
||||
<p className="text-base lg:text-lg">Hapus</p>
|
||||
<p className="text-base lg:text-lg">{t("delete")}</p>
|
||||
</a>
|
||||
) : (
|
||||
<a onClick={() => doBookmark()} className="flex flex-col mb-3 items-center justify-center cursor-pointer">
|
||||
<Icon icon="material-symbols:bookmark-outline" width={40} />
|
||||
<p className="text-base lg:text-lg">Simpan</p>
|
||||
<p className="text-base lg:text-lg">{t("save")}</p>
|
||||
</a>
|
||||
)}
|
||||
|
||||
|
|
@ -353,7 +501,7 @@ const DetailVideo = () => {
|
|||
<div className="border-t border-black my-4"></div>
|
||||
|
||||
{/* Opsi Ukuran Foto */}
|
||||
<h4 className="flex text-lg justify-center items-center font-semibold my-3">Opsi Ukuran Audio Visual</h4>
|
||||
<h4 className="flex text-lg justify-center items-center font-semibold my-3">{t("videoSize")}</h4>
|
||||
|
||||
<div className="border-t border-black my-4"></div>
|
||||
|
||||
|
|
@ -375,7 +523,7 @@ const DetailVideo = () => {
|
|||
<div className="mt-4">
|
||||
<label className="flex items-center space-x-2 text-sm">
|
||||
<input type="checkbox" className="text-red-600 focus:ring-red-600" onChange={() => setIsDownloadAll(!isDownloadAll)} />
|
||||
<span>Download Semua File?</span>
|
||||
<span>{t("downloadAll")}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
|
@ -384,12 +532,12 @@ const DetailVideo = () => {
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||
<path fill="white" d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z" />
|
||||
</svg>
|
||||
Download
|
||||
{t("download")}
|
||||
</button>
|
||||
|
||||
{/* Tombol Bagikan */}
|
||||
<div className="flex flex-row py-3">
|
||||
<p className="text-base font-semibold">Bagikan</p>
|
||||
<div className="flex flex-row justify-center py-3">
|
||||
<p className="text-base font-semibold">{t("share")}</p>
|
||||
<a className="ml-8 cursor-pointer" onClick={() => handleShare("fb", `https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}"e=${content?.title}`)}>
|
||||
<Icon icon="brandico:facebook" height="20" className="px-auto text-red-600 text-center" />
|
||||
</a>
|
||||
|
|
@ -407,13 +555,13 @@ const DetailVideo = () => {
|
|||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<div className="flex flex-col">
|
||||
<h1 className="mb-2">Share Ke Email</h1>
|
||||
<h1 className="mb-2">{t("shareTo")}</h1>
|
||||
<div className="flex flex-col mb-2">
|
||||
<p className="text-base font-semibold mb-1">Email Tujuan :</p>
|
||||
<Input value={emailShareInput} onChange={(event) => setEmailShareInput(event.target.value)} onKeyPress={handleEmailList} type="email" placeholder="Tekan Enter untuk input Email" />
|
||||
<p className="text-base font-semibold mb-1">{t("destinationEmail")}</p>
|
||||
<Input value={emailShareInput} onChange={(event) => setEmailShareInput(event.target.value)} onKeyPress={handleEmailList} type="email" placeholder={t("pressEnter")} />
|
||||
</div>
|
||||
<Button className="bg-blue-500 text-white p-2 w-fit rounded-lg" onClick={() => shareToEmail()}>
|
||||
Kirim
|
||||
{t("send")}
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
|
|
@ -424,11 +572,181 @@ const DetailVideo = () => {
|
|||
</div>
|
||||
<div className="w-full mb-8">
|
||||
{/* Comment */}
|
||||
<div className="flex flex-col my-16 p-10 bg-[#f7f7f7]">
|
||||
<div className="flex flex-col my-16 p-4 lg:p-10 bg-[#f7f7f7]">
|
||||
<div className="gap-5 flex flex-col px-4 lg:px-14">
|
||||
<p className="flex items-start text-lg">Berikan Komentar</p>
|
||||
<Textarea placeholder="Type your comments here." className="flex w-full" />
|
||||
<button className="flex items-start bg-[#bb3523] text-white rounded-lg w-fit px-4 py-1">Kirim</button>
|
||||
<p className="flex items-start text-lg">{t("comment")}</p>
|
||||
<Textarea placeholder="Type your comments here." className="flex w-full pb-12" onChange={getInputValue} />
|
||||
<button onClick={() => postData()} className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-4 py-1">
|
||||
{t("send")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="border-b-2 border-slate-300 mt-4 w-auto"></div>
|
||||
|
||||
<div>
|
||||
{listSuggestion?.map((data: any) => (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row mt-2 px-4 lg:px-14">
|
||||
<Image width={1080} height={1080} src={data?.suggestionFrom?.profilePictureUrl} className="h-12 lg:h-16 w-12 lg:w-16 mr-2" onError={addDefaultProfile} alt="" />
|
||||
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
|
||||
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
|
||||
{Number(data.suggestionFrom?.roleId) == 2 || Number(data.suggestionFrom?.roleId) == 3 || Number(data.suggestionFrom?.roleId) == 4 ? "HUMAS POLRI" : data.suggestionFrom?.fullname}
|
||||
{getPublicLocaleTimestamp(new Date(data.createdAt))}
|
||||
</p>
|
||||
<p className="text-slate-500 text-[13px] lg:text-sm mb-4">{data?.message}</p>
|
||||
<div>
|
||||
<a
|
||||
style={
|
||||
Number(data.suggestionFrom?.id) == Number(userId)
|
||||
? {
|
||||
display: "none",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
onClick={() => showInput(`comment-id-${data.id}`)}
|
||||
className="mr-2"
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("reply")}</small>
|
||||
</a>
|
||||
{Number(data.suggestionFrom?.id) == Number(userId) || Number(userRoleId) == 2 ? (
|
||||
<a onClick={() => deleteData(data.id)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("delete")}</small>
|
||||
</a>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{visibleInput === `comment-id-${data.id}` && (
|
||||
<div id={`comment-id-${data.id}`} className="px-4 pl-[72px] lg:px-14 lg:pl-32 mt-2 ">
|
||||
<Textarea id={`input-comment-${data.id}`} className="p-4 focus:outline-none focus:border-sky-500" placeholder={t("enterReply")} />
|
||||
<div className="flex flex-row gap-3">
|
||||
<a onClick={() => postDataChild(data.id)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 mt-2 cursor-pointer">{t("send")}</small>
|
||||
</a>
|
||||
<a onClick={() => showInput(`comment-id-${data.id}`)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg mt-2 w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("cancel")}</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data.children.length > 0
|
||||
? data.children?.map((child1: any) => (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-16 lg:pl-32">
|
||||
<Image width={1080} height={1080} src={child1.suggestionFrom?.profilePictureUrl} onError={addDefaultProfile} alt="" className="h-10 lg:h-16 w-10 lg:w-16 mr-2" />
|
||||
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
|
||||
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
|
||||
{" "}
|
||||
<b>{Number(child1.suggestionFrom?.roleId) == 2 || Number(child1.suggestionFrom?.roleId) == 3 || Number(child1.suggestionFrom?.roleId) == 4 ? "HUMAS POLRI" : child1.suggestionFrom?.fullname}</b>{" "}
|
||||
{getPublicLocaleTimestamp(new Date(child1.createdAt))}
|
||||
</p>
|
||||
<p className="text-slate-500 text-[13px] lg:text-sm mb-4">{parse(String(child1?.message))}</p>
|
||||
<div>
|
||||
<a
|
||||
style={
|
||||
Number(child1.suggestionFrom?.id) == Number(userId)
|
||||
? {
|
||||
display: "none",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
onClick={() => showInput(`comment-id-${child1.id}`)}
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("reply")}</small>
|
||||
</a>
|
||||
<a
|
||||
style={
|
||||
Number(child1.suggestionFrom?.id) == Number(userId)
|
||||
? {}
|
||||
: {
|
||||
display: "none",
|
||||
}
|
||||
}
|
||||
onClick={() => deleteData(child1.id)}
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("delete")}</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{visibleInput === `comment-id-${child1.id}` && (
|
||||
<div id={`comment-id-${child1.id}`} className="px-4 lg:px-14 pl-28 lg:pl-[200px]">
|
||||
<Textarea name="" className="mt-2 " id={`input-comment-${child1.id}`} placeholder={t("enterReply")} />
|
||||
<div className="flex flex-row mt-2 gap-3">
|
||||
<a onClick={() => postDataChild(child1.id)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("send")}</small>
|
||||
</a>
|
||||
<a onClick={() => showInput(`comment-id-${child1.id}`)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("cancel")}</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{child1.children.length > 0
|
||||
? child1.children?.map((child2: any) => (
|
||||
<div className="">
|
||||
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-28 lg:pl-48">
|
||||
<Image width={1080} height={1080} src={child2.suggestionFrom?.profilePictureUrl} className="h-9 lg:h-16 w-9 lg:w-16 mr-2" onError={addDefaultProfile} alt="" />
|
||||
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
|
||||
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
|
||||
{" "}
|
||||
<b>{Number(child2.suggestionFrom?.roleId) == 2 || Number(child2.suggestionFrom?.roleId) == 3 || Number(child2.suggestionFrom?.roleId) == 4 ? "HUMAS POLRI" : child2.suggestionFrom?.fullname}</b>{" "}
|
||||
{getPublicLocaleTimestamp(new Date(child2.createdAt))}
|
||||
</p>
|
||||
<p className="text-slate-500 text-sm mb-4">{parse(String(child2?.message))}</p>
|
||||
<div>
|
||||
<a
|
||||
style={
|
||||
Number(child2.suggestionFrom?.id) == Number(userId)
|
||||
? {
|
||||
display: "none",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
onClick={() => showInput(`comment-id-${child2.id}`)}
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("reply")}</small>
|
||||
</a>
|
||||
<a
|
||||
style={
|
||||
Number(child2.suggestionFrom?.id) == Number(userId)
|
||||
? {}
|
||||
: {
|
||||
display: "none",
|
||||
}
|
||||
}
|
||||
onClick={() => deleteData(child2.id)}
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("delete")}</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{visibleInput === `comment-id-${child2.id}` && (
|
||||
<div id={`comment-id-${child2.id}`} className="px-4 lg:px-14 pl-40 lg:pl-[265px]">
|
||||
<Textarea name="" id={`input-comment-${child2.id}`} className="my-2" placeholder={t("enterReply")} />
|
||||
<div className="flex flex-row gap-3">
|
||||
<a onClick={() => postDataChild(child2.id)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("send")}</small>
|
||||
</a>
|
||||
<a onClick={() => showInput(`comment-id-${child2.id}`)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("cancel")}</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
: ""}
|
||||
</div>
|
||||
))
|
||||
: ""}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ import "react-datepicker/dist/react-datepicker.css";
|
|||
import { close, loading } from "@/config/swal";
|
||||
import { useTranslations } from "next-intl";
|
||||
import ImageBlurry from "@/components/ui/image-blurry";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import Image from "next/image";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
|
|
@ -67,9 +69,18 @@ const FilterPage = () => {
|
|||
const [categories, setCategories] = useState([]);
|
||||
const [userLevels, setUserLevels] = useState([]);
|
||||
const t = useTranslations("FilterPage");
|
||||
const [isLoading, setIsLoading] = useState<any>(true);
|
||||
|
||||
// const [startDate, endDate] = dateRange;
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 3000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
const pageFromUrl = searchParams?.get("page");
|
||||
if (pageFromUrl) {
|
||||
|
|
@ -96,10 +107,6 @@ const FilterPage = () => {
|
|||
}
|
||||
}, [categorie]);
|
||||
|
||||
// useEffect(() => {
|
||||
// fetchData();
|
||||
// }, [page, sortBy, sortByOpt, title]);
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
if (isRegional) {
|
||||
|
|
@ -108,7 +115,6 @@ const FilterPage = () => {
|
|||
getDataAll();
|
||||
}
|
||||
}
|
||||
console.log(monthYearFilter, "monthFilter");
|
||||
initState();
|
||||
}, [change, asPath, monthYearFilter, page, sortBy, sortByOpt, title, startDateString, endDateString, categorie, formatFilter]);
|
||||
|
||||
|
|
@ -152,7 +158,8 @@ const FilterPage = () => {
|
|||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") : "",
|
||||
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : ""
|
||||
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : "",
|
||||
locale == "en" ? true : false
|
||||
);
|
||||
close();
|
||||
// setGetTotalPage(response?.data?.data?.totalPages);
|
||||
|
|
@ -167,7 +174,6 @@ const FilterPage = () => {
|
|||
}
|
||||
} else {
|
||||
const filter = categoryFilter?.length > 0 ? categoryFilter?.sort().join(",") : categorie || "";
|
||||
|
||||
const name = title == undefined ? "" : title;
|
||||
const format = formatFilter == undefined ? "" : formatFilter?.join(",");
|
||||
loading();
|
||||
|
|
@ -184,7 +190,8 @@ const FilterPage = () => {
|
|||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") : "",
|
||||
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : ""
|
||||
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : "",
|
||||
locale == "en" ? true : false
|
||||
);
|
||||
close();
|
||||
// setGetTotalPage(response?.data?.data?.totalPages);
|
||||
|
|
@ -281,25 +288,25 @@ const FilterPage = () => {
|
|||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
initFetch();
|
||||
}, [page]);
|
||||
const initFetch = async () => {
|
||||
const response = await getListContent({
|
||||
page: page - 1,
|
||||
size: 6,
|
||||
sortBy: "createdAt",
|
||||
contentTypeId: "2",
|
||||
isInt: locale == "en" ? true : false,
|
||||
});
|
||||
console.log(response);
|
||||
setVideoData(response?.data?.data?.content);
|
||||
const data = response?.data?.data;
|
||||
const contentData = data?.content;
|
||||
setVideoData(contentData);
|
||||
setTotalData(data?.totalElements);
|
||||
setTotalPage(data?.totalPages);
|
||||
};
|
||||
// useEffect(() => {
|
||||
// initFetch();
|
||||
// }, [page]);
|
||||
// const initFetch = async () => {
|
||||
// const response = await getListContent({
|
||||
// page: page - 1,
|
||||
// size: 6,
|
||||
// sortBy: "createdAt",
|
||||
// contentTypeId: "2",
|
||||
// isInt: locale == "en" ? true : false,
|
||||
// });
|
||||
// console.log(response);
|
||||
// setVideoData(response?.data?.data?.content);
|
||||
// const data = response?.data?.data;
|
||||
// const contentData = data?.content;
|
||||
// setVideoData(contentData);
|
||||
// setTotalData(data?.totalElements);
|
||||
// setTotalPage(data?.totalPages);
|
||||
// };
|
||||
|
||||
function getSelectedCategory() {
|
||||
const filter = [];
|
||||
|
|
@ -389,7 +396,7 @@ const FilterPage = () => {
|
|||
|
||||
{/* Left */}
|
||||
<div className="flex flex-col lg:flex-row gap-6 p-4">
|
||||
<div className="lg:w-[25%] w-full bg-[#f7f7f7] dark:bg-black p-4 rounded-lg shadow-md">
|
||||
<div className="lg:w-[55%] w-full bg-[#f7f7f7] dark:bg-black p-4 rounded-lg shadow-md">
|
||||
<h2 className="text-lg font-semibold mb-4 flex items-center gap-1">
|
||||
<Icon icon="stash:filter-light" fontSize={30} />
|
||||
Filter
|
||||
|
|
@ -502,7 +509,7 @@ const FilterPage = () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Konten Kanan */}
|
||||
{/* Right */}
|
||||
<Reveal>
|
||||
<div className="flex-1">
|
||||
<div className="flex flex-col items-end mb-4">
|
||||
|
|
@ -513,36 +520,53 @@ const FilterPage = () => {
|
|||
</select>
|
||||
</div>
|
||||
|
||||
{videoData?.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{videoData?.map((video: any) => (
|
||||
<Card key={video?.id} className="hover:scale-110 transition-transform duration-300">
|
||||
<CardContent className="flex flex-col text-xs lg:text-sm w-full p-0">
|
||||
<Link href={`/video/detail/${video?.slug}`}>
|
||||
{/* <img src={video?.thumbnailLink} className="h-60 object-cover items-center justify-center cursor-pointer rounded-lg place-self-center" /> */}
|
||||
<div className="img-container h-60 bg-[#e9e9e9] cursor-pointer rounded-lg">
|
||||
<ImageBlurry src={video?.thumbnailLink} alt={video?.title} style={{ objectFit: "contain", width: "100%", height: "100%" }} />
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-2 text-[10px] mx-2">
|
||||
{formatDateToIndonesian(new Date(video?.createdAt))} {video?.timezone ? 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>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col lg:flex-row space-y-3 w-full justify-center items-center gap-3">
|
||||
<Skeleton className="h-[280px] w-[380px] rounded-xl" />
|
||||
<Skeleton className="h-[280px] w-[380px] rounded-xl" />
|
||||
<Skeleton className="h-[280px] w-[380px] rounded-xl" />
|
||||
</div>
|
||||
<div className="flex flex-col lg:flex-row space-y-3 w-full justify-center items-center gap-3">
|
||||
<Skeleton className="h-[280px] w-[380px] rounded-xl" />
|
||||
<Skeleton className="h-[280px] w-[380px] rounded-xl" />
|
||||
<Skeleton className="h-[280px] w-[380px] rounded-xl" />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="flex items-center justify-center">
|
||||
<img src="/assets/empty-data.png" alt="empty" className="h-60 w-60 my-4" />
|
||||
</p>
|
||||
<>
|
||||
{videoData?.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{videoData?.map((video: any) => (
|
||||
<Card key={video?.id} className="hover:scale-110 transition-transform duration-300">
|
||||
<CardContent className="flex flex-col text-xs lg:text-sm w-full p-0">
|
||||
<Link href={`/video/detail/${video?.slug}`}>
|
||||
{/* <img src={video?.thumbnailLink} className="h-60 object-cover items-center justify-center cursor-pointer rounded-lg place-self-center" /> */}
|
||||
<div className="img-container h-60 bg-[#e9e9e9] cursor-pointer rounded-lg">
|
||||
<ImageBlurry src={video?.thumbnailLink} alt={video?.title} style={{ objectFit: "contain", width: "100%", height: "100%" }} />
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-2 text-[10px] mx-2">
|
||||
{formatDateToIndonesian(new Date(video?.createdAt))} {video?.timezone ? 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>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="flex items-center justify-center">
|
||||
<Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-60 w-60 my-4" />
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<LandingPagination table={table} totalData={totalData} totalPage={totalPage} />
|
||||
|
|
|
|||
|
|
@ -1,37 +1,20 @@
|
|||
import { Link } from '@/i18n/routing';
|
||||
import { Link } from "@/i18n/routing";
|
||||
import ForgotPass from "@/components/partials/auth/forgot-pass";
|
||||
import Image from "next/image";
|
||||
import Copyright from "@/components/partials/auth/copyright";
|
||||
import Logo from "@/components/partials/auth/logo";
|
||||
|
||||
const ForgotPassPage = () => {
|
||||
return (
|
||||
<div className="flex w-full items-center overflow-hidden min-h-dvh h-dvh basis-full">
|
||||
<div className="overflow-y-auto flex flex-wrap w-full h-dvh">
|
||||
<div
|
||||
className="lg:block hidden flex-1 overflow-hidden text-[40px] leading-[48px] text-default-600
|
||||
relative z-[1] bg-default-50"
|
||||
>
|
||||
<div className="max-w-[520px] pt-20 ps-20">
|
||||
<div className="lg:block hidden flex-1 overflow-hidden text-[40px] leading-[48px] text-default-600 relative z-[1] bg-default-50">
|
||||
<div className="max-w-[520px] pt-16 ps-20 ">
|
||||
<Link href="/" className="mb-6 inline-block">
|
||||
<Logo />
|
||||
<Image src="/assets/mediahub-logo.png" alt="" width={250} height={250} className="mb-10 w-full h-full" />
|
||||
</Link>
|
||||
|
||||
<h4>
|
||||
Unlock your Project{" "}
|
||||
<span className="text-default-800 font-bold ms-2">
|
||||
performance
|
||||
</span>
|
||||
</h4>
|
||||
</div>
|
||||
<div className="absolute left-0 bottom-[-130px] h-full w-full z-[-1]">
|
||||
<Image
|
||||
width={300}
|
||||
height={300}
|
||||
src="/images/auth/ils1.svg"
|
||||
alt=""
|
||||
className="h-full w-full object-contain"
|
||||
/>
|
||||
<div className="absolute left-0 2xl:bottom-[-160px] bottom-[-130px] h-full w-full z-[-1]">
|
||||
<Image src="/assets/vector-login.svg" alt="" width={300} height={300} className="mb-10 w-full h-full" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 relative dark:bg-default-100 bg-white">
|
||||
|
|
@ -44,29 +27,18 @@ const ForgotPassPage = () => {
|
|||
</div>
|
||||
<div className="text-center 2xl:mb-10 mb-5">
|
||||
<h4 className="font-medium mb-4">Forgot Your Password?</h4>
|
||||
<div className="text-default-500 text-base">
|
||||
Reset Password with Dashcode.
|
||||
</div>
|
||||
</div>
|
||||
<div className="font-normal text-base text-default-500 text-center px-2 bg-default-100 rounded py-3 mb-4 mt-10">
|
||||
Enter your Email and instructions will be sent to you!
|
||||
</div>
|
||||
<div className="font-normal text-base text-default-500 text-center px-2 bg-default-100 rounded py-3 mb-4 mt-10">Enter your Username and instructions will be sent to you!</div>
|
||||
|
||||
<ForgotPass />
|
||||
<div className="md:max-w-[345px] mx-auto font-normal text-default-500 2xl:mt-12 mt-8 uppercase text-sm">
|
||||
Forget It,
|
||||
<Link
|
||||
href="/"
|
||||
className="text-default-900 font-medium hover:underline"
|
||||
>
|
||||
<Link href="/auth" className="text-default-900 font-medium hover:underline">
|
||||
Send me Back
|
||||
</Link>
|
||||
to The Sign In
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs font-normal text-default-500 z-[999] pb-10 text-center">
|
||||
<Copyright />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -13,14 +13,16 @@ import Image from "next/image";
|
|||
import Social from "@/components/partials/auth/social";
|
||||
import Copyright from "@/components/partials/auth/copyright";
|
||||
import Logo from "@/components/partials/auth/logo";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const Login = ({ params: { locale } }: { params: { locale: string } }) => {
|
||||
const t = useTranslations("LandingPage");
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex w-full items-center overflow-hidden min-h-dvh h-dvh basis-full">
|
||||
<div className="overflow-y-auto flex flex-wrap w-full h-dvh">
|
||||
<div
|
||||
className="lg:block hidden flex-1 overflow-hidden text-[40px] leading-[48px] text-default-600 relative z-[1] bg-default-50"
|
||||
>
|
||||
<div className="lg:block hidden flex-1 overflow-hidden text-[40px] leading-[48px] text-default-600 relative z-[1] bg-default-50">
|
||||
<div className="max-w-[520px] pt-16 ps-20 ">
|
||||
<Link href="/" className="mb-6 inline-block">
|
||||
<Image src="/assets/mediahub-logo.png" alt="" width={250} height={250} className="mb-10 w-full h-full" />
|
||||
|
|
@ -33,15 +35,15 @@ const Login = ({ params: { locale } }: { params: { locale: string } }) => {
|
|||
<div className="flex-1 relative">
|
||||
<div className=" h-full flex flex-col dark:bg-default-100 bg-white">
|
||||
<div className="max-w-[524px] md:px-[42px] md:py-[44px] p-7 mx-auto w-full text-2xl text-default-900 mb-3 h-full flex flex-col justify-center">
|
||||
<div className="flex justify-center items-center text-center mb-6 lg:hidden ">
|
||||
{/* <div className="flex justify-center items-center text-center mb-6 lg:hidden ">
|
||||
<Link href="/">
|
||||
<Logo />
|
||||
</Link>
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="text-left 2xl:mb-10 mb-4 mt-10">
|
||||
<h4 className="font-semibold text-3xl text-left">Silahkan masuk ke akun Anda terlebih dahulu</h4>
|
||||
<h4 className="font-semibold text-3xl text-left">{t("logInPlease")}</h4>
|
||||
<div className="text-default-500 text-base">
|
||||
Belum punya akun? <span className="text-red-500">registrasi</span>
|
||||
{t("acc")} <span className="text-red-500">{t("reg")}</span>
|
||||
</div>
|
||||
</div>
|
||||
<LoginForm />
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import Image from "next/image";
|
||||
|
||||
import { close, error, loading, registerConfirm } from "@/config/swal";
|
||||
import { Link, useRouter } from "@/i18n/routing";
|
||||
import { getDataByNIK, getDataByNRP, getDataJournalist, getDataPersonil, listCity, listDistricts, listInstitusi, listProvince, postRegistration, saveInstitutes } from "@/service/auth";
|
||||
import { requestOTP, verifyOTP } from "@/service/landing/landing";
|
||||
import { getDataByNIK, getDataByNRP, getDataJournalist, getDataPersonil, listCity, listDistricts, listInstitusi, listProvince, postRegistration, requestOTP, saveInstitutes, verifyOTP } from "@/service/auth";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { useParams, useSearchParams } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
|
|
@ -21,8 +20,8 @@ import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "@/comp
|
|||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Icon } from "@/components/ui/icon";
|
||||
import dynamic from "next/dynamic";
|
||||
import ReactPasswordChecklist from "react-password-checklist";
|
||||
import { Select } from "@/components/ui/select";
|
||||
import sanitizeHtml from "sanitize-html";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
type Inputs = {
|
||||
example: string;
|
||||
|
|
@ -52,7 +51,6 @@ const page = () => {
|
|||
const [emailValidate, setEmailValidate] = useState("");
|
||||
const [otpValidate, setOtpValidate] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [passwordConf, setPasswordConf] = useState("");
|
||||
const [city, setCity] = useState([]);
|
||||
const [isValidPassword, setIsValidPassword] = useState(false);
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
|
@ -68,6 +66,7 @@ const page = () => {
|
|||
const [, setAssociation] = useState<any>();
|
||||
const [warningPassConf] = useState();
|
||||
const [otpValue, setOtpValue] = useState("");
|
||||
const t = useTranslations("LandingPage");
|
||||
|
||||
const [otp1, setOtp1] = useState();
|
||||
const [otp2, setOtp2] = useState();
|
||||
|
|
@ -80,17 +79,25 @@ const page = () => {
|
|||
const nSecondInMiliseconds = 1000;
|
||||
const [, setRefreshTimer] = useState(false);
|
||||
|
||||
const [passwordType, setPasswordType] = React.useState("password");
|
||||
const [passwordConf, setPasswordConf] = React.useState("password");
|
||||
|
||||
const togglePasswordType = () => {
|
||||
setPasswordType((prevType) => (prevType === "password" ? "text" : "password"));
|
||||
setPasswordConf((prevType) => (prevType === "same password" ? "text" : "same password"));
|
||||
};
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
firstName: Yup.string().required("Nama Lengkap tidak boleh kosong"),
|
||||
username: Yup.string().required("Username tidak boleh kosong"),
|
||||
phoneNumber: Yup.string().required("Nomor Telepon tidak boleh kosong"),
|
||||
address: Yup.string().required("Alamat tidak boleh kosong"),
|
||||
email: Yup.string().required("Email tidak boleh kosong"),
|
||||
provinsi: Yup.string().required("Provinsi tidak boleh kosong"),
|
||||
kota: Yup.string().required("Kota tidak boleh kosong"),
|
||||
kecamatan: Yup.string().required("Kecamatan tidak boleh kosong"),
|
||||
password: Yup.string().required("Kata Sandi tidak boleh kosong"),
|
||||
passwordConf: Yup.string().required("Konfirmasi Kata Sandi tidak boleh kosong"),
|
||||
firstName: Yup.string().required(t("nameEmpty")),
|
||||
username: Yup.string().required(t("usernameEmpty")),
|
||||
phoneNumber: Yup.string().required(t("numberEmpty")),
|
||||
address: Yup.string().required(t("addressEmpty")),
|
||||
email: Yup.string().required(t("emailEmpty")),
|
||||
provinsi: Yup.string().required(t("provinceEmpty")),
|
||||
kota: Yup.string().required(t("cityEmpty")),
|
||||
kecamatan: Yup.string().required(t("subdistrictEmpty")),
|
||||
password: Yup.string().required(t("passwordEmpty")),
|
||||
passwordConf: Yup.string().required(t("confirmEmpty")),
|
||||
});
|
||||
|
||||
const formOptions = {
|
||||
|
|
@ -106,22 +113,6 @@ const page = () => {
|
|||
let [timerCount, setTimerCount] = useState(convertMinutesToMiliseconds(1));
|
||||
let interval: any;
|
||||
|
||||
const showPass = () => {
|
||||
setTypePass("text");
|
||||
};
|
||||
|
||||
const hidePass = () => {
|
||||
setTypePass("password");
|
||||
};
|
||||
|
||||
const showPassConf = () => {
|
||||
setTypePassConf("text");
|
||||
};
|
||||
|
||||
const hidePassConf = () => {
|
||||
setTypePassConf("password");
|
||||
};
|
||||
|
||||
const setValUsername = (e: any) => {
|
||||
const uname = e.replaceAll(/[^\w.-]/g, "");
|
||||
setValue("username", uname.toLowerCase());
|
||||
|
|
@ -166,37 +157,37 @@ const page = () => {
|
|||
}
|
||||
}
|
||||
|
||||
// const sanitizedFirstName = sanitizeHtml(data.firstName);
|
||||
const sanitizedFirstName = sanitizeHtml(data.firstName);
|
||||
|
||||
// if (sanitizedFirstName == "") {
|
||||
// error("Invalid Name");
|
||||
// } else {
|
||||
// const datas = {
|
||||
// firstName: sanitizedFirstName,
|
||||
// lastName: "",
|
||||
// username: data.username,
|
||||
// phoneNumber: data.phoneNumber,
|
||||
// email,
|
||||
// address: data.address,
|
||||
// memberIdentity: userIdentity,
|
||||
// provinceId: Number(data.provinsi),
|
||||
// cityId: Number(data.kota),
|
||||
// districtId: Number(data.kecamatan),
|
||||
// password: data.password,
|
||||
// instituteId: Number(institutionId),
|
||||
// roleId: Number(category),
|
||||
// };
|
||||
if (sanitizedFirstName == "") {
|
||||
error("Invalid Name");
|
||||
} else {
|
||||
const datas = {
|
||||
firstName: sanitizedFirstName,
|
||||
lastName: sanitizedFirstName,
|
||||
username: data.username,
|
||||
phoneNumber: data.phoneNumber,
|
||||
email,
|
||||
address: data.address,
|
||||
memberIdentity: userIdentity,
|
||||
provinceId: Number(data.provinsi),
|
||||
cityId: Number(data.kota),
|
||||
districtId: Number(data.kecamatan),
|
||||
password: data.password,
|
||||
instituteId: Number(institutionId),
|
||||
roleId: Number(category),
|
||||
};
|
||||
|
||||
// const response = await postRegistration(datas);
|
||||
// console.log(response);
|
||||
// if (response?.error) {
|
||||
// error(response?.message);
|
||||
// return false;
|
||||
// }
|
||||
const response = await postRegistration(datas);
|
||||
console.log(response);
|
||||
if (response?.error) {
|
||||
error(response?.message);
|
||||
return false;
|
||||
}
|
||||
|
||||
// registerConfirm();
|
||||
// return false;
|
||||
// }
|
||||
registerConfirm();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const handleSendOTP = async () => {
|
||||
|
|
@ -367,7 +358,7 @@ const page = () => {
|
|||
setEmailValidate("");
|
||||
setEmail(e);
|
||||
} else {
|
||||
setEmailValidate("Email tidak valid");
|
||||
setEmailValidate(t("emailValid"));
|
||||
setEmail("");
|
||||
}
|
||||
};
|
||||
|
|
@ -513,22 +504,9 @@ const page = () => {
|
|||
initState();
|
||||
}, [institusi]);
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
if (category != undefined) {
|
||||
const resInstiution = await listInstitusi(category);
|
||||
const res = await listProvince();
|
||||
setInstitution(resInstiution?.data.data);
|
||||
setProvince(res?.data.data);
|
||||
}
|
||||
}
|
||||
|
||||
initState();
|
||||
}, [category]);
|
||||
|
||||
return (
|
||||
<div className="overflow-y-auto flex flex-wrap w-full h-dvh">
|
||||
<div className="lg:block hidden flex-1 overflow-hidden bg-black text-[40px] leading-[48px] text-default-600 relative z-[1]">
|
||||
<div className="lg:block hidden flex-1 overflow-hidden bg-[#f7f7f7] text-[40px] leading-[48px] text-default-600 relative z-[1]">
|
||||
<div className="max-w-[520px] pt-16 ps-20 ">
|
||||
<Link href="/" className="mb-6 inline-block">
|
||||
<Image src="/assets/mediahub-logo.png" alt="" width={250} height={250} className="mb-10 w-full h-full" />
|
||||
|
|
@ -540,7 +518,7 @@ const page = () => {
|
|||
</div>
|
||||
|
||||
<form
|
||||
className="flex-1 bg-[#f7f7f7]"
|
||||
className="flex-1 w-full bg-white"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
style={
|
||||
Number(category) < 5
|
||||
|
|
@ -584,16 +562,16 @@ const page = () => {
|
|||
: {}
|
||||
}
|
||||
>
|
||||
<p className="text-4xl px-12 font-bold ">{stepThreeActive ? "Silahkan lengkapi data pengguna" : "Silahkan registrasi terlebih dahulu"}</p>
|
||||
<p className="px-12 mt-2">
|
||||
Sudah memiliki akun?{" "}
|
||||
<p className="text-2xl lg:text-4xl px-0 lg:px-12 font-bold ">{stepThreeActive ? t("userData") : t("registerFirst")}</p>
|
||||
<p className="px-0 lg:px-12 text-sm lg:text-base mt-2">
|
||||
{t("alreadyHave")}{" "}
|
||||
<Link href="/auth" className="text-red-500">
|
||||
<b>Masuk</b>
|
||||
<b>{t("logIn")}</b>
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className="px-20 mb-6"
|
||||
className="px-8 lg:px-20 mb-6"
|
||||
style={
|
||||
!stepTwoActive || (stepOneActive && stepThreeActive)
|
||||
? {
|
||||
|
|
@ -602,14 +580,14 @@ const page = () => {
|
|||
: {}
|
||||
}
|
||||
>
|
||||
<p className="text-black text-2xl px-20 font-semibold">Masukkan Kode OTP</p>
|
||||
<p className="text-red-500 text-sm px-20">Silahkan cek inbox atau kotak spam pada email Anda.</p>
|
||||
<p className="text-black text-2xl px-0 lg:px-20 font-semibold">{t("enterOTP")}</p>
|
||||
<p className="text-red-500 text-sm px-0 lg:px-20">{t("checkInbox")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`flex flex-col gap-3 px-12 ${formProfile == true || stepTwoActive ? "hidden" : ""}`}>
|
||||
<div className={`flex flex-col gap-3 px-8 lg:px-12 ${formProfile == true || stepTwoActive ? "hidden" : ""}`}>
|
||||
<div
|
||||
className=" flex flex-col px-8 "
|
||||
className="flex flex-col px-0 lg:px-8"
|
||||
style={
|
||||
Number(category) == 6
|
||||
? {}
|
||||
|
|
@ -618,12 +596,17 @@ const page = () => {
|
|||
}
|
||||
}
|
||||
>
|
||||
<label htmlFor="association" className="mb-3">
|
||||
<b>Jenis Keanggotaan</b> <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<select className={`py-3 rounded-md border border-black ${errors.association ? "block" : ""}`} {...register("association")} id="association" onChange={(e) => setAssociation(e.target.value)}>
|
||||
<Label htmlFor="association" className="mb-2">
|
||||
{t("member")} <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<select
|
||||
className={`py-2 px-1 rounded-md border text-sm text-slate-400 border-slate-300 bg-white ${errors.association ? "block" : ""}`}
|
||||
{...register("association")}
|
||||
id="association"
|
||||
onChange={(e) => setAssociation(e.target.value)}
|
||||
>
|
||||
<option disabled selected>
|
||||
Pilih Asosiasi
|
||||
{t("association")}
|
||||
</option>
|
||||
<option value="PWI" key="1">
|
||||
PWI (Persatuan Wartawan Indonesia)
|
||||
|
|
@ -638,16 +621,16 @@ const page = () => {
|
|||
AJI (Asosiasi Jurnalis Indonesia)
|
||||
</option>
|
||||
<option value="Wartawan" key="5">
|
||||
Identitas Lainnya
|
||||
{t("otherIdentity")}
|
||||
</option>
|
||||
</select>
|
||||
<div className="hidden w-full mt-[0.25rem] text-lg text-[#dc3545]">{errors.association?.message}</div>
|
||||
</div>
|
||||
|
||||
{Number(category) == 7 ? (
|
||||
<div className="px-8">
|
||||
<div className="px-0 lg:px-[34px]">
|
||||
<label htmlFor="userIdentity" className="mb-3">
|
||||
<b>Nomor Registrasi Polri (NRP)</b> <span className="text-red-500">*</span>
|
||||
<b>{t("policeNumber")}</b> <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Input className="mt-3" autoComplete="off" placeholder="Masukan Nomor Registrasi Polri Anda" type="text" onChange={(event: any) => setPersonilNRP(event.target.value)} />
|
||||
<p className="text-red-500">{userIdentityValidate}</p>
|
||||
|
|
@ -657,7 +640,7 @@ const page = () => {
|
|||
)}
|
||||
{Number(category) == 6 ? (
|
||||
<div
|
||||
className="px-8 mb-3"
|
||||
className="px-0 lg:px-[34px]"
|
||||
style={
|
||||
Number(category) == 6
|
||||
? {}
|
||||
|
|
@ -666,24 +649,24 @@ const page = () => {
|
|||
}
|
||||
}
|
||||
>
|
||||
<label htmlFor="journalistCertificate">
|
||||
<b>Nomor Sertifikasi Wartawan</b> <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Input className="rounded-md py-3" autoComplete="off" placeholder="Masukan Nomor Sertifikasi" type="text" onChange={(event: any) => setJournalistCertificate(event.target.value)} />
|
||||
<Label htmlFor="journalistCertificate">
|
||||
{t("journalistNumber")} <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input className="rounded-md py-3" autoComplete="off" placeholder={t("inputJournalist")} type="text" onChange={(event: any) => setJournalistCertificate(event.target.value)} />
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
<div className="flex flex-col w-full px-8 gap-2">
|
||||
<div className="flex flex-col w-full px-0 lg:px-8 gap-2">
|
||||
<label htmlFor="email">
|
||||
<b>Email</b> <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Input className="w-full" autoComplete="off" placeholder="Masukan Email Anda" type="email" onChange={(event) => checkEmail(event.target.value)} />
|
||||
<Input className="w-full" autoComplete="off" placeholder={t("inputEmail")} type="email" onChange={(event) => checkEmail(event.target.value)} />
|
||||
<p className="text-sm text-red-500 mt-1">{emailValidate}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`flex flex-col mt-4 px-12 ${!stepTwoActive || stepThreeActive ? "hidden" : ""}`}>
|
||||
<div className={`flex flex-col mt-4 px-8 lg:px-12 ${!stepTwoActive || stepThreeActive ? "hidden" : ""}`}>
|
||||
<div className="flex justify-center mb-6">
|
||||
<InputOTP maxLength={6} onChange={(e) => setOtpValue(e)}>
|
||||
<InputOTPGroup>
|
||||
|
|
@ -705,29 +688,30 @@ const page = () => {
|
|||
<p className="text-red-500">
|
||||
<b>{otpValidate}</b>
|
||||
</p>
|
||||
<div className="flex flex-row px-28 justify-between items-center my-4">
|
||||
<div className="flex flex-row px-0 lg:px-28 justify-between items-center my-4">
|
||||
<a className="bg-slate-300 text-center rounded-lg mr-1 w-[200px] py-2 text-base cursor-pointer" onClick={() => handleResendOTP()}>
|
||||
Kirim Ulang ({convertMilisecondsToHour(timerCount)})
|
||||
{t("resending")} ({convertMilisecondsToHour(timerCount)})
|
||||
</a>
|
||||
<a className="bg-red-700 w-[200px] py-2 text-center text-white rounded-lg ml-1 cursor-pointer" onClick={() => handleVerifyOTP()}>
|
||||
Lanjut
|
||||
{t("next")}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`flex flex-col gap-3 my-4 px-12 ${formProfile == false ? "hidden" : ""}`}>
|
||||
<div className={`flex flex-col px-8 lg:px-12 ${formProfile == false ? "hidden" : ""}`}>
|
||||
<div>
|
||||
{Number(category) == 6 || Number(category) == 7 ? (
|
||||
<div className="">
|
||||
<label className="">
|
||||
<b>{`${Number(category) == 6 ? "Nomor Sertifikasi Wartawan" : "NRP"}`}</b> <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="px-0 lg:px-[34px]">
|
||||
<Label className="mb-2">
|
||||
{`${Number(category) == 6 ? t("journalistNumber") : "NRP"}`}
|
||||
<span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
className={` ${errors.memberIdentity ? "block" : ""}`}
|
||||
className={`mb-3 ${errors.memberIdentity ? "block" : ""}`}
|
||||
{...register("memberIdentity")}
|
||||
placeholder="Masukan Nomor Identitas"
|
||||
placeholder={t("inputNumberIdentity")}
|
||||
onChange={(e) => handleIdentity(e.target.value)}
|
||||
disabled
|
||||
/>
|
||||
|
|
@ -736,23 +720,23 @@ const page = () => {
|
|||
) : (
|
||||
""
|
||||
)}
|
||||
<div className="mb-4">
|
||||
<label className="mb-2">
|
||||
<b>Nama Lengkap</b> <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Input type="text" autoComplete="off" className={` ${errors.firstName ? "block" : ""}`} {...register("firstName")} placeholder="Masukan Nama Lengkap Anda" />
|
||||
<div className="mb-4 px-0 lg:px-[34px]">
|
||||
<Label className="mb-2">
|
||||
{t("fullName")} <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input type="text" autoComplete="off" className={` ${errors.firstName ? "block" : ""}`} {...register("firstName")} placeholder={t("enterFullName")} />
|
||||
<div className="text-red-500">{errors.firstName?.message}</div>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="mb-2">
|
||||
<b>Username</b> <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="mb-4 px-0 lg:px-[34px]">
|
||||
<Label className="mb-2">
|
||||
Username <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="username"
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
className={`form-control ${errors.username ? "block" : ""}`}
|
||||
{...register("username")}
|
||||
placeholder="Masukan Username"
|
||||
className={`${errors.username ? "block" : ""}`}
|
||||
placeholder={t("enterUsername")}
|
||||
onChange={(e) => {
|
||||
setValUsername(e.target.value.trim());
|
||||
}}
|
||||
|
|
@ -765,37 +749,37 @@ const page = () => {
|
|||
/>
|
||||
<div className="text-red-500">{errors.username?.message}</div>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="mb-2">
|
||||
<b>Email</b> <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Input type="email" autoComplete="off" className={` ${errors.email ? "block" : ""}`} {...register("email")} placeholder="Masukan Email Anda" disabled />
|
||||
<div className="mb-4 px-0 lg:px-[34px]">
|
||||
<Label className="mb-2">
|
||||
Email <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input type="email" autoComplete="off" className={`${errors.email ? "block" : ""}`} {...register("email")} placeholder="Masukan Email Anda" disabled />
|
||||
<div className="text-red-500">{errors.email?.message}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<label className="mb-2">
|
||||
<b>No. HP</b> <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Input type="number" autoComplete="off" className={` ${errors.phoneNumber ? "block" : ""}`} {...register("phoneNumber")} placeholder="Masukan Nomor Telepon Anda" />
|
||||
<div className="flex flex-col px-0 lg:px-[34px]">
|
||||
<Label className="mb-2">
|
||||
{t("number")} <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input type="number" autoComplete="off" className={`mb-3 ${errors.phoneNumber ? "block" : ""}`} {...register("phoneNumber")} placeholder={t("enterNumber")} />
|
||||
<div className="text-red-500">{errors.phoneNumber?.message}</div>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label htmlFor="address" className="mb-2">
|
||||
<b>Alamat</b> <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Textarea className={`form-control ${errors.address ? "block" : ""}`} {...register("address")} placeholder="Masukan Alamat Lengkap Anda" rows={3} />
|
||||
<div className="mb-4 px-0 lg:px-[34px]">
|
||||
<Label htmlFor="address" className="mb-2">
|
||||
{t("address")} <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Textarea className={` ${errors.address ? "block" : ""}`} {...register("address")} placeholder={t("insertAddress")} rows={3} />
|
||||
<div className="text-red-500">{errors.address?.message}</div>
|
||||
</div>
|
||||
{Number(category) == 6 ? (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="">
|
||||
<label htmlFor="provinsi">
|
||||
<b>Pilih Institusi</b> <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<select className="" id="provinsi" onChange={(event) => handleInstituteOption(event)}>
|
||||
<div className="flex flex-col gap-3 px-0 lg:px-[34px]">
|
||||
<div className="flex flex-col mb-2">
|
||||
<Label htmlFor="provinsi" className="mb-2">
|
||||
{t("institutions")} <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<select className="mb-3 p-2 border text-sm text-slate-400 rounded-md border-slate-300 bg-white cursor-pointer" id="provinsi" onChange={(event) => handleInstituteOption(event)}>
|
||||
<option disabled selected>
|
||||
Pilih Institusi
|
||||
{t("selectInst")}
|
||||
</option>
|
||||
{institution.map((row: any) => (
|
||||
<option value={row.id} key={row.id}>
|
||||
|
|
@ -803,12 +787,12 @@ const page = () => {
|
|||
</option>
|
||||
))}
|
||||
<option value="0" key="0">
|
||||
Nama Institusi Lainnya
|
||||
{t("otherInst")}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div
|
||||
className=""
|
||||
className="px-0 lg:px-[34px]"
|
||||
style={
|
||||
isCustomActive == false
|
||||
? {
|
||||
|
|
@ -817,28 +801,34 @@ const page = () => {
|
|||
: {}
|
||||
}
|
||||
>
|
||||
<label htmlFor="alamat" className="mb-2">
|
||||
<b>Nama Institusi</b> <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Input className="" autoComplete="off" placeholder="Masukan Nama Lengkap Institusi Anda" type="text" onChange={(event) => setCustomInstituteName(event.target.value)} />
|
||||
<Label htmlFor="alamat" className="mb-2">
|
||||
{t("instName")} <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input className="mb-3" autoComplete="off" placeholder="Masukan Nama Lengkap Institusi Anda" type="text" onChange={(event) => setCustomInstituteName(event.target.value)} />
|
||||
</div>
|
||||
<div className="">
|
||||
<label htmlFor="alamat" className="">
|
||||
<b>Alamat Institusi</b> <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Textarea className="" placeholder="Masukan Alamat Lengkap Institusi Anda" rows={3} value={institusiAddress} onChange={(event) => setInstitusiAddress(event.target.value)} />
|
||||
<div>
|
||||
<Label htmlFor="alamat" className="mb-2">
|
||||
{t("instAddress")} <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Textarea className="mb-3" placeholder={t("addressInst")} rows={3} value={institusiAddress} onChange={(event) => setInstitusiAddress(event.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="provinsi" className="mb-2">
|
||||
<b>Provinsi</b> <span className="text-red-500">*</span>
|
||||
<div className="flex flex-col px-0 lg:px-[34px]">
|
||||
<label htmlFor="provinsi" className="mb-2 ">
|
||||
{t("province")} <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<select className={` ${errors.provinsi ? "block" : ""}`} {...register("provinsi")} id="provinsi" name="provinsi" onChange={(event) => getCity(event.target.value)}>
|
||||
<select
|
||||
className={`mb-3 p-2 border rounded-md text-sm text-slate-400 border-slate-300 bg-white cursor-pointer ${errors.provinsi ? "block" : ""}`}
|
||||
{...register("provinsi")}
|
||||
id="provinsi"
|
||||
name="provinsi"
|
||||
onChange={(event) => getCity(event.target.value)}
|
||||
>
|
||||
<option disabled selected>
|
||||
Pilih Provinsi
|
||||
{t("selectProv")}
|
||||
</option>
|
||||
{province?.map((row: any) => (
|
||||
<option value={row.id} key={row.id}>
|
||||
|
|
@ -848,13 +838,18 @@ const page = () => {
|
|||
</select>
|
||||
<div className="text-red-500">{errors.provinsi?.message}</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col px-0 lg:px-[34px]">
|
||||
<label htmlFor="kota" className="mb-2">
|
||||
<b>Kota/Kabupaten</b> <span className="text-red-500">*</span>
|
||||
{t("city")} <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<select className={` ${errors.kota ? "block" : ""}`} {...register("kota")} id="kota" onChange={(event) => getDistricts(event.target.value)}>
|
||||
<select
|
||||
className={`mb-3 p-2 border text-sm text-slate-400 rounded-md border-slate-300 bg-white cursor-pointer ${errors.kota ? "block" : ""}`}
|
||||
{...register("kota")}
|
||||
id="kota"
|
||||
onChange={(event) => getDistricts(event.target.value)}
|
||||
>
|
||||
<option disabled selected>
|
||||
Pilih Kota/Kabupaten
|
||||
{t("selectCity")}
|
||||
</option>
|
||||
{city?.map((row: any) => (
|
||||
<option value={row.id} key={row.id}>
|
||||
|
|
@ -864,13 +859,13 @@ const page = () => {
|
|||
</select>
|
||||
<div className="text-red-500">{errors.kota?.message}</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col px-0 lg:px-[34px]">
|
||||
<label htmlFor="kecamatan" className="mb-2">
|
||||
<b>Kecamatan</b> <span className="text-red-500">*</span>
|
||||
{t("subdistrict")} <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<select className={` ${errors.kecamatan ? "block" : ""}`} {...register("kecamatan")} id="kecamatan">
|
||||
<select className={`p-2 border text-sm text-slate-400 rounded-md border-slate-300 bg-white cursor-pointer ${errors.kecamatan ? "block" : ""}`} {...register("kecamatan")} id="kecamatan">
|
||||
<option disabled selected>
|
||||
Pilih Kecamatan
|
||||
{t("selectSub")}
|
||||
</option>
|
||||
{districts?.map((row: any) => (
|
||||
<option value={row.id} key={row.id}>
|
||||
|
|
@ -880,7 +875,7 @@ const page = () => {
|
|||
</select>
|
||||
<div className="text-red-500">{errors.kecamatan?.message}</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
{/* <div className="flex flex-col">
|
||||
<label htmlFor="password" className="mb-2">
|
||||
<b>Kata Sandi</b> <span className="text-red-500">*</span>
|
||||
</label>
|
||||
|
|
@ -900,16 +895,45 @@ const page = () => {
|
|||
setValPassword(e.target.value.trim());
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<div className="text-red-500">{errors.password?.message}</div>
|
||||
<a className={`text-black ${typePass == "text" ? "hidden" : ""}`} onClick={showPass}>
|
||||
/>{" "}
|
||||
<a className={`text-black place-items-end -top-44 ${typePass == "text" ? "hidden" : ""}`} onClick={showPass}>
|
||||
<Icon icon="fa:eye" />
|
||||
</a>
|
||||
<a className={`text-black ${typePass == "password" ? "hidden" : ""}`} onClick={hidePass}>
|
||||
<Icon icon="fa:eye-slash" />
|
||||
</a>
|
||||
<div className="text-red-500">{errors.password?.message}</div>
|
||||
</div> */}
|
||||
<div className="mt-3.5 space-y-2 px-0 lg:px-[34px]">
|
||||
<Label htmlFor="password" className="mb-2 font-medium text-default-600">
|
||||
{t("password")} <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
size="lg"
|
||||
type={passwordType}
|
||||
autoComplete="off"
|
||||
className={` ${errors.password ? "block" : ""}`}
|
||||
{...register("password")}
|
||||
placeholder={t("inputPass")}
|
||||
onChange={(e) => {
|
||||
setValPassword(e.target.value.trim());
|
||||
}}
|
||||
onPaste={(e: any) => {
|
||||
setValPassword(e.target.value.trim());
|
||||
}}
|
||||
onCopy={(e: any) => {
|
||||
setValPassword(e.target.value.trim());
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<div className="absolute top-1/2 -translate-y-1/2 ltr:right-4 rtl:left-4 cursor-pointer" onClick={togglePasswordType}>
|
||||
{passwordType === "password" ? <Icon icon="heroicons:eye" className="w-5 h-5 text-default-400" /> : <Icon icon="heroicons:eye-slash" className="w-5 h-5 text-default-400" />}{" "}
|
||||
</div>
|
||||
</div>
|
||||
{errors.password?.message && <div className="text-destructive mt-2 text-sm">{errors.password.message}</div>}
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
{/* <div className="flex flex-col">
|
||||
<label htmlFor="password" className="mb-2">
|
||||
<b>Konfirmasi Kata Sandi</b> <span className="text-red-500">*</span>
|
||||
</label>
|
||||
|
|
@ -937,8 +961,37 @@ const page = () => {
|
|||
<a className={`text-dark show-pass ${typePassConf == "password" ? "hidden" : ""}`} onClick={hidePassConf}>
|
||||
<Icon icon="fa:eye-slash" />
|
||||
</a>
|
||||
</div> */}
|
||||
<div className="mt-3.5 space-y-2 px-0 lg:px-[34px]">
|
||||
<Label htmlFor="password" className="mb-2 font-medium text-default-600">
|
||||
{t("confirmPass")} <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
size="lg"
|
||||
type={passwordConf}
|
||||
autoComplete="off"
|
||||
className={` ${errors.passwordConf ? "block" : ""}`}
|
||||
{...register("passwordConf")}
|
||||
placeholder={t("samePass")}
|
||||
onChange={(e) => {
|
||||
setValPasswordConf(e.target.value.trim());
|
||||
}}
|
||||
onPaste={(e: any) => {
|
||||
setValPasswordConf(e.target.value.trim());
|
||||
}}
|
||||
onCopy={(e: any) => {
|
||||
setValPasswordConf(e.target.value.trim());
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<div className="absolute top-1/2 -translate-y-1/2 ltr:right-4 rtl:left-4 cursor-pointer" onClick={togglePasswordType}>
|
||||
{passwordConf === "same password" ? <Icon icon="heroicons:eye" className="w-5 h-5 text-default-400" /> : <Icon icon="heroicons:eye-slash" className="w-5 h-5 text-default-400" />}{" "}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-red-500">{warningPassConf}</div>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<div className="form-group px-0 lg:px-[34px]">
|
||||
<PasswordChecklist
|
||||
rules={["minLength", "specialChar", "number", "capital", "match"]}
|
||||
minLength={8}
|
||||
|
|
@ -948,18 +1001,18 @@ const page = () => {
|
|||
setIsValidPassword(isValid);
|
||||
}}
|
||||
messages={{
|
||||
minLength: "Password harus lebih dari 8 karakter",
|
||||
specialChar: "Password harus memiliki spesial karakter",
|
||||
number: "Password harus memiliki angka",
|
||||
capital: "Password harus memiliki huruf kapital",
|
||||
match: "Password sama",
|
||||
minLength: t("passCharacter"),
|
||||
specialChar: t("passSpecial"),
|
||||
number: t("passNumber"),
|
||||
capital: t("passCapital"),
|
||||
match: t("passSame"),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="flex flex-col mt-8 mb-2 px-12"
|
||||
className="flex flex-col mt-8 mb-2"
|
||||
style={
|
||||
(stepOneActive && !stepTwoActive) || stepThreeActive
|
||||
? {}
|
||||
|
|
@ -968,33 +1021,33 @@ const page = () => {
|
|||
}
|
||||
}
|
||||
>
|
||||
<div className="text-center mb-2">
|
||||
<p>
|
||||
Dengan mendaftar, saya telah menyetujui <br />{" "}
|
||||
<div className="text-center mb-2 px-[34px]">
|
||||
<p className="text-sm lg:text-base">
|
||||
{t("byRegis")} <br />{" "}
|
||||
<a href="/privacy" target="_blank" className="text-red-500">
|
||||
<b>Syarat dan Ketentuan</b>
|
||||
<b>{t("terms")}</b>
|
||||
</a>{" "}
|
||||
serta{" "}
|
||||
{t("and")}{" "}
|
||||
<a href="/privacy" target="_blank" className="text-red-500">
|
||||
<b>Kebijakan Privasi</b>
|
||||
<b>{t("privacy")}</b>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div className={`mb-5 mt-7 w-full text-center flex justify-center ${formProfile == true || stepTwoActive ? "hidden" : ""}`}>
|
||||
<div className={`mb-5 mt-7 px-[34px] w-full text-center flex justify-center ${formProfile == true || stepTwoActive ? "hidden" : ""}`}>
|
||||
{/* <a className="border cursor-pointer border-red-500 px-4 py-3 rounded-lg text-white bg-[#dc3545] w-full" onClick={() => handleSendOTP()}> */}
|
||||
<a
|
||||
className="border cursor-pointer border-red-500 px-4 py-3 rounded-lg text-white bg-[#dc3545] w-[400px]"
|
||||
className="border cursor-pointer border-red-500 px-4 py-3 rounded-lg text-white bg-[#dc3545] w-[550px]"
|
||||
onClick={() => {
|
||||
setStepOneActive(false);
|
||||
setStepTwoActive(true);
|
||||
}}
|
||||
>
|
||||
Kirim OTP
|
||||
{t("send")} OTP
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="flex flex-row mt-2 mb-4"
|
||||
className="flex justify-center items-center mt-2 mb-4 px-[34px]"
|
||||
style={
|
||||
formProfile == false
|
||||
? {
|
||||
|
|
@ -1003,11 +1056,9 @@ const page = () => {
|
|||
: {}
|
||||
}
|
||||
>
|
||||
<div className="text-center">
|
||||
<button type="submit" className="border border-red-500 text-red-500">
|
||||
Daftar
|
||||
</button>
|
||||
</div>
|
||||
<Button type="submit" className="border w-[550px] text-center bg-red-700 text-white hover:bg-white hover:text-red-700 ">
|
||||
{t("register")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -11,21 +11,25 @@ import Division from "@/components/landing-page/division";
|
|||
import Navbar from "@/components/landing-page/navbar";
|
||||
import { ReactLenis } from "@studio-freight/react-lenis";
|
||||
import MountedProvider from "@/providers/mounted.provider";
|
||||
import NewsTicker from "@/components/landing-page/news-tickers";
|
||||
|
||||
const Home = ({ params: { locale } }: { params: { locale: string } }) => {
|
||||
return (
|
||||
<MountedProvider isProtected={false}>
|
||||
<ReactLenis root>
|
||||
<Navbar />
|
||||
<Hero />
|
||||
<SearchSection />
|
||||
<NewContent group="mabes" type="latest" />
|
||||
<NewContent group="mabes" type="popular" />
|
||||
{/* <PopularContent /> */}
|
||||
<ContentCategory group="mabes" />
|
||||
<Coverage />
|
||||
<Division />
|
||||
<Footer />
|
||||
<div className="pb-14">
|
||||
<Navbar />
|
||||
<Hero />
|
||||
<SearchSection />
|
||||
<NewContent group="mabes" type="latest" />
|
||||
<NewContent group="mabes" type="popular" />
|
||||
{/* <PopularContent /> */}
|
||||
<ContentCategory group="mabes" />
|
||||
<Coverage />
|
||||
<Division />
|
||||
<Footer />
|
||||
<NewsTicker />
|
||||
</div>
|
||||
</ReactLenis>
|
||||
</MountedProvider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import {
|
|||
} from "@/service/content/content";
|
||||
import { getBlog, postBlog } from "@/service/blog/blog";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -76,6 +77,13 @@ const initialCategories: Category[] = [
|
|||
},
|
||||
];
|
||||
|
||||
const ViewEditor = dynamic(
|
||||
() => {
|
||||
return import("@/components/editor/view-editor");
|
||||
},
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export default function FormBlogDetail() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -202,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>
|
||||
|
|
@ -236,12 +244,7 @@ export default function FormBlogDetail() {
|
|||
control={control}
|
||||
name="narration"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<JoditEditor
|
||||
ref={editor}
|
||||
value={detail.narration}
|
||||
onChange={onChange}
|
||||
className="dark:text-black"
|
||||
/>
|
||||
<ViewEditor initialData={detail?.narration} />
|
||||
)}
|
||||
/>
|
||||
{errors.narration?.message && (
|
||||
|
|
@ -299,8 +302,8 @@ export default function FormBlogDetail() {
|
|||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div className="w-4/12">
|
||||
<Card className=" h-[600px]">
|
||||
<div className="w-full lg:w-4/12">
|
||||
<Card className=" h-[650px]">
|
||||
<div className="px-3 py-3">
|
||||
<Label htmlFor="fileInput">Gambar Utama</Label>
|
||||
<Input
|
||||
|
|
|
|||
|
|
@ -28,8 +28,10 @@ import {
|
|||
getTagsBySubCategoryId,
|
||||
listEnableCategory,
|
||||
} from "@/service/content/content";
|
||||
import { getBlog, postBlog } from "@/service/blog/blog";
|
||||
import { getBlog, postBlog, uploadThumbnailBlog } from "@/service/blog/blog";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import dynamic from "next/dynamic";
|
||||
import { loading } from "@/lib/swal";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -38,7 +40,7 @@ const taskSchema = z.object({
|
|||
narration: z
|
||||
.string()
|
||||
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
|
||||
categoryName: z.string().min(1, { message: "Kategori diperlukan" }),
|
||||
categoryId: z.string().min(1, { message: "Kategori diperlukan" }),
|
||||
});
|
||||
|
||||
type Category = {
|
||||
|
|
@ -77,6 +79,13 @@ const initialCategories: Category[] = [
|
|||
},
|
||||
];
|
||||
|
||||
const CustomEditor = dynamic(
|
||||
() => {
|
||||
return import("@/components/editor/custom-editor");
|
||||
},
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export default function FormBlogUpdate() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -98,6 +107,9 @@ export default function FormBlogUpdate() {
|
|||
|
||||
const [detail, setDetail] = useState<Detail>();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [thumbnail, setThumbnail] = useState<File | null>(null);
|
||||
const [preview, setPreview] = useState<string | null>(null);
|
||||
|
||||
const [unitSelection, setUnitSelection] = useState({
|
||||
allUnit: false,
|
||||
|
|
@ -117,18 +129,6 @@ export default function FormBlogUpdate() {
|
|||
resolver: zodResolver(taskSchema),
|
||||
});
|
||||
|
||||
const handleRemoveTag = (index: any) => {
|
||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.files) {
|
||||
const files = Array.from(event.target.files);
|
||||
setSelectedFiles((prevImages: any) => [...prevImages, ...files]);
|
||||
console.log("DATAFILE::", selectedFiles);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveImage = (index: number) => {
|
||||
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
|
||||
};
|
||||
|
|
@ -141,15 +141,20 @@ export default function FormBlogUpdate() {
|
|||
|
||||
setDetail(details);
|
||||
|
||||
// Set categoryId dari API ke form dan Select
|
||||
setValue("categoryName", details?.categoryName);
|
||||
setSelectedTarget(details?.categoryId); // Untuk dropdown
|
||||
if (details?.tags) {
|
||||
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
|
||||
}
|
||||
|
||||
setValue("categoryId", details?.categoryName);
|
||||
setSelectedTarget(details?.categoryId);
|
||||
}
|
||||
}
|
||||
initState();
|
||||
}, [refresh, setValue]);
|
||||
|
||||
const save = async (data: TaskSchema) => {
|
||||
loading();
|
||||
const finalTags = tags.join(", ");
|
||||
const requestData = {
|
||||
...data,
|
||||
id: detail?.id,
|
||||
|
|
@ -158,7 +163,7 @@ export default function FormBlogUpdate() {
|
|||
categoryId: selectedTarget,
|
||||
slug: data.slug,
|
||||
metadata: data.metadata,
|
||||
tags: "siap",
|
||||
tags: finalTags,
|
||||
isDraft,
|
||||
};
|
||||
|
||||
|
|
@ -166,6 +171,30 @@ export default function FormBlogUpdate() {
|
|||
console.log("Form Data Submitted:", requestData);
|
||||
console.log("response", response);
|
||||
|
||||
if (response?.error) {
|
||||
MySwal.fire("Error", response?.message, "error");
|
||||
return;
|
||||
}
|
||||
|
||||
const blogId = response?.data?.data.id;
|
||||
if (blogId) {
|
||||
if (thumbnail) {
|
||||
const formMedia = new FormData();
|
||||
formMedia.append("file", thumbnail); // Tambahkan file ke FormData
|
||||
console.log("FormMedia:", formMedia.get("file"));
|
||||
|
||||
const responseThumbnail = await uploadThumbnailBlog(blogId, formMedia);
|
||||
|
||||
if (responseThumbnail?.error) {
|
||||
MySwal.fire("Error", responseThumbnail?.message, "error");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
console.log("No thumbnail to upload");
|
||||
}
|
||||
}
|
||||
close();
|
||||
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Data berhasil disimpan.",
|
||||
|
|
@ -193,6 +222,17 @@ export default function FormBlogUpdate() {
|
|||
});
|
||||
};
|
||||
|
||||
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
setThumbnail(file); // Simpan file ke state
|
||||
setPreview(URL.createObjectURL(file)); // Buat URL untuk preview
|
||||
console.log("Selected Thumbnail:", file);
|
||||
} else {
|
||||
console.log("No file selected");
|
||||
}
|
||||
};
|
||||
|
||||
const handlePublish = () => {
|
||||
setIsDraft(false);
|
||||
};
|
||||
|
|
@ -201,10 +241,33 @@ export default function FormBlogUpdate() {
|
|||
setIsDraft(false);
|
||||
};
|
||||
|
||||
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter" && e.currentTarget.value.trim()) {
|
||||
e.preventDefault();
|
||||
const newTag = e.currentTarget.value.trim();
|
||||
if (!tags.includes(newTag)) {
|
||||
setTags((prevTags) => [...prevTags, newTag]); // Tambahkan tag baru
|
||||
if (inputRef.current) {
|
||||
inputRef.current.value = ""; // Kosongkan input
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveTag = (index: number) => {
|
||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleEditTag = (index: number, newValue: string) => {
|
||||
setTags((prevTags) =>
|
||||
prevTags.map((tag, i) => (i === index ? newValue : tag))
|
||||
);
|
||||
};
|
||||
|
||||
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>
|
||||
|
|
@ -238,11 +301,9 @@ export default function FormBlogUpdate() {
|
|||
control={control}
|
||||
name="narration"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<JoditEditor
|
||||
ref={editor}
|
||||
value={detail.narration}
|
||||
<CustomEditor
|
||||
onChange={onChange}
|
||||
className="dark:text-black"
|
||||
initialData={detail?.narration || value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
@ -301,32 +362,40 @@ export default function FormBlogUpdate() {
|
|||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div className="w-4/12">
|
||||
<Card className=" h-[600px]">
|
||||
<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
|
||||
<input
|
||||
id="fileInput"
|
||||
type="file"
|
||||
// onChange={(e) => {
|
||||
// const file = e.target.files[0];
|
||||
// if (file) {
|
||||
// console.log("Selected File:", file);
|
||||
// // Tambahkan logika jika diperlukan, misalnya upload file ke server
|
||||
// }
|
||||
// }}
|
||||
className=""
|
||||
onChange={handleImageChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-3 px-3">
|
||||
<Label>Pratinjau Gambar Utama</Label>
|
||||
<Card className="mt-2">
|
||||
<img
|
||||
src={detail.thumbnailLink}
|
||||
alt="Thumbnail Gambar Utama"
|
||||
className="w-full h-auto rounded"
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{preview ? (
|
||||
// Menampilkan pratinjau gambar yang baru dipilih
|
||||
<div className="mt-3 px-3">
|
||||
<img
|
||||
src={preview}
|
||||
alt="Thumbnail Preview"
|
||||
className="w-full h-auto rounded"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
// Menampilkan gambar dari `detail.thumbnailLink` jika tidak ada file yang dipilih
|
||||
detail?.thumbnailLink && (
|
||||
<div className="mt-3 px-3">
|
||||
<Label>Pratinjau Gambar Utama</Label>
|
||||
<Card className="mt-2">
|
||||
<img
|
||||
src={detail.thumbnailLink}
|
||||
alt="Thumbnail Gambar Utama"
|
||||
className="w-full h-auto rounded"
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<div className="px-3 py-3 mt-6">
|
||||
<label
|
||||
|
|
@ -336,7 +405,7 @@ export default function FormBlogUpdate() {
|
|||
Kategori
|
||||
</label>
|
||||
<Controller
|
||||
name="categoryName"
|
||||
name="categoryId"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
|
|
@ -350,12 +419,9 @@ export default function FormBlogUpdate() {
|
|||
<SelectValue placeholder="Pilih" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{categories.map((category) => (
|
||||
<SelectItem
|
||||
key={category.id}
|
||||
value={category.categoryName}
|
||||
>
|
||||
{category.id}
|
||||
{categories.map((category: any) => (
|
||||
<SelectItem key={category.id} value={category.id}>
|
||||
{category.categoryName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
|
|
@ -366,14 +432,34 @@ export default function FormBlogUpdate() {
|
|||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{detail?.tags?.split(",").map((tag, index) => (
|
||||
<Badge
|
||||
<Input
|
||||
type="text"
|
||||
id="tags"
|
||||
placeholder="Add a tag and press Enter"
|
||||
onKeyDown={handleAddTag}
|
||||
ref={inputRef}
|
||||
/>
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{tags.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="border rounded-md px-2 py-2"
|
||||
className="flex items-center gap-2 px-2 py-1 rounded-lg bg-black text-white text-sm"
|
||||
>
|
||||
{tag.trim()}
|
||||
</Badge>
|
||||
<input
|
||||
type="text"
|
||||
value={tag}
|
||||
onChange={(e) => handleEditTag(index, e.target.value)}
|
||||
className="bg-black text-white border-none focus:outline-none w-auto"
|
||||
/>
|
||||
<button
|
||||
value={tag}
|
||||
type="button"
|
||||
onClick={() => handleRemoveTag(index)}
|
||||
className="remove-tag-button text-white"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ import {
|
|||
listEnableCategory,
|
||||
} from "@/service/content/content";
|
||||
import { postBlog, uploadThumbnailBlog } from "@/service/blog/blog";
|
||||
import dynamic from "next/dynamic";
|
||||
import { error } from "console";
|
||||
import { loading } from "@/lib/swal";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -37,7 +40,6 @@ const taskSchema = z.object({
|
|||
narration: z
|
||||
.string()
|
||||
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
|
||||
tags: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
});
|
||||
|
||||
type Category = {
|
||||
|
|
@ -64,6 +66,13 @@ const initialCategories: Category[] = [
|
|||
},
|
||||
];
|
||||
|
||||
const CustomEditor = dynamic(
|
||||
() => {
|
||||
return import("@/components/editor/custom-editor");
|
||||
},
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export default function FormBlog() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -82,6 +91,7 @@ export default function FormBlog() {
|
|||
const [isDraft, setIsDraft] = useState(false);
|
||||
const [thumbnail, setThumbnail] = useState<File | null>(null);
|
||||
const [preview, setPreview] = useState<string | null>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [unitSelection, setUnitSelection] = useState({
|
||||
allUnit: false,
|
||||
|
|
@ -156,6 +166,8 @@ export default function FormBlog() {
|
|||
// };
|
||||
|
||||
const save = async (data: TaskSchema) => {
|
||||
loading();
|
||||
const finalTags = tags.join(", ");
|
||||
const requestData = {
|
||||
...data,
|
||||
title: data.title,
|
||||
|
|
@ -163,7 +175,7 @@ export default function FormBlog() {
|
|||
categoryId: selectedTarget,
|
||||
slug: data.slug,
|
||||
metadata: data.meta,
|
||||
tags: data.tags,
|
||||
tags: finalTags,
|
||||
isDraft,
|
||||
};
|
||||
|
||||
|
|
@ -177,19 +189,33 @@ export default function FormBlog() {
|
|||
}
|
||||
|
||||
const blogId = response?.data?.data.id;
|
||||
if (blogId && thumbnail) {
|
||||
const formMedia = new FormData();
|
||||
formMedia.append("file", thumbnail);
|
||||
if (blogId) {
|
||||
if (thumbnail) {
|
||||
const formMedia = new FormData();
|
||||
formMedia.append("file", thumbnail); // Tambahkan file ke FormData
|
||||
console.log("FormMedia:", formMedia.get("file"));
|
||||
|
||||
const responseThumbnail = await uploadThumbnailBlog(blogId, formMedia);
|
||||
const responseThumbnail = await uploadThumbnailBlog(blogId, formMedia);
|
||||
|
||||
if (responseThumbnail?.error) {
|
||||
MySwal.fire("Error", responseThumbnail?.message, "error");
|
||||
return;
|
||||
if (responseThumbnail?.error) {
|
||||
MySwal.fire("Error", responseThumbnail?.message, "error");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
console.log("No thumbnail to upload");
|
||||
}
|
||||
}
|
||||
close();
|
||||
|
||||
MySwal.fire("Sukses", "Data berhasil disimpan.", "success");
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Data berhasil disimpan.",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then(() => {
|
||||
router.push("/en/contributor/blog");
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmit = (data: TaskSchema) => {
|
||||
|
|
@ -198,8 +224,9 @@ export default function FormBlog() {
|
|||
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "Simpan",
|
||||
cancelButtonText: "Batal",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
save(data);
|
||||
|
|
@ -210,11 +237,11 @@ export default function FormBlog() {
|
|||
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
setThumbnail(file);
|
||||
setThumbnail(file); // Simpan file ke state
|
||||
setPreview(URL.createObjectURL(file)); // Buat URL untuk preview
|
||||
console.log("Selected Thumbnail:", file);
|
||||
}
|
||||
if (file) {
|
||||
setPreview(URL.createObjectURL(file));
|
||||
} else {
|
||||
console.log("No file selected");
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -226,9 +253,22 @@ export default function FormBlog() {
|
|||
setIsDraft(true);
|
||||
};
|
||||
|
||||
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter" && e.currentTarget.value.trim()) {
|
||||
e.preventDefault();
|
||||
const newTag = e.currentTarget.value.trim();
|
||||
if (!tags.includes(newTag)) {
|
||||
setTags((prevTags) => [...prevTags, newTag]); // Add new tag
|
||||
if (inputRef.current) {
|
||||
inputRef.current.value = ""; // Clear input field
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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>
|
||||
|
|
@ -260,12 +300,7 @@ export default function FormBlog() {
|
|||
control={control}
|
||||
name="narration"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<JoditEditor
|
||||
ref={editor}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className="dark:text-black"
|
||||
/>
|
||||
<CustomEditor onChange={onChange} initialData={value} />
|
||||
)}
|
||||
/>
|
||||
{errors.narration?.message && (
|
||||
|
|
@ -323,11 +358,11 @@ export default function FormBlog() {
|
|||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div className="w-4/12">
|
||||
<Card className=" h-[550px]">
|
||||
<div className="w-full lg:w-4/12">
|
||||
<Card className=" h-[600px]">
|
||||
<div className="px-3 py-3">
|
||||
<Label htmlFor="fileInput">Gambar Utama</Label>
|
||||
<Input id="fileInput" type="file" onChange={handleImageChange} />
|
||||
<label htmlFor="fileInput">Gambar Utama</label>
|
||||
<input id="fileInput" type="file" onChange={handleImageChange} />
|
||||
</div>
|
||||
{preview && (
|
||||
<div className="mt-3 px-3">
|
||||
|
|
@ -366,19 +401,30 @@ export default function FormBlog() {
|
|||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<Label>Tags</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="tags"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
size="md"
|
||||
type="text"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Enter Title"
|
||||
/>
|
||||
)}
|
||||
<Input
|
||||
type="text"
|
||||
id="tags"
|
||||
placeholder="Add a tag and press Enter"
|
||||
onKeyDown={handleAddTag}
|
||||
ref={inputRef}
|
||||
/>
|
||||
<div className="mt-3 ">
|
||||
{tags.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className=" px-1 py-1 rounded-lg bg-black text-white mr-2 text-sm font-sans"
|
||||
>
|
||||
{tag}{" "}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveTag(index)}
|
||||
className="remove-tag-button"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
{/* <div className="text-sm text-red-500">
|
||||
{tags.length === 0 && "Please add at least one tag."}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import {
|
|||
} from "@/components/ui/select";
|
||||
import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
getEscalationDiscussion,
|
||||
getTicketingDetail,
|
||||
getTicketingInternalDetail,
|
||||
getTicketingInternalDiscussion,
|
||||
|
|
@ -28,6 +29,8 @@ import {
|
|||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { loading } from "@/lib/swal";
|
||||
import { id } from "date-fns/locale";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -57,9 +60,24 @@ export default function FormDetailEscalation() {
|
|||
const [detail, setDetail] = useState<any>();
|
||||
const [ticketReply, setTicketReply] = useState<replyDetail[]>([]);
|
||||
const [replyVisible, setReplyVisible] = useState(false);
|
||||
const [replyMessage, setReplyMessage] = useState("");
|
||||
const [listDiscussion, setListDiscussion] = useState();
|
||||
const [message, setMessage] = useState("");
|
||||
const [selectedPriority, setSelectedPriority] = useState("");
|
||||
const [selectedStatus, setSelectedStatus] = useState("");
|
||||
const [replyMessage, setReplyMessage] = useState("");
|
||||
const [replies, setReplies] = useState([
|
||||
{
|
||||
id: 1,
|
||||
name: "Mabes Polri - Approver",
|
||||
message: "test",
|
||||
timestamp: "2024-12-20 00:56:10",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Mabes Polri - Approver",
|
||||
message: "balas",
|
||||
timestamp: "2025-01-18 17:42:48",
|
||||
},
|
||||
]);
|
||||
|
||||
const {
|
||||
control,
|
||||
|
|
@ -90,53 +108,32 @@ export default function FormDetailEscalation() {
|
|||
const handleReply = () => {
|
||||
setReplyVisible((prev) => !prev); // Toggle visibility
|
||||
};
|
||||
// useEffect(() => {
|
||||
// async function initState() {
|
||||
// if (id != undefined) {
|
||||
// loading();
|
||||
// const responseGet = await getEscalationDiscussion(id);
|
||||
// close();
|
||||
|
||||
const handleSendReply = async () => {
|
||||
if (replyMessage.trim() === "") {
|
||||
MySwal.fire({
|
||||
title: "Error",
|
||||
text: "Pesan tidak boleh kosong!",
|
||||
icon: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
// console.log("escal data", responseGet?.data);
|
||||
// setListDiscussion(responseGet?.data?.data);
|
||||
// }
|
||||
// }
|
||||
// initState();
|
||||
// }, [id]);
|
||||
|
||||
const data = {
|
||||
ticketId: id,
|
||||
const handleSendReply = () => {
|
||||
if (replyMessage.trim() === "") return;
|
||||
|
||||
const newReply = {
|
||||
id: replies.length + 1,
|
||||
name: "Mabes Polri - Approver", // Sesuaikan dengan data dinamis jika ada
|
||||
message: replyMessage,
|
||||
timestamp: new Date().toISOString().slice(0, 19).replace("T", " "),
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await saveTicketInternalReply(data);
|
||||
|
||||
// Tambahkan balasan baru ke daftar balasan
|
||||
const newReply: replyDetail = {
|
||||
id: response?.data?.id,
|
||||
message: replyMessage,
|
||||
createdAt: response?.data?.createdAt,
|
||||
messageFrom: response?.data?.messageFrom,
|
||||
messageTo: response?.data?.messageTo,
|
||||
};
|
||||
|
||||
setTicketReply((prevReplies) => [newReply, ...prevReplies]);
|
||||
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Pesan berhasil dikirim.",
|
||||
icon: "success",
|
||||
});
|
||||
|
||||
// Reset input dan sembunyikan form balasan
|
||||
setReplyMessage("");
|
||||
setReplyVisible(false);
|
||||
} catch (error) {
|
||||
MySwal.fire({
|
||||
title: "Error",
|
||||
text: "Gagal mengirim balasan.",
|
||||
icon: "error",
|
||||
});
|
||||
console.error("Error sending reply:", error);
|
||||
}
|
||||
setReplies([...replies, newReply]);
|
||||
setReplyMessage("");
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -244,26 +241,46 @@ export default function FormDetailEscalation() {
|
|||
</p>
|
||||
)} */}
|
||||
</div>
|
||||
<div className="mt-4 px-3 mb-3">
|
||||
<Label htmlFor="replyMessage">Tulis Pesan</Label>
|
||||
<div className="mx-3 my-3">
|
||||
<h3 className="text-gray-700 font-medium">Tanggapan</h3>
|
||||
<div className="space-y-4">
|
||||
{replies.map((reply) => (
|
||||
<div key={reply.id} className="border-b pb-2">
|
||||
<p className="font-semibold text-gray-800">{reply.name}</p>
|
||||
<p className="text-gray-600">{reply.message}</p>
|
||||
<p className="text-sm text-gray-400">{reply.timestamp}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-3">
|
||||
<label
|
||||
htmlFor="replyMessage"
|
||||
className="block text-gray-700 font-medium mb-2"
|
||||
>
|
||||
Tulis Tanggapan Anda
|
||||
</label>
|
||||
<textarea
|
||||
id="replyMessage"
|
||||
className="w-full h-24 border rounded-md p-2"
|
||||
className="w-full h-24 border border-gray-300 rounded-md p-2"
|
||||
value={replyMessage}
|
||||
onChange={(e) => setReplyMessage(e.target.value)}
|
||||
placeholder="Tulis pesan di sini..."
|
||||
placeholder="Tulis tanggapan anda di sini..."
|
||||
/>
|
||||
<div className="flex justify-end gap-3 mt-2">
|
||||
<Button
|
||||
onClick={() => setReplyVisible(false)}
|
||||
color="default"
|
||||
variant="outline"
|
||||
<div className="flex justify-end gap-3 mt-2 mb-3">
|
||||
<button
|
||||
onClick={() => setReplyMessage("")}
|
||||
className="px-4 py-2 bg-gray-200 text-gray-800 rounded-md"
|
||||
>
|
||||
Batal
|
||||
</Button>
|
||||
<Button onClick={handleSendReply} color="primary">
|
||||
Kirim
|
||||
</Button>
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSendReply}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-md"
|
||||
>
|
||||
Kirim Pesan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ import {
|
|||
saveTicketInternalReply,
|
||||
} from "@/service/communication/communication";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { list } from "postcss";
|
||||
import { htmlToString } from "@/utils/globals";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -72,6 +74,19 @@ export type replyDetail = {
|
|||
fullname: string;
|
||||
};
|
||||
};
|
||||
export type internalDetail = {
|
||||
id: number;
|
||||
message: string;
|
||||
createdAt: string;
|
||||
createdBy: {
|
||||
id: number;
|
||||
fullname: string;
|
||||
};
|
||||
sendTo: {
|
||||
id: number;
|
||||
fullname: string;
|
||||
};
|
||||
};
|
||||
|
||||
export default function FormDetailInternal() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
|
@ -79,6 +94,9 @@ export default function FormDetailInternal() {
|
|||
|
||||
const [detail, setDetail] = useState<taskDetail>();
|
||||
const [ticketReply, setTicketReply] = useState<replyDetail[]>([]);
|
||||
const [ticketInternal, setTicketInternal] = useState<internalDetail | null>(
|
||||
null
|
||||
);
|
||||
const [replyVisible, setReplyVisible] = useState(false);
|
||||
const [replyMessage, setReplyMessage] = useState("");
|
||||
const [selectedPriority, setSelectedPriority] = useState("");
|
||||
|
|
@ -96,6 +114,7 @@ export default function FormDetailInternal() {
|
|||
async function initState() {
|
||||
if (id) {
|
||||
const response = await getTicketingInternalDetail(id);
|
||||
setTicketInternal(response?.data?.data || null);
|
||||
setDetail(response?.data?.data);
|
||||
}
|
||||
}
|
||||
|
|
@ -203,6 +222,7 @@ export default function FormDetailInternal() {
|
|||
<p className="p-4 bg-slate-300 rounded-t-xl text-lg font-semibold">
|
||||
Ticket #{detail?.referenceNumber}
|
||||
</p>
|
||||
|
||||
{ticketReply?.map((list) => (
|
||||
<div key={list.id} className="flex flex-col">
|
||||
<div className="flex flex-row gap-3 bg-sky-100 p-4 items-center">
|
||||
|
|
@ -229,6 +249,38 @@ export default function FormDetailInternal() {
|
|||
<p className="p-4 bg-white text-sm">{list.message}</p>
|
||||
</div>
|
||||
))}
|
||||
{ticketInternal && (
|
||||
<div key={ticketInternal.id} className="flex flex-col">
|
||||
<div className="flex flex-row gap-3 bg-sky-100 p-4 items-center">
|
||||
<Icon icon="qlementine-icons:user-16" width={36} />
|
||||
<div>
|
||||
<p>
|
||||
<span className="font-bold text-sm">
|
||||
{ticketInternal?.createdBy?.fullname}
|
||||
</span>{" "}
|
||||
mengirimkan pesan untuk{" "}
|
||||
<span className="font-bold text-sm">
|
||||
{ticketInternal?.sendTo?.fullname}
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-xs">
|
||||
{`${new Date(ticketInternal?.createdAt).getDate()}-${
|
||||
new Date(ticketInternal?.createdAt).getMonth() + 1
|
||||
}-${new Date(
|
||||
ticketInternal?.createdAt
|
||||
).getFullYear()} ${new Date(
|
||||
ticketInternal?.createdAt
|
||||
).getHours()}:${new Date(
|
||||
ticketInternal?.createdAt
|
||||
).getMinutes()}`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="p-4 bg-white text-sm">
|
||||
{htmlToString(ticketInternal.message)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{detail !== undefined && (
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import { Switch } from "@/components/ui/switch";
|
|||
import Cookies from "js-cookie";
|
||||
import {
|
||||
createMedia,
|
||||
deleteFile,
|
||||
getTagsBySubCategoryId,
|
||||
listEnableCategory,
|
||||
uploadThumbnail,
|
||||
|
|
@ -81,6 +82,11 @@ interface FileWithPreview extends File {
|
|||
preview: string;
|
||||
}
|
||||
|
||||
type Option = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export default function FormAudioUpdate() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -111,10 +117,20 @@ export default function FormAudioUpdate() {
|
|||
const [detailThumb, setDetailThumb] = useState<any>([]);
|
||||
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
||||
const [selectedTarget, setSelectedTarget] = useState("");
|
||||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [selectedOptions, setSelectedOptions] = useState<{
|
||||
[fileId: number]: string;
|
||||
}>({});
|
||||
|
||||
const options: Option[] = [
|
||||
{ id: "all", name: "SEMUA" },
|
||||
{ id: "5", name: "UMUM" },
|
||||
{ id: "6", name: "JOURNALIS" },
|
||||
{ id: "7", name: "POLRI" },
|
||||
{ id: "8", name: "KSP" },
|
||||
];
|
||||
|
||||
const [unitSelection, setUnitSelection] = useState({
|
||||
allUnit: false,
|
||||
mabes: false,
|
||||
|
|
@ -139,21 +155,6 @@ export default function FormAudioUpdate() {
|
|||
resolver: zodResolver(audioSchema),
|
||||
});
|
||||
|
||||
// const handleKeyDown = (e: any) => {
|
||||
// const newTag = e.target.value.trim(); // Ambil nilai input
|
||||
// if (e.key === "Enter" && newTag) {
|
||||
// e.preventDefault(); // Hentikan submit form
|
||||
// if (!tags.includes(newTag)) {
|
||||
// setTags((prevTags) => [...prevTags, newTag]); // Tambah tag baru
|
||||
// setValue("tags", ""); // Kosongkan input
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
|
||||
const handleRemoveTag = (index: any) => {
|
||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.files) {
|
||||
const files = Array.from(event.target.files);
|
||||
|
|
@ -166,12 +167,6 @@ export default function FormAudioUpdate() {
|
|||
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleCheckboxChange = (id: number) => {
|
||||
setSelectedPublishers((prev) =>
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
getCategories();
|
||||
|
|
@ -205,6 +200,29 @@ export default function FormAudioUpdate() {
|
|||
}
|
||||
};
|
||||
|
||||
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter" && e.currentTarget.value.trim()) {
|
||||
e.preventDefault();
|
||||
const newTag = e.currentTarget.value.trim();
|
||||
if (!tags.includes(newTag)) {
|
||||
setTags((prevTags) => [...prevTags, newTag]); // Tambahkan tag baru
|
||||
if (inputRef.current) {
|
||||
inputRef.current.value = ""; // Kosongkan input
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveTag = (index: number) => {
|
||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleEditTag = (index: number, newValue: string) => {
|
||||
setTags((prevTags) =>
|
||||
prevTags.map((tag, i) => (i === index ? newValue : tag))
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
|
|
@ -217,11 +235,13 @@ export default function FormAudioUpdate() {
|
|||
setFiles(details.files);
|
||||
}
|
||||
|
||||
if (details.publishedForObject) {
|
||||
const publisherIds = details.publishedForObject.map(
|
||||
(obj: any) => obj.id
|
||||
);
|
||||
setSelectedPublishers(publisherIds);
|
||||
if (details?.publishedFor) {
|
||||
// Split string "7" to an array ["7"] if needed
|
||||
setPublishedFor(details.publishedFor.split(","));
|
||||
}
|
||||
|
||||
if (details?.tags) {
|
||||
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
|
||||
}
|
||||
|
||||
const matchingCategory = categories.find(
|
||||
|
|
@ -244,7 +264,25 @@ export default function FormAudioUpdate() {
|
|||
initState();
|
||||
}, [refresh, setValue]);
|
||||
|
||||
const handleCheckboxChange = (id: string) => {
|
||||
if (id === "all") {
|
||||
// Select all options except "all"
|
||||
const allOptions = options
|
||||
.filter((opt) => opt.id !== "all")
|
||||
.map((opt) => opt.id);
|
||||
setPublishedFor(
|
||||
publishedFor.length === allOptions.length ? [] : allOptions
|
||||
);
|
||||
} else {
|
||||
// Toggle individual option
|
||||
setPublishedFor((prev) =>
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const save = async (data: AudioSchema) => {
|
||||
const finalTags = tags.join(", ");
|
||||
const requestData = {
|
||||
...data,
|
||||
id: detail?.id,
|
||||
|
|
@ -256,9 +294,9 @@ export default function FormAudioUpdate() {
|
|||
subCategoryId: selectedTarget,
|
||||
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
|
||||
statusId: "1",
|
||||
publishedFor: "6",
|
||||
publishedFor: publishedFor.join(","),
|
||||
creatorName: data.creatorName,
|
||||
tags: "siap",
|
||||
tags: finalTags,
|
||||
isYoutube: false,
|
||||
isInternationalMedia: false,
|
||||
};
|
||||
|
|
@ -286,12 +324,7 @@ export default function FormAudioUpdate() {
|
|||
close();
|
||||
// showProgress();
|
||||
files.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(
|
||||
index,
|
||||
String(id),
|
||||
item,
|
||||
fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0"
|
||||
);
|
||||
await uploadResumableFile(index, String(id), item, "0");
|
||||
});
|
||||
|
||||
MySwal.fire({
|
||||
|
|
@ -430,22 +463,24 @@ export default function FormAudioUpdate() {
|
|||
}
|
||||
};
|
||||
|
||||
const handleRemoveFile = (file: FileWithPreview) => {
|
||||
const uploadedFiles = files;
|
||||
const filtered = uploadedFiles.filter((i) => i.name !== file.name);
|
||||
setFiles([...filtered]);
|
||||
};
|
||||
// const handleRemoveFile = (file: FileWithPreview) => {
|
||||
// const uploadedFiles = files;
|
||||
// const filtered = uploadedFiles.filter((i) => i.name !== file.name);
|
||||
// setFiles([...filtered]);
|
||||
// };
|
||||
|
||||
const fileList = files.map((file) => (
|
||||
const fileList = files.map((file: any) => (
|
||||
<div
|
||||
key={file.name}
|
||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
key={file.id} // Gunakan ID file sebagai key
|
||||
className="flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="file-preview">{renderFilePreview(file)}</div>
|
||||
<div>
|
||||
<div className=" text-sm text-card-foreground">{file.name}</div>
|
||||
<div className=" text-xs font-light text-muted-foreground">
|
||||
<div className="text-sm text-card-foreground">
|
||||
{file.fileName || file.name}
|
||||
</div>
|
||||
<div className="text-xs font-light text-muted-foreground">
|
||||
{Math.round(file.size / 100) / 10 > 1000 ? (
|
||||
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
|
||||
) : (
|
||||
|
|
@ -460,10 +495,10 @@ export default function FormAudioUpdate() {
|
|||
size="icon"
|
||||
color="destructive"
|
||||
variant="outline"
|
||||
className=" border-none rounded-full"
|
||||
onClick={() => handleRemoveFile(file)}
|
||||
className="border-none rounded-full"
|
||||
onClick={() => handleDeleteFile(file.id)} // Kirim ID spesifik
|
||||
>
|
||||
<Icon icon="tabler:x" className=" h-5 w-5" />
|
||||
<Icon icon="tabler:x" className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
));
|
||||
|
|
@ -500,10 +535,59 @@ export default function FormAudioUpdate() {
|
|||
});
|
||||
};
|
||||
|
||||
function success() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
// window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const handleDeleteFile = (id: number) => {
|
||||
MySwal.fire({
|
||||
title: "Hapus file",
|
||||
text: "Apakah Anda yakin ingin menghapus file ini?",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Hapus",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
doDelete(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
async function doDelete(id: number) {
|
||||
const data = { id };
|
||||
|
||||
try {
|
||||
const response = await deleteFile(data);
|
||||
if (response?.error) {
|
||||
error(response.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Jika berhasil, hapus file dari state lokal
|
||||
setFiles((prevFiles: any) =>
|
||||
prevFiles.filter((file: any) => file.id !== id)
|
||||
);
|
||||
success();
|
||||
} catch (err) {
|
||||
error("Terjadi kesalahan saat menghapus file");
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
|
|
@ -606,12 +690,12 @@ export default function FormAudioUpdate() {
|
|||
<Switch defaultChecked color="primary" id="c2" />
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
{/* <Button
|
||||
color="destructive"
|
||||
onClick={handleRemoveAllFiles}
|
||||
>
|
||||
Remove All
|
||||
</Button>
|
||||
</Button> */}
|
||||
</div>
|
||||
</Fragment>
|
||||
) : null}
|
||||
|
|
@ -629,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
|
||||
|
|
@ -724,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">
|
||||
|
|
@ -752,14 +836,34 @@ export default function FormAudioUpdate() {
|
|||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{detail?.tags?.split(",").map((tag, index) => (
|
||||
<Badge
|
||||
<Input
|
||||
type="text"
|
||||
id="tags"
|
||||
placeholder="Add a tag and press Enter"
|
||||
onKeyDown={handleAddTag}
|
||||
ref={inputRef}
|
||||
/>
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{tags.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="border rounded-md px-2 py-2"
|
||||
className="flex items-center gap-2 px-2 py-1 rounded-lg bg-black text-white text-sm"
|
||||
>
|
||||
{tag.trim()}
|
||||
</Badge>
|
||||
<input
|
||||
type="text"
|
||||
value={tag}
|
||||
onChange={(e) => handleEditTag(index, e.target.value)}
|
||||
className="bg-black text-white border-none focus:outline-none w-auto"
|
||||
/>
|
||||
<button
|
||||
value={tag}
|
||||
type="button"
|
||||
onClick={() => handleRemoveTag(index)}
|
||||
className="remove-tag-button text-white"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -767,38 +871,21 @@ export default function FormAudioUpdate() {
|
|||
<div className="px-3 py-3">
|
||||
<div className="flex flex-col gap-6">
|
||||
<Label>Target Publish</Label>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="5"
|
||||
checked={selectedPublishers.includes(5)}
|
||||
onChange={() => handleCheckboxChange(5)}
|
||||
/>
|
||||
<Label htmlFor="5">UMUM</Label>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="6"
|
||||
checked={selectedPublishers.includes(6)}
|
||||
onChange={() => handleCheckboxChange(6)}
|
||||
/>
|
||||
<Label htmlFor="6">JOURNALIS</Label>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="7"
|
||||
checked={selectedPublishers.includes(7)}
|
||||
onChange={() => handleCheckboxChange(7)}
|
||||
/>
|
||||
<Label htmlFor="7">POLRI</Label>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="8"
|
||||
checked={selectedPublishers.includes(8)}
|
||||
onChange={() => handleCheckboxChange(8)}
|
||||
/>
|
||||
<Label htmlFor="8">KSP</Label>
|
||||
</div>
|
||||
{options.map((option: Option) => (
|
||||
<div key={option.id} className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id={option.id}
|
||||
checked={
|
||||
option.id === "all"
|
||||
? publishedFor.length ===
|
||||
options.filter((opt) => opt.id !== "all").length
|
||||
: publishedFor.includes(option.id)
|
||||
}
|
||||
onCheckedChange={() => handleCheckboxChange(option.id)}
|
||||
/>
|
||||
<Label htmlFor={option.id}>{option.name}</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||
import * as z from "zod";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useParams } from "next/navigation";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
|
|
@ -55,6 +55,7 @@ import { getCookiesDecrypt } from "@/lib/utils";
|
|||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { error } from "@/lib/swal";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useRouter } from "@/i18n/routing";
|
||||
|
||||
const imageSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -294,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);
|
||||
}
|
||||
}
|
||||
|
|
@ -408,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>
|
||||
|
|
@ -519,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">
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ import { Switch } from "@/components/ui/switch";
|
|||
import Cookies from "js-cookie";
|
||||
import {
|
||||
createMedia,
|
||||
deleteFile,
|
||||
deleteMedia,
|
||||
getTagsBySubCategoryId,
|
||||
listEnableCategory,
|
||||
uploadThumbnail,
|
||||
|
|
@ -42,7 +44,7 @@ import dynamic from "next/dynamic";
|
|||
import { useDropzone } from "react-dropzone";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import Image from "next/image";
|
||||
import { error } from "@/lib/swal";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
import { Upload } from "tus-js-client";
|
||||
|
||||
|
|
@ -65,7 +67,13 @@ type Detail = {
|
|||
title: string;
|
||||
description: string;
|
||||
slug: string;
|
||||
categoryId: {
|
||||
category: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
publishedFor: string;
|
||||
|
||||
publishedForObject: {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
|
|
@ -75,6 +83,11 @@ type Detail = {
|
|||
tags: string;
|
||||
};
|
||||
|
||||
type Option = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
const CustomEditor = dynamic(
|
||||
() => {
|
||||
return import("@/components/editor/custom-editor");
|
||||
|
|
@ -122,7 +135,17 @@ export default function FormImageUpdate() {
|
|||
[fileId: number]: string;
|
||||
}>({});
|
||||
|
||||
const [selectedTarget, setSelectedTarget] = useState("");
|
||||
const options: Option[] = [
|
||||
{ id: "all", name: "SEMUA" },
|
||||
{ id: "5", name: "UMUM" },
|
||||
{ id: "6", name: "JOURNALIS" },
|
||||
{ id: "7", name: "POLRI" },
|
||||
{ id: "8", name: "KSP" },
|
||||
];
|
||||
|
||||
const [selectedTarget, setSelectedTarget] = useState<string | undefined>(
|
||||
detail?.category.id
|
||||
);
|
||||
const [unitSelection, setUnitSelection] = useState({
|
||||
allUnit: false,
|
||||
mabes: false,
|
||||
|
|
@ -158,10 +181,6 @@ export default function FormImageUpdate() {
|
|||
// }
|
||||
// };
|
||||
|
||||
const handleRemoveTag = (index: any) => {
|
||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.files) {
|
||||
const files = Array.from(event.target.files);
|
||||
|
|
@ -174,11 +193,11 @@ export default function FormImageUpdate() {
|
|||
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleCheckboxChange = (id: number) => {
|
||||
setSelectedPublishers((prev) =>
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
);
|
||||
};
|
||||
// const handleCheckboxChange = (id: number) => {
|
||||
// setSelectedPublishers((prev) =>
|
||||
// prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
// );
|
||||
// };
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
|
|
@ -193,14 +212,24 @@ export default function FormImageUpdate() {
|
|||
e.preventDefault();
|
||||
const newTag = e.currentTarget.value.trim();
|
||||
if (!tags.includes(newTag)) {
|
||||
setTags((prevTags) => [...prevTags, newTag]); // Add new tag
|
||||
setTags((prevTags) => [...prevTags, newTag]); // Tambahkan tag baru
|
||||
if (inputRef.current) {
|
||||
inputRef.current.value = ""; // Clear input field
|
||||
inputRef.current.value = ""; // Kosongkan input
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveTag = (index: number) => {
|
||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleEditTag = (index: number, newValue: string) => {
|
||||
setTags((prevTags) =>
|
||||
prevTags.map((tag, i) => (i === index ? newValue : tag))
|
||||
);
|
||||
};
|
||||
|
||||
const getCategories = async () => {
|
||||
try {
|
||||
const category = await listEnableCategory(fileTypeId);
|
||||
|
|
@ -238,11 +267,13 @@ export default function FormImageUpdate() {
|
|||
setFiles(details.files);
|
||||
}
|
||||
|
||||
if (details.publishedForObject) {
|
||||
const publisherIds = details.publishedForObject.map(
|
||||
(obj: any) => obj.id
|
||||
);
|
||||
setSelectedPublishers(publisherIds);
|
||||
if (details?.publishedFor) {
|
||||
// Split string "7" to an array ["7"] if needed
|
||||
setPublishedFor(details.publishedFor.split(","));
|
||||
}
|
||||
|
||||
if (details?.tags) {
|
||||
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
|
||||
}
|
||||
|
||||
const matchingCategory = categories.find(
|
||||
|
|
@ -259,7 +290,32 @@ export default function FormImageUpdate() {
|
|||
initState();
|
||||
}, [refresh, setValue]);
|
||||
|
||||
// const handleCheckboxChange = (id: number) => {
|
||||
// setSelectedPublishers((prev) =>
|
||||
// prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
// );
|
||||
// };
|
||||
|
||||
const handleCheckboxChange = (id: string) => {
|
||||
if (id === "all") {
|
||||
// Select all options except "all"
|
||||
const allOptions = options
|
||||
.filter((opt) => opt.id !== "all")
|
||||
.map((opt) => opt.id);
|
||||
setPublishedFor(
|
||||
publishedFor.length === allOptions.length ? [] : allOptions
|
||||
);
|
||||
} else {
|
||||
// Toggle individual option
|
||||
setPublishedFor((prev) =>
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const save = async (data: ImageSchema) => {
|
||||
loading();
|
||||
const finalTags = tags.join(", ");
|
||||
const requestData = {
|
||||
...data,
|
||||
id: detail?.id,
|
||||
|
|
@ -271,9 +327,9 @@ export default function FormImageUpdate() {
|
|||
subCategoryId: selectedTarget,
|
||||
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
|
||||
statusId: "1",
|
||||
publishedFor: "6",
|
||||
publishedFor: publishedFor.join(","),
|
||||
creatorName: data.creatorName,
|
||||
tags: "siap",
|
||||
tags: finalTags,
|
||||
isYoutube: false,
|
||||
isInternationalMedia: false,
|
||||
};
|
||||
|
|
@ -451,16 +507,18 @@ export default function FormImageUpdate() {
|
|||
setFiles([...filtered]);
|
||||
};
|
||||
|
||||
const fileList = files.map((file) => (
|
||||
const fileList = files.map((file: any) => (
|
||||
<div
|
||||
key={file.name}
|
||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
key={file.id} // Gunakan ID file sebagai key
|
||||
className="flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="file-preview">{renderFilePreview(file)}</div>
|
||||
<div>
|
||||
<div className=" text-sm text-card-foreground">{file.name}</div>
|
||||
<div className=" text-xs font-light text-muted-foreground">
|
||||
<div className="text-sm text-card-foreground">
|
||||
{file.fileName || file.name}
|
||||
</div>
|
||||
<div className="text-xs font-light text-muted-foreground">
|
||||
{Math.round(file.size / 100) / 10 > 1000 ? (
|
||||
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
|
||||
) : (
|
||||
|
|
@ -475,10 +533,10 @@ export default function FormImageUpdate() {
|
|||
size="icon"
|
||||
color="destructive"
|
||||
variant="outline"
|
||||
className=" border-none rounded-full"
|
||||
onClick={() => handleRemoveFile(file)}
|
||||
className="border-none rounded-full"
|
||||
onClick={() => handleDeleteFile(file.id)} // Kirim ID spesifik
|
||||
>
|
||||
<Icon icon="tabler:x" className=" h-5 w-5" />
|
||||
<Icon icon="tabler:x" className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
));
|
||||
|
|
@ -515,10 +573,59 @@ export default function FormImageUpdate() {
|
|||
});
|
||||
};
|
||||
|
||||
function success() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
// window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const handleDeleteFile = (id: number) => {
|
||||
MySwal.fire({
|
||||
title: "Hapus file",
|
||||
text: "Apakah Anda yakin ingin menghapus file ini?",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Hapus",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
doDelete(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
async function doDelete(id: number) {
|
||||
const data = { id };
|
||||
|
||||
try {
|
||||
const response = await deleteFile(data);
|
||||
if (response?.error) {
|
||||
error(response.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Jika berhasil, hapus file dari state lokal
|
||||
setFiles((prevFiles: any) =>
|
||||
prevFiles.filter((file: any) => file.id !== id)
|
||||
);
|
||||
success();
|
||||
} catch (err) {
|
||||
error("Terjadi kesalahan saat menghapus file");
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
|
|
@ -549,14 +656,17 @@ export default function FormImageUpdate() {
|
|||
<div className="py-3 w-full">
|
||||
<Label>Kategori</Label>
|
||||
<Select
|
||||
defaultValue={detail?.categoryId.name} // Nilai default berdasarkan detail
|
||||
defaultValue={detail?.category.id} // Gunakan ID sebagai defaultValue
|
||||
onValueChange={(id) => {
|
||||
console.log("Selected Category:", id);
|
||||
setSelectedTarget(id);
|
||||
console.log("Selected Category ID:", id);
|
||||
setSelectedTarget(id); // Perbarui ID kategori
|
||||
}}
|
||||
>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
<SelectValue placeholder="Pilih">
|
||||
{categories.find((cat) => cat.id === selectedTarget)
|
||||
?.name || "Pilih"}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{categories.map((category) => (
|
||||
|
|
@ -619,12 +729,12 @@ export default function FormImageUpdate() {
|
|||
<Switch defaultChecked color="primary" id="c2" />
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
{/* <Button
|
||||
color="destructive"
|
||||
onClick={handleRemoveAllFiles}
|
||||
onClick={() => handleDeleteFile(id)}
|
||||
>
|
||||
Remove All
|
||||
</Button>
|
||||
Remove file
|
||||
</Button> */}
|
||||
</div>
|
||||
</Fragment>
|
||||
) : null}
|
||||
|
|
@ -642,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
|
||||
|
|
@ -737,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>
|
||||
|
|
@ -782,24 +892,30 @@ export default function FormImageUpdate() {
|
|||
onKeyDown={handleAddTag}
|
||||
ref={inputRef}
|
||||
/>
|
||||
<div className="mt-3 ">
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{tags.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className=" px-1 py-1 rounded-lg bg-black text-white mr-2 text-sm font-sans"
|
||||
className="flex items-center gap-2 px-2 py-1 rounded-lg bg-black text-white text-sm"
|
||||
>
|
||||
{tag}{" "}
|
||||
<input
|
||||
type="text"
|
||||
value={tag}
|
||||
onChange={(e) => handleEditTag(index, e.target.value)}
|
||||
className="bg-black text-white border-none focus:outline-none w-auto"
|
||||
/>
|
||||
<button
|
||||
value={tag}
|
||||
type="button"
|
||||
onClick={() => handleRemoveTag(index)}
|
||||
className="remove-tag-button"
|
||||
className="remove-tag-button text-white"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{/* <div className="flex flex-wrap gap-2">
|
||||
{detail?.tags?.split(",").map((tag, index) => (
|
||||
<Badge
|
||||
key={index}
|
||||
|
|
@ -808,44 +924,27 @@ export default function FormImageUpdate() {
|
|||
{tag.trim()}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<div className="flex flex-col gap-6">
|
||||
<Label>Target Publish</Label>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="5"
|
||||
checked={selectedPublishers.includes(5)}
|
||||
onChange={() => handleCheckboxChange(5)}
|
||||
/>
|
||||
<Label htmlFor="5">UMUM</Label>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="6"
|
||||
checked={selectedPublishers.includes(6)}
|
||||
onChange={() => handleCheckboxChange(6)}
|
||||
/>
|
||||
<Label htmlFor="6">JOURNALIS</Label>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="7"
|
||||
checked={selectedPublishers.includes(7)}
|
||||
onChange={() => handleCheckboxChange(7)}
|
||||
/>
|
||||
<Label htmlFor="7">POLRI</Label>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="8"
|
||||
checked={selectedPublishers.includes(8)}
|
||||
onChange={() => handleCheckboxChange(8)}
|
||||
/>
|
||||
<Label htmlFor="8">KSP</Label>
|
||||
</div>
|
||||
{options.map((option: Option) => (
|
||||
<div key={option.id} className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id={option.id}
|
||||
checked={
|
||||
option.id === "all"
|
||||
? publishedFor.length ===
|
||||
options.filter((opt) => opt.id !== "all").length
|
||||
: publishedFor.includes(option.id)
|
||||
}
|
||||
onCheckedChange={() => handleCheckboxChange(option.id)}
|
||||
/>
|
||||
<Label htmlFor={option.id}>{option.name}</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
||||
|
|
|
|||
|
|
@ -96,6 +96,13 @@ interface PlacementData {
|
|||
placements: string;
|
||||
}
|
||||
|
||||
type FileType = {
|
||||
contentId: number;
|
||||
contentFile: string;
|
||||
thumbnailFileUrl: string;
|
||||
fileName: string;
|
||||
};
|
||||
|
||||
const CustomEditor = dynamic(
|
||||
() => {
|
||||
return import("@/components/editor/custom-editor");
|
||||
|
|
@ -122,7 +129,7 @@ export default function FormConvertSPIT() {
|
|||
null
|
||||
);
|
||||
const [tags, setTags] = useState<any[]>([]);
|
||||
const [detail, setDetail] = useState<Detail>();
|
||||
const [detail, setDetail] = useState<any>();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]);
|
||||
const [detailThumb, setDetailThumb] = useState<any>([]);
|
||||
|
|
@ -150,7 +157,9 @@ export default function FormConvertSPIT() {
|
|||
polres: false,
|
||||
});
|
||||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||
const [placementLength, setPlacementLength] = useState([]);
|
||||
const [filePlacements, setFilePlacements] = useState<string[][]>([]);
|
||||
const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
|
||||
const [files, setFiles] = useState<FileType[]>([]);
|
||||
|
||||
const options: Option[] = [
|
||||
{ id: "all", label: "SEMUA" },
|
||||
|
|
@ -201,6 +210,17 @@ export default function FormConvertSPIT() {
|
|||
initState();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
userLevelId != undefined &&
|
||||
roleId != undefined &&
|
||||
userLevelId == "216" &&
|
||||
roleId == "3"
|
||||
) {
|
||||
setIsUserMabesApprover(true);
|
||||
}
|
||||
}, [userLevelId, roleId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
userLevelId != undefined &&
|
||||
|
|
@ -237,6 +257,47 @@ export default function FormConvertSPIT() {
|
|||
}
|
||||
};
|
||||
|
||||
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 setupPlacementCheck = (length: number) => {
|
||||
const temp = [];
|
||||
for (let i = 0; i < length; i++) {
|
||||
temp.push([]);
|
||||
}
|
||||
setFilePlacements(temp);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
|
|
@ -244,6 +305,8 @@ export default function FormConvertSPIT() {
|
|||
const details = response?.data?.data;
|
||||
|
||||
setDetail(details);
|
||||
setFiles(details?.contentList);
|
||||
setupPlacementCheck(details?.contentList?.length);
|
||||
|
||||
const filesData = details.contentList || [];
|
||||
const fileUrls = filesData.map((file: { contentFile: string }) =>
|
||||
|
|
@ -272,49 +335,6 @@ export default function FormConvertSPIT() {
|
|||
}))
|
||||
);
|
||||
|
||||
const getPlacement = (): PlacementData[] => {
|
||||
return tempFile
|
||||
.filter((file: FileData) => (file.placement || []).length > 0) // Gunakan default array
|
||||
.map((file: FileData) => ({
|
||||
mediaFileId: Number(file.contentId),
|
||||
placements: (file.placement || []).join(","), // Gunakan default array
|
||||
}));
|
||||
};
|
||||
|
||||
const setupPlacement = (value: string, id: number): void => {
|
||||
const updatedFiles = tempFile.map((file: FileData) => {
|
||||
if (file.contentId === id) {
|
||||
const currentPlacement = file.placement || [];
|
||||
if (currentPlacement.includes(value)) {
|
||||
// Remove the placement value
|
||||
file.placement = currentPlacement.filter((val) => val !== value);
|
||||
} else {
|
||||
// Add the placement value
|
||||
file.placement =
|
||||
value === "all"
|
||||
? ["all", "mabes", "polda", "international"]
|
||||
: [...currentPlacement, value];
|
||||
if (file.placement.includes("all") && value !== "all") {
|
||||
file.placement = file.placement.filter((val) => val !== "all");
|
||||
}
|
||||
}
|
||||
}
|
||||
return file;
|
||||
});
|
||||
|
||||
const placementLength = updatedFiles.reduce(
|
||||
(acc: any, file: any) => acc + (file.placement?.length || 0),
|
||||
0
|
||||
);
|
||||
|
||||
setTempFile(
|
||||
updatedFiles.sort((a: any, b: any) => a.contentId - b.contentId)
|
||||
);
|
||||
setPlacementLength(placementLength);
|
||||
|
||||
console.log("Updated Files:", updatedFiles);
|
||||
};
|
||||
|
||||
const handleCheckboxChangeFile = (contentId: number, value: string) => {
|
||||
setTempFile((prevTempFile: any) => {
|
||||
return prevTempFile.map((file: any) => {
|
||||
|
|
@ -335,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")
|
||||
|
|
@ -350,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 {
|
||||
|
|
@ -359,12 +376,32 @@ export default function FormConvertSPIT() {
|
|||
}
|
||||
};
|
||||
|
||||
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].contentId,
|
||||
placement: now.join(","),
|
||||
};
|
||||
temp.push(data);
|
||||
}
|
||||
}
|
||||
return temp;
|
||||
};
|
||||
|
||||
const save = async (data: {
|
||||
contentTitle: string;
|
||||
contentDescription: string;
|
||||
contentRewriteDescription: string;
|
||||
contentCreator: string;
|
||||
}): Promise<void> => {
|
||||
const temp = [];
|
||||
for (const element of detail.contentList) {
|
||||
temp.push([]);
|
||||
}
|
||||
const description =
|
||||
selectedFileType === "original"
|
||||
? data.contentDescription
|
||||
|
|
@ -379,12 +416,13 @@ export default function FormConvertSPIT() {
|
|||
categoryId: selectedCategoryId,
|
||||
publishedFor: publishedFor.join(","),
|
||||
creator: data.contentCreator,
|
||||
files: getPlacement(), // Include placement data
|
||||
files: isUserMabesApprover ? getPlacement() : [],
|
||||
};
|
||||
|
||||
const response = await convertSPIT(requestData);
|
||||
console.log("Form Data Submitted:", response);
|
||||
|
||||
setFilePlacements(temp);
|
||||
setFiles(detail.files);
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Data berhasil disimpan.",
|
||||
|
|
@ -503,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>
|
||||
|
|
@ -657,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 }}
|
||||
|
|
@ -668,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}`}
|
||||
/>
|
||||
|
|
@ -678,7 +716,7 @@ export default function FormConvertSPIT() {
|
|||
<div className=" mt-2 ">
|
||||
<Swiper
|
||||
onSwiper={setThumbsSwiper}
|
||||
slidesPerView={6}
|
||||
slidesPerView={8}
|
||||
spaceBetween={8}
|
||||
pagination={{
|
||||
clickable: true,
|
||||
|
|
@ -689,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}`}
|
||||
/>
|
||||
|
|
@ -699,66 +737,100 @@ export default function FormConvertSPIT() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isMabesApprover ? (
|
||||
<div className="mt-5">
|
||||
<Label className="text-xl text-black">
|
||||
Penempatan File
|
||||
</Label>
|
||||
{detailThumb.map((data: any) => (
|
||||
<div
|
||||
key={data.contentId}
|
||||
className="flex items-center gap-3 mt-2"
|
||||
>
|
||||
<img
|
||||
className="object-cover w-36 h-32"
|
||||
src={data}
|
||||
alt={`Thumbnail ${data.contentId}`}
|
||||
/>
|
||||
<div className="flex flex-row gap-3 items-center">
|
||||
{[
|
||||
"all",
|
||||
"mabes",
|
||||
"polda",
|
||||
"satker",
|
||||
"internasional",
|
||||
].map((value) => (
|
||||
<label
|
||||
key={value}
|
||||
className="text-blue-500 cursor-pointer flex items-center gap-1"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value={value}
|
||||
onChange={() =>
|
||||
handleCheckboxChangeFile(
|
||||
data.contentId,
|
||||
value
|
||||
)
|
||||
}
|
||||
checked={
|
||||
tempFile
|
||||
.find(
|
||||
(file: FileData) =>
|
||||
file.contentId === data.contentId
|
||||
)
|
||||
?.placement?.includes(value) || false
|
||||
}
|
||||
/>
|
||||
{value.charAt(0).toUpperCase() + value.slice(1)}
|
||||
</label>
|
||||
))}
|
||||
{files?.map((file, index) => (
|
||||
<div
|
||||
key={file.contentId}
|
||||
className="flex flex-row gap-2 items-center my-3"
|
||||
>
|
||||
<img
|
||||
src={file.contentFile}
|
||||
className="w-[150px] rounded-md"
|
||||
/>
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<div className="flex justify-between text-sm">
|
||||
{file.fileName}
|
||||
{/* <a
|
||||
onClick={() =>
|
||||
handleDeleteFileApproval(file.id)
|
||||
}
|
||||
>
|
||||
<Icon icon="humbleicons:times" color="red" />
|
||||
</a> */}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap 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"
|
||||
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>
|
||||
</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">
|
||||
|
|
@ -797,14 +869,16 @@ export default function FormConvertSPIT() {
|
|||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{detail?.contentTag?.split(",").map((tag, index) => (
|
||||
<Badge
|
||||
key={index}
|
||||
className="border rounded-md px-2 py-2"
|
||||
>
|
||||
{tag.trim()}
|
||||
</Badge>
|
||||
))}
|
||||
{detail?.contentTag
|
||||
?.split(",")
|
||||
.map((tag: any, index: any) => (
|
||||
<Badge
|
||||
key={index}
|
||||
className="border rounded-md px-2 py-2"
|
||||
>
|
||||
{tag.trim()}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ import { CloudUpload, MailIcon } from "lucide-react";
|
|||
import { useDropzone } from "react-dropzone";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import Image from "next/image";
|
||||
import { error } from "@/lib/swal";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
import { Upload } from "tus-js-client";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
|
||||
|
|
@ -127,6 +127,7 @@ export default function FormTeksUpdate() {
|
|||
polres: false,
|
||||
});
|
||||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
let fileTypeId = "3";
|
||||
|
||||
|
|
@ -164,10 +165,6 @@ export default function FormTeksUpdate() {
|
|||
// }
|
||||
// };
|
||||
|
||||
const handleRemoveTag = (index: any) => {
|
||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.files) {
|
||||
const files = Array.from(event.target.files);
|
||||
|
|
@ -231,11 +228,13 @@ export default function FormTeksUpdate() {
|
|||
setFiles(details.files);
|
||||
}
|
||||
|
||||
if (details.publishedForObject) {
|
||||
const publisherIds = details.publishedForObject.map(
|
||||
(obj: any) => obj.id
|
||||
);
|
||||
setSelectedPublishers(publisherIds);
|
||||
if (details?.publishedFor) {
|
||||
// Split string "7" to an array ["7"] if needed
|
||||
setPublishedFor(details.publishedFor.split(","));
|
||||
}
|
||||
|
||||
if (details?.tags) {
|
||||
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
|
||||
}
|
||||
|
||||
const matchingCategory = categories.find(
|
||||
|
|
@ -252,34 +251,26 @@ export default function FormTeksUpdate() {
|
|||
initState();
|
||||
}, [refresh, setValue]);
|
||||
|
||||
const handleCheckboxChange = (id: string): void => {
|
||||
const handleCheckboxChange = (id: string) => {
|
||||
if (id === "all") {
|
||||
if (publishedFor.includes("all")) {
|
||||
// Uncheck all checkboxes
|
||||
setPublishedFor([]);
|
||||
} else {
|
||||
// Select all checkboxes
|
||||
setPublishedFor(
|
||||
options
|
||||
.filter((opt: any) => opt.id !== "all")
|
||||
.map((opt: any) => opt.id)
|
||||
);
|
||||
}
|
||||
// Select all options except "all"
|
||||
const allOptions = options
|
||||
.filter((opt) => opt.id !== "all")
|
||||
.map((opt) => opt.id);
|
||||
setPublishedFor(
|
||||
publishedFor.length === allOptions.length ? [] : allOptions
|
||||
);
|
||||
} else {
|
||||
const updatedPublishedFor = publishedFor.includes(id)
|
||||
? 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 {
|
||||
setPublishedFor(updatedPublishedFor);
|
||||
}
|
||||
// Toggle individual option
|
||||
setPublishedFor((prev) =>
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const save = async (data: TeksSchema) => {
|
||||
loading();
|
||||
const finalTags = tags.join(", ");
|
||||
const requestData = {
|
||||
...data,
|
||||
id: detail?.id,
|
||||
|
|
@ -293,7 +284,7 @@ export default function FormTeksUpdate() {
|
|||
statusId: "1",
|
||||
publishedFor: publishedFor.join(","),
|
||||
creatorName: data.creatorName,
|
||||
tags: "siap",
|
||||
tags: finalTags,
|
||||
isYoutube: false,
|
||||
isInternationalMedia: false,
|
||||
};
|
||||
|
|
@ -535,10 +526,33 @@ export default function FormTeksUpdate() {
|
|||
});
|
||||
};
|
||||
|
||||
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter" && e.currentTarget.value.trim()) {
|
||||
e.preventDefault();
|
||||
const newTag = e.currentTarget.value.trim();
|
||||
if (!tags.includes(newTag)) {
|
||||
setTags((prevTags) => [...prevTags, newTag]); // Tambahkan tag baru
|
||||
if (inputRef.current) {
|
||||
inputRef.current.value = ""; // Kosongkan input
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveTag = (index: number) => {
|
||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleEditTag = (index: number, newValue: string) => {
|
||||
setTags((prevTags) =>
|
||||
prevTags.map((tag, i) => (i === index ? newValue : tag))
|
||||
);
|
||||
};
|
||||
|
||||
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">
|
||||
|
|
@ -665,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
|
||||
|
|
@ -760,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">
|
||||
|
|
@ -785,7 +799,7 @@ export default function FormTeksUpdate() {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 px-3">
|
||||
{/* <div className="mt-3 px-3">
|
||||
<Label>Pratinjau Gambar Utama</Label>
|
||||
<Card className="mt-2">
|
||||
<img
|
||||
|
|
@ -794,18 +808,38 @@ export default function FormTeksUpdate() {
|
|||
className="w-full h-auto rounded"
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{detail?.tags?.split(",").map((tag, index) => (
|
||||
<Badge
|
||||
<Input
|
||||
type="text"
|
||||
id="tags"
|
||||
placeholder="Add a tag and press Enter"
|
||||
onKeyDown={handleAddTag}
|
||||
ref={inputRef}
|
||||
/>
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{tags.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="border rounded-md px-2 py-2"
|
||||
className="flex items-center gap-2 px-2 py-1 rounded-lg bg-black text-white text-sm"
|
||||
>
|
||||
{tag.trim()}
|
||||
</Badge>
|
||||
<input
|
||||
type="text"
|
||||
value={tag}
|
||||
onChange={(e) => handleEditTag(index, e.target.value)}
|
||||
className="bg-black text-white border-none focus:outline-none w-auto"
|
||||
/>
|
||||
<button
|
||||
value={tag}
|
||||
type="button"
|
||||
onClick={() => handleRemoveTag(index)}
|
||||
className="remove-tag-button text-white"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -817,7 +851,6 @@ export default function FormTeksUpdate() {
|
|||
<div key={option.id} className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id={option.id}
|
||||
value={detail?.publishedForObject.name}
|
||||
checked={
|
||||
option.id === "all"
|
||||
? publishedFor.length ===
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||
import * as z from "zod";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useParams } from "next/navigation";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
|
|
@ -55,6 +55,7 @@ import { getCookiesDecrypt } from "@/lib/utils";
|
|||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { error } from "@/lib/swal";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useRouter } from "@/i18n/routing";
|
||||
|
||||
const imageSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -328,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);
|
||||
}
|
||||
}
|
||||
|
|
@ -408,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">
|
||||
|
|
@ -523,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,7 +684,7 @@ export default function FormVideoDetail() {
|
|||
<Icon icon="humbleicons:times" color="red" />
|
||||
</a>
|
||||
</div>
|
||||
{isUserMabesApprover &&
|
||||
{isUserMabesApprover && (
|
||||
<div className="flex flex-row gap-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
|
|
@ -760,7 +761,7 @@ export default function FormVideoDetail() {
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
|
|
|
|||
|
|
@ -531,7 +531,7 @@ export default function FormVideo() {
|
|||
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, {
|
||||
|
|
@ -544,11 +544,11 @@ export default function FormVideo() {
|
|||
filename: file.name,
|
||||
filetype: file.type,
|
||||
duration,
|
||||
isWatermark: "false", // hardcode
|
||||
isWatermark: "true", // 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);
|
||||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import { Switch } from "@/components/ui/switch";
|
|||
import Cookies from "js-cookie";
|
||||
import {
|
||||
createMedia,
|
||||
deleteFile,
|
||||
getTagsBySubCategoryId,
|
||||
listEnableCategory,
|
||||
uploadThumbnail,
|
||||
|
|
@ -53,7 +54,7 @@ import Image from "next/image";
|
|||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { Upload } from "tus-js-client";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
import { error } from "@/lib/swal";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
|
||||
const videoSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -88,6 +89,11 @@ interface FileWithPreview extends File {
|
|||
preview: string;
|
||||
}
|
||||
|
||||
type Option = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export default function FormVideoUpdate() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -126,11 +132,20 @@ export default function FormVideoUpdate() {
|
|||
polda: false,
|
||||
polres: false,
|
||||
});
|
||||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [selectedOptions, setSelectedOptions] = useState<{
|
||||
[fileId: number]: string;
|
||||
}>({});
|
||||
|
||||
const options: Option[] = [
|
||||
{ id: "all", name: "SEMUA" },
|
||||
{ id: "5", name: "UMUM" },
|
||||
{ id: "6", name: "JOURNALIS" },
|
||||
{ id: "7", name: "POLRI" },
|
||||
{ id: "8", name: "KSP" },
|
||||
];
|
||||
|
||||
let fileTypeId = "2";
|
||||
|
||||
const { getRootProps, getInputProps } = useDropzone({
|
||||
|
|
@ -159,10 +174,6 @@ export default function FormVideoUpdate() {
|
|||
// }
|
||||
// };
|
||||
|
||||
const handleRemoveTag = (index: any) => {
|
||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.files) {
|
||||
const files = Array.from(event.target.files);
|
||||
|
|
@ -175,12 +186,6 @@ export default function FormVideoUpdate() {
|
|||
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleCheckboxChange = (id: number) => {
|
||||
setSelectedPublishers((prev) =>
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
getCategories();
|
||||
|
|
@ -189,6 +194,29 @@ export default function FormVideoUpdate() {
|
|||
initState();
|
||||
}, []);
|
||||
|
||||
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter" && e.currentTarget.value.trim()) {
|
||||
e.preventDefault();
|
||||
const newTag = e.currentTarget.value.trim();
|
||||
if (!tags.includes(newTag)) {
|
||||
setTags((prevTags) => [...prevTags, newTag]); // Tambahkan tag baru
|
||||
if (inputRef.current) {
|
||||
inputRef.current.value = ""; // Kosongkan input
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveTag = (index: number) => {
|
||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleEditTag = (index: number, newValue: string) => {
|
||||
setTags((prevTags) =>
|
||||
prevTags.map((tag, i) => (i === index ? newValue : tag))
|
||||
);
|
||||
};
|
||||
|
||||
const getCategories = async () => {
|
||||
try {
|
||||
const category = await listEnableCategory(fileTypeId);
|
||||
|
|
@ -226,11 +254,13 @@ export default function FormVideoUpdate() {
|
|||
setFiles(details.files);
|
||||
}
|
||||
|
||||
if (details.publishedForObject) {
|
||||
const publisherIds = details.publishedForObject.map(
|
||||
(obj: any) => obj.id
|
||||
);
|
||||
setSelectedPublishers(publisherIds);
|
||||
if (details?.publishedFor) {
|
||||
// Split string "7" to an array ["7"] if needed
|
||||
setPublishedFor(details.publishedFor.split(","));
|
||||
}
|
||||
|
||||
if (details?.tags) {
|
||||
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
|
||||
}
|
||||
|
||||
const matchingCategory = categories.find(
|
||||
|
|
@ -253,7 +283,26 @@ export default function FormVideoUpdate() {
|
|||
initState();
|
||||
}, [refresh, setValue]);
|
||||
|
||||
const handleCheckboxChange = (id: string) => {
|
||||
if (id === "all") {
|
||||
// Select all options except "all"
|
||||
const allOptions = options
|
||||
.filter((opt) => opt.id !== "all")
|
||||
.map((opt) => opt.id);
|
||||
setPublishedFor(
|
||||
publishedFor.length === allOptions.length ? [] : allOptions
|
||||
);
|
||||
} else {
|
||||
// Toggle individual option
|
||||
setPublishedFor((prev) =>
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const save = async (data: VideoSchema) => {
|
||||
loading();
|
||||
const finalTags = tags.join(", ");
|
||||
const requestData = {
|
||||
...data,
|
||||
id: detail?.id,
|
||||
|
|
@ -265,9 +314,9 @@ export default function FormVideoUpdate() {
|
|||
subCategoryId: selectedTarget,
|
||||
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
|
||||
statusId: "1",
|
||||
publishedFor: "6",
|
||||
publishedFor: publishedFor.join(","),
|
||||
creatorName: data.creatorName,
|
||||
tags: "siap",
|
||||
tags: finalTags,
|
||||
isYoutube: false,
|
||||
isInternationalMedia: false,
|
||||
};
|
||||
|
|
@ -295,32 +344,33 @@ export default function FormVideoUpdate() {
|
|||
close();
|
||||
// showProgress();
|
||||
files.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(
|
||||
index,
|
||||
String(id),
|
||||
item,
|
||||
fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0"
|
||||
);
|
||||
await uploadResumableFile(index, String(id), item, "0");
|
||||
});
|
||||
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Data berhasil disimpan.",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then(() => {
|
||||
router.push("/en/contributor/content/image");
|
||||
});
|
||||
// MySwal.fire({
|
||||
// title: "Sukses",
|
||||
// text: "Data berhasil disimpan.",
|
||||
// icon: "success",
|
||||
// confirmButtonColor: "#3085d6",
|
||||
// confirmButtonText: "OK",
|
||||
// }).then(() => {
|
||||
// router.push("/en/contributor/content/video");
|
||||
// });
|
||||
};
|
||||
|
||||
const onSubmit = (data: VideoSchema) => {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Data berhasil disimpan.",
|
||||
icon: "success",
|
||||
title: "Simpan Data",
|
||||
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then(() => {
|
||||
router.push("/en/contributor/content/video");
|
||||
confirmButtonText: "Simpan",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
save(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -385,22 +435,6 @@ export default function FormVideoUpdate() {
|
|||
upload.start();
|
||||
}
|
||||
|
||||
const onSubmit = (data: VideoSchema) => {
|
||||
MySwal.fire({
|
||||
title: "Simpan Data",
|
||||
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "Simpan",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
save(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const successSubmit = (redirect: string) => {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
|
|
@ -454,16 +488,18 @@ export default function FormVideoUpdate() {
|
|||
setFiles([...filtered]);
|
||||
};
|
||||
|
||||
const fileList = files.map((file) => (
|
||||
const fileList = files.map((file: any) => (
|
||||
<div
|
||||
key={file.name}
|
||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
key={file.id} // Gunakan ID file sebagai key
|
||||
className="flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="file-preview">{renderFilePreview(file)}</div>
|
||||
<div>
|
||||
<div className=" text-sm text-card-foreground">{file.name}</div>
|
||||
<div className=" text-xs font-light text-muted-foreground">
|
||||
<div className="text-sm text-card-foreground">
|
||||
{file.fileName || file.name}
|
||||
</div>
|
||||
<div className="text-xs font-light text-muted-foreground">
|
||||
{Math.round(file.size / 100) / 10 > 1000 ? (
|
||||
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
|
||||
) : (
|
||||
|
|
@ -478,10 +514,10 @@ export default function FormVideoUpdate() {
|
|||
size="icon"
|
||||
color="destructive"
|
||||
variant="outline"
|
||||
className=" border-none rounded-full"
|
||||
onClick={() => handleRemoveFile(file)}
|
||||
className="border-none rounded-full"
|
||||
onClick={() => handleDeleteFile(file.id)} // Kirim ID spesifik
|
||||
>
|
||||
<Icon icon="tabler:x" className=" h-5 w-5" />
|
||||
<Icon icon="tabler:x" className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
));
|
||||
|
|
@ -518,10 +554,59 @@ export default function FormVideoUpdate() {
|
|||
});
|
||||
};
|
||||
|
||||
function success() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
// window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const handleDeleteFile = (id: number) => {
|
||||
MySwal.fire({
|
||||
title: "Hapus file",
|
||||
text: "Apakah Anda yakin ingin menghapus file ini?",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Hapus",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
doDelete(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
async function doDelete(id: number) {
|
||||
const data = { id };
|
||||
|
||||
try {
|
||||
const response = await deleteFile(data);
|
||||
if (response?.error) {
|
||||
error(response.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Jika berhasil, hapus file dari state lokal
|
||||
setFiles((prevFiles: any) =>
|
||||
prevFiles.filter((file: any) => file.id !== id)
|
||||
);
|
||||
success();
|
||||
} catch (err) {
|
||||
error("Terjadi kesalahan saat menghapus file");
|
||||
}
|
||||
}
|
||||
|
||||
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">
|
||||
|
|
@ -626,12 +711,12 @@ export default function FormVideoUpdate() {
|
|||
<Switch defaultChecked color="primary" id="c2" />
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
{/* <Button
|
||||
color="destructive"
|
||||
onClick={handleRemoveAllFiles}
|
||||
>
|
||||
Remove All
|
||||
</Button>
|
||||
</Button> */}
|
||||
</div>
|
||||
</Fragment>
|
||||
) : null}
|
||||
|
|
@ -649,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
|
||||
|
|
@ -744,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">
|
||||
|
|
@ -782,14 +867,34 @@ export default function FormVideoUpdate() {
|
|||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{detail?.tags?.split(",").map((tag, index) => (
|
||||
<Badge
|
||||
<Input
|
||||
type="text"
|
||||
id="tags"
|
||||
placeholder="Add a tag and press Enter"
|
||||
onKeyDown={handleAddTag}
|
||||
ref={inputRef}
|
||||
/>
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{tags.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="border rounded-md px-2 py-2"
|
||||
className="flex items-center gap-2 px-2 py-1 rounded-lg bg-black text-white text-sm"
|
||||
>
|
||||
{tag.trim()}
|
||||
</Badge>
|
||||
<input
|
||||
type="text"
|
||||
value={tag}
|
||||
onChange={(e) => handleEditTag(index, e.target.value)}
|
||||
className="bg-black text-white border-none focus:outline-none w-auto"
|
||||
/>
|
||||
<button
|
||||
value={tag}
|
||||
type="button"
|
||||
onClick={() => handleRemoveTag(index)}
|
||||
className="remove-tag-button text-white"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -797,38 +902,21 @@ export default function FormVideoUpdate() {
|
|||
<div className="px-3 py-3">
|
||||
<div className="flex flex-col gap-6">
|
||||
<Label>Target Publish</Label>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="5"
|
||||
checked={selectedPublishers.includes(5)}
|
||||
onChange={() => handleCheckboxChange(5)}
|
||||
/>
|
||||
<Label htmlFor="5">UMUM</Label>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="6"
|
||||
checked={selectedPublishers.includes(6)}
|
||||
onChange={() => handleCheckboxChange(6)}
|
||||
/>
|
||||
<Label htmlFor="6">JOURNALIS</Label>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="7"
|
||||
checked={selectedPublishers.includes(7)}
|
||||
onChange={() => handleCheckboxChange(7)}
|
||||
/>
|
||||
<Label htmlFor="7">POLRI</Label>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="8"
|
||||
checked={selectedPublishers.includes(8)}
|
||||
onChange={() => handleCheckboxChange(8)}
|
||||
/>
|
||||
<Label htmlFor="8">KSP</Label>
|
||||
</div>
|
||||
{options.map((option: Option) => (
|
||||
<div key={option.id} className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id={option.id}
|
||||
checked={
|
||||
option.id === "all"
|
||||
? publishedFor.length ===
|
||||
options.filter((opt) => opt.id !== "all").length
|
||||
: publishedFor.includes(option.id)
|
||||
}
|
||||
onCheckedChange={() => handleCheckboxChange(option.id)}
|
||||
/>
|
||||
<Label htmlFor={option.id}>{option.name}</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import {
|
|||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
import { CalendarIcon, Clock1, MapPin, User2 } from "lucide-react";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import { addDays, format, parseISO, setDate } from "date-fns";
|
||||
import { DateRange } from "react-day-picker";
|
||||
|
|
@ -28,7 +28,19 @@ import MapHome from "@/components/maps/MapHome";
|
|||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
import Cookies from "js-cookie";
|
||||
import { detailSchedule, postSchedule } from "@/service/schedule/schedule";
|
||||
import {
|
||||
detailSchedule,
|
||||
listScheduleNext,
|
||||
listScheduleToday,
|
||||
postSchedule,
|
||||
} from "@/service/schedule/schedule";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion";
|
||||
import { formatDate } from "@fullcalendar/core/index.js";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -56,6 +68,8 @@ export default function FormEventDetail() {
|
|||
const [startTime, setStartTime] = useState("08:00");
|
||||
const [endTime, setEndTime] = useState("09:00");
|
||||
const [date, setDate] = useState<DateRange | undefined>();
|
||||
const [todayList, setTodayList] = useState([]);
|
||||
const [nextDayList, setNextDayList] = useState([]);
|
||||
|
||||
const [detail, setDetail] = useState<Detail>();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
|
|
@ -72,6 +86,16 @@ export default function FormEventDetail() {
|
|||
},
|
||||
});
|
||||
|
||||
async function getDataByDate() {
|
||||
const resToday = await listScheduleToday();
|
||||
const today = resToday?.data?.data;
|
||||
setTodayList(today);
|
||||
const resNext = await listScheduleNext();
|
||||
const next = resNext?.data?.data;
|
||||
|
||||
setNextDayList(next);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
|
|
@ -89,6 +113,7 @@ export default function FormEventDetail() {
|
|||
setStartTime(details.startTime);
|
||||
setEndTime(details.endTime);
|
||||
}
|
||||
getDataByDate();
|
||||
}
|
||||
}
|
||||
initState();
|
||||
|
|
@ -315,7 +340,66 @@ export default function FormEventDetail() {
|
|||
</div>
|
||||
</Card>
|
||||
<Card className="w-full lg:w-3/12">
|
||||
<div className="px-3 py-3">Jadwal Selanjutnya</div>
|
||||
<Accordion type="single" collapsible>
|
||||
{/* Jadwal Hari Ini */}
|
||||
<AccordionItem value="today">
|
||||
<AccordionTrigger className="font-semibold">
|
||||
Jadwal Hari Ini
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
{todayList?.length > 0 ? (
|
||||
<ul className="list-disc ml-4">
|
||||
{todayList.map((item: any, index) => (
|
||||
<li key={index} className="py-1">
|
||||
{item.name}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p className="text-gray-500">Tidak ada jadwal hari ini</p>
|
||||
)}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
{/* Jadwal Selanjutnya */}
|
||||
<AccordionItem value="next">
|
||||
<AccordionTrigger className="font-semibold">
|
||||
Jadwal Selanjutnya
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
{nextDayList?.length > 0 ? (
|
||||
<div className="list-disc ">
|
||||
{nextDayList.map((item: any, index) => (
|
||||
<div key={index} className="">
|
||||
<li className="text-base font-semibold">{item.title}</li>
|
||||
<p className="text-sm ml-5 flex my-2">
|
||||
<CalendarIcon size={20} />
|
||||
{formatDate(item?.startDate)}-
|
||||
{formatDate(item?.endDate)}
|
||||
</p>
|
||||
<p className="text-sm ml-5 flex my-2">
|
||||
<Clock1 size={20} />
|
||||
{item?.startTime}-{item?.endTime}
|
||||
</p>
|
||||
<p className="text-sm ml-5 flex items-center my-2">
|
||||
<MapPin size={50} />
|
||||
{item?.address}
|
||||
</p>
|
||||
<p className="text-sm ml-5">Disampaikan oleh:</p>
|
||||
<p className="text-sm ml-5 flex my-2">
|
||||
<User2 />
|
||||
{item?.speakerTitle} {item?.speakerName}
|
||||
</p>
|
||||
</div>
|
||||
// <p>{item.startDate}</p>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-gray-500">Tidak ada jadwal selanjutnya</p>
|
||||
)}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||
import * as z from "zod";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
|
|
@ -40,6 +39,7 @@ import { Textarea } from "@/components/ui/textarea";
|
|||
import { error } from "@/lib/swal";
|
||||
import Cookies from "js-cookie";
|
||||
import { postSchedule } from "@/service/schedule/schedule";
|
||||
import { useRouter } from "@/i18n/routing";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import {
|
|||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
import { CalendarIcon, Clock1, MapPin, User2 } from "lucide-react";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import { addDays, format, parseISO, setDate } from "date-fns";
|
||||
import { DateRange } from "react-day-picker";
|
||||
|
|
@ -28,7 +28,12 @@ import MapHome from "@/components/maps/MapHome";
|
|||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
import Cookies from "js-cookie";
|
||||
import { detailSchedule, postSchedule } from "@/service/schedule/schedule";
|
||||
import {
|
||||
detailSchedule,
|
||||
listScheduleNext,
|
||||
listScheduleToday,
|
||||
postSchedule,
|
||||
} from "@/service/schedule/schedule";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
|
|
@ -36,6 +41,13 @@ import {
|
|||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion";
|
||||
import { formatDate } from "@fullcalendar/core/index.js";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -64,6 +76,8 @@ export default function FormDetailPressRillis() {
|
|||
const [endTime, setEndTime] = useState("09:00");
|
||||
const [date, setDate] = useState<DateRange | undefined>();
|
||||
const [selectedTarget, setSelectedTarget] = useState("all");
|
||||
const [todayList, setTodayList] = useState([]);
|
||||
const [nextDayList, setNextDayList] = useState([]);
|
||||
|
||||
const [detail, setDetail] = useState<Detail>();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
|
|
@ -80,6 +94,16 @@ export default function FormDetailPressRillis() {
|
|||
},
|
||||
});
|
||||
|
||||
async function getDataByDate() {
|
||||
const resToday = await listScheduleToday();
|
||||
const today = resToday?.data?.data;
|
||||
setTodayList(today);
|
||||
const resNext = await listScheduleNext();
|
||||
const next = resNext?.data?.data;
|
||||
|
||||
setNextDayList(next);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
|
|
@ -97,6 +121,7 @@ export default function FormDetailPressRillis() {
|
|||
setStartTime(details.startTime);
|
||||
setEndTime(details.endTime);
|
||||
}
|
||||
getDataByDate();
|
||||
}
|
||||
}
|
||||
initState();
|
||||
|
|
@ -340,7 +365,66 @@ export default function FormDetailPressRillis() {
|
|||
</div>
|
||||
</Card>
|
||||
<Card className="w-full lg:w-3/12">
|
||||
<div className="px-3 py-3">Jadwal Selanjutnya</div>
|
||||
<Accordion type="single" collapsible>
|
||||
{/* Jadwal Hari Ini */}
|
||||
<AccordionItem value="today">
|
||||
<AccordionTrigger className="font-semibold">
|
||||
Jadwal Hari Ini
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
{todayList?.length > 0 ? (
|
||||
<ul className="list-disc ml-4">
|
||||
{todayList.map((item: any, index) => (
|
||||
<li key={index} className="py-1">
|
||||
{item.name}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p className="text-gray-500">Tidak ada jadwal hari ini</p>
|
||||
)}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
{/* Jadwal Selanjutnya */}
|
||||
<AccordionItem value="next">
|
||||
<AccordionTrigger className="font-semibold">
|
||||
Jadwal Selanjutnya
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
{nextDayList?.length > 0 ? (
|
||||
<div className="list-disc ">
|
||||
{nextDayList.map((item: any, index) => (
|
||||
<div key={index} className="">
|
||||
<li className="text-base font-semibold">{item.title}</li>
|
||||
<p className="text-sm ml-5 flex my-2">
|
||||
<CalendarIcon size={20} />
|
||||
{formatDate(item?.startDate)}-
|
||||
{formatDate(item?.endDate)}
|
||||
</p>
|
||||
<p className="text-sm ml-5 flex my-2">
|
||||
<Clock1 size={20} />
|
||||
{item?.startTime}-{item?.endTime}
|
||||
</p>
|
||||
<p className="text-sm ml-5 flex items-center my-2">
|
||||
<MapPin size={50} />
|
||||
{item?.address}
|
||||
</p>
|
||||
<p className="text-sm ml-5">Disampaikan oleh:</p>
|
||||
<p className="text-sm ml-5 flex my-2">
|
||||
<User2 />
|
||||
{item?.speakerTitle} {item?.speakerName}
|
||||
</p>
|
||||
</div>
|
||||
// <p>{item.startDate}</p>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-gray-500">Tidak ada jadwal selanjutnya</p>
|
||||
)}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import {
|
|||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
import { CalendarIcon, Clock1, Locate, MapPin, User2 } from "lucide-react";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import { addDays, format, parseISO, setDate } from "date-fns";
|
||||
import { DateRange } from "react-day-picker";
|
||||
|
|
@ -28,7 +28,20 @@ import MapHome from "@/components/maps/MapHome";
|
|||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
import Cookies from "js-cookie";
|
||||
import { detailSchedule, postSchedule } from "@/service/schedule/schedule";
|
||||
import {
|
||||
detailSchedule,
|
||||
listScheduleNext,
|
||||
listScheduleToday,
|
||||
postSchedule,
|
||||
} from "@/service/schedule/schedule";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion";
|
||||
import { formatDateToIndonesian } from "@/utils/globals";
|
||||
import { formatDate } from "@fullcalendar/core/index.js";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -56,6 +69,8 @@ export default function FormDetailPressConference() {
|
|||
const [startTime, setStartTime] = useState("08:00");
|
||||
const [endTime, setEndTime] = useState("09:00");
|
||||
const [date, setDate] = useState<DateRange | undefined>();
|
||||
const [todayList, setTodayList] = useState([]);
|
||||
const [nextDayList, setNextDayList] = useState([]);
|
||||
|
||||
const [detail, setDetail] = useState<Detail>();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
|
|
@ -72,6 +87,16 @@ export default function FormDetailPressConference() {
|
|||
},
|
||||
});
|
||||
|
||||
async function getDataByDate() {
|
||||
const resToday = await listScheduleToday();
|
||||
const today = resToday?.data?.data;
|
||||
setTodayList(today);
|
||||
const resNext = await listScheduleNext();
|
||||
const next = resNext?.data?.data;
|
||||
|
||||
setNextDayList(next);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
|
|
@ -89,6 +114,7 @@ export default function FormDetailPressConference() {
|
|||
setStartTime(details.startTime);
|
||||
setEndTime(details.endTime);
|
||||
}
|
||||
getDataByDate();
|
||||
}
|
||||
}
|
||||
initState();
|
||||
|
|
@ -315,7 +341,66 @@ export default function FormDetailPressConference() {
|
|||
</div>
|
||||
</Card>
|
||||
<Card className="w-full lg:w-3/12">
|
||||
<div className="px-3 py-3">Jadwal Selanjutnya</div>
|
||||
<Accordion type="single" collapsible>
|
||||
{/* Jadwal Hari Ini */}
|
||||
<AccordionItem value="today">
|
||||
<AccordionTrigger className="font-semibold">
|
||||
Jadwal Hari Ini
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
{todayList?.length > 0 ? (
|
||||
<ul className="list-disc ml-4">
|
||||
{todayList.map((item: any, index) => (
|
||||
<li key={index} className="py-1">
|
||||
{item.name}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p className="text-gray-500">Tidak ada jadwal hari ini</p>
|
||||
)}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
{/* Jadwal Selanjutnya */}
|
||||
<AccordionItem value="next">
|
||||
<AccordionTrigger className="font-semibold">
|
||||
Jadwal Selanjutnya
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
{nextDayList?.length > 0 ? (
|
||||
<div className="list-disc ">
|
||||
{nextDayList.map((item: any, index) => (
|
||||
<div key={index} className="">
|
||||
<li className="text-base font-semibold">{item.title}</li>
|
||||
<p className="text-sm ml-5 flex my-2">
|
||||
<CalendarIcon size={20} />
|
||||
{formatDate(item?.startDate)}-
|
||||
{formatDate(item?.endDate)}
|
||||
</p>
|
||||
<p className="text-sm ml-5 flex my-2">
|
||||
<Clock1 size={20} />
|
||||
{item?.startTime}-{item?.endTime}
|
||||
</p>
|
||||
<p className="text-sm ml-5 flex items-center my-2">
|
||||
<MapPin size={50} />
|
||||
{item?.address}
|
||||
</p>
|
||||
<p className="text-sm ml-5">Disampaikan oleh:</p>
|
||||
<p className="text-sm ml-5 flex my-2">
|
||||
<User2 />
|
||||
{item?.speakerTitle} {item?.speakerName}
|
||||
</p>
|
||||
</div>
|
||||
// <p>{item.startDate}</p>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-gray-500">Tidak ada jadwal selanjutnya</p>
|
||||
)}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||
import * as z from "zod";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
Popover,
|
||||
|
|
@ -30,6 +29,7 @@ import { Textarea } from "@/components/ui/textarea";
|
|||
import { error } from "@/lib/swal";
|
||||
import Cookies from "js-cookie";
|
||||
import { postSchedule } from "@/service/schedule/schedule";
|
||||
import { useRouter } from "@/i18n/routing";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -167,10 +167,10 @@ export default function FormPressConference() {
|
|||
)}
|
||||
</div>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="mt-5">
|
||||
<div className="mt-4 mb-3">
|
||||
<Label>Live Streaming</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<p>Aktifkan fitur live streaming</p>
|
||||
<Label>Aktifkan fitur live streaming</Label>
|
||||
<Switch
|
||||
defaultChecked={isLiveStreamingEnabled}
|
||||
color="primary"
|
||||
|
|
@ -184,7 +184,7 @@ export default function FormPressConference() {
|
|||
</div>
|
||||
|
||||
{isLiveStreamingEnabled && (
|
||||
<div className="mt-1">
|
||||
<div className="mb-2 mt-1">
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
|
|
@ -206,16 +206,17 @@ export default function FormPressConference() {
|
|||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col lg:flex-row mt-3 items-start lg:items-center justify-between">
|
||||
<div className="flex flex-col lg:flex-row mb-4 mt-2 items-start lg:items-center justify-between">
|
||||
<div className="flex flex-col ">
|
||||
<Label className="mr-3 mb-1">Tanggal</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
size="md"
|
||||
id="date"
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-[280px] lg:w-[300px] justify-start text-left font-normal",
|
||||
"w-[280px] lg:w-[250px] justify-start text-left font-normal border border-slate-300",
|
||||
!date && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
|
|
@ -292,7 +293,7 @@ export default function FormPressConference() {
|
|||
{errors.location?.message}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p>
|
||||
<p className="text-sm mt-4 font-semibold">DI SAMPAIKAN OLEH</p>
|
||||
<div className="flex flex-col ">
|
||||
<div className="mt-1">
|
||||
<Label>Nama Pangkat</Label>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -32,9 +32,22 @@ import {
|
|||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { ChevronDown, ChevronUp } from "lucide-react";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Dock,
|
||||
ImageIcon,
|
||||
Music,
|
||||
VideoIcon,
|
||||
} from "lucide-react";
|
||||
import FileUploader from "../shared/file-uploader";
|
||||
import { AudioRecorder } from "react-audio-voice-recorder";
|
||||
import Image from "next/image";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import WavesurferPlayer from "@wavesurfer/react";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
import { Upload } from "tus-js-client";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
|
||||
const taskSchema = z.object({
|
||||
// uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -62,6 +75,7 @@ export type taskDetail = {
|
|||
taskType: string;
|
||||
broadcastType: string;
|
||||
narration: string;
|
||||
attachmentUrl: string;
|
||||
is_active: string;
|
||||
};
|
||||
|
||||
|
|
@ -69,6 +83,16 @@ interface FileWithPreview extends File {
|
|||
preview: string;
|
||||
}
|
||||
|
||||
interface FileUploaded {
|
||||
id: number;
|
||||
url: string;
|
||||
}
|
||||
|
||||
type Url = {
|
||||
id: number;
|
||||
attachmentUrl: string;
|
||||
};
|
||||
|
||||
export default function FormTaskEdit() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -101,6 +125,7 @@ export default function FormTaskEdit() {
|
|||
const [type, setType] = useState<string>("1");
|
||||
const [selectedTarget, setSelectedTarget] = useState("3,4");
|
||||
const [detail, setDetail] = useState<taskDetail>();
|
||||
const [urlInputs, setUrlInputs] = useState<Url[]>([]);
|
||||
const [refresh] = useState(false);
|
||||
const [listDest, setListDest] = useState([]); // Data Polda dan Polres
|
||||
const [checkedLevels, setCheckedLevels] = useState(new Set());
|
||||
|
|
@ -110,14 +135,30 @@ export default function FormTaskEdit() {
|
|||
const [isRecording, setIsRecording] = useState(false);
|
||||
const [timer, setTimer] = useState<number>(120);
|
||||
|
||||
const [imageUploadedFiles, setImageUploadedFiles] = useState<FileUploaded[]>(
|
||||
[]
|
||||
);
|
||||
const [videoUploadedFiles, setVideoUploadedFiles] = useState<FileUploaded[]>(
|
||||
[]
|
||||
);
|
||||
const [textUploadedFiles, setTextUploadedFiles] = useState<FileUploaded[]>(
|
||||
[]
|
||||
);
|
||||
const [audioUploadedFiles, setAudioUploadedFiles] = useState<FileUploaded[]>(
|
||||
[]
|
||||
);
|
||||
|
||||
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
|
||||
const [unitSelection, setUnitSelection] = useState({
|
||||
allUnit: false,
|
||||
mabes: false,
|
||||
polda: false,
|
||||
polres: false,
|
||||
satker: false,
|
||||
});
|
||||
|
||||
const [links, setLinks] = useState<string[]>([""]);
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
|
|
@ -165,6 +206,11 @@ export default function FormTaskEdit() {
|
|||
|
||||
setDetail(details);
|
||||
|
||||
if (details?.urls) {
|
||||
// Save the URLs as objects
|
||||
setUrlInputs(details.urls);
|
||||
}
|
||||
|
||||
if (details?.assignedToLevel) {
|
||||
const levels = new Set(
|
||||
details.assignedToLevel.split(",").map(Number)
|
||||
|
|
@ -172,12 +218,32 @@ export default function FormTaskEdit() {
|
|||
setCheckedLevels(levels);
|
||||
}
|
||||
|
||||
const attachment = details?.files;
|
||||
setImageUploadedFiles(
|
||||
attachment?.filter((file: any) => file.fileTypeId == 1)
|
||||
);
|
||||
setVideoUploadedFiles(
|
||||
attachment?.filter((file: any) => file.fileTypeId == 2)
|
||||
);
|
||||
setTextUploadedFiles(
|
||||
attachment?.filter((file: any) => file.fileTypeId == 3)
|
||||
);
|
||||
setAudioUploadedFiles(
|
||||
attachment?.filter((file: any) => file.fileTypeId == 4)
|
||||
);
|
||||
|
||||
// Add more state setting here based on other fields like broadcastType, taskOutput, etc.
|
||||
}
|
||||
}
|
||||
initState();
|
||||
}, [id, refresh]);
|
||||
|
||||
const handleUrlChange = (index: number, newUrl: string) => {
|
||||
setUrlInputs((prev: any) =>
|
||||
prev.map((url: any, idx: any) => (idx === index ? newUrl : url))
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (detail?.broadcastType) {
|
||||
setBroadcastType(detail.broadcastType); // Mengatur nilai broadcastType dari API
|
||||
|
|
@ -188,11 +254,11 @@ export default function FormTaskEdit() {
|
|||
if (detail?.fileTypeOutput) {
|
||||
const outputSet = new Set(detail.fileTypeOutput.split(",").map(Number)); // Membagi string ke dalam array dan mengonversi ke nomor
|
||||
setTaskOutput({
|
||||
all: outputSet.has(0),
|
||||
all: outputSet.has(1),
|
||||
video: outputSet.has(2),
|
||||
audio: outputSet.has(4),
|
||||
image: outputSet.has(1),
|
||||
text: outputSet.has(3),
|
||||
image: outputSet.has(3),
|
||||
text: outputSet.has(5),
|
||||
});
|
||||
}
|
||||
}, [detail?.fileTypeOutput]);
|
||||
|
|
@ -207,6 +273,7 @@ export default function FormTaskEdit() {
|
|||
mabes: outputSet.has(1),
|
||||
polda: outputSet.has(2),
|
||||
polres: outputSet.has(3),
|
||||
satker: outputSet.has(4),
|
||||
});
|
||||
}
|
||||
}, [detail?.fileTypeOutput]);
|
||||
|
|
@ -231,8 +298,8 @@ export default function FormTaskEdit() {
|
|||
const fileTypeMapping = {
|
||||
all: "1",
|
||||
video: "2",
|
||||
audio: "3",
|
||||
image: "4",
|
||||
audio: "4",
|
||||
image: "3",
|
||||
text: "5",
|
||||
};
|
||||
|
||||
|
|
@ -241,6 +308,7 @@ export default function FormTaskEdit() {
|
|||
mabes: "1",
|
||||
polda: "2",
|
||||
polres: "3",
|
||||
satker: "4",
|
||||
};
|
||||
const assignmentPurposeString = Object.keys(unitSelection)
|
||||
.filter((key) => unitSelection[key as keyof typeof unitSelection])
|
||||
|
|
@ -265,6 +333,7 @@ export default function FormTaskEdit() {
|
|||
taskType: string;
|
||||
assignedToRole: string;
|
||||
broadcastType: string;
|
||||
attachmentUrl: string[];
|
||||
} = {
|
||||
...data,
|
||||
// assignmentType,
|
||||
|
|
@ -281,22 +350,58 @@ export default function FormTaskEdit() {
|
|||
narration: data.naration,
|
||||
platformType: "",
|
||||
title: data.title,
|
||||
attachmentUrl: links,
|
||||
};
|
||||
|
||||
const response = await createTask(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/contributor/task");
|
||||
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/contributor/task");
|
||||
// });
|
||||
};
|
||||
|
||||
const onSubmit = (data: TaskSchema) => {
|
||||
|
|
@ -322,24 +427,6 @@ export default function FormTaskEdit() {
|
|||
}));
|
||||
};
|
||||
|
||||
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
|
||||
const file = new File([blob], "voiceNote.webm", { type: "audio/webm" });
|
||||
setAudioFile(file);
|
||||
};
|
||||
const handleDeleteAudio = () => {
|
||||
// Remove the audio file by setting state to null
|
||||
setAudioFile(null);
|
||||
const audioElements = document.querySelectorAll("audio");
|
||||
audioElements.forEach((audio) => audio.remove());
|
||||
};
|
||||
|
||||
const onRecordingStart = () => {
|
||||
setIsRecording(true);
|
||||
|
||||
|
|
@ -365,6 +452,171 @@ export default function FormTaskEdit() {
|
|||
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 = () => {
|
||||
// Remove the audio file by setting state to null
|
||||
setAudioFile(null);
|
||||
const audioElements = document.querySelectorAll("audio");
|
||||
audioElements.forEach((audio) => audio.remove());
|
||||
};
|
||||
|
||||
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}/assignment/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/contributor/task");
|
||||
}
|
||||
}
|
||||
|
||||
const successSubmit = (redirect: string) => {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Data berhasil disimpan.",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then(() => {
|
||||
router.push(redirect);
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddRow = () => {
|
||||
setLinks([...links, ""]);
|
||||
};
|
||||
|
||||
// Remove a specific link row
|
||||
const handleRemoveRow = (index: number) => {
|
||||
const updatedLinks = links.filter((_: any, i: any) => i !== index);
|
||||
setLinks(updatedLinks);
|
||||
};
|
||||
|
||||
const renderFilePreview = (url: string) => {
|
||||
return (
|
||||
<Image
|
||||
width={48}
|
||||
height={48}
|
||||
alt={"file preview"}
|
||||
src={url}
|
||||
className=" rounded border p-0.5"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const handleLinkChange = (index: number, value: string) => {
|
||||
const updatedLinks = [...links];
|
||||
updatedLinks[index] = value;
|
||||
setLinks(updatedLinks);
|
||||
};
|
||||
|
||||
const handleAddLink = () => {
|
||||
setUrlInputs([
|
||||
...urlInputs,
|
||||
{ id: Date.now(), attachmentUrl: "" }, // Add a new empty object
|
||||
]);
|
||||
};
|
||||
|
||||
const handleRemoveFile = (id: number) => {};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<div className="px-6 py-6">
|
||||
|
|
@ -372,23 +624,6 @@ export default function FormTaskEdit() {
|
|||
{detail !== undefined ? (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="gap-5 mb-5">
|
||||
{/* <div className="space-y-2">
|
||||
<Label>Kode Unik</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="uniqueCode"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
size="md"
|
||||
type="text"
|
||||
value={detail?.uniqueCode}
|
||||
onChange={field.onChange}
|
||||
placeholder="Enter uniqueCode"
|
||||
readOnly
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div> */}
|
||||
<div className="space-y-2 mt-6">
|
||||
<Label>Judul</Label>
|
||||
<Controller
|
||||
|
|
@ -408,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
|
||||
|
|
@ -425,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
|
||||
|
|
@ -443,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">
|
||||
|
|
@ -530,7 +765,7 @@ export default function FormTaskEdit() {
|
|||
<div className="mt-6">
|
||||
<Label>Tipe Penugasan</Label>
|
||||
<RadioGroup
|
||||
value={detail.assignmentMainType.id.toString()} // State yang dipetakan ke value RadioGroup
|
||||
defaultValue={detail.assignmentMainType.id.toString()} // State yang dipetakan ke value RadioGroup
|
||||
onValueChange={(value) => setMainType(value)}
|
||||
// value={String(mainType)}
|
||||
// onValueChange={(value) => setMainType(Number(value))}
|
||||
|
|
@ -545,7 +780,7 @@ export default function FormTaskEdit() {
|
|||
<div className="mt-6">
|
||||
<Label>Jenis Tugas </Label>
|
||||
<RadioGroup
|
||||
value={detail.taskType.toString()}
|
||||
defaultValue={detail.taskType.toString()}
|
||||
onValueChange={(value) => setTaskType(String(value))}
|
||||
className="flex flex-wrap gap-3"
|
||||
>
|
||||
|
|
@ -559,7 +794,7 @@ export default function FormTaskEdit() {
|
|||
<div className="mt-6">
|
||||
<Label>Jenis Penugasan</Label>
|
||||
<RadioGroup
|
||||
value={detail.assignmentType.id.toString()} // State yang dipetakan ke value RadioGroup
|
||||
defaultValue={detail.assignmentType.id.toString()} // State yang dipetakan ke value RadioGroup
|
||||
onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
|
||||
className="flex flex-wrap gap-3"
|
||||
>
|
||||
|
|
@ -628,8 +863,37 @@ export default function FormTaskEdit() {
|
|||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .mp4 atau .mov."
|
||||
onDrop={(files) => setImageFiles(files)}
|
||||
onDrop={(files) => setVideoFiles(files)}
|
||||
/>
|
||||
{videoUploadedFiles?.map((file: any, index: number) => (
|
||||
<div>
|
||||
<div
|
||||
key={index}
|
||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="file-preview">
|
||||
<VideoIcon />
|
||||
</div>
|
||||
<div>
|
||||
<div className=" text-sm text-card-foreground">
|
||||
{file.fileName}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
size="icon"
|
||||
color="destructive"
|
||||
variant="outline"
|
||||
className=" border-none rounded-full"
|
||||
onClick={() => handleRemoveFile(file)}
|
||||
>
|
||||
<Icon icon="tabler:x" className=" h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<Label>Foto</Label>
|
||||
|
|
@ -641,6 +905,35 @@ export default function FormTaskEdit() {
|
|||
label="Upload file dengan format .png, .jpg, atau .jpeg."
|
||||
onDrop={(files) => setImageFiles(files)}
|
||||
/>
|
||||
{imageUploadedFiles?.map((file: any, index: number) => (
|
||||
<div>
|
||||
<div
|
||||
key={index}
|
||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="file-preview">
|
||||
<ImageIcon />
|
||||
</div>
|
||||
<div>
|
||||
<div className=" text-sm text-card-foreground">
|
||||
{file.fileName}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
size="icon"
|
||||
color="destructive"
|
||||
variant="outline"
|
||||
className=" border-none rounded-full"
|
||||
onClick={() => handleRemoveFile(file)}
|
||||
>
|
||||
<Icon icon="tabler:x" className=" h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<Label>Teks</Label>
|
||||
|
|
@ -652,9 +945,38 @@ export default function FormTaskEdit() {
|
|||
label="Upload file dengan format .pdf."
|
||||
onDrop={(files) => setTextFiles(files)}
|
||||
/>
|
||||
{textUploadedFiles?.map((file: any, index: number) => (
|
||||
<div>
|
||||
<div
|
||||
key={index}
|
||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="file-preview">
|
||||
<Dock />
|
||||
</div>
|
||||
<div>
|
||||
<div className=" text-sm text-card-foreground">
|
||||
{file.fileName}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
size="icon"
|
||||
color="destructive"
|
||||
variant="outline"
|
||||
className=" border-none rounded-full"
|
||||
onClick={() => handleRemoveFile(file)}
|
||||
>
|
||||
<Icon icon="tabler:x" className=" h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<Label>Voice Note</Label>
|
||||
<Label>Audio</Label>
|
||||
<AudioRecorder
|
||||
onRecordingComplete={addAudioElement}
|
||||
audioTrackConstraints={{
|
||||
|
|
@ -671,13 +993,44 @@ export default function FormTaskEdit() {
|
|||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .mp3 atau .wav."
|
||||
onDrop={(files) => setAudioFiles(files)}
|
||||
onDrop={(files) =>
|
||||
setAudioFiles((prev) => [...prev, ...files])
|
||||
}
|
||||
className="mt-2"
|
||||
/>
|
||||
{audioUploadedFiles?.map((file: any, index: number) => (
|
||||
<div>
|
||||
<div
|
||||
key={index}
|
||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="file-preview">
|
||||
<Music />
|
||||
</div>
|
||||
<div>
|
||||
<div className=" text-sm text-card-foreground">
|
||||
{file.fileName}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
size="icon"
|
||||
color="destructive"
|
||||
variant="outline"
|
||||
className=" border-none rounded-full"
|
||||
onClick={() => handleRemoveFile(file)}
|
||||
>
|
||||
<Icon icon="tabler:x" className=" h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{audioFile && (
|
||||
<div className="flex flex-row justify-between items-center">
|
||||
<p>Voice note ready to submit: {audioFile.name}</p>
|
||||
<p>Voice Note</p>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleDeleteAudio}
|
||||
|
|
@ -691,14 +1044,30 @@ export default function FormTaskEdit() {
|
|||
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
|
||||
{/* Display remaining time */}
|
||||
<div className="mt-4">
|
||||
<Label htmlFor="voiceNoteLink">Link Berita</Label>
|
||||
<Input
|
||||
id="voiceNoteLink"
|
||||
type="url"
|
||||
placeholder="Masukkan link voice note"
|
||||
value={voiceNoteLink}
|
||||
onChange={(e) => setVoiceNoteLink(e.target.value)}
|
||||
/>
|
||||
<h2 className="text-lg font-bold">Link Berita</h2>
|
||||
{urlInputs.map((url: any, index: any) => (
|
||||
<div
|
||||
key={url.id}
|
||||
className="flex items-center gap-2 mt-2"
|
||||
>
|
||||
<input
|
||||
type="url"
|
||||
className="border rounded p-2 w-full"
|
||||
defaultValue={url.attachmentUrl}
|
||||
onChange={(e) =>
|
||||
handleLinkChange(index, e.target.value)
|
||||
}
|
||||
placeholder={`Masukkan link berita ${index + 1}`}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
className="mt-4 bg-green-500 text-white px-4 py-2 rounded"
|
||||
onClick={handleAddLink}
|
||||
>
|
||||
Tambah Link
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -37,12 +37,15 @@ import { AudioRecorder } from "react-audio-voice-recorder";
|
|||
import FileUploader from "@/components/form/shared/file-uploader";
|
||||
import { Upload } from "tus-js-client";
|
||||
import { error } from "@/config/swal";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
import { loading } from "@/lib/swal";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
naration: z.string().min(2, {
|
||||
message: "Narasi Penugasan harus lebih dari 2 karakter.",
|
||||
}),
|
||||
// url: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
});
|
||||
|
||||
interface FileWithPreview extends File {
|
||||
|
|
@ -63,6 +66,7 @@ export type taskDetail = {
|
|||
id: number;
|
||||
name: string;
|
||||
};
|
||||
attachmentUrl: string;
|
||||
taskType: string;
|
||||
broadcastType: string;
|
||||
narration: string;
|
||||
|
|
@ -119,7 +123,9 @@ export default function FormTask() {
|
|||
mabes: false,
|
||||
polda: false,
|
||||
polres: false,
|
||||
satker: false,
|
||||
});
|
||||
const [links, setLinks] = useState<string[]>([""]);
|
||||
|
||||
const {
|
||||
register,
|
||||
|
|
@ -144,6 +150,7 @@ export default function FormTask() {
|
|||
try {
|
||||
const response = await getUserLevelForAssignments();
|
||||
setListDest(response?.data?.data.list);
|
||||
console.log("polda", response?.data?.data?.list);
|
||||
const initialExpandedState = response?.data?.data.list.reduce(
|
||||
(acc: any, polda: any) => {
|
||||
acc[polda.id] = false;
|
||||
|
|
@ -183,8 +190,8 @@ export default function FormTask() {
|
|||
const fileTypeMapping = {
|
||||
all: "1",
|
||||
video: "2",
|
||||
audio: "3",
|
||||
image: "4",
|
||||
audio: "4",
|
||||
image: "3",
|
||||
text: "5",
|
||||
};
|
||||
|
||||
|
|
@ -193,6 +200,7 @@ export default function FormTask() {
|
|||
mabes: "1",
|
||||
polda: "2",
|
||||
polres: "3",
|
||||
satker: "4",
|
||||
};
|
||||
const assignmentPurposeString = Object.keys(unitSelection)
|
||||
.filter((key) => unitSelection[key as keyof typeof unitSelection])
|
||||
|
|
@ -217,6 +225,7 @@ export default function FormTask() {
|
|||
taskType: string;
|
||||
assignedToRole: string;
|
||||
broadcastType: string;
|
||||
attachmentUrl: string[];
|
||||
} = {
|
||||
...data,
|
||||
// assignmentType,
|
||||
|
|
@ -232,6 +241,7 @@ export default function FormTask() {
|
|||
narration: data.naration,
|
||||
platformType: "",
|
||||
title: data.title,
|
||||
attachmentUrl: links,
|
||||
};
|
||||
|
||||
const response = await createTask(requestData);
|
||||
|
|
@ -240,7 +250,7 @@ export default function FormTask() {
|
|||
console.log("response", response);
|
||||
|
||||
const id = response?.data?.data.id;
|
||||
|
||||
loading();
|
||||
if (imageFiles?.length == 0) {
|
||||
setIsImageUploadFinish(true);
|
||||
}
|
||||
|
|
@ -265,19 +275,15 @@ export default function FormTask() {
|
|||
if (audioFiles?.length == 0) {
|
||||
setIsAudioUploadFinish(true);
|
||||
}
|
||||
audioFiles?.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(index, String(id), item, "4", "0");
|
||||
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/contributor/task");
|
||||
// });
|
||||
};
|
||||
|
||||
const onSubmit = (data: TaskSchema) => {
|
||||
|
|
@ -335,15 +341,19 @@ export default function FormTask() {
|
|||
audio.controls = true;
|
||||
document.body.appendChild(audio);
|
||||
|
||||
// Convert Blob to File
|
||||
const file = new File([blob], "voiceNote.webm", { type: "audio/webm" });
|
||||
setAudioFile(file);
|
||||
// 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 = () => {
|
||||
// Remove the audio file by setting state to null
|
||||
setAudioFile(null);
|
||||
const audioElements = document.querySelectorAll("audio");
|
||||
audioElements.forEach((audio) => audio.remove());
|
||||
|
||||
const handleDeleteAudio = (index: number) => {
|
||||
setAudioFiles((prev) => prev.filter((_, idx) => idx !== index));
|
||||
};
|
||||
|
||||
async function uploadResumableFile(
|
||||
|
|
@ -355,20 +365,28 @@ export default function FormTask() {
|
|||
) {
|
||||
console.log(idx, id, file, fileTypeId, duration);
|
||||
|
||||
// const placements = getPlacement(file.placements);
|
||||
// console.log("Placementttt: : ", placements);
|
||||
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}/assignment/file/upload`,
|
||||
headers: headers,
|
||||
retryDelays: [0, 3000, 6000, 12_000, 24_000],
|
||||
chunkSize: 20_000,
|
||||
metadata: {
|
||||
assignmentid: id,
|
||||
assignmentId: id,
|
||||
filename: file.name,
|
||||
filetype: file.type,
|
||||
contentType: file.type,
|
||||
fileTypeId: fileTypeId,
|
||||
duration: "",
|
||||
isWatermark: "true", // hardcode
|
||||
duration,
|
||||
},
|
||||
onBeforeRequest: function (req) {
|
||||
var xhr = req.getUnderlyingObject();
|
||||
xhr.withCredentials = true;
|
||||
},
|
||||
onError: async (e: any) => {
|
||||
console.log("Error upload :", e);
|
||||
|
|
@ -379,7 +397,7 @@ export default function FormTask() {
|
|||
bytesAccepted: any,
|
||||
bytesTotal: any
|
||||
) => {
|
||||
const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
|
||||
// const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
|
||||
// progressInfo[idx].percentage = uploadPersen;
|
||||
// counterUpdateProgress++;
|
||||
// console.log(counterUpdateProgress);
|
||||
|
|
@ -441,6 +459,22 @@ export default function FormTask() {
|
|||
});
|
||||
};
|
||||
|
||||
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">
|
||||
|
|
@ -468,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}>
|
||||
|
|
@ -482,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
|
||||
|
|
@ -498,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">
|
||||
|
|
@ -702,7 +736,7 @@ export default function FormTask() {
|
|||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .mp4 atau .mov."
|
||||
onDrop={(files) => setImageFiles(files)}
|
||||
onDrop={(files) => setVideoFiles(files)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -728,7 +762,7 @@ export default function FormTask() {
|
|||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Voice Note</Label>
|
||||
<Label>Audio</Label>
|
||||
<AudioRecorder
|
||||
onRecordingComplete={addAudioElement}
|
||||
audioTrackConstraints={{
|
||||
|
|
@ -745,34 +779,61 @@ export default function FormTask() {
|
|||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .mp3 atau .wav."
|
||||
onDrop={(files) => setAudioFiles(files)}
|
||||
onDrop={(files) =>
|
||||
setAudioFiles((prev) => [...prev, ...files])
|
||||
}
|
||||
className="mt-2"
|
||||
/>
|
||||
</div>
|
||||
{audioFile && (
|
||||
<div className="flex flex-row justify-between items-center">
|
||||
<p>Voice note ready to submit: {audioFile.name}</p>
|
||||
{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}
|
||||
onClick={() => handleDeleteAudio(idx)}
|
||||
size="sm"
|
||||
color="destructive"
|
||||
>
|
||||
X
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
))}
|
||||
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
|
||||
{/* Display remaining time */}
|
||||
<div className="mt-4">
|
||||
<Label htmlFor="voiceNoteLink">Link Berita</Label>
|
||||
<Input
|
||||
id="voiceNoteLink"
|
||||
type="url"
|
||||
placeholder="Masukkan link voice note"
|
||||
value={voiceNoteLink}
|
||||
onChange={(e) => setVoiceNoteLink(e.target.value)}
|
||||
/>
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { Reveal } from "./Reveal";
|
|||
import { useTranslations } from "next-intl";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useParams } from "next/navigation";
|
||||
import Image from "next/image";
|
||||
|
||||
const ContentCategory = (props: { group?: string }) => {
|
||||
const [categories, setCategories] = useState<any>();
|
||||
|
|
@ -20,9 +21,7 @@ const ContentCategory = (props: { group?: string }) => {
|
|||
}, []);
|
||||
const initFetch = async () => {
|
||||
const response = await getPublicCategoryData(
|
||||
props.group == "mabes" ? "" :
|
||||
props.group == "polda" && poldaName && String(poldaName)?.length > 1 ? poldaName :
|
||||
props.group == "satker" && satkerName && String(satkerName)?.length > 1 ? "satker-"+satkerName : "",
|
||||
props.group == "mabes" ? "" : props.group == "polda" && poldaName && String(poldaName)?.length > 1 ? poldaName : props.group == "satker" && satkerName && String(satkerName)?.length > 1 ? "satker-" + satkerName : "",
|
||||
"",
|
||||
locale == "en" ? true : false
|
||||
);
|
||||
|
|
@ -55,9 +54,9 @@ const ContentCategory = (props: { group?: string }) => {
|
|||
<div className="grid my-3 grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{categories?.map((category: any, index: number) =>
|
||||
!seeAllValue ? (
|
||||
index < 8 ? (
|
||||
index < 4 ? (
|
||||
<Link key={category?.id} href={`all/filter?category=${category?.id}`} className="relative group rounded-md overflow-hidden shadow-md hover:shadow-lg">
|
||||
<img src={category?.thumbnailLink} className="w-full h-48 sm:h-40 object-cover group-hover:scale-110 transition-transform duration-300" />
|
||||
<Image alt="category" width={2560} height={1440} src={category?.thumbnailLink} className="w-full h-48 sm:h-40 object-cover group-hover:scale-110 transition-transform duration-300" />
|
||||
<div className="absolute bottom-0 rounded-lg left-0 right-0 bg-gray-400 border-l-4 mb-4 border-[#bb3523] text-white p-2">
|
||||
<h3 className="text-sm font-semibold truncate">{category?.name}</h3>
|
||||
</div>
|
||||
|
|
@ -67,7 +66,7 @@ const ContentCategory = (props: { group?: string }) => {
|
|||
)
|
||||
) : (
|
||||
<Link key={category?.id} href={`all/filter?category=${category?.id}`} className="relative group rounded-md overflow-hidden shadow-md hover:shadow-lg">
|
||||
<img src={category?.thumbnailLink} className="w-full h-48 sm:h-40 object-cover group-hover:scale-110 transition-transform duration-300" />
|
||||
<Image alt="category" width={2560} height={1440} src={category?.thumbnailLink} className="w-full h-48 sm:h-40 object-cover group-hover:scale-110 transition-transform duration-300" />
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-gray-400 border-l-4 mb-4 border-[#bb3523] rounded-lg text-white p-2">
|
||||
<h3 className="text-sm font-semibold truncate">{category?.name}</h3>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@ import Link from "next/link";
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { generateLocalizedPath } from "@/utils/globals";
|
||||
import { useParams, usePathname, useRouter } from "next/navigation";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
|
||||
import { getPrivacy } from "@/service/landing/landing";
|
||||
import parse from "html-react-parser";
|
||||
import { useTranslations } from "next-intl";
|
||||
import NewsTicker from "./news-tickers";
|
||||
|
||||
const Footer = () => {
|
||||
const router = useRouter();
|
||||
|
|
@ -14,6 +16,7 @@ const Footer = () => {
|
|||
const params = useParams();
|
||||
const locale = params?.locale;
|
||||
const [privacy, setPrivacy] = useState();
|
||||
const t = useTranslations("LandingPage");
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
|
|
@ -24,19 +27,21 @@ const Footer = () => {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<footer className="bg-[#bb3523] text-white text-xs lg:text-sm py-4">
|
||||
<div className="container mx-auto flex flex-col md:flex-row justify-between items-center space-y-2 md:space-y-0 px-4">
|
||||
<footer className="bg-[#bb3523] text-white text-xs lg:text-sm py-4 space-y-3 lg:space-y-0 h-">
|
||||
<div className="mx-auto flex flex-col md:flex-row justify-between items-center px-4">
|
||||
{/* Hak Cipta */}
|
||||
<div className="text-center md:text-left">Hak Cipta © {new Date().getFullYear()} Divisi Humas Polri. All Right Reserved.</div>
|
||||
<div className="text-center md:text-left">
|
||||
{t("copyright")} © {new Date().getFullYear()} {t("publicRelation")} {t("reserved")}
|
||||
</div>
|
||||
|
||||
{/* Menu Links */}
|
||||
<div className="flex flex-wrap justify-center items-center space-x-3">
|
||||
<Link href={generateLocalizedPath("/feedback", String(locale))} className="hover:underline">
|
||||
FeedBack
|
||||
{t("feedback")}
|
||||
</Link>
|
||||
<span className="hidden md:inline-block ">|</span>
|
||||
<Link href={generateLocalizedPath("/contact", String(locale))} className="hover:underline">
|
||||
Hubungi Kami
|
||||
{t("contact")}
|
||||
</Link>
|
||||
<span className="hidden md:inline-block">|</span>
|
||||
<Link href={generateLocalizedPath("/faqs", String(locale))} className="hover:underline">
|
||||
|
|
@ -47,13 +52,13 @@ const Footer = () => {
|
|||
<DialogTrigger>
|
||||
<span className="hidden md:inline-block">|</span>
|
||||
<a href="#" className="hover:underline px-2">
|
||||
Privacy
|
||||
{t("privacy")}
|
||||
</a>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="flex flex-col overflow-y-scroll h-[80%]" size="md">
|
||||
<div className="flex flex-row items-center justify-center gap-4">
|
||||
<img src="/assets/icon-privacy.png" alt="Privacy" />
|
||||
<p className="font-semibold text-lg">Kebijakan Privacy</p>
|
||||
<p className="font-semibold text-lg">{t("privacy")}</p>
|
||||
</div>
|
||||
<div className="container text-dark">{parse(String(privacy))}</div>
|
||||
</DialogContent>
|
||||
|
|
|
|||
|
|
@ -67,8 +67,8 @@ const HeaderManagement = () => {
|
|||
return (
|
||||
<div>
|
||||
{/* Header */}
|
||||
<div className="bg-[#504e52] p-12">
|
||||
<div className="flex justify-between mx-10">
|
||||
<div className="bg-[#504e52] p-10 lg:p-12">
|
||||
<div className="flex flex-col lg:flex-row justify-center lg:justify-between mx-6 lg:mx-10">
|
||||
<div className="flex items-center gap-2 ">
|
||||
<img src="/assets/avatar-profile.png" alt="avatar" className="w-14 h-14" />
|
||||
<div className="flex flex-col mx-2">
|
||||
|
|
@ -82,9 +82,9 @@ const HeaderManagement = () => {
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Link href="/profile" className="flex items-center text-white gap-2">
|
||||
<Link href="/profile" className="flex justify-center items-center text-white gap-2 mt-3 lg:mt-0">
|
||||
<Icon icon="tdesign:setting-1-filled" />
|
||||
Pengaturan
|
||||
<p className="text-sm lg:text-base">Pengaturan</p>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,29 +6,40 @@ import { getHeroData } from "@/service/landing/landing";
|
|||
import Link from "next/link";
|
||||
import { useParams, usePathname, useRouter } from "next/navigation";
|
||||
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel";
|
||||
import { Skeleton } from "../ui/skeleton";
|
||||
import Image from "next/image";
|
||||
|
||||
const Hero: React.FC = () => {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const params = useParams();
|
||||
const locale = params?.locale;
|
||||
|
||||
const [isLoading, setIsLoading] = useState<any>(true);
|
||||
const [heroData, setHeroData] = useState<any>();
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 3000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchCategories() {
|
||||
const url = 'https://netidhub.com/api/csrf';
|
||||
|
||||
const url = "https://netidhub.com/api/csrf";
|
||||
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
|
||||
const data = await response.json();
|
||||
return data; // Menampilkan data yang diterima dari API
|
||||
return data; // Menampilkan data yang diterima dari API
|
||||
} catch (error) {
|
||||
console.error('Fetch error: ', error);
|
||||
console.error("Fetch error: ", error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -43,63 +54,113 @@ const Hero: React.FC = () => {
|
|||
return (
|
||||
<div className="flex flex-col lg:flex-row items-start justify-center gap-8 px-4 lg:px-20 py-4 mx-auto w-auto mt-6">
|
||||
{/* Section Gambar Utama */}
|
||||
<Carousel className="lg:w-2/3 w-full lg:h-full ">
|
||||
<CarouselContent>
|
||||
{heroData?.map((list: any) => (
|
||||
<CarouselItem key={list?.id}>
|
||||
<div className="relative h-[310px] lg:h-[420px]">
|
||||
<img src={list?.thumbnailLink} alt="Gambar Utama" className="w-full h-[310px] lg:h-[420px] rounded-lg object-cover" />
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-transparent backdrop-blur-sm text-black dark:text-white p-4 rounded-b-lg">
|
||||
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-sm px-4 py-1">{list?.categoryName}</span>
|
||||
<Link href={`${locale}/image/detail/${list?.slug}`}>
|
||||
<h2 className="text-lg font-bold mt-2">{list?.title}</h2>
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col space-y-3 mx-auto w-full lg:w-2/3">
|
||||
<Skeleton className="h-[310px] lg:h-[420px] rounded-xl" />
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-[250px]" />
|
||||
<Skeleton className="h-4 w-[200px]" />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Carousel className="lg:w-2/3 lg:h-full ">
|
||||
<CarouselContent>
|
||||
{heroData?.map((list: any) => (
|
||||
<CarouselItem key={list?.id}>
|
||||
<div className="relative h-[310px] lg:h-[420px]">
|
||||
<Image src={list?.thumbnailLink} alt="gambar-utama" width={1920} height={1080} className="w-full h-[310px] lg:h-[420px] rounded-lg object-cover" />
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-transparent backdrop-blur-sm text-black dark:text-white p-4 rounded-b-lg">
|
||||
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-xs px-2 py-1">{list?.categoryName}</span>
|
||||
<Link href={`${locale}/image/detail/${list?.slug}`}>
|
||||
<h2 className="text-lg font-bold mt-2">{list?.title}</h2>
|
||||
</Link>
|
||||
<p className="text-xs flex flex-row items-center gap-1 mt-1">
|
||||
{formatDateToIndonesian(new Date(list?.createdAt))} {list?.timezone ? list?.timezone : "WIB"}|{" "}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
|
||||
/>
|
||||
</svg>{" "}
|
||||
{list?.clickCount}{" "}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
)}
|
||||
|
||||
{/* Section Kanan */}
|
||||
<div>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<div className="flex items-center gap-4 max-w-sm mx-auto mb-3">
|
||||
<Skeleton className="h-[73px] w-16 rounded-md" />
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-[250px]" />
|
||||
<Skeleton className="h-4 w-[200px]" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="items-center hidden md:block gap-4 max-w-sm mx-auto mb-3 lg:flex">
|
||||
<Skeleton className="h-[73px] w-16 rounded-md" />
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-[250px]" />
|
||||
<Skeleton className="h-4 w-[200px]" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden md:block items-center gap-4 max-w-sm mx-auto mb-3 lg:flex">
|
||||
<Skeleton className="h-[73px] w-16 rounded-md" />
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-[250px]" />
|
||||
<Skeleton className="h-4 w-[200px]" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden md:block items-center gap-4 max-w-sm mx-auto mb-3 lg:flex">
|
||||
<Skeleton className="h-[73px] w-16 rounded-md" />
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-[250px]" />
|
||||
<Skeleton className="h-4 w-[200px]" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden md:block items-center gap-4 max-w-sm mx-auto lg:flex">
|
||||
<Skeleton className="h-[73px] w-16 rounded-md" />
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-[250px]" />
|
||||
<Skeleton className="h-4 w-[200px]" />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<ul className="py-4 lg:py-0 flex flex-row lg:flex-col gap-4 flex-nowrap w-[95vw] lg:w-auto overflow-x-auto">
|
||||
{heroData?.map((item: any) => (
|
||||
<li key={item?.id} className="flex gap-4 flex-row lg:w-full ">
|
||||
<div className="flex-shrink-0 w-24 rounded-lg">
|
||||
<Image width={720} height={480} src={item?.thumbnailLink} alt={item?.title} className="w-full h-[73px] object-cover rounded-lg" />
|
||||
</div>
|
||||
<div className="w-[280px] lg:w-auto">
|
||||
<span className="text-white bg-[#bb3523] px-4 py-1 rounded-lg flex text-[8px] font-bold uppercase w-fit">{item?.categoryName}</span>
|
||||
<Link href={`${locale}/image/detail/${item?.slug}`}>
|
||||
<h3 className="text-base font-bold mt-2">{textEllipsis(item?.title, 30)}</h3>
|
||||
</Link>
|
||||
<p className="text-xs flex flex-row items-center gap-1 mt-1">
|
||||
{formatDateToIndonesian(new Date(list?.createdAt))} {list?.timezone ? list?.timezone : "WIB"}|{" "}
|
||||
<p className="text-[10px] flex flex-row items-center gap-1 text-gray-500 mt-1">
|
||||
{formatDateToIndonesian(new Date(item?.createdAt))} {item?.timezone ? item?.timezone : "WIB"} |{" "}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
|
||||
/>
|
||||
</svg>{" "}
|
||||
{list?.clickCount}{" "}
|
||||
{item?.clickCount}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
|
||||
{/* Section Kanan */}
|
||||
<div className=" ">
|
||||
<ul className="py-4 lg:py-0 flex flex-row lg:flex-col gap-4 flex-nowrap w-full overflow-x-auto">
|
||||
{heroData?.map((item: any) => (
|
||||
<li key={item?.id} className="flex gap-4 flex-row lg:w-full ">
|
||||
<div className="flex-shrink-0 w-24 rounded-lg">
|
||||
<img src={item?.thumbnailLink} alt={item?.title} className="w-full h-[73px] object-cover rounded-lg" />
|
||||
</div>
|
||||
<div className="w-[280px] lg:w-auto">
|
||||
<span className="text-white bg-[#bb3523] px-4 py-1 rounded-lg flex text-[8px] font-bold uppercase w-fit">{item?.categoryName}</span>
|
||||
<Link href={`${locale}/image/detail/${item?.slug}`}>
|
||||
<h3 className="text-base font-bold mt-2">{textEllipsis(item?.title, 30)}</h3>
|
||||
</Link>
|
||||
<p className="text-[10px] flex flex-row items-center gap-1 text-gray-500 mt-1">
|
||||
{formatDateToIndonesian(new Date(item?.createdAt))} {item?.timezone ? item?.timezone : "WIB"} |{" "}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
|
||||
/>
|
||||
</svg>{" "}
|
||||
{item?.clickCount}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -5,15 +5,18 @@ import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|||
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel";
|
||||
import { useParams, usePathname, useRouter } from "next/navigation";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { formatDateToIndonesian } from "@/utils/globals";
|
||||
import { formatDateToIndonesian, secondToTimes } from "@/utils/globals";
|
||||
import { getListContent } from "@/service/landing/landing";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { Reveal } from "./Reveal";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Skeleton } from "../ui/skeleton";
|
||||
import Image from "next/image";
|
||||
|
||||
const NewContent = (props: { group: string; type: string }) => {
|
||||
const [newContent, setNewContent] = useState<any>();
|
||||
const [selectedTab, setSelectedTab] = useState("image");
|
||||
const [isLoading, setIsLoading] = useState<any>(true);
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const params = useParams();
|
||||
|
|
@ -22,6 +25,14 @@ const NewContent = (props: { group: string; type: string }) => {
|
|||
const satkerName = params?.satker_name;
|
||||
const t = useTranslations("LandingPage");
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 3000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
initFetch();
|
||||
}, [selectedTab]);
|
||||
|
|
@ -91,55 +102,135 @@ const NewContent = (props: { group: string; type: string }) => {
|
|||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
<div className="px-0 lg:px-10">
|
||||
{selectedTab == "video" ? (
|
||||
newContent?.length > 0 ? (
|
||||
<Carousel className="w-full max-w-7xl mx-auto">
|
||||
<CarouselContent>
|
||||
{newContent?.map((video: any) => (
|
||||
<CarouselItem key={video?.id} className="md:basis-1/2 lg:basis-1/3">
|
||||
<Link href={`/video/detail/${video?.slug}`} className="relative group overflow-hidden shadow-md hover:shadow-lg">
|
||||
<img src={video?.thumbnailLink} className="w-full rounded-lg h-48 lg:h-60 object-cover group-hover:scale-100 transition-transform duration-300" />
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-gray-600 border-l-4 border-[#bb3523] rounded-lg backdrop-blur-sm text-white p-2">
|
||||
<h1 className="text-sm lg:text-lg mb-2 font-semibold h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">{video?.title}</h1>
|
||||
<p className="flex flex-row items-center text-[10px] gap-2">
|
||||
{formatDateToIndonesian(new Date(video?.createdAt))} {video?.timezone ? video?.timezone : "WIB"} | <Icon icon="formkit:eye" width="15" height="15" /> {video.clickCount}{" "}
|
||||
</p>
|
||||
|
||||
{isLoading ? (
|
||||
<div className="flex flex-row space-y-3 w-full justify-center items-center gap-10">
|
||||
<Skeleton className="h-[200px] w-[400px] rounded-xl" />
|
||||
<Skeleton className="h-[200px] w-[400px] rounded-xl hidden md:block" />
|
||||
<Skeleton className="h-[200px] w-[400px] rounded-xl hidden md:block" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="px-0 lg:px-10">
|
||||
{selectedTab == "image" ? (
|
||||
newContent?.length > 0 ? (
|
||||
<Carousel className="w-full max-w-7xl mx-auto">
|
||||
<CarouselContent>
|
||||
{newContent?.map((image: any) => (
|
||||
<CarouselItem key={image?.id} className="md:basis-1/2 lg:basis-1/3">
|
||||
<Link href={`/image/detail/${image?.slug}`} className="relative group overflow-hidden shadow-md hover:shadow-lg">
|
||||
<Image width={2560} height={1440} alt="image" src={image?.thumbnailLink} className="w-full rounded-lg h-48 lg:h-60 object-cover group-hover:scale-100 transition-transform duration-300" />
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-gray-600 border-l-4 border-[#bb3523] rounded-lg backdrop-blur-sm text-white p-2">
|
||||
<p className="text-sm lg:text-base mb-2 font-semibold h-6 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">{image?.title}</p>
|
||||
<p className="flex flex-row items-center text-[10px] gap-2">
|
||||
{formatDateToIndonesian(new Date(image?.createdAt))} {image?.timezone ? image?.timezone : "WIB"} | <Icon icon="formkit:eye" width="15" height="15" /> {image.clickCount}{" "}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
) : (
|
||||
<p className="flex items-center justify-center">
|
||||
<Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
</p>
|
||||
)
|
||||
) : selectedTab == "audio" ? (
|
||||
newContent?.length > 0 ? (
|
||||
<Carousel className="w-full max-w-7xl mx-auto">
|
||||
<CarouselContent>
|
||||
{newContent?.map((audio: any) => (
|
||||
<CarouselItem key={audio?.id} className="md:basis-1/2 lg:basis-1/3">
|
||||
<div className="flex flex-row gap-6">
|
||||
<Link href={`/audio/detail/${audio?.slug}`} className="flex flex-col sm:flex-row items-center 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-24 h-8 lg: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 text-center items-center">
|
||||
{formatDateToIndonesian(new Date(audio?.createdAt))} {audio?.timezone ? audio?.timezone : "WIB"} | <Icon icon="formkit:eye" width="15" height="15" />
|
||||
{audio?.clickCount}{" "}
|
||||
</div>
|
||||
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">{audio?.title}</div>
|
||||
<p className="text-sm"> {audio?.duration ? secondToTimes(Number(audio?.duration)) : "00:00:00"}</p>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</Link>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
) : (
|
||||
<p className="flex items-center justify-center">
|
||||
<img src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
</p>
|
||||
)
|
||||
) : selectedTab == "audio" ? (
|
||||
newContent?.length > 0 ? (
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
) : (
|
||||
<p className="flex items-center justify-center">
|
||||
<Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
</p>
|
||||
)
|
||||
) : selectedTab == "video" ? (
|
||||
newContent?.length > 0 ? (
|
||||
<Carousel className="w-full max-w-7xl mx-auto">
|
||||
<CarouselContent>
|
||||
{newContent?.map((video: any) => (
|
||||
<CarouselItem key={video?.id} className="md:basis-1/2 lg:basis-1/3">
|
||||
<Link href={`/video/detail/${video?.slug}`} className="relative group rounded-md overflow-hidden shadow-md hover:shadow-lg">
|
||||
<Image alt="video" width={2560} height={1440} src={video?.thumbnailLink} className="w-full h-40 lg:h-60 object-cover rounded-lg group-hover:scale-100 transition-transform duration-300" />
|
||||
<div className="absolute bottom-0 rounded-lg left-0 right-0 border-l-4 border-[#bb3523] bg-gray-600 text-white p-2">
|
||||
<p className="text-sm lg:text-base mb-2 font-semibold h-6 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">{video?.title}</p>
|
||||
<p className="flex flex-row items-center text-sm gap-2">
|
||||
{formatDateToIndonesian(new Date(video?.createdAt))} {video?.timezone ? video?.timezone : "WIB"}| <Icon icon="formkit:eye" width="15" height="15" /> {video?.clickCount}{" "}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
) : (
|
||||
<p className="flex items-center justify-center">
|
||||
<Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
</p>
|
||||
)
|
||||
) : newContent.length > 0 ? (
|
||||
<Carousel className="w-full max-w-7xl mx-auto">
|
||||
<CarouselContent>
|
||||
{newContent?.map((audio: any) => (
|
||||
<CarouselItem key={audio?.id} className="md:basis-1/2 lg:basis-1/3">
|
||||
<div className="flex flex-row gap-6">
|
||||
<Link href={`/audio/detail/${audio?.slug}`} className="flex flex-col sm:flex-row items-center 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-8 lg:h-16">
|
||||
<svg width="32" height="34" viewBox="0 0 32 34" fill="null" xmlns="http://www.w3.org/2000/svg">
|
||||
{newContent?.map((text: any) => (
|
||||
<CarouselItem key={text?.id} className="md:basis-1/2 lg:basis-1/3">
|
||||
<div className="md:basis-1/2 lg:basis-1/3">
|
||||
<Link href={`/document/detail/${text?.slug}`} 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">
|
||||
<div className="flex items-center justify-center rounded-lg w-16 h-2 lg:h-16">
|
||||
<svg width="28" height="34" viewBox="0 0 28 34" fill="none" 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"
|
||||
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(audio?.createdAt))} {audio?.timezone ? audio?.timezone : "WIB"} | <Icon icon="formkit:eye" width="15" height="15" /> {audio?.clickCount}{" "}
|
||||
<div className="flex w-full pr-10 flex-col flex-1">
|
||||
<div className="text-gray-500 dark:text-gray-400 flex flex-row items-center text-xs gap-0 lg:gap-1 mt-1 lg:text-sm">
|
||||
{formatDateToIndonesian(new Date(text?.createdAt))}
|
||||
{text?.timezone ? text?.timezone : "WIB"}|
|
||||
<Icon icon="formkit:eye" width="15" height="15" />
|
||||
{text?.clickCount}
|
||||
</div>
|
||||
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible ">{text?.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 {t("document")}
|
||||
</div>
|
||||
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">{audio?.title}</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
|
@ -151,80 +242,11 @@ const NewContent = (props: { group: string; type: string }) => {
|
|||
</Carousel>
|
||||
) : (
|
||||
<p className="flex items-center justify-center">
|
||||
<img src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
<Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
</p>
|
||||
)
|
||||
) : selectedTab == "image" ? (
|
||||
newContent?.length > 0 ? (
|
||||
<Carousel className="w-full max-w-7xl mx-auto">
|
||||
<CarouselContent>
|
||||
{newContent?.map((image: any) => (
|
||||
<CarouselItem key={image?.id} className="md:basis-1/2 lg:basis-1/3">
|
||||
<Link href={`/image/detail/${image?.slug}`} className="relative group rounded-md overflow-hidden shadow-md hover:shadow-lg">
|
||||
<img src={image?.thumbnailLink} className="w-full h-40 lg:h-60 object-cover rounded-lg group-hover:scale-100 transition-transform duration-300" />
|
||||
<div className="absolute bottom-0 rounded-lg left-0 right-0 border-l-4 border-[#bb3523] bg-gray-600 text-white p-2">
|
||||
<h1 className="text-sm font-semibold h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">{image?.title}</h1>
|
||||
<p className="flex flex-row items-center text-sm gap-2">
|
||||
{formatDateToIndonesian(new Date(image?.createdAt))} {image?.timezone ? image?.timezone : "WIB"}| <Icon icon="formkit:eye" width="15" height="15" /> {image?.clickCount}{" "}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
) : (
|
||||
<p className="flex items-center justify-center">
|
||||
<img src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
</p>
|
||||
)
|
||||
) : newContent.length > 0 ? (
|
||||
<Carousel className="w-full max-w-7xl mx-auto">
|
||||
<CarouselContent>
|
||||
{newContent?.map((text: any) => (
|
||||
<CarouselItem key={text?.id} className="md:basis-1/2 lg:basis-1/3">
|
||||
<div className="md:basis-1/2 lg:basis-1/3">
|
||||
<Link href={`/document/detail/${text?.slug}`} 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">
|
||||
<div className="flex items-center justify-center rounded-lg w-16 h-2 lg: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 w-full pr-10 flex-col flex-1">
|
||||
<div className="text-gray-500 dark:text-gray-400 flex flex-row items-center text-xs gap-0 lg:gap-1 mt-1 lg:text-sm">
|
||||
{formatDateToIndonesian(new Date(text?.createdAt))}
|
||||
{text?.timezone ? text?.timezone : "WIB"}|
|
||||
<Icon icon="formkit:eye" width="15" height="15" />
|
||||
{text?.clickCount}
|
||||
</div>
|
||||
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible ">{text?.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 {t("document")}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
) : (
|
||||
<p className="flex items-center justify-center">
|
||||
<img src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center flex-row justify-center">
|
||||
<Link href={`/${selectedTab}/filter?sortBy=${props.type}`} className="border text-[#bb3523] rounded-lg text-sm lg:text-md px-4 py-1 border-[#bb3523]">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
"use client";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { getHeroData, listCarousel } from "@/service/landing/landing";
|
||||
import { formatDateToIndonesian, textEllipsis } from "@/utils/globals";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Icon } from "../ui/icon";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export default function NewsTicker() {
|
||||
const [article, setArticle] = useState<any>([]);
|
||||
const [currentNewsIndex, setCurrentNewsIndex] = useState(0);
|
||||
const [animate, setAnimate] = useState(false);
|
||||
const t = useTranslations("LandingPage");
|
||||
|
||||
useEffect(() => {
|
||||
async function getArticle() {
|
||||
const response = await getHeroData();
|
||||
setArticle(response?.data?.data?.content);
|
||||
}
|
||||
getArticle();
|
||||
}, []);
|
||||
|
||||
const triggerAnimation = (newIndex: number) => {
|
||||
setAnimate(true);
|
||||
setTimeout(() => {
|
||||
setCurrentNewsIndex(newIndex);
|
||||
setAnimate(false);
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const handlePrev = () => {
|
||||
const newIndex = (currentNewsIndex - 1 + article.length) % article.length;
|
||||
triggerAnimation(newIndex);
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
const newIndex = (currentNewsIndex + 1) % article.length;
|
||||
triggerAnimation(newIndex);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
triggerAnimation((currentNewsIndex + 1) % article.length);
|
||||
}, 7000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [article.length]);
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-0 z-50 flex flex-row h-[60px] gap-3 w-full justify-between dark:bg-stone-800 bg-gray-50">
|
||||
<div className="relative px-4 py-2 font-semibold text-xs lg:text-sm flex items-center bg-[#bb3523] text-white w-[30%] lg:w-[10%]">
|
||||
<span className="mr-2"></span> {t("breakingNews")}
|
||||
<div className="absolute right-0 top-0 h-full w-4 bg-[#bb3523] transform translate-x-full clip-path-triangle"></div>
|
||||
</div>
|
||||
<div className={`w-full px-5 py-1 flex flex-col gap-1 transition-transform duration-300 ${animate ? "opacity-0 translate-y-5" : "opacity-100 translate-y-0"}`}>
|
||||
<Link href={`news/detail/${article[currentNewsIndex]?.id}`} className="hidden lg:block">
|
||||
<p className="text-sm lg:text-base">{article[currentNewsIndex]?.title}</p>
|
||||
</Link>
|
||||
<Link href={`news/detail/${article[currentNewsIndex]?.id}`} className="lg:hidden">
|
||||
<p className="text-sm lg:text-base">{textEllipsis(article[currentNewsIndex]?.title, 28)}</p>
|
||||
</Link>
|
||||
<p className="text-xs">{formatDateToIndonesian(article[currentNewsIndex]?.createdAt)}</p>
|
||||
</div>
|
||||
<div className="flex flex-row text-white h-full gap-[1px]">
|
||||
<a className="bg-[#bb3523] h-full flex items-center" onClick={() => handlePrev()}>
|
||||
<Icon icon="ic:twotone-arrow-left" fontSize={30} />
|
||||
</a>
|
||||
<a className="bg-[#bb3523] h-full flex items-center" onClick={() => handleNext()}>
|
||||
<Icon icon="ic:twotone-arrow-right" fontSize={30} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -65,7 +65,7 @@ const SidebarManagement = () => {
|
|||
// Render
|
||||
if (!hasMounted) return null;
|
||||
return (
|
||||
<div className="p-12 w-1/3 ">
|
||||
<div className="p-4 lg:p-12 w-full lg:w-1/3 ">
|
||||
<div className="border rounded-2xl border-black m-4">
|
||||
<h1 className="text-xl p-5">Tentang Saya</h1>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -9,30 +9,63 @@ type Inputs = {
|
|||
exampleRequired: string;
|
||||
};
|
||||
import { useForm, SubmitHandler } from "react-hook-form";
|
||||
import { error, loading } from "@/config/swal";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import Swal from "sweetalert2";
|
||||
import { useRouter } from "@/i18n/routing";
|
||||
import { forgotPassword } from "@/service/landing/landing";
|
||||
|
||||
const ForgotPass = () => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
watch,
|
||||
formState: { errors },
|
||||
} = useForm<Inputs>();
|
||||
const onSubmit: SubmitHandler<Inputs> = (data) => console.log(data);
|
||||
console.log(watch("example"));
|
||||
const [username, setUsername] = useState<any>();
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
watch,
|
||||
formState: { errors },
|
||||
} = useForm<Inputs>();
|
||||
const onSubmit: SubmitHandler<Inputs> = (data) => console.log(data);
|
||||
|
||||
async function handleCheckUsername() {
|
||||
loading();
|
||||
const response = await forgotPassword(username);
|
||||
|
||||
if (response.error) {
|
||||
error(response.message);
|
||||
return false;
|
||||
}
|
||||
|
||||
successSubmit();
|
||||
return false;
|
||||
}
|
||||
|
||||
function successSubmit() {
|
||||
MySwal.fire({
|
||||
title: "Email berhasil dikirim. Silahkan cek email Anda.",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result: any) => {
|
||||
if (result.isConfirmed) {
|
||||
router.push("/admin");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4 ">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
defaultValue="dashcode@gmail.com"
|
||||
{...register("example")}
|
||||
className="h-[48px] text-sm text-default-900 "
|
||||
/>
|
||||
<Label htmlFor="user-name">Username</Label>
|
||||
<Input id="user-name" defaultValue="akun1234" className="h-[48px] text-sm text-default-900 " onChange={(e) => setUsername(e.target.value)} />
|
||||
</div>
|
||||
|
||||
<Button type="submit" fullWidth>
|
||||
Send recovery email
|
||||
<Button type="submit" fullWidth onClick={handleCheckUsername}>
|
||||
Check Username
|
||||
</Button>
|
||||
<Button type="submit" fullWidth onClick={handleCheckUsername}>
|
||||
Kirim Ulang?{" "}
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue