Merge branch 'dev-anang' of https://gitlab.com/hanifsalafi/mediahub_redesign
This commit is contained in:
commit
199aaf5808
|
|
@ -9,12 +9,7 @@ import { Button } from "@/components/ui/button";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Calendar } from "@/components/ui/calendar";
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import {
|
import { Book, CheckCheck, Plus, Timer } from "lucide-react";
|
||||||
Book,
|
|
||||||
CheckCheck,
|
|
||||||
Plus,
|
|
||||||
Timer,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { EventContentArg } from "@fullcalendar/core";
|
import { EventContentArg } from "@fullcalendar/core";
|
||||||
import EventModal from "./event-modal";
|
import EventModal from "./event-modal";
|
||||||
|
|
@ -171,7 +166,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
const monthData = res?.data?.data;
|
const monthData = res?.data?.data;
|
||||||
if (monthData) {
|
if (monthData) {
|
||||||
const allEvents: CalendarEvent[] = [];
|
const allEvents: CalendarEvent[] = [];
|
||||||
// Map API data to the calendarEvents structure
|
// Map API data to the calendarEvents structure
|
||||||
const events = monthData?.map((event: any) => ({
|
const events = monthData?.map((event: any) => ({
|
||||||
id: event.id.toString(),
|
id: event.id.toString(),
|
||||||
title: event.title,
|
title: event.title,
|
||||||
|
|
@ -237,10 +232,13 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
|
|
||||||
const allCategoryId = ["1", "2", "3", "4", "5"];
|
const allCategoryId = ["1", "2", "3", "4", "5"];
|
||||||
// Cek apakah SEMUA validTypeIds ada di typeIdsInData
|
// Cek apakah SEMUA validTypeIds ada di typeIdsInData
|
||||||
const hasAllCategories = allCategoryId.every(categoryId => selectedCategory.includes(categoryId));
|
const hasAllCategories = allCategoryId.every((categoryId) =>
|
||||||
|
selectedCategory.includes(categoryId)
|
||||||
|
);
|
||||||
|
|
||||||
return eventCategories?.some((cat: string) =>
|
return eventCategories?.some(
|
||||||
selectedCategory.includes(cat) || (hasAllCategories && cat == "0")
|
(cat: string) =>
|
||||||
|
selectedCategory.includes(cat) || (hasAllCategories && cat == "0")
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -266,7 +264,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCategorySelection = (category: string) => {
|
const handleCategorySelection = (category: string) => {
|
||||||
setSelectedCategory(prev => {
|
setSelectedCategory((prev) => {
|
||||||
if (prev.includes(category)) {
|
if (prev.includes(category)) {
|
||||||
return prev.filter((c) => c !== category);
|
return prev.filter((c) => c !== category);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -346,15 +344,22 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
|
|
||||||
const renderEventContent = (eventInfo: any) => {
|
const renderEventContent = (eventInfo: any) => {
|
||||||
const { title } = eventInfo.event;
|
const { title } = eventInfo.event;
|
||||||
const { createdByName, isPublish, calendar } = eventInfo.event.extendedProps;
|
const { createdByName, isPublish, calendar } =
|
||||||
|
eventInfo.event.extendedProps;
|
||||||
const bgColor = getEventColor(calendar);
|
const bgColor = getEventColor(calendar);
|
||||||
const colorList = getEventColorList(calendar);
|
const colorList = getEventColorList(calendar);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`w-full p-2 mb-2 rounded-md text-white text-sm flex justify-between items-stretch ${bgColor}`}>
|
<div
|
||||||
|
className={`w-full p-2 mb-2 rounded-md text-white text-sm flex justify-between items-stretch ${bgColor}`}
|
||||||
|
>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex flex-row items-center">
|
<div className="flex flex-row items-center">
|
||||||
{isPublish === true ? <CheckCheck size={15} /> : <Timer size={15} />}
|
{isPublish === true ? (
|
||||||
|
<CheckCheck size={15} />
|
||||||
|
) : (
|
||||||
|
<Timer size={15} />
|
||||||
|
)}
|
||||||
<p className="ml-1">{title}</p>
|
<p className="ml-1">{title}</p>
|
||||||
</div>
|
</div>
|
||||||
<p className="ml-1 text-xs text-start mt-2">
|
<p className="ml-1 text-xs text-start mt-2">
|
||||||
|
|
@ -461,8 +466,12 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
text={event.title}
|
text={event.title}
|
||||||
createdBy={event.createdByName}
|
createdBy={event.createdByName}
|
||||||
isPublish={event.isPublish}
|
isPublish={event.isPublish}
|
||||||
bgColor={getEventColor(event.agendaType as EventType)}
|
bgColor={getEventColor(
|
||||||
colorList={getEventColorList(event.agendaType as EventType)}
|
event.agendaType as EventType
|
||||||
|
)}
|
||||||
|
colorList={getEventColorList(
|
||||||
|
event.agendaType as EventType
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -593,7 +602,9 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
className={category.className}
|
className={category.className}
|
||||||
id={category.label}
|
id={category.label}
|
||||||
checked={selectedCategory.includes(category.value)}
|
checked={selectedCategory.includes(category.value)}
|
||||||
onCheckedChange={() => handleCategorySelection(category.value)}
|
onCheckedChange={() =>
|
||||||
|
handleCategorySelection(category.value)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Label htmlFor={category.label}>{category.label}</Label>
|
<Label htmlFor={category.label}>{category.label}</Label>
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -605,7 +616,12 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
<Card className="col-span-12 lg:col-span-8 2xl:col-span-9 pt-5">
|
<Card className="col-span-12 lg:col-span-8 2xl:col-span-9 pt-5">
|
||||||
<CardContent className="dashcode-app-calendar">
|
<CardContent className="dashcode-app-calendar">
|
||||||
<FullCalendar
|
<FullCalendar
|
||||||
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin, listPlugin]}
|
plugins={[
|
||||||
|
dayGridPlugin,
|
||||||
|
timeGridPlugin,
|
||||||
|
interactionPlugin,
|
||||||
|
listPlugin,
|
||||||
|
]}
|
||||||
headerToolbar={{
|
headerToolbar={{
|
||||||
left: "prev,next today",
|
left: "prev,next today",
|
||||||
center: "title",
|
center: "title",
|
||||||
|
|
@ -636,7 +652,9 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
handleDateChange(info.view.currentStart, info.view.currentEnd);
|
handleDateChange(info.view.currentStart, info.view.currentEnd);
|
||||||
handleViewChange(info.view.type);
|
handleViewChange(info.view.type);
|
||||||
}}
|
}}
|
||||||
viewClassNames={activeView === "listYear" ? "hide-calendar-grid" : ""}
|
viewClassNames={
|
||||||
|
activeView === "listYear" ? "hide-calendar-grid" : ""
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{activeView === "listYear" && (
|
{activeView === "listYear" && (
|
||||||
|
|
|
||||||
|
|
@ -148,10 +148,12 @@ const EventModal = ({
|
||||||
satker: false,
|
satker: false,
|
||||||
international: false,
|
international: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [agendaType, setAgendaType] = React.useState(""); // State untuk agendaType
|
const [agendaType, setAgendaType] = React.useState(""); // State untuk agendaType
|
||||||
const [selectedPolda, setSelectedPolda] = React.useState([]); // Untuk data Polda
|
const [selectedPolda, setSelectedPolda] = useState<string[]>([]);
|
||||||
const [selectedSatker, setSelectedSatker] = React.useState([]);
|
const [selectedPolres, setSelectedPolres] = useState<string[]>([]);
|
||||||
const [selectedPolres, setSelectedPolres] = React.useState([]);
|
const [selectedSatker, setSelectedSatker] = useState<string[]>([]);
|
||||||
|
const isDetailMode = true;
|
||||||
const [wavesurfer, setWavesurfer] = useState<WaveSurfer>();
|
const [wavesurfer, setWavesurfer] = useState<WaveSurfer>();
|
||||||
const [isPlaying, setIsPlaying] = useState(false);
|
const [isPlaying, setIsPlaying] = useState(false);
|
||||||
const [isPublishing, setIsPublishing] = useState(false);
|
const [isPublishing, setIsPublishing] = useState(false);
|
||||||
|
|
@ -174,10 +176,7 @@ const EventModal = ({
|
||||||
const detail = res?.data?.data;
|
const detail = res?.data?.data;
|
||||||
setDetailData(detail);
|
setDetailData(detail);
|
||||||
|
|
||||||
const description = res?.data?.data?.description;
|
const description = detail?.description;
|
||||||
console.log("description", res?.data?.data?.description);
|
|
||||||
|
|
||||||
// Set nilai awal description ke form control
|
|
||||||
if (description) {
|
if (description) {
|
||||||
setValue("description", description);
|
setValue("description", description);
|
||||||
}
|
}
|
||||||
|
|
@ -196,17 +195,56 @@ const EventModal = ({
|
||||||
attachments?.filter((file: any) => file.fileTypeId == 4)
|
attachments?.filter((file: any) => file.fileTypeId == 4)
|
||||||
);
|
);
|
||||||
|
|
||||||
const agendaType = detail?.agendaType;
|
const rawAgendaTypes = detail?.agendaType?.split(",") || []; // ["0","1","2","3","4","5"]
|
||||||
setWilayahPublish({
|
const assignedToLevel = detail?.assignedToLevel?.split(",") || [];
|
||||||
semua: agendaType === "all",
|
|
||||||
nasional: agendaType === "mabes",
|
|
||||||
polda: agendaType === "polda",
|
|
||||||
polres: agendaType === "polres",
|
|
||||||
satker: agendaType === "satker",
|
|
||||||
international: agendaType === "international",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const wilayahState = {
|
||||||
|
semua: false,
|
||||||
|
nasional: false,
|
||||||
|
polda: false,
|
||||||
|
polres: false,
|
||||||
|
satker: false,
|
||||||
|
international: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
rawAgendaTypes.forEach((type: any) => {
|
||||||
|
switch (type) {
|
||||||
|
case "0":
|
||||||
|
wilayahState.semua = true;
|
||||||
|
break;
|
||||||
|
case "1":
|
||||||
|
wilayahState.nasional = true;
|
||||||
|
break;
|
||||||
|
case "2":
|
||||||
|
wilayahState.polda = true;
|
||||||
|
break;
|
||||||
|
case "3":
|
||||||
|
wilayahState.polres = true;
|
||||||
|
break;
|
||||||
|
case "4":
|
||||||
|
wilayahState.satker = true;
|
||||||
|
break;
|
||||||
|
case "5":
|
||||||
|
wilayahState.international = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setWilayahPublish(wilayahState);
|
||||||
|
|
||||||
|
// Atur unit berdasarkan agendaType
|
||||||
|
if (rawAgendaTypes.includes("2")) {
|
||||||
|
setSelectedPolda(assignedToLevel);
|
||||||
|
}
|
||||||
|
if (rawAgendaTypes.includes("3")) {
|
||||||
|
setSelectedPolres(assignedToLevel);
|
||||||
|
}
|
||||||
|
if (rawAgendaTypes.includes("4")) {
|
||||||
|
setSelectedSatker(assignedToLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
fetchDetailData();
|
fetchDetailData();
|
||||||
}, [event, setValue]);
|
}, [event, setValue]);
|
||||||
|
|
||||||
|
|
@ -233,33 +271,39 @@ const EventModal = ({
|
||||||
|
|
||||||
const toggleWilayah = (key: string) => {
|
const toggleWilayah = (key: string) => {
|
||||||
setWilayahPublish((prev: any) => {
|
setWilayahPublish((prev: any) => {
|
||||||
const newState = { ...prev, [key]: !prev[key] };
|
let newState = { ...prev };
|
||||||
|
|
||||||
// Handle "semua" logic to check all options
|
// Jika key === semua dan sebelumnya belum aktif, aktifkan semua
|
||||||
if (key === "semua" && newState.semua) {
|
if (key === "semua") {
|
||||||
setAgendaType("all");
|
const newChecked = !prev.semua;
|
||||||
return {
|
newState = {
|
||||||
semua: true,
|
semua: newChecked,
|
||||||
nasional: true,
|
nasional: newChecked,
|
||||||
polda: true,
|
polda: newChecked,
|
||||||
polres: true,
|
polres: newChecked,
|
||||||
satker: true,
|
satker: newChecked,
|
||||||
international: true,
|
international: newChecked,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (newChecked) {
|
||||||
|
setAgendaType("0,1,2,3,4,5");
|
||||||
|
} else {
|
||||||
|
setAgendaType("");
|
||||||
|
}
|
||||||
|
|
||||||
|
return newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Uncheck "semua" if any other option is selected
|
// Jika key bukan "semua"
|
||||||
if (key !== "semua") {
|
newState[key] = !prev[key];
|
||||||
newState.semua = false;
|
newState.semua = false; // Uncheck "semua" jika yang dipilih adalah individu
|
||||||
}
|
|
||||||
|
|
||||||
// Set agendaType based on the selected checkbox
|
// Hitung ulang agendaType berdasarkan pilihan
|
||||||
if (newState.nasional) setAgendaType("mabes");
|
const selectedKeys = Object.entries(newState)
|
||||||
else if (newState.polda) setAgendaType("polda");
|
.filter(([k, v]) => v && k !== "semua")
|
||||||
else if (newState.polres) setAgendaType("polres");
|
.map(([k]) => wilayahValueMap[k]);
|
||||||
else if (newState.satker) setAgendaType("satker");
|
|
||||||
else if (newState.international) setAgendaType("international");
|
setAgendaType(selectedKeys.join(","));
|
||||||
else setAgendaType(""); // Reset if no checkbox is selected
|
|
||||||
|
|
||||||
return newState;
|
return newState;
|
||||||
});
|
});
|
||||||
|
|
@ -269,12 +313,25 @@ const EventModal = ({
|
||||||
const agendaTypeList: string[] = [];
|
const agendaTypeList: string[] = [];
|
||||||
const assignedToLevelList: string[] = [];
|
const assignedToLevelList: string[] = [];
|
||||||
|
|
||||||
// Mapping dari checkbox wilayah ke agendaType
|
// // Mapping dari checkbox wilayah ke agendaType
|
||||||
Object.keys(wilayahPublish).forEach((key) => {
|
// Object.keys(wilayahPublish).forEach((key) => {
|
||||||
if (wilayahPublish[key as keyof typeof wilayahPublish]) {
|
// if (wilayahPublish[key as keyof typeof wilayahPublish]) {
|
||||||
agendaTypeList.push(wilayahValueMap[key]);
|
// agendaTypeList.push(wilayahValueMap[key]);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
|
if (wilayahPublish.semua) {
|
||||||
|
agendaTypeList.push("0", "1", "2", "3", "4", "5");
|
||||||
|
} else {
|
||||||
|
Object.keys(wilayahPublish).forEach((key) => {
|
||||||
|
if (
|
||||||
|
wilayahPublish[key as keyof typeof wilayahPublish] &&
|
||||||
|
key !== "semua"
|
||||||
|
) {
|
||||||
|
agendaTypeList.push(wilayahValueMap[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Unit-unit berdasarkan wilayah yang aktif
|
// Unit-unit berdasarkan wilayah yang aktif
|
||||||
if (wilayahPublish.polda && selectedPolda.length > 0) {
|
if (wilayahPublish.polda && selectedPolda.length > 0) {
|
||||||
|
|
@ -558,6 +615,7 @@ const EventModal = ({
|
||||||
confirmButtonText: "OK",
|
confirmButtonText: "OK",
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
router.push(redirect);
|
router.push(redirect);
|
||||||
|
window.location.reload();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -784,7 +842,8 @@ const EventModal = ({
|
||||||
{wilayahPublish.polda && (
|
{wilayahPublish.polda && (
|
||||||
<UnitMapping
|
<UnitMapping
|
||||||
unit="Polda"
|
unit="Polda"
|
||||||
isDetail={false}
|
isDetail={isDetailMode} // jika Anda punya kondisi detail
|
||||||
|
initData={selectedPolda}
|
||||||
sendDataToParent={(data: any) =>
|
sendDataToParent={(data: any) =>
|
||||||
setSelectedPolda(data)
|
setSelectedPolda(data)
|
||||||
}
|
}
|
||||||
|
|
@ -802,8 +861,9 @@ const EventModal = ({
|
||||||
</label>
|
</label>
|
||||||
{wilayahPublish.polres && (
|
{wilayahPublish.polres && (
|
||||||
<UnitMapping
|
<UnitMapping
|
||||||
isDetail={false}
|
|
||||||
unit="Polres"
|
unit="Polres"
|
||||||
|
isDetail={isDetailMode}
|
||||||
|
initData={selectedPolres}
|
||||||
sendDataToParent={(data: any) =>
|
sendDataToParent={(data: any) =>
|
||||||
setSelectedPolres(data)
|
setSelectedPolres(data)
|
||||||
}
|
}
|
||||||
|
|
@ -821,8 +881,9 @@ const EventModal = ({
|
||||||
</label>
|
</label>
|
||||||
{wilayahPublish.satker && (
|
{wilayahPublish.satker && (
|
||||||
<UnitMapping
|
<UnitMapping
|
||||||
isDetail={false}
|
|
||||||
unit="Satker"
|
unit="Satker"
|
||||||
|
isDetail={isDetailMode}
|
||||||
|
initData={selectedSatker}
|
||||||
sendDataToParent={(data: any) =>
|
sendDataToParent={(data: any) =>
|
||||||
setSelectedSatker(data)
|
setSelectedSatker(data)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -202,6 +202,23 @@ export default function FormAskExpert() {
|
||||||
const details = response?.data?.data;
|
const details = response?.data?.data;
|
||||||
|
|
||||||
setDetail(details);
|
setDetail(details);
|
||||||
|
|
||||||
|
if (details?.assignedToLevel) {
|
||||||
|
const levels: Set<number> = new Set(
|
||||||
|
details.assignedToLevel.split(",").map((x: any) => Number(x))
|
||||||
|
);
|
||||||
|
setCheckedLevels(levels);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (details?.assignedToUsers) {
|
||||||
|
const userIds = details.assignedToUsers.split(",").map(Number);
|
||||||
|
setCheckedLevels(new Set(userIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (details?.expertCompetencies) {
|
||||||
|
const compIds = details.expertCompetencies.split(",").map(Number);
|
||||||
|
setSelectedCompetencies(new Set(compIds));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initState();
|
initState();
|
||||||
|
|
@ -515,7 +532,7 @@ export default function FormAskExpert() {
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<div className="px-6 py-6">
|
<div className="px-6 py-6">
|
||||||
<p className="text-lg font-semibold mb-3">{t("form-task")}</p>
|
<p className="text-lg font-semibold mb-3">{t("form-task-ta")}</p>
|
||||||
{detail !== undefined ? (
|
{detail !== undefined ? (
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="gap-5 mb-5">
|
<div className="gap-5 mb-5">
|
||||||
|
|
@ -617,7 +634,14 @@ export default function FormAskExpert() {
|
||||||
}
|
}
|
||||||
className="mr-3"
|
className="mr-3"
|
||||||
/>
|
/>
|
||||||
{expert.fullname}
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="font-bold">
|
||||||
|
{expert.fullname}
|
||||||
|
</div>
|
||||||
|
<div className="italic">
|
||||||
|
({expert.username})
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
@ -625,6 +649,46 @@ export default function FormAskExpert() {
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
|
{checkedLevels.size > 0 && (
|
||||||
|
<div className="mt-3">
|
||||||
|
<Label className="text-sm text-gray-600 mb-2 block">
|
||||||
|
Tenaga Ahli Terpilih ({checkedLevels.size})
|
||||||
|
</Label>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{Array.from(checkedLevels).map((expertId) => {
|
||||||
|
const expert = listExpert?.find(
|
||||||
|
(exp: any) => exp.id === expertId
|
||||||
|
);
|
||||||
|
return expert ? (
|
||||||
|
<div
|
||||||
|
key={expert.id}
|
||||||
|
className="inline-flex items-center gap-2 bg-blue-100 text-blue-800 text-sm font-medium px-3 py-1.5 rounded-full border border-blue-200"
|
||||||
|
>
|
||||||
|
<span>{expert.fullname}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleCheckboxChange(expert.id)}
|
||||||
|
className="ml-1 text-blue-600 hover:text-blue-800 hover:bg-blue-200 rounded-full p-0.5 transition-colors"
|
||||||
|
title="Remove expert"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-3 h-3"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-5 space-y-2">
|
<div className="mt-5 space-y-2">
|
||||||
|
|
|
||||||
|
|
@ -636,7 +636,7 @@ export default function FormDoItYourself() {
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<div className="px-6 py-6">
|
<div className="px-6 py-6">
|
||||||
<p className="text-lg font-semibold mb-3">{t("form-task")}</p>
|
<p className="text-lg font-semibold mb-3">{t("form-task-ta-do")}</p>
|
||||||
{detail !== undefined ? (
|
{detail !== undefined ? (
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="gap-5 mb-5">
|
<div className="gap-5 mb-5">
|
||||||
|
|
|
||||||
|
|
@ -10,42 +10,23 @@ import * as z from "zod";
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
import withReactContent from "sweetalert2-react-content";
|
import withReactContent from "sweetalert2-react-content";
|
||||||
import { useParams, useRouter } from "next/navigation";
|
import { useParams, useRouter } from "next/navigation";
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
||||||
import JoditEditor from "jodit-react";
|
|
||||||
import {
|
import {
|
||||||
acceptAssignment,
|
acceptAssignmentTa,
|
||||||
createAssignmentResponse,
|
createAssignmentResponse,
|
||||||
createTask,
|
|
||||||
deleteAssignmentResponse,
|
deleteAssignmentResponse,
|
||||||
deleteTask,
|
deleteTask,
|
||||||
finishTask,
|
finishTaskTa,
|
||||||
getAcceptance,
|
getAcceptance,
|
||||||
getAcceptanceAssignmentStatus,
|
getAcceptanceAssignmentStatus,
|
||||||
getAssignmentResponseList,
|
getAssignmentResponseList,
|
||||||
getMediaUpload,
|
getMediaUpload,
|
||||||
getMediaUploadTa,
|
getMediaUploadTa,
|
||||||
getTask,
|
|
||||||
getTaskTa,
|
getTaskTa,
|
||||||
getUserLevelForAssignments,
|
getUserLevelForAssignments,
|
||||||
|
getUserLevelForExpert,
|
||||||
} from "@/service/task";
|
} from "@/service/task";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import {
|
|
||||||
ChevronDown,
|
|
||||||
ChevronUp,
|
|
||||||
Dock,
|
Dock,
|
||||||
DotSquare,
|
DotSquare,
|
||||||
ImageIcon,
|
ImageIcon,
|
||||||
|
|
@ -61,14 +42,20 @@ import { close, error, loading } from "@/lib/swal";
|
||||||
import { getCookiesDecrypt } from "@/lib/utils";
|
import { getCookiesDecrypt } from "@/lib/utils";
|
||||||
import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
||||||
import { successCallback } from "@/config/swal";
|
import { successCallback } from "@/config/swal";
|
||||||
import FileUploader from "../shared/file-uploader";
|
|
||||||
import { AudioRecorder } from "react-audio-voice-recorder";
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
import WavesurferPlayer from "@wavesurfer/react";
|
import WavesurferPlayer from "@wavesurfer/react";
|
||||||
import WaveSurfer from "wavesurfer.js";
|
import WaveSurfer from "wavesurfer.js";
|
||||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { getListCompetencies } from "@/service/management-user/management-user";
|
||||||
|
|
||||||
const taskSchema = z.object({
|
const taskSchema = z.object({
|
||||||
uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
|
uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -252,7 +239,11 @@ export default function FormTaskTaDetail() {
|
||||||
const [acceptAcceptance, setAcceptAcceptance] = useState<AcceptanceData[]>(
|
const [acceptAcceptance, setAcceptAcceptance] = useState<AcceptanceData[]>(
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
const [listExpert, setListExpert] = useState<any[]>([]);
|
||||||
|
const [userCompetencies, setUserCompetencies] = useState<any[]>([]);
|
||||||
|
const [selectedCompetencies, setSelectedCompetencies] = useState<Set<number>>(
|
||||||
|
new Set()
|
||||||
|
);
|
||||||
const [totalPage, setTotalPage] = React.useState(1);
|
const [totalPage, setTotalPage] = React.useState(1);
|
||||||
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
||||||
const [totalData, setTotalData] = React.useState<number>(1);
|
const [totalData, setTotalData] = React.useState<number>(1);
|
||||||
|
|
@ -327,37 +318,37 @@ export default function FormTaskTaDetail() {
|
||||||
// setPlatformTypeVisible(selectedValue === 2);
|
// setPlatformTypeVisible(selectedValue === 2);
|
||||||
// };
|
// };
|
||||||
|
|
||||||
const handleExpertiseOutputChange = (
|
// const handleExpertiseOutputChange = (
|
||||||
key: keyof typeof expertise,
|
// key: keyof typeof expertise,
|
||||||
value: boolean
|
// value: boolean
|
||||||
) => {
|
// ) => {
|
||||||
if (key === "semua") {
|
// if (key === "semua") {
|
||||||
const newState = {
|
// const newState = {
|
||||||
semua: value,
|
// semua: value,
|
||||||
komunikasi: value,
|
// komunikasi: value,
|
||||||
hukum: value,
|
// hukum: value,
|
||||||
bahasa: value,
|
// bahasa: value,
|
||||||
ekonomi: value,
|
// ekonomi: value,
|
||||||
politik: value,
|
// politik: value,
|
||||||
sosiologi: value,
|
// sosiologi: value,
|
||||||
ilmuadministrasipemerintah: value,
|
// ilmuadministrasipemerintah: value,
|
||||||
ti: value,
|
// ti: value,
|
||||||
};
|
// };
|
||||||
setExpertiseOutput(newState);
|
// setExpertiseOutput(newState);
|
||||||
} else {
|
// } else {
|
||||||
const updated = {
|
// const updated = {
|
||||||
...expertise,
|
// ...expertise,
|
||||||
[key]: value,
|
// [key]: value,
|
||||||
};
|
// };
|
||||||
|
|
||||||
const allChecked = ["video", "audio", "image", "text"].every(
|
// const allChecked = ["video", "audio", "image", "text"].every(
|
||||||
(k) => updated[k as keyof typeof expertise]
|
// (k) => updated[k as keyof typeof expertise]
|
||||||
);
|
// );
|
||||||
|
|
||||||
updated.semua = allChecked;
|
// updated.semua = allChecked;
|
||||||
setExpertiseOutput(updated);
|
// setExpertiseOutput(updated);
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
const handleExpertOutputChange = (
|
const handleExpertOutputChange = (
|
||||||
key: keyof typeof expert,
|
key: keyof typeof expert,
|
||||||
|
|
@ -383,6 +374,68 @@ export default function FormTaskTaDetail() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getDataAdditional();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function getDataAdditional() {
|
||||||
|
const resCompetencies = await getListCompetencies();
|
||||||
|
console.log("competency", resCompetencies);
|
||||||
|
setUserCompetencies(resCompetencies?.data?.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function fetchListExpert() {
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await getUserLevelForExpert(id);
|
||||||
|
setListExpert(response?.data?.data);
|
||||||
|
console.log("tenaga ahli", response?.data?.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching Polda/Polres data:", error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchListExpert();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchExpertsForCompetencies = async () => {
|
||||||
|
const allExperts: any[] = [];
|
||||||
|
|
||||||
|
for (const compId of Array.from(selectedCompetencies)) {
|
||||||
|
const response = await getUserLevelForExpert(compId);
|
||||||
|
const experts = response?.data?.data || [];
|
||||||
|
allExperts.push(...experts);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueExperts = Array.from(
|
||||||
|
new Map(allExperts.map((e) => [e.id, e])).values()
|
||||||
|
);
|
||||||
|
|
||||||
|
setListExpert(uniqueExperts);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (selectedCompetencies.size > 0) {
|
||||||
|
fetchExpertsForCompetencies();
|
||||||
|
} else {
|
||||||
|
setListExpert([]);
|
||||||
|
}
|
||||||
|
}, [selectedCompetencies]);
|
||||||
|
|
||||||
|
const handleCompetencyChange = async (competencyId: number) => {
|
||||||
|
setSelectedCompetencies((prev) => {
|
||||||
|
const updated = new Set(prev);
|
||||||
|
if (updated.has(competencyId)) {
|
||||||
|
updated.delete(competencyId);
|
||||||
|
} else {
|
||||||
|
updated.add(competencyId);
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchPoldaPolres() {
|
async function fetchPoldaPolres() {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
@ -458,6 +511,16 @@ export default function FormTaskTaDetail() {
|
||||||
setCheckedLevels(levels);
|
setCheckedLevels(levels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (details?.assignedToUsers) {
|
||||||
|
const userIds = details.assignedToUsers.split(",").map(Number);
|
||||||
|
setCheckedLevels(new Set(userIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (details?.expertCompetencies) {
|
||||||
|
const compIds = details.expertCompetencies.split(",").map(Number);
|
||||||
|
setSelectedCompetencies(new Set(compIds));
|
||||||
|
}
|
||||||
|
|
||||||
const attachment = details?.files;
|
const attachment = details?.files;
|
||||||
setImageUploadedFiles(
|
setImageUploadedFiles(
|
||||||
attachment?.filter((file: any) => file.fileTypeId == 1)
|
attachment?.filter((file: any) => file.fileTypeId == 1)
|
||||||
|
|
@ -542,7 +605,7 @@ export default function FormTaskTaDetail() {
|
||||||
confirmButtonText: "OK",
|
confirmButtonText: "OK",
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
router.push("/en/contributor/task");
|
router.push("/en/contributor/task-ta");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -682,7 +745,7 @@ export default function FormTaskTaDetail() {
|
||||||
const handleAcceptAcceptance = async () => {
|
const handleAcceptAcceptance = async () => {
|
||||||
loading();
|
loading();
|
||||||
console.log("Id user :", userId);
|
console.log("Id user :", userId);
|
||||||
const response = await acceptAssignment(id);
|
const response = await acceptAssignmentTa(id);
|
||||||
|
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
error(response?.message);
|
error(response?.message);
|
||||||
|
|
@ -773,7 +836,7 @@ export default function FormTaskTaDetail() {
|
||||||
);
|
);
|
||||||
|
|
||||||
async function finishAssignment() {
|
async function finishAssignment() {
|
||||||
const response = finishTask(id);
|
const response = finishTaskTa(id);
|
||||||
|
|
||||||
// if (response.error) {
|
// if (response.error) {
|
||||||
// error(response.message);
|
// error(response.message);
|
||||||
|
|
@ -1011,48 +1074,98 @@ export default function FormTaskTaDetail() {
|
||||||
<div className="mt-5 space-y-2">
|
<div className="mt-5 space-y-2">
|
||||||
<Label>{t("areas-expertise")}</Label>
|
<Label>{t("areas-expertise")}</Label>
|
||||||
<div className="flex flex-wrap gap-4">
|
<div className="flex flex-wrap gap-4">
|
||||||
{Object.keys(expertise).map((key) => (
|
{userCompetencies?.map((item: any) => (
|
||||||
<div className="flex items-center gap-2" key={key}>
|
<div className="flex items-center gap-2" key={item.id}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id={key}
|
id={`comp-${item.id}`}
|
||||||
checked={expertise[key as keyof typeof expertise]}
|
checked={selectedCompetencies.has(item.id)}
|
||||||
onCheckedChange={(value) =>
|
onCheckedChange={() => handleCompetencyChange(item.id)}
|
||||||
handleExpertiseOutputChange(
|
|
||||||
key as keyof typeof expertise,
|
|
||||||
value as boolean
|
|
||||||
)
|
|
||||||
}
|
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
<Label htmlFor={key}>
|
<Label htmlFor={`comp-${item.id}`}>{item.name}</Label>
|
||||||
{key.charAt(0).toUpperCase() + key.slice(1)}
|
|
||||||
</Label>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-5 space-y-2">
|
<div className="mt-5 space-y-2">
|
||||||
<Label>{t("choose-expert")}</Label>
|
{/* <Label>{t("choose-expert")}</Label>
|
||||||
<div className="flex flex-wrap gap-4">
|
<div className="flex flex-wrap gap-4">
|
||||||
{Object.keys(expert).map((key) => (
|
<Dialog>
|
||||||
<div className="flex items-center gap-2" key={key}>
|
<DialogTrigger asChild>
|
||||||
<Checkbox
|
<Button variant="soft" size="sm" color="primary">
|
||||||
id={key}
|
[{"Pilih Tenaga Ahli"}]
|
||||||
checked={expert[key as keyof typeof expert]}
|
</Button>
|
||||||
onCheckedChange={(value) =>
|
</DialogTrigger>
|
||||||
handleExpertOutputChange(
|
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
|
||||||
key as keyof typeof expert,
|
<DialogHeader>
|
||||||
value as boolean
|
<DialogTitle>Daftar Tenaga Ahli</DialogTitle>
|
||||||
)
|
</DialogHeader>
|
||||||
}
|
<div className="grid grid-cols-2 gap-2 max-h-[400px] overflow-y-auto">
|
||||||
/>
|
{listExpert?.map((expert: any) => (
|
||||||
<Label htmlFor={key}>
|
<div key={expert.id} className="border p-2">
|
||||||
{key.charAt(0).toUpperCase() + key.slice(1)}
|
<Label className="flex items-center">
|
||||||
</Label>
|
<Checkbox
|
||||||
|
checked={checkedLevels.has(expert.id)}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleCheckboxChange(expert.id)
|
||||||
|
}
|
||||||
|
className="mr-3"
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="font-bold">
|
||||||
|
{expert.fullname}
|
||||||
|
</div>
|
||||||
|
<div className="italic">
|
||||||
|
({expert.username})
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</div> */}
|
||||||
|
{checkedLevels.size > 0 && (
|
||||||
|
<div className="mt-3">
|
||||||
|
<Label className="text-sm text-gray-600 mb-2 block">
|
||||||
|
Tenaga Ahli Terpilih ({checkedLevels.size})
|
||||||
|
</Label>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{Array.from(checkedLevels).map((expertId) => {
|
||||||
|
const expert = listExpert?.find(
|
||||||
|
(exp: any) => exp.id === expertId
|
||||||
|
);
|
||||||
|
return expert ? (
|
||||||
|
<div
|
||||||
|
key={expert.id}
|
||||||
|
className="inline-flex items-center gap-2 bg-blue-100 text-blue-800 text-sm font-medium px-3 py-1.5 rounded-full border border-blue-200"
|
||||||
|
>
|
||||||
|
<span>{expert.fullname}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleCheckboxChange(expert.id)}
|
||||||
|
className="ml-1 text-blue-600 hover:text-blue-800 hover:bg-blue-200 rounded-full p-0.5 transition-colors"
|
||||||
|
title="Remove expert"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-3 h-3"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
))}
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div></div>
|
<div></div>
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import {
|
||||||
getTask,
|
getTask,
|
||||||
getTaskTa,
|
getTaskTa,
|
||||||
getUserLevelForAssignments,
|
getUserLevelForAssignments,
|
||||||
|
getUserLevelForExpert,
|
||||||
} from "@/service/task";
|
} from "@/service/task";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
|
|
@ -40,6 +41,7 @@ import {
|
||||||
Dock,
|
Dock,
|
||||||
ImageIcon,
|
ImageIcon,
|
||||||
Music,
|
Music,
|
||||||
|
Trash2,
|
||||||
VideoIcon,
|
VideoIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import FileUploader from "../shared/file-uploader";
|
import FileUploader from "../shared/file-uploader";
|
||||||
|
|
@ -52,6 +54,7 @@ import { Upload } from "tus-js-client";
|
||||||
import { error, loading } from "@/lib/swal";
|
import { error, loading } from "@/lib/swal";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
import { getListCompetencies } from "@/service/management-user/management-user";
|
||||||
|
|
||||||
const taskSchema = z.object({
|
const taskSchema = z.object({
|
||||||
// uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
|
// uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -148,11 +151,18 @@ export default function FormTaskTaEdit() {
|
||||||
const [broadcastType, setBroadcastType] = useState<string>(""); // untuk Tipe Penugasan
|
const [broadcastType, setBroadcastType] = useState<string>(""); // untuk Tipe Penugasan
|
||||||
const [type, setType] = useState<string>("1");
|
const [type, setType] = useState<string>("1");
|
||||||
const [selectedTarget, setSelectedTarget] = useState("3,4");
|
const [selectedTarget, setSelectedTarget] = useState("3,4");
|
||||||
|
|
||||||
|
const [listExpert, setListExpert] = useState<any[]>([]);
|
||||||
|
const [userCompetencies, setUserCompetencies] = useState<any[]>([]);
|
||||||
|
const [selectedCompetencies, setSelectedCompetencies] = useState<Set<number>>(
|
||||||
|
new Set()
|
||||||
|
);
|
||||||
|
|
||||||
const [detail, setDetail] = useState<taskDetail>();
|
const [detail, setDetail] = useState<taskDetail>();
|
||||||
const [urlInputs, setUrlInputs] = useState<Url[]>([]);
|
const [urlInputs, setUrlInputs] = useState<Url[]>([]);
|
||||||
const [refresh] = useState(false);
|
const [refresh] = useState(false);
|
||||||
const [listDest, setListDest] = useState([]); // Data Polda dan Polres
|
const [listDest, setListDest] = useState([]); // Data Polda dan Polres
|
||||||
const [checkedLevels, setCheckedLevels] = useState(new Set());
|
const [checkedLevels, setCheckedLevels] = useState<Set<number>>(new Set());
|
||||||
const [expandedPolda, setExpandedPolda] = useState([{}]);
|
const [expandedPolda, setExpandedPolda] = useState([{}]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [audioFile, setAudioFile] = useState<File | null>(null);
|
const [audioFile, setAudioFile] = useState<File | null>(null);
|
||||||
|
|
@ -247,6 +257,68 @@ export default function FormTaskTaEdit() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getDataAdditional();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function getDataAdditional() {
|
||||||
|
const resCompetencies = await getListCompetencies();
|
||||||
|
console.log("competency", resCompetencies);
|
||||||
|
setUserCompetencies(resCompetencies?.data?.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function fetchListExpert() {
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await getUserLevelForExpert(id);
|
||||||
|
setListExpert(response?.data?.data);
|
||||||
|
console.log("tenaga ahli", response?.data?.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching Polda/Polres data:", error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchListExpert();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchExpertsForCompetencies = async () => {
|
||||||
|
const allExperts: any[] = [];
|
||||||
|
|
||||||
|
for (const compId of Array.from(selectedCompetencies)) {
|
||||||
|
const response = await getUserLevelForExpert(compId);
|
||||||
|
const experts = response?.data?.data || [];
|
||||||
|
allExperts.push(...experts);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueExperts = Array.from(
|
||||||
|
new Map(allExperts.map((e) => [e.id, e])).values()
|
||||||
|
);
|
||||||
|
|
||||||
|
setListExpert(uniqueExperts);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (selectedCompetencies.size > 0) {
|
||||||
|
fetchExpertsForCompetencies();
|
||||||
|
} else {
|
||||||
|
setListExpert([]);
|
||||||
|
}
|
||||||
|
}, [selectedCompetencies]);
|
||||||
|
|
||||||
|
const handleCompetencyChange = async (competencyId: number) => {
|
||||||
|
setSelectedCompetencies((prev) => {
|
||||||
|
const updated = new Set(prev);
|
||||||
|
if (updated.has(competencyId)) {
|
||||||
|
updated.delete(competencyId);
|
||||||
|
} else {
|
||||||
|
updated.add(competencyId);
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchPoldaPolres() {
|
async function fetchPoldaPolres() {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
@ -285,12 +357,22 @@ export default function FormTaskTaEdit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details?.assignedToLevel) {
|
if (details?.assignedToLevel) {
|
||||||
const levels = new Set(
|
const levels: Set<number> = new Set(
|
||||||
details.assignedToLevel.split(",").map(Number)
|
details.assignedToLevel.split(",").map((x: any) => Number(x))
|
||||||
);
|
);
|
||||||
setCheckedLevels(levels);
|
setCheckedLevels(levels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (details?.assignedToUsers) {
|
||||||
|
const userIds = details.assignedToUsers.split(",").map(Number);
|
||||||
|
setCheckedLevels(new Set(userIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (details?.expertCompetencies) {
|
||||||
|
const compIds = details.expertCompetencies.split(",").map(Number);
|
||||||
|
setSelectedCompetencies(new Set(compIds));
|
||||||
|
}
|
||||||
|
|
||||||
const attachment = details?.files;
|
const attachment = details?.files;
|
||||||
setImageUploadedFiles(
|
setImageUploadedFiles(
|
||||||
attachment?.filter((file: any) => file.fileTypeId == 1)
|
attachment?.filter((file: any) => file.fileTypeId == 1)
|
||||||
|
|
@ -376,7 +458,7 @@ export default function FormTaskTaEdit() {
|
||||||
confirmButtonText: "OK",
|
confirmButtonText: "OK",
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
router.push("/en/contributor/task");
|
router.push("/en/contributor/task-ta");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -393,9 +475,9 @@ export default function FormTaskTaEdit() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePoldaPolresChange = () => {
|
// const handlePoldaPolresChange = () => {
|
||||||
return Array.from(checkedLevels).join(","); // Mengonversi Set ke string
|
// return Array.from(checkedLevels).join(","); // Mengonversi Set ke string
|
||||||
};
|
// };
|
||||||
|
|
||||||
const handleUnitChange = (
|
const handleUnitChange = (
|
||||||
key: keyof typeof unitSelection,
|
key: keyof typeof unitSelection,
|
||||||
|
|
@ -454,6 +536,10 @@ export default function FormTaskTaEdit() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleExpertChange = () => {
|
||||||
|
return Array.from(checkedLevels).join(",");
|
||||||
|
};
|
||||||
|
|
||||||
const save = async (data: TaskSchema) => {
|
const save = async (data: TaskSchema) => {
|
||||||
const fileTypeMapping = {
|
const fileTypeMapping = {
|
||||||
all: "1",
|
all: "1",
|
||||||
|
|
@ -483,16 +569,11 @@ export default function FormTaskTaEdit() {
|
||||||
const requestData: {
|
const requestData: {
|
||||||
id?: any;
|
id?: any;
|
||||||
title: string;
|
title: string;
|
||||||
assignedToLevel: any;
|
|
||||||
assignedToUsers: any;
|
assignedToUsers: any;
|
||||||
assignmentTypeId: string;
|
assignmentTypeId: string;
|
||||||
fileTypeOutput: string;
|
|
||||||
narration: string;
|
narration: string;
|
||||||
platformType: string | null;
|
|
||||||
assignmentMainTypeId: any;
|
|
||||||
assignmentType: string;
|
assignmentType: string;
|
||||||
assignedToRole: string;
|
assignedToRole: string;
|
||||||
broadcastType: string;
|
|
||||||
expertCompetencies: string;
|
expertCompetencies: string;
|
||||||
attachmentUrl: string[];
|
attachmentUrl: string[];
|
||||||
} = {
|
} = {
|
||||||
|
|
@ -500,17 +581,12 @@ export default function FormTaskTaEdit() {
|
||||||
// assignmentType,
|
// assignmentType,
|
||||||
// assignmentCategory,
|
// assignmentCategory,
|
||||||
id: detail?.id || null,
|
id: detail?.id || null,
|
||||||
assignedToLevel: handlePoldaPolresChange(),
|
assignedToUsers: handleExpertChange(),
|
||||||
assignedToUsers: assignmentPurposeString,
|
|
||||||
assignedToRole: selectedTarget,
|
assignedToRole: selectedTarget,
|
||||||
assignmentType: taskType,
|
assignmentType: taskType,
|
||||||
broadcastType: broadcastType,
|
|
||||||
assignmentMainTypeId: mainType,
|
|
||||||
assignmentTypeId: type,
|
assignmentTypeId: type,
|
||||||
fileTypeOutput: selectedOutputs,
|
|
||||||
narration: data.naration,
|
narration: data.naration,
|
||||||
platformType: "",
|
expertCompetencies: Array.from(selectedCompetencies).join(","),
|
||||||
expertCompetencies: "1,2,3",
|
|
||||||
title: data.title,
|
title: data.title,
|
||||||
attachmentUrl: links,
|
attachmentUrl: links,
|
||||||
};
|
};
|
||||||
|
|
@ -808,47 +884,97 @@ export default function FormTaskTaEdit() {
|
||||||
<div className="mt-5 space-y-2">
|
<div className="mt-5 space-y-2">
|
||||||
<Label>{t("areas-expertise")}</Label>
|
<Label>{t("areas-expertise")}</Label>
|
||||||
<div className="flex flex-wrap gap-4">
|
<div className="flex flex-wrap gap-4">
|
||||||
{Object.keys(expertise).map((key) => (
|
{userCompetencies?.map((item: any) => (
|
||||||
<div className="flex items-center gap-2" key={key}>
|
<div className="flex items-center gap-2" key={item.id}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id={key}
|
id={`comp-${item.id}`}
|
||||||
checked={expertise[key as keyof typeof expertise]}
|
checked={selectedCompetencies.has(item.id)}
|
||||||
onCheckedChange={(value) =>
|
onCheckedChange={() => handleCompetencyChange(item.id)}
|
||||||
handleExpertiseOutputChange(
|
|
||||||
key as keyof typeof expertise,
|
|
||||||
value as boolean
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<Label htmlFor={key}>
|
<Label htmlFor={`comp-${item.id}`}>{item.name}</Label>
|
||||||
{key.charAt(0).toUpperCase() + key.slice(1)}
|
|
||||||
</Label>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-5 space-y-2">
|
<div className="mt-5 space-y-2">
|
||||||
<Label>{t("choose-expert")}</Label>
|
<Label>{t("choose-expert")}</Label>
|
||||||
<div className="flex flex-wrap gap-4">
|
<div className="flex flex-wrap gap-4">
|
||||||
{Object.keys(expert).map((key) => (
|
<Dialog>
|
||||||
<div className="flex items-center gap-2" key={key}>
|
<DialogTrigger asChild>
|
||||||
<Checkbox
|
<Button variant="soft" size="sm" color="primary">
|
||||||
id={key}
|
[{"Pilih Tenaga Ahli"}]
|
||||||
checked={expert[key as keyof typeof expert]}
|
</Button>
|
||||||
onCheckedChange={(value) =>
|
</DialogTrigger>
|
||||||
handleExpertOutputChange(
|
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
|
||||||
key as keyof typeof expert,
|
<DialogHeader>
|
||||||
value as boolean
|
<DialogTitle>Daftar Tenaga Ahli</DialogTitle>
|
||||||
)
|
</DialogHeader>
|
||||||
}
|
<div className="grid grid-cols-2 gap-2 max-h-[400px] overflow-y-auto">
|
||||||
/>
|
{listExpert?.map((expert: any) => (
|
||||||
<Label htmlFor={key}>
|
<div key={expert.id} className="border p-2">
|
||||||
{key.charAt(0).toUpperCase() + key.slice(1)}
|
<Label className="flex items-center">
|
||||||
</Label>
|
<Checkbox
|
||||||
</div>
|
checked={checkedLevels.has(expert.id)}
|
||||||
))}
|
onCheckedChange={() =>
|
||||||
|
handleCheckboxChange(expert.id)
|
||||||
|
}
|
||||||
|
className="mr-3"
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="font-bold">
|
||||||
|
{expert.fullname}
|
||||||
|
</div>
|
||||||
|
<div className="italic">
|
||||||
|
({expert.username})
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
|
{checkedLevels.size > 0 && (
|
||||||
|
<div className="mt-3">
|
||||||
|
<Label className="text-sm text-gray-600 mb-2 block">
|
||||||
|
Tenaga Ahli Terpilih ({checkedLevels.size})
|
||||||
|
</Label>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{Array.from(checkedLevels).map((expertId) => {
|
||||||
|
const expert = listExpert?.find(
|
||||||
|
(exp: any) => exp.id === expertId
|
||||||
|
);
|
||||||
|
return expert ? (
|
||||||
|
<div
|
||||||
|
key={expert.id}
|
||||||
|
className="inline-flex items-center gap-2 bg-blue-100 text-blue-800 text-sm font-medium px-3 py-1.5 rounded-full border border-blue-200"
|
||||||
|
>
|
||||||
|
<span>{expert.fullname}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleCheckboxChange(expert.id)}
|
||||||
|
className="ml-1 text-blue-600 hover:text-blue-800 hover:bg-blue-200 rounded-full p-0.5 transition-colors"
|
||||||
|
title="Remove expert"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-3 h-3"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-5 space-y-2">
|
<div className="mt-5 space-y-2">
|
||||||
|
|
@ -1079,13 +1205,42 @@ export default function FormTaskTaEdit() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<button
|
<div className="mt-4 space-y-2">
|
||||||
type="button"
|
<Label className="">{t("news-links")}</Label>
|
||||||
className="mt-4 bg-green-500 text-white px-4 py-2 rounded"
|
{links.map((link, index) => (
|
||||||
onClick={handleAddLink}
|
<div
|
||||||
>
|
key={index}
|
||||||
{t("add-links")}
|
className="flex items-center gap-2 mt-2"
|
||||||
</button>
|
>
|
||||||
|
<Input
|
||||||
|
type="url"
|
||||||
|
className="border rounded p-2 w-full"
|
||||||
|
placeholder={`Masukkan link berita ${index + 1}`}
|
||||||
|
value={link}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleLinkChange(index, e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{links.length > 1 && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="bg-red-500 text-white px-3 py-1 rounded"
|
||||||
|
onClick={() => handleRemoveRow(index)}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
className="mt-2 bg-blue-500 text-white px-4 py-2 rounded"
|
||||||
|
onClick={handleAddRow}
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
{t("add-links")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -784,6 +784,8 @@
|
||||||
"assignment-type": "Assignment Type",
|
"assignment-type": "Assignment Type",
|
||||||
"description-task": "Description Task",
|
"description-task": "Description Task",
|
||||||
"form-task": "Form Task",
|
"form-task": "Form Task",
|
||||||
|
"form-task-ta": "Form Ask the Expert",
|
||||||
|
"form-task-ta-do": "Form Do it yourself",
|
||||||
"assignment-selection": "Assignment Recipient",
|
"assignment-selection": "Assignment Recipient",
|
||||||
"custom": "Costum",
|
"custom": "Costum",
|
||||||
"assigment-type": "Assigment Type",
|
"assigment-type": "Assigment Type",
|
||||||
|
|
|
||||||
|
|
@ -571,47 +571,33 @@
|
||||||
"divisionNews": "Berita Satker",
|
"divisionNews": "Berita Satker",
|
||||||
"areaCoverage": "Liputan Wilayah & Satker",
|
"areaCoverage": "Liputan Wilayah & Satker",
|
||||||
"calendar": "KALENDER ACARA",
|
"calendar": "KALENDER ACARA",
|
||||||
"january": "Januari",
|
"january": "Januari",
|
||||||
"february": "February",
|
"february": "February",
|
||||||
"march": "March",
|
"march": "March",
|
||||||
"may": "May",
|
"may": "May",
|
||||||
"june": "Juni",
|
"june": "Juni",
|
||||||
"july": "Juli",
|
"july": "Juli",
|
||||||
"august": "Agustus",
|
"august": "Agustus",
|
||||||
"october": "Oktober",
|
"october": "Oktober",
|
||||||
"december": "Desember",
|
"december": "Desember",
|
||||||
"eventList": "Daftar Acara",
|
"eventList": "Daftar Acara",
|
||||||
"noEvent": "Tidak ada acara yang tersedia",
|
"noEvent": "Tidak ada acara yang tersedia",
|
||||||
"eventDetails": "Detail Acara",
|
"eventDetails": "Detail Acara",
|
||||||
"date": "Tanggal:",
|
"date": "Tanggal:",
|
||||||
"selectEvent": "Pilih acara untuk melihat detail",
|
"selectEvent": "Pilih acara untuk melihat detail",
|
||||||
"regionalPolice": "Polda Jajaran",
|
"regionalPolice": "Polda Jajaran",
|
||||||
"policeDivision": "Satuan Kerja Polri",
|
"policeDivision": "Satuan Kerja Polri",
|
||||||
"close": "Tutup",
|
"close": "Tutup",
|
||||||
"survey1": "SURVEI KEPUASAN PENGGUNA MEDIAHUB POLRI",
|
"survey1": "SURVEI KEPUASAN PENGGUNA MEDIAHUB POLRI",
|
||||||
"survey2": "Kami menghargai pendapat Anda! Survei ini bertujuan untuk meningkatkan kualitas layanan MediaHub Polri. Mohon luangkan waktu beberapa menit untuk mengisi survei ini.",
|
"survey2": "Kami menghargai pendapat Anda! Survei ini bertujuan untuk meningkatkan kualitas layanan MediaHub Polri. Mohon luangkan waktu beberapa menit untuk mengisi survei ini.",
|
||||||
"survey3": "SURVEY SEKARANG",
|
"survey3": "SURVEY SEKARANG",
|
||||||
"survey4": "SURVEI KEPUASAN PENGGUNA MEDIAHUB POLRI",
|
"survey4": "SURVEI KEPUASAN PENGGUNA MEDIAHUB POLRI",
|
||||||
"survey5": "Kami menghargai pendapat Anda! Survei ini bertujuan untuk meningkatkan kualitas layanan MediaHub Polri.",
|
"survey5": "Kami menghargai pendapat Anda! Survei ini bertujuan untuk meningkatkan kualitas layanan MediaHub Polri.",
|
||||||
"survey6": "1. Seberapa sering Anda mengakses MediaHub Polri?",
|
"survey6": "1. Seberapa sering Anda mengakses MediaHub Polri?",
|
||||||
"survey7": "2. Bagaimana pengalaman Anda dalam mengakses website ini?",
|
"survey7": "2. Bagaimana pengalaman Anda dalam mengakses website ini?",
|
||||||
"survey8": "3. Seberapa puas Anda dengan informasi yang tersedia di MediaHub Polri?",
|
"survey8": "3. Seberapa puas Anda dengan informasi yang tersedia di MediaHub Polri?",
|
||||||
"survey9": "4. Apakah Anda merasa website ini membantu dalam mendapatkan informasi terkait Polri?",
|
"survey9": "4. Apakah Anda merasa website ini membantu dalam mendapatkan informasi terkait Polri?",
|
||||||
"survey10": "5. Apa saran atau masukan Anda?"
|
"survey10": "5. Apa saran atau masukan Anda?"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"FilterPage": {
|
"FilterPage": {
|
||||||
"image": "Foto",
|
"image": "Foto",
|
||||||
|
|
@ -798,6 +784,8 @@
|
||||||
"assignment-type": "Jenis Penugasan",
|
"assignment-type": "Jenis Penugasan",
|
||||||
"description-task": "Narasi Penugasan",
|
"description-task": "Narasi Penugasan",
|
||||||
"form-task": "Form Penugasan",
|
"form-task": "Form Penugasan",
|
||||||
|
"form-task-ta": "Form Tanya Tenaga Ahli",
|
||||||
|
"form-task-ta-do": "Form Tenaga Ahli Kurasi",
|
||||||
"assignment-selection": "Penerima Tugas",
|
"assignment-selection": "Penerima Tugas",
|
||||||
"custom": "Kostum",
|
"custom": "Kostum",
|
||||||
"assigment-type": "Jenis Tugas",
|
"assigment-type": "Jenis Tugas",
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,11 @@ export async function acceptAssignment(id: any) {
|
||||||
return httpPostInterceptor(url, id);
|
return httpPostInterceptor(url, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function acceptAssignmentTa(id: any) {
|
||||||
|
const url = `assignment-expert/acceptance?id=${id}`;
|
||||||
|
return httpPostInterceptor(url, id);
|
||||||
|
}
|
||||||
|
|
||||||
export async function postFinishAcceptance(id: any) {
|
export async function postFinishAcceptance(id: any) {
|
||||||
const url = `assignment/finish-acceptance?id=${id}`;
|
const url = `assignment/finish-acceptance?id=${id}`;
|
||||||
return httpPostInterceptor(url, id);
|
return httpPostInterceptor(url, id);
|
||||||
|
|
@ -128,6 +133,11 @@ export async function finishTask(id: any) {
|
||||||
return httpPostInterceptor(url);
|
return httpPostInterceptor(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function finishTaskTa(id: any) {
|
||||||
|
const url = `assignment-expert/finish?id=${id}`;
|
||||||
|
return httpPostInterceptor(url);
|
||||||
|
}
|
||||||
|
|
||||||
export async function listTaskTa(
|
export async function listTaskTa(
|
||||||
page: any,
|
page: any,
|
||||||
title: string = "",
|
title: string = "",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue