This commit is contained in:
Sabda Yagra 2025-06-24 11:26:58 +07:00
commit 6490679901
38 changed files with 2774 additions and 1787 deletions

View File

@ -26,12 +26,13 @@ import {
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { import {
AdministrationLevelList, AdministrationLevelList,
checkRolePlacementsAvailability,
getListCompetencies, getListCompetencies,
getListExperiences, getListExperiences,
saveUserInternal, saveUserInternal,
saveUserRolePlacements, saveUserRolePlacements,
} from "@/service/management-user/management-user"; } from "@/service/management-user/management-user";
import { loading } from "@/config/swal"; import { error, loading } from "@/config/swal";
import { Eye, EyeOff } from "lucide-react"; import { Eye, EyeOff } from "lucide-react";
const FormSchema = z.object({ const FormSchema = z.object({
@ -134,6 +135,26 @@ export default function AddExpertForm() {
}; };
loading(); loading();
// check availability first
var placementArr: any[] = [];
placementRows.forEach((row: any) => {
placementArr.push({
roleId: Number(row.roleId),
userLevelId: Number(row.userLevelId),
});
});
const dataReqAvail = {
placements: placementArr,
};
const resAvail = await checkRolePlacementsAvailability(dataReqAvail);
if (resAvail?.error) {
close();
error(resAvail.message);
return false;
}
const res = await saveUserInternal(dataReq); const res = await saveUserInternal(dataReq);
const resData = res?.data?.data; const resData = res?.data?.data;
const userProfileId = resData?.id; const userProfileId = resData?.id;

View File

@ -49,11 +49,11 @@ const columns: ColumnDef<any>[] = [
header: "Jumlah Amplifikasi", header: "Jumlah Amplifikasi",
cell: ({ row }) => <span>{row.getValue("link")}</span>, cell: ({ row }) => <span>{row.getValue("link")}</span>,
}, },
// { {
// accessorKey: "status", accessorKey: "status",
// header: "Status", header: "Status",
// cell: ({ row }) => <span>{row.getValue("status")}</span>, cell: ({ row }) => <span>{row.getValue("status")}</span>,
// }, },
{ {
accessorKey: "date", accessorKey: "date",
header: "Tanggal Penarikan", header: "Tanggal Penarikan",
@ -77,12 +77,12 @@ const columns: ColumnDef<any>[] = [
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end"> <DropdownMenuContent className="p-0" align="end">
<Link <Link href={`/admin/media-tracking/detail/${row.original.id}`}>
href={`/admin/media-tracking/detail/${row.original.id}`}
>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none"> <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<Eye className="w-4 h-4 me-1.5" /> <Eye className="w-4 h-4 me-1.5" />
Detail View
{row.original.mediaUpload.fileType.secondaryName &&
row.original.mediaUpload.fileType.secondaryName.toLowerCase()}
</DropdownMenuItem> </DropdownMenuItem>
</Link> </Link>
</DropdownMenuContent> </DropdownMenuContent>

View File

@ -58,16 +58,6 @@ const columns: ColumnDef<any>[] = [
<span>{formatDateToIndonesian(row.getValue("createdAt"))}</span> <span>{formatDateToIndonesian(row.getValue("createdAt"))}</span>
), ),
}, },
{
accessorKey: "isStaticBanner",
header: "Static Banner",
cell: ({ row }) => (
<StaticToogle
id={row.original.id}
initChecked={row.original.isStaticBanner}
/>
),
},
{ {
accessorKey: "statusName", accessorKey: "statusName",
header: "Status Banner", header: "Status Banner",
@ -75,7 +65,6 @@ const columns: ColumnDef<any>[] = [
<StatusToogle id={row.original.id} initChecked={row.original.isBanner} /> <StatusToogle id={row.original.id} initChecked={row.original.isBanner} />
), ),
}, },
{ {
id: "actions", id: "actions",
accessorKey: "action", accessorKey: "action",

View File

@ -74,7 +74,7 @@ import CustomPagination from "@/components/table/custom-pagination";
const ContentListBanner = () => { const ContentListBanner = () => {
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const [showData, setShowData] = React.useState("10"); const [showData, setShowData] = React.useState("9");
const [categories, setCategories] = React.useState<any>(); const [categories, setCategories] = React.useState<any>();
const [data, setData] = React.useState<any[]>([]); const [data, setData] = React.useState<any[]>([]);
const [totalData, setTotalData] = React.useState<number>(1); const [totalData, setTotalData] = React.useState<number>(1);
@ -412,7 +412,7 @@ const ContentListBanner = () => {
/> />
</div> </div>
<img <img
src={item.thumbnailLink || "/placeholder.jpg"} src={item.smallThumbnailLink || "/placeholder.jpg"}
alt={item.title} alt={item.title}
className="w-full h-48 object-cover" className="w-full h-48 object-cover"
/> />

View File

@ -28,7 +28,7 @@ export default function AdminBanner() {
: "bg-white text-black " : "bg-white text-black "
}`} }`}
> >
Kontent Konten
</Button> </Button>
<Button <Button
rounded="md" rounded="md"

View File

@ -67,6 +67,16 @@ const columns: ColumnDef<any>[] = [
</span> </span>
), ),
}, },
{
id: "created",
accessorKey: "created",
header: "Dibuat Oleh",
cell: ({ row }) => (
<div className="flex flex-col items-center">
<span className="text-sm text-gray-500">{row.original.createdByName}</span><span className="text-xs font-bold">({row.original.createdByUserLevelName})</span>
</div>
),
},
{ {
id: "actions", id: "actions",
accessorKey: "action", accessorKey: "action",

View File

@ -58,24 +58,13 @@ const columns: ColumnDef<any>[] = [
<span>{formatDateToIndonesian(row.getValue("createdAt"))}</span> <span>{formatDateToIndonesian(row.getValue("createdAt"))}</span>
), ),
}, },
{
accessorKey: "isStaticBanner",
header: "Static Banner",
cell: ({ row }) => (
<StaticToogle
id={row.original.id}
initChecked={row.original.isStaticBanner}
/>
),
},
{ {
accessorKey: "statusName", accessorKey: "statusName",
header: "Status Banner", header: "Status Pop Up",
cell: ({ row }) => ( cell: ({ row }) => (
<StatusToogle id={row.original.id} initChecked={row.original.isBanner} /> <StatusToogle id={row.original.id} initChecked={row.original.isInterstitial} />
), ),
}, },
{ {
id: "actions", id: "actions",
accessorKey: "action", accessorKey: "action",

View File

@ -1,7 +1,7 @@
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { useToast } from "@/components/ui/use-toast"; import { useToast } from "@/components/ui/use-toast";
import { useRouter } from "@/i18n/routing"; import { useRouter } from "@/i18n/routing";
import { setBanner } from "@/service/settings/settings"; import { setBanner, setPopUp } from "@/service/settings/settings";
export default function StatusToogle(props: { export default function StatusToogle(props: {
id: number; id: number;
@ -12,7 +12,7 @@ export default function StatusToogle(props: {
const router = useRouter(); const router = useRouter();
const disableBanner = async () => { const disableBanner = async () => {
const response = await setBanner(id, false); const response = await setPopUp(id, false);
if (response?.error) { if (response?.error) {
toast({ toast({
@ -25,7 +25,7 @@ export default function StatusToogle(props: {
toast({ toast({
title: "Success ", title: "Success ",
}); });
router.push("/admin/settings/banner?dataChange=true"); router.push("/admin/settings/popup?dataChange=true");
}; };
return ( return (
<Switch <Switch

View File

@ -74,7 +74,7 @@ import CustomPagination from "@/components/table/custom-pagination";
const ContentListPopUp = () => { const ContentListPopUp = () => {
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const [showData, setShowData] = React.useState("10"); const [showData, setShowData] = React.useState("9");
const [categories, setCategories] = React.useState<any>(); const [categories, setCategories] = React.useState<any>();
const [data, setData] = React.useState<any[]>([]); const [data, setData] = React.useState<any[]>([]);
const [totalData, setTotalData] = React.useState<number>(1); const [totalData, setTotalData] = React.useState<number>(1);
@ -433,7 +433,7 @@ const ContentListPopUp = () => {
/> />
</div> </div>
<img <img
src={item.thumbnailLink || "/placeholder.jpg"} src={item.smallThumbnailLink || "/placeholder.jpg"}
alt={item.title} alt={item.title}
className="w-full h-48 object-cover" className="w-full h-48 object-cover"
/> />

View File

@ -28,7 +28,7 @@ export default function AdminPopup() {
: "bg-white text-black " : "bg-white text-black "
}`} }`}
> >
Kontent Konten
</Button> </Button>
<Button <Button
rounded="md" rounded="md"

View File

@ -9,12 +9,7 @@ import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Calendar } from "@/components/ui/calendar"; import { Calendar } from "@/components/ui/calendar";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { import { Book, CheckCheck, Plus, Timer } from "lucide-react";
Book,
CheckCheck,
Plus,
Timer,
} from "lucide-react";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { EventContentArg } from "@fullcalendar/core"; import { EventContentArg } from "@fullcalendar/core";
import EventModal from "./event-modal"; import EventModal from "./event-modal";
@ -171,7 +166,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
const monthData = res?.data?.data; const monthData = res?.data?.data;
if (monthData) { if (monthData) {
const allEvents: CalendarEvent[] = []; const allEvents: CalendarEvent[] = [];
// Map API data to the calendarEvents structure // Map API data to the calendarEvents structure
const events = monthData?.map((event: any) => ({ const events = monthData?.map((event: any) => ({
id: event.id.toString(), id: event.id.toString(),
title: event.title, title: event.title,
@ -237,10 +232,13 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
const allCategoryId = ["1", "2", "3", "4", "5"]; const allCategoryId = ["1", "2", "3", "4", "5"];
// Cek apakah SEMUA validTypeIds ada di typeIdsInData // Cek apakah SEMUA validTypeIds ada di typeIdsInData
const hasAllCategories = allCategoryId.every(categoryId => selectedCategory.includes(categoryId)); const hasAllCategories = allCategoryId.every((categoryId) =>
selectedCategory.includes(categoryId)
);
return eventCategories?.some((cat: string) => return eventCategories?.some(
selectedCategory.includes(cat) || (hasAllCategories && cat == "0") (cat: string) =>
selectedCategory.includes(cat) || (hasAllCategories && cat == "0")
); );
}); });
@ -266,7 +264,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
}; };
const handleCategorySelection = (category: string) => { const handleCategorySelection = (category: string) => {
setSelectedCategory(prev => { setSelectedCategory((prev) => {
if (prev.includes(category)) { if (prev.includes(category)) {
return prev.filter((c) => c !== category); return prev.filter((c) => c !== category);
} else { } else {
@ -346,15 +344,22 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
const renderEventContent = (eventInfo: any) => { const renderEventContent = (eventInfo: any) => {
const { title } = eventInfo.event; const { title } = eventInfo.event;
const { createdByName, isPublish, calendar } = eventInfo.event.extendedProps; const { createdByName, isPublish, calendar } =
eventInfo.event.extendedProps;
const bgColor = getEventColor(calendar); const bgColor = getEventColor(calendar);
const colorList = getEventColorList(calendar); const colorList = getEventColorList(calendar);
return ( return (
<div className={`w-full p-2 mb-2 rounded-md text-white text-sm flex justify-between items-stretch ${bgColor}`}> <div
className={`w-full p-2 mb-2 rounded-md text-white text-sm flex justify-between items-stretch ${bgColor}`}
>
<div className="flex-1"> <div className="flex-1">
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
{isPublish === true ? <CheckCheck size={15} /> : <Timer size={15} />} {isPublish === true ? (
<CheckCheck size={15} />
) : (
<Timer size={15} />
)}
<p className="ml-1">{title}</p> <p className="ml-1">{title}</p>
</div> </div>
<p className="ml-1 text-xs text-start mt-2"> <p className="ml-1 text-xs text-start mt-2">
@ -461,8 +466,12 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
text={event.title} text={event.title}
createdBy={event.createdByName} createdBy={event.createdByName}
isPublish={event.isPublish} isPublish={event.isPublish}
bgColor={getEventColor(event.agendaType as EventType)} bgColor={getEventColor(
colorList={getEventColorList(event.agendaType as EventType)} event.agendaType as EventType
)}
colorList={getEventColorList(
event.agendaType as EventType
)}
/> />
))} ))}
</div> </div>
@ -593,7 +602,9 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
className={category.className} className={category.className}
id={category.label} id={category.label}
checked={selectedCategory.includes(category.value)} checked={selectedCategory.includes(category.value)}
onCheckedChange={() => handleCategorySelection(category.value)} onCheckedChange={() =>
handleCategorySelection(category.value)
}
/> />
<Label htmlFor={category.label}>{category.label}</Label> <Label htmlFor={category.label}>{category.label}</Label>
</li> </li>
@ -605,7 +616,12 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
<Card className="col-span-12 lg:col-span-8 2xl:col-span-9 pt-5"> <Card className="col-span-12 lg:col-span-8 2xl:col-span-9 pt-5">
<CardContent className="dashcode-app-calendar"> <CardContent className="dashcode-app-calendar">
<FullCalendar <FullCalendar
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin, listPlugin]} plugins={[
dayGridPlugin,
timeGridPlugin,
interactionPlugin,
listPlugin,
]}
headerToolbar={{ headerToolbar={{
left: "prev,next today", left: "prev,next today",
center: "title", center: "title",
@ -636,7 +652,9 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
handleDateChange(info.view.currentStart, info.view.currentEnd); handleDateChange(info.view.currentStart, info.view.currentEnd);
handleViewChange(info.view.type); handleViewChange(info.view.type);
}} }}
viewClassNames={activeView === "listYear" ? "hide-calendar-grid" : ""} viewClassNames={
activeView === "listYear" ? "hide-calendar-grid" : ""
}
/> />
{activeView === "listYear" && ( {activeView === "listYear" && (

View File

@ -148,10 +148,12 @@ const EventModal = ({
satker: false, satker: false,
international: false, international: false,
}); });
const [agendaType, setAgendaType] = React.useState(""); // State untuk agendaType const [agendaType, setAgendaType] = React.useState(""); // State untuk agendaType
const [selectedPolda, setSelectedPolda] = React.useState([]); // Untuk data Polda const [selectedPolda, setSelectedPolda] = useState<string[]>([]);
const [selectedSatker, setSelectedSatker] = React.useState([]); const [selectedPolres, setSelectedPolres] = useState<string[]>([]);
const [selectedPolres, setSelectedPolres] = React.useState([]); const [selectedSatker, setSelectedSatker] = useState<string[]>([]);
const isDetailMode = true;
const [wavesurfer, setWavesurfer] = useState<WaveSurfer>(); const [wavesurfer, setWavesurfer] = useState<WaveSurfer>();
const [isPlaying, setIsPlaying] = useState(false); const [isPlaying, setIsPlaying] = useState(false);
const [isPublishing, setIsPublishing] = useState(false); const [isPublishing, setIsPublishing] = useState(false);
@ -174,10 +176,7 @@ const EventModal = ({
const detail = res?.data?.data; const detail = res?.data?.data;
setDetailData(detail); setDetailData(detail);
const description = res?.data?.data?.description; const description = detail?.description;
console.log("description", res?.data?.data?.description);
// Set nilai awal description ke form control
if (description) { if (description) {
setValue("description", description); setValue("description", description);
} }
@ -196,17 +195,56 @@ const EventModal = ({
attachments?.filter((file: any) => file.fileTypeId == 4) attachments?.filter((file: any) => file.fileTypeId == 4)
); );
const agendaType = detail?.agendaType; const rawAgendaTypes = detail?.agendaType?.split(",") || []; // ["0","1","2","3","4","5"]
setWilayahPublish({ const assignedToLevel = detail?.assignedToLevel?.split(",") || [];
semua: agendaType === "all",
nasional: agendaType === "mabes",
polda: agendaType === "polda",
polres: agendaType === "polres",
satker: agendaType === "satker",
international: agendaType === "international",
});
}
const wilayahState = {
semua: false,
nasional: false,
polda: false,
polres: false,
satker: false,
international: false,
};
rawAgendaTypes.forEach((type: any) => {
switch (type) {
case "0":
wilayahState.semua = true;
break;
case "1":
wilayahState.nasional = true;
break;
case "2":
wilayahState.polda = true;
break;
case "3":
wilayahState.polres = true;
break;
case "4":
wilayahState.satker = true;
break;
case "5":
wilayahState.international = true;
break;
default:
break;
}
});
setWilayahPublish(wilayahState);
// Atur unit berdasarkan agendaType
if (rawAgendaTypes.includes("2")) {
setSelectedPolda(assignedToLevel);
}
if (rawAgendaTypes.includes("3")) {
setSelectedPolres(assignedToLevel);
}
if (rawAgendaTypes.includes("4")) {
setSelectedSatker(assignedToLevel);
}
}
fetchDetailData(); fetchDetailData();
}, [event, setValue]); }, [event, setValue]);
@ -233,33 +271,39 @@ const EventModal = ({
const toggleWilayah = (key: string) => { const toggleWilayah = (key: string) => {
setWilayahPublish((prev: any) => { setWilayahPublish((prev: any) => {
const newState = { ...prev, [key]: !prev[key] }; let newState = { ...prev };
// Handle "semua" logic to check all options // Jika key === semua dan sebelumnya belum aktif, aktifkan semua
if (key === "semua" && newState.semua) { if (key === "semua") {
setAgendaType("all"); const newChecked = !prev.semua;
return { newState = {
semua: true, semua: newChecked,
nasional: true, nasional: newChecked,
polda: true, polda: newChecked,
polres: true, polres: newChecked,
satker: true, satker: newChecked,
international: true, international: newChecked,
}; };
if (newChecked) {
setAgendaType("0,1,2,3,4,5");
} else {
setAgendaType("");
}
return newState;
} }
// Uncheck "semua" if any other option is selected // Jika key bukan "semua"
if (key !== "semua") { newState[key] = !prev[key];
newState.semua = false; newState.semua = false; // Uncheck "semua" jika yang dipilih adalah individu
}
// Set agendaType based on the selected checkbox // Hitung ulang agendaType berdasarkan pilihan
if (newState.nasional) setAgendaType("mabes"); const selectedKeys = Object.entries(newState)
else if (newState.polda) setAgendaType("polda"); .filter(([k, v]) => v && k !== "semua")
else if (newState.polres) setAgendaType("polres"); .map(([k]) => wilayahValueMap[k]);
else if (newState.satker) setAgendaType("satker");
else if (newState.international) setAgendaType("international"); setAgendaType(selectedKeys.join(","));
else setAgendaType(""); // Reset if no checkbox is selected
return newState; return newState;
}); });
@ -269,12 +313,25 @@ const EventModal = ({
const agendaTypeList: string[] = []; const agendaTypeList: string[] = [];
const assignedToLevelList: string[] = []; const assignedToLevelList: string[] = [];
// Mapping dari checkbox wilayah ke agendaType // // Mapping dari checkbox wilayah ke agendaType
Object.keys(wilayahPublish).forEach((key) => { // Object.keys(wilayahPublish).forEach((key) => {
if (wilayahPublish[key as keyof typeof wilayahPublish]) { // if (wilayahPublish[key as keyof typeof wilayahPublish]) {
agendaTypeList.push(wilayahValueMap[key]); // agendaTypeList.push(wilayahValueMap[key]);
} // }
}); // });
if (wilayahPublish.semua) {
agendaTypeList.push("0", "1", "2", "3", "4", "5");
} else {
Object.keys(wilayahPublish).forEach((key) => {
if (
wilayahPublish[key as keyof typeof wilayahPublish] &&
key !== "semua"
) {
agendaTypeList.push(wilayahValueMap[key]);
}
});
}
// Unit-unit berdasarkan wilayah yang aktif // Unit-unit berdasarkan wilayah yang aktif
if (wilayahPublish.polda && selectedPolda.length > 0) { if (wilayahPublish.polda && selectedPolda.length > 0) {
@ -558,6 +615,7 @@ const EventModal = ({
confirmButtonText: "OK", confirmButtonText: "OK",
}).then(() => { }).then(() => {
router.push(redirect); router.push(redirect);
window.location.reload();
}); });
}; };
@ -784,7 +842,8 @@ const EventModal = ({
{wilayahPublish.polda && ( {wilayahPublish.polda && (
<UnitMapping <UnitMapping
unit="Polda" unit="Polda"
isDetail={false} isDetail={isDetailMode} // jika Anda punya kondisi detail
initData={selectedPolda}
sendDataToParent={(data: any) => sendDataToParent={(data: any) =>
setSelectedPolda(data) setSelectedPolda(data)
} }
@ -802,8 +861,9 @@ const EventModal = ({
</label> </label>
{wilayahPublish.polres && ( {wilayahPublish.polres && (
<UnitMapping <UnitMapping
isDetail={false}
unit="Polres" unit="Polres"
isDetail={isDetailMode}
initData={selectedPolres}
sendDataToParent={(data: any) => sendDataToParent={(data: any) =>
setSelectedPolres(data) setSelectedPolres(data)
} }
@ -821,8 +881,9 @@ const EventModal = ({
</label> </label>
{wilayahPublish.satker && ( {wilayahPublish.satker && (
<UnitMapping <UnitMapping
isDetail={false}
unit="Satker" unit="Satker"
isDetail={isDetailMode}
initData={selectedSatker}
sendDataToParent={(data: any) => sendDataToParent={(data: any) =>
setSelectedSatker(data) setSelectedSatker(data)
} }

View File

@ -97,14 +97,23 @@ const useTableColumns = () => {
cell: ({ row }) => { cell: ({ row }) => {
const isPublish = row.original.isPublish; const isPublish = row.original.isPublish;
const isPublishOnPolda = row.original.isPublishOnPolda; const isPublishOnPolda = row.original.isPublishOnPolda;
const creatorGroupParentLevelId = row.original.creatorGroupParentLevelId;
let displayText = "-"; let displayText = "-";
if (isPublish && !isPublishOnPolda) { if (isPublish && !isPublishOnPolda) {
displayText = "Mabes"; displayText = "Mabes";
} else if (isPublish && isPublishOnPolda) { } else if (isPublish && isPublishOnPolda) {
displayText = "Mabes & Polda"; if (Number(creatorGroupParentLevelId) == 761) {
displayText = "Mabes & Satker";
} else {
displayText = "Mabes & Polda";
}
} else if (!isPublish && isPublishOnPolda) { } else if (!isPublish && isPublishOnPolda) {
displayText = "Polda"; if (Number(creatorGroupParentLevelId) == 761) {
displayText = "Satker";
} else {
displayText = "Polda";
}
} }
return ( return (

View File

@ -100,14 +100,23 @@ const useTableColumns = () => {
cell: ({ row }) => { cell: ({ row }) => {
const isPublish = row.original.isPublish; const isPublish = row.original.isPublish;
const isPublishOnPolda = row.original.isPublishOnPolda; const isPublishOnPolda = row.original.isPublishOnPolda;
const creatorGroupParentLevelId = row.original.creatorGroupParentLevelId;
let displayText = "-"; let displayText = "-";
if (isPublish && !isPublishOnPolda) { if (isPublish && !isPublishOnPolda) {
displayText = "Mabes"; displayText = "Mabes";
} else if (isPublish && isPublishOnPolda) { } else if (isPublish && isPublishOnPolda) {
displayText = "Mabes & Polda"; if (Number(creatorGroupParentLevelId) == 761) {
displayText = "Mabes & Satker";
} else {
displayText = "Mabes & Polda";
}
} else if (!isPublish && isPublishOnPolda) { } else if (!isPublish && isPublishOnPolda) {
displayText = "Polda"; if (Number(creatorGroupParentLevelId) == 761) {
displayText = "Satker";
} else {
displayText = "Polda";
}
} }
return ( return (

View File

@ -97,14 +97,23 @@ const useTableColumns = () => {
cell: ({ row }) => { cell: ({ row }) => {
const isPublish = row.original.isPublish; const isPublish = row.original.isPublish;
const isPublishOnPolda = row.original.isPublishOnPolda; const isPublishOnPolda = row.original.isPublishOnPolda;
const creatorGroupParentLevelId = row.original.creatorGroupParentLevelId;
let displayText = "-"; let displayText = "-";
if (isPublish && !isPublishOnPolda) { if (isPublish && !isPublishOnPolda) {
displayText = "Mabes"; displayText = "Mabes";
} else if (isPublish && isPublishOnPolda) { } else if (isPublish && isPublishOnPolda) {
displayText = "Mabes & Polda"; if (Number(creatorGroupParentLevelId) == 761) {
displayText = "Mabes & Satker";
} else {
displayText = "Mabes & Polda";
}
} else if (!isPublish && isPublishOnPolda) { } else if (!isPublish && isPublishOnPolda) {
displayText = "Polda"; if (Number(creatorGroupParentLevelId) == 761) {
displayText = "Satker";
} else {
displayText = "Polda";
}
} }
return ( return (

View File

@ -97,14 +97,23 @@ const useTableColumns = () => {
cell: ({ row }) => { cell: ({ row }) => {
const isPublish = row.original.isPublish; const isPublish = row.original.isPublish;
const isPublishOnPolda = row.original.isPublishOnPolda; const isPublishOnPolda = row.original.isPublishOnPolda;
const creatorGroupParentLevelId = row.original.creatorGroupParentLevelId;
let displayText = "-"; let displayText = "-";
if (isPublish && !isPublishOnPolda) { if (isPublish && !isPublishOnPolda) {
displayText = "Mabes"; displayText = "Mabes";
} else if (isPublish && isPublishOnPolda) { } else if (isPublish && isPublishOnPolda) {
displayText = "Mabes & Polda"; if (Number(creatorGroupParentLevelId) == 761) {
displayText = "Mabes & Satker";
} else {
displayText = "Mabes & Polda";
}
} else if (!isPublish && isPublishOnPolda) { } else if (!isPublish && isPublishOnPolda) {
displayText = "Polda"; if (Number(creatorGroupParentLevelId) == 761) {
displayText = "Satker";
} else {
displayText = "Polda";
}
} }
return ( return (

View File

@ -23,7 +23,7 @@ import { Link, useRouter } from "@/components/navigation";
import Swal from "sweetalert2"; import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content"; import withReactContent from "sweetalert2-react-content";
import { deleteBlog } from "@/service/blog/blog"; import { deleteBlog } from "@/service/blog/blog";
import { error, loading } from "@/lib/swal"; import { error, loading, close } from "@/lib/swal";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import axios from "axios"; import axios from "axios";
@ -126,6 +126,7 @@ const useTableColumns = ({
const handleDownload = async (id: string) => { const handleDownload = async (id: string) => {
try { try {
loading();
const response = await axios.get( const response = await axios.get(
`https://netidhub.com/api/media/report/download?id=${id}`, `https://netidhub.com/api/media/report/download?id=${id}`,
{ {
@ -140,6 +141,7 @@ const useTableColumns = ({
document.body.appendChild(link); document.body.appendChild(link);
link.click(); link.click();
link.remove(); link.remove();
close();
} catch (error) { } catch (error) {
console.error("Download failed", error); console.error("Download failed", error);
MySwal.fire({ MySwal.fire({

View File

@ -37,6 +37,7 @@ export default function ExecutiveDataDashboard() {
const state = Cookies.get("state"); const state = Cookies.get("state");
const provState = Cookies.get("state-prov"); const provState = Cookies.get("state-prov");
const t = useTranslations("AnalyticsDashboard"); const t = useTranslations("AnalyticsDashboard");
const [refreshTicket, setRefreshTicket] = useState(true);
const [ticket1, setTicket1] = useState(""); const [ticket1, setTicket1] = useState("");
const [ticket2, setTicket2] = useState(""); const [ticket2, setTicket2] = useState("");
@ -44,6 +45,7 @@ export default function ExecutiveDataDashboard() {
const [ticket4, setTicket4] = useState(""); const [ticket4, setTicket4] = useState("");
const [ticket5, setTicket5] = useState(""); const [ticket5, setTicket5] = useState("");
const [ticket6, setTicket6] = useState(""); const [ticket6, setTicket6] = useState("");
const [ticket7, setTicket7] = useState("");
const [isInternational, setIsInternational] = useState([false, false, false]); const [isInternational, setIsInternational] = useState([false, false, false]);
const baseUrl = "https://analytic.sitani.info/"; const baseUrl = "https://analytic.sitani.info/";
@ -54,13 +56,40 @@ export default function ExecutiveDataDashboard() {
const view1 = const view1 =
levelName == "MABES POLRI" levelName == "MABES POLRI"
? isInternational[0] ? isInternational[0]
? "views/2023_08_MediaHUB-KtnMgt_Rev100/db-emg-issue-executive?" ? "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-per-polda-new?polda-selected=ALL"
: "views/2023_08_MediaHUB-KtnMgt_Rev100/db-emg-issue-executive?" : "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-per-polda-new?polda-selected=ALL"
: safeLevelName.includes("POLDA") : safeLevelName.includes("POLDA")
? `views/2023_08_MediaHUB-KtnMgt_Rev100/db-emg-issue?provinsi-polda=${state}&` ? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-per-polda-new?polda-selected=${state}&`
: `views/2023_08_MediaHUB-KtnMgt_Rev100/db-emg-issue?provinsi-polda=${state}&`; : `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-per-polda-new?polda-selected=${state}&`;
const view2 = const view2 =
levelName == "MABES POLRI"
? isInternational[1]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-content-interaction-per-satker?polda-selected=ALL"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-per-satker?polda-selected=ALL"
: safeLevelName.includes("POLDA")
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-per-satker?polda-selected=SATKER POLRI&`
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-per-satker?polda-selected=SATKER POLRI&`;
const view3 =
levelName == "MABES POLRI"
? isInternational[2]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-content-category-per-polda-new?polda-selected=ALL"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-category-per-polda-new?polda-selected=ALL"
: safeLevelName.includes("POLDA")
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-category-per-polda-new?polda-selected=${state}&`
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-category-per-polda-new?polda-selected=${state}&`;
const view4 =
levelName == "MABES POLRI"
? isInternational[1]
? "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-category-per-satker?polda-selected=ALL"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-category-per-satker?polda-selected=ALL"
: safeLevelName.includes("POLDA")
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-category-per-satker?polda-selected=SATKER POLRI&`
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-category-per-satker?polda-selected=SATKER POLRI&`;
const view5 =
levelName == "MABES POLRI" levelName == "MABES POLRI"
? isInternational[1] ? isInternational[1]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-published-produksi?" ? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-published-produksi?"
@ -69,7 +98,7 @@ export default function ExecutiveDataDashboard() {
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-published-produksi-polda-executive?polda-selected=${state}&` ? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-published-produksi-polda-executive?polda-selected=${state}&`
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-published-produksi-polda-executive?polda-selected=${state}&`; : `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-published-produksi-polda-executive?polda-selected=${state}&`;
const view3 = const view6 =
levelName == "MABES POLRI" levelName == "MABES POLRI"
? isInternational[2] ? isInternational[2]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-waktu-akses-pengguna?" ? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-waktu-akses-pengguna?"
@ -78,59 +107,14 @@ export default function ExecutiveDataDashboard() {
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-waktu-akses-pengguna-polda-executive?polda-selected=${state}&` ? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-waktu-akses-pengguna-polda-executive?polda-selected=${state}&`
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-waktu-akses-pengguna-polda-executive?polda-selected=${state}&`; : `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-waktu-akses-pengguna-polda-executive?polda-selected=${state}&`;
const view4 =
levelName == "MABES POLRI"
? isInternational[1]
? "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polda?"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polda?"
: safeLevelName.includes("POLDA")
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-per-polda-new?polda-selected=${state}&`
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-per-polda-new?polda-selected=${state}&`;
const view5 =
levelName == "MABES POLRI"
? isInternational[1]
? "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polres?"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polres?"
: safeLevelName.includes("POLDA")
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polres?provinsi-polda=${state}&`
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polres?provinsi-polda=${state}&`;
const view6 =
levelName == "MABES POLRI"
? isInternational[1]
? "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-satker?"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-satker?"
: safeLevelName.includes("POLDA")
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-satker?satker-selected=${state}&`
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-satker?satker-selected=${state}&`;
const view7 = const view7 =
levelName == "MABES POLRI" levelName == "MABES POLRI"
? isInternational[2] ? isInternational[2]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-penugasan?" ? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-penugasan-vertical-bar?"
: "views/2023_09_db-penugasan_rev100/db-penugasan?" : "views/2023_09_db-penugasan_rev100/db-penugasan-vertical-bar?"
: safeLevelName.includes("POLDA") : safeLevelName.includes("POLDA")
? `views/2023_09_db-penugasan_rev100/db-penugasan?polda-selected=${state}&` ? `views/2023_09_db-penugasan_rev100/db-penugasan-vertical-bar?polda-selected=${state}&`
: `views/2023_09_db-penugasan_rev100/db-penugasan?polda-selected=${state}&`; : `views/2023_09_db-penugasan_rev100/db-penugasan-vertical-bar?polda-selected=${state}&`;
const view8 =
levelName == "MABES POLRI"
? isInternational[2]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-konten-kategori-top10?"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-konten-kategori-top10?"
: safeLevelName.includes("POLDA")
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polda-new?polda-selected=${state}&`
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polda-new?polda-selected=${state}&`;
const view9 =
levelName == "MABES POLRI"
? isInternational[3]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-konten-kategori?"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-konten-kategori?"
: safeLevelName.includes("POLDA")
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-konten-kategori-polda?polda-selected=${state}&`
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-konten-kategori-polda?polda-selected=${state}&`;
const param = ":embed=yes&:toolbar=no&:iframeSizedToWindow=true"; const param = ":embed=yes&:toolbar=no&:iframeSizedToWindow=true";
@ -153,10 +137,13 @@ export default function ExecutiveDataDashboard() {
const response6 = await generateTicket(); const response6 = await generateTicket();
setTicket6(response6?.data?.data); setTicket6(response6?.data?.data);
const response7 = await generateTicket();
setTicket7(response7?.data?.data);
} }
initState(); initState();
}, [isInternational]); }, [isInternational, refreshTicket]);
// Hooks // Hooks
useEffect(() => { useEffect(() => {
@ -178,7 +165,7 @@ export default function ExecutiveDataDashboard() {
<SiteBreadcrumb /> <SiteBreadcrumb />
<div> <div>
<div className="my-3"> <div className="my-3">
<Tabs defaultValue="content-publish" className="w-full"> <Tabs defaultValue="content-publish" className="w-full" onValueChange={() => setRefreshTicket(!refreshTicket)}>
<TabsList className="flex-wrap bg-black"> <TabsList className="flex-wrap bg-black">
<TabsTrigger <TabsTrigger
value="content-publish" value="content-publish"
@ -217,19 +204,17 @@ export default function ExecutiveDataDashboard() {
{/* Polda */} {/* Polda */}
{(levelNumber === "1" || levelNumber === "2") && ( {(levelNumber === "1" || levelNumber === "2") && (
<Card <Card
className={`rounded-sm p-3 ${ className={`rounded-sm p-3 w-full`}
levelNumber === "2" ? "w-full" : "w-full"
}`}
> >
<div className="flex flex-row justify-between"> {/* <div className="flex flex-row justify-between">
<p className="text-base font-semibold"> <p className="text-base font-semibold">
Upload konten hari ini Polda Upload konten hari ini Polda
</p> </p>
</div> </div> */}
<div className="my-5"> <div className="my-5">
{ticket1 == "w-full" ? ( {ticket1 == "w-full" ? (
<iframe <iframe
src={`${baseUrl + view4 + param}`} src={`${baseUrl + view1 + param}`}
width="100%" width="100%"
height="750" height="750"
className="w-full" className="w-full"
@ -237,7 +222,7 @@ export default function ExecutiveDataDashboard() {
/> />
) : ( ) : (
<iframe <iframe
src={`${url + ticket1}/${view4}${param}`} src={`${url + ticket1}/${view1}${param}`}
width="100%" width="100%"
height="750" height="750"
frameBorder="0" frameBorder="0"
@ -248,60 +233,26 @@ export default function ExecutiveDataDashboard() {
)} )}
{/* Satker */} {/* Satker */}
{(levelNumber === "1" || levelNumber === "3") && ( {(levelNumber === "1") && (
<Card <Card
className={`rounded-sm p-3 ${ className={`rounded-sm p-3 w-full`}
levelNumber === "3" ? "w-full" : "w-full"
}`}
> >
<div className="flex flex-row justify-between"> {/* <div className="flex flex-row justify-between">
<p className="text-base font-semibold"> <p className="text-base font-semibold">
Upload konten hari ini Satker Upload konten hari ini Satker
</p> </p>
</div> </div> */}
<div className="my-5"> <div className="my-5">
{ticket2 == "" ? ( {ticket2 == "" ? (
<iframe <iframe
src={`${baseUrl + view6 + param}`} src={`${baseUrl + view2 + param}`}
width="100%" width="100%"
height="750" height="750"
frameBorder="0" frameBorder="0"
/> />
) : ( ) : (
<iframe <iframe
src={`${url + ticket2}/${view6}${param}`} src={`${url + ticket2}/${view2}${param}`}
width="100%"
height="750"
frameBorder="0"
/>
)}
</div>
</Card>
)}
{/* Polres */}
{(levelNumber === "1" || levelNumber === "2") && (
<Card
className={`rounded-sm p-3 ${
levelNumber === "2" ? "w-full" : "w-full"
}`}
>
<div className="flex flex-row justify-between">
<p className="text-base font-semibold">
Upload konten hari ini Polres
</p>
</div>
<div className="my-5">
{ticket3 == "" ? (
<iframe
src={`${baseUrl + view5 + param}`}
width="100%"
height="750"
frameBorder="0"
/>
) : (
<iframe
src={`${url + ticket3}/${view5}${param}`}
width="100%" width="100%"
height="750" height="750"
frameBorder="0" frameBorder="0"
@ -313,82 +264,63 @@ export default function ExecutiveDataDashboard() {
</div> </div>
</TabsContent> </TabsContent>
<TabsContent value="content-category"> <TabsContent value="content-category">
<Card className="px-3 py-3"> {(levelNumber === "1" || levelNumber === "2") && (
<p className="text-lg"> <Card className="px-3 py-3">
<b> <div className="my-5">
{isInternational[2] {ticket3 == "" ? (
? "INTERACTION OF THE MOST POPULAR CATEGORIES AND TITLES" <iframe
: "INTERAKSI KATEGORI DAN JUDUL TERPOPULER"} src={`${baseUrl + view3 + param}`}
</b> width="100%"
</p> height="750"
{levelName === "MABES POLRI" ? ( frameBorder="0"
<div className="flex flex-col gap-1"> />
<p>{t("choose_category")}</p> ) : (
<div className="flex flex-row gap-1 border-2 w-fit"> <iframe
<Button src={`${`${url + ticket3}/${view3}${param}`}`}
onClick={() => handleInternational(2, false)} width="100%"
className={` hover:text-white rounded-none height="750"
${ frameBorder="0"
isInternational[2] />
? "bg-white text-black " )}
: "bg-black text-white "
}`}
>
Indonesia
</Button>
<Button
onClick={() => handleInternational(2, true)}
className={`hover:text-white rounded-none ${
isInternational[1]
? "bg-black text-white "
: "bg-white text-black "
}
`}
>
{t("international")}
</Button>
</div> </div>
</div> </Card>
) : ( )}
""
)} {levelNumber === "1" && (
<div className="my-5"> <Card className="px-3 py-3">
{ticket3 == "" ? ( <div className="my-5">
<iframe {ticket4 == "" ? (
src={`${baseUrl + view8 + param}`} <iframe
width="100%" src={`${baseUrl + view4 + param}`}
height="750" width="100%"
frameBorder="0" height="750"
/> frameBorder="0"
) : ( />
<iframe ) : (
src={`${`${url + ticket3}/${view8}${param}`}`} <iframe
width="100%" src={`${`${url + ticket4}/${view4}${param}`}`}
height="750" width="100%"
frameBorder="0" height="750"
/> frameBorder="0"
)} />
</div> )}
</Card> </div>
</Card>
)}
</TabsContent> </TabsContent>
<TabsContent value="popular-content"> <TabsContent value="popular-content">
<Card className="rounded-sm p-3 h-[750px]"> <Card className="rounded-sm p-3 h-[750px]">
<div className="flex flex-row justify-between">
<p className="text-base font-semibold">
Konten Paling Populer
</p>
</div>
<div className="my-5"> <div className="my-5">
{ticket4 == "" ? ( {ticket5 == "" ? (
<iframe <iframe
src={`${baseUrl + view2 + param}`} src={`${baseUrl + view5 + param}`}
width="100%" width="100%"
height="650" height="650"
frameBorder="0" frameBorder="0"
/> />
) : ( ) : (
<iframe <iframe
src={`${`${url + ticket4}/${view2}${param}`}`} src={`${`${url + ticket5}/${view5}${param}`}`}
width="100%" width="100%"
height="650" height="650"
frameBorder="0" frameBorder="0"
@ -400,22 +332,17 @@ export default function ExecutiveDataDashboard() {
<TabsContent value="heatmap"> <TabsContent value="heatmap">
<Card className="rounded-sm p-3 h-[750px]"> <Card className="rounded-sm p-3 h-[750px]">
<div className="flex flex-row justify-between mx-3">
<p className="text-base font-semibold">
Heatmap Konten dan Kategori dengan Interaksi
</p>
</div>
<div className="my-5"> <div className="my-5">
{ticket5 == "" ? ( {ticket6 == "" ? (
<iframe <iframe
src={`${baseUrl + view3 + param}`} src={`${baseUrl + view6 + param}`}
width="100%" width="100%"
height="600" height="600"
frameBorder="0" frameBorder="0"
/> />
) : ( ) : (
<iframe <iframe
src={`${`${url + ticket5}/${view3}${param}`}`} src={`${`${url + ticket6}/${view6}${param}`}`}
width="100%" width="100%"
height="600" height="600"
frameBorder="0" frameBorder="0"
@ -425,60 +352,25 @@ export default function ExecutiveDataDashboard() {
</Card> </Card>
</TabsContent> </TabsContent>
<TabsContent value="task"> <TabsContent value="task">
<div className="grid grid-cols-12 gap-5"> <Card className="rounded-sm p-3 h-[750px]">
<div className="lg:col-span-12 col-span-12"> <div className="my-5">
<Card className="px-3 py-3"> {ticket7 == "" ? (
{levelName === "MABES POLRI" ? ( <iframe
<div className="flex flex-col gap-1"> src={`${baseUrl + view7 + param}`}
<p>{t("choose_category")}</p> width="100%"
<div className="flex flex-row gap-1 border-2 w-fit"> height="750"
<Button frameBorder="0"
onClick={() => handleInternational(2, false)} />
className={` hover:text-white rounded-none ) : (
${ <iframe
isInternational[2] src={`${`${url + ticket7}/${view7}${param}`}`}
? "bg-white text-black " width="100%"
: "bg-black text-white " height="750"
}`} frameBorder="0"
> />
Indonesia )}
</Button>
<Button
onClick={() => handleInternational(2, true)}
className={`hover:text-white rounded-none ${
isInternational[1]
? "bg-black text-white "
: "bg-white text-black "
}
`}
>
{t("international")}
</Button>
</div>
</div>
) : (
""
)}
<div className="my-5">
{ticket3 == "" ? (
<iframe
src={`${baseUrl + view7 + param}`}
width="100%"
height="750"
frameBorder="0"
/>
) : (
<iframe
src={`${`${url + ticket3}/${view7}${param}`}`}
width="100%"
height="750"
frameBorder="0"
/>
)}
</div>
</Card>
</div> </div>
</div> </Card>
</TabsContent> </TabsContent>
</Tabs> </Tabs>
</div> </div>

View File

@ -51,7 +51,7 @@ import { ChevronDownIcon } from "lucide-react";
import { getOperatorUser } from "@/service/management-user/management-user"; import { getOperatorUser } from "@/service/management-user/management-user";
const taskSchema = z.object({ const taskSchema = z.object({
message: z.string().optional(), title: z.string().optional(),
description: z.string().optional(), description: z.string().optional(),
}); });
@ -275,7 +275,7 @@ export default function FormQuestionsReply() {
title: data.title, title: data.title,
description: data.description, description: data.description,
priorityId: selectedPriority, priorityId: selectedPriority,
statusId: selectedStatus, statusId: 1,
typeId: detailTickets?.typeId, typeId: detailTickets?.typeId,
parentCommentId: detailTickets?.feedId, parentCommentId: detailTickets?.feedId,
operatorTeam: selectedOperator.join(","), operatorTeam: selectedOperator.join(","),
@ -507,7 +507,7 @@ export default function FormQuestionsReply() {
<Label>Judul</Label> <Label>Judul</Label>
<Controller <Controller
control={control} control={control}
name="message" name="title"
render={({ field }) => ( render={({ field }) => (
<Input <Input
size="md" size="md"

View File

@ -40,6 +40,7 @@ import { uploadThumbnailBlog } from "@/service/blog/blog";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { import {
generateDataArticle, generateDataArticle,
generateDataRewrite,
getDetailArticle, getDetailArticle,
getGenerateKeywords, getGenerateKeywords,
getGenerateTitle, getGenerateTitle,
@ -55,6 +56,7 @@ import dynamic from "next/dynamic";
import { getCsrfToken } from "@/service/auth"; import { getCsrfToken } from "@/service/auth";
import { Link } from "@/i18n/routing"; import { Link } from "@/i18n/routing";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useParams } from "next/navigation";
interface FileWithPreview extends File { interface FileWithPreview extends File {
preview: string; preview: string;
@ -82,6 +84,10 @@ export default function FormAudio() {
const router = useRouter(); const router = useRouter();
const editor = useRef(null); const editor = useRef(null);
type AudioSchema = z.infer<typeof audioSchema>; type AudioSchema = z.infer<typeof audioSchema>;
const params = useParams();
const locale = params?.locale;
const [selectedFileType, setSelectedFileType] = useState("original");
const [selectedFiles, setSelectedFiles] = useState<File[]>([]); const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId"); const taskId = Cookies.get("taskId");
@ -97,6 +103,11 @@ export default function FormAudio() {
const [preview, setPreview] = useState<string | null>(null); const [preview, setPreview] = useState<string | null>(null);
const [selectedLanguage, setSelectedLanguage] = useState(""); const [selectedLanguage, setSelectedLanguage] = useState("");
const [selectedWritingStyle, setSelectedWritingStyle] =
useState("professional");
const [editorContent, setEditorContent] = useState(""); // Untuk original editor
const [rewriteEditorContent, setRewriteEditorContent] = useState("");
const [selectedSEO, setSelectedSEO] = useState<string>(""); const [selectedSEO, setSelectedSEO] = useState<string>("");
const [title, setTitle] = useState<string>(""); const [title, setTitle] = useState<string>("");
const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>(""); const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>("");
@ -111,7 +122,6 @@ export default function FormAudio() {
null null
); );
const [selectedMainKeyword, setSelectedMainKeyword] = useState(""); const [selectedMainKeyword, setSelectedMainKeyword] = useState("");
const [selectedWritingStyle, setSelectedWritingStyle] = useState("");
const [selectedSize, setSelectedSize] = useState(""); const [selectedSize, setSelectedSize] = useState("");
const [detailData, setDetailData] = useState<any>(null); const [detailData, setDetailData] = useState<any>(null);
const [articleImages, setArticleImages] = useState<string[]>([]); const [articleImages, setArticleImages] = useState<string[]>([]);
@ -133,6 +143,8 @@ export default function FormAudio() {
const [isStartUpload, setIsStartUpload] = useState(false); const [isStartUpload, setIsStartUpload] = useState(false);
const [counterProgress, setCounterProgress] = useState(0); const [counterProgress, setCounterProgress] = useState(0);
const [isContentRewriteClicked, setIsContentRewriteClicked] = useState(false);
const [showRewriteEditor, setShowRewriteEditor] = useState(false);
const [files, setFiles] = useState<FileWithPreview[]>([]); const [files, setFiles] = useState<FileWithPreview[]>([]);
const [publishedFor, setPublishedFor] = useState<string[]>([]); const [publishedFor, setPublishedFor] = useState<string[]>([]);
@ -155,15 +167,11 @@ export default function FormAudio() {
const audioSchema = z.object({ const audioSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
description: z description: z.string().optional(),
.string() descriptionOri: z.string().optional(), // Original editor
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }) rewriteDescription: z.string().optional(), // Rewrite editor
.or(
z.literal(articleBody || "").refine((val) => val.length > 0, {
message: "Deskripsi diperlukan.",
})
),
creatorName: z.string().min(1, { message: "Creator diperlukan" }), creatorName: z.string().min(1, { message: "Creator diperlukan" }),
// tags: z.string().min(1, { message: "Judul diperlukan" }), // tags: z.string().min(1, { message: "Judul diperlukan" }),
}); });
@ -175,6 +183,11 @@ export default function FormAudio() {
formState: { errors }, formState: { errors },
} = useForm<AudioSchema>({ } = useForm<AudioSchema>({
resolver: zodResolver(audioSchema), resolver: zodResolver(audioSchema),
defaultValues: {
description: "",
descriptionOri: "",
rewriteDescription: "",
},
}); });
const doGenerateMainKeyword = async () => { const doGenerateMainKeyword = async () => {
@ -437,12 +450,24 @@ export default function FormAudio() {
} }
}; };
useEffect(() => {
if (articleBody) {
// Set ke dua field jika rewrite juga aktif
setValue("description", articleBody);
setValue("rewriteDescription", articleBody);
}
}, [articleBody, setValue]);
const save = async (data: AudioSchema) => { const save = async (data: AudioSchema) => {
loading(); loading();
const finalTags = tags.join(", "); const finalTags = tags.join(", ");
const finalTitle = isSwitchOn ? title : data.title; const finalTitle = isSwitchOn ? title : data.title;
const finalDescription = articleBody || data.description; const finalDescription = isSwitchOn
if (!finalDescription.trim()) { ? data.description
: selectedFileType === "rewrite"
? data.rewriteDescription
: data.descriptionOri;
if (!finalDescription?.trim()) {
MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error"); MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
return; return;
} }
@ -719,6 +744,45 @@ export default function FormAudio() {
} }
}, [title, getValues, setValue]); }, [title, getValues, setValue]);
const handleRewriteClick = async () => {
setIsContentRewriteClicked(true);
const request = {
style: selectedWritingStyle,
lang: "id",
contextType: "text",
urlContext: null,
context: editorContent, // Ambil isi editor original
createdBy: roleId,
sentiment: "Humorous",
clientId: "7QTW8cMojyayt6qnhqTOeJaBI70W4EaQ",
};
const res = await generateDataRewrite(request);
close();
if (res?.error) {
console.error(res.message);
return false;
}
const newArticleId = res?.data?.data?.id;
setIsGeneratedArticle(true);
setArticleIds((prevIds: string[]) => {
if (prevIds.length < 3) {
return [...prevIds, newArticleId];
} else {
const updatedIds = [...prevIds];
updatedIds[2] = newArticleId;
return updatedIds;
}
});
Cookies.set("nulisAIArticleIdTemp", JSON.stringify(articleIds));
setShowRewriteEditor(true);
};
return ( return (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-col lg:flex-row gap-10"> <div className="flex flex-col lg:flex-row gap-10">
@ -861,7 +925,7 @@ export default function FormAudio() {
placeholder="Enter Main Keyword" placeholder="Enter Main Keyword"
/> />
{/* )} {/* )}
/> */} /> */}
</div> </div>
</div> </div>
<div className="mt-5"> <div className="mt-5">
@ -911,7 +975,7 @@ export default function FormAudio() {
</div> </div>
</div> </div>
<div className="mt-5"> <div className="mt-5">
<Label>{t("special-instructions")} (Optional)</Label> <Label>{t("special-instructions")}(Optional)</Label>
<div className="mt-3"> <div className="mt-3">
<Controller <Controller
control={control} control={control}
@ -940,7 +1004,7 @@ export default function FormAudio() {
</div> </div>
{isGeneratedArticle && ( {isGeneratedArticle && (
<div className="mt-3 pb-0 flex flex-row "> <div className="mt-3 pb-0 flex flex-row">
{articleIds.map((id: string, index: number) => ( {articleIds.map((id: string, index: number) => (
<p <p
key={index} key={index}
@ -960,50 +1024,155 @@ export default function FormAudio() {
<div className="pt-3"> <div className="pt-3">
<div className="flex flex-row justify-between items-center"> <div className="flex flex-row justify-between items-center">
{selectedArticleId && ( {selectedArticleId && (
<Link <Button
href={`/contributor/content/audio/update-seo/${selectedArticleId}`} className="mb-2"
target="_blank" size="sm"
rel="noopener noreferrer" variant={"outline"}
color="primary"
onClick={() => {
const url = `/${locale}/contributor/content/image/update-seo/${selectedArticleId}`;
window.open(url, "_blank", "noopener,noreferrer");
}}
> >
<Button {t("update")}
className="mb-2" </Button>
size="sm"
variant={"outline"}
color="primary"
>
{t("update")}
</Button>
</Link>
)} )}
</div> </div>
</div> </div>
</div> </div>
<div className="py-3 space-y-2">
<Label>{t("description")}</Label>
<Controller
control={control}
name="description"
render={({ field: { onChange, value } }) =>
isLoadingData ? (
<div className="flex justify-center items-center h-40">
<p className="text-gray-500">
Loading Proses Data...
</p>
</div>
) : (
<CustomEditor
onChange={(value: any) => {
onChange(value);
setEditorContent(value);
}}
initialData={articleBody || value}
/>
)
}
/>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
</div> </div>
)} )}
<div className="space-y-2"> {!isSwitchOn && (
<Label>{t("description")}</Label> <>
<Controller <RadioGroup
control={control} onValueChange={(value) => setSelectedFileType(value)}
name="description" value={selectedFileType}
render={({ field: { onChange, value } }) => className=" grid-cols-1"
isLoadingData ? ( >
<div className="flex justify-center items-center h-40"> <div className="">
<p className="text-gray-500">Loading Proses Data...</p> <RadioGroupItem value="original" id="original-file" />
</div> <Label htmlFor="original-file">
) : ( Select Original Description
<CustomEditor </Label>
onChange={onChange} </div>
initialData={articleBody || value} <div className="py-3 space-y-2">
<Label>{t("description")}</Label>
<Controller
control={control}
name="descriptionOri"
render={({ field: { onChange, value } }) => (
<CustomEditor
onChange={(value: any) => {
onChange(value);
setEditorContent(value);
}}
initialData={value}
/>
)}
/> />
) {errors.description?.message && (
} <p className="text-red-400 text-sm">
/> {errors.description.message}
{errors.description?.message && ( </p>
<p className="text-red-400 text-sm"> )}
{errors.description.message} </div>
</p>
)} <p className="text-sm font-semibold">Content Rewrite</p>
</div> <div className="my-2">
<Button
size="sm"
type="button"
onClick={handleRewriteClick}
className="bg-blue-500 text-white py-2 px-4 rounded"
>
Content Rewrite
</Button>
</div>
{showRewriteEditor && (
<div>
{isGeneratedArticle && (
<div className="mt-3 pb-0 flex flex-row ">
{articleIds.map((id: string, index: number) => (
<Button
type="button"
key={index}
className={`mr-3 px-3 py-2 rounded-md ${
selectedArticleId === id
? "bg-green-500 text-white"
: "border-2 border-green-500 bg-white text-green-500 hover:bg-green-500 hover:text-white hover:border-green-500"
}`}
onClick={() => handleArticleIdClick(id)}
>
{"Narasi " + (index + 1)}
</Button>
))}
</div>
)}
<div className="flex items-center space-x-2 mt-3">
<RadioGroupItem value="rewrite" id="rewrite-file" />
<Label htmlFor="rewrite-file">
Select Description Rewrite
</Label>
</div>
<div className="py-3 space-y-2">
<Label>{t("file-rewrite")}</Label>
<Controller
control={control}
name="rewriteDescription"
render={({ field: { onChange, value } }) =>
isLoadingData ? (
<div className="flex justify-center items-center h-40">
<p className="text-gray-500">
Loading Proses Data...
</p>
</div>
) : (
<CustomEditor
onChange={(value: any) => {
onChange(value);
setRewriteEditorContent(value);
}}
initialData={articleBody || value}
/>
)
}
/>
</div>
</div>
)}
</RadioGroup>
</>
)}
<div className="py-3 space-y-2"> <div className="py-3 space-y-2">
<Label>{t("select-file")}</Label> <Label>{t("select-file")}</Label>
{/* <Input {/* <Input

View File

@ -40,6 +40,7 @@ import { uploadThumbnailBlog } from "@/service/blog/blog";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { import {
generateDataArticle, generateDataArticle,
generateDataRewrite,
getDetailArticle, getDetailArticle,
getGenerateKeywords, getGenerateKeywords,
getGenerateTitle, getGenerateTitle,
@ -94,6 +95,7 @@ export default function FormImage() {
const scheduleId = Cookies.get("scheduleId"); const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType"); const scheduleType = Cookies.get("scheduleType");
const roleId = getCookiesDecrypt("urie"); const roleId = getCookiesDecrypt("urie");
const [selectedFileType, setSelectedFileType] = useState("original");
const [categories, setCategories] = useState<Category[]>([]); const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategory, setSelectedCategory] = useState<any>(); const [selectedCategory, setSelectedCategory] = useState<any>();
@ -116,7 +118,10 @@ export default function FormImage() {
null null
); );
const [selectedMainKeyword, setSelectedMainKeyword] = useState(""); const [selectedMainKeyword, setSelectedMainKeyword] = useState("");
const [selectedWritingStyle, setSelectedWritingStyle] = useState(""); const [selectedWritingStyle, setSelectedWritingStyle] =
useState("professional");
const [editorContent, setEditorContent] = useState(""); // Untuk original editor
const [rewriteEditorContent, setRewriteEditorContent] = useState("");
const [selectedSize, setSelectedSize] = useState(""); const [selectedSize, setSelectedSize] = useState("");
const [detailData, setDetailData] = useState<any>(null); const [detailData, setDetailData] = useState<any>(null);
const [articleImages, setArticleImages] = useState<string[]>([]); const [articleImages, setArticleImages] = useState<string[]>([]);
@ -125,6 +130,8 @@ export default function FormImage() {
const [content, setContent] = useState(""); const [content, setContent] = useState("");
const [isContentRewriteClicked, setIsContentRewriteClicked] = useState(false);
const [selectedTarget, setSelectedTarget] = useState(""); const [selectedTarget, setSelectedTarget] = useState("");
const [unitSelection, setUnitSelection] = useState({ const [unitSelection, setUnitSelection] = useState({
allUnit: false, allUnit: false,
@ -140,6 +147,7 @@ export default function FormImage() {
let uploadPersen = 0; let uploadPersen = 0;
const [isStartUpload, setIsStartUpload] = useState(false); const [isStartUpload, setIsStartUpload] = useState(false);
const [counterProgress, setCounterProgress] = useState(0); const [counterProgress, setCounterProgress] = useState(0);
const [showRewriteEditor, setShowRewriteEditor] = useState(false);
const [files, setFiles] = useState<FileWithPreview[]>([]); const [files, setFiles] = useState<FileWithPreview[]>([]);
const [filesTemp, setFilesTemp] = useState<File[]>([]); const [filesTemp, setFilesTemp] = useState<File[]>([]);
@ -164,16 +172,10 @@ export default function FormImage() {
const imageSchema = z.object({ const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
description: z description: z.string().optional(),
.string() descriptionOri: z.string().optional(),
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }) rewriteDescription: z.string().optional(),
.or(
z.literal(articleBody || "").refine((val) => val.length > 0, {
message: "Deskripsi diperlukan.",
})
),
creatorName: z.string().min(1, { message: "Creator diperlukan" }), creatorName: z.string().min(1, { message: "Creator diperlukan" }),
// tags: z.string().min(1, { message: "Judul diperlukan" }),
}); });
const { const {
@ -184,6 +186,11 @@ export default function FormImage() {
formState: { errors }, formState: { errors },
} = useForm<ImageSchema>({ } = useForm<ImageSchema>({
resolver: zodResolver(imageSchema), resolver: zodResolver(imageSchema),
defaultValues: {
description: "",
descriptionOri: "",
rewriteDescription: "",
},
}); });
const doGenerateMainKeyword = async () => { const doGenerateMainKeyword = async () => {
@ -446,13 +453,25 @@ export default function FormImage() {
} }
}; };
useEffect(() => {
if (articleBody) {
setValue("description", articleBody);
setValue("rewriteDescription", articleBody);
}
}, [articleBody, setValue]);
const save = async (data: ImageSchema) => { const save = async (data: ImageSchema) => {
loading(); loading();
const finalTags = tags.join(", "); const finalTags = tags.join(", ");
const finalTitle = isSwitchOn ? title : data.title; const finalTitle = isSwitchOn ? title : data.title;
const finalDescription = articleBody || data.description; // const finalDescription = articleBody || data.description;
const finalDescription = isSwitchOn
? data.description
: selectedFileType === "rewrite"
? data.rewriteDescription
: data.descriptionOri;
if (!finalDescription.trim()) { if (!finalDescription?.trim()) {
MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error"); MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
return; return;
} }
@ -716,6 +735,45 @@ export default function FormImage() {
} }
}, [title, getValues, setValue]); }, [title, getValues, setValue]);
const handleRewriteClick = async () => {
setIsContentRewriteClicked(true);
const request = {
style: selectedWritingStyle,
lang: "id",
contextType: "text",
urlContext: null,
context: editorContent, // Ambil isi editor original
createdBy: roleId,
sentiment: "Humorous",
clientId: "7QTW8cMojyayt6qnhqTOeJaBI70W4EaQ",
};
const res = await generateDataRewrite(request);
close();
if (res?.error) {
console.error(res.message);
return false;
}
const newArticleId = res?.data?.data?.id;
setIsGeneratedArticle(true);
setArticleIds((prevIds: string[]) => {
if (prevIds.length < 3) {
return [...prevIds, newArticleId];
} else {
const updatedIds = [...prevIds];
updatedIds[2] = newArticleId;
return updatedIds;
}
});
Cookies.set("nulisAIArticleIdTemp", JSON.stringify(articleIds));
setShowRewriteEditor(true);
};
return ( return (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-col lg:flex-row gap-10"> <div className="flex flex-col lg:flex-row gap-10">
@ -930,6 +988,7 @@ export default function FormImage() {
color="primary" color="primary"
onClick={handleGenerateArtikel} onClick={handleGenerateArtikel}
size="sm" size="sm"
type="button"
> >
Generate Article Generate Article
</Button> </Button>
@ -972,32 +1031,139 @@ export default function FormImage() {
</div> </div>
</div> </div>
</div> </div>
<div className="py-3 space-y-2">
<Label>{t("description")}</Label>
<Controller
control={control}
name="description"
render={({ field: { onChange, value } }) =>
isLoadingData ? (
<div className="flex justify-center items-center h-40">
<p className="text-gray-500">
Loading Proses Data...
</p>
</div>
) : (
<CustomEditor
onChange={(value: any) => {
onChange(value);
setEditorContent(value);
}}
initialData={articleBody || value}
/>
)
}
/>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
</div> </div>
)} )}
<div className="py-3 space-y-2"> {!isSwitchOn && (
<Label>{t("description")}</Label> <>
<Controller <RadioGroup
control={control} onValueChange={(value) => setSelectedFileType(value)}
name="description" value={selectedFileType}
render={({ field: { onChange, value } }) => className=" grid-cols-1"
isLoadingData ? ( >
<div className="flex justify-center items-center h-40"> <div className="">
<p className="text-gray-500">Loading Proses Data...</p> <RadioGroupItem value="original" id="original-file" />
</div> <Label htmlFor="original-file">
) : ( Select Original Description
<CustomEditor </Label>
onChange={onChange} </div>
initialData={articleBody || value} <div className="py-3 space-y-2">
<Label>{t("description")}</Label>
<Controller
control={control}
name="descriptionOri"
render={({ field: { onChange, value } }) => (
<CustomEditor
onChange={(value: any) => {
onChange(value);
setEditorContent(value);
}}
initialData={value}
/>
)}
/> />
) {errors.description?.message && (
} <p className="text-red-400 text-sm">
/> {errors.description.message}
{errors.description?.message && ( </p>
<p className="text-red-400 text-sm"> )}
{errors.description.message} </div>
</p>
)} <p className="text-sm font-semibold">Content Rewrite</p>
</div> <div className="my-2">
<Button
size="sm"
type="button"
onClick={handleRewriteClick}
className="bg-blue-500 text-white py-2 px-4 rounded"
>
Content Rewrite
</Button>
</div>
{showRewriteEditor && (
<div>
{isGeneratedArticle && (
<div className="mt-3 pb-0 flex flex-row ">
{articleIds.map((id: string, index: number) => (
<Button
type="button"
key={index}
className={`mr-3 px-3 py-2 rounded-md ${
selectedArticleId === id
? "bg-green-500 text-white"
: "border-2 border-green-500 bg-white text-green-500 hover:bg-green-500 hover:text-white hover:border-green-500"
}`}
onClick={() => handleArticleIdClick(id)}
>
{"Narasi " + (index + 1)}
</Button>
))}
</div>
)}
<div className="flex items-center space-x-2 mt-3">
<RadioGroupItem value="rewrite" id="rewrite-file" />
<Label htmlFor="rewrite-file">
Select Description Rewrite
</Label>
</div>
<div className="py-3 space-y-2">
<Label>{t("file-rewrite")}</Label>
<Controller
control={control}
name="rewriteDescription"
render={({ field: { onChange, value } }) =>
isLoadingData ? (
<div className="flex justify-center items-center h-40">
<p className="text-gray-500">
Loading Proses Data...
</p>
</div>
) : (
<CustomEditor
onChange={(value: any) => {
onChange(value);
setRewriteEditorContent(value);
}}
initialData={articleBody || value}
/>
)
}
/>
</div>
</div>
)}
</RadioGroup>
</>
)}
<div className="py-3 space-y-2"> <div className="py-3 space-y-2">
<Label>{t("select-file")}</Label> <Label>{t("select-file")}</Label>
{/* <Input {/* <Input

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,7 @@ import * as z from "zod";
import { Upload } from "tus-js-client"; import { Upload } from "tus-js-client";
import Swal from "sweetalert2"; import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content"; import withReactContent from "sweetalert2-react-content";
import { redirect, useRouter } from "next/navigation"; import { redirect, useParams, useRouter } from "next/navigation";
import { import {
Select, Select,
SelectContent, SelectContent,
@ -40,6 +40,7 @@ import { uploadThumbnailBlog } from "@/service/blog/blog";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { import {
generateDataArticle, generateDataArticle,
generateDataRewrite,
getDetailArticle, getDetailArticle,
getGenerateKeywords, getGenerateKeywords,
getGenerateTitle, getGenerateTitle,
@ -83,11 +84,15 @@ export default function FormTeks() {
const editor = useRef(null); const editor = useRef(null);
type TeksSchema = z.infer<typeof teksSchema>; type TeksSchema = z.infer<typeof teksSchema>;
const params = useParams();
const locale = params?.locale;
const [selectedFiles, setSelectedFiles] = useState<File[]>([]); const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId"); const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId"); const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType"); const scheduleType = Cookies.get("scheduleType");
const roleId = getCookiesDecrypt("urie"); const roleId = getCookiesDecrypt("urie");
const [selectedFileType, setSelectedFileType] = useState("original");
const [categories, setCategories] = useState<Category[]>([]); const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategory, setSelectedCategory] = useState<any>(); const [selectedCategory, setSelectedCategory] = useState<any>();
@ -102,6 +107,10 @@ export default function FormTeks() {
const [editingArticleId, setEditingArticleId] = useState<string | null>(null); const [editingArticleId, setEditingArticleId] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const [isLoadingData, setIsLoadingData] = useState<boolean>(false); const [isLoadingData, setIsLoadingData] = useState<boolean>(false);
const [selectedWritingStyle, setSelectedWritingStyle] =
useState("professional");
const [editorContent, setEditorContent] = useState(""); // Untuk original editor
const [rewriteEditorContent, setRewriteEditorContent] = useState("");
const [articleIds, setArticleIds] = useState<string[]>([]); const [articleIds, setArticleIds] = useState<string[]>([]);
const [isGeneratedArticle, setIsGeneratedArticle] = useState(false); const [isGeneratedArticle, setIsGeneratedArticle] = useState(false);
@ -109,8 +118,9 @@ export default function FormTeks() {
const [selectedArticleId, setSelectedArticleId] = useState<string | null>( const [selectedArticleId, setSelectedArticleId] = useState<string | null>(
null null
); );
const [isContentRewriteClicked, setIsContentRewriteClicked] = useState(false);
const [showRewriteEditor, setShowRewriteEditor] = useState(false);
const [selectedMainKeyword, setSelectedMainKeyword] = useState(""); const [selectedMainKeyword, setSelectedMainKeyword] = useState("");
const [selectedWritingStyle, setSelectedWritingStyle] = useState("");
const [selectedSize, setSelectedSize] = useState(""); const [selectedSize, setSelectedSize] = useState("");
const [detailData, setDetailData] = useState<any>(null); const [detailData, setDetailData] = useState<any>(null);
const [articleImages, setArticleImages] = useState<string[]>([]); const [articleImages, setArticleImages] = useState<string[]>([]);
@ -157,14 +167,9 @@ export default function FormTeks() {
const teksSchema = z.object({ const teksSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
description: z description: z.string().optional(),
.string() descriptionOri: z.string().optional(), // Original editor
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }) rewriteDescription: z.string().optional(),
.or(
z.literal(articleBody || "").refine((val) => val.length > 0, {
message: "Deskripsi diperlukan.",
})
),
creatorName: z.string().min(1, { message: "Creator diperlukan" }), creatorName: z.string().min(1, { message: "Creator diperlukan" }),
// tags: z.string().min(1, { message: "Judul diperlukan" }), // tags: z.string().min(1, { message: "Judul diperlukan" }),
}); });
@ -177,6 +182,11 @@ export default function FormTeks() {
formState: { errors }, formState: { errors },
} = useForm<TeksSchema>({ } = useForm<TeksSchema>({
resolver: zodResolver(teksSchema), resolver: zodResolver(teksSchema),
defaultValues: {
description: "",
descriptionOri: "",
rewriteDescription: "",
},
}); });
const doGenerateMainKeyword = async () => { const doGenerateMainKeyword = async () => {
@ -439,12 +449,26 @@ export default function FormTeks() {
} }
}; };
useEffect(() => {
if (articleBody) {
// Set ke dua field jika rewrite juga aktif
setValue("description", articleBody);
setValue("rewriteDescription", articleBody);
}
}, [articleBody, setValue]);
const save = async (data: TeksSchema) => { const save = async (data: TeksSchema) => {
loading(); loading();
const finalTags = tags.join(", "); const finalTags = tags.join(", ");
const finalTitle = isSwitchOn ? title : data.title; const finalTitle = isSwitchOn ? title : data.title;
const finalDescription = articleBody || data.description;
if (!finalDescription.trim()) { const finalDescription = isSwitchOn
? data.description
: selectedFileType === "rewrite"
? data.rewriteDescription
: data.descriptionOri;
if (!finalDescription?.trim()) {
MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error"); MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
return; return;
} }
@ -709,6 +733,45 @@ export default function FormTeks() {
} }
}, [title, getValues, setValue]); }, [title, getValues, setValue]);
const handleRewriteClick = async () => {
setIsContentRewriteClicked(true);
const request = {
style: selectedWritingStyle,
lang: "id",
contextType: "text",
urlContext: null,
context: editorContent, // Ambil isi editor original
createdBy: roleId,
sentiment: "Humorous",
clientId: "7QTW8cMojyayt6qnhqTOeJaBI70W4EaQ",
};
const res = await generateDataRewrite(request);
close();
if (res?.error) {
console.error(res.message);
return false;
}
const newArticleId = res?.data?.data?.id;
setIsGeneratedArticle(true);
setArticleIds((prevIds: string[]) => {
if (prevIds.length < 3) {
return [...prevIds, newArticleId];
} else {
const updatedIds = [...prevIds];
updatedIds[2] = newArticleId;
return updatedIds;
}
});
Cookies.set("nulisAIArticleIdTemp", JSON.stringify(articleIds));
setShowRewriteEditor(true);
};
return ( return (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-col lg:flex-row gap-10"> <div className="flex flex-col lg:flex-row gap-10">
@ -851,7 +914,7 @@ export default function FormTeks() {
placeholder="Enter Main Keyword" placeholder="Enter Main Keyword"
/> />
{/* )} {/* )}
/> */} /> */}
</div> </div>
</div> </div>
<div className="mt-5"> <div className="mt-5">
@ -901,7 +964,7 @@ export default function FormTeks() {
</div> </div>
</div> </div>
<div className="mt-5"> <div className="mt-5">
<Label>{t("special-instructions")} (Optional)</Label> <Label>{t("special-instructions")}(Optional)</Label>
<div className="mt-3"> <div className="mt-3">
<Controller <Controller
control={control} control={control}
@ -923,13 +986,14 @@ export default function FormTeks() {
color="primary" color="primary"
onClick={handleGenerateArtikel} onClick={handleGenerateArtikel}
size="sm" size="sm"
type="button"
> >
Generate Article Generate Article
</Button> </Button>
</div> </div>
{isGeneratedArticle && ( {isGeneratedArticle && (
<div className="mt-3 pb-0 flex flex-row "> <div className="mt-3 pb-0 flex flex-row">
{articleIds.map((id: string, index: number) => ( {articleIds.map((id: string, index: number) => (
<p <p
key={index} key={index}
@ -949,50 +1013,155 @@ export default function FormTeks() {
<div className="pt-3"> <div className="pt-3">
<div className="flex flex-row justify-between items-center"> <div className="flex flex-row justify-between items-center">
{selectedArticleId && ( {selectedArticleId && (
<Link <Button
href={`/contributor/content/teks/update-seo/${selectedArticleId}`} className="mb-2"
target="_blank" size="sm"
rel="noopener noreferrer" variant={"outline"}
color="primary"
onClick={() => {
const url = `/${locale}/contributor/content/image/update-seo/${selectedArticleId}`;
window.open(url, "_blank", "noopener,noreferrer");
}}
> >
<Button {t("update")}
className="mb-2" </Button>
size="sm"
variant={"outline"}
color="primary"
>
{t("update")}
</Button>
</Link>
)} )}
</div> </div>
</div> </div>
</div> </div>
<div className="py-3 space-y-2">
<Label>{t("description")}</Label>
<Controller
control={control}
name="description"
render={({ field: { onChange, value } }) =>
isLoadingData ? (
<div className="flex justify-center items-center h-40">
<p className="text-gray-500">
Loading Proses Data...
</p>
</div>
) : (
<CustomEditor
onChange={(value: any) => {
onChange(value);
setEditorContent(value);
}}
initialData={articleBody || value}
/>
)
}
/>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
</div> </div>
)} )}
<div className="space-y-2"> {!isSwitchOn && (
<Label>{t("description")}</Label> <>
<Controller <RadioGroup
control={control} onValueChange={(value) => setSelectedFileType(value)}
name="description" value={selectedFileType}
render={({ field: { onChange, value } }) => className=" grid-cols-1"
isLoadingData ? ( >
<div className="flex justify-center items-center h-40"> <div className="">
<p className="text-gray-500">Loading Proses Data...</p> <RadioGroupItem value="original" id="original-file" />
</div> <Label htmlFor="original-file">
) : ( Select Original Description
<CustomEditor </Label>
onChange={onChange} </div>
initialData={articleBody || value} <div className="py-3 space-y-2">
<Label>{t("description")}</Label>
<Controller
control={control}
name="descriptionOri"
render={({ field: { onChange, value } }) => (
<CustomEditor
onChange={(value: any) => {
onChange(value);
setEditorContent(value);
}}
initialData={value}
/>
)}
/> />
) {errors.description?.message && (
} <p className="text-red-400 text-sm">
/> {errors.description.message}
{errors.description?.message && ( </p>
<p className="text-red-400 text-sm"> )}
{errors.description.message} </div>
</p>
)} <p className="text-sm font-semibold">Content Rewrite</p>
</div> <div className="my-2">
<Button
size="sm"
type="button"
onClick={handleRewriteClick}
className="bg-blue-500 text-white py-2 px-4 rounded"
>
Content Rewrite
</Button>
</div>
{showRewriteEditor && (
<div>
{isGeneratedArticle && (
<div className="mt-3 pb-0 flex flex-row ">
{articleIds.map((id: string, index: number) => (
<Button
type="button"
key={index}
className={`mr-3 px-3 py-2 rounded-md ${
selectedArticleId === id
? "bg-green-500 text-white"
: "border-2 border-green-500 bg-white text-green-500 hover:bg-green-500 hover:text-white hover:border-green-500"
}`}
onClick={() => handleArticleIdClick(id)}
>
{"Narasi " + (index + 1)}
</Button>
))}
</div>
)}
<div className="flex items-center space-x-2 mt-3">
<RadioGroupItem value="rewrite" id="rewrite-file" />
<Label htmlFor="rewrite-file">
Select Description Rewrite
</Label>
</div>
<div className="py-3 space-y-2">
<Label>{t("file-rewrite")}</Label>
<Controller
control={control}
name="rewriteDescription"
render={({ field: { onChange, value } }) =>
isLoadingData ? (
<div className="flex justify-center items-center h-40">
<p className="text-gray-500">
Loading Proses Data...
</p>
</div>
) : (
<CustomEditor
onChange={(value: any) => {
onChange(value);
setRewriteEditorContent(value);
}}
initialData={articleBody || value}
/>
)
}
/>
</div>
</div>
)}
</RadioGroup>
</>
)}
<div className="py-3 space-y-2"> <div className="py-3 space-y-2">
<Label>{t("select-file")}</Label> <Label>{t("select-file")}</Label>
{/* <Input {/* <Input

View File

@ -16,7 +16,7 @@ import * as z from "zod";
import { Upload } from "tus-js-client"; import { Upload } from "tus-js-client";
import Swal from "sweetalert2"; import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content"; import withReactContent from "sweetalert2-react-content";
import { redirect, useRouter } from "next/navigation"; import { redirect, useParams, useRouter } from "next/navigation";
import { import {
Select, Select,
SelectContent, SelectContent,
@ -40,6 +40,7 @@ import { uploadThumbnailBlog } from "@/service/blog/blog";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { import {
generateDataArticle, generateDataArticle,
generateDataRewrite,
getDetailArticle, getDetailArticle,
getGenerateKeywords, getGenerateKeywords,
getGenerateTitle, getGenerateTitle,
@ -82,12 +83,15 @@ export default function FormVideo() {
const router = useRouter(); const router = useRouter();
const editor = useRef(null); const editor = useRef(null);
type VideoSchema = z.infer<typeof videoSchema>; type VideoSchema = z.infer<typeof videoSchema>;
const params = useParams();
const locale = params?.locale;
const [selectedFiles, setSelectedFiles] = useState<File[]>([]); const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId"); const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId"); const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType"); const scheduleType = Cookies.get("scheduleType");
const roleId = getCookiesDecrypt("urie"); const roleId = getCookiesDecrypt("urie");
const [selectedFileType, setSelectedFileType] = useState("original");
const t = useTranslations("Form"); const t = useTranslations("Form");
const [categories, setCategories] = useState<Category[]>([]); const [categories, setCategories] = useState<Category[]>([]);
@ -97,6 +101,11 @@ export default function FormVideo() {
const [preview, setPreview] = useState<string | null>(null); const [preview, setPreview] = useState<string | null>(null);
const [selectedLanguage, setSelectedLanguage] = useState(""); const [selectedLanguage, setSelectedLanguage] = useState("");
const [selectedWritingStyle, setSelectedWritingStyle] =
useState("professional");
const [editorContent, setEditorContent] = useState(""); // Untuk original editor
const [rewriteEditorContent, setRewriteEditorContent] = useState("");
const [selectedSEO, setSelectedSEO] = useState<string>(""); const [selectedSEO, setSelectedSEO] = useState<string>("");
const [title, setTitle] = useState<string>(""); const [title, setTitle] = useState<string>("");
const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>(""); const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>("");
@ -111,13 +120,17 @@ export default function FormVideo() {
null null
); );
const [selectedMainKeyword, setSelectedMainKeyword] = useState(""); const [selectedMainKeyword, setSelectedMainKeyword] = useState("");
const [selectedWritingStyle, setSelectedWritingStyle] = useState(""); // const [selectedWritingStyle, setSelectedWritingStyle] = useState("");
const [selectedSize, setSelectedSize] = useState(""); const [selectedSize, setSelectedSize] = useState("");
const [detailData, setDetailData] = useState<any>(null); const [detailData, setDetailData] = useState<any>(null);
const [articleImages, setArticleImages] = useState<string[]>([]); const [articleImages, setArticleImages] = useState<string[]>([]);
const [isSwitchOn, setIsSwitchOn] = useState<boolean>(false); const [isSwitchOn, setIsSwitchOn] = useState<boolean>(false);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const [showRewriteEditor, setShowRewriteEditor] = useState(false);
const [isContentRewriteClicked, setIsContentRewriteClicked] = useState(false);
const [selectedTarget, setSelectedTarget] = useState(""); const [selectedTarget, setSelectedTarget] = useState("");
const [unitSelection, setUnitSelection] = useState({ const [unitSelection, setUnitSelection] = useState({
allUnit: false, allUnit: false,
@ -155,14 +168,9 @@ export default function FormVideo() {
const videoSchema = z.object({ const videoSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
description: z description: z.string().optional(),
.string() descriptionOri: z.string().optional(), // Original editor
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }) rewriteDescription: z.string().optional(),
.or(
z.literal(articleBody || "").refine((val) => val.length > 0, {
message: "Deskripsi diperlukan.",
})
),
creatorName: z.string().min(1, { message: "Creator diperlukan" }), creatorName: z.string().min(1, { message: "Creator diperlukan" }),
// tags: z.string().min(1, { message: "Judul diperlukan" }), // tags: z.string().min(1, { message: "Judul diperlukan" }),
}); });
@ -175,6 +183,11 @@ export default function FormVideo() {
formState: { errors }, formState: { errors },
} = useForm<VideoSchema>({ } = useForm<VideoSchema>({
resolver: zodResolver(videoSchema), resolver: zodResolver(videoSchema),
defaultValues: {
description: "",
descriptionOri: "",
rewriteDescription: "",
},
}); });
const doGenerateMainKeyword = async () => { const doGenerateMainKeyword = async () => {
@ -437,15 +450,30 @@ export default function FormVideo() {
} }
}; };
useEffect(() => {
if (articleBody) {
// Set ke dua field jika rewrite juga aktif
setValue("description", articleBody);
setValue("rewriteDescription", articleBody);
}
}, [articleBody, setValue]);
const save = async (data: VideoSchema) => { const save = async (data: VideoSchema) => {
loading(); loading();
const finalTags = tags.join(", "); const finalTags = tags.join(", ");
const finalTitle = isSwitchOn ? title : data.title; const finalTitle = isSwitchOn ? title : data.title;
const finalDescription = articleBody || data.description; // const finalDescription = articleBody || data.description;
if (!finalDescription.trim()) { const finalDescription = isSwitchOn
? data.description
: selectedFileType === "rewrite"
? data.rewriteDescription
: data.descriptionOri;
if (!finalDescription?.trim()) {
MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error"); MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
return; return;
} }
let requestData: { let requestData: {
title: string; title: string;
description: string; description: string;
@ -726,6 +754,45 @@ export default function FormVideo() {
} }
}, [title, getValues, setValue]); }, [title, getValues, setValue]);
const handleRewriteClick = async () => {
setIsContentRewriteClicked(true);
const request = {
style: selectedWritingStyle,
lang: "id",
contextType: "text",
urlContext: null,
context: editorContent, // Ambil isi editor original
createdBy: roleId,
sentiment: "Humorous",
clientId: "7QTW8cMojyayt6qnhqTOeJaBI70W4EaQ",
};
const res = await generateDataRewrite(request);
close();
if (res?.error) {
console.error(res.message);
return false;
}
const newArticleId = res?.data?.data?.id;
setIsGeneratedArticle(true);
setArticleIds((prevIds: string[]) => {
if (prevIds.length < 3) {
return [...prevIds, newArticleId];
} else {
const updatedIds = [...prevIds];
updatedIds[2] = newArticleId;
return updatedIds;
}
});
Cookies.set("nulisAIArticleIdTemp", JSON.stringify(articleIds));
setShowRewriteEditor(true);
};
return ( return (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-col lg:flex-row gap-10"> <div className="flex flex-col lg:flex-row gap-10">
@ -867,7 +934,7 @@ export default function FormVideo() {
placeholder="Enter Main Keyword" placeholder="Enter Main Keyword"
/> />
{/* )} {/* )}
/> */} /> */}
</div> </div>
</div> </div>
<div className="mt-5"> <div className="mt-5">
@ -917,7 +984,7 @@ export default function FormVideo() {
</div> </div>
</div> </div>
<div className="mt-5"> <div className="mt-5">
<Label>{t("special-instructions")} (Optional)</Label> <Label>{t("special-instructions")}(Optional)</Label>
<div className="mt-3"> <div className="mt-3">
<Controller <Controller
control={control} control={control}
@ -939,13 +1006,14 @@ export default function FormVideo() {
color="primary" color="primary"
onClick={handleGenerateArtikel} onClick={handleGenerateArtikel}
size="sm" size="sm"
type="button"
> >
Generate Article Generate Article
</Button> </Button>
</div> </div>
{isGeneratedArticle && ( {isGeneratedArticle && (
<div className="mt-3 pb-0 flex flex-row "> <div className="mt-3 pb-0 flex flex-row">
{articleIds.map((id: string, index: number) => ( {articleIds.map((id: string, index: number) => (
<p <p
key={index} key={index}
@ -965,50 +1033,155 @@ export default function FormVideo() {
<div className="pt-3"> <div className="pt-3">
<div className="flex flex-row justify-between items-center"> <div className="flex flex-row justify-between items-center">
{selectedArticleId && ( {selectedArticleId && (
<Link <Button
href={`/contributor/content/video/update-seo/${selectedArticleId}`} className="mb-2"
target="_blank" size="sm"
rel="noopener noreferrer" variant={"outline"}
color="primary"
onClick={() => {
const url = `/${locale}/contributor/content/image/update-seo/${selectedArticleId}`;
window.open(url, "_blank", "noopener,noreferrer");
}}
> >
<Button {t("update")}
className="mb-2" </Button>
size="sm"
variant={"outline"}
color="primary"
>
{t("update")}
</Button>
</Link>
)} )}
</div> </div>
</div> </div>
</div> </div>
<div className="py-3 space-y-2">
<Label>{t("description")}</Label>
<Controller
control={control}
name="description"
render={({ field: { onChange, value } }) =>
isLoadingData ? (
<div className="flex justify-center items-center h-40">
<p className="text-gray-500">
Loading Proses Data...
</p>
</div>
) : (
<CustomEditor
onChange={(value: any) => {
onChange(value);
setEditorContent(value);
}}
initialData={articleBody || value}
/>
)
}
/>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
</div> </div>
)} )}
<div className="space-y-2"> {!isSwitchOn && (
<Label>{t("description")}</Label> <>
<Controller <RadioGroup
control={control} onValueChange={(value) => setSelectedFileType(value)}
name="description" value={selectedFileType}
render={({ field: { onChange, value } }) => className=" grid-cols-1"
isLoadingData ? ( >
<div className="flex justify-center items-center h-40"> <div className="">
<p className="text-gray-500">Loading Proses Data...</p> <RadioGroupItem value="original" id="original-file" />
</div> <Label htmlFor="original-file">
) : ( Select Original Description
<CustomEditor </Label>
onChange={onChange} </div>
initialData={articleBody || value} <div className="py-3 space-y-2">
<Label>{t("description")}</Label>
<Controller
control={control}
name="descriptionOri"
render={({ field: { onChange, value } }) => (
<CustomEditor
onChange={(value: any) => {
onChange(value);
setEditorContent(value);
}}
initialData={value}
/>
)}
/> />
) {errors.description?.message && (
} <p className="text-red-400 text-sm">
/> {errors.description.message}
{errors.description?.message && ( </p>
<p className="text-red-400 text-sm"> )}
{errors.description.message} </div>
</p>
)} <p className="text-sm font-semibold">Content Rewrite</p>
</div> <div className="my-2">
<Button
size="sm"
type="button"
onClick={handleRewriteClick}
className="bg-blue-500 text-white py-2 px-4 rounded"
>
Content Rewrite
</Button>
</div>
{showRewriteEditor && (
<div>
{isGeneratedArticle && (
<div className="mt-3 pb-0 flex flex-row ">
{articleIds.map((id: string, index: number) => (
<Button
type="button"
key={index}
className={`mr-3 px-3 py-2 rounded-md ${
selectedArticleId === id
? "bg-green-500 text-white"
: "border-2 border-green-500 bg-white text-green-500 hover:bg-green-500 hover:text-white hover:border-green-500"
}`}
onClick={() => handleArticleIdClick(id)}
>
{"Narasi " + (index + 1)}
</Button>
))}
</div>
)}
<div className="flex items-center space-x-2 mt-3">
<RadioGroupItem value="rewrite" id="rewrite-file" />
<Label htmlFor="rewrite-file">
Select Description Rewrite
</Label>
</div>
<div className="py-3 space-y-2">
<Label>{t("file-rewrite")}</Label>
<Controller
control={control}
name="rewriteDescription"
render={({ field: { onChange, value } }) =>
isLoadingData ? (
<div className="flex justify-center items-center h-40">
<p className="text-gray-500">
Loading Proses Data...
</p>
</div>
) : (
<CustomEditor
onChange={(value: any) => {
onChange(value);
setRewriteEditorContent(value);
}}
initialData={articleBody || value}
/>
)
}
/>
</div>
</div>
)}
</RadioGroup>
</>
)}
<div className="py-3 space-y-2"> <div className="py-3 space-y-2">
<Label>{t("select-file")}</Label> <Label>{t("select-file")}</Label>
{/* <Input {/* <Input

View File

@ -202,6 +202,23 @@ export default function FormAskExpert() {
const details = response?.data?.data; const details = response?.data?.data;
setDetail(details); setDetail(details);
if (details?.assignedToLevel) {
const levels: Set<number> = new Set(
details.assignedToLevel.split(",").map((x: any) => Number(x))
);
setCheckedLevels(levels);
}
if (details?.assignedToUsers) {
const userIds = details.assignedToUsers.split(",").map(Number);
setCheckedLevels(new Set(userIds));
}
if (details?.expertCompetencies) {
const compIds = details.expertCompetencies.split(",").map(Number);
setSelectedCompetencies(new Set(compIds));
}
} }
} }
initState(); initState();
@ -515,7 +532,7 @@ export default function FormAskExpert() {
return ( return (
<Card> <Card>
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">{t("form-task")}</p> <p className="text-lg font-semibold mb-3">{t("form-task-ta")}</p>
{detail !== undefined ? ( {detail !== undefined ? (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
@ -617,7 +634,14 @@ export default function FormAskExpert() {
} }
className="mr-3" className="mr-3"
/> />
{expert.fullname} <div className="flex flex-col gap-2">
<div className="font-bold">
{expert.fullname}
</div>
<div className="italic">
({expert.username})
</div>
</div>
</Label> </Label>
</div> </div>
))} ))}
@ -625,6 +649,46 @@ export default function FormAskExpert() {
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div> </div>
{checkedLevels.size > 0 && (
<div className="mt-3">
<Label className="text-sm text-gray-600 mb-2 block">
Tenaga Ahli Terpilih ({checkedLevels.size})
</Label>
<div className="flex flex-wrap gap-2">
{Array.from(checkedLevels).map((expertId) => {
const expert = listExpert?.find(
(exp: any) => exp.id === expertId
);
return expert ? (
<div
key={expert.id}
className="inline-flex items-center gap-2 bg-blue-100 text-blue-800 text-sm font-medium px-3 py-1.5 rounded-full border border-blue-200"
>
<span>{expert.fullname}</span>
<button
type="button"
onClick={() => handleCheckboxChange(expert.id)}
className="ml-1 text-blue-600 hover:text-blue-800 hover:bg-blue-200 rounded-full p-0.5 transition-colors"
title="Remove expert"
>
<svg
className="w-3 h-3"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
</button>
</div>
) : null;
})}
</div>
</div>
)}
</div> </div>
<div className="mt-5 space-y-2"> <div className="mt-5 space-y-2">

View File

@ -636,7 +636,7 @@ export default function FormDoItYourself() {
return ( return (
<Card> <Card>
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">{t("form-task")}</p> <p className="text-lg font-semibold mb-3">{t("form-task-ta-do")}</p>
{detail !== undefined ? ( {detail !== undefined ? (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">

View File

@ -10,42 +10,23 @@ import * as z from "zod";
import Swal from "sweetalert2"; import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content"; import withReactContent from "sweetalert2-react-content";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import JoditEditor from "jodit-react";
import { import {
acceptAssignment, acceptAssignmentTa,
createAssignmentResponse, createAssignmentResponse,
createTask,
deleteAssignmentResponse, deleteAssignmentResponse,
deleteTask, deleteTask,
finishTask, finishTaskTa,
getAcceptance, getAcceptance,
getAcceptanceAssignmentStatus, getAcceptanceAssignmentStatus,
getAssignmentResponseList, getAssignmentResponseList,
getMediaUpload, getMediaUpload,
getMediaUploadTa, getMediaUploadTa,
getTask,
getTaskTa, getTaskTa,
getUserLevelForAssignments, getUserLevelForAssignments,
getUserLevelForExpert,
} from "@/service/task"; } from "@/service/task";
import { import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
ChevronDown,
ChevronUp,
Dock, Dock,
DotSquare, DotSquare,
ImageIcon, ImageIcon,
@ -61,14 +42,20 @@ import { close, error, loading } from "@/lib/swal";
import { getCookiesDecrypt } from "@/lib/utils"; import { getCookiesDecrypt } from "@/lib/utils";
import { Avatar, AvatarImage } from "@/components/ui/avatar"; import { Avatar, AvatarImage } from "@/components/ui/avatar";
import { successCallback } from "@/config/swal"; import { successCallback } from "@/config/swal";
import FileUploader from "../shared/file-uploader";
import { AudioRecorder } from "react-audio-voice-recorder";
import Image from "next/image"; import Image from "next/image";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import WavesurferPlayer from "@wavesurfer/react"; import WavesurferPlayer from "@wavesurfer/react";
import WaveSurfer from "wavesurfer.js"; import WaveSurfer from "wavesurfer.js";
import { InputGroup, InputGroupText } from "@/components/ui/input-group"; import { InputGroup, InputGroupText } from "@/components/ui/input-group";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { getListCompetencies } from "@/service/management-user/management-user";
const taskSchema = z.object({ const taskSchema = z.object({
uniqueCode: z.string().min(1, { message: "Judul diperlukan" }), uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
@ -252,7 +239,11 @@ export default function FormTaskTaDetail() {
const [acceptAcceptance, setAcceptAcceptance] = useState<AcceptanceData[]>( const [acceptAcceptance, setAcceptAcceptance] = useState<AcceptanceData[]>(
[] []
); );
const [listExpert, setListExpert] = useState<any[]>([]);
const [userCompetencies, setUserCompetencies] = useState<any[]>([]);
const [selectedCompetencies, setSelectedCompetencies] = useState<Set<number>>(
new Set()
);
const [totalPage, setTotalPage] = React.useState(1); const [totalPage, setTotalPage] = React.useState(1);
const [dataTable, setDataTable] = React.useState<any[]>([]); const [dataTable, setDataTable] = React.useState<any[]>([]);
const [totalData, setTotalData] = React.useState<number>(1); const [totalData, setTotalData] = React.useState<number>(1);
@ -327,37 +318,37 @@ export default function FormTaskTaDetail() {
// setPlatformTypeVisible(selectedValue === 2); // setPlatformTypeVisible(selectedValue === 2);
// }; // };
const handleExpertiseOutputChange = ( // const handleExpertiseOutputChange = (
key: keyof typeof expertise, // key: keyof typeof expertise,
value: boolean // value: boolean
) => { // ) => {
if (key === "semua") { // if (key === "semua") {
const newState = { // const newState = {
semua: value, // semua: value,
komunikasi: value, // komunikasi: value,
hukum: value, // hukum: value,
bahasa: value, // bahasa: value,
ekonomi: value, // ekonomi: value,
politik: value, // politik: value,
sosiologi: value, // sosiologi: value,
ilmuadministrasipemerintah: value, // ilmuadministrasipemerintah: value,
ti: value, // ti: value,
}; // };
setExpertiseOutput(newState); // setExpertiseOutput(newState);
} else { // } else {
const updated = { // const updated = {
...expertise, // ...expertise,
[key]: value, // [key]: value,
}; // };
const allChecked = ["video", "audio", "image", "text"].every( // const allChecked = ["video", "audio", "image", "text"].every(
(k) => updated[k as keyof typeof expertise] // (k) => updated[k as keyof typeof expertise]
); // );
updated.semua = allChecked; // updated.semua = allChecked;
setExpertiseOutput(updated); // setExpertiseOutput(updated);
} // }
}; // };
const handleExpertOutputChange = ( const handleExpertOutputChange = (
key: keyof typeof expert, key: keyof typeof expert,
@ -383,6 +374,68 @@ export default function FormTaskTaDetail() {
} }
}; };
useEffect(() => {
getDataAdditional();
}, []);
async function getDataAdditional() {
const resCompetencies = await getListCompetencies();
console.log("competency", resCompetencies);
setUserCompetencies(resCompetencies?.data?.data);
}
useEffect(() => {
async function fetchListExpert() {
setIsLoading(true);
try {
const response = await getUserLevelForExpert(id);
setListExpert(response?.data?.data);
console.log("tenaga ahli", response?.data?.data);
} catch (error) {
console.error("Error fetching Polda/Polres data:", error);
} finally {
setIsLoading(false);
}
}
fetchListExpert();
}, []);
useEffect(() => {
const fetchExpertsForCompetencies = async () => {
const allExperts: any[] = [];
for (const compId of Array.from(selectedCompetencies)) {
const response = await getUserLevelForExpert(compId);
const experts = response?.data?.data || [];
allExperts.push(...experts);
}
const uniqueExperts = Array.from(
new Map(allExperts.map((e) => [e.id, e])).values()
);
setListExpert(uniqueExperts);
};
if (selectedCompetencies.size > 0) {
fetchExpertsForCompetencies();
} else {
setListExpert([]);
}
}, [selectedCompetencies]);
const handleCompetencyChange = async (competencyId: number) => {
setSelectedCompetencies((prev) => {
const updated = new Set(prev);
if (updated.has(competencyId)) {
updated.delete(competencyId);
} else {
updated.add(competencyId);
}
return updated;
});
};
useEffect(() => { useEffect(() => {
async function fetchPoldaPolres() { async function fetchPoldaPolres() {
setIsLoading(true); setIsLoading(true);
@ -458,6 +511,16 @@ export default function FormTaskTaDetail() {
setCheckedLevels(levels); setCheckedLevels(levels);
} }
if (details?.assignedToUsers) {
const userIds = details.assignedToUsers.split(",").map(Number);
setCheckedLevels(new Set(userIds));
}
if (details?.expertCompetencies) {
const compIds = details.expertCompetencies.split(",").map(Number);
setSelectedCompetencies(new Set(compIds));
}
const attachment = details?.files; const attachment = details?.files;
setImageUploadedFiles( setImageUploadedFiles(
attachment?.filter((file: any) => file.fileTypeId == 1) attachment?.filter((file: any) => file.fileTypeId == 1)
@ -542,7 +605,7 @@ export default function FormTaskTaDetail() {
confirmButtonText: "OK", confirmButtonText: "OK",
}).then((result) => { }).then((result) => {
if (result.isConfirmed) { if (result.isConfirmed) {
router.push("/en/contributor/task"); router.push("/en/contributor/task-ta");
} }
}); });
}; };
@ -682,7 +745,7 @@ export default function FormTaskTaDetail() {
const handleAcceptAcceptance = async () => { const handleAcceptAcceptance = async () => {
loading(); loading();
console.log("Id user :", userId); console.log("Id user :", userId);
const response = await acceptAssignment(id); const response = await acceptAssignmentTa(id);
if (response?.error) { if (response?.error) {
error(response?.message); error(response?.message);
@ -773,7 +836,7 @@ export default function FormTaskTaDetail() {
); );
async function finishAssignment() { async function finishAssignment() {
const response = finishTask(id); const response = finishTaskTa(id);
// if (response.error) { // if (response.error) {
// error(response.message); // error(response.message);
@ -1011,48 +1074,98 @@ export default function FormTaskTaDetail() {
<div className="mt-5 space-y-2"> <div className="mt-5 space-y-2">
<Label>{t("areas-expertise")}</Label> <Label>{t("areas-expertise")}</Label>
<div className="flex flex-wrap gap-4"> <div className="flex flex-wrap gap-4">
{Object.keys(expertise).map((key) => ( {userCompetencies?.map((item: any) => (
<div className="flex items-center gap-2" key={key}> <div className="flex items-center gap-2" key={item.id}>
<Checkbox <Checkbox
id={key} id={`comp-${item.id}`}
checked={expertise[key as keyof typeof expertise]} checked={selectedCompetencies.has(item.id)}
onCheckedChange={(value) => onCheckedChange={() => handleCompetencyChange(item.id)}
handleExpertiseOutputChange(
key as keyof typeof expertise,
value as boolean
)
}
disabled disabled
/> />
<Label htmlFor={key}> <Label htmlFor={`comp-${item.id}`}>{item.name}</Label>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
</div> </div>
))} ))}
</div> </div>
</div> </div>
<div className="mt-5 space-y-2"> <div className="mt-5 space-y-2">
<Label>{t("choose-expert")}</Label> {/* <Label>{t("choose-expert")}</Label>
<div className="flex flex-wrap gap-4"> <div className="flex flex-wrap gap-4">
{Object.keys(expert).map((key) => ( <Dialog>
<div className="flex items-center gap-2" key={key}> <DialogTrigger asChild>
<Checkbox <Button variant="soft" size="sm" color="primary">
id={key} [{"Pilih Tenaga Ahli"}]
checked={expert[key as keyof typeof expert]} </Button>
onCheckedChange={(value) => </DialogTrigger>
handleExpertOutputChange( <DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
key as keyof typeof expert, <DialogHeader>
value as boolean <DialogTitle>Daftar Tenaga Ahli</DialogTitle>
) </DialogHeader>
} <div className="grid grid-cols-2 gap-2 max-h-[400px] overflow-y-auto">
/> {listExpert?.map((expert: any) => (
<Label htmlFor={key}> <div key={expert.id} className="border p-2">
{key.charAt(0).toUpperCase() + key.slice(1)} <Label className="flex items-center">
</Label> <Checkbox
checked={checkedLevels.has(expert.id)}
onCheckedChange={() =>
handleCheckboxChange(expert.id)
}
className="mr-3"
/>
<div className="flex flex-col gap-2">
<div className="font-bold">
{expert.fullname}
</div>
<div className="italic">
({expert.username})
</div>
</div>
</Label>
</div>
))}
</div>
</DialogContent>
</Dialog>
</div> */}
{checkedLevels.size > 0 && (
<div className="mt-3">
<Label className="text-sm text-gray-600 mb-2 block">
Tenaga Ahli Terpilih ({checkedLevels.size})
</Label>
<div className="flex flex-wrap gap-2">
{Array.from(checkedLevels).map((expertId) => {
const expert = listExpert?.find(
(exp: any) => exp.id === expertId
);
return expert ? (
<div
key={expert.id}
className="inline-flex items-center gap-2 bg-blue-100 text-blue-800 text-sm font-medium px-3 py-1.5 rounded-full border border-blue-200"
>
<span>{expert.fullname}</span>
<button
type="button"
onClick={() => handleCheckboxChange(expert.id)}
className="ml-1 text-blue-600 hover:text-blue-800 hover:bg-blue-200 rounded-full p-0.5 transition-colors"
title="Remove expert"
>
<svg
className="w-3 h-3"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
</button>
</div>
) : null;
})}
</div> </div>
))} </div>
</div> )}
</div> </div>
<div></div> <div></div>

View File

@ -26,6 +26,7 @@ import {
getTask, getTask,
getTaskTa, getTaskTa,
getUserLevelForAssignments, getUserLevelForAssignments,
getUserLevelForExpert,
} from "@/service/task"; } from "@/service/task";
import { import {
Dialog, Dialog,
@ -40,6 +41,7 @@ import {
Dock, Dock,
ImageIcon, ImageIcon,
Music, Music,
Trash2,
VideoIcon, VideoIcon,
} from "lucide-react"; } from "lucide-react";
import FileUploader from "../shared/file-uploader"; import FileUploader from "../shared/file-uploader";
@ -52,6 +54,7 @@ import { Upload } from "tus-js-client";
import { error, loading } from "@/lib/swal"; import { error, loading } from "@/lib/swal";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { getListCompetencies } from "@/service/management-user/management-user";
const taskSchema = z.object({ const taskSchema = z.object({
// uniqueCode: z.string().min(1, { message: "Judul diperlukan" }), // uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
@ -148,11 +151,18 @@ export default function FormTaskTaEdit() {
const [broadcastType, setBroadcastType] = useState<string>(""); // untuk Tipe Penugasan const [broadcastType, setBroadcastType] = useState<string>(""); // untuk Tipe Penugasan
const [type, setType] = useState<string>("1"); const [type, setType] = useState<string>("1");
const [selectedTarget, setSelectedTarget] = useState("3,4"); const [selectedTarget, setSelectedTarget] = useState("3,4");
const [listExpert, setListExpert] = useState<any[]>([]);
const [userCompetencies, setUserCompetencies] = useState<any[]>([]);
const [selectedCompetencies, setSelectedCompetencies] = useState<Set<number>>(
new Set()
);
const [detail, setDetail] = useState<taskDetail>(); const [detail, setDetail] = useState<taskDetail>();
const [urlInputs, setUrlInputs] = useState<Url[]>([]); const [urlInputs, setUrlInputs] = useState<Url[]>([]);
const [refresh] = useState(false); const [refresh] = useState(false);
const [listDest, setListDest] = useState([]); // Data Polda dan Polres const [listDest, setListDest] = useState([]); // Data Polda dan Polres
const [checkedLevels, setCheckedLevels] = useState(new Set()); const [checkedLevels, setCheckedLevels] = useState<Set<number>>(new Set());
const [expandedPolda, setExpandedPolda] = useState([{}]); const [expandedPolda, setExpandedPolda] = useState([{}]);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [audioFile, setAudioFile] = useState<File | null>(null); const [audioFile, setAudioFile] = useState<File | null>(null);
@ -247,6 +257,68 @@ export default function FormTaskTaEdit() {
} }
}; };
useEffect(() => {
getDataAdditional();
}, []);
async function getDataAdditional() {
const resCompetencies = await getListCompetencies();
console.log("competency", resCompetencies);
setUserCompetencies(resCompetencies?.data?.data);
}
useEffect(() => {
async function fetchListExpert() {
setIsLoading(true);
try {
const response = await getUserLevelForExpert(id);
setListExpert(response?.data?.data);
console.log("tenaga ahli", response?.data?.data);
} catch (error) {
console.error("Error fetching Polda/Polres data:", error);
} finally {
setIsLoading(false);
}
}
fetchListExpert();
}, []);
useEffect(() => {
const fetchExpertsForCompetencies = async () => {
const allExperts: any[] = [];
for (const compId of Array.from(selectedCompetencies)) {
const response = await getUserLevelForExpert(compId);
const experts = response?.data?.data || [];
allExperts.push(...experts);
}
const uniqueExperts = Array.from(
new Map(allExperts.map((e) => [e.id, e])).values()
);
setListExpert(uniqueExperts);
};
if (selectedCompetencies.size > 0) {
fetchExpertsForCompetencies();
} else {
setListExpert([]);
}
}, [selectedCompetencies]);
const handleCompetencyChange = async (competencyId: number) => {
setSelectedCompetencies((prev) => {
const updated = new Set(prev);
if (updated.has(competencyId)) {
updated.delete(competencyId);
} else {
updated.add(competencyId);
}
return updated;
});
};
useEffect(() => { useEffect(() => {
async function fetchPoldaPolres() { async function fetchPoldaPolres() {
setIsLoading(true); setIsLoading(true);
@ -285,12 +357,22 @@ export default function FormTaskTaEdit() {
} }
if (details?.assignedToLevel) { if (details?.assignedToLevel) {
const levels = new Set( const levels: Set<number> = new Set(
details.assignedToLevel.split(",").map(Number) details.assignedToLevel.split(",").map((x: any) => Number(x))
); );
setCheckedLevels(levels); setCheckedLevels(levels);
} }
if (details?.assignedToUsers) {
const userIds = details.assignedToUsers.split(",").map(Number);
setCheckedLevels(new Set(userIds));
}
if (details?.expertCompetencies) {
const compIds = details.expertCompetencies.split(",").map(Number);
setSelectedCompetencies(new Set(compIds));
}
const attachment = details?.files; const attachment = details?.files;
setImageUploadedFiles( setImageUploadedFiles(
attachment?.filter((file: any) => file.fileTypeId == 1) attachment?.filter((file: any) => file.fileTypeId == 1)
@ -376,7 +458,7 @@ export default function FormTaskTaEdit() {
confirmButtonText: "OK", confirmButtonText: "OK",
}).then((result) => { }).then((result) => {
if (result.isConfirmed) { if (result.isConfirmed) {
router.push("/en/contributor/task"); router.push("/en/contributor/task-ta");
} }
}); });
}; };
@ -393,9 +475,9 @@ export default function FormTaskTaEdit() {
}); });
}; };
const handlePoldaPolresChange = () => { // const handlePoldaPolresChange = () => {
return Array.from(checkedLevels).join(","); // Mengonversi Set ke string // return Array.from(checkedLevels).join(","); // Mengonversi Set ke string
}; // };
const handleUnitChange = ( const handleUnitChange = (
key: keyof typeof unitSelection, key: keyof typeof unitSelection,
@ -454,6 +536,10 @@ export default function FormTaskTaEdit() {
} }
}; };
const handleExpertChange = () => {
return Array.from(checkedLevels).join(",");
};
const save = async (data: TaskSchema) => { const save = async (data: TaskSchema) => {
const fileTypeMapping = { const fileTypeMapping = {
all: "1", all: "1",
@ -483,16 +569,11 @@ export default function FormTaskTaEdit() {
const requestData: { const requestData: {
id?: any; id?: any;
title: string; title: string;
assignedToLevel: any;
assignedToUsers: any; assignedToUsers: any;
assignmentTypeId: string; assignmentTypeId: string;
fileTypeOutput: string;
narration: string; narration: string;
platformType: string | null;
assignmentMainTypeId: any;
assignmentType: string; assignmentType: string;
assignedToRole: string; assignedToRole: string;
broadcastType: string;
expertCompetencies: string; expertCompetencies: string;
attachmentUrl: string[]; attachmentUrl: string[];
} = { } = {
@ -500,17 +581,12 @@ export default function FormTaskTaEdit() {
// assignmentType, // assignmentType,
// assignmentCategory, // assignmentCategory,
id: detail?.id || null, id: detail?.id || null,
assignedToLevel: handlePoldaPolresChange(), assignedToUsers: handleExpertChange(),
assignedToUsers: assignmentPurposeString,
assignedToRole: selectedTarget, assignedToRole: selectedTarget,
assignmentType: taskType, assignmentType: taskType,
broadcastType: broadcastType,
assignmentMainTypeId: mainType,
assignmentTypeId: type, assignmentTypeId: type,
fileTypeOutput: selectedOutputs,
narration: data.naration, narration: data.naration,
platformType: "", expertCompetencies: Array.from(selectedCompetencies).join(","),
expertCompetencies: "1,2,3",
title: data.title, title: data.title,
attachmentUrl: links, attachmentUrl: links,
}; };
@ -808,47 +884,97 @@ export default function FormTaskTaEdit() {
<div className="mt-5 space-y-2"> <div className="mt-5 space-y-2">
<Label>{t("areas-expertise")}</Label> <Label>{t("areas-expertise")}</Label>
<div className="flex flex-wrap gap-4"> <div className="flex flex-wrap gap-4">
{Object.keys(expertise).map((key) => ( {userCompetencies?.map((item: any) => (
<div className="flex items-center gap-2" key={key}> <div className="flex items-center gap-2" key={item.id}>
<Checkbox <Checkbox
id={key} id={`comp-${item.id}`}
checked={expertise[key as keyof typeof expertise]} checked={selectedCompetencies.has(item.id)}
onCheckedChange={(value) => onCheckedChange={() => handleCompetencyChange(item.id)}
handleExpertiseOutputChange(
key as keyof typeof expertise,
value as boolean
)
}
/> />
<Label htmlFor={key}> <Label htmlFor={`comp-${item.id}`}>{item.name}</Label>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
</div> </div>
))} ))}
</div> </div>
</div> </div>
<div className="mt-5 space-y-2"> <div className="mt-5 space-y-2">
<Label>{t("choose-expert")}</Label> <Label>{t("choose-expert")}</Label>
<div className="flex flex-wrap gap-4"> <div className="flex flex-wrap gap-4">
{Object.keys(expert).map((key) => ( <Dialog>
<div className="flex items-center gap-2" key={key}> <DialogTrigger asChild>
<Checkbox <Button variant="soft" size="sm" color="primary">
id={key} [{"Pilih Tenaga Ahli"}]
checked={expert[key as keyof typeof expert]} </Button>
onCheckedChange={(value) => </DialogTrigger>
handleExpertOutputChange( <DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
key as keyof typeof expert, <DialogHeader>
value as boolean <DialogTitle>Daftar Tenaga Ahli</DialogTitle>
) </DialogHeader>
} <div className="grid grid-cols-2 gap-2 max-h-[400px] overflow-y-auto">
/> {listExpert?.map((expert: any) => (
<Label htmlFor={key}> <div key={expert.id} className="border p-2">
{key.charAt(0).toUpperCase() + key.slice(1)} <Label className="flex items-center">
</Label> <Checkbox
</div> checked={checkedLevels.has(expert.id)}
))} onCheckedChange={() =>
handleCheckboxChange(expert.id)
}
className="mr-3"
/>
<div className="flex flex-col gap-2">
<div className="font-bold">
{expert.fullname}
</div>
<div className="italic">
({expert.username})
</div>
</div>
</Label>
</div>
))}
</div>
</DialogContent>
</Dialog>
</div> </div>
{checkedLevels.size > 0 && (
<div className="mt-3">
<Label className="text-sm text-gray-600 mb-2 block">
Tenaga Ahli Terpilih ({checkedLevels.size})
</Label>
<div className="flex flex-wrap gap-2">
{Array.from(checkedLevels).map((expertId) => {
const expert = listExpert?.find(
(exp: any) => exp.id === expertId
);
return expert ? (
<div
key={expert.id}
className="inline-flex items-center gap-2 bg-blue-100 text-blue-800 text-sm font-medium px-3 py-1.5 rounded-full border border-blue-200"
>
<span>{expert.fullname}</span>
<button
type="button"
onClick={() => handleCheckboxChange(expert.id)}
className="ml-1 text-blue-600 hover:text-blue-800 hover:bg-blue-200 rounded-full p-0.5 transition-colors"
title="Remove expert"
>
<svg
className="w-3 h-3"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
</button>
</div>
) : null;
})}
</div>
</div>
)}
</div> </div>
<div className="mt-5 space-y-2"> <div className="mt-5 space-y-2">
@ -1079,13 +1205,42 @@ export default function FormTaskTaEdit() {
/> />
</div> </div>
))} ))}
<button <div className="mt-4 space-y-2">
type="button" <Label className="">{t("news-links")}</Label>
className="mt-4 bg-green-500 text-white px-4 py-2 rounded" {links.map((link, index) => (
onClick={handleAddLink} <div
> key={index}
{t("add-links")} className="flex items-center gap-2 mt-2"
</button> >
<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)}
>
<Trash2 className="h-4 w-4" />
</button>
)}
</div>
))}
<Button
type="button"
className="mt-2 bg-blue-500 text-white px-4 py-2 rounded"
onClick={handleAddRow}
size="sm"
>
{t("add-links")}
</Button>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -81,7 +81,7 @@ const ContentCategory = (props: { group?: string; type: string }) => {
alt="category" alt="category"
width={2560} width={2560}
height={1440} height={1440}
src={category?.smallThumbnailLink} src={category?.thumbnailLink}
className="w-full lg:h-[300px] h-40 object-cover group-hover:scale-110 transition-transform duration-300" className="w-full lg:h-[300px] h-40 object-cover group-hover:scale-110 transition-transform duration-300"
/> />

View File

@ -10,7 +10,7 @@ import Image from "next/image";
const regions = [ const regions = [
{ name: "ITWASUM POLRI", slug: "itwasum", logo: "/logo/satker/ITWASUM.png" }, { name: "ITWASUM POLRI", slug: "itwasum", logo: "/logo/satker/ITWASUM.png" },
{ {
name: "BAINTELKAM POLRI", name: "BAINTELKAM POLRI ",
slug: "baintelkam", slug: "baintelkam",
logo: "/logo/satker/BAINTELKAM.png", logo: "/logo/satker/BAINTELKAM.png",
}, },

View File

@ -71,22 +71,22 @@ export default function DashboardVisualization() {
setIsInternational(updatedIsInternational); setIsInternational(updatedIsInternational);
}; };
useEffect(() => { // useEffect(() => {
async function fetchUrl() { // async function fetchUrl() {
console.log("Fetch tableau"); // console.log("Fetch tableau");
const urlView = `${url + ticket1}/${view1}${param}`; // const urlView = `${url + ticket1}/${view1}${param}`;
console.log("Fetch tableau ", urlView); // console.log("Fetch tableau ", urlView);
const urlRender = await fetch(urlView) // const urlRender = await fetch(urlView)
.then((response) => { // .then((response) => {
console.log("Tableau res : ", response); // console.log("Tableau res : ", response);
}) // })
.catch((error) => { // .catch((error) => {
console.log("Tableau error: ", error); // console.log("Tableau error: ", error);
}); // });
} // }
fetchUrl(); // fetchUrl();
}, [ticket1]); // }, [ticket1]);
return ( return (
<div className="flex flex-col gap-2 bg-white rounded-lg p-3"> <div className="flex flex-col gap-2 bg-white rounded-lg p-3">

View File

@ -3627,20 +3627,20 @@ export function getMenuList(pathname: string, t: any): Group[] {
}, },
], ],
}, },
{ // {
groupLabel: "", // groupLabel: "",
id: "contest", // id: "contest",
menus: [ // menus: [
{ // {
id: "contest", // id: "contest",
href: "/shared/contest", // href: "/shared/contest",
label: t("contest"), // label: t("contest"),
active: pathname.includes("/contest"), // active: pathname.includes("/contest"),
icon: "ic:outline-emoji-events", // icon: "ic:outline-emoji-events",
submenus: [], // submenus: [],
}, // },
], // ],
}, // },
{ {
groupLabel: "", groupLabel: "",
id: "communication", id: "communication",
@ -3896,16 +3896,16 @@ export function getMenuList(pathname: string, t: any): Group[] {
icon: "material-symbols:map-search-outline", icon: "material-symbols:map-search-outline",
submenus: [ submenus: [
{ {
href: "/admin/media-tracking/media-online", href: "/admin/media-tracking/tracking-berita",
label: "Media Online", label: "Tracking Beritra",
active: pathname === "/media-tracking/media-online", active: pathname === "/admin/media-tracking/tracking-berita",
icon: "heroicons:arrow-trending-up", icon: "heroicons:arrow-trending-up",
children: [], children: [],
}, },
{ {
href: "/admin/media-tracking/tb-news", href: "/admin/media-tracking/results",
label: "Tracking Berita Hari Ini", label: "Hasil",
active: pathname === "/media-tracking/news", active: pathname === "/admin/media-tracking/results",
icon: "heroicons:arrow-trending-up", icon: "heroicons:arrow-trending-up",
children: [], children: [],
}, },
@ -4079,20 +4079,20 @@ export function getMenuList(pathname: string, t: any): Group[] {
}, },
], ],
}, },
// { {
// groupLabel: "", groupLabel: "",
// id: "performance-satker", id: "performance-satker",
// menus: [ menus: [
// { {
// id: "performance-polres", id: "performance-polres",
// href: "/admin/performance-satker", href: "/admin/performance-satker",
// label: t("performance-satker"), label: t("performance-satker"),
// active: pathname.includes("/admin/performance-satker"), active: pathname.includes("/admin/performance-satker"),
// icon: "ant-design:signal-filled", icon: "ant-design:signal-filled",
// submenus: [], submenus: [],
// }, },
// ], ],
// }, },
{ {
groupLabel: "", groupLabel: "",
id: "media-tracking", id: "media-tracking",
@ -4105,16 +4105,16 @@ export function getMenuList(pathname: string, t: any): Group[] {
icon: "material-symbols:map-search-outline", icon: "material-symbols:map-search-outline",
submenus: [ submenus: [
{ {
href: "/admin/media-tracking/media-online", href: "/admin/media-tracking/tracking-berita",
label: "Media Online", label: "Tracking Beritra",
active: pathname === "/media-tracking/media-online", active: pathname === "/admin/media-tracking/tracking-berita",
icon: "heroicons:arrow-trending-up", icon: "heroicons:arrow-trending-up",
children: [], children: [],
}, },
{ {
href: "/admin/media-tracking/tb-news", href: "/admin/media-tracking/results",
label: "Tracking Berita Hari Ini", label: "Hasil",
active: pathname === "/media-tracking/news", active: pathname === "/admin/media-tracking/results",
icon: "heroicons:arrow-trending-up", icon: "heroicons:arrow-trending-up",
children: [], children: [],
}, },

View File

@ -784,6 +784,8 @@
"assignment-type": "Assignment Type", "assignment-type": "Assignment Type",
"description-task": "Description Task", "description-task": "Description Task",
"form-task": "Form Task", "form-task": "Form Task",
"form-task-ta": "Form Ask the Expert",
"form-task-ta-do": "Form Do it yourself",
"assignment-selection": "Assignment Recipient", "assignment-selection": "Assignment Recipient",
"custom": "Costum", "custom": "Costum",
"assigment-type": "Assigment Type", "assigment-type": "Assigment Type",

View File

@ -571,47 +571,33 @@
"divisionNews": "Berita Satker", "divisionNews": "Berita Satker",
"areaCoverage": "Liputan Wilayah & Satker", "areaCoverage": "Liputan Wilayah & Satker",
"calendar": "KALENDER ACARA", "calendar": "KALENDER ACARA",
"january": "Januari", "january": "Januari",
"february": "February", "february": "February",
"march": "March", "march": "March",
"may": "May", "may": "May",
"june": "Juni", "june": "Juni",
"july": "Juli", "july": "Juli",
"august": "Agustus", "august": "Agustus",
"october": "Oktober", "october": "Oktober",
"december": "Desember", "december": "Desember",
"eventList": "Daftar Acara", "eventList": "Daftar Acara",
"noEvent": "Tidak ada acara yang tersedia", "noEvent": "Tidak ada acara yang tersedia",
"eventDetails": "Detail Acara", "eventDetails": "Detail Acara",
"date": "Tanggal:", "date": "Tanggal:",
"selectEvent": "Pilih acara untuk melihat detail", "selectEvent": "Pilih acara untuk melihat detail",
"regionalPolice": "Polda Jajaran", "regionalPolice": "Polda Jajaran",
"policeDivision": "Satuan Kerja Polri", "policeDivision": "Satuan Kerja Polri",
"close": "Tutup", "close": "Tutup",
"survey1": "SURVEI KEPUASAN PENGGUNA MEDIAHUB POLRI", "survey1": "SURVEI KEPUASAN PENGGUNA MEDIAHUB POLRI",
"survey2": "Kami menghargai pendapat Anda! Survei ini bertujuan untuk meningkatkan kualitas layanan MediaHub Polri. Mohon luangkan waktu beberapa menit untuk mengisi survei ini.", "survey2": "Kami menghargai pendapat Anda! Survei ini bertujuan untuk meningkatkan kualitas layanan MediaHub Polri. Mohon luangkan waktu beberapa menit untuk mengisi survei ini.",
"survey3": "SURVEY SEKARANG", "survey3": "SURVEY SEKARANG",
"survey4": "SURVEI KEPUASAN PENGGUNA MEDIAHUB POLRI", "survey4": "SURVEI KEPUASAN PENGGUNA MEDIAHUB POLRI",
"survey5": "Kami menghargai pendapat Anda! Survei ini bertujuan untuk meningkatkan kualitas layanan MediaHub Polri.", "survey5": "Kami menghargai pendapat Anda! Survei ini bertujuan untuk meningkatkan kualitas layanan MediaHub Polri.",
"survey6": "1. Seberapa sering Anda mengakses MediaHub Polri?", "survey6": "1. Seberapa sering Anda mengakses MediaHub Polri?",
"survey7": "2. Bagaimana pengalaman Anda dalam mengakses website ini?", "survey7": "2. Bagaimana pengalaman Anda dalam mengakses website ini?",
"survey8": "3. Seberapa puas Anda dengan informasi yang tersedia di MediaHub Polri?", "survey8": "3. Seberapa puas Anda dengan informasi yang tersedia di MediaHub Polri?",
"survey9": "4. Apakah Anda merasa website ini membantu dalam mendapatkan informasi terkait Polri?", "survey9": "4. Apakah Anda merasa website ini membantu dalam mendapatkan informasi terkait Polri?",
"survey10": "5. Apa saran atau masukan Anda?" "survey10": "5. Apa saran atau masukan Anda?"
}, },
"FilterPage": { "FilterPage": {
"image": "Foto", "image": "Foto",
@ -798,6 +784,8 @@
"assignment-type": "Jenis Penugasan", "assignment-type": "Jenis Penugasan",
"description-task": "Narasi Penugasan", "description-task": "Narasi Penugasan",
"form-task": "Form Penugasan", "form-task": "Form Penugasan",
"form-task-ta": "Form Tanya Tenaga Ahli",
"form-task-ta-do": "Form Tenaga Ahli Kurasi",
"assignment-selection": "Penerima Tugas", "assignment-selection": "Penerima Tugas",
"custom": "Kostum", "custom": "Kostum",
"assigment-type": "Jenis Tugas", "assigment-type": "Jenis Tugas",

View File

@ -152,7 +152,7 @@ export async function getTagsBySubCategoryId(subCategory: any) {
} }
export async function listEnableCategory(type: any) { export async function listEnableCategory(type: any) {
const url = `media/categories/list/enable?enablePage=0&sort=desc&sortBy=id&type=${type}`; const url = `media/categories/list?enablePage=0&sort=desc&sortBy=id&type=${type}`;
return httpGetInterceptor(url); return httpGetInterceptor(url);
} }

View File

@ -46,6 +46,11 @@ export async function saveUserInternal(data: any) {
return httpPostInterceptor(url, data); return httpPostInterceptor(url, data);
} }
export async function checkRolePlacementsAvailability(data: any) {
const url = "users/role-placements/availability";
return httpPostInterceptor(url, data);
}
export async function saveUserRolePlacements(data: any) { export async function saveUserRolePlacements(data: any) {
const url = "users/role-placements"; const url = "users/role-placements";
return httpPostInterceptor(url, data); return httpPostInterceptor(url, data);

View File

@ -108,6 +108,11 @@ export async function acceptAssignment(id: any) {
return httpPostInterceptor(url, id); return httpPostInterceptor(url, id);
} }
export async function acceptAssignmentTa(id: any) {
const url = `assignment-expert/acceptance?id=${id}`;
return httpPostInterceptor(url, id);
}
export async function postFinishAcceptance(id: any) { export async function postFinishAcceptance(id: any) {
const url = `assignment/finish-acceptance?id=${id}`; const url = `assignment/finish-acceptance?id=${id}`;
return httpPostInterceptor(url, id); return httpPostInterceptor(url, id);
@ -128,6 +133,11 @@ export async function finishTask(id: any) {
return httpPostInterceptor(url); return httpPostInterceptor(url);
} }
export async function finishTaskTa(id: any) {
const url = `assignment-expert/finish?id=${id}`;
return httpPostInterceptor(url);
}
export async function listTaskTa( export async function listTaskTa(
page: any, page: any,
title: string = "", title: string = "",

View File

@ -181,7 +181,7 @@ const LoadScript = () => {
const script = document.createElement("script"); const script = document.createElement("script");
script.src = "https://cdn.userway.org/widget.js"; script.src = "https://cdn.userway.org/widget.js";
script.setAttribute("data-account", "X36s1DpjqB"); script.setAttribute("data-account", "X36s1DpjqB");
script.setAttribute("data-position", "3"); script.setAttribute("data-position", "5");
script.async = true; script.async = true;
document.head.appendChild(script); document.head.appendChild(script);