QUDO-159
This commit is contained in:
commit
4ab11ea377
|
|
@ -145,6 +145,7 @@ export default function EditUserForm() {
|
|||
password: data.password,
|
||||
passwordConf: data.confirmPassword,
|
||||
isDefault: false,
|
||||
isAdmin: true,
|
||||
};
|
||||
|
||||
loading();
|
||||
|
|
|
|||
|
|
@ -285,6 +285,7 @@ export default function CreateUserForm() {
|
|||
password: data.password,
|
||||
passwordConf: data.confirmPassword,
|
||||
isDefault: false,
|
||||
isAdmin: true,
|
||||
};
|
||||
|
||||
if (data.role == "OPT-ID") {
|
||||
|
|
|
|||
|
|
@ -302,7 +302,7 @@ export default function EditUserForm() {
|
|||
|
||||
async function save(data: z.infer<typeof FormSchema>) {
|
||||
let req: any = {
|
||||
id: id,
|
||||
id: Number(id),
|
||||
firstName: data.fullname,
|
||||
username: data.username,
|
||||
roleId: data.role,
|
||||
|
|
@ -313,6 +313,7 @@ export default function EditUserForm() {
|
|||
password: data.password,
|
||||
passwordConf: data.confirmPassword,
|
||||
isDefault: false,
|
||||
isAdmin: true,
|
||||
};
|
||||
|
||||
if (data.role == "OPT-ID") {
|
||||
|
|
|
|||
|
|
@ -31,9 +31,12 @@ import {
|
|||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
|
||||
import { setBanner } from "@/service/settings/settings";
|
||||
import { deleteAdvertisements, setBanner } from "@/service/settings/settings";
|
||||
import { error } from "@/config/swal";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import Swal from "sweetalert2";
|
||||
import { deleteMedia } from "@/service/content/content";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
|
|
@ -74,12 +77,49 @@ const columns: ColumnDef<any>[] = [
|
|||
header: "Actions",
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const { toast } = useToast();
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
const handleBanner = async (id: number) => {
|
||||
const response = setBanner(id, true);
|
||||
toast({
|
||||
title: "Success",
|
||||
async function doDelete(id: any) {
|
||||
// loading();
|
||||
const data = {
|
||||
id,
|
||||
};
|
||||
|
||||
const response = await deleteAdvertisements(id);
|
||||
|
||||
if (response?.error) {
|
||||
error(response.message);
|
||||
return false;
|
||||
}
|
||||
success();
|
||||
}
|
||||
|
||||
function success() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const handleDeleteAdvertisements = (id: any) => {
|
||||
MySwal.fire({
|
||||
title: "Hapus Data",
|
||||
text: "",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Hapus",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
doDelete(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
return (
|
||||
|
|
@ -93,17 +133,24 @@ const columns: ColumnDef<any>[] = [
|
|||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="p-0" align="end">
|
||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||
<Link
|
||||
href={`/contributor/content/image/detail/${row.original.id}`}
|
||||
>
|
||||
Detail
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||
<a onClick={() => handleBanner(row.original.id)}>
|
||||
Jadikan Banner
|
||||
</a>
|
||||
<Link href={`/admin/settings/iklan/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" />
|
||||
View
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<Link href={`/admin/settings/iklan/update/${row.original.id}`}>
|
||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||
<SquarePen className="w-4 h-4 me-1.5" />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDeleteAdvertisements(row.original.id)}
|
||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||
>
|
||||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import { TambahIklanDetail } from "@/components/form/setting/form-add-iklan-detail";
|
||||
|
||||
const AdvertisementsDetailPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
<TambahIklanDetail />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdvertisementsDetailPage;
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import { TambahIklanUpdate } from "@/components/form/setting/form-add-iklan-update";
|
||||
|
||||
const AdvertisementsUpdatePage = () => {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
<TambahIklanUpdate />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdvertisementsUpdatePage;
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -21,7 +21,7 @@ const CalenderPage = () => {
|
|||
const categories = await getCategories();
|
||||
let valueShowed: string[] = [];
|
||||
if (userLevelNumber == 1) {
|
||||
valueShowed = ["1", "2", "3", "4", "5"];
|
||||
valueShowed = ["0", "1", "2", "3", "4", "5"];
|
||||
} else if (userLevelNumber == 2 && userLevelId != 761) {
|
||||
valueShowed = ["2", "3"];
|
||||
} else if (
|
||||
|
|
|
|||
|
|
@ -84,8 +84,8 @@ export default function ExecutiveDataDashboard() {
|
|||
? "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-polda?polda-selected=${state}&`
|
||||
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polda?polda-selected=${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 view5 =
|
||||
levelName == "MABES POLRI"
|
||||
|
|
@ -120,8 +120,8 @@ export default function ExecutiveDataDashboard() {
|
|||
? "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-konten-kategori-top10-polda?polda-selected=${state}&`
|
||||
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-konten-kategori-top10-polda?polda-selected=${state}&`;
|
||||
? `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"
|
||||
|
|
@ -369,61 +369,6 @@ export default function ExecutiveDataDashboard() {
|
|||
/>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-lg">
|
||||
<b>
|
||||
{isInternational[3]
|
||||
? "INTERACTIONS OF CATEGORY AND ACCESS TIME"
|
||||
: "INTERAKSI KATEGORI DAN WAKTU AKSES"}
|
||||
</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(3, false)}
|
||||
className={` hover:text-white rounded-none
|
||||
${
|
||||
isInternational[2]
|
||||
? "bg-white text-black "
|
||||
: "bg-black text-white "
|
||||
}`}
|
||||
>
|
||||
Indonesia
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleInternational(3, 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">
|
||||
{ticket4 == "" ? (
|
||||
<iframe
|
||||
src={`${baseUrl + view9 + param}`}
|
||||
width="100%"
|
||||
height="750"
|
||||
frameBorder="0"
|
||||
/>
|
||||
) : (
|
||||
<iframe
|
||||
src={`${`${url + ticket4}/${view9}${param}`}`}
|
||||
width="100%"
|
||||
height="750"
|
||||
frameBorder="0"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
<TabsContent value="popular-content">
|
||||
|
|
|
|||
|
|
@ -149,9 +149,7 @@ export default function ExecutiveDashboard() {
|
|||
}`}
|
||||
>
|
||||
<div className="flex flex-row justify-between">
|
||||
<p className="text-base font-semibold">
|
||||
Upload konten hari ini Polda
|
||||
</p>
|
||||
<p className="text-base font-semibold">Publish Konten Polda</p>
|
||||
</div>
|
||||
<div className="my-5">
|
||||
{ticket1 == "" ? (
|
||||
|
|
@ -173,39 +171,6 @@ export default function ExecutiveDashboard() {
|
|||
</Card>
|
||||
)}
|
||||
|
||||
{/* Satker */}
|
||||
{(levelNumber === "1" || levelNumber === "3") && (
|
||||
<Card
|
||||
className={`rounded-sm p-3 ${
|
||||
levelNumber === "3" ? "w-full" : "w-4/12"
|
||||
}`}
|
||||
>
|
||||
<div className="flex flex-row justify-between">
|
||||
<p className="text-base font-semibold">
|
||||
Upload konten hari ini Satker
|
||||
</p>
|
||||
</div>
|
||||
<div className="my-5">
|
||||
{ticket2 == "" ? (
|
||||
<iframe
|
||||
src={`${baseUrl + view6 + 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 ${
|
||||
|
|
@ -213,9 +178,7 @@ export default function ExecutiveDashboard() {
|
|||
}`}
|
||||
>
|
||||
<div className="flex flex-row justify-between">
|
||||
<p className="text-base font-semibold">
|
||||
Upload konten hari ini Polres
|
||||
</p>
|
||||
<p className="text-base font-semibold">Publish Konten Polres</p>
|
||||
</div>
|
||||
<div className="my-5">
|
||||
{ticket3 == "" ? (
|
||||
|
|
@ -236,6 +199,36 @@ export default function ExecutiveDashboard() {
|
|||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Satker */}
|
||||
{(levelNumber === "1" || levelNumber === "3") && (
|
||||
<Card
|
||||
className={`rounded-sm p-3 ${
|
||||
levelNumber === "3" ? "w-full" : "w-4/12"
|
||||
}`}
|
||||
>
|
||||
<div className="flex flex-row justify-between">
|
||||
<p className="text-base font-semibold">Publish Konten Satker</p>
|
||||
</div>
|
||||
<div className="my-5">
|
||||
{ticket2 == "" ? (
|
||||
<iframe
|
||||
src={`${baseUrl + view6 + param}`}
|
||||
width="100%"
|
||||
height="750"
|
||||
frameBorder="0"
|
||||
/>
|
||||
) : (
|
||||
<iframe
|
||||
src={`${url + ticket2}/${view6}${param}`}
|
||||
width="100%"
|
||||
height="750"
|
||||
frameBorder="0"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
<div className="w-full mt-3">
|
||||
<Card className="rounded-sm p-3 h-[750px]">
|
||||
|
|
|
|||
|
|
@ -531,7 +531,7 @@ const FilterPage = () => {
|
|||
height={1440}
|
||||
alt="filter-image"
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`}
|
||||
src={image?.thumbnailLink}
|
||||
src={image?.smallThumbnailLink || image?.thumbnailLink}
|
||||
className="h-full lg:h-[300px] w-full object-cover rounded-lg transition-transform duration-300"
|
||||
/>
|
||||
|
||||
|
|
|
|||
|
|
@ -545,7 +545,7 @@ const FilterPage = () => {
|
|||
{/* <img src={image?.thumbnailLink} className="h-60 object-cover items-center justify-center cursor-pointer rounded-lg" /> */}
|
||||
<div className="img-container h-60 bg-[#e9e9e9] cursor-pointer rounded-lg">
|
||||
<ImageBlurry
|
||||
src={image?.thumbnailLink}
|
||||
src={image?.smallThumbnailLink || image?.thumbnailLink}
|
||||
alt={image?.title}
|
||||
style={{
|
||||
objectFit: "contain",
|
||||
|
|
|
|||
|
|
@ -13,96 +13,560 @@ import { Button } from "@/components/ui/button";
|
|||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Plus } from "lucide-react";
|
||||
import { ChevronDown, ChevronUp, Plus } from "lucide-react";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import Image from "next/image";
|
||||
import { Upload } from "tus-js-client";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
import { format, parseISO } from "date-fns";
|
||||
import { getUserLevelForAssignments } from "@/service/task";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Swal from "sweetalert2";
|
||||
import { z } from "zod";
|
||||
import { DateRange } from "react-day-picker";
|
||||
import { postCalendar } from "@/service/schedule/schedule";
|
||||
import { id } from "date-fns/locale";
|
||||
import router from "next/router";
|
||||
import {
|
||||
detailAdvertisements,
|
||||
postAdvertisements,
|
||||
} from "@/service/settings/settings";
|
||||
import Cookies from "js-cookie";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import FileUploader from "../shared/file-uploader";
|
||||
import { Icon } from "@/components/ui/icon";
|
||||
import { useParams } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
|
||||
export function TambahIklanModalDetail() {
|
||||
const calendarSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
description: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
});
|
||||
|
||||
interface FileWithPreview extends File {
|
||||
preview: string;
|
||||
}
|
||||
|
||||
interface FileUploaded {
|
||||
id: number;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface Detail {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export function TambahIklanDetail() {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const MySwal = withReactContent(Swal);
|
||||
const t = useTranslations("Schedule");
|
||||
const { id } = useParams() as { id: string };
|
||||
type CalendarSchema = z.infer<typeof calendarSchema>;
|
||||
const [eventDate, setEventDate] = React.useState<Date | null>(new Date());
|
||||
const [listDest, setListDest] = React.useState([]);
|
||||
const [checkedLevels, setCheckedLevels] = React.useState(new Set());
|
||||
const [expandedPolda, setExpandedPolda] = React.useState([{}]);
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
const [isImageUploadFinish, setIsImageUploadFinish] = React.useState(false);
|
||||
const [files, setFiles] = React.useState<FileWithPreview[]>([]);
|
||||
const [selectedPlacement, setSelectedPlacement] = React.useState<string>("");
|
||||
const [imageUploadedFiles, setImageUploadedFiles] = React.useState<
|
||||
FileUploaded[]
|
||||
>([]);
|
||||
const [detail, setDetail] = React.useState<Detail>();
|
||||
const [refresh, setRefresh] = React.useState(false);
|
||||
const [imageFiles, setImageFiles] = React.useState<FileWithPreview[]>([]);
|
||||
const [date, setDate] = React.useState<DateRange | undefined>({
|
||||
from: new Date(2025, 0, 1),
|
||||
});
|
||||
|
||||
const [unitSelection, setUnitSelection] = React.useState({
|
||||
semua: false,
|
||||
mabes: false,
|
||||
polda: false,
|
||||
satker: false,
|
||||
internasional: false,
|
||||
});
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm<CalendarSchema>({
|
||||
resolver: zodResolver(calendarSchema),
|
||||
defaultValues: {
|
||||
description: "",
|
||||
},
|
||||
});
|
||||
|
||||
const handlePlacementSelect = (value: string) => {
|
||||
setSelectedPlacement(value);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
const response = await detailAdvertisements(id);
|
||||
const details = response?.data?.data;
|
||||
|
||||
setDetail(details);
|
||||
if (details?.assignedToLevel) {
|
||||
const levelIds = details.assignedToLevel
|
||||
.split(",")
|
||||
.map((id: string) => parseInt(id));
|
||||
setCheckedLevels(new Set(levelIds));
|
||||
}
|
||||
|
||||
if (details?.placements) {
|
||||
setSelectedPlacement(details.placements); // "left-bottom", etc.
|
||||
}
|
||||
}
|
||||
}
|
||||
initState();
|
||||
}, [refresh, setValue]);
|
||||
|
||||
React.useEffect(() => {
|
||||
async function fetchPoldaPolres() {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await getUserLevelForAssignments();
|
||||
setListDest(response?.data?.data.list);
|
||||
console.log("polda", response?.data?.data?.list);
|
||||
const initialExpandedState = response?.data?.data.list.reduce(
|
||||
(acc: any, polda: any) => {
|
||||
acc[polda.id] = false;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
setExpandedPolda(initialExpandedState);
|
||||
console.log("polres", initialExpandedState);
|
||||
} catch (error) {
|
||||
console.error("Error fetching Polda/Polres data:", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
fetchPoldaPolres();
|
||||
}, []);
|
||||
|
||||
const handleCheckboxChange = (levelId: number) => {
|
||||
setCheckedLevels((prev) => {
|
||||
const updatedLevels = new Set(prev);
|
||||
if (updatedLevels.has(levelId)) {
|
||||
updatedLevels.delete(levelId);
|
||||
} else {
|
||||
updatedLevels.add(levelId);
|
||||
}
|
||||
return updatedLevels;
|
||||
});
|
||||
};
|
||||
|
||||
const handlePoldaPolresChange = () => {
|
||||
return Array.from(checkedLevels).join(",");
|
||||
};
|
||||
|
||||
const handleUnitChange = (
|
||||
key: keyof typeof unitSelection,
|
||||
value: boolean
|
||||
) => {
|
||||
if (key === "semua") {
|
||||
const newState = {
|
||||
semua: value,
|
||||
mabes: value,
|
||||
polda: value,
|
||||
satker: value,
|
||||
internasional: value,
|
||||
};
|
||||
setUnitSelection(newState);
|
||||
} else {
|
||||
const updatedSelection = {
|
||||
...unitSelection,
|
||||
[key]: value,
|
||||
};
|
||||
|
||||
const allChecked = ["mabes", "polda", "satker", "internasional"].every(
|
||||
(k) => updatedSelection[k as keyof typeof unitSelection]
|
||||
);
|
||||
|
||||
updatedSelection.semua = allChecked;
|
||||
|
||||
setUnitSelection(updatedSelection);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleExpand = (poldaId: any) => {
|
||||
setExpandedPolda((prev: any) => ({
|
||||
...prev,
|
||||
[poldaId]: !prev[poldaId],
|
||||
}));
|
||||
};
|
||||
|
||||
const save = async (data: CalendarSchema) => {
|
||||
const unitMapping = {
|
||||
allUnit: "0",
|
||||
mabes: "1",
|
||||
polda: "2",
|
||||
satker: "4",
|
||||
internasional: "5",
|
||||
};
|
||||
const assignmentToString = Object.keys(unitSelection)
|
||||
.filter((key) => unitSelection[key as keyof typeof unitSelection])
|
||||
.map((key) => unitMapping[key as keyof typeof unitMapping])
|
||||
.join(",");
|
||||
|
||||
const formMedia = new FormData();
|
||||
formMedia.append("title", data.title);
|
||||
formMedia.append("placements", selectedPlacement);
|
||||
formMedia.append("description", data.description);
|
||||
formMedia.append("redirectLink", "https://new.netidhub.com");
|
||||
formMedia.append("assignedToLevel", handlePoldaPolresChange());
|
||||
formMedia.append("file", imageFiles[0]);
|
||||
|
||||
console.log("Form Data Submitted:", formMedia);
|
||||
|
||||
loading();
|
||||
const response = await postAdvertisements(formMedia);
|
||||
if (response?.error) {
|
||||
error(response?.message);
|
||||
return false;
|
||||
}
|
||||
close();
|
||||
|
||||
Cookies.set("scheduleId", response?.data?.data.id, {
|
||||
expires: 1,
|
||||
});
|
||||
};
|
||||
|
||||
async function uploadResumableFile(
|
||||
idx: number,
|
||||
id: string,
|
||||
file: any,
|
||||
fileTypeId: string,
|
||||
duration: string
|
||||
) {
|
||||
console.log(idx, id, file, fileTypeId, duration);
|
||||
|
||||
const resCsrf = await getCsrfToken();
|
||||
const csrfToken = resCsrf?.data?.token;
|
||||
console.log("CSRF TOKEN : ", csrfToken);
|
||||
const headers = {
|
||||
"X-XSRF-TOKEN": csrfToken,
|
||||
};
|
||||
|
||||
const upload = new Upload(file, {
|
||||
endpoint: `${process.env.NEXT_PUBLIC_API}/advertisements/file/upload`,
|
||||
headers: headers,
|
||||
retryDelays: [0, 3000, 6000, 12_000, 24_000],
|
||||
chunkSize: 20_000,
|
||||
metadata: {
|
||||
advertisementsId: id,
|
||||
filename: file.name,
|
||||
contentType: file.type,
|
||||
fileTypeId: fileTypeId,
|
||||
duration,
|
||||
},
|
||||
onBeforeRequest: function (req) {
|
||||
var xhr = req.getUnderlyingObject();
|
||||
xhr.withCredentials = true;
|
||||
},
|
||||
onError: async (e: any) => {
|
||||
console.log("Error upload :", e);
|
||||
error(e);
|
||||
},
|
||||
onChunkComplete: (
|
||||
chunkSize: any,
|
||||
bytesAccepted: any,
|
||||
bytesTotal: any
|
||||
) => {
|
||||
// const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
|
||||
// progressInfo[idx].percentage = uploadPersen;
|
||||
// counterUpdateProgress++;
|
||||
// console.log(counterUpdateProgress);
|
||||
// setProgressList(progressInfo);
|
||||
// setCounterProgress(counterUpdateProgress);
|
||||
},
|
||||
onSuccess: async () => {
|
||||
// uploadPersen = 100;
|
||||
// progressInfo[idx].percentage = 100;
|
||||
// counterUpdateProgress++;
|
||||
// setCounterProgress(counterUpdateProgress);
|
||||
successTodo();
|
||||
if (fileTypeId == "1") {
|
||||
setIsImageUploadFinish(true);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
upload.start();
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
successTodo();
|
||||
}, [isImageUploadFinish]);
|
||||
|
||||
function successTodo() {
|
||||
if (isImageUploadFinish) {
|
||||
successSubmit("/in/admin/settings/iklan");
|
||||
}
|
||||
}
|
||||
|
||||
const successSubmit = (redirect: string) => {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Data berhasil disimpan.",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then(() => {
|
||||
router.push(redirect);
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmit = (data: CalendarSchema) => {
|
||||
MySwal.fire({
|
||||
title: "Simpan Data",
|
||||
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "Simpan",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
save(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const renderFilePreview = (url: string) => {
|
||||
return (
|
||||
<Image
|
||||
width={48}
|
||||
height={48}
|
||||
alt={"file preview"}
|
||||
src={url}
|
||||
className=" rounded border p-0.5"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const handleRemoveFile = (id: number) => {};
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button onClick={() => setOpen(true)} color="primary" size="md">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Tambah Iklan
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent size="md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Tambah Iklan</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div>
|
||||
<Card className="px-3 py-3">
|
||||
{detail !== undefined ? (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<p className="font-medium">Target Area</p>
|
||||
<div className="flex flex-wrap gap-4 mt-2">
|
||||
{[
|
||||
{ label: "Kiri - 1", value: "left-top" },
|
||||
{ label: "Kiri - 2", value: "left-bottom" },
|
||||
{ label: "Kanan - 1", value: "right-top" },
|
||||
{ label: "Kanan - 2", value: "right-bottom" },
|
||||
].map(({ label, value }) => (
|
||||
<label key={value} className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id={value}
|
||||
checked={selectedPlacement === value}
|
||||
onCheckedChange={() => handlePlacementSelect(value)}
|
||||
/>
|
||||
<span>{label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<p className="font-medium">Target Area</p>
|
||||
<div className="flex flex-wrap gap-4 mt-2">
|
||||
{["Kiri - 1", "Kiri - 2", "Kanan - 1", "Kanan - 2"].map(
|
||||
(label) => (
|
||||
<label key={label} className="flex items-center gap-2">
|
||||
<Checkbox id={label} />
|
||||
<span>{label}</span>
|
||||
</label>
|
||||
)
|
||||
)}
|
||||
{/* <div>
|
||||
<p className="font-medium">Publish Area</p>
|
||||
<div className="flex flex-row">
|
||||
<div className="flex flex-wrap gap-3 lg:ml-3 ">
|
||||
{Object.keys(unitSelection).map((key) => (
|
||||
<div className="flex items-center gap-2" key={key}>
|
||||
<Checkbox
|
||||
id={key}
|
||||
checked={
|
||||
unitSelection[key as keyof typeof unitSelection]
|
||||
}
|
||||
onCheckedChange={(value) =>
|
||||
handleUnitChange(
|
||||
key as keyof typeof unitSelection,
|
||||
value as boolean
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Label htmlFor={key}>
|
||||
{key.charAt(0).toUpperCase() + key.slice(1)}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className=" lg:pl-3">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="soft" size="sm" color="primary">
|
||||
[{t("custom")}]
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
Daftar Wilayah Polda dan Polres
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="grid grid-cols-2 gap-2 max-h-[400px] overflow-y-auto">
|
||||
{listDest.map((polda: any) => (
|
||||
<div key={polda.id} className="border p-2">
|
||||
<Label className="flex items-center">
|
||||
<Checkbox
|
||||
checked={checkedLevels.has(polda.id)}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(polda.id)
|
||||
}
|
||||
className="mr-3"
|
||||
/>
|
||||
{polda.name}
|
||||
<button
|
||||
onClick={() => toggleExpand(polda.id)}
|
||||
className="ml-2 focus:outline-none"
|
||||
>
|
||||
{expandedPolda[polda.id] ? (
|
||||
<ChevronUp size={16} />
|
||||
) : (
|
||||
<ChevronDown size={16} />
|
||||
)}
|
||||
</button>
|
||||
</Label>
|
||||
{expandedPolda[polda.id] && (
|
||||
<div className="ml-6 mt-2">
|
||||
<Label className="block">
|
||||
<Checkbox
|
||||
checked={polda?.subDestination?.every(
|
||||
(polres: any) =>
|
||||
checkedLevels.has(polres.id)
|
||||
)}
|
||||
onCheckedChange={(isChecked) => {
|
||||
const updatedLevels = new Set(
|
||||
checkedLevels
|
||||
);
|
||||
polda?.subDestination?.forEach(
|
||||
(polres: any) => {
|
||||
if (isChecked) {
|
||||
updatedLevels.add(polres.id);
|
||||
} else {
|
||||
updatedLevels.delete(polres.id);
|
||||
}
|
||||
}
|
||||
);
|
||||
setCheckedLevels(updatedLevels);
|
||||
}}
|
||||
className="mr-2"
|
||||
/>
|
||||
Pilih Semua Polres
|
||||
</Label>
|
||||
{polda?.subDestination?.map((polres: any) => (
|
||||
<Label
|
||||
key={polres.id}
|
||||
className="block mt-1"
|
||||
>
|
||||
<Checkbox
|
||||
checked={checkedLevels.has(polres.id)}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(polres.id)
|
||||
}
|
||||
className="mr-2"
|
||||
/>
|
||||
{polres.name}
|
||||
</Label>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div>
|
||||
<p className="font-medium">Nama Iklan</p>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
size={"md"}
|
||||
type="text"
|
||||
value={detail?.title}
|
||||
onChange={field.onChange}
|
||||
placeholder="Masukan Nama Iklan"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.title?.message && (
|
||||
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>Foto</Label>
|
||||
<p className="text-xs text-red-500">
|
||||
(Warning: Foto yang di upload adalah Foto Potrait)
|
||||
</p>
|
||||
|
||||
<Image
|
||||
src={`https://netidhub.com/api/advertisements/viewer/${id}`}
|
||||
alt="Thumbnail Gambar Utama"
|
||||
className=" rounded-md my-3"
|
||||
width={300}
|
||||
height={500}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium">Deskripsi</p>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
render={({ field }) => (
|
||||
<Textarea
|
||||
rows={3}
|
||||
value={detail?.description}
|
||||
onChange={field.onChange}
|
||||
placeholder="Masukan Description"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.description?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.description?.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="text-right">
|
||||
<Link href={"admin/settings/iklan"}>
|
||||
<Button type="button" variant={"outline"} color="primary">
|
||||
Kembali
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="font-medium">Publish Area</p>
|
||||
<div className="flex flex-wrap gap-4 mt-2">
|
||||
{["Semua", "Nasional", "Polda", "Satker", "International"].map(
|
||||
(label) => (
|
||||
<label key={label} className="flex items-center gap-2">
|
||||
<Checkbox id={label} />
|
||||
<span>{label}</span>
|
||||
</label>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="font-medium">Nama Iklan</p>
|
||||
<Input placeholder="Masukkan nama iklan" />
|
||||
</div>
|
||||
|
||||
<div className="border-2 border-dashed rounded-md p-4 flex flex-col items-center justify-center text-center text-sm text-muted-foreground">
|
||||
<svg
|
||||
className="w-6 h-6 mb-2 text-blue-500"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M4 16v1a2 2 0 002 2h12a2 2 0 002-2v-1M12 12v9m0-9l3 3m-3-3l-3 3m6-11a4 4 0 00-8 0v1H4a2 2 0 00-2 2v4h20v-4a2 2 0 00-2-2h-4v-1z"
|
||||
/>
|
||||
</svg>
|
||||
<div>
|
||||
Drag your file(s) or{" "}
|
||||
<span className="text-blue-500 underline cursor-pointer">
|
||||
browse
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-xs mt-1">Max 10 MB files are allowed</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="font-medium">Deskripsi</p>
|
||||
<Textarea placeholder="Masukkan deskripsi iklan" rows={4} />
|
||||
</div>
|
||||
|
||||
<div className="text-right">
|
||||
<Button
|
||||
type="submit"
|
||||
className="bg-blue-600 text-white hover:bg-blue-700"
|
||||
>
|
||||
Tambah Iklan
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</form>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,96 +13,554 @@ import { Button } from "@/components/ui/button";
|
|||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Plus } from "lucide-react";
|
||||
import { ChevronDown, ChevronUp, Plus } from "lucide-react";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import Image from "next/image";
|
||||
import { Upload } from "tus-js-client";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
import { format, parseISO } from "date-fns";
|
||||
import { getUserLevelForAssignments } from "@/service/task";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Swal from "sweetalert2";
|
||||
import { z } from "zod";
|
||||
import { DateRange } from "react-day-picker";
|
||||
import { postCalendar } from "@/service/schedule/schedule";
|
||||
import { id } from "date-fns/locale";
|
||||
import router from "next/router";
|
||||
import {
|
||||
detailAdvertisements,
|
||||
postAdvertisements,
|
||||
} from "@/service/settings/settings";
|
||||
import Cookies from "js-cookie";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import FileUploader from "../shared/file-uploader";
|
||||
import { Icon } from "@/components/ui/icon";
|
||||
import { useParams } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
|
||||
export function TambahIklanModalUpdate() {
|
||||
const calendarSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
description: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
});
|
||||
|
||||
interface FileWithPreview extends File {
|
||||
preview: string;
|
||||
}
|
||||
|
||||
interface FileUploaded {
|
||||
id: number;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface Detail {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export function TambahIklanUpdate() {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const MySwal = withReactContent(Swal);
|
||||
const t = useTranslations("Schedule");
|
||||
const { id } = useParams() as { id: string };
|
||||
type CalendarSchema = z.infer<typeof calendarSchema>;
|
||||
const [eventDate, setEventDate] = React.useState<Date | null>(new Date());
|
||||
const [listDest, setListDest] = React.useState([]);
|
||||
const [checkedLevels, setCheckedLevels] = React.useState(new Set());
|
||||
const [expandedPolda, setExpandedPolda] = React.useState([{}]);
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
const [isImageUploadFinish, setIsImageUploadFinish] = React.useState(false);
|
||||
const [files, setFiles] = React.useState<FileWithPreview[]>([]);
|
||||
const [selectedPlacement, setSelectedPlacement] = React.useState<string>("");
|
||||
const [imageUploadedFiles, setImageUploadedFiles] = React.useState<
|
||||
FileUploaded[]
|
||||
>([]);
|
||||
const [detail, setDetail] = React.useState<Detail>();
|
||||
const [refresh, setRefresh] = React.useState(false);
|
||||
const [imageFiles, setImageFiles] = React.useState<FileWithPreview[]>([]);
|
||||
const [date, setDate] = React.useState<DateRange | undefined>({
|
||||
from: new Date(2025, 0, 1),
|
||||
});
|
||||
|
||||
const [unitSelection, setUnitSelection] = React.useState({
|
||||
semua: false,
|
||||
mabes: false,
|
||||
polda: false,
|
||||
satker: false,
|
||||
internasional: false,
|
||||
});
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm<CalendarSchema>({
|
||||
resolver: zodResolver(calendarSchema),
|
||||
defaultValues: {
|
||||
description: "",
|
||||
},
|
||||
});
|
||||
|
||||
const handlePlacementSelect = (value: string) => {
|
||||
setSelectedPlacement(value);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
const response = await detailAdvertisements(id);
|
||||
const details = response?.data?.data;
|
||||
|
||||
setDetail(details);
|
||||
if (details?.assignedToLevel) {
|
||||
const levelIds = details.assignedToLevel
|
||||
.split(",")
|
||||
.map((id: string) => parseInt(id));
|
||||
setCheckedLevels(new Set(levelIds));
|
||||
}
|
||||
|
||||
if (details?.placements) {
|
||||
setSelectedPlacement(details.placements); // "left-bottom", etc.
|
||||
}
|
||||
}
|
||||
}
|
||||
initState();
|
||||
}, [refresh, setValue]);
|
||||
|
||||
React.useEffect(() => {
|
||||
async function fetchPoldaPolres() {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await getUserLevelForAssignments();
|
||||
setListDest(response?.data?.data.list);
|
||||
console.log("polda", response?.data?.data?.list);
|
||||
const initialExpandedState = response?.data?.data.list.reduce(
|
||||
(acc: any, polda: any) => {
|
||||
acc[polda.id] = false;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
setExpandedPolda(initialExpandedState);
|
||||
console.log("polres", initialExpandedState);
|
||||
} catch (error) {
|
||||
console.error("Error fetching Polda/Polres data:", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
fetchPoldaPolres();
|
||||
}, []);
|
||||
|
||||
const handleCheckboxChange = (levelId: number) => {
|
||||
setCheckedLevels((prev) => {
|
||||
const updatedLevels = new Set(prev);
|
||||
if (updatedLevels.has(levelId)) {
|
||||
updatedLevels.delete(levelId);
|
||||
} else {
|
||||
updatedLevels.add(levelId);
|
||||
}
|
||||
return updatedLevels;
|
||||
});
|
||||
};
|
||||
|
||||
const handlePoldaPolresChange = () => {
|
||||
return Array.from(checkedLevels).join(",");
|
||||
};
|
||||
|
||||
const handleUnitChange = (
|
||||
key: keyof typeof unitSelection,
|
||||
value: boolean
|
||||
) => {
|
||||
if (key === "semua") {
|
||||
const newState = {
|
||||
semua: value,
|
||||
mabes: value,
|
||||
polda: value,
|
||||
satker: value,
|
||||
internasional: value,
|
||||
};
|
||||
setUnitSelection(newState);
|
||||
} else {
|
||||
const updatedSelection = {
|
||||
...unitSelection,
|
||||
[key]: value,
|
||||
};
|
||||
|
||||
const allChecked = ["mabes", "polda", "satker", "internasional"].every(
|
||||
(k) => updatedSelection[k as keyof typeof unitSelection]
|
||||
);
|
||||
|
||||
updatedSelection.semua = allChecked;
|
||||
|
||||
setUnitSelection(updatedSelection);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleExpand = (poldaId: any) => {
|
||||
setExpandedPolda((prev: any) => ({
|
||||
...prev,
|
||||
[poldaId]: !prev[poldaId],
|
||||
}));
|
||||
};
|
||||
|
||||
const save = async (data: CalendarSchema) => {
|
||||
const unitMapping = {
|
||||
allUnit: "0",
|
||||
mabes: "1",
|
||||
polda: "2",
|
||||
satker: "4",
|
||||
internasional: "5",
|
||||
};
|
||||
const assignmentToString = Object.keys(unitSelection)
|
||||
.filter((key) => unitSelection[key as keyof typeof unitSelection])
|
||||
.map((key) => unitMapping[key as keyof typeof unitMapping])
|
||||
.join(",");
|
||||
|
||||
const formMedia = new FormData();
|
||||
if (detail?.id) {
|
||||
formMedia.append("id", detail.id.toString()); // Kirim ID untuk update
|
||||
}
|
||||
formMedia.append("title", data.title);
|
||||
formMedia.append("placements", selectedPlacement);
|
||||
formMedia.append("description", data.description);
|
||||
formMedia.append("redirectLink", "https://new.netidhub.com");
|
||||
formMedia.append("assignedToLevel", handlePoldaPolresChange());
|
||||
formMedia.append("file", imageFiles[0]);
|
||||
|
||||
console.log("Form Data Submitted:", formMedia);
|
||||
|
||||
loading();
|
||||
const response = await postAdvertisements(formMedia);
|
||||
if (response?.error) {
|
||||
error(response?.message);
|
||||
return false;
|
||||
}
|
||||
close();
|
||||
|
||||
Cookies.set("scheduleId", response?.data?.data.id, {
|
||||
expires: 1,
|
||||
});
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
successTodo();
|
||||
}, [isImageUploadFinish]);
|
||||
|
||||
function successTodo() {
|
||||
if (isImageUploadFinish) {
|
||||
successSubmit("/in/admin/settings/iklan");
|
||||
}
|
||||
}
|
||||
|
||||
const successSubmit = (redirect: string) => {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Data berhasil disimpan.",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then(() => {
|
||||
router.push(redirect);
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmit = (data: CalendarSchema) => {
|
||||
MySwal.fire({
|
||||
title: "Simpan Data",
|
||||
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "Simpan",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
save(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const renderFilePreview = (url: string) => {
|
||||
return (
|
||||
<Image
|
||||
width={48}
|
||||
height={48}
|
||||
alt={"file preview"}
|
||||
src={url}
|
||||
className=" rounded border p-0.5"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const handleRemoveFile = (id: number) => {};
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button onClick={() => setOpen(true)} color="primary" size="md">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Tambah Iklan
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent size="md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Tambah Iklan</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div>
|
||||
<Card className="px-3 py-3">
|
||||
{detail !== undefined ? (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<p className="font-medium">Target Area</p>
|
||||
<div className="flex flex-wrap gap-4 mt-2">
|
||||
{[
|
||||
{ label: "Kiri - 1", value: "left-top" },
|
||||
{ label: "Kiri - 2", value: "left-bottom" },
|
||||
{ label: "Kanan - 1", value: "right-top" },
|
||||
{ label: "Kanan - 2", value: "right-bottom" },
|
||||
].map(({ label, value }) => (
|
||||
<label key={value} className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id={value}
|
||||
checked={selectedPlacement === value}
|
||||
onCheckedChange={() => handlePlacementSelect(value)}
|
||||
/>
|
||||
<span>{label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<p className="font-medium">Target Area</p>
|
||||
<div className="flex flex-wrap gap-4 mt-2">
|
||||
{["Kiri - 1", "Kiri - 2", "Kanan - 1", "Kanan - 2"].map(
|
||||
(label) => (
|
||||
<label key={label} className="flex items-center gap-2">
|
||||
<Checkbox id={label} />
|
||||
<span>{label}</span>
|
||||
</label>
|
||||
)
|
||||
)}
|
||||
{/* <div>
|
||||
<p className="font-medium">Publish Area</p>
|
||||
<div className="flex flex-row">
|
||||
<div className="flex flex-wrap gap-3 lg:ml-3 ">
|
||||
{Object.keys(unitSelection).map((key) => (
|
||||
<div className="flex items-center gap-2" key={key}>
|
||||
<Checkbox
|
||||
id={key}
|
||||
checked={
|
||||
unitSelection[key as keyof typeof unitSelection]
|
||||
}
|
||||
onCheckedChange={(value) =>
|
||||
handleUnitChange(
|
||||
key as keyof typeof unitSelection,
|
||||
value as boolean
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Label htmlFor={key}>
|
||||
{key.charAt(0).toUpperCase() + key.slice(1)}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className=" lg:pl-3">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="soft" size="sm" color="primary">
|
||||
[{t("custom")}]
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
Daftar Wilayah Polda dan Polres
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="grid grid-cols-2 gap-2 max-h-[400px] overflow-y-auto">
|
||||
{listDest.map((polda: any) => (
|
||||
<div key={polda.id} className="border p-2">
|
||||
<Label className="flex items-center">
|
||||
<Checkbox
|
||||
checked={checkedLevels.has(polda.id)}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(polda.id)
|
||||
}
|
||||
className="mr-3"
|
||||
/>
|
||||
{polda.name}
|
||||
<button
|
||||
onClick={() => toggleExpand(polda.id)}
|
||||
className="ml-2 focus:outline-none"
|
||||
>
|
||||
{expandedPolda[polda.id] ? (
|
||||
<ChevronUp size={16} />
|
||||
) : (
|
||||
<ChevronDown size={16} />
|
||||
)}
|
||||
</button>
|
||||
</Label>
|
||||
{expandedPolda[polda.id] && (
|
||||
<div className="ml-6 mt-2">
|
||||
<Label className="block">
|
||||
<Checkbox
|
||||
checked={polda?.subDestination?.every(
|
||||
(polres: any) =>
|
||||
checkedLevels.has(polres.id)
|
||||
)}
|
||||
onCheckedChange={(isChecked) => {
|
||||
const updatedLevels = new Set(
|
||||
checkedLevels
|
||||
);
|
||||
polda?.subDestination?.forEach(
|
||||
(polres: any) => {
|
||||
if (isChecked) {
|
||||
updatedLevels.add(polres.id);
|
||||
} else {
|
||||
updatedLevels.delete(polres.id);
|
||||
}
|
||||
}
|
||||
);
|
||||
setCheckedLevels(updatedLevels);
|
||||
}}
|
||||
className="mr-2"
|
||||
/>
|
||||
Pilih Semua Polres
|
||||
</Label>
|
||||
{polda?.subDestination?.map((polres: any) => (
|
||||
<Label
|
||||
key={polres.id}
|
||||
className="block mt-1"
|
||||
>
|
||||
<Checkbox
|
||||
checked={checkedLevels.has(polres.id)}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(polres.id)
|
||||
}
|
||||
className="mr-2"
|
||||
/>
|
||||
{polres.name}
|
||||
</Label>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div>
|
||||
<p className="font-medium">Nama Iklan</p>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
size={"md"}
|
||||
type="text"
|
||||
defaultValue={detail?.title}
|
||||
onChange={field.onChange}
|
||||
placeholder="Masukan Nama Iklan"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.title?.message && (
|
||||
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>Foto</Label>
|
||||
<p className="text-xs text-red-500">
|
||||
(Warning: Foto yang di upload adalah Foto Potrait)
|
||||
</p>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"image/*": [],
|
||||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .png, .jpg, atau .jpeg."
|
||||
onDrop={(files) => setImageFiles(files)}
|
||||
/>
|
||||
{imageUploadedFiles?.map((file: any, index: number) => (
|
||||
<div>
|
||||
<Card className="mt-2">
|
||||
<img
|
||||
src={file.url}
|
||||
alt="Thumbnail Gambar Utama"
|
||||
className="w-full h-auto rounded-md"
|
||||
/>
|
||||
</Card>
|
||||
<div
|
||||
key={index}
|
||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="file-preview">
|
||||
{renderFilePreview(file.url)}
|
||||
</div>
|
||||
<div>
|
||||
<div className=" text-sm text-card-foreground">
|
||||
{file.fileName}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
size="icon"
|
||||
color="destructive"
|
||||
variant="outline"
|
||||
className=" border-none rounded-full"
|
||||
onClick={() => handleRemoveFile(file)}
|
||||
>
|
||||
<Icon icon="tabler:x" className=" h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<Image
|
||||
src={`https://netidhub.com/api/advertisements/viewer/${id}`}
|
||||
alt="Thumbnail Gambar Utama"
|
||||
className=" rounded-md my-3"
|
||||
width={300}
|
||||
height={500}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="font-medium">Deskripsi</p>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
render={({ field }) => (
|
||||
<Textarea
|
||||
rows={3}
|
||||
defaultValue={detail?.description}
|
||||
onChange={field.onChange}
|
||||
placeholder="Masukan Description"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.description?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.description?.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="text-right">
|
||||
<Link href={"admin/settings/iklan"}>
|
||||
<Button type="button" variant={"outline"} color="primary">
|
||||
Kembali
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
variant={"default"}
|
||||
color="primary"
|
||||
className="mx-3"
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="font-medium">Publish Area</p>
|
||||
<div className="flex flex-wrap gap-4 mt-2">
|
||||
{["Semua", "Nasional", "Polda", "Satker", "International"].map(
|
||||
(label) => (
|
||||
<label key={label} className="flex items-center gap-2">
|
||||
<Checkbox id={label} />
|
||||
<span>{label}</span>
|
||||
</label>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="font-medium">Nama Iklan</p>
|
||||
<Input placeholder="Masukkan nama iklan" />
|
||||
</div>
|
||||
|
||||
<div className="border-2 border-dashed rounded-md p-4 flex flex-col items-center justify-center text-center text-sm text-muted-foreground">
|
||||
<svg
|
||||
className="w-6 h-6 mb-2 text-blue-500"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M4 16v1a2 2 0 002 2h12a2 2 0 002-2v-1M12 12v9m0-9l3 3m-3-3l-3 3m6-11a4 4 0 00-8 0v1H4a2 2 0 00-2 2v4h20v-4a2 2 0 00-2-2h-4v-1z"
|
||||
/>
|
||||
</svg>
|
||||
<div>
|
||||
Drag your file(s) or{" "}
|
||||
<span className="text-blue-500 underline cursor-pointer">
|
||||
browse
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-xs mt-1">Max 10 MB files are allowed</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="font-medium">Deskripsi</p>
|
||||
<Textarea placeholder="Masukkan deskripsi iklan" rows={4} />
|
||||
</div>
|
||||
|
||||
<div className="text-right">
|
||||
<Button
|
||||
type="submit"
|
||||
className="bg-blue-600 text-white hover:bg-blue-700"
|
||||
>
|
||||
Tambah Iklan
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</form>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ const EventCalender = () => {
|
|||
</div>
|
||||
|
||||
{/* Event List */}
|
||||
<div className="space-y-3 max-h-[230px] overflow-y-auto pr-5">
|
||||
<div className="space-y-3 max-h-[230px] overflow-y-auto pr-5" data-lenis-prevent>
|
||||
<h3 className="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-3">
|
||||
Daftar Acara
|
||||
</h3>
|
||||
|
|
|
|||
|
|
@ -113,10 +113,10 @@ const NewContent = (props: { group: string; type: string }) => {
|
|||
<Tabs value={selectedTab} onValueChange={setSelectedTab}>
|
||||
<TabsList className="flex gap-2 bg-transparent p-0">
|
||||
{[
|
||||
{ label: "Audio Visual", value: "video" },
|
||||
{ label: "Audio", value: "audio" },
|
||||
{ label: "Foto", value: "image" },
|
||||
{ label: "Audio Visual", value: "video" },
|
||||
{ label: "Teks", value: "text" },
|
||||
{ label: "Audio", value: "audio" },
|
||||
].map((tab) => (
|
||||
<TabsTrigger
|
||||
key={tab.value}
|
||||
|
|
|
|||
|
|
@ -228,15 +228,17 @@ const LoginForm = () => {
|
|||
Number(profile?.data?.data?.roleId) == 18 ||
|
||||
Number(profile?.data?.data?.roleId) == 19
|
||||
) {
|
||||
if (
|
||||
profile?.data?.data?.roleId === 18 ||
|
||||
profile?.data?.data?.roleId === 2
|
||||
) {
|
||||
window.location.href = "/in/dashboard/executive";
|
||||
if (profile?.data?.data?.roleId === 18) {
|
||||
window.location.href = "/in/dashboard/executive-data";
|
||||
// router.push('/admin/dashboard');
|
||||
Cookies.set("status", "login", {
|
||||
expires: 1,
|
||||
});
|
||||
} else if (profile?.data?.data?.roleId === 2) {
|
||||
window.location.href = "/in/dashboard/executive";
|
||||
Cookies.set("status", "login", {
|
||||
expires: 1,
|
||||
});
|
||||
} else if (
|
||||
profile?.data?.data?.userLevel?.id == 794 ||
|
||||
profile?.data?.data?.userLevel?.parentLevelId == 761
|
||||
|
|
@ -532,10 +534,10 @@ const LoginForm = () => {
|
|||
</div>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
type="button"
|
||||
fullWidth
|
||||
// onClick={handleEmailValidation}
|
||||
// disabled={isPending}
|
||||
onClick={handleEmailValidation}
|
||||
disabled={isPending}
|
||||
>
|
||||
Selanjutnya
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -3017,9 +3017,9 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
|||
icon: "material-symbols:dashboard",
|
||||
submenus: [
|
||||
{
|
||||
href: "/dashboard/executive-data",
|
||||
href: "/dashboard/executive",
|
||||
label: "Executive",
|
||||
active: pathname === "/dashboard/executive-data",
|
||||
active: pathname === "/dashboard/executive",
|
||||
icon: "heroicons:arrow-trending-up",
|
||||
children: [],
|
||||
},
|
||||
|
|
@ -3348,9 +3348,9 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
|||
icon: "material-symbols:dashboard",
|
||||
submenus: [
|
||||
{
|
||||
href: "/dashboard/executive-data",
|
||||
href: "/dashboard/executive",
|
||||
label: "Executive",
|
||||
active: pathname === "/dashboard/executive-data",
|
||||
active: pathname === "/dashboard/executive",
|
||||
icon: "heroicons:arrow-trending-up",
|
||||
children: [],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -137,3 +137,13 @@ export async function postAdvertisements(data: any) {
|
|||
};
|
||||
return httpPostInterceptor(url, data, headers);
|
||||
}
|
||||
|
||||
export async function detailAdvertisements(id: any) {
|
||||
const url = `advertisements?id=${id}`;
|
||||
return httpGetInterceptor(url);
|
||||
}
|
||||
|
||||
export async function deleteAdvertisements(id: string | number) {
|
||||
const url = `advertisements?id=${id}`;
|
||||
return httpDeleteInterceptor(url);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue