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 {
AdministrationLevelList,
checkRolePlacementsAvailability,
getListCompetencies,
getListExperiences,
saveUserInternal,
saveUserRolePlacements,
} from "@/service/management-user/management-user";
import { loading } from "@/config/swal";
import { error, loading } from "@/config/swal";
import { Eye, EyeOff } from "lucide-react";
const FormSchema = z.object({
@ -134,6 +135,26 @@ export default function AddExpertForm() {
};
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 resData = res?.data?.data;
const userProfileId = resData?.id;

View File

@ -49,11 +49,11 @@ const columns: ColumnDef<any>[] = [
header: "Jumlah Amplifikasi",
cell: ({ row }) => <span>{row.getValue("link")}</span>,
},
// {
// accessorKey: "status",
// header: "Status",
// cell: ({ row }) => <span>{row.getValue("status")}</span>,
// },
{
accessorKey: "status",
header: "Status",
cell: ({ row }) => <span>{row.getValue("status")}</span>,
},
{
accessorKey: "date",
header: "Tanggal Penarikan",
@ -77,12 +77,12 @@ const columns: ColumnDef<any>[] = [
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end">
<Link
href={`/admin/media-tracking/detail/${row.original.id}`}
>
<Link 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">
<Eye className="w-4 h-4 me-1.5" />
Detail
View
{row.original.mediaUpload.fileType.secondaryName &&
row.original.mediaUpload.fileType.secondaryName.toLowerCase()}
</DropdownMenuItem>
</Link>
</DropdownMenuContent>

View File

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

View File

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

View File

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

View File

@ -67,6 +67,16 @@ const columns: ColumnDef<any>[] = [
</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",
accessorKey: "action",

View File

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

View File

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

View File

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

View File

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

View File

@ -9,12 +9,7 @@ import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Calendar } from "@/components/ui/calendar";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
Book,
CheckCheck,
Plus,
Timer,
} from "lucide-react";
import { Book, CheckCheck, Plus, Timer } from "lucide-react";
import { Checkbox } from "@/components/ui/checkbox";
import { EventContentArg } from "@fullcalendar/core";
import EventModal from "./event-modal";
@ -124,7 +119,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
const [calendarEvents, setCalendarEvents] = useState<CalendarEvent[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);
const t = useTranslations("CalendarApp");
// Modal states
const [sheetOpen, setSheetOpen] = useState<boolean>(false);
const [date, setDate] = useState<Date>(new Date());
@ -171,7 +166,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
const monthData = res?.data?.data;
if (monthData) {
const allEvents: CalendarEvent[] = [];
// Map API data to the calendarEvents structure
// Map API data to the calendarEvents structure
const events = monthData?.map((event: any) => ({
id: event.id.toString(),
title: event.title,
@ -207,7 +202,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
"",
""
);
if (res?.error) {
error(res?.message || "Failed to fetch yearly events");
return;
@ -230,17 +225,20 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
const filteredEvents = calendarEvents.filter((event) => {
if (!selectedCategory.length) return false;
console.log("Event category : ", selectedCategory);
const eventCategories = event.extendedProps.calendar
?.split(",")
.map((val: string) => val.trim());
const allCategoryId = ["1", "2", "3", "4", "5"];
// 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) =>
selectedCategory.includes(cat) || (hasAllCategories && cat == "0")
return eventCategories?.some(
(cat: string) =>
selectedCategory.includes(cat) || (hasAllCategories && cat == "0")
);
});
@ -266,7 +264,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
};
const handleCategorySelection = (category: string) => {
setSelectedCategory(prev => {
setSelectedCategory((prev) => {
if (prev.includes(category)) {
return prev.filter((c) => c !== category);
} else {
@ -313,7 +311,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
const getEventColor = (type: EventType): string => {
const typeSplit = type.split(",");
const firstType = typeSplit[0] as EventType;
const colors: Record<EventType, string> = {
"0": "bg-black",
"1": "bg-yellow-500",
@ -322,7 +320,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
"4": "bg-orange-500",
"5": "bg-green-400",
};
return colors[firstType] || "bg-gray-400";
};
@ -346,15 +344,22 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
const renderEventContent = (eventInfo: any) => {
const { title } = eventInfo.event;
const { createdByName, isPublish, calendar } = eventInfo.event.extendedProps;
const { createdByName, isPublish, calendar } =
eventInfo.event.extendedProps;
const bgColor = getEventColor(calendar);
const colorList = getEventColorList(calendar);
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 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>
</div>
<p className="ml-1 text-xs text-start mt-2">
@ -386,7 +391,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
bgColor,
colorList,
}) => (
<div
<div
className={`w-full p-2 mb-2 rounded-md text-white text-sm flex justify-between items-stretch cursor-pointer hover:opacity-80 ${bgColor}`}
onClick={() => handleClickListItem(item)}
>
@ -461,8 +466,12 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
text={event.title}
createdBy={event.createdByName}
isPublish={event.isPublish}
bgColor={getEventColor(event.agendaType as EventType)}
colorList={getEventColorList(event.agendaType as EventType)}
bgColor={getEventColor(
event.agendaType as EventType
)}
colorList={getEventColorList(
event.agendaType as EventType
)}
/>
))}
</div>
@ -537,7 +546,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
{t("addEvent")}
</Button>
)}
{roleId === 3 && userLevelId === 216 && (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
@ -593,7 +602,9 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
className={category.className}
id={category.label}
checked={selectedCategory.includes(category.value)}
onCheckedChange={() => handleCategorySelection(category.value)}
onCheckedChange={() =>
handleCategorySelection(category.value)
}
/>
<Label htmlFor={category.label}>{category.label}</Label>
</li>
@ -605,7 +616,12 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
<Card className="col-span-12 lg:col-span-8 2xl:col-span-9 pt-5">
<CardContent className="dashcode-app-calendar">
<FullCalendar
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin, listPlugin]}
plugins={[
dayGridPlugin,
timeGridPlugin,
interactionPlugin,
listPlugin,
]}
headerToolbar={{
left: "prev,next today",
center: "title",
@ -636,7 +652,9 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
handleDateChange(info.view.currentStart, info.view.currentEnd);
handleViewChange(info.view.type);
}}
viewClassNames={activeView === "listYear" ? "hide-calendar-grid" : ""}
viewClassNames={
activeView === "listYear" ? "hide-calendar-grid" : ""
}
/>
{activeView === "listYear" && (
@ -657,7 +675,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
</CardContent>
</Card>
</div>
<EventModal
open={sheetOpen}
onClose={handleCloseModal}
@ -669,4 +687,4 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
);
};
export default CalendarView;
export default CalendarView;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -40,6 +40,7 @@ import { uploadThumbnailBlog } from "@/service/blog/blog";
import { Textarea } from "@/components/ui/textarea";
import {
generateDataArticle,
generateDataRewrite,
getDetailArticle,
getGenerateKeywords,
getGenerateTitle,
@ -55,6 +56,7 @@ import dynamic from "next/dynamic";
import { getCsrfToken } from "@/service/auth";
import { Link } from "@/i18n/routing";
import { useTranslations } from "next-intl";
import { useParams } from "next/navigation";
interface FileWithPreview extends File {
preview: string;
@ -82,6 +84,10 @@ export default function FormAudio() {
const router = useRouter();
const editor = useRef(null);
type AudioSchema = z.infer<typeof audioSchema>;
const params = useParams();
const locale = params?.locale;
const [selectedFileType, setSelectedFileType] = useState("original");
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId");
@ -97,6 +103,11 @@ export default function FormAudio() {
const [preview, setPreview] = useState<string | null>(null);
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 [title, setTitle] = useState<string>("");
const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>("");
@ -111,7 +122,6 @@ export default function FormAudio() {
null
);
const [selectedMainKeyword, setSelectedMainKeyword] = useState("");
const [selectedWritingStyle, setSelectedWritingStyle] = useState("");
const [selectedSize, setSelectedSize] = useState("");
const [detailData, setDetailData] = useState<any>(null);
const [articleImages, setArticleImages] = useState<string[]>([]);
@ -133,6 +143,8 @@ export default function FormAudio() {
const [isStartUpload, setIsStartUpload] = useState(false);
const [counterProgress, setCounterProgress] = useState(0);
const [isContentRewriteClicked, setIsContentRewriteClicked] = useState(false);
const [showRewriteEditor, setShowRewriteEditor] = useState(false);
const [files, setFiles] = useState<FileWithPreview[]>([]);
const [publishedFor, setPublishedFor] = useState<string[]>([]);
@ -155,15 +167,11 @@ export default function FormAudio() {
const audioSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
description: z
.string()
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." })
.or(
z.literal(articleBody || "").refine((val) => val.length > 0, {
message: "Deskripsi diperlukan.",
})
),
description: z.string().optional(),
descriptionOri: z.string().optional(), // Original editor
rewriteDescription: z.string().optional(), // Rewrite editor
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
// tags: z.string().min(1, { message: "Judul diperlukan" }),
});
@ -175,6 +183,11 @@ export default function FormAudio() {
formState: { errors },
} = useForm<AudioSchema>({
resolver: zodResolver(audioSchema),
defaultValues: {
description: "",
descriptionOri: "",
rewriteDescription: "",
},
});
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) => {
loading();
const finalTags = tags.join(", ");
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");
return;
}
@ -719,6 +744,45 @@ export default function FormAudio() {
}
}, [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 (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-col lg:flex-row gap-10">
@ -861,7 +925,7 @@ export default function FormAudio() {
placeholder="Enter Main Keyword"
/>
{/* )}
/> */}
/> */}
</div>
</div>
<div className="mt-5">
@ -911,7 +975,7 @@ export default function FormAudio() {
</div>
</div>
<div className="mt-5">
<Label>{t("special-instructions")} (Optional)</Label>
<Label>{t("special-instructions")}(Optional)</Label>
<div className="mt-3">
<Controller
control={control}
@ -940,7 +1004,7 @@ export default function FormAudio() {
</div>
{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) => (
<p
key={index}
@ -960,50 +1024,155 @@ export default function FormAudio() {
<div className="pt-3">
<div className="flex flex-row justify-between items-center">
{selectedArticleId && (
<Link
href={`/contributor/content/audio/update-seo/${selectedArticleId}`}
target="_blank"
rel="noopener noreferrer"
<Button
className="mb-2"
size="sm"
variant={"outline"}
color="primary"
onClick={() => {
const url = `/${locale}/contributor/content/image/update-seo/${selectedArticleId}`;
window.open(url, "_blank", "noopener,noreferrer");
}}
>
<Button
className="mb-2"
size="sm"
variant={"outline"}
color="primary"
>
{t("update")}
</Button>
</Link>
{t("update")}
</Button>
)}
</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 className="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={onChange}
initialData={articleBody || value}
{!isSwitchOn && (
<>
<RadioGroup
onValueChange={(value) => setSelectedFileType(value)}
value={selectedFileType}
className=" grid-cols-1"
>
<div className="">
<RadioGroupItem value="original" id="original-file" />
<Label htmlFor="original-file">
Select Original Description
</Label>
</div>
<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}
</p>
)}
</div>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
<p className="text-sm font-semibold">Content Rewrite</p>
<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">
<Label>{t("select-file")}</Label>
{/* <Input

View File

@ -40,6 +40,7 @@ import { uploadThumbnailBlog } from "@/service/blog/blog";
import { Textarea } from "@/components/ui/textarea";
import {
generateDataArticle,
generateDataRewrite,
getDetailArticle,
getGenerateKeywords,
getGenerateTitle,
@ -94,6 +95,7 @@ export default function FormImage() {
const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType");
const roleId = getCookiesDecrypt("urie");
const [selectedFileType, setSelectedFileType] = useState("original");
const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategory, setSelectedCategory] = useState<any>();
@ -116,7 +118,10 @@ export default function FormImage() {
null
);
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 [detailData, setDetailData] = useState<any>(null);
const [articleImages, setArticleImages] = useState<string[]>([]);
@ -125,6 +130,8 @@ export default function FormImage() {
const [content, setContent] = useState("");
const [isContentRewriteClicked, setIsContentRewriteClicked] = useState(false);
const [selectedTarget, setSelectedTarget] = useState("");
const [unitSelection, setUnitSelection] = useState({
allUnit: false,
@ -140,6 +147,7 @@ export default function FormImage() {
let uploadPersen = 0;
const [isStartUpload, setIsStartUpload] = useState(false);
const [counterProgress, setCounterProgress] = useState(0);
const [showRewriteEditor, setShowRewriteEditor] = useState(false);
const [files, setFiles] = useState<FileWithPreview[]>([]);
const [filesTemp, setFilesTemp] = useState<File[]>([]);
@ -164,16 +172,10 @@ export default function FormImage() {
const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
description: z
.string()
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." })
.or(
z.literal(articleBody || "").refine((val) => val.length > 0, {
message: "Deskripsi diperlukan.",
})
),
description: z.string().optional(),
descriptionOri: z.string().optional(),
rewriteDescription: z.string().optional(),
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
// tags: z.string().min(1, { message: "Judul diperlukan" }),
});
const {
@ -184,6 +186,11 @@ export default function FormImage() {
formState: { errors },
} = useForm<ImageSchema>({
resolver: zodResolver(imageSchema),
defaultValues: {
description: "",
descriptionOri: "",
rewriteDescription: "",
},
});
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) => {
loading();
const finalTags = tags.join(", ");
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");
return;
}
@ -716,6 +735,45 @@ export default function FormImage() {
}
}, [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 (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-col lg:flex-row gap-10">
@ -930,6 +988,7 @@ export default function FormImage() {
color="primary"
onClick={handleGenerateArtikel}
size="sm"
type="button"
>
Generate Article
</Button>
@ -972,32 +1031,139 @@ export default function FormImage() {
</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 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={onChange}
initialData={articleBody || value}
{!isSwitchOn && (
<>
<RadioGroup
onValueChange={(value) => setSelectedFileType(value)}
value={selectedFileType}
className=" grid-cols-1"
>
<div className="">
<RadioGroupItem value="original" id="original-file" />
<Label htmlFor="original-file">
Select Original Description
</Label>
</div>
<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}
</p>
)}
</div>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
<p className="text-sm font-semibold">Content Rewrite</p>
<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">
<Label>{t("select-file")}</Label>
{/* <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 Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { redirect, useRouter } from "next/navigation";
import { redirect, useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
@ -40,6 +40,7 @@ import { uploadThumbnailBlog } from "@/service/blog/blog";
import { Textarea } from "@/components/ui/textarea";
import {
generateDataArticle,
generateDataRewrite,
getDetailArticle,
getGenerateKeywords,
getGenerateTitle,
@ -83,11 +84,15 @@ export default function FormTeks() {
const editor = useRef(null);
type TeksSchema = z.infer<typeof teksSchema>;
const params = useParams();
const locale = params?.locale;
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType");
const roleId = getCookiesDecrypt("urie");
const [selectedFileType, setSelectedFileType] = useState("original");
const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategory, setSelectedCategory] = useState<any>();
@ -102,6 +107,10 @@ export default function FormTeks() {
const [editingArticleId, setEditingArticleId] = useState<string | null>(null);
const [isLoading, setIsLoading] = 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 [isGeneratedArticle, setIsGeneratedArticle] = useState(false);
@ -109,8 +118,9 @@ export default function FormTeks() {
const [selectedArticleId, setSelectedArticleId] = useState<string | null>(
null
);
const [isContentRewriteClicked, setIsContentRewriteClicked] = useState(false);
const [showRewriteEditor, setShowRewriteEditor] = useState(false);
const [selectedMainKeyword, setSelectedMainKeyword] = useState("");
const [selectedWritingStyle, setSelectedWritingStyle] = useState("");
const [selectedSize, setSelectedSize] = useState("");
const [detailData, setDetailData] = useState<any>(null);
const [articleImages, setArticleImages] = useState<string[]>([]);
@ -157,14 +167,9 @@ export default function FormTeks() {
const teksSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
description: z
.string()
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." })
.or(
z.literal(articleBody || "").refine((val) => val.length > 0, {
message: "Deskripsi diperlukan.",
})
),
description: z.string().optional(),
descriptionOri: z.string().optional(), // Original editor
rewriteDescription: z.string().optional(),
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
// tags: z.string().min(1, { message: "Judul diperlukan" }),
});
@ -177,6 +182,11 @@ export default function FormTeks() {
formState: { errors },
} = useForm<TeksSchema>({
resolver: zodResolver(teksSchema),
defaultValues: {
description: "",
descriptionOri: "",
rewriteDescription: "",
},
});
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) => {
loading();
const finalTags = tags.join(", ");
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");
return;
}
@ -709,6 +733,45 @@ export default function FormTeks() {
}
}, [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 (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-col lg:flex-row gap-10">
@ -851,7 +914,7 @@ export default function FormTeks() {
placeholder="Enter Main Keyword"
/>
{/* )}
/> */}
/> */}
</div>
</div>
<div className="mt-5">
@ -901,7 +964,7 @@ export default function FormTeks() {
</div>
</div>
<div className="mt-5">
<Label>{t("special-instructions")} (Optional)</Label>
<Label>{t("special-instructions")}(Optional)</Label>
<div className="mt-3">
<Controller
control={control}
@ -923,13 +986,14 @@ export default function FormTeks() {
color="primary"
onClick={handleGenerateArtikel}
size="sm"
type="button"
>
Generate Article
</Button>
</div>
{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) => (
<p
key={index}
@ -949,50 +1013,155 @@ export default function FormTeks() {
<div className="pt-3">
<div className="flex flex-row justify-between items-center">
{selectedArticleId && (
<Link
href={`/contributor/content/teks/update-seo/${selectedArticleId}`}
target="_blank"
rel="noopener noreferrer"
<Button
className="mb-2"
size="sm"
variant={"outline"}
color="primary"
onClick={() => {
const url = `/${locale}/contributor/content/image/update-seo/${selectedArticleId}`;
window.open(url, "_blank", "noopener,noreferrer");
}}
>
<Button
className="mb-2"
size="sm"
variant={"outline"}
color="primary"
>
{t("update")}
</Button>
</Link>
{t("update")}
</Button>
)}
</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 className="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={onChange}
initialData={articleBody || value}
{!isSwitchOn && (
<>
<RadioGroup
onValueChange={(value) => setSelectedFileType(value)}
value={selectedFileType}
className=" grid-cols-1"
>
<div className="">
<RadioGroupItem value="original" id="original-file" />
<Label htmlFor="original-file">
Select Original Description
</Label>
</div>
<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}
</p>
)}
</div>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
<p className="text-sm font-semibold">Content Rewrite</p>
<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">
<Label>{t("select-file")}</Label>
{/* <Input

View File

@ -16,7 +16,7 @@ import * as z from "zod";
import { Upload } from "tus-js-client";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { redirect, useRouter } from "next/navigation";
import { redirect, useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
@ -40,6 +40,7 @@ import { uploadThumbnailBlog } from "@/service/blog/blog";
import { Textarea } from "@/components/ui/textarea";
import {
generateDataArticle,
generateDataRewrite,
getDetailArticle,
getGenerateKeywords,
getGenerateTitle,
@ -82,12 +83,15 @@ export default function FormVideo() {
const router = useRouter();
const editor = useRef(null);
type VideoSchema = z.infer<typeof videoSchema>;
const params = useParams();
const locale = params?.locale;
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType");
const roleId = getCookiesDecrypt("urie");
const [selectedFileType, setSelectedFileType] = useState("original");
const t = useTranslations("Form");
const [categories, setCategories] = useState<Category[]>([]);
@ -97,6 +101,11 @@ export default function FormVideo() {
const [preview, setPreview] = useState<string | null>(null);
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 [title, setTitle] = useState<string>("");
const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>("");
@ -111,13 +120,17 @@ export default function FormVideo() {
null
);
const [selectedMainKeyword, setSelectedMainKeyword] = useState("");
const [selectedWritingStyle, setSelectedWritingStyle] = useState("");
// const [selectedWritingStyle, setSelectedWritingStyle] = useState("");
const [selectedSize, setSelectedSize] = useState("");
const [detailData, setDetailData] = useState<any>(null);
const [articleImages, setArticleImages] = useState<string[]>([]);
const [isSwitchOn, setIsSwitchOn] = useState<boolean>(false);
const inputRef = useRef<HTMLInputElement>(null);
const [showRewriteEditor, setShowRewriteEditor] = useState(false);
const [isContentRewriteClicked, setIsContentRewriteClicked] = useState(false);
const [selectedTarget, setSelectedTarget] = useState("");
const [unitSelection, setUnitSelection] = useState({
allUnit: false,
@ -155,14 +168,9 @@ export default function FormVideo() {
const videoSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
description: z
.string()
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." })
.or(
z.literal(articleBody || "").refine((val) => val.length > 0, {
message: "Deskripsi diperlukan.",
})
),
description: z.string().optional(),
descriptionOri: z.string().optional(), // Original editor
rewriteDescription: z.string().optional(),
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
// tags: z.string().min(1, { message: "Judul diperlukan" }),
});
@ -175,6 +183,11 @@ export default function FormVideo() {
formState: { errors },
} = useForm<VideoSchema>({
resolver: zodResolver(videoSchema),
defaultValues: {
description: "",
descriptionOri: "",
rewriteDescription: "",
},
});
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) => {
loading();
const finalTags = tags.join(", ");
const finalTitle = isSwitchOn ? title : data.title;
const finalDescription = articleBody || data.description;
if (!finalDescription.trim()) {
// const finalDescription = articleBody || data.description;
const finalDescription = isSwitchOn
? data.description
: selectedFileType === "rewrite"
? data.rewriteDescription
: data.descriptionOri;
if (!finalDescription?.trim()) {
MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
return;
}
let requestData: {
title: string;
description: string;
@ -726,6 +754,45 @@ export default function FormVideo() {
}
}, [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 (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-col lg:flex-row gap-10">
@ -867,7 +934,7 @@ export default function FormVideo() {
placeholder="Enter Main Keyword"
/>
{/* )}
/> */}
/> */}
</div>
</div>
<div className="mt-5">
@ -917,7 +984,7 @@ export default function FormVideo() {
</div>
</div>
<div className="mt-5">
<Label>{t("special-instructions")} (Optional)</Label>
<Label>{t("special-instructions")}(Optional)</Label>
<div className="mt-3">
<Controller
control={control}
@ -939,13 +1006,14 @@ export default function FormVideo() {
color="primary"
onClick={handleGenerateArtikel}
size="sm"
type="button"
>
Generate Article
</Button>
</div>
{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) => (
<p
key={index}
@ -965,50 +1033,155 @@ export default function FormVideo() {
<div className="pt-3">
<div className="flex flex-row justify-between items-center">
{selectedArticleId && (
<Link
href={`/contributor/content/video/update-seo/${selectedArticleId}`}
target="_blank"
rel="noopener noreferrer"
<Button
className="mb-2"
size="sm"
variant={"outline"}
color="primary"
onClick={() => {
const url = `/${locale}/contributor/content/image/update-seo/${selectedArticleId}`;
window.open(url, "_blank", "noopener,noreferrer");
}}
>
<Button
className="mb-2"
size="sm"
variant={"outline"}
color="primary"
>
{t("update")}
</Button>
</Link>
{t("update")}
</Button>
)}
</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 className="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={onChange}
initialData={articleBody || value}
{!isSwitchOn && (
<>
<RadioGroup
onValueChange={(value) => setSelectedFileType(value)}
value={selectedFileType}
className=" grid-cols-1"
>
<div className="">
<RadioGroupItem value="original" id="original-file" />
<Label htmlFor="original-file">
Select Original Description
</Label>
</div>
<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}
</p>
)}
</div>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
<p className="text-sm font-semibold">Content Rewrite</p>
<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">
<Label>{t("select-file")}</Label>
{/* <Input

View File

@ -202,6 +202,23 @@ export default function FormAskExpert() {
const details = response?.data?.data;
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();
@ -515,7 +532,7 @@ export default function FormAskExpert() {
return (
<Card>
<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 ? (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="gap-5 mb-5">
@ -617,7 +634,14 @@ export default function FormAskExpert() {
}
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>
</div>
))}
@ -625,6 +649,46 @@ export default function FormAskExpert() {
</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 className="mt-5 space-y-2">

View File

@ -636,7 +636,7 @@ export default function FormDoItYourself() {
return (
<Card>
<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 ? (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="gap-5 mb-5">

View File

@ -10,42 +10,23 @@ import * as z from "zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import JoditEditor from "jodit-react";
import {
acceptAssignment,
acceptAssignmentTa,
createAssignmentResponse,
createTask,
deleteAssignmentResponse,
deleteTask,
finishTask,
finishTaskTa,
getAcceptance,
getAcceptanceAssignmentStatus,
getAssignmentResponseList,
getMediaUpload,
getMediaUploadTa,
getTask,
getTaskTa,
getUserLevelForAssignments,
getUserLevelForExpert,
} from "@/service/task";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
ChevronDown,
ChevronUp,
Dock,
DotSquare,
ImageIcon,
@ -61,14 +42,20 @@ import { close, error, loading } from "@/lib/swal";
import { getCookiesDecrypt } from "@/lib/utils";
import { Avatar, AvatarImage } from "@/components/ui/avatar";
import { successCallback } from "@/config/swal";
import FileUploader from "../shared/file-uploader";
import { AudioRecorder } from "react-audio-voice-recorder";
import Image from "next/image";
import { Icon } from "@iconify/react/dist/iconify.js";
import WavesurferPlayer from "@wavesurfer/react";
import WaveSurfer from "wavesurfer.js";
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
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({
uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
@ -252,7 +239,11 @@ export default function FormTaskTaDetail() {
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 [dataTable, setDataTable] = React.useState<any[]>([]);
const [totalData, setTotalData] = React.useState<number>(1);
@ -327,37 +318,37 @@ export default function FormTaskTaDetail() {
// setPlatformTypeVisible(selectedValue === 2);
// };
const handleExpertiseOutputChange = (
key: keyof typeof expertise,
value: boolean
) => {
if (key === "semua") {
const newState = {
semua: value,
komunikasi: value,
hukum: value,
bahasa: value,
ekonomi: value,
politik: value,
sosiologi: value,
ilmuadministrasipemerintah: value,
ti: value,
};
setExpertiseOutput(newState);
} else {
const updated = {
...expertise,
[key]: value,
};
// const handleExpertiseOutputChange = (
// key: keyof typeof expertise,
// value: boolean
// ) => {
// if (key === "semua") {
// const newState = {
// semua: value,
// komunikasi: value,
// hukum: value,
// bahasa: value,
// ekonomi: value,
// politik: value,
// sosiologi: value,
// ilmuadministrasipemerintah: value,
// ti: value,
// };
// setExpertiseOutput(newState);
// } else {
// const updated = {
// ...expertise,
// [key]: value,
// };
const allChecked = ["video", "audio", "image", "text"].every(
(k) => updated[k as keyof typeof expertise]
);
// const allChecked = ["video", "audio", "image", "text"].every(
// (k) => updated[k as keyof typeof expertise]
// );
updated.semua = allChecked;
setExpertiseOutput(updated);
}
};
// updated.semua = allChecked;
// setExpertiseOutput(updated);
// }
// };
const handleExpertOutputChange = (
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(() => {
async function fetchPoldaPolres() {
setIsLoading(true);
@ -458,6 +511,16 @@ export default function FormTaskTaDetail() {
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;
setImageUploadedFiles(
attachment?.filter((file: any) => file.fileTypeId == 1)
@ -542,7 +605,7 @@ export default function FormTaskTaDetail() {
confirmButtonText: "OK",
}).then((result) => {
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 () => {
loading();
console.log("Id user :", userId);
const response = await acceptAssignment(id);
const response = await acceptAssignmentTa(id);
if (response?.error) {
error(response?.message);
@ -773,7 +836,7 @@ export default function FormTaskTaDetail() {
);
async function finishAssignment() {
const response = finishTask(id);
const response = finishTaskTa(id);
// if (response.error) {
// error(response.message);
@ -1011,48 +1074,98 @@ export default function FormTaskTaDetail() {
<div className="mt-5 space-y-2">
<Label>{t("areas-expertise")}</Label>
<div className="flex flex-wrap gap-4">
{Object.keys(expertise).map((key) => (
<div className="flex items-center gap-2" key={key}>
{userCompetencies?.map((item: any) => (
<div className="flex items-center gap-2" key={item.id}>
<Checkbox
id={key}
checked={expertise[key as keyof typeof expertise]}
onCheckedChange={(value) =>
handleExpertiseOutputChange(
key as keyof typeof expertise,
value as boolean
)
}
id={`comp-${item.id}`}
checked={selectedCompetencies.has(item.id)}
onCheckedChange={() => handleCompetencyChange(item.id)}
disabled
/>
<Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
<Label htmlFor={`comp-${item.id}`}>{item.name}</Label>
</div>
))}
</div>
</div>
<div className="mt-5 space-y-2">
<Label>{t("choose-expert")}</Label>
{/* <Label>{t("choose-expert")}</Label>
<div className="flex flex-wrap gap-4">
{Object.keys(expert).map((key) => (
<div className="flex items-center gap-2" key={key}>
<Checkbox
id={key}
checked={expert[key as keyof typeof expert]}
onCheckedChange={(value) =>
handleExpertOutputChange(
key as keyof typeof expert,
value as boolean
)
}
/>
<Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
<Dialog>
<DialogTrigger asChild>
<Button variant="soft" size="sm" color="primary">
[{"Pilih Tenaga Ahli"}]
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
<DialogHeader>
<DialogTitle>Daftar Tenaga Ahli</DialogTitle>
</DialogHeader>
<div className="grid grid-cols-2 gap-2 max-h-[400px] overflow-y-auto">
{listExpert?.map((expert: any) => (
<div key={expert.id} className="border p-2">
<Label className="flex items-center">
<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>

View File

@ -26,6 +26,7 @@ import {
getTask,
getTaskTa,
getUserLevelForAssignments,
getUserLevelForExpert,
} from "@/service/task";
import {
Dialog,
@ -40,6 +41,7 @@ import {
Dock,
ImageIcon,
Music,
Trash2,
VideoIcon,
} from "lucide-react";
import FileUploader from "../shared/file-uploader";
@ -52,6 +54,7 @@ import { Upload } from "tus-js-client";
import { error, loading } from "@/lib/swal";
import dynamic from "next/dynamic";
import { useTranslations } from "next-intl";
import { getListCompetencies } from "@/service/management-user/management-user";
const taskSchema = z.object({
// 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 [type, setType] = useState<string>("1");
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 [urlInputs, setUrlInputs] = useState<Url[]>([]);
const [refresh] = useState(false);
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 [isLoading, setIsLoading] = useState(false);
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(() => {
async function fetchPoldaPolres() {
setIsLoading(true);
@ -285,12 +357,22 @@ export default function FormTaskTaEdit() {
}
if (details?.assignedToLevel) {
const levels = new Set(
details.assignedToLevel.split(",").map(Number)
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));
}
const attachment = details?.files;
setImageUploadedFiles(
attachment?.filter((file: any) => file.fileTypeId == 1)
@ -376,7 +458,7 @@ export default function FormTaskTaEdit() {
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
router.push("/en/contributor/task");
router.push("/en/contributor/task-ta");
}
});
};
@ -393,9 +475,9 @@ export default function FormTaskTaEdit() {
});
};
const handlePoldaPolresChange = () => {
return Array.from(checkedLevels).join(","); // Mengonversi Set ke string
};
// const handlePoldaPolresChange = () => {
// return Array.from(checkedLevels).join(","); // Mengonversi Set ke string
// };
const handleUnitChange = (
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 fileTypeMapping = {
all: "1",
@ -483,16 +569,11 @@ export default function FormTaskTaEdit() {
const requestData: {
id?: any;
title: string;
assignedToLevel: any;
assignedToUsers: any;
assignmentTypeId: string;
fileTypeOutput: string;
narration: string;
platformType: string | null;
assignmentMainTypeId: any;
assignmentType: string;
assignedToRole: string;
broadcastType: string;
expertCompetencies: string;
attachmentUrl: string[];
} = {
@ -500,17 +581,12 @@ export default function FormTaskTaEdit() {
// assignmentType,
// assignmentCategory,
id: detail?.id || null,
assignedToLevel: handlePoldaPolresChange(),
assignedToUsers: assignmentPurposeString,
assignedToUsers: handleExpertChange(),
assignedToRole: selectedTarget,
assignmentType: taskType,
broadcastType: broadcastType,
assignmentMainTypeId: mainType,
assignmentTypeId: type,
fileTypeOutput: selectedOutputs,
narration: data.naration,
platformType: "",
expertCompetencies: "1,2,3",
expertCompetencies: Array.from(selectedCompetencies).join(","),
title: data.title,
attachmentUrl: links,
};
@ -808,47 +884,97 @@ export default function FormTaskTaEdit() {
<div className="mt-5 space-y-2">
<Label>{t("areas-expertise")}</Label>
<div className="flex flex-wrap gap-4">
{Object.keys(expertise).map((key) => (
<div className="flex items-center gap-2" key={key}>
{userCompetencies?.map((item: any) => (
<div className="flex items-center gap-2" key={item.id}>
<Checkbox
id={key}
checked={expertise[key as keyof typeof expertise]}
onCheckedChange={(value) =>
handleExpertiseOutputChange(
key as keyof typeof expertise,
value as boolean
)
}
id={`comp-${item.id}`}
checked={selectedCompetencies.has(item.id)}
onCheckedChange={() => handleCompetencyChange(item.id)}
/>
<Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
<Label htmlFor={`comp-${item.id}`}>{item.name}</Label>
</div>
))}
</div>
</div>
<div className="mt-5 space-y-2">
<Label>{t("choose-expert")}</Label>
<div className="flex flex-wrap gap-4">
{Object.keys(expert).map((key) => (
<div className="flex items-center gap-2" key={key}>
<Checkbox
id={key}
checked={expert[key as keyof typeof expert]}
onCheckedChange={(value) =>
handleExpertOutputChange(
key as keyof typeof expert,
value as boolean
)
}
/>
<Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
</div>
))}
<Dialog>
<DialogTrigger asChild>
<Button variant="soft" size="sm" color="primary">
[{"Pilih Tenaga Ahli"}]
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
<DialogHeader>
<DialogTitle>Daftar Tenaga Ahli</DialogTitle>
</DialogHeader>
<div className="grid grid-cols-2 gap-2 max-h-[400px] overflow-y-auto">
{listExpert?.map((expert: any) => (
<div key={expert.id} className="border p-2">
<Label className="flex items-center">
<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 className="mt-5 space-y-2">
@ -1079,13 +1205,42 @@ export default function FormTaskTaEdit() {
/>
</div>
))}
<button
type="button"
className="mt-4 bg-green-500 text-white px-4 py-2 rounded"
onClick={handleAddLink}
>
{t("add-links")}
</button>
<div className="mt-4 space-y-2">
<Label className="">{t("news-links")}</Label>
{links.map((link, index) => (
<div
key={index}
className="flex items-center gap-2 mt-2"
>
<Input
type="url"
className="border rounded p-2 w-full"
placeholder={`Masukkan link berita ${index + 1}`}
value={link}
onChange={(e) =>
handleLinkChange(index, e.target.value)
}
/>
{links.length > 1 && (
<button
type="button"
className="bg-red-500 text-white px-3 py-1 rounded"
onClick={() => handleRemoveRow(index)}
>
<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>

View File

@ -81,7 +81,7 @@ const ContentCategory = (props: { group?: string; type: string }) => {
alt="category"
width={2560}
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"
/>

View File

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

View File

@ -71,22 +71,22 @@ export default function DashboardVisualization() {
setIsInternational(updatedIsInternational);
};
useEffect(() => {
async function fetchUrl() {
console.log("Fetch tableau");
const urlView = `${url + ticket1}/${view1}${param}`;
console.log("Fetch tableau ", urlView);
const urlRender = await fetch(urlView)
.then((response) => {
console.log("Tableau res : ", response);
})
.catch((error) => {
console.log("Tableau error: ", error);
});
}
// useEffect(() => {
// async function fetchUrl() {
// console.log("Fetch tableau");
// const urlView = `${url + ticket1}/${view1}${param}`;
// console.log("Fetch tableau ", urlView);
// const urlRender = await fetch(urlView)
// .then((response) => {
// console.log("Tableau res : ", response);
// })
// .catch((error) => {
// console.log("Tableau error: ", error);
// });
// }
fetchUrl();
}, [ticket1]);
// fetchUrl();
// }, [ticket1]);
return (
<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: "",
id: "contest",
menus: [
{
id: "contest",
href: "/shared/contest",
label: t("contest"),
active: pathname.includes("/contest"),
icon: "ic:outline-emoji-events",
submenus: [],
},
],
},
// {
// groupLabel: "",
// id: "contest",
// menus: [
// {
// id: "contest",
// href: "/shared/contest",
// label: t("contest"),
// active: pathname.includes("/contest"),
// icon: "ic:outline-emoji-events",
// submenus: [],
// },
// ],
// },
{
groupLabel: "",
id: "communication",
@ -3896,16 +3896,16 @@ export function getMenuList(pathname: string, t: any): Group[] {
icon: "material-symbols:map-search-outline",
submenus: [
{
href: "/admin/media-tracking/media-online",
label: "Media Online",
active: pathname === "/media-tracking/media-online",
href: "/admin/media-tracking/tracking-berita",
label: "Tracking Beritra",
active: pathname === "/admin/media-tracking/tracking-berita",
icon: "heroicons:arrow-trending-up",
children: [],
},
{
href: "/admin/media-tracking/tb-news",
label: "Tracking Berita Hari Ini",
active: pathname === "/media-tracking/news",
href: "/admin/media-tracking/results",
label: "Hasil",
active: pathname === "/admin/media-tracking/results",
icon: "heroicons:arrow-trending-up",
children: [],
},
@ -4079,20 +4079,20 @@ export function getMenuList(pathname: string, t: any): Group[] {
},
],
},
// {
// groupLabel: "",
// id: "performance-satker",
// menus: [
// {
// id: "performance-polres",
// href: "/admin/performance-satker",
// label: t("performance-satker"),
// active: pathname.includes("/admin/performance-satker"),
// icon: "ant-design:signal-filled",
// submenus: [],
// },
// ],
// },
{
groupLabel: "",
id: "performance-satker",
menus: [
{
id: "performance-polres",
href: "/admin/performance-satker",
label: t("performance-satker"),
active: pathname.includes("/admin/performance-satker"),
icon: "ant-design:signal-filled",
submenus: [],
},
],
},
{
groupLabel: "",
id: "media-tracking",
@ -4105,16 +4105,16 @@ export function getMenuList(pathname: string, t: any): Group[] {
icon: "material-symbols:map-search-outline",
submenus: [
{
href: "/admin/media-tracking/media-online",
label: "Media Online",
active: pathname === "/media-tracking/media-online",
href: "/admin/media-tracking/tracking-berita",
label: "Tracking Beritra",
active: pathname === "/admin/media-tracking/tracking-berita",
icon: "heroicons:arrow-trending-up",
children: [],
},
{
href: "/admin/media-tracking/tb-news",
label: "Tracking Berita Hari Ini",
active: pathname === "/media-tracking/news",
href: "/admin/media-tracking/results",
label: "Hasil",
active: pathname === "/admin/media-tracking/results",
icon: "heroicons:arrow-trending-up",
children: [],
},

View File

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

View File

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

View File

@ -152,7 +152,7 @@ export async function getTagsBySubCategoryId(subCategory: 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);
}

View File

@ -46,6 +46,11 @@ export async function saveUserInternal(data: any) {
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) {
const url = "users/role-placements";
return httpPostInterceptor(url, data);

View File

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

View File

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