fix: admin page section

This commit is contained in:
Sabda Yagra 2025-09-30 12:49:54 +07:00
parent 6be42d03e4
commit 7012e43ef9
12 changed files with 437 additions and 29 deletions

View File

@ -188,7 +188,7 @@ const AddExpertTable = () => {
}
return (
<div className="w-full overflow-x-auto bg-white dark:bg-black p-4 rounded-sm space-y-3">
<div className="w-full overflow-x-auto bg-slate-50 dark:bg-black p-4 rounded-sm space-y-3 border">
<div className="flex justify-between mb-10 items-center">
<p className="text-xl font-medium text-default-900">Tenaga Ahli</p>
<Link href="/admin/add-experts/create">
@ -239,7 +239,7 @@ const AddExpertTable = () => {
<Table className="overflow-hidden">
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id} className="bg-default-200">
<TableRow key={headerGroup.id} className="bg-slate-200">
{headerGroup.headers.map((header) => (
<TableHead key={header.id}>
{header.isPlaceholder

View File

@ -47,27 +47,19 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
import { paginationBlog } from "@/service/blog/blog";
import { ticketingPagination } from "@/service/ticketing/ticketing";
import { Badge } from "@/components/ui/badge";
import { useRouter, useSearchParams } from "next/navigation";
import TablePagination from "@/components/table/table-pagination";
import columns from "./column";
import { getPlanningPagination } from "@/service/agenda-setting/agenda-setting";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import {
listDataMedia,
listDataMediaBroadCast,
} from "@/service/broadcast/broadcast";
import { listEnableCategory } from "@/service/content/content";
import { Checkbox } from "@/components/ui/checkbox";
import { close, loading } from "@/config/swal";
import { Link } from "@/i18n/routing";
import { listDataMediaBroadCast } from "@/service/service/broadcast/broadcast";
const BroadcastEmailTable = () => {
const router = useRouter();

View File

@ -37,7 +37,6 @@ import {
TrendingUp,
UserIcon,
} from "lucide-react";
import { cn } from "@/lib/utils";
import {
DropdownMenu,
DropdownMenuContent,
@ -47,27 +46,19 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
import { paginationBlog } from "@/service/blog/blog";
import { ticketingPagination } from "@/service/ticketing/ticketing";
import { Badge } from "@/components/ui/badge";
import { useRouter, useSearchParams } from "next/navigation";
import TablePagination from "@/components/table/table-pagination";
import columns from "./column";
import { getPlanningPagination } from "@/service/agenda-setting/agenda-setting";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import {
listDataMedia,
listDataMediaBroadCast,
} from "@/service/broadcast/broadcast";
import { listEnableCategory } from "@/service/content/content";
import { Checkbox } from "@/components/ui/checkbox";
import { close, loading } from "@/config/swal";
import { Link } from "@/i18n/routing";
import { listDataMediaBroadCast } from "@/service/service/broadcast/broadcast";
const BroadcastWhatsAppTable = () => {
const router = useRouter();

View File

@ -36,7 +36,7 @@ export default function ManagementUser() {
<ManagementUserVisualization />
</section>
<section className="flex flex-col gap-2 bg-white dark:bg-black rounded-lg p-3 mt-5">
<section className="flex flex-col gap-2 bg-slate-50 dark:bg-black rounded-lg p-3 mt-5 border">
<div className="flex justify-between py-3">
<p className="text-lg">
Data User {isInternal ? "Internal" : "Eksternal"}

View File

@ -236,7 +236,7 @@ const ResultTable = () => {
};
return (
<div className="w-full overflow-x-auto bg-white dark:bg-black p-4 rounded-sm space-y-3 border ">
<div className="w-full overflow-x-auto bg-slate-50 dark:bg-black p-4 rounded-sm space-y-3 border ">
<div className="flex flex-col sm:flex-row lg:flex-row justify-end sm:items-center md:items-center lg:items-center">
<div className=" flex flex-row justify-end items-center gap-3">
<div className="flex items-center">

View File

@ -135,7 +135,7 @@ export default function TrackingBeritaCard() {
};
return (
<Card>
<Card className="border bg-slate-50">
<div className="p-4 space-y-4">
<div className="flex justify-between items-center">
<Input

View File

@ -266,10 +266,10 @@ const SurveyListTable = () => {
</ResponsiveContainer>
</div>
<Table className="overflow-hidden">
<Table className="overflow-hidden border">
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id} className="bg-default-200">
<TableRow key={headerGroup.id} className="bg-slate-100">
{headerGroup.headers.map((header) => (
<TableHead key={header.id}>
{header.isPlaceholder

View File

@ -0,0 +1,107 @@
"use client";
import Image from "next/image";
import { useEffect, useState } from "react";
import { useParams } from "next/navigation";
import { loading, close } from "@/config/swal";
import { getMediaBlastBroadCast } from "@/service/service/broadcast/broadcast";
interface BroadcastDetail {
id: number;
body: string;
subject: string;
sendTime: string;
thumbnail: string;
contentUrl: string;
}
export default function DetailContentBlast() {
const params = useParams();
const { id } = params as { id: string };
const [detail, setDetail] = useState<BroadcastDetail | null>(null);
const [notFound, setNotFound] = useState(false);
useEffect(() => {
fetchDetailData();
}, [id]);
async function fetchDetailData() {
loading();
try {
const res = await getMediaBlastBroadCast(id);
close();
const detailData = res?.data?.data;
let updatedUrl = detailData.contentUrl;
const domainsToUpdate = ["mediahub.polri.go.id", "netidhub.com"];
domainsToUpdate.forEach((domain) => {
if (
updatedUrl.includes(domain) &&
!updatedUrl.includes(`${domain}/in`)
) {
updatedUrl = updatedUrl.replace(domain, `${domain}/in`);
}
});
if (detailData && detailData.id === Number(id)) {
setDetail({
id: detailData.id,
body: detailData.body,
subject: detailData.subject,
sendTime: detailData.sendTime,
thumbnail: detailData.thumbnail,
contentUrl: updatedUrl,
});
} else {
setNotFound(true);
}
} catch (error) {
close();
console.error("Failed to fetch broadcast detail:", error);
setNotFound(true);
}
}
if (notFound) {
return <div className="text-red-500 p-4">Data tidak ditemukan.</div>;
}
if (!detail) {
return <div className="text-gray-500 p-4">Loading preview...</div>;
}
return (
<div className="bg-white rounded-md w-full p-4">
<p className="text-xl font-semibold py-1">Preview</p>
<div className="bg-[#ddf7c8] p-4 rounded-xl mx-auto space-y-4">
<div className="flex gap-4">
<Image
src={detail.thumbnail}
alt="Media Thumbnail"
width={250}
height={200}
className="rounded-md object-cover"
/>
</div>
<div className="text-sm text-black font-bold">{detail.subject}</div>
<p>
Selengkapnya silakan cek di sini:{" "}
<a
href={detail.contentUrl}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 underline"
>
{detail.contentUrl}
</a>
</p>
<div className="text-right text-xs text-gray-500">
{detail.sendTime}
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,318 @@
"use client";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import {
Form,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import {
getLocaleTime,
getLocaleTimestamp,
textEllipsis,
} from "@/utils/globals";
import { Link, useRouter } from "@/i18n/routing";
import { useEffect, useRef, useState } from "react";
import { useParams } from "next/navigation";
import Select from "react-select";
import makeAnimated from "react-select/animated";
import { Textarea } from "@/components/ui/textarea";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import dynamic from "next/dynamic";
import { detailMediaSummary, getMediaBlastCampaignList, saveMediaBlastBroadcast } from "@/service/service/broadcast/broadcast";
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
const animatedComponent = makeAnimated();
const FormSchema = z.object({
title: z.string({
required_error: "Required",
}),
url: z.string({
required_error: "Required",
}),
thumbnail: z.string({
required_error: "Required",
}),
detail: z.string({
required_error: "Required",
}),
selected: z
.array(
z.object({
id: z.number(),
label: z.string(),
value: z.string(),
})
)
.refine((value) => value.length > 0, {
message: "Required",
}),
});
interface Campaign {
id: string;
name: string;
}
export default function ContentBlast(props: { type: string }) {
const editor = useRef(null);
const id = useParams()?.id;
const MySwal = withReactContent(Swal);
const router = useRouter();
const { type } = props;
const [dataSelectCampaign, setDataSelectCampaign] = useState<Campaign[]>([]);
const [openModal, setOpenModal] = useState(false);
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: { selected: [], detail: "" },
});
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
if (form.getValues("detail") == "") {
form.setError("detail", {
type: "manual",
message: "Required",
});
} else {
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 save = async (data: z.infer<typeof FormSchema>) => {
const selectedCampaign = form.getValues("selected");
for (let i = 0; i < selectedCampaign.length; i++) {
const reqData = {
mediaUploadId: id,
mediaBlastCampaignId: selectedCampaign[i].id,
subject: type == "wa" ? `*${data.title}*` : data.title,
body: data.detail?.replace(/\n/g, ""),
type: type,
isScheduled: false,
thumbnail: data?.thumbnail,
sendDate: getLocaleTimestamp(new Date()),
sendTime: getLocaleTime(new Date()),
contentUrl: data.url,
};
console.log("req =>", reqData);
const response = await saveMediaBlastBroadcast(reqData);
}
setOpenModal(true);
};
useEffect(() => {
async function initState() {
const response = await detailMediaSummary(String(id));
const details = response?.data?.data;
let pageUrl = details?.pageUrl || "";
if (pageUrl.includes("mediahub.polri.go.id")) {
pageUrl = pageUrl.replace(
/(\.id)(\/|$)/,
(match: any, p1: any, p2: any) => {
return p2.startsWith("/in") ? match : `${p1}/in${p2}`;
}
);
}
if (details != undefined) {
form.setValue("thumbnail", details.smallThumbnailLink);
let body = `<div><p>Berita hari ini !!!</p>
<div style='margin-top:20px;border:1px solid;border-radius:10px;width:500px;background-color:#f7f7f7'>
<div>
<img style='width:500px;height:auto;border-radius:10px;object-fit:cover' src='${
details?.smallThumbnailLink
}'>
</div>
<a style='padding:5px 20px;margin:0;text-decoration:none' href='${pageUrl}'>${pageUrl}</a>
<h3 style='padding:5px 20px;margin:0'>${details?.title}</h3>
<p style='padding:0 20px;margin:0;margin-bottom:10px'>
${textEllipsis(details?.description, 150)}
</p>
</div>
</div>`;
form.setValue("title", `${details?.title}`);
form.setValue(
"url",
details?.pageUrl || "https://mediahub.polri.go.id"
);
if (type == "wa") {
body = `${textEllipsis(details?.description, 150)}`;
form.setValue("detail", body);
} else {
form.setValue("detail", body);
}
}
}
async function getCampaign() {
const response = await getMediaBlastCampaignList();
const campaign = response?.data?.data?.content;
handleLabelCampaign(campaign);
console.log(campaign);
}
initState();
getCampaign();
}, [id]);
function handleLabelCampaign(data: any) {
const optionArr: any = [];
data.map((option: any) => {
optionArr.push({
id: option.id,
label: option.title,
value: option.title,
});
});
console.log("option arr", optionArr);
setDataSelectCampaign(optionArr);
}
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-3 bg-white rounded-sm p-4"
>
<p className="fonnt-semibold">Broadcast</p>
<FormField
control={form.control}
name="selected"
render={({ field }) => (
<FormItem>
<FormLabel>Subject</FormLabel>
<Select
className="z-50"
options={dataSelectCampaign}
closeMenuOnSelect={false}
components={animatedComponent}
onChange={field.onChange}
isMulti
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Subject</FormLabel>
<Input
value={field.value}
placeholder="Masukkan Judul"
onChange={field.onChange}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="detail"
render={({ field }) => (
<FormItem>
<FormLabel>Detail Perencanaan</FormLabel>
{type === "wa" ? (
<Textarea value={field.value} onChange={field.onChange} />
) : (
<CustomEditor
onChange={field.onChange}
initialData={field.value}
/>
)}
<FormMessage />
</FormItem>
)}
/>
<div className="flex flex-row gap-2 mt-4 pt-4">
<Button
size="md"
type="button"
variant="outline"
color="destructive"
className="text-xs"
>
Cancel
</Button>
<Button size="md" type="submit" color="primary" className="text-xs">
Submit
</Button>
</div>
<Dialog open={openModal}>
<DialogContent>
<DialogHeader>
<DialogTitle>Terkirim !!</DialogTitle>
</DialogHeader>
<div className="flex flex-col justify-center items-center gap-3 mb-3 text-sm">
<img
src="/assets/img/illust-for-broadcast-sent.png"
className="w-[70%]"
/>
Untuk melihat Email Terkirim silahkan cek menu Sent!
</div>
<DialogFooter className="flex justify-center">
{/* <Link
href={`/admin/broadcast/campaign-list/detail/${
form.getValues("selected")[0]?.id
}`}
>
<Button type="button" color="success">
Menu "Sent"
</Button>
</Link> */}
<Button
type="button"
onClick={() => router.push("/admin/broadcast")}
color="primary"
>
Okay
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</form>
</Form>
);
}

View File

@ -83,7 +83,7 @@ export default function ContentProductionVisualization() {
};
return (
<div className="flex flex-col gap-2 bg-white dark:bg-black rounded-lg p-3">
<div className="flex flex-col gap-2 bg-slate-50 dark:bg-black rounded-lg p-3 border">
<p className="text-lg">
<b>
{isInternational[0]

View File

@ -28,7 +28,7 @@ export default function ManagementUserVisualization() {
}, []);
return (
<div className="flex flex-col gap-2 bg-white dark:bg-black rounded-lg p-3">
<div className="flex flex-col gap-2 dark:bg-black rounded-lg p-3 border bg-slate-50">
{isInternational ? (
<p className="font-semibold">STATISTICS TO THE NUMBER OF USERS</p>
) : (

View File

@ -96,7 +96,7 @@ export default function PatternRelationVisualization() {
};
return (
<div className="flex flex-col gap-2 bg-white dark:bg-black rounded-lg p-3">
<div className="flex flex-col gap-2 bg-slate-50 dark:bg-black rounded-lg p-3 border">
<p className="text-lg">
<b>
{isInternational[0]