pull main

This commit is contained in:
Sabda Yagra 2025-05-06 07:58:13 +07:00
commit fbc0db0ab4
98 changed files with 7678 additions and 1370 deletions

View File

@ -87,7 +87,7 @@ export default function ContentManagement() {
? "views/2023_08_MediaHUB-KtnMgt_Rev100/db-ktn-intl?"
: "views/2023_08_MediaHUB-KtnMgt_Rev100/db-ktn-intl?"
: `views/2023_08_MediaHUB-KtnMgt_Rev100/db-ktn-intl?provinsi-polda=${poldaState}&`;
const param = ":embed=yes&:toolbar=yes&:iframeSizedToWindow=true";
const param = ":embed=yes&:toolbar=no&:iframeSizedToWindow=true";
useEffect(() => {
async function initState() {

View File

@ -53,7 +53,7 @@ export default function EmergencyIssue() {
: "views/2023_08_MediaHUB-KtnMgt_Rev100/db-emg-issue?"
: `views/2023_08_MediaHUB-KtnMgt_Rev100/db-emg-issue?provinsi-polda=${provState}&`;
const param = ":embed=yes&:toolbar=yes&:iframeSizedToWindow=true";
const param = ":embed=yes&:toolbar=no&:iframeSizedToWindow=true";
useEffect(() => {
async function initState() {

View File

@ -53,7 +53,7 @@ export default function FeedbackCenter() {
: "views/2023_08_MediaHUB-KtnMgt_Rev100/db-tickets?"
: `views/2023_08_MediaHUB-KtnMgt_Rev100/db-tickets?provinsi-polda=${provState}&`;
const param = ":embed=yes&:toolbar=yes&:iframeSizedToWindow=true";
const param = ":embed=yes&:toolbar=no&:iframeSizedToWindow=true";
useEffect(() => {
async function initState() {

View File

@ -69,7 +69,7 @@ export default function ContentManagement() {
: "views/2023_08_MediaHUB-KtnMgt_Rev100/db-ktn-act-jnl?"
: `views/2023_08_MediaHUB-KtnMgt_Rev100/db-ktn-act-jnl?provinsi-polda=${poldaState}&`;
const param = ":embed=yes&:toolbar=yes&:iframeSizedToWindow=true";
const param = ":embed=yes&:toolbar=no&:iframeSizedToWindow=true";
useEffect(() => {
async function initState() {

View File

@ -13,6 +13,9 @@ import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Link, useRouter } from "@/i18n/routing";
import { format } from "date-fns";
import header from "@/components/partials/header";
import { date } from "zod";
const columns: ColumnDef<any>[] = [
{
@ -23,50 +26,61 @@ const columns: ColumnDef<any>[] = [
{
accessorKey: "createdAt",
header: "Tanggal",
cell: ({ row }) => (
<span className="normal-case">{row.getValue("createdAt")}</span>
),
cell: ({ row }) => {
const createdAt = row.getValue("createdAt") as
| string
| number
| undefined;
const formattedDate =
createdAt && !isNaN(new Date(createdAt).getTime())
? format(new Date(createdAt), "dd-MM-yyyy HH:mm:ss")
: "-";
return <span className="whitespace-nowrap">{formattedDate}</span>;
},
},
{
accessorKey: "account-type",
accessorKey: "createdByCategory",
header: "Jenis Akun",
cell: ({ row }) => (
<span className="normal-case">{row.getValue("account-type")}</span>
<span className="normal-case">{row.getValue("createdByCategory")}</span>
),
},
{
accessorKey: "userName",
accessorKey: "createdByUsername",
header: "UserName",
cell: ({ row }) => (
<span className="normal-case">{row.getValue("userName")}</span>
<span className="normal-case">{row.getValue("createdByUsername")}</span>
),
},
{
accessorKey: "accessMediahub",
accessorKey: "accessFrequency",
header: "Akses Mediahub",
cell: ({ row }) => (
<span className="normal-case">{row.getValue("accessMediahub")}</span>
<span className="normal-case">{row.getValue("accessFrequency")}</span>
),
},
{
accessorKey: "desaignWeb",
accessorKey: "uiExperienceDesign",
header: "Tampilan Desain Web",
cell: ({ row }) => (
<span className="normal-case">{row.getValue("desaignWeb")}</span>
<span className="normal-case">{row.getValue("uiExperienceDesign")}</span>
),
},
{
accessorKey: "navigation",
accessorKey: "uiExperienceNavigation",
header: "Kemudahan Navigasi",
cell: ({ row }) => (
<span className="normal-case">{row.getValue("navigation")}</span>
<span className="normal-case">
{row.getValue("uiExperienceNavigation")}
</span>
),
},
{
accessorKey: "fastAccess",
accessorKey: "uiExperienceSpeed",
header: "Kecepatan Akses",
cell: ({ row }) => (
<span className="normal-case">{row.getValue("fastAccess")}</span>
<span className="normal-case">{row.getValue("uiExperienceSpeed")}</span>
),
},
{
@ -86,13 +100,12 @@ 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={`/admin/broadcast/campaign-list/detail/${row.original.id}`}
>
Detail
</Link>
</DropdownMenuItem>
<Link href={`/admin/survey/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 mt-1" />
View
</DropdownMenuItem>
</Link>
</DropdownMenuContent>
</DropdownMenu>
);

View File

@ -90,6 +90,7 @@ import {
XAxis,
YAxis,
} from "recharts";
import { getSurveyData } from "@/service/survey/survey";
const data = [
{
@ -181,7 +182,7 @@ const SurveyListTable = () => {
async function fetchData() {
try {
loading();
const res = await getMediaBlastCampaignPage(page - 1);
const res = await getSurveyData();
const data = res?.data?.data;
const contentData = data?.content;
contentData.forEach((item: any, index: number) => {
@ -249,7 +250,7 @@ const SurveyListTable = () => {
</div>
</div>
</div>
<div className="min-h-screen p-3">
<div className="p-3">
<h2 className="text-center font-semibold mb-4">
Survei Kepuasan Pengguna MediaHub Polri
</h2>

View File

@ -0,0 +1,17 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormBlogDetail from "@/components/form/blog/blog--detail-form";
import FormSurvey from "@/components/landing-page/survey";
import FormSurveyDetail from "@/components/form/survey/survey-detail";
const SurveyDetailPage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormSurveyDetail />
</div>
</div>
);
};
export default SurveyDetailPage;

View File

@ -1,12 +1,13 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormBlogDetail from "@/components/form/blog/blog--detail-form";
import FormSurveyDetailPage from "@/components/form/survey/survey-detail";
const BlogDetailPage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormBlogDetail />
<FormSurveyDetailPage />
</div>
</div>
);

View File

@ -43,6 +43,7 @@ import { InputGroup, InputGroupText } from "@/components/ui/input-group";
import columns from "./columns";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
@ -192,56 +193,86 @@ const TableSPIT = () => {
/>
</InputGroup>
</div>
<div className="flex items-center py-4">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md">
Filter <ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="w-64 h-[150px] overflow-y-auto"
>
<div className="flex flex-row justify-between my-1 mx-1">
<p>Filter</p>
</div>
<div className="mx-2 my-1">
<Label>{t("date")}</Label>
<Input
type="date"
value={dateFilter}
onChange={(e) => setDateFilter(e.target.value)}
className="max-w-sm"
/>
</div>
<Label className="ml-2 mt-2">Status</Label>
<div className="flex items-center px-4 py-1">
<input
type="checkbox"
id="status-2"
className="mr-2"
checked={statusFilter.includes(1)}
onChange={() => handleStatusCheckboxChange(1)}
/>
<label htmlFor="status-2" className="text-sm">
Menunggu Review
</label>
</div>
<div className="flex items-center px-4 py-1">
<input
type="checkbox"
id="status-2"
className="mr-2"
checked={statusFilter.includes(2)}
onChange={() => handleStatusCheckboxChange(2)}
/>
<label htmlFor="status-2" className="text-sm">
Diterima
</label>
</div>
</DropdownMenuContent>
</DropdownMenu>
<div className="flex fle-row items-center gap-3">
<div className="flex items-center py-4">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md">
Filter <ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="w-64 h-[150px] overflow-y-auto"
>
<div className="flex flex-row justify-between my-1 mx-1">
<p>Filter</p>
</div>
<div className="mx-2 my-1">
<Label>{t("date")}</Label>
<Input
type="date"
value={dateFilter}
onChange={(e) => setDateFilter(e.target.value)}
className="max-w-sm"
/>
</div>
<Label className="ml-2 mt-2">Status</Label>
<div className="flex items-center px-4 py-1">
<input
type="checkbox"
id="status-2"
className="mr-2"
checked={statusFilter.includes(1)}
onChange={() => handleStatusCheckboxChange(1)}
/>
<label htmlFor="status-2" className="text-sm">
Menunggu Review
</label>
</div>
<div className="flex items-center px-4 py-1">
<input
type="checkbox"
id="status-2"
className="mr-2"
checked={statusFilter.includes(2)}
onChange={() => handleStatusCheckboxChange(2)}
/>
<label htmlFor="status-2" className="text-sm">
Diterima
</label>
</div>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="flex items-center py-4">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md">
Columns <ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>

View File

@ -26,6 +26,7 @@ import {
} from "@/components/ui/table";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
ChevronDown,
ChevronLeft,
ChevronRight,
Eye,
@ -39,6 +40,7 @@ import {
import { cn } from "@/lib/utils";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
@ -163,17 +165,47 @@ const MediahubTable = () => {
/>
</InputGroup>
</div>
<div className="w-[150px] md:w-[250px] lg:w-[250px]">
<Input
placeholder="Filter Status..."
value={
(table.getColumn("status")?.getFilterValue() as string) ?? ""
}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
table.getColumn("status")?.setFilterValue(event.target.value)
}
className="max-w-sm "
/>
<div className="flex flex-row items-center gap-3">
<div className="w-[150px] md:w-[250px] lg:w-[250px] border border-black rounded-md">
<Input
placeholder="Filter Status..."
value={
(table.getColumn("status")?.getFilterValue() as string) ?? ""
}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
table.getColumn("status")?.setFilterValue(event.target.value)
}
className="max-w-sm "
/>
</div>
<div className="flex items-center py-4">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md">
Columns <ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
<Table className="overflow-hidden mt-3">

View File

@ -26,6 +26,7 @@ import {
} from "@/components/ui/table";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
ChevronDown,
ChevronLeft,
ChevronRight,
Eye,
@ -39,6 +40,7 @@ import {
import { cn } from "@/lib/utils";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
@ -162,17 +164,47 @@ const MedsosTable = () => {
/>
</InputGroup>
</div>
<div className="w-[150px] md:w-[250px] lg:w-[250px]">
<Input
placeholder="Filter Status..."
value={
(table.getColumn("status")?.getFilterValue() as string) ?? ""
}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
table.getColumn("status")?.setFilterValue(event.target.value)
}
className="max-w-sm "
/>
<div className="flex flex-row items-center gap-3">
<div className="w-[150px] md:w-[250px] lg:w-[250px]">
<Input
placeholder="Filter Status..."
value={
(table.getColumn("status")?.getFilterValue() as string) ?? ""
}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
table.getColumn("status")?.setFilterValue(event.target.value)
}
className="max-w-sm "
/>
</div>
<div className="flex items-center py-4">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md">
Columns <ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
<Table className="overflow-hidden mt-3">

View File

@ -49,7 +49,7 @@ const useTableColumns = ({
},
{
accessorKey: "createdAt",
header: t("upload-date"),
header: t("generate-date"),
cell: ({ row }) => {
const createdAt = row.getValue("createdAt") as
| string
@ -69,6 +69,12 @@ const useTableColumns = ({
cell: ({ row }) => <span className="">{row.getValue("version")}</span>,
},
{
accessorKey: "status",
header: t("status"),
cell: ({ row }) => <span className="">{row.getValue("status")}</span>,
},
{
id: "actions",
accessorKey: "action",

View File

@ -36,6 +36,7 @@ import { Button } from "@/components/ui/button";
import useTableColumns from "./columns";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
@ -172,8 +173,8 @@ const EventTable = () => {
/>
</InputGroup>
</div>
<div className="flex flex-row">
<div className="mx-3">
<div className="flex flex-row gap-3 items-center">
<div className="">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
@ -244,6 +245,34 @@ const EventTable = () => {
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="flex items-center">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md">
Columns <ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
<Table className="overflow-hidden mt-3">

View File

@ -50,6 +50,7 @@ import { useTranslations } from "next-intl";
import useTableColumns from "./columns";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
@ -186,8 +187,8 @@ const PressConferenceTable = () => {
/>
</InputGroup>
</div>
<div className="flex flex-row gap-3">
<div className="mx-3">
<div className="flex flex-row items-center gap-3">
<div className="">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
@ -258,6 +259,34 @@ const PressConferenceTable = () => {
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="flex items-center">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md">
Columns <ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
<Table className="overflow-hidden mt-3">

View File

@ -51,6 +51,7 @@ import { Link } from "@/i18n/routing";
import useTableColumns from "./columns";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
@ -187,8 +188,8 @@ const PressReleaseTable = () => {
/>
</InputGroup>
</div>
<div className="flex flex-row">
<div className="mx-3">
<div className="flex flex-row items-center gap-3">
<div className="">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
@ -259,6 +260,34 @@ const PressReleaseTable = () => {
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="flex items-center">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md">
Columns <ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
<Table className="overflow-hidden mt-3">

View File

@ -1,8 +1,8 @@
import * as React from "react";
import { ColumnDef } from "@tanstack/react-table";
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
import { cn } from "@/lib/utils";
import { Eye, MoreVertical, SquarePen, Trash2, Upload } from "lucide-react";
import { cn, getCookiesDecrypt } from "@/lib/utils";
import {
DropdownMenu,
DropdownMenuContent,
@ -105,6 +105,7 @@ const useTableColumns = () => {
cell: ({ row }) => {
const router = useRouter();
const MySwal = withReactContent(Swal);
const roleId = Number(getCookiesDecrypt("urie")) || 0;
async function deleteProcess(id: any) {
loading();
@ -170,6 +171,16 @@ const useTableColumns = () => {
Edit
</DropdownMenuItem>
</Link>
{roleId == 12 && (
<Link
href={`/contributor/task-ta/upload-task/${row.original.id}`}
>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<Upload className="w-4 h-4 me-1.5" />
Upload Tugas
</DropdownMenuItem>
</Link>
)}
<DropdownMenuItem
onClick={() => TaskDelete(row.original.id)}
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"

View File

@ -5,7 +5,11 @@ import { Button } from "@/components/ui/button";
import { UploadIcon } from "lucide-react";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import { Link } from "@/components/navigation";
import { checkAuthorization, checkLoginSession } from "@/lib/utils";
import {
checkAuthorization,
checkLoginSession,
getCookiesDecrypt,
} from "@/lib/utils";
import React, { useEffect } from "react";
import { useTranslations } from "next-intl";
import TaskTaTable from "./components/task-ta-table";
@ -20,6 +24,7 @@ const TaskTaPage = () => {
initState();
}, []);
const roleId = Number(getCookiesDecrypt("urie")) || 0;
return (
<div>
@ -33,12 +38,14 @@ const TaskTaPage = () => {
{t("tabel")} {t("task-ta")}
</div>
<div className="flex-none">
<Link href={"/contributor/task-ta/create"}>
<Button color="primary" className="text-white">
<UploadIcon size={18} className="mr-2" />
{t("create-task")}
</Button>
</Link>
{roleId !== 12 && (
<Link href={"/contributor/task-ta/create"}>
<Button color="primary" className="text-white">
<UploadIcon size={18} className="mr-2" />
{t("create-task")}
</Button>
</Link>
)}
</div>
</div>
</CardTitle>

View File

@ -0,0 +1,17 @@
import { Card, CardContent } from "@/components/ui/card";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormTaskTa from "@/components/form/task-ta/task-ta-form";
import FormTaskTaNew from "@/components/form/task-ta/task-ta-upload-form";
const TaskTaUploadPage = () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormTaskTaNew />
</div>
</div>
);
};
export default TaskTaUploadPage;

View File

@ -40,6 +40,7 @@ import {
import { cn } from "@/lib/utils";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuRadioGroup,
@ -338,6 +339,35 @@ const TaskTable = () => {
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="flex items-center py-4">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md">
Columns <ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
{/* <div className="flex-none">
<Input

View File

@ -61,6 +61,7 @@ import {
import { getOnlyDate } from "@/utils/globals";
import { useParams } from "next/navigation";
import { getPlanningById } from "@/service/planning/planning";
import dynamic from "next/dynamic";
const FormSchema = z.object({
date: z.date({
@ -118,7 +119,19 @@ const units = [
id: "3",
label: "Polres",
},
{
id: "4",
label: "Satker",
},
];
const ViewEditor = dynamic(
() => {
return import("@/components/editor/view-editor");
},
{ ssr: false }
);
export default function DetailDaily() {
const id = useParams()?.id;
const MySwal = withReactContent(Swal);
@ -725,16 +738,10 @@ export default function DetailDaily() {
<FormField
control={form.control}
name="detail"
render={({ field }) => (
render={({ field: { onChange, value } }) => (
<FormItem>
<FormLabel>Detail Perencanaan</FormLabel>
<JoditEditor
ref={editor}
value={field.value}
config={{ readonly: true }}
className="dark:text-black"
onChange={field.onChange}
/>
<ViewEditor initialData={value} />
<FormMessage />
</FormItem>

View File

@ -61,6 +61,7 @@ import {
import { getOnlyDate } from "@/utils/globals";
import { useParams } from "next/navigation";
import { getPlanningById } from "@/service/planning/planning";
import dynamic from "next/dynamic";
const FormSchema = z.object({
date: z.date({
@ -118,7 +119,18 @@ const units = [
id: "3",
label: "Polres",
},
{
id: "4",
label: "Satker",
},
];
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function EditDaily() {
const id = useParams()?.id;
const MySwal = withReactContent(Swal);
@ -728,15 +740,10 @@ export default function EditDaily() {
<FormField
control={form.control}
name="detail"
render={({ field }) => (
render={({ field: { onChange, value } }) => (
<FormItem>
<FormLabel>Detail Perencanaan</FormLabel>
<JoditEditor
ref={editor}
value={field.value}
className="dark:text-black"
onChange={field.onChange}
/>
<CustomEditor onChange={onChange} initialData={value} />
<FormMessage />
</FormItem>

View File

@ -59,6 +59,7 @@ import {
savePlanning,
} from "@/service/agenda-setting/agenda-setting";
import { getOnlyDate } from "@/utils/globals";
import dynamic from "next/dynamic";
const FormSchema = z.object({
date: z.date({
@ -116,7 +117,18 @@ const units = [
id: "3",
label: "Polres",
},
{
id: "4",
label: "Satker",
},
];
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function CreateDaily() {
const MySwal = withReactContent(Swal);
const [listDest, setListDest] = useState<any>([]);
@ -694,15 +706,10 @@ export default function CreateDaily() {
<FormField
control={form.control}
name="detail"
render={({ field }) => (
render={({ field: { onChange, value } }) => (
<FormItem>
<FormLabel>Detail Perencanaan</FormLabel>
<JoditEditor
ref={editor}
value={field.value}
className="dark:text-black"
onChange={field.onChange}
/>
<CustomEditor onChange={onChange} initialData={value} />
<FormMessage />
</FormItem>

View File

@ -32,6 +32,7 @@ import { close, error, loading } from "@/config/swal";
import { savePlanning } from "@/service/agenda-setting/agenda-setting";
import { getPlanningById } from "@/service/planning/planning";
import { useParams } from "next/navigation";
import dynamic from "next/dynamic";
const FormSchema = z.object({
month: z.date({
@ -44,6 +45,13 @@ const FormSchema = z.object({
required_error: "Required",
}),
});
const ViewEditor = dynamic(
() => {
return import("@/components/editor/view-editor");
},
{ ssr: false }
);
export default function DetailMonthly() {
const id = useParams()?.id;
const MySwal = withReactContent(Swal);
@ -215,16 +223,10 @@ export default function DetailMonthly() {
<FormField
control={form.control}
name="detail"
render={({ field }) => (
render={({ field: { onChange, value } }) => (
<FormItem>
<FormLabel>Detail Perencanaan</FormLabel>
<JoditEditor
ref={editor}
config={{ readonly: true }}
value={field.value}
className="dark:text-black"
onChange={field.onChange}
/>
<ViewEditor initialData={value} />
<FormMessage />
</FormItem>

View File

@ -32,6 +32,7 @@ import { close, error, loading } from "@/config/swal";
import { savePlanning } from "@/service/agenda-setting/agenda-setting";
import { getPlanningById } from "@/service/planning/planning";
import { useParams } from "next/navigation";
import dynamic from "next/dynamic";
const FormSchema = z.object({
month: z.date({
@ -44,6 +45,14 @@ const FormSchema = z.object({
required_error: "Required",
}),
});
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function EditMonthly() {
const id = useParams()?.id;
const MySwal = withReactContent(Swal);
@ -108,6 +117,8 @@ export default function EditMonthly() {
};
const save = async (data: z.infer<typeof FormSchema>) => {
const month = new Date(data.month).getMonth() + 1;
const year = new Date(data.month).getFullYear();
const reqData = {
id: id,
planningTypeId: 1,
@ -115,9 +126,7 @@ export default function EditMonthly() {
time: "3",
description: data.detail,
username: "",
date: `${new Date(data.month).getMonth() + 1}/${new Date(
data.month
).getFullYear()}`,
date: `${month.toString().padStart(2, "0")}/${year}`,
status: "Open",
};
console.log("req", reqData, data.month);
@ -214,15 +223,10 @@ export default function EditMonthly() {
<FormField
control={form.control}
name="detail"
render={({ field }) => (
render={({ field: { onChange, value } }) => (
<FormItem>
<FormLabel>Detail Perencanaan</FormLabel>
<JoditEditor
ref={editor}
value={field.value}
className="dark:text-black"
onChange={field.onChange}
/>
<CustomEditor onChange={onChange} initialData={value} />
<FormMessage />
</FormItem>

View File

@ -30,6 +30,9 @@ import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { error } from "@/config/swal";
import { savePlanning } from "@/service/agenda-setting/agenda-setting";
import month from "react-datepicker/dist/month";
import year from "react-datepicker/dist/year";
import dynamic from "next/dynamic";
const FormSchema = z.object({
month: z.date({
@ -42,6 +45,12 @@ const FormSchema = z.object({
required_error: "Required",
}),
});
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function CreateMonthly() {
const MySwal = withReactContent(Swal);
const router = useRouter();
@ -77,15 +86,15 @@ export default function CreateMonthly() {
};
const save = async (data: z.infer<typeof FormSchema>) => {
const month = new Date(data.month).getMonth() + 1;
const year = new Date(data.month).getFullYear();
const reqData = {
planningTypeId: 1,
title: data.title,
time: "3",
description: data.detail,
username: "",
date: `${new Date(data.month).getMonth() + 1}/${new Date(
data.month
).getFullYear()}`,
// username: "",
date: `${month.toString().padStart(2, "0")}/${year}`,
status: "Open",
};
console.log("req", reqData, data.month);
@ -110,6 +119,7 @@ export default function CreateMonthly() {
const handleMonthSelect = (selectedDate: Date | undefined) => {
if (!selectedDate) return;
// Set ke tanggal 1 agar tidak ada hari yang diambil
const newDate = new Date(
selectedDate.getFullYear(),
selectedDate.getMonth(),
@ -198,15 +208,10 @@ export default function CreateMonthly() {
<FormField
control={form.control}
name="detail"
render={({ field }) => (
render={({ field: { onChange, value } }) => (
<FormItem>
<FormLabel>Detail Perencanaan</FormLabel>
<JoditEditor
ref={editor}
value={field.value}
className="dark:text-black"
onChange={field.onChange}
/>
<CustomEditor onChange={onChange} initialData={value} />
<FormMessage />
</FormItem>

View File

@ -44,6 +44,7 @@ import {
import dayjs from "dayjs";
import { getPlanningById } from "@/service/planning/planning";
import { useParams } from "next/navigation";
import dynamic from "next/dynamic";
const FormSchema = z.object({
week: z.object({
@ -61,7 +62,15 @@ const FormSchema = z.object({
required_error: "Required",
}),
});
export default function DetailMonthly() {
const ViewEditor = dynamic(
() => {
return import("@/components/editor/view-editor");
},
{ ssr: false }
);
export default function DetailWeekly() {
const id = useParams()?.id;
const MySwal = withReactContent(Swal);
const router = useRouter();
@ -227,16 +236,10 @@ export default function DetailMonthly() {
<FormField
control={form.control}
name="detail"
render={({ field }) => (
render={({ field: { onChange, value } }) => (
<FormItem>
<FormLabel>Detail Perencanaan</FormLabel>
<JoditEditor
ref={editor}
value={field.value}
className="dark:text-black"
onChange={field.onChange}
config={{ readonly: true }}
/>
<ViewEditor initialData={value} />
<FormMessage />
</FormItem>

View File

@ -44,6 +44,7 @@ import {
import dayjs from "dayjs";
import { getPlanningById } from "@/service/planning/planning";
import { useParams } from "next/navigation";
import dynamic from "next/dynamic";
const FormSchema = z.object({
week: z.object({
@ -61,7 +62,14 @@ const FormSchema = z.object({
required_error: "Required",
}),
});
export default function EditMonthly() {
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function EditWeekly() {
const id = useParams()?.id;
const MySwal = withReactContent(Swal);
const router = useRouter();
@ -225,15 +233,10 @@ export default function EditMonthly() {
<FormField
control={form.control}
name="detail"
render={({ field }) => (
render={({ field: { onChange, value } }) => (
<FormItem>
<FormLabel>Detail Perencanaan</FormLabel>
<JoditEditor
ref={editor}
value={field.value}
className="dark:text-black"
onChange={field.onChange}
/>
<CustomEditor onChange={onChange} initialData={value} />
<FormMessage />
</FormItem>

View File

@ -42,6 +42,14 @@ import {
SelectValue,
} from "@/components/ui/select";
import dayjs from "dayjs";
import dynamic from "next/dynamic";
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
const FormSchema = z.object({
week: z.object({
@ -260,15 +268,10 @@ export default function CreateMonthly() {
<FormField
control={form.control}
name="detail"
render={({ field }) => (
render={({ field: { onChange, value } }) => (
<FormItem>
<FormLabel>Detail Perencanaan</FormLabel>
<JoditEditor
ref={editor}
value={field.value}
className="dark:text-black"
onChange={field.onChange}
/>
<CustomEditor onChange={onChange} initialData={value} />
<FormMessage />
</FormItem>

View File

@ -65,6 +65,16 @@ const columns: ColumnDef<any>[] = [
Detail
</Link>
</DropdownMenuItem>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<Link
href={`/curator/task-plan/medsos-mediahub/create-daily/edit/${row.original.id}`}
>
Edit
</Link>
</DropdownMenuItem>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<a className="text-red-600">Delete</a>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);

View File

@ -0,0 +1,766 @@
"use client";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import { Input } from "@/components/ui/input";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Link, useRouter } from "@/i18n/routing";
import { CalendarIcon } from "lucide-react";
import React, { useEffect, useRef, useState } from "react";
import { cn } from "@/lib/utils";
import { format } from "date-fns";
import JoditEditor from "jodit-react";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { getUserLevelForAssignments } from "@/service/task";
import { list } from "postcss";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { close, error, loading } from "@/config/swal";
import { id } from "date-fns/locale";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
getWeeklyPlanList,
savePlanning,
} from "@/service/agenda-setting/agenda-setting";
import { getOnlyDate } from "@/utils/globals";
import { useParams } from "next/navigation";
import { getPlanningById } from "@/service/planning/planning";
import dynamic from "next/dynamic";
const FormSchema = z.object({
date: z.date({
required_error: "Required",
}),
title: z.string({
required_error: "Required",
}),
detail: z.string({
required_error: "Required",
}),
output: z.array(z.string()).refine((value) => value.some((item) => item), {
message: "Required",
}),
unit: z.array(z.string()).refine((value) => value.some((item) => item), {
message: "Required",
}),
type: z.string({
required_error: "Required",
}),
parentId: z.string({
required_error: "Required",
}),
});
const items = [
{
id: "2",
label: "Audio Visual",
},
{
id: "1",
label: "Foto",
},
{
id: "4",
label: "Audio",
},
{
id: "3",
label: "Text",
},
];
const units = [
{
id: "1",
label: "Mabes Polri",
},
{
id: "2",
label: "Polda",
},
{
id: "3",
label: "Polres",
},
{
id: "4",
label: "Satker",
},
];
const ViewEditor = dynamic(
() => {
return import("@/components/editor/view-editor");
},
{ ssr: false }
);
export default function DetailDaily() {
const id = useParams()?.id;
const MySwal = withReactContent(Swal);
const [listDest, setListDest] = useState<any>([]);
const router = useRouter();
const [weeklyList, setWeeklyList] = useState<any>();
const [selected, setSelected] = useState<{ [key: string]: boolean }>({});
const [selectAll, setSelectAll] = useState<{ [key: string]: boolean }>({});
useEffect(() => {
initFetch();
}, [id]);
async function initFetch() {
if (id != undefined) {
loading();
const res = await getPlanningById(id);
close();
if (res?.data?.data != undefined) {
const data = res?.data?.data;
console.log("data");
console.log("Data :", data);
form.setValue("title", data.title);
form.setValue("detail", data.description);
form.setValue("date", new Date(data.date));
form.setValue(
"output",
data.fileTypeOutput.split(",")?.length > 1
? data.fileTypeOutput.split(",")
: [data.fileTypeOutput]
);
form.setValue(
"unit",
data.assignedToLevel.split(",")?.length > 1
? data.assignedToLevel.split(",")
: [data.assignedToLevel]
);
form.setValue("type", String(data?.assignmentTypeId));
form.setValue("parentId", String(data?.parentId));
}
}
}
useEffect(() => {
getWeeklyPlanning();
}, []);
async function getWeeklyPlanning() {
const res = await getWeeklyPlanList(new Date().getDate(), 2);
if (res?.data !== null) {
const rawUser = res?.data?.data;
const optionArr = rawUser.map((option: any) => ({
id: option.id,
label: option.title,
value: String(option.id),
}));
setWeeklyList(optionArr);
}
}
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
unit: [],
output: [],
detail: "",
},
});
const editor = useRef(null);
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
console.log("data", data);
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 getSelectedString = () => {
return Object.keys(selected)
.filter((key) => selected[key])
.join(", ");
};
console.log("data", data, selected);
loading();
const reqData = {
planningTypeId: 2,
time: "1",
title: data.title,
assignmentTypeId: data.type, //string
description: data.detail,
assignedToLevel: unit?.join(","), //string
assignmentPurpose: getSelectedString(), //string
fileTypeOutput: data.output?.join(","), //string
status: "Open",
date: getOnlyDate(data.date),
// date:
// isPublish || isUpdate
// ? selectedDate?.length > 10
// ? data.date?.toISOString().slice(0, 10)
// : selectedDate
// : data.date?.toISOString().slice(0, 10),
parentId: Number(data.parentId), //number
assignmentMainTypeId: 1,
};
console.log("req =>", reqData);
const response = await savePlanning(reqData);
if (response?.error) {
error(response?.message);
return false;
}
close();
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
router.push("/curator/task-plan/mediahub");
}
});
};
const output = form.watch("output");
const isAllChecked = items.every((item) => output?.includes(item.id));
const unit = form.watch("unit");
const isAllUnitChecked = units.every((item) => unit?.includes(item.id));
const handleAllCheckedChange = (checked: boolean | string) => {
if (checked) {
form.setValue(
"output",
items.map((item) => item.id)
);
} else {
form.setValue("output", []);
}
};
const handleItemCheckedChange = (id: string, checked: boolean | string) => {
form.setValue(
"output",
checked ? [...output, id] : output.filter((value) => value !== id)
);
};
const handleAllUnitCheckedChange = (checked: boolean | string) => {
if (checked) {
form.setValue(
"unit",
units.map((item) => item.id)
);
} else {
form.setValue("unit", []);
}
};
const handleUnitCheckedChange = (id: string, checked: boolean | string) => {
if (checked) {
form.setValue("unit", [...unit, id]);
} else {
if (id == "2") {
const temp = [];
for (const element of unit) {
if (element == "1") {
temp.push("1");
}
}
form.setValue("unit", temp);
} else {
form.setValue(
"unit",
unit.filter((value) => value !== id)
);
}
}
};
useEffect(() => {
async function initState() {
const response = await getUserLevelForAssignments();
setListDest(response?.data?.data.list);
}
initState();
}, []);
const handleParentChange = (listId: string) => {
setSelected((prev) => ({
...prev,
[listId]: !prev[listId],
}));
};
const handleSelectAllPolres = (listId: string, isChecked: boolean) => {
setSelectAll((prev) => ({
...prev,
[listId]: isChecked,
}));
setSelected((prev) => {
const updatedState = { ...prev };
listDest
.find((list: any) => list.id === listId)
?.subDestination.forEach((subDes: any) => {
updatedState[`${listId}${subDes.id}`] = isChecked;
});
return updatedState;
});
};
const handleChildChange = (childId: string) => {
setSelected((prev) => ({
...prev,
[childId]: !prev[childId],
}));
};
return (
<div>
<SiteBreadcrumb />
<div className="flex flex-col gap-4">
<div className="flex flex-col bg-white gap-2 p-6">
<p className="text-lg">Perencanaan MediaHub</p>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-3">
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Judul Perencanaan</FormLabel>
<Input
value={field.value}
placeholder="Masukkan Judul Perencanaan"
onChange={field.onChange}
readOnly
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="output"
render={() => (
<FormItem>
<div className="mb-4">
<FormLabel className="text-sm">Output Tugas</FormLabel>
</div>
<div className="flex flex-row gap-3 items-center ">
<div className="flex items-center gap-3">
<Checkbox
id="all"
checked={isAllChecked}
onCheckedChange={(checked) =>
handleAllCheckedChange(checked)
}
disabled
/>
<label htmlFor="all" className="text-sm">
Semua
</label>
</div>
{items.map((item) => (
<FormField
key={item.id}
control={form.control}
name="output"
render={({ field }) => {
return (
<FormItem
key={item.id}
className="flex flex-row items-start space-x-3 space-y-0"
>
<FormControl>
<Checkbox
checked={output?.includes(item.id)}
onCheckedChange={(checked) =>
handleItemCheckedChange(item.id, checked)
}
disabled
/>
</FormControl>
<FormLabel className="font-normal">
{item.label}
</FormLabel>
</FormItem>
);
}}
/>
))}
</div>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="unit"
render={() => (
<FormItem>
<div className="mb-4">
<FormLabel className="text-sm">Pelaksana Tugas</FormLabel>
</div>
<div className="flex flex-row gap-3 items-center ">
<div className="flex items-center gap-3">
<Checkbox
id="all"
checked={isAllUnitChecked}
onCheckedChange={(checked) =>
handleAllUnitCheckedChange(checked)
}
disabled
/>
<label htmlFor="all" className="text-sm">
Semua
</label>
</div>
{units.map((item) => (
<FormField
key={item.id}
control={form.control}
name="unit"
render={({ field }) => {
return (
<FormItem
key={item.id}
className="flex flex-row items-start space-x-3 space-y-0"
>
<FormControl>
<Checkbox
checked={unit?.includes(item.id)}
// disabled={
// item.id === "3" && !unit.includes("2")
// }
onCheckedChange={(checked) =>
handleUnitCheckedChange(item.id, checked)
}
disabled
/>
</FormControl>
<FormLabel className="font-normal">
{item.label}
</FormLabel>
</FormItem>
);
}}
/>
))}
<Dialog>
<DialogTrigger disabled>
<a className="text-primary cursor-pointer text-sm">
{`[Kustom]`}
</a>
</DialogTrigger>
<DialogContent size="md">
<DialogHeader>
<DialogTitle>
Daftar Wilayah Polda dan Polres
</DialogTitle>
</DialogHeader>
<div className="grid grid-cols-2 gap-4 py-4 px-2 max-h-[600px] overflow-y-auto custom-scrollbar-table">
{listDest?.map((list: any) => (
<div key={list.id}>
<Accordion
type="single"
collapsible
className="h-fit border-none"
>
<AccordionItem
value={list.name}
className="border-none"
>
<div className="flex items-center space-x-2">
<Checkbox
id={list.id}
name={`all${list.id}`}
checked={
unit.includes("2")
? true
: !!selected[list.id]
}
onCheckedChange={() =>
handleParentChange(list.id)
}
disabled={unit.includes("2")}
/>
<label
htmlFor={list.name}
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{list.name}
</label>
<AccordionTrigger className="w-fit bg-transparent"></AccordionTrigger>
</div>
<AccordionContent>
<div className="flex flex-col gap-1">
<div className="flex items-center space-x-2">
<Checkbox
checked={
unit.includes("3")
? true
: !!selectAll[list.id]
}
onCheckedChange={(e) =>
handleSelectAllPolres(
list.id,
Boolean(e)
)
}
disabled={unit.includes("3")}
/>
<label
htmlFor="all-polres"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Pilih Semua Polres
</label>
</div>
{list.subDestination.map(
(subDes: any) => (
<div
key={subDes.id}
className="flex items-center space-x-2"
>
<Checkbox
id={`${list.id}${subDes.id}`}
checked={
unit.includes("3")
? true
: !!selected[
`${list.id}${subDes.id}`
]
}
onCheckedChange={() =>
handleChildChange(
`${list.id}${subDes.id}`
)
}
disabled={unit.includes("3")}
/>
<label
htmlFor={`${list.id}${subDes.id}`}
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{subDes.name}
</label>
</div>
)
)}
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
))}
</div>
</DialogContent>
</Dialog>
</div>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="type"
render={({ field }) => (
<FormItem>
<FormLabel>Jenis Penugasan</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
value={field.value}
className="flex flex-row gap-3"
disabled
>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="1" />
</FormControl>
<FormLabel className="font-normal">
Publikasi
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="2" />
</FormControl>
<FormLabel className="font-normal">
Amplifikasi
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="3" />
</FormControl>
<FormLabel className="font-normal">Kontra</FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="date"
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel>Pilih Tanggal</FormLabel>
<Popover>
<PopoverTrigger asChild>
<Button
variant={"outline"}
className={cn(
"w-[280px] justify-start text-left font-normal",
!field.value && "text-muted-foreground"
)}
disabled
>
<CalendarIcon className="mr-2 h-4 w-4" />
{field.value ? (
format(field.value, "PPP")
) : (
<span>Masukkan Tanggal</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar
mode="single"
selected={field.value}
onSelect={field.onChange}
initialFocus
/>
</PopoverContent>
</Popover>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="parentId"
render={({ field }) => (
<FormItem>
<FormLabel>Perencanaan Mingguan</FormLabel>
<Select
value={field.value}
onValueChange={field.onChange}
disabled
>
<FormControl>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
</FormControl>
<SelectContent>
{weeklyList?.map((list: any) => (
<SelectItem key={list.id} value={list.value}>
{list.label}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="detail"
render={({ field: { onChange, value } }) => (
<FormItem>
<FormLabel>Detail Perencanaan</FormLabel>
<ViewEditor initialData={value} />
<FormMessage />
</FormItem>
)}
/>
<div className="flex flex-row gap-2 justify-end mt-4 pt-4">
<Button
onClick={() => router.back()}
variant="outline"
color="destructive"
type="button"
>
Back
</Button>
</div>
</form>
</Form>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,771 @@
"use client";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import { Input } from "@/components/ui/input";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Link, useRouter } from "@/i18n/routing";
import { CalendarIcon } from "lucide-react";
import React, { useEffect, useRef, useState } from "react";
import { cn } from "@/lib/utils";
import { format } from "date-fns";
import JoditEditor from "jodit-react";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { getUserLevelForAssignments } from "@/service/task";
import { list } from "postcss";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { close, error, loading } from "@/config/swal";
import { id, te } from "date-fns/locale";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
getWeeklyPlanList,
savePlanning,
} from "@/service/agenda-setting/agenda-setting";
import { getOnlyDate } from "@/utils/globals";
import { useParams } from "next/navigation";
import { getPlanningById } from "@/service/planning/planning";
import dynamic from "next/dynamic";
const FormSchema = z.object({
date: z.date({
required_error: "Required",
}),
title: z.string({
required_error: "Required",
}),
detail: z.string({
required_error: "Required",
}),
output: z.array(z.string()).refine((value) => value.some((item) => item), {
message: "Required",
}),
unit: z.array(z.string()).refine((value) => value.some((item) => item), {
message: "Required",
}),
type: z.string({
required_error: "Required",
}),
parentId: z.string({
required_error: "Required",
}),
});
const items = [
{
id: "2",
label: "Audio Visual",
},
{
id: "1",
label: "Foto",
},
{
id: "4",
label: "Audio",
},
{
id: "3",
label: "Text",
},
];
const units = [
{
id: "1",
label: "Mabes Polri",
},
{
id: "2",
label: "Polda",
},
{
id: "3",
label: "Polres",
},
{
id: "4",
label: "Satker",
},
];
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function EditDaily() {
const id = useParams()?.id;
const MySwal = withReactContent(Swal);
const [listDest, setListDest] = useState<any>([]);
const router = useRouter();
const [weeklyList, setWeeklyList] = useState<any>();
const [selected, setSelected] = useState<{ [key: string]: boolean }>({});
const [selectAll, setSelectAll] = useState<{ [key: string]: boolean }>({});
useEffect(() => {
initFetch();
}, [id]);
async function initFetch() {
if (id != undefined) {
loading();
const res = await getPlanningById(id);
close();
if (res?.data?.data != undefined) {
const data = res?.data?.data;
console.log("data");
console.log("Data :", data);
form.setValue("title", data.title);
form.setValue("detail", data.description);
form.setValue("date", new Date(data.date));
form.setValue(
"output",
data.fileTypeOutput.split(",")?.length > 1
? data.fileTypeOutput.split(",")
: [data.fileTypeOutput]
);
form.setValue("type", String(data?.assignmentTypeId));
form.setValue("parentId", String(data?.parentId));
mapTopDestination(data?.assignedToLevel);
mapDestination(data?.assignedToTopLevel);
}
}
}
const mapTopDestination = (data: string) => {
const temp: string[] = [];
data.split(",").map((list) => {
if (list.length < 2) {
temp.push(list);
}
});
form.setValue("unit", temp);
};
const mapDestination = (data: string) => {
const temp: { [key: number]: boolean } = {};
data.split(",").forEach((list) => {
temp[Number(list)] = true;
});
setSelected(temp);
};
useEffect(() => {
getWeeklyPlanning();
}, []);
async function getWeeklyPlanning() {
const res = await getWeeklyPlanList(new Date().getDate(), 2);
if (res?.data !== null) {
const rawUser = res?.data?.data;
const optionArr = rawUser.map((option: any) => ({
id: option.id,
label: option.title,
value: String(option.id),
}));
setWeeklyList(optionArr);
}
}
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
unit: [],
output: [],
detail: "",
},
});
const editor = useRef(null);
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
console.log("data", data);
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 getSelectedString = () => {
return Object.keys(selected)
.filter((key) => selected[key])
.join(", ");
};
console.log("data", data, selected);
loading();
const reqData = {
id: id,
planningTypeId: 2,
time: "1",
title: data.title,
assignmentTypeId: data.type, //string
description: data.detail,
assignedToLevel: unit?.join(","), //string
assignmentPurpose: getSelectedString(), //string
fileTypeOutput: data.output?.join(","), //string
status: "Open",
date: getOnlyDate(data.date),
// date:
// isPublish || isUpdate
// ? selectedDate?.length > 10
// ? data.date?.toISOString().slice(0, 10)
// : selectedDate
// : data.date?.toISOString().slice(0, 10),
parentId: Number(data.parentId), //number
assignmentMainTypeId: 1,
};
const response = await savePlanning(reqData);
if (response?.error) {
error(response?.message);
return false;
}
close();
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
router.push("/curator/task-plan/mediahub");
}
});
};
const output = form.watch("output");
const isAllChecked = items.every((item) => output?.includes(item.id));
const unit = form.watch("unit");
const isAllUnitChecked = units.every((item) => unit?.includes(item.id));
const handleAllCheckedChange = (checked: boolean | string) => {
if (checked) {
form.setValue(
"output",
items.map((item) => item.id)
);
} else {
form.setValue("output", []);
}
};
const handleItemCheckedChange = (id: string, checked: boolean | string) => {
form.setValue(
"output",
checked ? [...output, id] : output.filter((value) => value !== id)
);
};
const handleAllUnitCheckedChange = (checked: boolean | string) => {
if (checked) {
form.setValue(
"unit",
units.map((item) => item.id)
);
} else {
form.setValue("unit", []);
}
};
const handleUnitCheckedChange = (id: string, checked: boolean | string) => {
if (checked) {
form.setValue("unit", [...unit, id]);
} else {
if (id == "2") {
const temp = [];
for (const element of unit) {
if (element == "1") {
temp.push("1");
}
}
form.setValue("unit", temp);
} else {
form.setValue(
"unit",
unit.filter((value) => value !== id)
);
}
}
};
useEffect(() => {
async function initState() {
const response = await getUserLevelForAssignments();
setListDest(response?.data?.data.list);
}
initState();
}, []);
const handleParentChange = (listId: string) => {
setSelected((prev) => ({
...prev,
[listId]: !prev[listId],
}));
};
const handleSelectAllPolres = (listId: string, isChecked: boolean) => {
setSelectAll((prev) => ({
...prev,
[listId]: isChecked,
}));
setSelected((prev) => {
const updatedState = { ...prev };
listDest
.find((list: any) => list.id === listId)
?.subDestination.forEach((subDes: any) => {
updatedState[`${listId}${subDes.id}`] = isChecked;
});
return updatedState;
});
};
const handleChildChange = (childId: string) => {
setSelected((prev) => ({
...prev,
[childId]: !prev[childId],
}));
};
return (
<div>
<SiteBreadcrumb />
<div className="flex flex-col gap-4">
<div className="flex flex-col bg-white gap-2 p-6">
<p className="text-lg">Perencanaan MediaHub</p>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-3">
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Judul Perencanaan</FormLabel>
<Input
value={field.value}
placeholder="Masukkan Judul Perencanaan"
onChange={field.onChange}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="output"
render={() => (
<FormItem>
<div className="mb-4">
<FormLabel className="text-sm">Output Tugas</FormLabel>
</div>
<div className="flex flex-row gap-3 items-center ">
<div className="flex items-center gap-3">
<Checkbox
id="all"
checked={isAllChecked}
onCheckedChange={(checked) =>
handleAllCheckedChange(checked)
}
/>
<label htmlFor="all" className="text-sm">
Semua
</label>
</div>
{items.map((item) => (
<FormField
key={item.id}
control={form.control}
name="output"
render={({ field }) => {
return (
<FormItem
key={item.id}
className="flex flex-row items-start space-x-3 space-y-0"
>
<FormControl>
<Checkbox
checked={output?.includes(item.id)}
onCheckedChange={(checked) =>
handleItemCheckedChange(item.id, checked)
}
/>
</FormControl>
<FormLabel className="font-normal">
{item.label}
</FormLabel>
</FormItem>
);
}}
/>
))}
</div>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="unit"
render={() => (
<FormItem>
<div className="mb-4">
<FormLabel className="text-sm">Pelaksana Tugas</FormLabel>
</div>
<div className="flex flex-row gap-3 items-center ">
<div className="flex items-center gap-3">
<Checkbox
id="all"
checked={isAllUnitChecked}
onCheckedChange={(checked) =>
handleAllUnitCheckedChange(checked)
}
/>
<label htmlFor="all" className="text-sm">
Semua
</label>
</div>
{units.map((item) => (
<FormField
key={item.id}
control={form.control}
name="unit"
render={({ field }) => {
return (
<FormItem
key={item.id}
className="flex flex-row items-start space-x-3 space-y-0"
>
<FormControl>
<Checkbox
checked={unit?.includes(item.id)}
disabled={
item.id === "3" && !unit.includes("2")
}
onCheckedChange={(checked) =>
handleUnitCheckedChange(item.id, checked)
}
/>
</FormControl>
<FormLabel className="font-normal">
{item.label}
</FormLabel>
</FormItem>
);
}}
/>
))}
<Dialog>
<DialogTrigger>
<a className="text-primary cursor-pointer text-sm">
{`[Kustom]`}
</a>
</DialogTrigger>
<DialogContent size="md">
<DialogHeader>
<DialogTitle>
Daftar Wilayah Polda dan Polres
</DialogTitle>
</DialogHeader>
<div className="grid grid-cols-2 gap-4 py-4 px-2 max-h-[600px] overflow-y-auto custom-scrollbar-table">
{listDest?.map((list: any) => (
<div key={list.id}>
<Accordion
type="single"
collapsible
className="h-fit border-none"
>
<AccordionItem
value={list.name}
className="border-none"
>
<div className="flex items-center space-x-2">
<Checkbox
id={list.id}
name={`all${list.id}`}
checked={
unit.includes("2")
? true
: !!selected[list.id]
}
onCheckedChange={() =>
handleParentChange(list.id)
}
disabled={unit.includes("2")}
/>
<label
htmlFor={list.name}
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{list.name}
</label>
<AccordionTrigger className="w-fit bg-transparent"></AccordionTrigger>
</div>
<AccordionContent>
<div className="flex flex-col gap-1">
<div className="flex items-center space-x-2">
<Checkbox
checked={
unit.includes("3")
? true
: !!selectAll[list.id]
}
onCheckedChange={(e) =>
handleSelectAllPolres(
list.id,
Boolean(e)
)
}
disabled={unit.includes("3")}
/>
<label
htmlFor="all-polres"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Pilih Semua Polres
</label>
</div>
{list.subDestination.map(
(subDes: any) => (
<div
key={subDes.id}
className="flex items-center space-x-2"
>
<Checkbox
id={`${list.id}${subDes.id}`}
checked={
unit.includes("3")
? true
: !!selected[
`${list.id}${subDes.id}`
]
}
onCheckedChange={() =>
handleChildChange(
`${list.id}${subDes.id}`
)
}
disabled={unit.includes("3")}
/>
<label
htmlFor={`${list.id}${subDes.id}`}
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{subDes.name}
</label>
</div>
)
)}
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
))}
</div>
</DialogContent>
</Dialog>
</div>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="type"
render={({ field }) => (
<FormItem>
<FormLabel>Jenis Penugasan</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
value={field.value}
className="flex flex-row gap-3"
>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="1" />
</FormControl>
<FormLabel className="font-normal">
Publikasi
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="2" />
</FormControl>
<FormLabel className="font-normal">
Amplifikasi
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="3" />
</FormControl>
<FormLabel className="font-normal">Kontra</FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="date"
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel>Pilih Tanggal</FormLabel>
<Popover>
<PopoverTrigger asChild>
<Button
variant={"outline"}
className={cn(
"w-[280px] justify-start text-left font-normal",
!field.value && "text-muted-foreground"
)}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{field.value ? (
format(field.value, "PPP")
) : (
<span>Masukkan Tanggal</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar
mode="single"
selected={field.value}
onSelect={field.onChange}
initialFocus
/>
</PopoverContent>
</Popover>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="parentId"
render={({ field }) => (
<FormItem>
<FormLabel>Perencanaan Mingguan</FormLabel>
<Select value={field.value} onValueChange={field.onChange}>
<FormControl>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
</FormControl>
<SelectContent>
{weeklyList?.map((list: any) => (
<SelectItem key={list.id} value={list.value}>
{list.label}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="detail"
render={({ field: { onChange, value } }) => (
<FormItem>
<FormLabel>Detail Perencanaan</FormLabel>
<CustomEditor onChange={onChange} initialData={value} />
<FormMessage />
</FormItem>
)}
/>
<div className="flex flex-row gap-2 justify-end mt-4 pt-4">
<Button
onClick={() => router.back()}
variant="outline"
color="destructive"
type="button"
>
Back
</Button>
<Button type="submit" color="primary">
Submit
</Button>
</div>
</form>
</Form>
</div>
</div>
</div>
);
}

View File

@ -58,6 +58,7 @@ import {
DialogTrigger,
} from "@/components/ui/dialog";
import { getUserLevelForAssignments } from "@/service/task";
import dynamic from "next/dynamic";
const FormSchema = z.object({
date: z.date({
@ -141,8 +142,20 @@ const units = [
id: "3",
label: "Polres",
},
{
id: "4",
label: "Satker",
},
];
export default function CreateMonthly() {
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function CreateDaily() {
const MySwal = withReactContent(Swal);
const [weeklyList, setWeeklyList] = useState<any>();
const router = useRouter();
@ -760,15 +773,10 @@ export default function CreateMonthly() {
<FormField
control={form.control}
name="detail"
render={({ field }) => (
render={({ field: { onChange, value } }) => (
<FormItem>
<FormLabel>Detail Perencanaan</FormLabel>
<JoditEditor
ref={editor}
value={field.value}
className="dark:text-black"
onChange={field.onChange}
/>
<CustomEditor onChange={onChange} initialData={value} />
<FormMessage />
</FormItem>

View File

@ -32,6 +32,7 @@ import { close, error, loading } from "@/config/swal";
import { savePlanning } from "@/service/agenda-setting/agenda-setting";
import { getPlanningById } from "@/service/planning/planning";
import { useParams } from "next/navigation";
import dynamic from "next/dynamic";
const FormSchema = z.object({
month: z.date({
@ -44,6 +45,13 @@ const FormSchema = z.object({
required_error: "Required",
}),
});
const ViewEditor = dynamic(
() => {
return import("@/components/editor/view-editor");
},
{ ssr: false }
);
export default function DetailMonthly() {
const id = useParams()?.id;
const MySwal = withReactContent(Swal);
@ -108,15 +116,15 @@ export default function DetailMonthly() {
};
const save = async (data: z.infer<typeof FormSchema>) => {
const month = new Date(data.month).getMonth() + 1;
const year = new Date(data.month).getFullYear();
const reqData = {
planningTypeId: 1,
title: data.title,
time: "3",
description: data.detail,
username: "",
date: `${new Date(data.month).getMonth() + 1}/${new Date(
data.month
).getFullYear()}`,
date: `${month.toString().padStart(2, "0")}/${year}`,
status: "Open",
};
console.log("req", reqData, data.month);
@ -215,16 +223,10 @@ export default function DetailMonthly() {
<FormField
control={form.control}
name="detail"
render={({ field }) => (
render={({ field: { onChange, value } }) => (
<FormItem>
<FormLabel>Detail Perencanaan</FormLabel>
<JoditEditor
ref={editor}
config={{ readonly: true }}
value={field.value}
className="dark:text-black"
onChange={field.onChange}
/>
<ViewEditor initialData={value} />
<FormMessage />
</FormItem>

View File

@ -32,6 +32,7 @@ import { close, error, loading } from "@/config/swal";
import { savePlanning } from "@/service/agenda-setting/agenda-setting";
import { getPlanningById } from "@/service/planning/planning";
import { useParams } from "next/navigation";
import dynamic from "next/dynamic";
const FormSchema = z.object({
month: z.date({
@ -44,6 +45,13 @@ const FormSchema = z.object({
required_error: "Required",
}),
});
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function EditMonthly() {
const id = useParams()?.id;
const MySwal = withReactContent(Swal);
@ -108,6 +116,8 @@ export default function EditMonthly() {
};
const save = async (data: z.infer<typeof FormSchema>) => {
const month = new Date(data.month).getMonth() + 1;
const year = new Date(data.month).getFullYear();
const reqData = {
id: id,
planningTypeId: 1,
@ -115,9 +125,7 @@ export default function EditMonthly() {
time: "3",
description: data.detail,
username: "",
date: `${new Date(data.month).getMonth() + 1}/${new Date(
data.month
).getFullYear()}`,
date: `${month.toString().padStart(2, "0")}/${year}`,
status: "Open",
};
console.log("req", reqData, data.month);
@ -214,15 +222,10 @@ export default function EditMonthly() {
<FormField
control={form.control}
name="detail"
render={({ field }) => (
render={({ field: { onChange, value } }) => (
<FormItem>
<FormLabel>Detail Perencanaan</FormLabel>
<JoditEditor
ref={editor}
value={field.value}
className="dark:text-black"
onChange={field.onChange}
/>
<CustomEditor onChange={onChange} initialData={value} />
<FormMessage />
</FormItem>

View File

@ -30,6 +30,7 @@ import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { error } from "@/config/swal";
import { savePlanning } from "@/service/agenda-setting/agenda-setting";
import dynamic from "next/dynamic";
const FormSchema = z.object({
month: z.date({
@ -42,6 +43,12 @@ const FormSchema = z.object({
required_error: "Required",
}),
});
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function CreateMonthly() {
const MySwal = withReactContent(Swal);
const router = useRouter();
@ -77,15 +84,15 @@ export default function CreateMonthly() {
};
const save = async (data: z.infer<typeof FormSchema>) => {
const month = new Date(data.month).getMonth() + 1;
const year = new Date(data.month).getFullYear();
const reqData = {
planningTypeId: 2,
title: data.title,
time: "3",
description: data.detail,
username: "",
date: `${new Date(data.month).getMonth() + 1}/${new Date(
data.month
).getFullYear()}`,
date: `${month.toString().padStart(2, "0")}/${year}`,
status: "Open",
};
console.log("req", reqData, data.month);
@ -198,15 +205,10 @@ export default function CreateMonthly() {
<FormField
control={form.control}
name="detail"
render={({ field }) => (
render={({ field: { onChange, value } }) => (
<FormItem>
<FormLabel>Detail Perencanaan</FormLabel>
<JoditEditor
ref={editor}
value={field.value}
className="dark:text-black"
onChange={field.onChange}
/>
<CustomEditor onChange={onChange} initialData={value} />
<FormMessage />
</FormItem>

View File

@ -44,6 +44,7 @@ import {
import dayjs from "dayjs";
import { getPlanningById } from "@/service/planning/planning";
import { useParams } from "next/navigation";
import dynamic from "next/dynamic";
const FormSchema = z.object({
week: z.object({
@ -61,7 +62,15 @@ const FormSchema = z.object({
required_error: "Required",
}),
});
export default function DetailMonthly() {
const ViewEditor = dynamic(
() => {
return import("@/components/editor/view-editor");
},
{ ssr: false }
);
export default function DetailWeekly() {
const id = useParams()?.id;
const MySwal = withReactContent(Swal);
const router = useRouter();
@ -227,16 +236,10 @@ export default function DetailMonthly() {
<FormField
control={form.control}
name="detail"
render={({ field }) => (
render={({ field: { onChange, value } }) => (
<FormItem>
<FormLabel>Detail Perencanaan</FormLabel>
<JoditEditor
ref={editor}
value={field.value}
className="dark:text-black"
onChange={field.onChange}
config={{ readonly: true }}
/>
<ViewEditor initialData={value} />
<FormMessage />
</FormItem>

View File

@ -44,6 +44,7 @@ import {
import dayjs from "dayjs";
import { getPlanningById } from "@/service/planning/planning";
import { useParams } from "next/navigation";
import dynamic from "next/dynamic";
const FormSchema = z.object({
week: z.object({
@ -61,7 +62,13 @@ const FormSchema = z.object({
required_error: "Required",
}),
});
export default function EditMonthly() {
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function EditWeekly() {
const id = useParams()?.id;
const MySwal = withReactContent(Swal);
const router = useRouter();
@ -225,15 +232,10 @@ export default function EditMonthly() {
<FormField
control={form.control}
name="detail"
render={({ field }) => (
render={({ field: { onChange, value } }) => (
<FormItem>
<FormLabel>Detail Perencanaan</FormLabel>
<JoditEditor
ref={editor}
value={field.value}
className="dark:text-black"
onChange={field.onChange}
/>
<CustomEditor onChange={onChange} initialData={value} />
<FormMessage />
</FormItem>

View File

@ -41,6 +41,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import dynamic from "next/dynamic";
const FormSchema = z.object({
week: z.object({
@ -61,6 +62,12 @@ const FormSchema = z.object({
required_error: "Required",
}),
});
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function CreateMonthly() {
const MySwal = withReactContent(Swal);
const router = useRouter();
@ -261,15 +268,10 @@ export default function CreateMonthly() {
<FormField
control={form.control}
name="detail"
render={({ field }) => (
render={({ field: { onChange, value } }) => (
<FormItem>
<FormLabel>Detail Perencanaan</FormLabel>
<JoditEditor
ref={editor}
value={field.value}
className="dark:text-black"
onChange={field.onChange}
/>
<CustomEditor onChange={onChange} initialData={value} />
<FormMessage />
</FormItem>

View File

@ -20,144 +20,13 @@ import { useEffect, useState } from "react";
import Cookies from "js-cookie";
export default function ExecutiveDashboard() {
// const downloadReport = async () => {
// // const formattedDate = `${reportDate.year}-${String(
// // reportDate.month
// // ).padStart(2, "0")}-${String(reportDate.day).padStart(2, "0")}`;
// // const resLogin = await tableauSignin();
// // const token = resLogin?.data.data?.credentials?.token;
// // const resCover = await tableauViewImage(
// // token,
// // "1df3df4a-0457-4483-a8e1-160f70e7834f",
// // formattedDate
// // );
// // const resTotalLink = await tableauViewImage(
// // token,
// // "8f902032-a6eb-4083-817a-57350f509b75",
// // formattedDate
// // );
// // const resCount = await tableauViewImage(
// // token,
// // "11b2fe3c-f853-4156-800e-43342bf8e5ce",
// // formattedDate
// // );
// // const resCoverCommentTwitter = await tableauViewImage(
// // token,
// // "28183e0b-80d0-428d-8684-2cbb572e97b3",
// // formattedDate,
// // 2
// // );
// // const resCoverCommentTiktok = await tableauViewImage(
// // token,
// // "28183e0b-80d0-428d-8684-2cbb572e97b3",
// // formattedDate,
// // 3
// // );
// // const resCoverCommentFacebook = await tableauViewImage(
// // token,
// // "28183e0b-80d0-428d-8684-2cbb572e97b3",
// // formattedDate,
// // 4
// // );
// // const resCoverCommentInstagram = await tableauViewImage(
// // token,
// // "28183e0b-80d0-428d-8684-2cbb572e97b3",
// // formattedDate,
// // 5
// // );
// // const resCoverCommentYoutube = await tableauViewImage(
// // token,
// // "28183e0b-80d0-428d-8684-2cbb572e97b3",
// // formattedDate,
// // 6
// // );
// // const resCommentTwitter = await tableauViewImage(
// // token,
// // "9a6d05ed-dea7-4a93-b709-82d0dab4790d",
// // formattedDate,
// // 2
// // );
// // const resCommentTiktok = await tableauViewImage(
// // token,
// // "9a6d05ed-dea7-4a93-b709-82d0dab4790d",
// // formattedDate,
// // 3
// // );
// // const resCommentFacebook = await tableauViewImage(
// // token,
// // "9a6d05ed-dea7-4a93-b709-82d0dab4790d",
// // formattedDate,
// // 4
// // );
// // const resCommentInstagram = await tableauViewImage(
// // token,
// // "9a6d05ed-dea7-4a93-b709-82d0dab4790d",
// // formattedDate,
// // 5
// // );
// // const resCommentYoutube = await tableauViewImage(
// // token,
// // "9a6d05ed-dea7-4a93-b709-82d0dab4790d",
// // formattedDate,
// // 6
// // );
// // const blobCover = new Blob([resCover.data], { type: "image/png" });
// // const blobTotalLink = new Blob([resTotalLink.data], { type: "image/png" });
// // const blobCount = new Blob([resCount.data], { type: "image/png" });
// // const blobCoverCommentTwitter = new Blob([resCoverCommentTwitter.data], {
// // type: "image/png",
// // });
// // const blobCoverCommentTiktok = new Blob([resCoverCommentTiktok.data], {
// // type: "image/png",
// // });
// // const blobCoverCommentFacebook = new Blob([resCoverCommentFacebook.data], {
// // type: "image/png",
// // });
// // const blobCoverCommentInstagram = new Blob(
// // [resCoverCommentInstagram.data],
// // { type: "image/png" }
// // );
// // const blobCoverCommentYoutube = new Blob([resCoverCommentYoutube.data], {
// // type: "image/png",
// // });
// // const blobCommentTwitter = new Blob([resCommentTwitter.data], {
// // type: "image/png",
// // });
// // const blobCommentTiktok = new Blob([resCommentTiktok.data], {
// // type: "image/png",
// // });
// // const blobCommentFacebook = new Blob([resCommentFacebook.data], {
// // type: "image/png",
// // });
// // const blobCommentInstagram = new Blob([resCommentInstagram.data], {
// // type: "image/png",
// // });
// // const blobCommentYoutube = new Blob([resCommentYoutube.data], {
// // type: "image/png",
// // });
// // await pdfGenerator([
// // blobCover,
// // blobTotalLink,
// // blobCount,
// // blobCoverCommentTwitter,
// // blobCommentTwitter,
// // blobCoverCommentTiktok,
// // blobCommentTiktok,
// // blobCoverCommentFacebook,
// // blobCommentFacebook,
// // blobCoverCommentInstagram,
// // blobCommentInstagram,
// // blobCoverCommentYoutube,
// // blobCommentYoutube,
// // ]);
// };
const [startDate, setStartDate] = useState<any>(new Date());
const [endDate, setEndDate] = useState<any>(new Date());
const [hasMounted, setHasMounted] = useState(false);
// const t = useTranslations("AnalyticsDashboard");
const levelName = getCookiesDecrypt("ulnae");
const poldaState = Cookies.get("state");
const levelNumber = getCookiesDecrypt("ulne");
const state = Cookies.get("state");
const provState = Cookies.get("state-prov");
const [ticket1, setTicket1] = useState("");
@ -171,42 +40,63 @@ export default function ExecutiveDashboard() {
const baseUrl = "https://analytic.sitani.info/";
const url = "https://analytic.sitani.info/trusted/";
const safeLevelName = levelNumber ?? "";
const view1 =
levelName == "MABES POLRI"
? isInternational[0]
? "views/2023_08_MediaHUB-KtnMgt_Rev100/db-emg-issue?"
: "views/2023_08_MediaHUB-KtnMgt_Rev100/db-emg-issue?"
: `views/2023_08_MediaHUB-KtnMgt_Rev100/db-emg-issue?provinsi-polda=${provState}&`;
? "views/2023_08_MediaHUB-KtnMgt_Rev100/db-emg-issue-executive?"
: "views/2023_08_MediaHUB-KtnMgt_Rev100/db-emg-issue-executive?"
: safeLevelName.includes("POLDA")
? `views/2023_08_MediaHUB-KtnMgt_Rev100/db-emg-issue?provinsi-polda=${state}&`
: `views/2023_08_MediaHUB-KtnMgt_Rev100/db-emg-issue?provinsi-polda=${state}&`;
const view2 =
levelName == "MABES POLRI"
? isInternational[1]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-published-produksi?"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-published-produksi-executive?"
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-konten-publisher-polda-executive?provinsi-polda=${poldaState}&`;
: safeLevelName.includes("POLDA")
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-published-produksi-polda-executive?polda-selected=${state}&`
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-published-produksi-polda-executive?polda-selected=${state}&`;
const view3 =
levelName == "MABES POLRI"
? isInternational[2]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-waktu-akses-pengguna?"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-waktu-akses-pengguna-executive?"
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-waktu-akses-pengguna-polda-executive?provinsi-polda=${poldaState}&`;
: safeLevelName.includes("POLDA")
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-waktu-akses-pengguna-polda-executive?polda-selected=${state}&`
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-waktu-akses-pengguna-polda-executive?polda-selected=${state}&`;
const view4 =
levelName == "MABES POLRI"
? isInternational[1]
? "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polda?"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polda?"
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polda?provinsi-polda=${poldaState}&`;
: 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}&`;
const view5 =
levelName == "MABES POLRI"
? isInternational[1]
? "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polres?"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polres?"
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polres?provinsi-polda=${poldaState}&`;
: safeLevelName.includes("POLDA")
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polres?provinsi-polda=${state}&`
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polres?provinsi-polda=${state}&`;
const param = ":embed=yes&:toolbar=yes&:iframeSizedToWindow=true";
const view6 =
levelName == "MABES POLRI"
? isInternational[1]
? "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-satker?"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-satker?"
: safeLevelName.includes("POLDA")
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-satker?satker-selected=${state}&`
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-satker?satker-selected=${state}&`;
const param = ":embed=yes&:toolbar=no&:iframeSizedToWindow=true";
useEffect(() => {
async function initState() {
@ -250,88 +140,107 @@ export default function ExecutiveDashboard() {
return (
<div>
<SiteBreadcrumb />
<div className="mt-3 flex flex-row gap-3 justify-center">
<Card className="rounded-sm w-4/12 p-3">
<div className="flex flex-row justify-between">
<p className="text-base font-semibold">
Upload konten hari ini Polda
</p>
<LucideBoxSelect />
</div>
<div className="my-5">
{ticket1 == "" ? (
<iframe
src={`${baseUrl + view4 + param}`}
width="100%"
height="750"
frameBorder="0"
/>
) : (
<iframe
src={`${`${url + ticket1}/${view4}${param}`}`}
width="100%"
height="750"
frameBorder="0"
/>
)}
</div>
</Card>
<Card className="rounded-sm w-4/12 p-3">
<div className="flex flex-row justify-between">
<p className="text-base font-semibold">
Upload konten hari ini Satker
</p>
<LucideBoxSelect />
</div>
<div className="my-5">
{ticket2 == "" ? (
<iframe
src={`${baseUrl + view4 + param}`}
width="100%"
height="750"
frameBorder="0"
/>
) : (
<iframe
src={`${`${url + ticket2}/${view4}${param}`}`}
width="100%"
height="750"
frameBorder="0"
/>
)}
</div>
</Card>
<Card className="rounded-sm w-4/12 p-3">
<div className="flex flex-row justify-between">
<p className="text-base font-semibold">
Upload konten hari ini Polres
</p>
<LucideBoxSelect />
</div>
<div className="my-5">
{ticket3 == "" ? (
<iframe
src={`${baseUrl + view5 + param}`}
width="100%"
height="750"
frameBorder="0"
/>
) : (
<iframe
src={`${`${url + ticket3}/${view5}${param}`}`}
width="100%"
height="750"
frameBorder="0"
/>
)}
</div>
</Card>
<div className="mt-3 flex gap-2 flex-row justify-center">
{/* Polda */}
{(levelNumber === "1" || levelNumber === "2") && (
<Card
className={`rounded-sm p-3 ${
levelNumber === "2" ? "w-6/12" : "w-4/12"
}`}
>
<div className="flex flex-row justify-between">
<p className="text-base font-semibold">
Upload konten hari ini Polda
</p>
</div>
<div className="my-5">
{ticket1 == "" ? (
<iframe
src={`${baseUrl + view4 + param}`}
width="100%"
height="750"
frameBorder="0"
/>
) : (
<iframe
src={`${url + ticket1}/${view4}${param}`}
width="100%"
height="750"
frameBorder="0"
/>
)}
</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">
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 ${
levelNumber === "2" ? "w-6/12" : "w-4/12"
}`}
>
<div className="flex flex-row justify-between">
<p className="text-base font-semibold">
Upload konten hari ini Polres
</p>
</div>
<div className="my-5">
{ticket3 == "" ? (
<iframe
src={`${baseUrl + view5 + param}`}
width="100%"
height="750"
frameBorder="0"
/>
) : (
<iframe
src={`${url + ticket3}/${view5}${param}`}
width="100%"
height="750"
frameBorder="0"
/>
)}
</div>
</Card>
)}
</div>
<div className="w-full mt-3">
<Card className="rounded-sm p-3 h-[750px]">
<div className="flex flex-row justify-between">
<p className="text-base font-semibold">Konten Paling Populer</p>
<LucideBoxSelect />
</div>
<div className="my-5">
{ticket4 == "" ? (
@ -354,14 +263,10 @@ export default function ExecutiveDashboard() {
</div>
<div className="w-full mt-3">
<Card className="rounded-sm p-3 h-[750px]">
<div className="flex flex-row justify-between">
<div className="flex flex-row justify-between mx-3">
<p className="text-base font-semibold">
Heatmap Konten Dengan Interaksi
Heatmap Konten dan Kategori dengan Interaksi
</p>
<p className="text-base font-semibold">
Heatmap Kategori Dengan Interaksi
</p>
<LucideBoxSelect />
</div>
<div className="my-5">
{ticket5 == "" ? (
@ -386,7 +291,6 @@ export default function ExecutiveDashboard() {
<Card className="rounded-sm p-3 h-auto">
<div className="flex flex-row justify-between">
<p className="text-base font-semibold">Emergency Issue</p>
<LucideBoxSelect />
</div>
<div className="flex flex-col">

View File

@ -26,6 +26,7 @@ import {
} from "@/components/ui/table";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
ChevronDown,
ChevronLeft,
ChevronRight,
Eye,
@ -39,6 +40,7 @@ import {
import { cn, getCookiesDecrypt } from "@/lib/utils";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuRadioGroup,
@ -177,32 +179,62 @@ const CollaborationTable = () => {
/>
</InputGroup>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
1 - {showData} Data
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 text-sm">
<DropdownMenuRadioGroup
value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
1 - 10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="20">
1 - 20 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="25">
1 - 25 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
1 - 50 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
<div className="flex flex-row items-center gap-3">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
1 - {showData} Data
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 text-sm">
<DropdownMenuRadioGroup
value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
1 - 10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="20">
1 - 20 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="25">
1 - 25 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
1 - 50 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
<div className="flex items-center">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md">
Columns <ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
<Table className="overflow-hidden mt-3">

View File

@ -26,6 +26,7 @@ import {
} from "@/components/ui/table";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
ChevronDown,
ChevronLeft,
ChevronRight,
Eye,
@ -39,6 +40,7 @@ import {
import { cn, getCookiesDecrypt } from "@/lib/utils";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuRadioGroup,
@ -186,32 +188,62 @@ const EscalationTable = () => {
/>
</InputGroup>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
1 - {showData} Data
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 text-sm">
<DropdownMenuRadioGroup
value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
1 - 10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="20">
1 - 20 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="25">
1 - 25 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
1 - 50 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
<div className="flex flex-row items-center gap-3">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
1 - {showData} Data
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 text-sm">
<DropdownMenuRadioGroup
value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
1 - 10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="20">
1 - 20 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="25">
1 - 25 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
1 - 50 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
<div className="flex items-center">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md">
Columns <ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
<Table className="overflow-hidden mt-3">
<TableHeader>

View File

@ -26,6 +26,7 @@ import {
} from "@/components/ui/table";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
ChevronDown,
ChevronLeft,
ChevronRight,
Eye,
@ -40,6 +41,7 @@ import {
import { cn, getCookiesDecrypt } from "@/lib/utils";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuRadioGroup,
@ -184,32 +186,62 @@ const InternalTable = () => {
/>
</InputGroup>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
1 - {showData} Data
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 text-sm">
<DropdownMenuRadioGroup
value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
1 - 10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="20">
1 - 20 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="25">
1 - 25 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
1 - 50 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
<div className="flex flex-row items-center gap-3">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
1 - {showData} Data
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 text-sm">
<DropdownMenuRadioGroup
value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
1 - 10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="20">
1 - 20 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="25">
1 - 25 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
1 - 50 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
<div className="flex items-center">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md">
Columns <ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
<Table className="overflow-hidden mt-3">
<TableHeader>

View File

@ -46,6 +46,7 @@ import { listContest } from "@/service/contest/contest";
import useTableColumns from "./columns";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
@ -172,8 +173,8 @@ const TaskTable = () => {
/>
</InputGroup>
</div>
<div className="flex flex-row">
<div className="mx-3">
<div className="flex flex-row items-center gap-3">
<div className="">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
@ -256,6 +257,34 @@ const TaskTable = () => {
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="flex items-center">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md">
Columns <ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
<Table className="overflow-hidden mt-3">

View File

@ -0,0 +1,19 @@
import { Card, CardContent } from "@/components/ui/card";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormTaskTa from "@/components/form/task-ta/task-ta-form";
import FormAskExpert from "@/components/form/shared/ask-expert-form";
import FormDoItYourself from "@/components/form/shared/do-it-yourself-form";
import FormAcceptAssignment from "@/components/form/shared/accept-assignment-form";
const AcceptAssignmentPage = () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormAcceptAssignment />
</div>
</div>
);
};
export default AcceptAssignmentPage;

View File

@ -0,0 +1,17 @@
import { Card, CardContent } from "@/components/ui/card";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormTaskTa from "@/components/form/task-ta/task-ta-form";
import FormAskExpert from "@/components/form/shared/ask-expert-form";
const AskExpertCreatePage = () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormAskExpert />
</div>
</div>
);
};
export default AskExpertCreatePage;

View File

@ -0,0 +1,18 @@
import { Card, CardContent } from "@/components/ui/card";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormTaskTa from "@/components/form/task-ta/task-ta-form";
import FormAskExpert from "@/components/form/shared/ask-expert-form";
import FormDoItYourself from "@/components/form/shared/do-it-yourself-form";
const DoItYourselfCreatePage = () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormDoItYourself />
</div>
</div>
);
};
export default DoItYourselfCreatePage;

View File

@ -1,5 +1,6 @@
"use client";
import { Link } from "@/components/navigation";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import {
Carousel,
@ -8,6 +9,7 @@ import {
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
import { getCookiesDecrypt } from "@/lib/utils";
import { listCuratedContent } from "@/service/curated-content/curated-content";
import { formatDateToIndonesian } from "@/utils/globals";
@ -26,6 +28,7 @@ type ImageData = {
const ImageSliderPage = () => {
const router = useRouter();
const roleId = Number(getCookiesDecrypt("urie")) || 0;
const [imageData, setImageData] = useState<ImageData[]>([]);
const [page, setPage] = useState(1);
const [limit] = useState(10);
@ -88,6 +91,40 @@ const ImageSliderPage = () => {
</div>
</CardContent>
</Link>
{roleId === 11 && (
<div className="flex flex-row justify-between mx-3 mb-3">
<Link
href={`/shared/curated-content/giat-routine/image/ask-the-expert/${image.id}`}
>
<Button color="primary" size="md">
Ask The Expert
</Button>
</Link>
<Link
href={`/shared/curated-content/giat-routine/image/do-it-yourself/${image.id}`}
>
<Button color="primary" size="md">
Do it Yourself
</Button>
</Link>
</div>
)}
{roleId === 12 && (
<div className="mx-3 mb-3">
<Link
href={`/shared/curated-content/giat-routine/image/accept-assignment/${image.id}`}
>
<Button
className="w-full "
color="primary"
size="md"
>
Accept Assignment
</Button>
</Link>
</div>
)}
</Card>
</div>
</CarouselItem>

View File

@ -1,5 +1,6 @@
"use client";
import { Link } from "@/components/navigation";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import {
Carousel,

View File

@ -0,0 +1,81 @@
"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { MoreVertical } from "lucide-react";
const FeedbackForm = () => {
const [region, setRegion] = useState("nasional");
const [feedbackList, setFeedbackList] = useState<string[]>([
"Silahkan berikan rating Anda terkait dengan kemudahan akses website MediaHUB Polri",
"Silahkan berikan rating Anda terkait dengan konten MediaHUB Polri",
"Silahkan berikan rating Anda terkait dengan tampilan MediaHUB Polri",
]);
const [newFeedback, setNewFeedback] = useState("");
const handleAddFeedback = () => {
if (newFeedback.trim()) {
setFeedbackList([...feedbackList, newFeedback.trim()]);
setNewFeedback("");
}
};
return (
<div className="space-y-4">
<div className="bg-white rounded-md p-6 flex flex-col md:flex-row gap-6">
<div className="md:w-1/2 space-y-4">
<div>
<p className="font-medium mb-2">Wilayah Publish</p>
<RadioGroup
defaultValue="nasional"
onValueChange={(value) => setRegion(value)}
className="flex items-center gap-6"
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="nasional" id="nasional" />
<label htmlFor="nasional">Nasional</label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="internasional" id="internasional" />
<label htmlFor="internasional">Internasional</label>
</div>
</RadioGroup>
</div>
<div>
<p className="font-medium mb-2">Poin Penilaian</p>
<Textarea
placeholder="Tulis poin yang ingin dijadikan penilaian"
value={newFeedback}
onChange={(e) => setNewFeedback(e.target.value)}
className="min-h-[100px]"
/>
</div>
<Button
onClick={handleAddFeedback}
className="bg-blue-600 text-white"
>
Tambah Feedback
</Button>
</div>
<div className="md:w-1/2">
<h3 className="font-semibold mb-2 border-b pb-1">Feedback</h3>
<p className="font-medium mb-4">Pertanyaan</p>
<ul className="space-y-3">
{feedbackList.map((item, index) => (
<li key={index} className="flex justify-between items-start">
<span>{item}</span>
<MoreVertical className="w-4 h-4 text-gray-600 cursor-pointer" />
</li>
))}
</ul>
</div>
</div>
</div>
);
};
export default FeedbackForm;

View File

@ -0,0 +1,16 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FeedbackForm from "./components/feedback";
const FeedbackPage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="bg-white rounded-md p-4 shadow-lg border">
<h2 className="text-lg font-semibold">Feedback</h2>
</div>
<FeedbackForm />
</div>
);
};
export default FeedbackPage;

View File

@ -0,0 +1,37 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import { Button } from "@/components/ui/button";
import { FaFacebookF, FaGoogle } from "react-icons/fa";
const SocialMediaPage = async () => {
return (
<div className="space-y-4">
<SiteBreadcrumb />
<div className="bg-white rounded-md p-4 shadow-lg border">
<h2 className="text-lg font-semibold">Social Media</h2>
</div>
<div className="bg-white rounded-md p-6 shadow-lg border">
<p className="text-base mb-6">Koneksi Social Media</p>
<div className="flex flex-col md:flex-row justify-center items-center gap-10">
<div className="flex flex-col items-center">
<Button className="bg-[#3b5998] hover:bg-[#2d4373] text-white px-6 py-2 rounded-md flex items-center gap-2">
<FaFacebookF className="text-lg" />
Login With Facebook
</Button>
<p className="mt-2 text-sm text-gray-700">Tidak Terhubung</p>
</div>
<div className="flex flex-col items-center">
<Button className="bg-[#ea4335] hover:bg-[#c73a2a] text-white px-6 py-2 rounded-md flex items-center gap-2">
<FaGoogle className="text-lg" />
Login With Google
</Button>
<p className="mt-2 text-sm text-gray-700">Tidak Terhubung</p>
</div>
</div>
</div>
</div>
);
};
export default SocialMediaPage;

View File

@ -582,7 +582,10 @@ const DetailInfo = () => {
{formatDateToIndonesian(
new Date(detailDataImage?.updatedAt)
)}{" "}
{"WIB"}
{detailDataImage?.timezone
? detailDataImage?.timezone
: "WIB"}
&nbsp;
</p>
<p className="text-xs lg:text-sm flex justify-center items-center">
&nbsp;|&nbsp;

View File

@ -698,11 +698,12 @@ const FilterPage = () => {
}}
/>
</div>
<div className="flex flex-row items-center gap-2 text-[10px] mx-2 mt-2">
<div className="flex flex-row items-center gap-2 text-[10px] mx-1 mt-2">
{formatDateToIndonesian(
new Date(image?.createdAt)
)}{" "}
{image?.timezone ? image?.timezone : "WIB"}|{" "}
{image?.timezone ? image?.timezone : "WIB"}
&nbsp; |
<Icon
icon="formkit:eye"
width="15"

View File

@ -17,7 +17,7 @@ import LoadScript from "@/utils/globals";
export const metadata: Metadata = {
title: "Media Hub | POLRI",
description: "created by codeshaper",
description: "",
};
export default async function RootLayout({
@ -33,15 +33,24 @@ export default async function RootLayout({
<html lang={locale} dir={direction}>
<head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap" rel="stylesheet" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin="anonymous"
/>
<link
href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap"
rel="stylesheet"
/>
<LoadScript />
</head>
<body className={`${inter.className} dashcode-app`}>
<NextIntlClientProvider messages={messages} locale={locale}>
<AuthProvider>
<ThemeProvider attribute="class" defaultTheme="light">
<DirectionProvider direction={direction}>{children}</DirectionProvider>
<DirectionProvider direction={direction}>
{children}
</DirectionProvider>
<Toaster />
<SonnerToaster />
</ThemeProvider>

View File

@ -25,7 +25,7 @@ const Home = ({ params: { locale } }: { params: { locale: string } }) => {
<NewContent group="mabes" type="latest" />
<NewContent group="mabes" type="popular" />
{/* <PopularContent /> */}
<ContentCategory group="mabes" />
<ContentCategory group="mabes" type="popular" />
{/* <Coverage /> */}
{/* <Division /> */}
<AreaCoverageWorkUnits />

View File

@ -202,7 +202,7 @@ export default function FormBlogUpdate() {
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/contributor/blog");
router.push("/in/contributor/blog");
});
};

View File

@ -214,7 +214,7 @@ export default function FormBlog() {
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/contributor/blog");
router.push("/in/contributor/blog");
});
};

View File

@ -948,6 +948,8 @@ export default function FormAudio() {
{selectedArticleId && (
<Link
href={`/contributor/content/audio/update-seo/${selectedArticleId}`}
target="_blank"
rel="noopener noreferrer"
>
<Button
className="mb-2"

View File

@ -344,7 +344,7 @@ export default function FormAudioUpdate() {
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/contributor/content/audio");
router.push("/in/contributor/content/audio");
});
};

View File

@ -401,7 +401,7 @@ export default function FormAudioSeo() {
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/contributor/content/image");
router.push("/in/contributor/content/image");
});
};

View File

@ -16,7 +16,7 @@ import * as z from "zod";
import { Upload } from "tus-js-client";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { redirect, useRouter } from "next/navigation";
import { redirect, useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
@ -57,7 +57,7 @@ import dynamic from "next/dynamic";
import { getCsrfToken } from "@/service/auth";
import { Link } from "@/i18n/routing";
import { request } from "http";
import { useTranslations } from "next-intl";
import { useLocale, useTranslations } from "next-intl";
interface FileWithPreview extends File {
preview: string;
@ -85,6 +85,8 @@ export default function FormImage() {
const router = useRouter();
const editor = useRef(null);
type ImageSchema = z.infer<typeof imageSchema>;
const params = useParams();
const locale = params?.locale;
const t = useTranslations("Form");
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
@ -466,7 +468,7 @@ export default function FormImage() {
tags: string;
isYoutube: boolean;
isInternationalMedia: boolean;
attachFromScheduleId?: number; // ✅ Tambahkan properti ini
attachFromScheduleId?: number;
} = {
...data,
title: finalTitle,
@ -631,7 +633,7 @@ export default function FormImage() {
setIsStartUpload(false);
// hideProgress();
Cookies.remove("idCreate");
successSubmit("/in/contributor/content/image/");
successSubmit("in/contributor/content/image/");
}
}
@ -951,18 +953,18 @@ export default function FormImage() {
<div className="pt-3">
<div className="flex flex-row justify-between items-center">
{selectedArticleId && (
<Link
href={`/contributor/content/image/update-seo/${selectedArticleId}`}
<Button
className="mb-2"
size="sm"
variant={"outline"}
color="primary"
onClick={() => {
const url = `/${locale}/contributor/content/image/update-seo/${selectedArticleId}`;
window.open(url, "_blank", "noopener,noreferrer");
}}
>
<Button
className="mb-2"
size="sm"
variant={"outline"}
color="primary"
>
{t("update")}
</Button>
</Link>
{t("update")}
</Button>
)}
</div>
</div>

View File

@ -373,7 +373,7 @@ export default function FormImageUpdate() {
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/contributor/content/image");
router.push("/in/contributor/content/image");
});
};

View File

@ -62,6 +62,8 @@ import {
Legend,
ChartOptions,
} from "chart.js";
import { useTranslations } from "next-intl";
import { Link } from "@/i18n/routing";
ChartJS.register(ArcElement, Tooltip, Legend);
const imageSchema = z.object({
@ -127,6 +129,7 @@ export default function FormImageSeo() {
let progressInfo: any = [];
let counterUpdateProgress = 0;
const t = useTranslations("Form");
const [progressList, setProgressList] = useState<any>([]);
let uploadPersen = 0;
const [isStartUpload, setIsStartUpload] = useState(false);
@ -401,7 +404,7 @@ export default function FormImageSeo() {
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/contributor/content/image");
router.push("/in/contributor/content/image");
});
};
@ -567,61 +570,174 @@ export default function FormImageSeo() {
return (
<div>
<div className="mx-5 mb-3">
<Card>
<Tabs defaultValue="content" className="">
<TabsList className="grid w-[300px] grid-cols-2 bg-slate-400 my-3 mx-3 ">
<TabsTrigger
value="content"
className="data-[state=active]:text-black text-gray-500 data-[state=active]:rounded-md"
>
Konten
</TabsTrigger>
<TabsTrigger
value="checker"
className="data-[state=active]:text-black text-gray-500 data-[state=active]:rounded-md"
>
Checker
</TabsTrigger>
</TabsList>
<TabsContent value="content">
{articleData !== undefined ? (
<CardContent className="space-y-2 my-3">
<form onSubmit={handleSubmit(onSubmit)}>
<div className="mx-5 mb-3">
<Card className="p-0">
<Tabs defaultValue="content" className="p-0">
<TabsList className="grid w-[300px] grid-cols-2 bg-slate-400 my-3 mx-3 ">
<TabsTrigger
value="content"
className="data-[state=active]:text-black text-gray-500 data-[state=active]:rounded-md"
>
Konten
</TabsTrigger>
<TabsTrigger
value="checker"
className="data-[state=active]:text-black text-gray-500 data-[state=active]:rounded-md"
>
Checker
</TabsTrigger>
</TabsList>
<TabsContent value="content">
{articleData !== undefined ? (
<CardContent className="space-y-2 my-3">
<div className="space-y-1">
<Label htmlFor="name">Judul</Label>
<Input id="name" defaultValue={articleData?.title} />
</div>
<div className="flex flex-row gap-3 w-full">
<div className="w-full">
<Label htmlFor="username">Main Keyword</Label>
<Textarea
id="mainKeyword"
value={articleData?.mainKeyword}
/>
</div>
<div className="w-full">
<Label htmlFor="username">Additional Keyword</Label>
<Textarea
id="additionalKeywords"
value={articleData?.additionalKeywords}
/>
</div>
</div>
<div className="flex flex-row gap-3 w-full">
<div className="w-full">
<Label htmlFor="username">Meta Title</Label>
<Textarea
id="metaTitle"
value={articleData?.metaTitle}
/>
</div>
<div className="w-full">
<Label htmlFor="username">Meta Description</Label>
<Textarea
id="metaDescription"
value={articleData?.metaDescription}
/>
</div>
</div>
<div className="py-3">
<Label>Article</Label>
<Controller
control={control}
name="description"
render={({ field: { onChange, value } }) => (
<CustomEditor
onChange={onChange}
initialData={articleBody}
/>
)}
/>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
</CardContent>
) : (
""
)}
</TabsContent>
<TabsContent value="checker">
<CardContent className="space-y-2">
<div className="flex items-start justify-start">
<Pie
data={data}
options={options}
className="text-left flex items-start justify-start"
/>
</div>
<div className="space-y-1">
<Label htmlFor="name">Judul</Label>
<Input id="name" defaultValue={articleData?.title} />
</div>
<div className="flex flex-row gap-3 w-full">
<div className="w-full">
<Label htmlFor="username">Main Keyword</Label>
<Textarea
id="mainKeyword"
value={articleData?.mainKeyword}
/>
</div>
<div className="w-full">
<Label htmlFor="username">Additional Keyword</Label>
<Textarea
id="additionalKeywords"
value={articleData?.additionalKeywords}
/>
</div>
</div>
<div className="flex flex-row gap-3 w-full">
<div className="w-full">
<Label htmlFor="username">Meta Title</Label>
<Textarea id="metaTitle" value={articleData?.metaTitle} />
</div>
<div className="w-full">
<Label htmlFor="username">Meta Description</Label>
<Textarea
id="metaDescription"
value={articleData?.metaDescription}
/>
</div>
<Accordion type="single" collapsible className="w-full ">
<AccordionItem
value="error"
className="border border-red-600"
>
<AccordionTrigger>
<div className="flex items-center">
<XIcon className="text-red-600" />
Errors ({errorSEO.length})
</div>
</AccordionTrigger>
<AccordionContent>
{errorSEO.length > 0 ? (
<ul className="list-disc list-inside">
{errorSEO.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
) : (
<p>No errors found.</p>
)}
</AccordionContent>
</AccordionItem>
<AccordionItem
value="warning"
className="border border-yellow-600"
>
<AccordionTrigger>
<div className="flex items-center">
<XIcon className="text-yellow-600" />
Warnings ({warningSEO.length})
</div>
</AccordionTrigger>
<AccordionContent>
{warningSEO.length > 0 ? (
<ul className="list-disc list-inside">
{warningSEO.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
) : (
<p>No warnings found.</p>
)}
</AccordionContent>
</AccordionItem>
<AccordionItem
value="optimized"
className="border border-green-600"
>
<AccordionTrigger>
<div className="flex items-center">
<XIcon className="text-green-600" />
Optimized ({optimizedSEO.length})
</div>
</AccordionTrigger>
<AccordionContent>
{optimizedSEO.length > 0 ? (
<ul className="list-disc list-inside">
{optimizedSEO.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
) : (
<p>No optimizations found.</p>
)}
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
<div className="py-3">
<Label>Article</Label>
<div className="flex flex-row justify-between items-center mb-3">
<Label>Article</Label>
<Button size="md" className="bg-blue-500">
Select Image From Content Bank
</Button>
</div>
<Controller
control={control}
name="description"
@ -639,119 +755,25 @@ export default function FormImageSeo() {
)}
</div>
</CardContent>
) : (
""
)}
</TabsContent>
<TabsContent value="checker">
<CardContent className="space-y-2">
<div className="flex items-start justify-start">
<Pie
data={data}
options={options}
className="text-left flex items-start justify-start"
/>
</div>
<div className="space-y-1">
<Accordion type="single" collapsible className="w-full ">
<AccordionItem
value="error"
className="border border-red-600"
>
<AccordionTrigger>
<div className="flex items-center">
<XIcon className="text-red-600" />
Errors ({errorSEO.length})
</div>
</AccordionTrigger>
<AccordionContent>
{errorSEO.length > 0 ? (
<ul className="list-disc list-inside">
{errorSEO.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
) : (
<p>No errors found.</p>
)}
</AccordionContent>
</AccordionItem>
<AccordionItem
value="warning"
className="border border-yellow-600"
>
<AccordionTrigger>
<div className="flex items-center">
<XIcon className="text-yellow-600" />
Warnings ({warningSEO.length})
</div>
</AccordionTrigger>
<AccordionContent>
{warningSEO.length > 0 ? (
<ul className="list-disc list-inside">
{warningSEO.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
) : (
<p>No warnings found.</p>
)}
</AccordionContent>
</AccordionItem>
<AccordionItem
value="optimized"
className="border border-green-600"
>
<AccordionTrigger>
<div className="flex items-center">
<XIcon className="text-green-600" />
Optimized ({optimizedSEO.length})
</div>
</AccordionTrigger>
<AccordionContent>
{optimizedSEO.length > 0 ? (
<ul className="list-disc list-inside">
{optimizedSEO.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
) : (
<p>No optimizations found.</p>
)}
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
<div className="py-3">
<div className="flex flex-row justify-between items-center mb-3">
<Label>Article</Label>
<Button size="md" className="bg-blue-500">
Select Image From Content Bank
</Button>
</div>
<Controller
control={control}
name="description"
render={({ field: { onChange, value } }) => (
<CustomEditor
onChange={onChange}
initialData={articleBody}
/>
)}
/>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
</CardContent>
</TabsContent>
</Tabs>
</Card>
</div>
</TabsContent>
</Tabs>
<div className="flex flex-row justify-star gap-3 mx-5">
<div className="mb-3">
<Link href={"/contributor/content/image"}>
<Button type="submit" color="primary" variant="outline">
{t("cancel")}
</Button>
</Link>
</div>
<div className="mb-3">
<Button type="submit" color="primary">
{t("submit")}
</Button>
</div>
</div>
</Card>
</div>
</form>
</div>
);
}

View File

@ -945,6 +945,8 @@ export default function FormTeks() {
{selectedArticleId && (
<Link
href={`/contributor/content/teks/update-seo/${selectedArticleId}`}
target="_blank"
rel="noopener noreferrer"
>
<Button
className="mb-2"

View File

@ -337,7 +337,7 @@ export default function FormTeksUpdate() {
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/contributor/content/teks");
router.push("/in/contributor/content/teks");
});
};

View File

@ -401,7 +401,7 @@ export default function FormTeksSeo() {
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/contributor/content/image");
router.push("/in/contributor/content/image");
});
};

View File

@ -944,6 +944,8 @@ export default function FormVideo() {
{selectedArticleId && (
<Link
href={`/contributor/content/video/update-seo/${selectedArticleId}`}
target="_blank"
rel="noopener noreferrer"
>
<Button
className="mb-2"

View File

@ -284,7 +284,7 @@ export default function PublishMediahub() {
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/contributor/planning/mediahub");
router.push("/in/contributor/planning/mediahub");
});
};

View File

@ -277,7 +277,7 @@ export default function PublishMedsos() {
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/contributor/planning/medsos-mediahub");
router.push("/in/contributor/planning/medsos-mediahub");
});
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,952 @@
"use client";
import React, { useEffect, useRef, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Card } from "@/components/ui/card";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import JoditEditor from "jodit-react";
import {
createTask,
createTaskTa,
getTask,
getUserLevelForAssignments,
} from "@/service/task";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { CalendarIcon, ChevronDown, ChevronUp, Trash2 } from "lucide-react";
import { AudioRecorder } from "react-audio-voice-recorder";
import FileUploader from "@/components/form/shared/file-uploader";
import { Upload } from "tus-js-client";
import { error } from "@/config/swal";
import { getCsrfToken } from "@/service/auth";
import { loading } from "@/lib/swal";
import { useTranslations } from "next-intl";
import dynamic from "next/dynamic";
import { cn } from "@/lib/utils";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Calendar } from "@/components/ui/calendar";
import { addDays, format, setDate } from "date-fns";
import { DateRange } from "react-day-picker";
import TimePicker from "react-time-picker";
import "react-time-picker/dist/TimePicker.css";
import "react-clock/dist/Clock.css";
import { detailMedia } from "@/service/curated-content/curated-content";
const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
naration: z.string().min(2, {
message: "Narasi Penugasan harus lebih dari 2 karakter.",
}),
// url: z.string().min(1, { message: "Judul diperlukan" }),
});
interface FileWithPreview extends File {
preview: string;
}
export type taskDetail = {
id: number;
title: string;
fileTypeOutput: string;
assignedToTopLevel: string;
assignedToLevel: string;
assignmentType: {
id: number;
name: string;
};
assignmentMainType: {
id: number;
name: string;
};
attachmentUrl: string;
taskType: string;
broadcastType: string;
narration: string;
is_active: string;
};
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function FormAskExpert() {
const MySwal = withReactContent(Swal);
const router = useRouter();
const editor = useRef(null);
type TaskSchema = z.infer<typeof taskSchema>;
const { id } = useParams() as { id: string };
console.log(id);
// State for various form fields
const [expertise, setExpertiseOutput] = useState({
semua: false,
komunikasi: false,
hukum: false,
bahasa: false,
ekonomi: false,
politik: false,
sosiologi: false,
ilmuadministrasipemerintah: false,
ti: false,
});
const [expert, setExpertOutput] = useState({
semua: false,
});
// const [assignmentType, setAssignmentType] = useState("mediahub");
// const [assignmentCategory, setAssignmentCategory] = useState("publication");
const [mainType, setMainType] = useState<string>("1");
const [taskType, setTaskType] = useState<string>("atensi-khusus");
const [broadcastType, setBroadcastType] = useState<string>("");
const [type, setType] = useState<string>("1");
const [selectedTarget, setSelectedTarget] = useState("3,4");
const [detail, setDetail] = useState<taskDetail>();
const [refresh] = useState(false);
const [listDest, setListDest] = useState([]);
const [checkedLevels, setCheckedLevels] = useState(new Set());
const [expandedPolda, setExpandedPolda] = useState([{}]);
const [isLoading, setIsLoading] = useState(false);
const [audioFile, setAudioFile] = useState<File | null>(null);
const [isRecording, setIsRecording] = useState(false);
const [timer, setTimer] = useState<number>(120);
const t = useTranslations("Form");
const [imageFiles, setImageFiles] = useState<FileWithPreview[]>([]);
const [videoFiles, setVideoFiles] = useState<FileWithPreview[]>([]);
const [textFiles, setTextFiles] = useState<FileWithPreview[]>([]);
const [audioFiles, setAudioFiles] = useState<FileWithPreview[]>([]);
const [isImageUploadFinish, setIsImageUploadFinish] = useState(false);
const [isVideoUploadFinish, setIsVideoUploadFinish] = useState(false);
const [isTextUploadFinish, setIsTextUploadFinish] = useState(false);
const [isAudioUploadFinish, setIsAudioUploadFinish] = useState(false);
const [voiceNoteLink, setVoiceNoteLink] = useState("");
const [date, setDate] = React.useState<DateRange | undefined>({
from: new Date(2024, 0, 1),
});
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
const [unitSelection, setUnitSelection] = useState({
semua: false,
mabes: false,
polda: false,
polres: false,
satker: false,
});
const [taskOutput, setTaskOutput] = useState({
all: false,
video: false,
audio: false,
image: false,
text: false,
});
const [links, setLinks] = useState<string[]>([""]);
const {
register,
control,
setValue,
handleSubmit,
formState: { errors },
} = useForm<TaskSchema>({
resolver: zodResolver(taskSchema),
mode: "all",
});
// const handleRadioChange = (event: React.ChangeEvent<HTMLInputElement>) => {
// const selectedValue = Number(event.target.value);
// setMainType(selectedValue);
// setPlatformTypeVisible(selectedValue === 2);
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(","); // Mengonversi Set ke string
};
const handleUnitChange = (
key: keyof typeof unitSelection,
value: boolean
) => {
if (key === "semua") {
const newState = {
semua: value,
mabes: value,
polda: value,
polres: value,
satker: value,
};
setUnitSelection(newState);
} else {
const updatedSelection = {
...unitSelection,
[key]: value,
};
const allChecked = ["mabes", "polda", "polres", "satker"].every(
(k) => updatedSelection[k as keyof typeof unitSelection]
);
updatedSelection.semua = allChecked;
setUnitSelection(updatedSelection);
}
};
const handleExpertiseOutputChange = (
key: keyof typeof expertise,
value: boolean
) => {
if (key === "semua") {
const newState = {
semua: value,
komunikasi: value,
hukum: value,
bahasa: value,
ekonomi: value,
politik: value,
sosiologi: value,
ilmuadministrasipemerintah: value,
ti: value,
};
setExpertiseOutput(newState);
} else {
const updated = {
...expertise,
[key]: value,
};
const allChecked = [
"komunikasi",
"hukum",
"bahasa",
"ekonomi",
"politik",
"sosiologi",
"ilmuadministrasipemerintah",
"ti",
].every((k) => updated[k as keyof typeof expertise]);
updated.semua = allChecked;
setExpertiseOutput(updated);
}
};
const handleExpertOutputChange = (
key: keyof typeof expert,
value: boolean
) => {
if (key === "semua") {
const newState = {
semua: value,
};
setExpertOutput(newState);
} else {
const updated = {
...expert,
[key]: value,
};
const allChecked = ["video", "audio", "image", "text"].every(
(k) => updated[k as keyof typeof expert]
);
updated.semua = allChecked;
setExpertOutput(updated);
}
};
useEffect(() => {
async function initState() {
if (id) {
const response = await detailMedia(id);
const details = response?.data?.data;
setDetail(details);
}
}
initState();
}, [id, refresh]);
const handleTaskOutputChange = (
key: keyof typeof taskOutput,
value: boolean
) => {
if (key === "all") {
const newState = {
all: value,
video: value,
audio: value,
image: value,
text: value,
};
setTaskOutput(newState);
} else {
const updated = {
...taskOutput,
[key]: value,
};
const allChecked = ["video", "audio", "image", "text"].every(
(k) => updated[k as keyof typeof taskOutput]
);
updated.all = allChecked;
setTaskOutput(updated);
}
};
const save = async (data: TaskSchema) => {
const fileTypeMapping = {
all: "1",
video: "2",
audio: "4",
image: "3",
text: "5",
};
const areasMapping = {
semua: "0",
komunikasi: "1",
hukum: "2",
bahasa: "3",
ekonomi: "4",
politik: "5",
sosiologi: "6",
ilmuadministrasipemerintah: "7",
ti: "8",
};
const unitMapping = {
allUnit: "0",
mabes: "1",
polda: "2",
polres: "3",
satker: "4",
};
const assignmentPurposeString = Object.keys(unitSelection)
.filter((key) => unitSelection[key as keyof typeof unitSelection])
.map((key) => unitMapping[key as keyof typeof unitMapping])
.join(",");
const selectedOutputs = Object.keys(taskOutput)
.filter((key) => taskOutput[key as keyof typeof taskOutput])
.map((key) => fileTypeMapping[key as keyof typeof fileTypeMapping])
.join(",");
const selectedAreaExpert = Object.keys(expertise)
.filter((key) => expertise[key as keyof typeof expertise])
.map((key) => areasMapping[key as keyof typeof areasMapping])
.join(",");
const requestData: {
id?: number;
title: string;
assignedToLevel: any;
assignedToUsers: any;
assignmentTypeId: string;
fileTypeOutput: string;
areasExpertise: string;
narration: string;
platformType: string | null;
assignmentMainTypeId: any;
assignmentType: string;
assignedToRole: string;
broadcastType: string;
expertCompetencies: string;
attachmentUrl: string[];
} = {
...data,
// assignmentType,
// assignmentCategory,
assignedToLevel: handlePoldaPolresChange(),
assignedToUsers: assignmentPurposeString,
assignedToRole: selectedTarget,
assignmentType: taskType,
broadcastType: broadcastType,
assignmentMainTypeId: mainType,
areasExpertise: selectedAreaExpert,
assignmentTypeId: type,
fileTypeOutput: selectedOutputs,
narration: data.naration,
platformType: "",
expertCompetencies: "1,2,3",
title: data.title,
attachmentUrl: links,
};
const response = await createTaskTa(requestData);
console.log("Form Data Submitted:", requestData);
console.log("response", response);
const id = response?.data?.data.id;
loading();
if (imageFiles?.length == 0) {
setIsImageUploadFinish(true);
}
imageFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "1", "0");
});
if (videoFiles?.length == 0) {
setIsVideoUploadFinish(true);
}
videoFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "2", "0");
});
if (textFiles?.length == 0) {
setIsTextUploadFinish(true);
}
textFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "3", "0");
});
if (audioFiles?.length == 0) {
setIsAudioUploadFinish(true);
}
audioFiles.map(async (item: FileWithPreview, index: number) => {
await uploadResumableFile(
index,
String(id),
item, // Use .file to access the actual File object
"4",
"0" // Optional: Replace with actual duration if available
);
});
};
const onSubmit = (data: TaskSchema) => {
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 toggleExpand = (poldaId: any) => {
setExpandedPolda((prev: any) => ({
...prev,
[poldaId]: !prev[poldaId],
}));
};
const onRecordingStart = () => {
setIsRecording(true);
const countdown = setInterval(() => {
setTimer((prevTimer) => {
if (prevTimer <= 1) {
clearInterval(countdown);
return 0;
}
return prevTimer - 1;
});
}, 1000);
setTimeout(() => {
if (isRecording) {
handleStopRecording();
}
}, 120000);
};
const handleStopRecording = () => {
setIsRecording(false);
setTimer(120);
};
const addAudioElement = (blob: Blob) => {
const url = URL.createObjectURL(blob);
const audio = document.createElement("audio");
audio.src = url;
audio.controls = true;
document.body.appendChild(audio);
// Convert Blob to File and add preview
const fileWithPreview: FileWithPreview = Object.assign(
new File([blob], "voiceNote.webm", { type: "audio/webm" }),
{ preview: url }
);
// Add to state
setAudioFile(fileWithPreview);
setAudioFiles((prev) => [...prev, fileWithPreview]);
};
const handleDeleteAudio = (index: number) => {
setAudioFiles((prev) => prev.filter((_, idx) => idx !== index));
};
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}/assignment-expert/file/upload`,
headers: headers,
retryDelays: [0, 3000, 6000, 12_000, 24_000],
chunkSize: 20_000,
metadata: {
assignmentId: 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);
} else if (fileTypeId == "2") {
setIsVideoUploadFinish(true);
}
if (fileTypeId == "3") {
setIsTextUploadFinish(true);
}
if (fileTypeId == "4") {
setIsAudioUploadFinish(true);
}
},
});
upload.start();
}
useEffect(() => {
successTodo();
}, [
isImageUploadFinish,
isVideoUploadFinish,
isAudioUploadFinish,
isTextUploadFinish,
]);
function successTodo() {
if (
isImageUploadFinish &&
isVideoUploadFinish &&
isAudioUploadFinish &&
isTextUploadFinish
) {
successSubmit("/in/contributor/task-ta");
}
}
const successSubmit = (redirect: string) => {
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push(redirect);
});
};
const handleLinkChange = (index: number, value: string) => {
const updatedLinks = [...links];
updatedLinks[index] = value;
setLinks(updatedLinks);
};
const handleAddRow = () => {
setLinks([...links, ""]);
};
// Remove a specific link row
const handleRemoveRow = (index: number) => {
const updatedLinks = links.filter((_: any, i: any) => i !== index);
setLinks(updatedLinks);
};
return (
<Card>
<div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">{t("form-task")}</p>
{detail !== undefined ? (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="gap-5 mb-5">
{/* Input Title */}
<div className="space-y-2">
<Label>{t("title")}</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
defaultValue={detail?.title}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.title?.message && (
<p className="text-red-400 text-sm">{errors.title.message}</p>
)}
</div>
<div className="mt-5">
<Label>Ditujukan Kepada :</Label>
</div>
<div className=" space-y-2">
<Label>{t("areas-expertise")}</Label>
<div className="flex flex-wrap gap-4">
{Object.keys(expertise).map((key) => (
<div className="flex items-center gap-2" key={key}>
<Checkbox
id={key}
checked={expertise[key as keyof typeof expertise]}
onCheckedChange={(value) =>
handleExpertiseOutputChange(
key as keyof typeof expertise,
value as boolean
)
}
/>
<Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
</div>
))}
</div>
</div>
<div className="mt-5 space-y-2">
<Label>{t("choose-expert")}</Label>
<div className="flex flex-wrap gap-4">
{Object.keys(expert).map((key) => (
<div className="flex items-center gap-2" key={key}>
<Checkbox
id={key}
checked={expert[key as keyof typeof expert]}
onCheckedChange={(value) =>
handleExpertOutputChange(
key as keyof typeof expert,
value as boolean
)
}
/>
<Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
</div>
))}
</div>
</div>
<div className="flex flex-col space-y-2 mt-5">
<Label className="mr-3 mb-1">Tanggal</Label>
<Popover>
<PopoverTrigger asChild className="px-0">
<Button
size="md"
id="date"
variant={"outline"}
className={cn(
"w-[280px] lg:w-[250px] justify-start text-left font-normal border border-slate-300 px-0 md:px-0 lg:px-4",
!date && "text-muted-foreground"
)}
>
<CalendarIcon size={15} className="mr-3" />
{date?.from ? (
date.to ? (
<>
{format(date.from, "LLL dd, y")} -{" "}
{format(date.to, "LLL dd, y")}
</>
) : (
format(date.from, "LLL dd, y")
)
) : (
<span>Pick a date</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
initialFocus
mode="range"
defaultMonth={date?.from}
selected={date}
onSelect={setDate}
numberOfMonths={1}
/>
</PopoverContent>
</Popover>
</div>
<div className="mt-5 space-y-2">
<Label>{t("output-task")}</Label>
<div className="flex flex-wrap gap-4">
{Object.keys(taskOutput).map((key) => (
<div className="flex items-center gap-2" key={key}>
<Checkbox
id={key}
checked={taskOutput[key as keyof typeof taskOutput]}
onCheckedChange={(value) =>
handleTaskOutputChange(
key as keyof typeof taskOutput,
value as boolean
)
}
/>
<Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
</div>
))}
</div>
</div>
<div className="mt-5 space-y-2">
<Label>{t("description")}</Label>
<Controller
control={control}
name="naration"
render={({ field: { onChange, value } }) => (
<CustomEditor onChange={onChange} initialData={value} />
)}
/>
{errors.naration?.message && (
<p className="text-red-400 text-sm">
{errors.naration.message}
</p>
)}
</div>
<div className="space-y-2.5 mt-5">
<Label htmlFor="attachments">{t("attachment")}</Label>
<div className="space-y-3">
<div>
<Label>{t("audio-visual")}</Label>
<FileUploader
accept={{
"mp4/*": [],
"mov/*": [],
}}
maxSize={100}
label="Upload file dengan format .mp4 atau .mov."
onDrop={(files) => setVideoFiles(files)}
/>
</div>
<div className="space-y-2">
<Label>{t("image")}</Label>
<FileUploader
accept={{
"image/*": [],
}}
maxSize={100}
label="Upload file dengan format .png, .jpg, atau .jpeg."
onDrop={(files) => setImageFiles(files)}
/>
</div>
<div className="space-y-2">
<Label>{t("text")}</Label>
<FileUploader
accept={{
"pdf/*": [],
}}
maxSize={100}
label="Upload file dengan format .pdf."
onDrop={(files) => setTextFiles(files)}
/>
</div>
<div className="space-y-2">
<Label>{t("audio")}</Label>
<AudioRecorder
onRecordingComplete={addAudioElement}
audioTrackConstraints={{
noiseSuppression: true,
echoCancellation: true,
}}
downloadOnSavePress={true}
downloadFileExtension="webm"
/>
<FileUploader
accept={{
"mp3/*": [],
"wav/*": [],
}}
maxSize={100}
label="Upload file dengan format .mp3 atau .wav."
onDrop={(files) =>
setAudioFiles((prev) => [...prev, ...files])
}
className="mt-2"
/>
</div>
{audioFiles?.map((audio: any, idx: any) => (
<div
key={idx}
className="flex flex-row justify-between items-center"
>
<p>{t("voice-note")}</p>
<Button
type="button"
onClick={() => handleDeleteAudio(idx)}
size="sm"
color="destructive"
>
X
</Button>
</div>
))}
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
{/* Display remaining time */}
<div className="mt-4 space-y-2">
<Label className="">{t("news-links")}</Label>
{links.map((link, index) => (
<div key={index} className="flex items-center gap-2 mt-2">
<Input
type="url"
className="border rounded p-2 w-full"
placeholder={`Masukkan link berita ${index + 1}`}
value={link}
onChange={(e) =>
handleLinkChange(index, e.target.value)
}
/>
{links.length > 1 && (
<button
type="button"
className="bg-red-500 text-white px-3 py-1 rounded"
onClick={() => handleRemoveRow(index)}
>
<Trash2 className="h-4 w-4" />
</button>
)}
</div>
))}
<Button
type="button"
className="mt-2 bg-blue-500 text-white px-4 py-2 rounded"
onClick={handleAddRow}
size="sm"
>
{t("add-links")}
</Button>
</div>
</div>
</div>
</div>
{/* Submit Button */}
<div className="mt-4">
<Button type="submit" color="primary">
{t("submit")}
</Button>
</div>
</form>
) : (
""
)}
</div>
</Card>
);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,263 @@
"use client";
import { useEffect, useState } from "react";
import { z } from "zod";
import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { createSurveyData, getSurveyById } from "@/service/survey/survey";
import { Checkbox } from "@/components/ui/checkbox";
import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button";
import { useParams } from "next/navigation";
const surveySchema = z.object({
accessFrequency: z.string(),
uiExperienceDesign: z.string(),
uiExperienceNavigation: z.string(),
uiExperienceSpeed: z.string(),
infoAccuracy: z.string(),
infoCompleteness: z.string(),
usefulness: z.string(),
suggestion: z.string().optional(),
});
type SurveySchema = z.infer<typeof surveySchema>;
export default function FormSurveyDetailPage() {
const [isLoading, setIsLoading] = useState(false);
const [loadingSurvey, setLoadingSurvey] = useState(true);
const [detail, setDetail] = useState<any>();
const { id } = useParams() as { id: string };
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<SurveySchema>({
resolver: zodResolver(surveySchema),
defaultValues: {
accessFrequency: "",
uiExperienceDesign: "",
uiExperienceNavigation: "",
uiExperienceSpeed: "",
infoAccuracy: "",
infoCompleteness: "",
usefulness: "",
suggestion: "",
},
});
const options = {
accessFrequency: [
"Setiap hari",
"Beberapa kali seminggu",
"Beberapa kali dalam sebulan",
"Baru pertama kali",
],
uiExperienceDesign: ["Sangat baik", "Baik", "Cukup", "Kurang", "Buruk"],
uiExperienceNavigation: [
"Sangat mudah",
"Mudah",
"Cukup",
"Sulit",
"Sangat sulit",
],
uiExperienceSpeed: [
"Sangat cepat",
"Cepat",
"Cukup",
"Lambat",
"Sangat lambat",
],
infoAccuracy: ["Sangat puas", "Puas", "Cukup", "Kurang puas", "Tidak puas"],
infoCompleteness: [
"Sangat lengkap",
"Lengkap",
"Cukup",
"Kurang lengkap",
"Tidak lengkap",
],
usefulness: [
"Sangat membantu",
"Membantu",
"Cukup membantu",
"Kurang membantu",
"Tidak membantu",
],
};
const renderControllerGroup = (
name: keyof SurveySchema,
question: string,
choices: string[]
) => (
<div className="space-y-2">
<p className="font-medium">{question}</p>
<div className="grid grid-cols-2 gap-2">
{choices.map((choice, i) => (
<Controller
key={i}
name={name}
control={control}
render={({ field }) => (
<label className="flex items-center space-x-2">
<Checkbox
checked={field.value === choice}
onCheckedChange={() => field.onChange(choice)}
/>
<span>{choice}</span>
</label>
)}
/>
))}
</div>
{errors[name] && (
<p className="text-red-500 text-sm">
{errors[name]?.message as string}
</p>
)}
</div>
);
const onSubmit = async (data: SurveySchema) => {
setIsLoading(true);
try {
const response = await createSurveyData(data);
console.log("Survey submitted:", response);
} catch (error) {
console.error("Failed to submit survey:", error);
} finally {
setIsLoading(false);
}
};
useEffect(() => {
const fetchSurvey = async () => {
if (id) {
setLoadingSurvey(true);
try {
const response = await getSurveyById(id);
const details = response?.data?.data;
setDetail(details);
if (details) {
setValue("accessFrequency", details.accessFrequency || "");
setValue("uiExperienceDesign", details.uiExperienceDesign || "");
setValue(
"uiExperienceNavigation",
details.uiExperienceNavigation || ""
);
setValue("uiExperienceSpeed", details.uiExperienceSpeed || "");
setValue("infoAccuracy", details.infoAccuracy || "");
setValue("infoCompleteness", details.infoCompleteness || "");
setValue("usefulness", details.usefulness || "");
setValue("suggestion", details.suggestion || "");
}
} catch (error) {
console.error("Failed to fetch survey detail:", error);
} finally {
setLoadingSurvey(false);
}
}
};
fetchSurvey();
}, [id, setValue]);
if (loadingSurvey) {
return <div className="p-6">Loading survey data...</div>;
}
return (
<div className="w-full mx-auto p-6">
<h1 className="text-2xl font-bold mb-2">
SURVEI KEPUASAN PENGGUNA MEDIAHUB POLRI
</h1>
<p className="text-sm mb-6 text-gray-600">
Kami menghargai pendapat Anda! Survei ini bertujuan untuk meningkatkan
kualitas layanan MediaHub Polri.
</p>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
{renderControllerGroup(
"accessFrequency",
"1. Seberapa sering Anda mengakses MediaHub Polri?",
options.accessFrequency
)}
<div>
<p className="font-medium">
2. Bagaimana pengalaman Anda dalam mengakses website ini?
</p>
<div className="space-y-3 mt-2">
{renderControllerGroup(
"uiExperienceDesign",
"a) Tampilan dan desain website",
options.uiExperienceDesign
)}
{renderControllerGroup(
"uiExperienceNavigation",
"b) Kemudahan navigasi",
options.uiExperienceNavigation
)}
{renderControllerGroup(
"uiExperienceSpeed",
"c) Kecepatan akses website",
options.uiExperienceSpeed
)}
</div>
</div>
<div>
<p className="font-medium">
3. Seberapa puas Anda dengan informasi yang tersedia di MediaHub
Polri?
</p>
<div className="space-y-3 mt-2">
{renderControllerGroup(
"infoAccuracy",
"a) Akurat dan terpercaya",
options.infoAccuracy
)}
{renderControllerGroup(
"infoCompleteness",
"b) Kelengkapan berita dan informasi",
options.infoCompleteness
)}
</div>
</div>
{renderControllerGroup(
"usefulness",
"4. Apakah Anda merasa website ini membantu dalam mendapatkan informasi terkait Polri?",
options.usefulness
)}
<div>
<p className="font-medium">5. Apa saran atau masukan Anda?</p>
<Controller
name="suggestion"
control={control}
render={({ field }) => (
<Textarea
placeholder="Tulis pesan Anda..."
value={field.value}
onChange={field.onChange}
/>
)}
/>
</div>
<div className="flex justify-end gap-2">
<Button type="submit" disabled={isLoading}>
{isLoading ? "Mengirim..." : "Kirim"}
</Button>
</div>
</form>
</div>
);
}

View File

@ -990,129 +990,7 @@ export default function FormTaskTaDetail() {
<p className="text-red-400 text-sm">{errors.title.message}</p>
)}
</div>
<div className="flex flex-col sm:flex-row lg:flex-row sm:items-center lg:items-center">
<div className="mt-6 space-y-2">
<Label>{t("assignment-selection")}</Label>
<Select
onValueChange={setSelectedTarget}
value={detail.assignedToRole}
>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
<SelectItem value="3,4">Semua Pengguna</SelectItem>
<SelectItem value="4">Kontributor</SelectItem>
<SelectItem value="3">Approver</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex flex-wrap gap-3 mt-6 lg:pt-5 lg:ml-3">
{Object.keys(unitSelection).map((key) => (
<div className="flex items-center gap-2" key={key}>
<Checkbox
id={key}
disabled
checked={
unitSelection[key as keyof typeof unitSelection]
}
onCheckedChange={(value) =>
setUnitSelection({ ...unitSelection, [key]: value })
}
/>
<Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
</div>
))}
</div>
<div className="mt-6 lg:pt-5 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
disabled
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
disabled
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
disabled
checked={checkedLevels.has(polres.id)}
onCheckedChange={() =>
handleCheckboxChange(polres.id)
}
className="mr-2"
/>
{polres.name}
</Label>
))}
</div>
)}
</div>
))}
</div>
</DialogContent>
</Dialog>
</div>
</div>
{/* <div className="mt-6 space-y-2">
<Label>{t("type-task")}</Label>
<RadioGroup
@ -1128,19 +1006,6 @@ export default function FormTaskTaDetail() {
<Label htmlFor="medsos-mediahub">Medsos Mediahub</Label>
</RadioGroup>
</div> */}
<div className="mt-6 space-y-2">
<Label>{t("assigment-type")} </Label>
<RadioGroup
value={detail?.assignmentType}
onValueChange={(value) => setTaskType(String(value))}
className="flex flex-wrap gap-3"
>
<RadioGroupItem value="atensi-khusus" id="khusus" />
<Label htmlFor="atensi-khusus">Atensi Khusus</Label>
<RadioGroupItem value="tugas-harian" id="harian" />
<Label htmlFor="tugas-harian">Tugas Harian</Label>
</RadioGroup>
</div>
<div className="mt-5 space-y-2">
<Label>{t("areas-expertise")}</Label>
<div className="flex flex-wrap gap-4">
@ -1450,25 +1315,6 @@ export default function FormTaskTaDetail() {
)}
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
{/* Display remaining time */}
<div className="mt-4">
<Label>Link Url</Label>
{urlInputs.map((url: any, index: any) => (
<div
key={url.id}
className="flex items-center gap-2 mt-2"
>
<input
type="url"
className="border rounded p-2 w-full"
value={url}
// onChange={(e) =>
// handleLinkChange(index, e.target.value)
// }
placeholder={`Masukkan link berita ${index + 1}`}
/>
</div>
))}
</div>
</div>
</div>
</div>

View File

@ -805,141 +805,6 @@ export default function FormTaskTaEdit() {
<p className="text-red-400 text-sm">{errors.title.message}</p>
)}
</div>
<div className="flex flex-col sm:flex-row lg:flex-row sm:items-center lg:items-center">
<div className="mt-5 space-y-2">
<Label>Tujuan Pemilihan Tugas</Label>
<Select
onValueChange={setSelectedTarget}
value={detail.assignedToRole}
>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
<SelectItem value="3,4">Semua Pengguna</SelectItem>
<SelectItem value="4">Kontributor</SelectItem>
<SelectItem value="3">Approver</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex flex-wrap gap-3 mt-6 lg:pt-7 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="mt-6 lg:pt-6 lg:pl-3">
<Dialog>
<DialogTrigger asChild>
<Button variant="soft" size="sm" color="primary">
[Kustom]
</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 className="mt-6 space-y-2">
<Label>{t("assigment-type")} </Label>
<RadioGroup
defaultValue={detail?.assignmentType}
onValueChange={(value) => setTaskType(String(value))}
className="flex flex-wrap gap-3"
>
<RadioGroupItem value="atensi-khusus" id="khusus" />
<Label htmlFor="atensi-khusus">Atensi Khusus</Label>
<RadioGroupItem value="tugas-harian" id="harian" />
<Label htmlFor="tugas-harian">Tugas Harian</Label>
</RadioGroup>
</div>
<div className="mt-5 space-y-2">
<Label>{t("areas-expertise")}</Label>
<div className="flex flex-wrap gap-4">

View File

@ -33,7 +33,7 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { ChevronDown, ChevronUp, Trash2 } from "lucide-react";
import { CalendarIcon, ChevronDown, ChevronUp, Trash2 } from "lucide-react";
import { AudioRecorder } from "react-audio-voice-recorder";
import FileUploader from "@/components/form/shared/file-uploader";
import { Upload } from "tus-js-client";
@ -42,6 +42,18 @@ import { getCsrfToken } from "@/service/auth";
import { loading } from "@/lib/swal";
import { useTranslations } from "next-intl";
import dynamic from "next/dynamic";
import { cn } from "@/lib/utils";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Calendar } from "@/components/ui/calendar";
import { addDays, format, setDate } from "date-fns";
import { DateRange } from "react-day-picker";
import TimePicker from "react-time-picker";
import "react-time-picker/dist/TimePicker.css";
import "react-clock/dist/Clock.css";
const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -134,6 +146,9 @@ export default function FormTaskTa() {
const [isTextUploadFinish, setIsTextUploadFinish] = useState(false);
const [isAudioUploadFinish, setIsAudioUploadFinish] = useState(false);
const [voiceNoteLink, setVoiceNoteLink] = useState("");
const [date, setDate] = React.useState<DateRange | undefined>({
from: new Date(2024, 0, 1),
});
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
const [unitSelection, setUnitSelection] = useState({
@ -478,7 +493,7 @@ export default function FormTaskTa() {
};
const upload = new Upload(file, {
endpoint: `${process.env.NEXT_PUBLIC_API}/assignment/file/upload`,
endpoint: `${process.env.NEXT_PUBLIC_API}/assignment-expert/file/upload`,
headers: headers,
retryDelays: [0, 3000, 6000, 12_000, 24_000],
chunkSize: 20_000,
@ -548,7 +563,7 @@ export default function FormTaskTa() {
isAudioUploadFinish &&
isTextUploadFinish
) {
successSubmit("/in/contributor/task");
successSubmit("/in/contributor/task-ta");
}
}
@ -735,6 +750,46 @@ export default function FormTaskTa() {
<Label htmlFor="tugas-harian">Tugas Harian</Label>
</RadioGroup>
</div>
<div className="flex flex-col space-y-2 mt-5">
<Label className="mr-3 mb-1">Tanggal</Label>
<Popover>
<PopoverTrigger asChild className="px-0">
<Button
size="md"
id="date"
variant={"outline"}
className={cn(
"w-[280px] lg:w-[250px] justify-start text-left font-normal border border-slate-300 px-0 md:px-0 lg:px-4",
!date && "text-muted-foreground"
)}
>
<CalendarIcon size={15} className="mr-3" />
{date?.from ? (
date.to ? (
<>
{format(date.from, "LLL dd, y")} -{" "}
{format(date.to, "LLL dd, y")}
</>
) : (
format(date.from, "LLL dd, y")
)
) : (
<span>Pick a date</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
initialFocus
mode="range"
defaultMonth={date?.from}
selected={date}
onSelect={setDate}
numberOfMonths={1}
/>
</PopoverContent>
</Popover>
</div>
<div className="mt-5 space-y-2">
<Label>{t("areas-expertise")}</Label>
<div className="flex flex-wrap gap-4">
@ -779,27 +834,6 @@ export default function FormTaskTa() {
))}
</div>
</div>
{/* <div className="mt-5">
<Label>Broadcast </Label>
<RadioGroup
value={broadcastType} // Nilai terpilih diambil dari state broadcastType
onValueChange={(value) => setBroadcastType(value)} // Mengatur nilai saat radio berubah
className="flex flex-wrap gap-3"
>
<div className="flex items-center gap-2">
<RadioGroupItem value="all" id="all" />
<Label htmlFor="all">Semua</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="email" id="email" />
<Label htmlFor="email">Email Blast</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="whatsapp" id="whatsapp" />
<Label htmlFor="whatsapp">WhatsApp Blast</Label>
</div>
</RadioGroup>
</div> */}
<div className="mt-5 space-y-2">
<Label>{t("description")}</Label>
<Controller

View File

@ -0,0 +1,934 @@
"use client";
import React, {
ChangeEvent,
useEffect,
useRef,
Fragment,
useState,
} from "react";
import { useForm, Controller } from "react-hook-form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Card } from "@/components/ui/card";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import { Upload } from "tus-js-client";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { redirect, useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import JoditEditor from "jodit-react";
import { register } from "module";
import { Switch } from "@/components/ui/switch";
import Cookies from "js-cookie";
import {
createMedia,
getTagsBySubCategoryId,
listEnableCategory,
uploadThumbnail,
} from "@/service/content/content";
import { uploadThumbnailBlog } from "@/service/blog/blog";
import { Textarea } from "@/components/ui/textarea";
import {
generateDataArticle,
getDetailArticle,
getGenerateKeywords,
getGenerateTitle,
} from "@/service/content/ai";
import { getCookiesDecrypt } from "@/lib/utils";
import { useDropzone } from "react-dropzone";
import { Icon } from "@iconify/react";
import { CloudUpload, Trash2 } from "lucide-react";
import Image from "next/image";
import { error, loading } from "@/config/swal";
import { Item } from "@radix-ui/react-dropdown-menu";
import { data } from "jquery";
import { options } from "@fullcalendar/core/preact.js";
import dynamic from "next/dynamic";
import { getCsrfToken } from "@/service/auth";
import { Link } from "@/i18n/routing";
import { request } from "http";
import { useTranslations } from "next-intl";
import FileUploader from "../shared/file-uploader";
import { AudioRecorder } from "react-audio-voice-recorder";
import { getTaskTa } from "@/service/task";
import { taskDetail } from "./task-ta-form";
interface FileWithPreview extends File {
preview: string;
}
type Category = {
id: string;
name: string;
};
type Option = {
id: string;
label: string;
};
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function FormTaskTaNew() {
const MySwal = withReactContent(Swal);
const router = useRouter();
const editor = useRef(null);
type ImageSchema = z.infer<typeof imageSchema>;
const t = useTranslations("Form");
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType");
const roleId = getCookiesDecrypt("urie");
const { id } = useParams() as { id: string };
console.log(id);
const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategory, setSelectedCategory] = useState<any>();
const [tags, setTags] = useState<any[]>([]);
const [thumbnail, setThumbnail] = useState<File | null>(null);
const [preview, setPreview] = useState<string | null>(null);
const [selectedLanguage, setSelectedLanguage] = useState("");
const [selectedSEO, setSelectedSEO] = useState<string>("");
const [title, setTitle] = useState<string>("");
const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>("");
const [editingArticleId, setEditingArticleId] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [isLoadingData, setIsLoadingData] = useState<boolean>(false);
const [articleIds, setArticleIds] = useState<string[]>([]);
const [isGeneratedArticle, setIsGeneratedArticle] = useState(false);
const [articleBody, setArticleBody] = useState<string>("");
const [selectedArticleId, setSelectedArticleId] = useState<string | null>(
null
);
const [selectedMainKeyword, setSelectedMainKeyword] = useState("");
const [selectedWritingStyle, setSelectedWritingStyle] = useState("");
const [selectedSize, setSelectedSize] = useState("");
const [detailData, setDetailData] = useState<any>(null);
const [articleImages, setArticleImages] = useState<string[]>([]);
const [isSwitchOn, setIsSwitchOn] = useState<boolean>(false);
const inputRef = useRef<HTMLInputElement>(null);
const [imageFiles, setImageFiles] = useState<FileWithPreview[]>([]);
const [videoFiles, setVideoFiles] = useState<FileWithPreview[]>([]);
const [textFiles, setTextFiles] = useState<FileWithPreview[]>([]);
const [audioFiles, setAudioFiles] = useState<FileWithPreview[]>([]);
const [isImageUploadFinish, setIsImageUploadFinish] = useState(false);
const [isVideoUploadFinish, setIsVideoUploadFinish] = useState(false);
const [isTextUploadFinish, setIsTextUploadFinish] = useState(false);
const [isAudioUploadFinish, setIsAudioUploadFinish] = useState(false);
const [audioFile, setAudioFile] = useState<File | null>(null);
const [isRecording, setIsRecording] = useState(false);
const [links, setLinks] = useState<string[]>([""]);
const [timer, setTimer] = useState<number>(120);
const [fileTypeId, setFileTypeId] = useState<string>("");
const [content, setContent] = useState("");
const [selectedTarget, setSelectedTarget] = useState("");
const [unitSelection, setUnitSelection] = useState({
allUnit: false,
mabes: false,
polda: false,
polres: false,
});
let progressInfo: any = [];
let counterUpdateProgress = 0;
const [progressList, setProgressList] = useState<any>([]);
let uploadPersen = 0;
const [isStartUpload, setIsStartUpload] = useState(false);
const [counterProgress, setCounterProgress] = useState(0);
const [files, setFiles] = useState<FileWithPreview[]>([]);
const [filesTemp, setFilesTemp] = useState<File[]>([]);
const [publishedFor, setPublishedFor] = useState<string[]>([]);
const [detail, setDetail] = useState<taskDetail>();
const [refresh] = useState(false);
const options: Option[] = [
{ id: "all", label: "SEMUA" },
{ id: "5", label: "UMUM" },
{ id: "6", label: "JOURNALIS" },
{ id: "7", label: "POLRI" },
{ id: "8", label: "KSP" },
];
const { getRootProps, getInputProps } = useDropzone({
onDrop: (acceptedFiles) => {
setFiles(acceptedFiles.map((file) => Object.assign(file)));
},
});
const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
description: z
.string()
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." })
.or(
z.literal(articleBody || "").refine((val) => val.length > 0, {
message: "Deskripsi diperlukan.",
})
),
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
// tags: z.string().min(1, { message: "Judul diperlukan" }),
});
const {
control,
handleSubmit,
getValues,
setValue,
formState: { errors },
} = useForm<ImageSchema>({
resolver: zodResolver(imageSchema),
});
const handleLinkChange = (index: number, value: string) => {
const updatedLinks = [...links];
updatedLinks[index] = value;
setLinks(updatedLinks);
};
const handleAddRow = () => {
setLinks([...links, ""]);
};
// Remove a specific link row
const handleRemoveRow = (index: number) => {
const updatedLinks = links.filter((_: any, i: any) => i !== index);
setLinks(updatedLinks);
};
const addAudioElement = (blob: Blob) => {
const url = URL.createObjectURL(blob);
const audio = document.createElement("audio");
audio.src = url;
audio.controls = true;
document.body.appendChild(audio);
// Convert Blob to File and add preview
const fileWithPreview: FileWithPreview = Object.assign(
new File([blob], "voiceNote.webm", { type: "audio/webm" }),
{ preview: url }
);
// Add to state
setAudioFile(fileWithPreview);
setAudioFiles((prev) => [...prev, fileWithPreview]);
};
const handleDeleteAudio = (index: number) => {
setAudioFiles((prev) => prev.filter((_, idx) => idx !== index));
};
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" && e.currentTarget.value.trim()) {
e.preventDefault();
const newTag = e.currentTarget.value.trim();
if (!tags.includes(newTag)) {
setTags((prevTags) => [...prevTags, newTag]); // Add new tag
if (inputRef.current) {
inputRef.current.value = ""; // Clear input field
}
}
}
};
const handleRemoveTag = (index: number) => {
setTags((prevTags) => prevTags.filter((_, i) => i !== index)); // Remove tag
};
const handleRemoveImage = (index: number) => {
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
};
useEffect(() => {
async function initState() {
getCategories();
// setVideoActive(fileTypeId == '2');
// getRoles();
}
initState();
}, []);
const getCategories = async () => {
try {
const category = await listEnableCategory(fileTypeId);
const resCategory: Category[] = category?.data.data.content;
setCategories(resCategory);
console.log("data category", resCategory);
if (scheduleId && scheduleType === "3") {
const findCategory = resCategory.find((o) =>
o.name.toLowerCase().includes("pers rilis")
);
if (findCategory) {
// setValue("categoryId", findCategory.id);
setSelectedCategory(findCategory.id); // Set the selected category
const response = await getTagsBySubCategoryId(findCategory.id);
setTags(response?.data?.data);
}
}
} catch (error) {
console.error("Failed to fetch categories:", error);
}
};
const handleCheckboxChange = (id: string): void => {
if (id === "all") {
if (publishedFor.includes("all")) {
// Uncheck all checkboxes
setPublishedFor([]);
} else {
// Select all checkboxes
setPublishedFor(
options
.filter((opt: any) => opt.id !== "all")
.map((opt: any) => opt.id)
);
}
} else {
const updatedPublishedFor = publishedFor.includes(id)
? publishedFor.filter((item) => item !== id)
: [...publishedFor, id];
// Remove "all" if any checkbox is unchecked
if (publishedFor.includes("all") && id !== "all") {
setPublishedFor(updatedPublishedFor.filter((item) => item !== "all"));
} else {
setPublishedFor(updatedPublishedFor);
}
}
};
useEffect(() => {
async function initState() {
if (id) {
const response = await getTaskTa(id);
const details = response?.data?.data;
setDetail(details);
// Add more state setting here based on other fields like broadcastType, taskOutput, etc.
}
}
initState();
}, [id, refresh]);
const save = async (data: ImageSchema) => {
loading();
const finalTags = tags.join(", ");
const finalTitle = isSwitchOn ? title : data.title;
const finalDescription = articleBody || data.description;
if (!finalDescription.trim()) {
MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
return;
}
let requestData: {
assignmentExpertId: any;
title: string;
description: string;
htmlDescription: string;
fileTypeId: string;
categoryId: any;
subCategoryId: any;
uploadedBy: string;
statusId: string;
publishedFor: string;
creatorName: string;
tags: string;
isYoutube: boolean;
isInternationalMedia: boolean;
attachFromScheduleId?: number; // ✅ Tambahkan properti ini
} = {
...data,
assignmentExpertId: detail?.id || null,
title: finalTitle,
description: finalDescription,
htmlDescription: finalDescription,
fileTypeId: fileTypeId,
categoryId: selectedCategory,
subCategoryId: selectedCategory,
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
statusId: "1",
publishedFor: publishedFor.join(","),
creatorName: data.creatorName,
tags: finalTags,
isYoutube: false,
isInternationalMedia: false,
};
let id = Cookies.get("idCreate");
if (scheduleId !== undefined) {
requestData.attachFromScheduleId = Number(scheduleId); // ✅ Tambahkan nilai ini
}
if (id == undefined) {
const response = await createMedia(requestData);
console.log("Form Data Submitted:", requestData);
Cookies.set("idCreate", response?.data?.data, { expires: 1 });
id = response?.data?.data;
// Upload Thumbnail
loading();
if (imageFiles?.length == 0) {
setIsImageUploadFinish(true);
}
imageFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "1", "0");
});
if (videoFiles?.length == 0) {
setIsVideoUploadFinish(true);
}
videoFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "2", "0");
});
if (textFiles?.length == 0) {
setIsTextUploadFinish(true);
}
textFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "3", "0");
});
if (audioFiles?.length == 0) {
setIsAudioUploadFinish(true);
}
audioFiles.map(async (item: FileWithPreview, index: number) => {
await uploadResumableFile(
index,
String(id),
item, // Use .file to access the actual File object
"4",
"0" // Optional: Replace with actual duration if available
);
});
}
};
const onSubmit = (data: ImageSchema) => {
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);
}
});
};
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}/media/file/upload`,
headers: headers,
retryDelays: [0, 3000, 6000, 12_000, 24_000],
chunkSize: 20_000,
metadata: {
mediaid: id,
filename: file.name,
contentType: file.type,
fileTypeId: fileTypeId,
duration,
isWatermark: "true",
},
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);
} else if (fileTypeId == "2") {
setIsVideoUploadFinish(true);
}
if (fileTypeId == "3") {
setIsTextUploadFinish(true);
}
if (fileTypeId == "4") {
setIsAudioUploadFinish(true);
}
},
});
upload.start();
}
useEffect(() => {
successTodo();
}, [
isImageUploadFinish,
isVideoUploadFinish,
isAudioUploadFinish,
isTextUploadFinish,
]);
function successTodo() {
if (
isImageUploadFinish &&
isVideoUploadFinish &&
isAudioUploadFinish &&
isTextUploadFinish
) {
successSubmit("/in/contributor/task-ta");
}
}
const successSubmit = (redirect: string) => {
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push(redirect);
});
};
// function successTodo() {
// let counter = 0;
// for (const element of progressInfo) {
// if (element.percentage == 100) {
// counter++;
// }
// }
// if (counter == progressInfo.length) {
// setIsStartUpload(false);
// // hideProgress();
// Cookies.remove("idCreate");
// successSubmit("in/contributor/content/image/");
// }
// }
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
setThumbnail(file);
console.log("Selected Thumbnail:", file);
}
if (file) {
setPreview(URL.createObjectURL(file));
}
};
const renderFilePreview = (file: FileWithPreview) => {
if (file.type.startsWith("image")) {
return (
<Image
width={48}
height={48}
alt={file.name}
src={URL.createObjectURL(file)}
className=" rounded border p-0.5"
/>
);
} else {
return <Icon icon="tabler:file-description" />;
}
};
const handleRemoveFile = (file: FileWithPreview) => {
const uploadedFiles = files;
const filtered = uploadedFiles.filter((i) => i.name !== file.name);
setFiles([...filtered]);
};
const fileList = files.map((file) => (
<div
key={file.name}
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)}</div>
<div>
<div className=" text-sm text-card-foreground">{file.name}</div>
<div className=" text-xs font-light text-muted-foreground">
{Math.round(file.size / 100) / 10 > 1000 ? (
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
) : (
<>{(Math.round(file.size / 100) / 10).toFixed(1)}</>
)}
{" kb"}
</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>
));
const handleRemoveAllFiles = () => {
setFiles([]);
};
useEffect(() => {
// Jika input title kosong, isi dengan hasil generate title
if (!getValues("title") && title) {
setValue("title", title);
}
}, [title, getValues, setValue]);
return (
<div>
{detail !== undefined ? (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-col lg:flex-row gap-10">
<Card className="w-full lg:w-8/12">
<div className="px-6 py-6">
<div className="gap-5 mb-5">
{/* Input Title */}
<div className="space-y-2 py-3">
<Label>{t("title")}</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
defaultValue={detail?.title}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.title?.message && (
<p className="text-red-400 text-sm">
{errors.title.message}
</p>
)}
</div>
<div className="flex flex-col sm:flex-row lg:flex-row sm:items-center lg:items-center gap-3">
<div className=" space-y-2 w-3/12">
<Label>{t("assigment-type")}</Label>
<Select onValueChange={(val) => setFileTypeId(val)}>
<SelectTrigger size="md">
<SelectValue placeholder="Choose" />
</SelectTrigger>
<SelectContent>
<SelectItem value="2">Audio Visual</SelectItem>
<SelectItem value="4">Audio</SelectItem>
<SelectItem value="1">Image</SelectItem>
<SelectItem value="3">Teks</SelectItem>
</SelectContent>
</Select>
</div>
<div className="py-3 space-y-2 w-9/12">
<Label>{t("category")}</Label>
<Select
value={selectedCategory} // Ensure selectedTarget is updated correctly
onValueChange={(id) => {
console.log("Selected Category ID:", id);
setSelectedCategory(id);
}}
>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
{categories.map((category) => (
<SelectItem
key={category.id}
value={category.id.toString()}
>
{category.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div className="py-3 space-y-2">
<Label>{t("description")}</Label>
<Controller
control={control}
name="description"
render={({ field: { onChange, value } }) =>
isLoadingData ? (
<div className="flex justify-center items-center h-40">
<p className="text-gray-500">
Loading Proses Data...
</p>
</div>
) : (
<CustomEditor
onChange={onChange}
initialData={articleBody || value}
/>
)
}
/>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
<div className="space-y-2.5 mt-5">
<Label htmlFor="attachments">{t("attachment")}</Label>
<div className="space-y-3">
{fileTypeId === "2" && (
<div>
<Label>{t("audio-visual")}</Label>
<FileUploader
accept={{ "mp4/*": [], "mov/*": [] }}
maxSize={100}
label="Upload file dengan format .mp4 atau .mov."
onDrop={(files) => setVideoFiles(files)}
/>
</div>
)}
{fileTypeId === "1" && (
<div className="space-y-2">
<Label>{t("image")}</Label>
<FileUploader
accept={{ "image/*": [] }}
maxSize={100}
label="Upload file dengan format .png, .jpg, atau .jpeg."
onDrop={(files) => setImageFiles(files)}
/>
</div>
)}
{fileTypeId === "3" && (
<div className="space-y-2">
<Label>{t("text")}</Label>
<FileUploader
accept={{ "pdf/*": [] }}
maxSize={100}
label="Upload file dengan format .pdf."
onDrop={(files) => setTextFiles(files)}
/>
</div>
)}
{fileTypeId === "4" && (
<div className="space-y-2">
<Label>{t("audio")}</Label>
<AudioRecorder
onRecordingComplete={addAudioElement}
audioTrackConstraints={{
noiseSuppression: true,
echoCancellation: true,
}}
downloadOnSavePress={true}
downloadFileExtension="webm"
/>
<FileUploader
accept={{ "mp3/*": [], "wav/*": [] }}
maxSize={100}
label="Upload file dengan format .mp3 atau .wav."
onDrop={(files) =>
setAudioFiles((prev) => [...prev, ...files])
}
className="mt-2"
/>
</div>
)}
{fileTypeId === "4" &&
audioFiles?.map((audio: any, idx: any) => (
<div
key={idx}
className="flex flex-row justify-between items-center"
>
<p>{t("voice-note")}</p>
<Button
type="button"
onClick={() => handleDeleteAudio(idx)}
size="sm"
color="destructive"
>
X
</Button>
</div>
))}
{isRecording && (
<p>Recording... {timer} seconds remaining</p>
)}
</div>
</div>
</div>
{/* Submit Button */}
</div>
</Card>
<div className="w-full lg:w-4/12">
<Card className=" h-[500px]">
<div className="px-3 py-3">
<div className="space-y-2">
<Label>{t("creator")}</Label>
<Controller
control={control}
name="creatorName"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.creatorName?.message && (
<p className="text-red-400 text-sm">
{errors.creatorName.message}
</p>
)}
</div>
</div>
<div className="px-3 py-3 space-y-2">
<Label htmlFor="tags">{t("tags")}</Label>
<Input
type="text"
id="tags"
placeholder="Add a tag and press Enter"
onKeyDown={handleAddTag}
ref={inputRef}
/>
<div className="mt-3 ">
{tags.map((tag, index) => (
<span
key={index}
className=" px-1 py-1 rounded-lg bg-black text-white mr-2 text-sm font-sans"
>
{tag}{" "}
<button
type="button"
onClick={() => handleRemoveTag(index)}
className="remove-tag-button"
>
×
</button>
</span>
))}
</div>
</div>
<div className="px-3 py-3">
<div className="flex flex-col gap-3 space-y-2">
<Label>{t("publish-target")}</Label>
{options.map((option) => (
<div key={option.id} className="flex gap-2 items-center">
<Checkbox
id={option.id}
checked={
option.id === "all"
? publishedFor.length ===
options.filter((opt: any) => opt.id !== "all")
.length
: publishedFor.includes(option.id)
}
onCheckedChange={() =>
handleCheckboxChange(option.id)
}
/>
<Label htmlFor={option.id}>{option.label}</Label>
</div>
))}
</div>
</div>
</Card>
<div className="flex flex-row justify-end gap-3">
<div className="mt-4">
<Button type="submit" color="primary">
{t("submit")}
</Button>
</div>
<div className="mt-4">
<Link href={"/contributor/content/image"}>
<Button type="submit" color="primary" variant="outline">
{t("cancel")}
</Button>
</Link>
</div>
</div>
</div>
</div>
</form>
) : (
""
)}
</div>
);
}

View File

@ -692,7 +692,7 @@ export default function FormTaskEdit() {
<form onSubmit={handleSubmit(onSubmit)}>
<div className="gap-5 mb-5">
<div className="space-y-2">
<Label>Judul</Label>
<Label>{t("title")}</Label>
<Controller
control={control}
name="title"
@ -712,7 +712,7 @@ export default function FormTaskEdit() {
</div>
<div className="flex flex-col sm:flex-row lg:flex-row sm:items-center lg:items-center">
<div className="mt-5 space-y-2">
<Label>Tujuan Pemilihan Tugas</Label>
<Label>{t("assignment-selection")}</Label>
<Select
onValueChange={setSelectedTarget}
value={detail.assignedToRole}
@ -752,7 +752,7 @@ export default function FormTaskEdit() {
<Dialog>
<DialogTrigger asChild>
<Button variant="soft" size="sm" color="primary">
[Kustom]
[{t("custom")}]
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">

View File

@ -1,16 +1,40 @@
"use client";
import React from "react";
import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import React, { useEffect, useState } from "react";
import {
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import Image from "next/image";
import Coverage from "./coverage";
import Division from "./division";
const AreaCoverageWorkUnits = () => {
const [openPolda, setOpenPolda] = useState(false);
const [openSatker, setOpenSatker] = useState(false);
useEffect(() => {
if (openPolda || openSatker) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "";
}
// Cleanup on unmount
return () => {
document.body.style.overflow = "";
};
}, [openPolda, openSatker]);
return (
<div className="mx-auto px-4 lg:px-12 py-6">
<h2 className="text-center text-2xl font-bold text-gray-800 dark:text-white mb-4">
Liputan <span className="text-[#bb3523]">Wilayah</span> & <span className="text-[#bb3523]">Satker</span>
Liputan <span className="text-[#bb3523]">Wilayah</span> &{" "}
<span className="text-[#bb3523]">Satker</span>
</h2>
<div className="h-1 w-[250px] bg-[#bb3523] mx-auto mb-6 rounded"></div>
<div className="flex flex-col justify-center lg:flex-row gap-8">
@ -43,9 +67,15 @@ const AreaCoverageWorkUnits = () => {
</Dialog>
{/* SATKER */}
<Dialog>
<Dialog open={openSatker} onOpenChange={setOpenSatker}>
<DialogTrigger className="flex flex-col gap-2 justify-center items-center shadow-lg group rounded-xl py-5 px-24 border-2 border-transparent hover:border-[#bb3523] transition-all duration-300">
<Image width={1920} height={1080} alt="polri" src="/assets/logo-polri.png" className="h-32 w-32 group-hover:scale-110 group-hover:border-[#bb3523] transition-transform duration-300" />
<Image
width={1920}
height={1080}
alt="polri"
src="/assets/logo-polri.png"
className="h-32 w-32 group-hover:scale-110 group-hover:border-[#bb3523] transition-transform duration-300"
/>
<p className="text-base font-bold">Satuan Kerja Polri</p>
</DialogTrigger>
<DialogContent size="md">

View File

@ -1,4 +1,7 @@
import { getCategoryData, getPublicCategoryData } from "@/service/landing/landing";
import {
getCategoryData,
getPublicCategoryData,
} from "@/service/landing/landing";
import Link from "next/link";
import React, { useEffect, useState } from "react";
import { Button } from "../ui/button";
@ -7,26 +10,45 @@ import { useTranslations } from "next-intl";
import { usePathname } from "next/navigation";
import { useParams } from "next/navigation";
import Image from "next/image";
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "../ui/carousel";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "../ui/carousel";
import { useRouter } from "@/i18n/routing";
const ContentCategory = (props: { group?: string }) => {
const ContentCategory = (props: { group?: string; type: string }) => {
const [categories, setCategories] = useState<any>();
const t = useTranslations("LandingPage");
const params = useParams();
const locale = params?.locale;
const [selectedTab, setSelectedTab] = useState("image");
const poldaName = params?.polda_name;
const satkerName = params?.satker_name;
const router = useRouter();
let prefixPath = poldaName ? `/polda/${poldaName}` : satkerName ? `/satker/${satkerName}` : "/";
let prefixPath = poldaName
? `/polda/${poldaName}`
: satkerName
? `/satker/${satkerName}`
: "/";
useEffect(() => {
initFetch();
}, []);
const initFetch = async () => {
const response = await getPublicCategoryData(
props.group == "mabes" ? "" : props.group == "polda" && poldaName && String(poldaName)?.length > 1 ? poldaName : props.group == "satker" && satkerName && String(satkerName)?.length > 1 ? "satker-" + satkerName : "",
props.group == "mabes"
? ""
: props.group == "polda" && poldaName && String(poldaName)?.length > 1
? poldaName
: props.group == "satker" &&
satkerName &&
String(satkerName)?.length > 1
? "satker-" + satkerName
: "",
"",
locale == "en" ? true : false
);
@ -52,7 +74,10 @@ const ContentCategory = (props: { group?: string }) => {
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`;
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str));
const toBase64 = (str: string) =>
typeof window === "undefined"
? Buffer.from(str).toString("base64")
: window.btoa(str);
return (
<div className="px-4 lg:px-24 py-10">
@ -60,12 +85,16 @@ const ContentCategory = (props: { group?: string }) => {
<h2 className="text-center text-xl lg:text-3xl font-bold text-[#bb3523] mb-4">
{pathname?.split("/")[1] == "in" ? (
<>
<span className="text-black dark:text-white">{t("category")}&nbsp;</span>
<span className="text-black dark:text-white">
{t("category")}&nbsp;
</span>
{t("content")}
</>
) : (
<>
<span className="text-black dark:text-white">{t("content")}&nbsp;</span>
<span className="text-black dark:text-white">
{t("content")}&nbsp;
</span>
{t("category")}
</>
)}
@ -80,7 +109,9 @@ const ContentCategory = (props: { group?: string }) => {
<div onClick={() => router.push(prefixPath + `all/filter?category=${category?.id}`)} className="cursor-pointer relative group rounded-md overflow-hidden shadow-md hover:shadow-lg block">
{/* Gambar */}
<Image
placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`}
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
alt="category"
width={2560}
height={1440}
@ -93,7 +124,9 @@ const ContentCategory = (props: { group?: string }) => {
{/* Judul */}
<div className="absolute bottom-5 left-0 right-16 bg-transparent backdrop-blur-md text-white p-4 border-l-2 border-[#bb3523] z-10 group-hover:scale-x-150 origin-left">
<h3 className="text-sm font-semibold truncate">{category?.name}</h3>
<h3 className="text-sm font-semibold truncate">
{category?.name}
</h3>
</div>
</div>
</CarouselItem>
@ -110,7 +143,11 @@ const ContentCategory = (props: { group?: string }) => {
</div> */}
<div className="flex items-center flex-row justify-center mt-7">
<div
// onClick={() => router.push(prefixPath + `/${selectedTab}/filter?sortBy=${props.type}`)}
onClick={() =>
router.push(
prefixPath + `/${selectedTab}/filter?sortBy=${props.type}`
)
}
className="cursor-pointer border text-[#bb3523] rounded-lg text-sm lg:text-md px-4 py-1 border-[#bb3523]"
>
{t("seeAll")}

View File

@ -41,6 +41,7 @@ import { Autoplay, Pagination } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
import "swiper/css/pagination";
import FormSurvey from "./survey";
const HeroModal = ({ onClose }: { onClose: () => void }) => {
const [heroData, setHeroData] = useState<any>();
@ -69,13 +70,21 @@ const HeroModal = ({ onClose }: { onClose: () => void }) => {
initFetch();
}, []);
useEffect(() => {
document.body.classList.add("overflow-hidden");
return () => {
document.body.classList.remove("overflow-hidden");
};
}, []);
const initFetch = async () => {
const response = await getHeroData();
console.log(response);
setHeroData(response?.data?.data?.content);
};
return (
<div className="fixed inset-0 flex items-center justify-center backdrop-brightness-50 z-50">
<div className="fixed inset-0 flex items-center justify-center backdrop-brightness-50 z-50 ">
<div className="relative dark:bg-gray-900 rounded-lg w-[90%] md:w-[600px] p-4 shadow-none">
<Swiper
pagination={{ dynamicBullets: true }}
@ -87,7 +96,7 @@ const HeroModal = ({ onClose }: { onClose: () => void }) => {
<div className="relative h-[310px] lg:h-[420px]">
<button
onClick={onClose}
className="absolute top-3 right-3 text-gray-700 dark:text-gray-300 hover:text-black dark:hover:text-white"
className="absolute top-3 right-3 text-gray-700 dark:text-gray-300 hover:text-black dark:hover:text-white border border-white bg-white rounded-full h-8 w-8"
>
</button>
@ -179,197 +188,6 @@ const SurveyIntroModal = ({ onNext }: { onNext: () => void }) => {
);
};
const options = {
q1: [
"Setiap hari",
"Beberapa kali seminggu",
"Beberapa kali dalam sebulan",
"Baru pertama kali",
],
q2a: ["Sangat baik", "Baik", "Cukup", "Kurang", "Buruk"],
q2b: ["Sangat mudah", "Mudah", "Cukup", "Sulit", "Sangat sulit"],
q2c: ["Sangat cepat", "Cepat", "Cukup", "Lambat", "Sangat lambat"],
q3a: ["Sangat puas", "Puas", "Cukup", "Kurang puas", "Tidak puas"],
q3b: [
"Sangat lengkap",
"Lengkap",
"Cukup",
"Kurang lengkap",
"Tidak lengkap",
],
q4: [
"Sangat membantu",
"Membantu",
"Cukup membantu",
"Kurang membantu",
"Tidak membantu",
],
};
const SurveyFormModal = ({ onClose }: { onClose: () => void }) => {
useEffect(() => {
document.body.style.overflow = "hidden";
return () => {
document.body.style.overflow = "";
};
}, []);
return (
<Dialog
open
onOpenChange={(open) => {
if (!open) onClose();
}}
>
<DialogContent className="z-50 min-w-max h-[600px] overflow-y-auto">
<DialogHeader>
<DialogTitle className="text-lg font-bold">
SURVEI KEPUASAN PENGGUNA MEDIAHUB POLRI
</DialogTitle>
<DialogDescription className="text-sm">
Kami menghargai pendapat Anda! Survei ini bertujuan untuk
meningkatkan kualitas layanan MediaHub Polri. Mohon luangkan waktu
beberapa menit untuk mengisi survei ini.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 mt-4">
{/* 1 */}
<div>
<p className="font-medium">
1. Seberapa sering Anda mengakses MediaHub Polri?
</p>
<div className="grid grid-cols-2 gap-2 mt-2">
{options.q1.map((item, i) => (
<label key={i} className="flex items-center space-x-2">
<Checkbox id={`q1-${i}`} />
<span>{item}</span>
</label>
))}
</div>
</div>
{/* 2 */}
<div>
<p className="font-medium">
2. Bagaimana pengalaman Anda dalam mengakses website ini?
</p>
<div className="mt-2 space-y-3">
<div>
<p className="text-sm font-medium">
a) Tampilan dan desain website
</p>
<div className="grid grid-cols-3 gap-2 mt-1">
{options.q2a.map((item, i) => (
<label key={i} className="flex items-center space-x-2">
<Checkbox id={`q2a-${i}`} />
<span>{item}</span>
</label>
))}
</div>
</div>
<div>
<p className="text-sm font-medium">
b) Kemudahan navigasi (pencarian informasi, menu, dll)
</p>
<div className="grid grid-cols-3 gap-2 mt-1">
{options.q2b.map((item, i) => (
<label key={i} className="flex items-center space-x-2">
<Checkbox id={`q2b-${i}`} />
<span>{item}</span>
</label>
))}
</div>
</div>
<div>
<p className="text-sm font-medium">
c) Kecepatan akses website
</p>
<div className="grid grid-cols-3 gap-2 mt-1">
{options.q2c.map((item, i) => (
<label key={i} className="flex items-center space-x-2">
<Checkbox id={`q2c-${i}`} />
<span>{item}</span>
</label>
))}
</div>
</div>
</div>
</div>
{/* 3 */}
<div>
<p className="font-medium">
3. Seberapa puas Anda dengan informasi yang tersedia di MediaHub
Polri?
</p>
<div className="mt-2 space-y-3">
<div>
<p className="text-sm font-medium">a) Akurat dan terpercaya</p>
<div className="grid grid-cols-3 gap-2 mt-1">
{options.q3a.map((item, i) => (
<label key={i} className="flex items-center space-x-2">
<Checkbox id={`q3a-${i}`} />
<span>{item}</span>
</label>
))}
</div>
</div>
<div>
<p className="text-sm font-medium">
b) Kelengkapan berita dan informasi
</p>
<div className="grid grid-cols-3 gap-2 mt-1">
{options.q3b.map((item, i) => (
<label key={i} className="flex items-center space-x-2">
<Checkbox id={`q3b-${i}`} />
<span>{item}</span>
</label>
))}
</div>
</div>
</div>
</div>
{/* 4 */}
<div>
<p className="font-medium">
5. Apakah Anda merasa website ini membantu dalam mendapatkan
informasi terkait Polri?
</p>
<div className="grid grid-cols-2 gap-2 mt-2">
{options.q4.map((item, i) => (
<label key={i} className="flex items-center space-x-2">
<Checkbox id={`q4-${i}`} />
<span>{item}</span>
</label>
))}
</div>
</div>
{/* 5 */}
<div>
<p className="font-medium">
6. Apa saran atau masukan Anda untuk meningkatkan layanan MediaHub
Polri?
</p>
<Textarea placeholder="Tulis pesan Anda" />
</div>
</div>
<div className="flex justify-end gap-2 mt-6">
<DialogClose asChild>
<Button variant="outline">Batal</Button>
</DialogClose>
<Button>Kirim</Button>
</div>
</DialogContent>
</Dialog>
);
};
const ONE_MONTH = 30 * 24 * 60 * 60 * 1000;
const Hero: React.FC = () => {
@ -399,12 +217,29 @@ const Hero: React.FC = () => {
initFetch();
}, []);
// useEffect(() => {
// const roleId = Cookies.get("urie");
// const lastShown = Cookies.get("surveyLastShown");
// const now = new Date().getTime();
// if (roleId && roleId !== "2") {
// if (!lastShown || now - parseInt(lastShown) > ONE_MONTH) {
// setShowSurveyModal(true);
// Cookies.set("surveyLastShown", now.toString(), { expires: 30 });
// }
// }
// initFetch();
// }, []);
useEffect(() => {
const roleId = Cookies.get("urie");
const lastShown = Cookies.get("surveyLastShown");
const now = new Date().getTime();
if (roleId && roleId !== "2") {
const allowedRoles = ["1", "2", "3"];
if (roleId && allowedRoles.includes(roleId)) {
if (!lastShown || now - parseInt(lastShown) > ONE_MONTH) {
setShowSurveyModal(true);
Cookies.set("surveyLastShown", now.toString(), { expires: 30 });
@ -474,9 +309,7 @@ const Hero: React.FC = () => {
/>
)}
{showFormModal && (
<SurveyFormModal onClose={() => setShowFormModal(false)} />
)}
{showFormModal && <FormSurvey />}
</div>
{isLoading ? (
<div className="flex flex-col space-y-3 mx-auto w-full lg:w-2/3">
@ -504,7 +337,7 @@ const Hero: React.FC = () => {
/>
<div className="absolute bottom-0 left-0 right-0 bg-black/30 backdrop-brightness-50 text-white pb-4 px-4 pt-8 rounded-bl-2xl rounded-tr-2xl mx-3 mb-2">
<div className="absolute top-0 left-0 bottom-0 w-2 bg-[#bb3523] rounded-bl-lg"></div>
<div className="absolute top-0 left-0 bottom-0 w-1 bg-[#bb3523] rounded-bl-lg"></div>
<span className="absolute top-0 left-0 mt-2 mb-3 mx-3 bg-[#bb3523] text-white text-xs font-semibold uppercase px-2 py-1 rounded">
{list?.categoryName || "Liputan Kegiatan"}
</span>

View File

@ -71,7 +71,7 @@ const Navbar = () => {
Cookies.remove(cookieName);
});
router.push("/");
window.location.href = "/";
};
// const profilePicture = Cookies.get("profile_picture");
@ -155,7 +155,7 @@ const Navbar = () => {
return (
<div className="bg-[#f7f7f7] dark:bg-black shadow-md sticky top-0 z-50">
<div className="flex items-center justify-between px-4 lg:px-20 py-4 gap-3">
<div className="flex items-center justify-between px-4 lg:px-20 gap-3">
<div className="flex flex-row gap-8">
{/* Logo */}
<Link href={prefixPath} className="flex items-center w-[150px] h-[120px]">

View File

@ -0,0 +1,235 @@
"use client";
import { z } from "zod";
import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "../ui/dialog";
import { Checkbox } from "../ui/checkbox";
import { Button } from "../ui/button";
import { Textarea } from "../ui/textarea";
import { useState } from "react";
import { createTaskTa } from "@/service/task";
import { createSurveyData } from "@/service/survey/survey";
const surveySchema = z.object({
accessFrequency: z.string(),
uiExperienceDesign: z.string(),
uiExperienceNavigation: z.string(),
uiExperienceSpeed: z.string(),
infoAccuracy: z.string(),
infoCompleteness: z.string(),
usefulness: z.string(),
suggestion: z.string().optional(),
});
type SurveySchema = z.infer<typeof surveySchema>;
export default function FormSurvey() {
const [showSurvey, setShowSurvey] = useState(true);
const [isLoading, setIsLoading] = useState(false);
const {
control,
handleSubmit,
formState: { errors },
} = useForm<SurveySchema>({
resolver: zodResolver(surveySchema),
mode: "all",
defaultValues: {
accessFrequency: "",
uiExperienceDesign: "",
uiExperienceNavigation: "",
uiExperienceSpeed: "",
infoAccuracy: "",
infoCompleteness: "",
usefulness: "",
suggestion: "",
},
});
const options = {
accessFrequency: [
"Setiap hari",
"Beberapa kali seminggu",
"Beberapa kali dalam sebulan",
"Baru pertama kali",
],
uiExperienceDesign: ["Sangat baik", "Baik", "Cukup", "Kurang", "Buruk"],
uiExperienceNavigation: [
"Sangat mudah",
"Mudah",
"Cukup",
"Sulit",
"Sangat sulit",
],
uiExperienceSpeed: [
"Sangat cepat",
"Cepat",
"Cukup",
"Lambat",
"Sangat lambat",
],
infoAccuracy: ["Sangat puas", "Puas", "Cukup", "Kurang puas", "Tidak puas"],
infoCompleteness: [
"Sangat lengkap",
"Lengkap",
"Cukup",
"Kurang lengkap",
"Tidak lengkap",
],
usefulness: [
"Sangat membantu",
"Membantu",
"Cukup membantu",
"Kurang membantu",
"Tidak membantu",
],
};
const renderControllerGroup = (
name: keyof SurveySchema,
question: string,
choices: string[]
) => (
<div className="space-y-2">
<p className="font-medium">{question}</p>
<div className="grid grid-cols-2 gap-2">
{choices.map((choice, i) => (
<Controller
key={i}
name={name}
control={control}
render={({ field }) => (
<label className="flex items-center space-x-2">
<Checkbox
checked={field.value === choice}
onCheckedChange={() => field.onChange(choice)}
/>
<span>{choice}</span>
</label>
)}
/>
))}
</div>
{errors[name] && (
<p className="text-red-500 text-sm">
{errors[name]?.message as string}
</p>
)}
</div>
);
const onSubmit = async (data: SurveySchema) => {
setIsLoading(true);
try {
const response = await createSurveyData(data);
console.log("API Response:", response);
setShowSurvey(false);
} catch (error) {
console.error("Error submitting survey:", error);
} finally {
setIsLoading(false);
}
};
return (
<Dialog open={showSurvey} onOpenChange={setShowSurvey}>
<DialogContent className="z-50 min-w-max h-[600px] overflow-y-auto">
<DialogHeader>
<DialogTitle className="text-lg font-bold">
SURVEI KEPUASAN PENGGUNA MEDIAHUB POLRI
</DialogTitle>
<DialogDescription className="text-sm">
Kami menghargai pendapat Anda! Survei ini bertujuan untuk
meningkatkan kualitas layanan MediaHub Polri.
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6 mt-4">
{renderControllerGroup(
"accessFrequency",
"1. Seberapa sering Anda mengakses MediaHub Polri?",
options.accessFrequency
)}
<div>
<p className="font-medium">
2. Bagaimana pengalaman Anda dalam mengakses website ini?
</p>
<div className="space-y-3 mt-2">
{renderControllerGroup(
"uiExperienceDesign",
"a) Tampilan dan desain website",
options.uiExperienceDesign
)}
{renderControllerGroup(
"uiExperienceNavigation",
"b) Kemudahan navigasi",
options.uiExperienceNavigation
)}
{renderControllerGroup(
"uiExperienceSpeed",
"c) Kecepatan akses website",
options.uiExperienceSpeed
)}
</div>
</div>
<div>
<p className="font-medium">
3. Seberapa puas Anda dengan informasi yang tersedia di MediaHub
Polri?
</p>
<div className="space-y-3 mt-2">
{renderControllerGroup(
"infoAccuracy",
"a) Akurat dan terpercaya",
options.infoAccuracy
)}
{renderControllerGroup(
"infoCompleteness",
"b) Kelengkapan berita dan informasi",
options.infoCompleteness
)}
</div>
</div>
{renderControllerGroup(
"usefulness",
"4. Apakah Anda merasa website ini membantu dalam mendapatkan informasi terkait Polri?",
options.usefulness
)}
<div>
<p className="font-medium">5. Apa saran atau masukan Anda?</p>
<Controller
name="suggestion"
control={control}
render={({ field }) => (
<Textarea
placeholder="Tulis pesan Anda..."
value={field.value}
onChange={field.onChange}
/>
)}
/>
</div>
<div className="flex justify-end gap-2">
<Button variant="outline" onClick={() => setShowSurvey(false)}>
Batal
</Button>
<Button type="submit" disabled={isLoading}>
{isLoading ? "Mengirim..." : "Kirim"}
</Button>
</div>
</form>
</DialogContent>
</Dialog>
);
}

View File

@ -206,17 +206,20 @@ const LoginForm = () => {
Number(profile?.data?.data?.roleId) == 18 ||
Number(profile?.data?.data?.roleId) == 19
) {
if (profile?.data?.data?.roleId === 18) {
if (
profile?.data?.data?.roleId === 18 ||
profile?.data?.data?.roleId === 2
) {
window.location.href = "/in/dashboard/executive";
// router.push('/admin/dashboard');
Cookies.set("status", "login", {
expires: 1,
});
} else if (
profile?.data?.data?.userLevel?.id == 761 ||
profile?.data?.data?.userLevel?.id == 794 ||
profile?.data?.data?.userLevel?.parentLevelId == 761
) {
window.location.href = "/in/dashboard/executive";
window.location.href = "/in/dashboard";
Cookies.set("status", "login", {
expires: 1,
});
@ -261,8 +264,11 @@ const LoginForm = () => {
} else if (msg == "Username & password valid") {
onSubmit(data);
} else {
setStep(1);
error("Username / password incorrect");
}
// else {
// setStep(1);
// }
};
const handleSetupEmail = async () => {

View File

@ -10,7 +10,7 @@ export default function ContentProductionVisualization() {
const [hasMounted, setHasMounted] = useState(false);
const t = useTranslations("AnalyticsDashboard");
const levelName = getCookiesDecrypt("ulnae");
const poldaState = Cookies.get("state");
const state = Cookies.get("state");
const provState = Cookies.get("state-prov");
const [ticket1, setTicket1] = useState("");
@ -21,28 +21,36 @@ export default function ContentProductionVisualization() {
const baseUrl = "https://analytic.sitani.info/";
const url = "https://analytic.sitani.info/trusted/";
const safeLevelName = levelName ?? "";
const view1 =
levelName == "MABES POLRI"
? isInternational[0]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-published-produksi?"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-published-produksi?"
: `views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-published-produksi-polda?provinsi-polda=${provState}&`;
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-published-produksi?"
: safeLevelName.includes("POLDA")
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-published-produksi-polda?polda-selected=${state}&`
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-published-produksi-polda?polda-selected=${state}`;
const view2 =
levelName == "MABES POLRI"
? isInternational[1]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-konten-publisher?"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-konten-publisher?"
: `views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-konten-publisher-polda?provinsi-polda=${poldaState}&`;
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-konten-publisher?"
: safeLevelName.includes("POLDA")
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-konten-publisher-polda?polda-selected=${state}&`
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-konten-publisher-polda?polda-selected=${state}&`;
const view3 =
levelName == "MABES POLRI"
? isInternational[2]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-waktu-akses-pengguna?"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-waktu-akses-pengguna?"
: `views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-waktu-akses-pengguna-polda?provinsi-polda=${poldaState}&`;
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-waktu-akses-pengguna?"
: safeLevelName.includes("POLDA")
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-waktu-akses-pengguna-polda?polda-selected=${state}`
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-waktu-akses-pengguna-polda?polda-selected=${state}&`;
const param = ":embed=yes&:toolbar=yes&:iframeSizedToWindow=true";
const param = ":embed=yes&:toolbar=no&:iframeSizedToWindow=true";
useEffect(() => {
async function initState() {

View File

@ -8,7 +8,7 @@ import { useTranslations } from "next-intl";
export default function DashboardVisualization() {
const levelName = getCookiesDecrypt("ulnae");
const poldaState = Cookies.get("state");
const state = Cookies.get("state");
const t = useTranslations("AnalyticsDashboard");
const [ticket1, setTicket1] = useState("");
@ -18,28 +18,36 @@ export default function DashboardVisualization() {
const baseUrl = "https://analytic.sitani.info/";
const url = "https://analytic.sitani.info/trusted/";
const safeLevelName = levelName ?? "";
const view1 =
levelName == "MABES POLRI"
safeLevelName === "MABES POLRI"
? isInternational[0]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-content-monitor?"
: "views/2023_09_MediaHUB-Viz-POLDA-content-monitor_Rev100/db-content-monitor?"
: `views/2023_09_MediaHUB-Viz-ADMIN-POLDA-content-monitor_Rev100/db-content-monitor?provinsi-polda=${poldaState}&`;
: "views/2023_09_MediaHUB-Viz-ADMIN-POLDA-content-monitor_Rev100/db-content-monitor?"
: safeLevelName.includes("POLDA")
? `views/2023_09_MediaHUB-Viz-ADMIN-POLDA-content-monitor_Rev100/db-content-monitor?provinsi-polda=${state}&`
: `views/2023_09_MediaHUB-Viz-ADMIN-POLDA-content-monitor_Rev100/db-content-monitor?satker-selected=${state}&`;
const view2 =
levelName == "MABES POLRI"
? isInternational[1]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-content-interaction-konten?"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-content-interaction-konten?"
: `views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-content-interaction-konten-polda?provinsi-polda=${poldaState}&`;
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-konten-polda?"
: safeLevelName.includes("POLDA")
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-konten-polda?polda-selected=${state}&`
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-konten-polda?polda-selected=${state}&`;
const view3 =
levelName == "MABES POLRI"
? isInternational[2]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-penugasan?"
: "views/2023_09_db-penugasan_rev100/db-penugasan?"
: `views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-penugasan-polda?provinsi-polda=${poldaState}&`;
: safeLevelName.includes("POLDA")
? `views/2023_09_db-penugasan_rev100/db-penugasan?polda-selected=${state}&`
: `views/2023_09_db-penugasan_rev100/db-penugasan?polda-selected=${state}&`;
const param = ":embed=yes&:toolbar=yes&:iframeSizedToWindow=true";
const param = ":embed=yes&:toolbar=no&:iframeSizedToWindow=true";
useEffect(() => {
async function initState() {

View File

@ -10,8 +10,8 @@ export default function ManagementUserVisualization() {
const [ticket, setTicket] = useState("");
const baseUrl = "https://analytic.sitani.info/";
const url = "https://analytic.sitani.info/trusted/";
const view = "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-user-count?:iid=5&";
const param = ":embed=yes&:toolbar=yes&:iframeSizedToWindow=true";
const view = "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-user-count?";
const param = ":embed=yes&:toolbar=no&:iframeSizedToWindow=true";
const [isInternational, setIsInternational] = useState(false);
const t = useTranslations("AnalyticsDashboard");

View File

@ -10,7 +10,7 @@ export default function PatternRelationVisualization() {
const [hasMounted, setHasMounted] = useState(false);
const t = useTranslations("AnalyticsDashboard");
const levelName = getCookiesDecrypt("ulnae");
const poldaState = Cookies.get("state");
const state = Cookies.get("state");
const provState = Cookies.get("state-prov");
const [ticket1, setTicket1] = useState("");
@ -22,35 +22,45 @@ export default function PatternRelationVisualization() {
const baseUrl = "https://analytic.sitani.info/";
const url = "https://analytic.sitani.info/trusted/";
const safeLevelName = levelName ?? "";
const view1 =
levelName == "MABES POLRI"
? isInternational[0]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-konten-top10?"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-konten-top10?"
: `views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-konten-top10-polda?provinsi-polda=${provState}&`;
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-konten-top10?"
: safeLevelName.includes("POLDA")
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-konten-top10-polda?polda-selected=${state}&`
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-konten-top10-polda?polda-selected=${state}`;
const view2 =
levelName == "MABES POLRI"
? isInternational[1]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-konten?"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-konten?"
: `views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-konten-polda?provinsi-polda=${poldaState}&`;
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-konten?"
: safeLevelName.includes("POLDA")
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-konten-polda?polda-selected=${state}&`
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-konten-polda?polda-selected=${state}`;
const view3 =
levelName == "MABES POLRI"
? isInternational[2]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-konten-kategori-top10?"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-konten-kategori-top10?"
: `views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-konten-kategori-top10-polda?provinsi-polda=${poldaState}&`;
: "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}&`;
const view4 =
levelName == "MABES POLRI"
? isInternational[3]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-konten-kategori?"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-konten-kategori?"
: `views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-konten-kategori-polda?provinsi-polda=${poldaState}&`;
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-konten-kategori?"
: safeLevelName.includes("POLDA")
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-konten-kategori-polda?polda-selected=${state}&`
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-konten-kategori-polda?polda-selected=${state}&`;
const param = ":embed=yes&:toolbar=yes&:iframeSizedToWindow=true";
const param = ":embed=yes&:toolbar=no&:iframeSizedToWindow=true";
useEffect(() => {
async function initState() {

View File

@ -10,7 +10,7 @@ export default function PerformancePoldaViz() {
const [hasMounted, setHasMounted] = useState(false);
const t = useTranslations("AnalyticsDashboard");
const levelName = getCookiesDecrypt("ulnae");
const poldaState = Cookies.get("state");
const state = Cookies.get("state");
const provState = Cookies.get("state-prov");
const [ticket1, setTicket1] = useState("");
@ -22,21 +22,25 @@ export default function PerformancePoldaViz() {
const baseUrl = "https://analytic.sitani.info/";
const url = "https://analytic.sitani.info/trusted/";
const safeLevelName = levelName ?? "";
const view1 =
levelName == "MABES POLRI"
? isInternational[0]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-konten-top10?"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-ranking-polda?"
: `/views/2023_09_db-ranking-polres-by-polda_rev100/db-ranking-by-polda?polda-selected=${provState}&`;
: `views/2023_09_db-ranking-polres-by-polda_rev100/db-ranking-by-polda?polda-selected=${state}&`;
const view2 =
levelName == "MABES POLRI"
? isInternational[0]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-konten-top10?"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-konten-top10?"
: `/views/2023_09_db-ranking-polres-by-polda_rev100/db-ranking-by-polda?polda-selected=${poldaState}&`;
: safeLevelName.includes("POLDA")
? `views/2023_09_db-ranking-polres-by-polda_rev100/db-ranking-by-polda?polda-selected=${state}&`
: `views/2023_09_db-ranking-polres-by-polda_rev100/db-ranking-by-polda?polda-selected=${state}&`;
const param = ":embed=yes&:toolbar=yes&:iframeSizedToWindow=true";
const param = ":embed=yes&:toolbar=no&:iframeSizedToWindow=true";
useEffect(() => {
async function initState() {

View File

@ -29,7 +29,7 @@ export default function PerformancePolresViz() {
: "views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-konten-top10?"
: `/views/2023_09_db-ranking-polres-by-polda_rev100/db-ranking-by-polda?polda-selected=${poldaState}&`;
const param = ":embed=yes&:toolbar=yes&:iframeSizedToWindow=true";
const param = ":embed=yes&:toolbar=no&:iframeSizedToWindow=true";
useEffect(() => {
async function initState() {

View File

@ -29,7 +29,7 @@ export default function PerformanceSatkerViz() {
: "views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-konten-top10?"
: `/views/2023_09_db-ranking-polres-by-polda_rev100/db-ranking-by-polda?polda-selected=${provState}&`;
const param = ":embed=yes&:toolbar=yes&:iframeSizedToWindow=true";
const param = ":embed=yes&:toolbar=no&:iframeSizedToWindow=true";
useEffect(() => {
async function initState() {

View File

@ -1,3 +1,3 @@
export const locales = ['en', 'in', 'ar'];
export const locales = ["in", "en", "ar"];
export const baseURL = process.env.NEXT_PUBLIC_SITE_URL + "/api";
export const baseURL = process.env.NEXT_PUBLIC_SITE_URL + "/api";

View File

@ -2787,22 +2787,22 @@ export function getMenuList(pathname: string, t: any): Group[] {
menus: [
{
id: "settings",
href: "/supervisor/settings",
href: "/supervisor/setting",
label: t("settings"),
active: pathname.includes("/settings"),
active: pathname.includes("/setting"),
icon: "uil:setting",
submenus: [
{
href: "/settings/feedback",
href: "/supervisor/setting/feedback",
label: t("feedback"),
active: pathname.includes("/settings/feedback"),
active: pathname.includes("/supervisor/setting/feedback"),
icon: "clarity:employee-group-line",
children: [],
},
{
href: "/settings/social-media",
href: "/supervisor/setting/social-media",
label: t("social-media"),
active: pathname.includes("/settings/social-media"),
active: pathname.includes("/supervisor/setting/social-media"),
icon: "clarity:employee-group-line",
children: [],
},
@ -2811,7 +2811,11 @@ export function getMenuList(pathname: string, t: any): Group[] {
],
},
];
} else if (Number(roleId) == 11 || Number(roleId) == 19) {
} else if (
Number(roleId) == 11 ||
Number(roleId) == 19 ||
Number(roleId) == 12
) {
menusSelected = [
{
groupLabel: t("apps"),
@ -3604,20 +3608,20 @@ export function getMenuList(pathname: string, t: any): Group[] {
},
],
},
{
groupLabel: "",
id: "performance-polres",
menus: [
{
id: "performance-polres",
href: "/admin/performance-satker",
label: t("performance-satker"),
active: pathname.includes("/admin/performance-satker"),
icon: "ant-design:signal-filled",
submenus: [],
},
],
},
// {
// groupLabel: "",
// id: "performance-satker",
// menus: [
// {
// id: "performance-polres",
// href: "/admin/performance-satker",
// label: t("performance-satker"),
// active: pathname.includes("/admin/performance-satker"),
// icon: "ant-design:signal-filled",
// submenus: [],
// },
// ],
// },
{
groupLabel: "",
id: "media-tracking",

View File

@ -639,6 +639,8 @@
"title": "Title",
"category-name": "Category Name",
"upload-date": "Upload Date",
"generate-date": "Generate Date",
"status": "status",
"creator-group": "Creator Group",
"source": "source",
"published": "Published",

View File

@ -640,6 +640,8 @@
"title": "Judul",
"category-name": "Nama Kategori",
"upload-date": "Tanggal Upload",
"generate-date": "Tanggal Generate",
"status": "status",
"creator-group": "Pembuat",
"source": "Sumber",
"published": "Diterbitkan",

View File

@ -1,32 +1,25 @@
import createMiddleware from 'next-intl/middleware';
import {NextRequest, NextResponse} from 'next/server';
import {locales} from '@/config';
import createMiddleware from "next-intl/middleware";
import { NextRequest, NextResponse } from "next/server";
import { locales } from "@/config";
export default async function middleware(request: NextRequest) {
// Step 1: Use the incoming request (example)
const defaultLocale = request.headers.get('dashcode-locale') || 'en';
const defaultLocale = request.headers.get("dashcode-locale") || "in";
// Step 2: Create and call the next-intl middleware (example)
const handleI18nRouting = createMiddleware({
locales,
defaultLocale
defaultLocale,
});
const response = handleI18nRouting(request);
// Step 3: Alter the response (example)
response.headers.set('dashcode-locale', defaultLocale);
response.headers.set("dashcode-locale", defaultLocale);
return response;
}
export const config = {
// Match only internationalized pathnames
matcher: ['/', '/(ar|en|in)/:path*']
};
matcher: ["/", "/(ar|in|en)/:path*"],
};

View File

@ -16,6 +16,10 @@ const bundleAnalyzer = withBundleAnalyzer({
});
const nextConfig = {
// i18n: {
// locales: ["en", "in"],
// defaultLocale: "in",
// },
images: {
remotePatterns: [
{

19
service/survey/survey.ts Normal file
View File

@ -0,0 +1,19 @@
import {
httpGetInterceptor,
httpPostInterceptor,
} from "../http-config/http-interceptor-service";
export async function createSurveyData(data: any) {
const pathUrl = "users/satisfaction-survey";
return httpPostInterceptor(pathUrl, data);
}
export async function getSurveyData() {
const url = `users/satisfaction-survey/pagination`;
return httpGetInterceptor(url);
}
export async function getSurveyById(id: any) {
const url = `users/satisfaction-survey?id=${id}`;
return httpGetInterceptor(url);
}