test
This commit is contained in:
commit
0fe7721eb6
|
|
@ -17,7 +17,8 @@ COPY package.json pnpm-lock.yaml ./
|
|||
COPY vendor/ckeditor5 ./vendor/ckeditor5
|
||||
|
||||
# Install dependencies
|
||||
RUN pnpm install --frozen-lockfile
|
||||
RUN pnpm install
|
||||
# RUN pnpm install --frozen-lockfile
|
||||
|
||||
# Menyalin source code aplikasi
|
||||
COPY . .
|
||||
|
|
|
|||
|
|
@ -61,6 +61,12 @@ const columns: ColumnDef<any>[] = [
|
|||
cell: ({ row }) => <span>{row.getValue("experience")}</span>,
|
||||
},
|
||||
|
||||
{
|
||||
accessorKey: "experience",
|
||||
header: "Posisi",
|
||||
cell: ({ row }) => <span>{row.getValue("experience")}</span>,
|
||||
},
|
||||
|
||||
{
|
||||
id: "actions",
|
||||
accessorKey: "action",
|
||||
|
|
|
|||
|
|
@ -24,7 +24,13 @@ import {
|
|||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { useEffect, useState } from "react";
|
||||
import { AdministrationLevelList, getListCompetencies, getListExperiences, saveUserInternal, saveUserRolePlacements } from "@/service/management-user/management-user";
|
||||
import {
|
||||
AdministrationLevelList,
|
||||
getListCompetencies,
|
||||
getListExperiences,
|
||||
saveUserInternal,
|
||||
saveUserRolePlacements,
|
||||
} from "@/service/management-user/management-user";
|
||||
import { loading } from "@/config/swal";
|
||||
|
||||
const FormSchema = z.object({
|
||||
|
|
@ -58,7 +64,7 @@ export type Placements = {
|
|||
index: number;
|
||||
roleId?: string;
|
||||
userLevelId?: number;
|
||||
}
|
||||
};
|
||||
|
||||
export default function AddExpertForm() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
|
@ -67,7 +73,9 @@ export default function AddExpertForm() {
|
|||
resolver: zodResolver(FormSchema),
|
||||
});
|
||||
const [incrementId, setIncrementId] = useState(1);
|
||||
const [placementRows, setPlacementRows] = useState<Placements[]>([{ index: 0, roleId: "", userLevelId: 0 }]);
|
||||
const [placementRows, setPlacementRows] = useState<Placements[]>([
|
||||
{ index: 0, roleId: "", userLevelId: 0 },
|
||||
]);
|
||||
const [userCompetencies, setUserCompetencies] = useState<any>();
|
||||
const [userExperiences, setUserExperiences] = useState<any>();
|
||||
const [userLevels, setUserLevels] = useState<any>();
|
||||
|
|
@ -80,7 +88,7 @@ export default function AddExpertForm() {
|
|||
{
|
||||
id: "12",
|
||||
name: "Kurator",
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
|
||||
|
|
@ -113,12 +121,12 @@ export default function AddExpertForm() {
|
|||
userCompetencyId: data.skills,
|
||||
userExperienceId: data.experiences,
|
||||
companyName: data.company,
|
||||
}
|
||||
};
|
||||
|
||||
loading();
|
||||
const res = await saveUserInternal(dataReq);
|
||||
const resData = res?.data?.data;
|
||||
const userProfileId = resData.id;
|
||||
const userProfileId = resData?.id;
|
||||
|
||||
var placementArr: any[] = [];
|
||||
placementRows.forEach((row: any) => {
|
||||
|
|
@ -131,15 +139,14 @@ export default function AddExpertForm() {
|
|||
|
||||
const dataReq2 = {
|
||||
userId: userProfileId,
|
||||
placements: placementArr
|
||||
}
|
||||
placements: placementArr,
|
||||
};
|
||||
const res2 = await saveUserRolePlacements(dataReq2);
|
||||
const resData2 = res2?.data?.data;
|
||||
|
||||
success("/admin/add-experts");
|
||||
};
|
||||
|
||||
|
||||
function success(redirect: string): void {
|
||||
MySwal.fire({
|
||||
title: '<p class="text-green-600 font-bold">Sukses</p>',
|
||||
|
|
@ -180,7 +187,6 @@ export default function AddExpertForm() {
|
|||
setUserLevels(levelsArr);
|
||||
}
|
||||
|
||||
|
||||
function successSubmit() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
|
|
@ -194,7 +200,11 @@ export default function AddExpertForm() {
|
|||
});
|
||||
}
|
||||
|
||||
const handleSelectionChange = (index: number, type: "roleId" | "userLevelId", value: string) => {
|
||||
const handleSelectionChange = (
|
||||
index: number,
|
||||
type: "roleId" | "userLevelId",
|
||||
value: string
|
||||
) => {
|
||||
setPlacementRows((prevRows) =>
|
||||
prevRows.map((row) =>
|
||||
row.index === index ? { ...row, [type]: value } : row
|
||||
|
|
@ -202,7 +212,6 @@ export default function AddExpertForm() {
|
|||
);
|
||||
};
|
||||
|
||||
|
||||
const handleRemoveRow = (index: number) => {
|
||||
console.log(index);
|
||||
console.log(placementRows);
|
||||
|
|
@ -212,7 +221,10 @@ export default function AddExpertForm() {
|
|||
};
|
||||
|
||||
const handleAddRow = () => {
|
||||
setPlacementRows((prevRows: any) => [...prevRows, { index: incrementId, roleId: "", userLevelId: 0 }]);
|
||||
setPlacementRows((prevRows: any) => [
|
||||
...prevRows,
|
||||
{ index: incrementId, roleId: "", userLevelId: 0 },
|
||||
]);
|
||||
setIncrementId((prevId) => prevId + 1);
|
||||
};
|
||||
|
||||
|
|
@ -372,10 +384,14 @@ export default function AddExpertForm() {
|
|||
/>
|
||||
|
||||
<div className="mt-4">
|
||||
<FormLabel>Penempatan</FormLabel>
|
||||
<FormLabel>Posisi</FormLabel>
|
||||
{placementRows?.map((row: any) => (
|
||||
<div key={row.index} className="flex items-center gap-2 my-2">
|
||||
<Select onValueChange={(e) => handleSelectionChange(row.index, "roleId", e)} >
|
||||
<Select
|
||||
onValueChange={(e) =>
|
||||
handleSelectionChange(row.index, "roleId", e)
|
||||
}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Pilih Role" />
|
||||
|
|
@ -389,7 +405,11 @@ export default function AddExpertForm() {
|
|||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select onValueChange={(e) => handleSelectionChange(row.index, "userLevelId", e)}>
|
||||
<Select
|
||||
onValueChange={(e) =>
|
||||
handleSelectionChange(row.index, "userLevelId", e)
|
||||
}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Pilih User Level" />
|
||||
|
|
@ -415,11 +435,7 @@ export default function AddExpertForm() {
|
|||
)}
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
type="button"
|
||||
size="md"
|
||||
onClick={() => handleAddRow()}
|
||||
>
|
||||
<Button type="button" size="md" onClick={() => handleAddRow()}>
|
||||
Tambah
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -43,8 +43,8 @@ export default function ContentManagement() {
|
|||
const [ticket6, setTicket6] = useState("");
|
||||
const [isInternational, setIsInternational] = useState([false, false, false]);
|
||||
|
||||
const baseUrl = "https://db-mediahub.polri.go.id/";
|
||||
const url = "https://db-mediahub.polri.go.id/trusted/";
|
||||
const baseUrl = "https://analytic.sitani.info/";
|
||||
const url = "https://analytic.sitani.info/trusted/";
|
||||
|
||||
const view1 =
|
||||
levelName == "MABES POLRI"
|
||||
|
|
|
|||
|
|
@ -43,8 +43,8 @@ export default function EmergencyIssue() {
|
|||
const [ticket6, setTicket6] = useState("");
|
||||
const [isInternational, setIsInternational] = useState([false, false, false]);
|
||||
|
||||
const baseUrl = "https://db-mediahub.polri.go.id/";
|
||||
const url = "https://db-mediahub.polri.go.id/trusted/";
|
||||
const baseUrl = "https://analytic.sitani.info/";
|
||||
const url = "https://analytic.sitani.info/trusted/";
|
||||
|
||||
const view1 =
|
||||
levelName == "MABES POLRI"
|
||||
|
|
|
|||
|
|
@ -43,8 +43,8 @@ export default function FeedbackCenter() {
|
|||
const [ticket6, setTicket6] = useState("");
|
||||
const [isInternational, setIsInternational] = useState([false, false, false]);
|
||||
|
||||
const baseUrl = "https://db-mediahub.polri.go.id/";
|
||||
const url = "https://db-mediahub.polri.go.id/trusted/";
|
||||
const baseUrl = "https://analytic.sitani.info/";
|
||||
const url = "https://analytic.sitani.info/trusted/";
|
||||
|
||||
const view1 =
|
||||
levelName == "MABES POLRI"
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ export default function ContentManagement() {
|
|||
const [ticket6, setTicket6] = useState("");
|
||||
const [isInternational, setIsInternational] = useState([false, false, false]);
|
||||
|
||||
const baseUrl = "https://db-mediahub.polri.go.id/";
|
||||
const url = "https://db-mediahub.polri.go.id/trusted/";
|
||||
const baseUrl = "https://analytic.sitani.info/";
|
||||
const url = "https://analytic.sitani.info/trusted/";
|
||||
|
||||
const view1 =
|
||||
levelName == "MABES POLRI"
|
||||
|
|
|
|||
|
|
@ -2,20 +2,33 @@
|
|||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import UserExternalTable from "@/components/table/management-user/management-user-external-table";
|
||||
import UserInternalTable from "@/components/table/management-user/management-user-internal-table";
|
||||
import InternalTable from "@/components/table/management-user/management-user-internal-table";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import DashboardVisualization from "@/components/visualization/dashboard-viz";
|
||||
import ManagementUserVisualization from "@/components/visualization/management-user-viz";
|
||||
import { Link, useRouter } from "@/i18n/routing";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import Cookies from "js-cookie";
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
|
||||
export default function ManagementUser() {
|
||||
const [isInternal, setIsInternal] = useState(true);
|
||||
const [levelNumber, setLevelNumber] = useState<number | null>(null);
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
const encryptedLevel = Cookies.get("ulne");
|
||||
if (encryptedLevel) {
|
||||
const decryptedLevel = getCookiesDecrypt("ulne");
|
||||
setLevelNumber(Number(decryptedLevel));
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
router.push("?page=1");
|
||||
}, [isInternal]);
|
||||
|
||||
const showExternalButton = levelNumber !== 2 && levelNumber !== 3;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
|
|
@ -23,10 +36,7 @@ export default function ManagementUser() {
|
|||
<ManagementUserVisualization />
|
||||
</section>
|
||||
|
||||
<section
|
||||
id="table"
|
||||
className="flex flex-col gap-2 bg-white rounded-lg p-3 mt-5"
|
||||
>
|
||||
<section className="flex flex-col gap-2 bg-white rounded-lg p-3 mt-5">
|
||||
<div className="flex justify-between py-3">
|
||||
<p className="text-lg">
|
||||
Data User {isInternal ? "Internal" : "Eksternal"}
|
||||
|
|
@ -40,28 +50,31 @@ export default function ManagementUser() {
|
|||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row gap-1 border-2 rounded-md w-fit mb-5">
|
||||
<Button
|
||||
rounded="md"
|
||||
onClick={() => setIsInternal(true)}
|
||||
className={` hover:text-white
|
||||
${
|
||||
!isInternal ? "bg-white text-black " : "bg-black text-white "
|
||||
}`}
|
||||
className={`hover:text-white ${
|
||||
!isInternal ? "bg-white text-black" : "bg-black text-white"
|
||||
}`}
|
||||
>
|
||||
User Internal
|
||||
</Button>
|
||||
<Button
|
||||
rounded="md"
|
||||
onClick={() => setIsInternal(false)}
|
||||
className={`hover:text-white ${
|
||||
!isInternal ? "bg-black text-white " : "bg-white text-black "
|
||||
}
|
||||
`}
|
||||
>
|
||||
User Eksternal
|
||||
</Button>
|
||||
|
||||
{showExternalButton && (
|
||||
<Button
|
||||
rounded="md"
|
||||
onClick={() => setIsInternal(false)}
|
||||
className={`hover:text-white ${
|
||||
!isInternal ? "bg-black text-white" : "bg-white text-black"
|
||||
}`}
|
||||
>
|
||||
User Eksternal
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isInternal ? <UserInternalTable /> : <UserExternalTable />}
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -243,7 +243,10 @@ export default function CreateCategoryModal() {
|
|||
{t("add-category")}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent size="md">
|
||||
<DialogContent
|
||||
size="md"
|
||||
className="sm:h-[300px] md:h-[300px] lg:h-[500px] overflow-y-auto"
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle> {t("add-category")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
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 {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuItem,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
import { Link, useRouter } from "@/i18n/routing";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
accessorKey: "no",
|
||||
header: "No",
|
||||
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
||||
},
|
||||
{
|
||||
accessorKey: "createdAt",
|
||||
header: "Tanggal",
|
||||
cell: ({ row }) => (
|
||||
<span className="normal-case">{row.getValue("createdAt")}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "account-type",
|
||||
header: "Jenis Akun",
|
||||
cell: ({ row }) => (
|
||||
<span className="normal-case">{row.getValue("account-type")}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "userName",
|
||||
header: "UserName",
|
||||
cell: ({ row }) => (
|
||||
<span className="normal-case">{row.getValue("userName")}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "accessMediahub",
|
||||
header: "Akses Mediahub",
|
||||
cell: ({ row }) => (
|
||||
<span className="normal-case">{row.getValue("accessMediahub")}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "desaignWeb",
|
||||
header: "Tampilan Desain Web",
|
||||
cell: ({ row }) => (
|
||||
<span className="normal-case">{row.getValue("desaignWeb")}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "navigation",
|
||||
header: "Kemudahan Navigasi",
|
||||
cell: ({ row }) => (
|
||||
<span className="normal-case">{row.getValue("navigation")}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "fastAccess",
|
||||
header: "Kecepatan Akses",
|
||||
cell: ({ row }) => (
|
||||
<span className="normal-case">{row.getValue("fastAccess")}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
accessorKey: "action",
|
||||
header: "Actions",
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
|
||||
>
|
||||
<MoreVertical className="h-4 w-4 text-default-800" />
|
||||
</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>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export default columns;
|
||||
|
|
@ -0,0 +1,346 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
PaginationState,
|
||||
SortingState,
|
||||
VisibilityState,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Eye,
|
||||
MoreVertical,
|
||||
Search,
|
||||
SquarePen,
|
||||
Trash2,
|
||||
TrendingDown,
|
||||
TrendingUp,
|
||||
UserIcon,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||
import { paginationBlog } from "@/service/blog/blog";
|
||||
import { ticketingPagination } from "@/service/ticketing/ticketing";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import TablePagination from "@/components/table/table-pagination";
|
||||
import columns from "./column";
|
||||
import { getPlanningPagination } from "@/service/agenda-setting/agenda-setting";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import {
|
||||
getMediaBlastCampaignPage,
|
||||
listDataMedia,
|
||||
} from "@/service/broadcast/broadcast";
|
||||
import { listEnableCategory } from "@/service/content/content";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { close, loading } from "@/config/swal";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { NewCampaignIcon } from "@/components/icon";
|
||||
import search from "../../../app/chat/components/search";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import {
|
||||
Bar,
|
||||
BarChart,
|
||||
CartesianGrid,
|
||||
Label,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
|
||||
const data = [
|
||||
{
|
||||
question: "Seberapa Sering User Mengakses\nMediaHub Polri",
|
||||
total: 100,
|
||||
},
|
||||
{
|
||||
question: "Tampilan dan Desain pada Website\nMediaHub Polri",
|
||||
total: 90,
|
||||
},
|
||||
{
|
||||
question: "Kemudahan Navigasi pada Website\nMediaHub Polri",
|
||||
total: 80,
|
||||
},
|
||||
{
|
||||
question: "Kecepatan Akses pada Website\nMediaHub Polri",
|
||||
total: 70,
|
||||
},
|
||||
{
|
||||
question:
|
||||
"Seberapa Akurat dan Terpercaya\nInformasi pada Website MediaHub Polri",
|
||||
total: 60,
|
||||
},
|
||||
{
|
||||
question:
|
||||
"Seberapa Lengkap Informasi dan Berita\npada Website MediaHub Polri",
|
||||
total: 55,
|
||||
},
|
||||
{
|
||||
question: "Seberapa membantu dalam\nmendapatkan Informasi terkait Polri",
|
||||
total: 52,
|
||||
},
|
||||
];
|
||||
|
||||
const SurveyListTable = () => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
||||
const [totalData, setTotalData] = React.useState<number>(1);
|
||||
const [search, setSearch] = React.useState<string>("");
|
||||
const [showData, setShowData] = React.useState("polri");
|
||||
const [startDate, setStartDate] = React.useState("");
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
[]
|
||||
);
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
const [page, setPage] = React.useState(1);
|
||||
const [totalPage, setTotalPage] = React.useState(1);
|
||||
const table = useReactTable({
|
||||
data: dataTable,
|
||||
columns,
|
||||
onSortingChange: setSorting,
|
||||
onColumnFiltersChange: setColumnFilters,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
onColumnVisibilityChange: setColumnVisibility,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
onPaginationChange: setPagination,
|
||||
state: {
|
||||
sorting,
|
||||
columnFilters,
|
||||
columnVisibility,
|
||||
rowSelection,
|
||||
pagination,
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const pageFromUrl = searchParams?.get("page");
|
||||
if (pageFromUrl) {
|
||||
setPage(Number(pageFromUrl));
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchData();
|
||||
}, [page]);
|
||||
|
||||
async function fetchData() {
|
||||
try {
|
||||
loading();
|
||||
const res = await getMediaBlastCampaignPage(page - 1);
|
||||
const data = res?.data?.data;
|
||||
const contentData = data?.content;
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * 10 + index + 1;
|
||||
});
|
||||
|
||||
console.log("contentData : ", data);
|
||||
|
||||
setDataTable(contentData);
|
||||
setTotalData(data?.totalElements);
|
||||
setTotalPage(data?.totalPages);
|
||||
close();
|
||||
} catch (error) {
|
||||
console.error("Error fetching tasks:", error);
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearch(e.target.value); // Perbarui state search
|
||||
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
||||
<div className="flex-1 text-xl font-medium text-default-900">Survey</div>
|
||||
<div className="flex flex-row gap-2 items-center justify-between">
|
||||
<div className="w-full md:w-[200px] lg:w-[300px] px-2">
|
||||
<InputGroup merged>
|
||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||
<Search className=" h-4 w-4 dark:text-white" />
|
||||
</InputGroupText>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search Judul..."
|
||||
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||
value={search}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<div className="mx-2 my-1">
|
||||
<Input
|
||||
type="date"
|
||||
value={startDate}
|
||||
onChange={(e) => setStartDate(e.target.value)}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="mx-3">
|
||||
<Select>
|
||||
<SelectTrigger className="w-[150px]">
|
||||
<SelectValue placeholder="Select a filter" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>Filter</SelectLabel>
|
||||
<SelectItem value="polri">Polri</SelectItem>
|
||||
<SelectItem value="umum">Umum</SelectItem>
|
||||
<SelectItem value="jurnalist">Journalist</SelectItem>
|
||||
<SelectItem value="ksp">Ksp</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-h-screen p-3">
|
||||
<h2 className="text-center font-semibold mb-4">
|
||||
Survei Kepuasan Pengguna MediaHub Polri
|
||||
</h2>
|
||||
<ResponsiveContainer width="100%" height={500}>
|
||||
<BarChart
|
||||
layout="vertical"
|
||||
data={data}
|
||||
margin={{ top: 20, right: 30, left: 30, bottom: 80 }}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="5 5" />
|
||||
<XAxis type="number">
|
||||
<Label
|
||||
value="Total Survei"
|
||||
offset={-30}
|
||||
position="insideBottom"
|
||||
style={{ textAnchor: "middle", fontStyle: "italic" }}
|
||||
/>
|
||||
</XAxis>
|
||||
<YAxis
|
||||
type="category"
|
||||
dataKey="question"
|
||||
width={240} // Lebarkan agar teks muat
|
||||
tick={{
|
||||
fontSize: 12,
|
||||
}}
|
||||
tickFormatter={(value: string) =>
|
||||
value.length > 40 ? value.slice(0, 37) + "..." : value
|
||||
} // Atur potong teks panjang, atau hapus kalau mau tampil semua
|
||||
>
|
||||
<Label
|
||||
value="Pertanyaan"
|
||||
angle={-90}
|
||||
position="insideLeft"
|
||||
style={{ textAnchor: "middle", fontStyle: "italic" }}
|
||||
dx={-20}
|
||||
/>
|
||||
</YAxis>
|
||||
<Tooltip />
|
||||
<Bar dataKey="total" fill="#3163d4" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
||||
<Table className="overflow-hidden">
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id} className="bg-default-200">
|
||||
{headerGroup.headers.map((header) => (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
className="h-[75px]"
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination
|
||||
table={table}
|
||||
totalData={totalData}
|
||||
totalPage={totalPage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SurveyListTable;
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
"use client";
|
||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import { useState } from "react";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import SurveyListTable from "./component/table";
|
||||
|
||||
export default function AdminSurvey() {
|
||||
const [tab, setTab] = useState("Email Blast");
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
||||
<SurveyListTable />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -581,7 +581,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
{roleId == 3 || roleId == 11 || roleId == 2 || roleId == 12 ? (
|
||||
<Button
|
||||
onClick={handleDateClick}
|
||||
className="dark:bg-background dark:text-foreground w-[250px]"
|
||||
className="dark:bg-background dark:text-foreground w-full"
|
||||
>
|
||||
<Plus className="w-4 h-4 me-1" />
|
||||
{t("addEvent")}
|
||||
|
|
@ -593,8 +593,8 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{roleId === 3 && userLevelId === 216 ? (
|
||||
<Button className="dark:bg-background dark:text-foreground w-[250px]">
|
||||
<Book className="w-4 h-4" />
|
||||
<Button className="dark:bg-background dark:text-foreground w-full">
|
||||
<Book size={15} className="w-4 h-4 mr-3" />
|
||||
{t("bag-pa-monitoring-results")}
|
||||
</Button>
|
||||
) : null}
|
||||
|
|
|
|||
|
|
@ -775,7 +775,7 @@ const EventModal = ({
|
|||
checked={wilayahPublish.semua}
|
||||
onCheckedChange={() => toggleWilayah("semua")}
|
||||
/>
|
||||
<label htmlFor="semua" className="ml-2">
|
||||
<label htmlFor="semua" className="ml-2 text-sm">
|
||||
Semua
|
||||
</label>
|
||||
</div>
|
||||
|
|
@ -785,7 +785,7 @@ const EventModal = ({
|
|||
checked={wilayahPublish.nasional}
|
||||
onCheckedChange={() => toggleWilayah("nasional")}
|
||||
/>
|
||||
<label htmlFor="nasional" className="ml-2">
|
||||
<label htmlFor="nasional" className="ml-2 text-sm mr-2">
|
||||
Nasional
|
||||
</label>
|
||||
</div>
|
||||
|
|
@ -795,7 +795,7 @@ const EventModal = ({
|
|||
checked={wilayahPublish.polda}
|
||||
onCheckedChange={() => toggleWilayah("polda")}
|
||||
/>
|
||||
<label htmlFor="polda" className="mx-2">
|
||||
<label htmlFor="polda" className="mx-2 text-sm mr-2">
|
||||
Polda
|
||||
</label>
|
||||
{wilayahPublish.polda && (
|
||||
|
|
@ -814,7 +814,7 @@ const EventModal = ({
|
|||
checked={wilayahPublish.polres}
|
||||
onCheckedChange={() => toggleWilayah("polres")}
|
||||
/>
|
||||
<label htmlFor="polres" className="ml-2">
|
||||
<label htmlFor="polres" className="ml-2 text-sm mr-2">
|
||||
Polres
|
||||
</label>
|
||||
{wilayahPublish.polres && (
|
||||
|
|
@ -833,7 +833,7 @@ const EventModal = ({
|
|||
checked={wilayahPublish.satker}
|
||||
onCheckedChange={() => toggleWilayah("satker")}
|
||||
/>
|
||||
<label htmlFor="satker" className="mx-2">
|
||||
<label htmlFor="satker" className="mx-2 text-sm mr-2">
|
||||
Satker
|
||||
</label>
|
||||
{wilayahPublish.satker && (
|
||||
|
|
@ -852,7 +852,10 @@ const EventModal = ({
|
|||
checked={wilayahPublish.international}
|
||||
onCheckedChange={() => toggleWilayah("international")}
|
||||
/>
|
||||
<label htmlFor="international" className="ml-2">
|
||||
<label
|
||||
htmlFor="international"
|
||||
className="ml-2 text-sm mr-2"
|
||||
>
|
||||
Internasional
|
||||
</label>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ import {
|
|||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
|
@ -60,9 +62,10 @@ const BlogTable = () => {
|
|||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
const [showData, setShowData] = React.useState("50");
|
||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
pageSize: Number(showData),
|
||||
});
|
||||
const [page, setPage] = React.useState(1);
|
||||
const [totalPage, setTotalPage] = React.useState(1);
|
||||
|
|
@ -106,12 +109,12 @@ const BlogTable = () => {
|
|||
React.useEffect(() => {
|
||||
fetchData();
|
||||
getCategories();
|
||||
}, [categoryFilter, statusFilter, page, limit, search]);
|
||||
}, [categoryFilter, statusFilter, page, showData, search]);
|
||||
|
||||
async function fetchData() {
|
||||
try {
|
||||
const res = await paginationBlog(
|
||||
limit,
|
||||
showData,
|
||||
page - 1,
|
||||
search,
|
||||
categoryFilter,
|
||||
|
|
@ -120,7 +123,7 @@ const BlogTable = () => {
|
|||
const data = res?.data?.data;
|
||||
const contentData = data?.content;
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * limit + index + 1;
|
||||
item.no = (page - 1) * Number(showData) + index + 1;
|
||||
});
|
||||
|
||||
console.log("contentData : ", contentData);
|
||||
|
|
@ -183,7 +186,7 @@ const BlogTable = () => {
|
|||
<div className="flex-none">
|
||||
<Link href={"/contributor/blog/create"}>
|
||||
<Button fullWidth color="primary">
|
||||
<Plus className="w-6 h-6 me-1.5" />
|
||||
<Plus size={18} className=" me-1.5" />
|
||||
{t("create-indeks")}
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
@ -210,6 +213,34 @@ const BlogTable = () => {
|
|||
</div>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<div className="flex items-center py-4">
|
||||
<div className="mx-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="50">
|
||||
1 - 50 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="100">
|
||||
1 - 100 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="250">
|
||||
1 - 250 Data
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="ml-auto" size="md">
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ import {
|
|||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
|
@ -76,9 +78,10 @@ const TableAudio = () => {
|
|||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
const [showData, setShowData] = React.useState("50");
|
||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
pageSize: Number(showData),
|
||||
});
|
||||
const [page, setPage] = React.useState(1);
|
||||
const [totalPage, setTotalPage] = React.useState(1);
|
||||
|
|
@ -132,7 +135,15 @@ const TableAudio = () => {
|
|||
React.useEffect(() => {
|
||||
fetchData();
|
||||
getCategories();
|
||||
}, [categoryFilter, statusFilter, page, limit, search, startDate, endDate]);
|
||||
}, [
|
||||
categoryFilter,
|
||||
statusFilter,
|
||||
page,
|
||||
showData,
|
||||
search,
|
||||
startDate,
|
||||
endDate,
|
||||
]);
|
||||
|
||||
async function getCategories() {
|
||||
const category = await listEnableCategory("4");
|
||||
|
|
@ -171,7 +182,7 @@ const TableAudio = () => {
|
|||
try {
|
||||
const isForSelf = Number(roleId) === 4;
|
||||
const res = await listDataAudio(
|
||||
limit,
|
||||
showData,
|
||||
page - 1,
|
||||
isForSelf,
|
||||
!isForSelf,
|
||||
|
|
@ -189,7 +200,7 @@ const TableAudio = () => {
|
|||
const data = res?.data?.data;
|
||||
const contentData = data?.content;
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * limit + index + 1;
|
||||
item.no = (page - 1) * Number(showData) + index + 1;
|
||||
});
|
||||
|
||||
setDataTable(contentData);
|
||||
|
|
@ -248,6 +259,34 @@ const TableAudio = () => {
|
|||
</div>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<div className="flex items-center py-4">
|
||||
<div className="mx-3">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button size="md" variant="outline">
|
||||
{showData} Data
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56 text-sm">
|
||||
<DropdownMenuRadioGroup
|
||||
value={showData}
|
||||
onValueChange={setShowData}
|
||||
>
|
||||
<DropdownMenuRadioItem value="10">
|
||||
10 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="50">
|
||||
50 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="100">
|
||||
100 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="250">
|
||||
250 Data
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="ml-auto" size="md">
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ const ReactTableAudioPage = () => {
|
|||
<div className="flex-none">
|
||||
<Link href={"/contributor/content/audio/create"}>
|
||||
<Button color="primary" className="text-white">
|
||||
<UploadIcon />
|
||||
<UploadIcon size={18} className="mr-2" />
|
||||
{t("create-audio")}
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ import {
|
|||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
|
@ -81,13 +83,13 @@ const TableImage = () => {
|
|||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
const [showData, setShowData] = React.useState("10");
|
||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
pageSize: Number(showData),
|
||||
});
|
||||
const [page, setPage] = React.useState(1);
|
||||
const [totalPage, setTotalPage] = React.useState(1);
|
||||
const [limit, setLimit] = React.useState(10);
|
||||
const [search, setSearch] = React.useState("");
|
||||
const userId = getCookiesDecrypt("uie");
|
||||
const userLevelId = getCookiesDecrypt("ulie");
|
||||
|
|
@ -96,6 +98,7 @@ const TableImage = () => {
|
|||
const [selectedCategories, setSelectedCategories] = React.useState<number[]>(
|
||||
[]
|
||||
);
|
||||
|
||||
const [categoryFilter, setCategoryFilter] = React.useState<string>("");
|
||||
const [statusFilter, setStatusFilter] = React.useState<any[]>([]);
|
||||
const [startDate, setStartDate] = React.useState("");
|
||||
|
|
@ -135,10 +138,17 @@ const TableImage = () => {
|
|||
}, [searchParams]);
|
||||
|
||||
React.useEffect(() => {
|
||||
// Panggil fetchData saat filter kategori berubah
|
||||
fetchData();
|
||||
getCategories();
|
||||
}, [categoryFilter, statusFilter, page, limit, search, startDate, endDate]);
|
||||
}, [
|
||||
categoryFilter,
|
||||
statusFilter,
|
||||
page,
|
||||
showData,
|
||||
search,
|
||||
startDate,
|
||||
endDate,
|
||||
]);
|
||||
|
||||
async function getCategories() {
|
||||
const category = await listEnableCategory("1");
|
||||
|
|
@ -177,7 +187,7 @@ const TableImage = () => {
|
|||
try {
|
||||
const isForSelf = Number(roleId) === 4;
|
||||
const res = await listDataImage(
|
||||
limit,
|
||||
showData,
|
||||
page - 1,
|
||||
isForSelf,
|
||||
!isForSelf,
|
||||
|
|
@ -195,7 +205,7 @@ const TableImage = () => {
|
|||
const data = res?.data?.data;
|
||||
const contentData = data?.content;
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * limit + index + 1;
|
||||
item.no = (page - 1) * Number(showData) + index + 1;
|
||||
});
|
||||
|
||||
setDataTable(contentData);
|
||||
|
|
@ -254,6 +264,34 @@ const TableImage = () => {
|
|||
</div>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<div className="flex items-center py-4">
|
||||
<div className="mx-3">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button size="md" variant="outline">
|
||||
{showData} Data
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56 text-sm">
|
||||
<DropdownMenuRadioGroup
|
||||
value={showData}
|
||||
onValueChange={setShowData}
|
||||
>
|
||||
<DropdownMenuRadioItem value="10">
|
||||
10 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="50">
|
||||
50 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="100">
|
||||
100 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="250">
|
||||
250 Data
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="ml-auto" size="md">
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ const ReactTableImagePage = () => {
|
|||
<div className="flex-none">
|
||||
<Link href={"/contributor/content/image/create"}>
|
||||
<Button color="primary" className="text-white">
|
||||
<UploadIcon />
|
||||
<UploadIcon size={18} className="mr-2" />
|
||||
{t("create-image")}
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ import {
|
|||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
|
@ -75,9 +77,10 @@ const TableTeks = () => {
|
|||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
const [showData, setShowData] = React.useState("50");
|
||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
pageSize: Number(showData),
|
||||
});
|
||||
const [page, setPage] = React.useState(1);
|
||||
const [totalPage, setTotalPage] = React.useState(1);
|
||||
|
|
@ -131,7 +134,15 @@ const TableTeks = () => {
|
|||
React.useEffect(() => {
|
||||
fetchData();
|
||||
getCategories();
|
||||
}, [categoryFilter, statusFilter, page, limit, search, startDate, endDate]);
|
||||
}, [
|
||||
categoryFilter,
|
||||
statusFilter,
|
||||
page,
|
||||
showData,
|
||||
search,
|
||||
startDate,
|
||||
endDate,
|
||||
]);
|
||||
|
||||
async function getCategories() {
|
||||
const category = await listEnableCategory("3");
|
||||
|
|
@ -170,7 +181,7 @@ const TableTeks = () => {
|
|||
try {
|
||||
const isForSelf = Number(roleId) === 4;
|
||||
const res = await listDataTeks(
|
||||
limit,
|
||||
showData,
|
||||
page - 1,
|
||||
isForSelf,
|
||||
!isForSelf,
|
||||
|
|
@ -188,7 +199,7 @@ const TableTeks = () => {
|
|||
const data = res?.data?.data;
|
||||
const contentData = data?.content;
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * limit + index + 1;
|
||||
item.no = (page - 1) * Number(showData) + index + 1;
|
||||
});
|
||||
|
||||
setDataTable(contentData);
|
||||
|
|
@ -247,6 +258,34 @@ const TableTeks = () => {
|
|||
</div>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<div className="flex items-center py-4">
|
||||
<div className="mx-3">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button size="md" variant="outline">
|
||||
{showData} Data
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56 text-sm">
|
||||
<DropdownMenuRadioGroup
|
||||
value={showData}
|
||||
onValueChange={setShowData}
|
||||
>
|
||||
<DropdownMenuRadioItem value="10">
|
||||
10 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="50">
|
||||
50 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="100">
|
||||
100 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="250">
|
||||
250 Data
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="ml-auto" size="md">
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ const ReactTableTeksPage = () => {
|
|||
<div className="flex-none">
|
||||
<Link href={"/contributor/content/teks/create"}>
|
||||
<Button color="primary" className="text-white">
|
||||
<UploadIcon />
|
||||
<UploadIcon size={18} className="mr-2" />
|
||||
{t("create-text")}
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ import {
|
|||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
|
@ -75,9 +77,10 @@ const TableVideo = () => {
|
|||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
const [showData, setShowData] = React.useState("50");
|
||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
pageSize: Number(showData),
|
||||
});
|
||||
const [page, setPage] = React.useState(1);
|
||||
const [totalPage, setTotalPage] = React.useState(1);
|
||||
|
|
@ -131,7 +134,16 @@ const TableVideo = () => {
|
|||
React.useEffect(() => {
|
||||
fetchData();
|
||||
getCategories();
|
||||
}, [categoryFilter, statusFilter, page, limit, search, startDate, endDate]);
|
||||
}, [
|
||||
categoryFilter,
|
||||
statusFilter,
|
||||
page,
|
||||
showData,
|
||||
,
|
||||
search,
|
||||
startDate,
|
||||
endDate,
|
||||
]);
|
||||
|
||||
async function getCategories() {
|
||||
const category = await listEnableCategory("2");
|
||||
|
|
@ -170,7 +182,7 @@ const TableVideo = () => {
|
|||
try {
|
||||
const isForSelf = Number(roleId) === 4;
|
||||
const res = await listDataVideo(
|
||||
limit,
|
||||
showData,
|
||||
page - 1,
|
||||
isForSelf,
|
||||
!isForSelf,
|
||||
|
|
@ -188,7 +200,7 @@ const TableVideo = () => {
|
|||
const data = res?.data?.data;
|
||||
const contentData = data?.content;
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * limit + index + 1;
|
||||
item.no = (page - 1) * Number(showData) + index + 1;
|
||||
});
|
||||
|
||||
setDataTable(contentData);
|
||||
|
|
@ -248,6 +260,34 @@ const TableVideo = () => {
|
|||
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<div className="flex items-center py-4">
|
||||
<div className="mx-3">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button size="md" variant="outline">
|
||||
{showData} Data
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56 text-sm">
|
||||
<DropdownMenuRadioGroup
|
||||
value={showData}
|
||||
onValueChange={setShowData}
|
||||
>
|
||||
<DropdownMenuRadioItem value="10">
|
||||
10 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="50">
|
||||
50 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="100">
|
||||
100 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="250">
|
||||
250 Data
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="ml-auto" size="md">
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ const ReactTableVideoPage = () => {
|
|||
<div className="flex-none">
|
||||
<Link href={"/contributor/content/video/create"}>
|
||||
<Button color="primary" className="text-white">
|
||||
<UploadIcon />
|
||||
<UploadIcon size={18} className="mr-2" />
|
||||
{t("create-video")}
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,192 @@
|
|||
import * as React from "react";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
|
||||
import {
|
||||
Eye,
|
||||
MoreVertical,
|
||||
SquarePen,
|
||||
Trash2,
|
||||
Upload,
|
||||
UploadCloud,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuItem,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { format } from "date-fns";
|
||||
import { Link, useRouter } from "@/components/navigation";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { deleteBlog } from "@/service/blog/blog";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
import { useTranslations } from "next-intl";
|
||||
import axios from "axios";
|
||||
|
||||
const useTableColumns = ({
|
||||
handlePreview,
|
||||
}: {
|
||||
handlePreview: (id: string) => void;
|
||||
}) => {
|
||||
const t = useTranslations("Table"); // Panggil di dalam hook
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
accessorKey: "no",
|
||||
header: t("no"),
|
||||
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
||||
},
|
||||
{
|
||||
accessorKey: "title",
|
||||
header: t("title"),
|
||||
cell: ({ row }) => (
|
||||
<span className="whitespace-normal">{row.getValue("title")}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "createdAt",
|
||||
header: t("upload-date"),
|
||||
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: "version",
|
||||
header: t("version"),
|
||||
cell: ({ row }) => <span className="">{row.getValue("version")}</span>,
|
||||
},
|
||||
|
||||
{
|
||||
id: "actions",
|
||||
accessorKey: "action",
|
||||
header: t("action"),
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const router = useRouter();
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
async function deleteProcess(id: any) {
|
||||
loading();
|
||||
const resDelete = await deleteBlog(id);
|
||||
|
||||
if (resDelete?.error) {
|
||||
error(resDelete.message);
|
||||
return false;
|
||||
}
|
||||
success();
|
||||
}
|
||||
|
||||
function success() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const handleDeleteBlog = (id: any) => {
|
||||
MySwal.fire({
|
||||
title: "Hapus Data",
|
||||
text: "",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Hapus",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
deleteProcess(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleDownload = async (id: string) => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`https://netidhub.com/api/media/report/download?id=${id}`,
|
||||
{
|
||||
responseType: "blob",
|
||||
}
|
||||
);
|
||||
|
||||
const url = window.URL.createObjectURL(new Blob([response.data]));
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.setAttribute("download", `report-${id}.pdf`);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
} catch (error) {
|
||||
console.error("Download failed", error);
|
||||
MySwal.fire({
|
||||
title: "Gagal",
|
||||
text: "Terjadi kesalahan saat mengunduh file.",
|
||||
icon: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
|
||||
>
|
||||
<span className="sr-only">Open menu</span>
|
||||
<MoreVertical className="h-4 w-4 text-default-800" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="p-0" align="end">
|
||||
<DropdownMenuItem
|
||||
onClick={() => handlePreview(row.original.id)}
|
||||
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" />
|
||||
Preview
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDownload(row.original.id)}
|
||||
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" />
|
||||
Download
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDeleteBlog(row.original.id)}
|
||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||
>
|
||||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return columns;
|
||||
};
|
||||
|
||||
export default useTableColumns;
|
||||
|
|
@ -0,0 +1,479 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
PaginationState,
|
||||
SortingState,
|
||||
VisibilityState,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { ChevronDown, Plus, Search } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||
import { getBlogCategory, paginationBlog } from "@/service/blog/blog";
|
||||
import { ticketingPagination } from "@/service/ticketing/ticketing";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import TablePagination from "@/components/table/table-pagination";
|
||||
import columns from "./columns";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import Swal from "sweetalert2";
|
||||
import { listEnableCategory } from "@/service/content/content";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import useTableColumns from "./columns";
|
||||
import {
|
||||
getPreviewById,
|
||||
paginationReport,
|
||||
saveReport,
|
||||
} from "@/service/report/report";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { format } from "date-fns";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
|
||||
type PreviewApiResponse = {
|
||||
error: boolean;
|
||||
message: string;
|
||||
data: {
|
||||
id: number;
|
||||
title: string;
|
||||
filePath: string;
|
||||
version: number;
|
||||
} | null;
|
||||
};
|
||||
|
||||
const ReportTable = () => {
|
||||
const router = useRouter();
|
||||
const MySwal = withReactContent(Swal);
|
||||
const searchParams = useSearchParams();
|
||||
const t = useTranslations("Report");
|
||||
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
||||
const [totalData, setTotalData] = React.useState<number>(1);
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
[]
|
||||
);
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
const [showData, setShowData] = React.useState("50");
|
||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: Number(showData),
|
||||
});
|
||||
const [page, setPage] = React.useState(1);
|
||||
const [totalPage, setTotalPage] = React.useState(1);
|
||||
const [limit, setLimit] = React.useState(10);
|
||||
const [search, setSearch] = React.useState<string>("");
|
||||
const [categories, setCategories] = React.useState<any[]>([]);
|
||||
const [selectedCategories, setSelectedCategories] = React.useState<number[]>(
|
||||
[]
|
||||
);
|
||||
const [categoryFilter, setCategoryFilter] = React.useState<string>("");
|
||||
const [dateFilter, setDateFilter] = React.useState("");
|
||||
const [statusFilter, setStatusFilter] = React.useState<any[]>([]);
|
||||
const [openPreview, setOpenPreview] = React.useState(false);
|
||||
const [previewData, setPreviewData] = React.useState<any>(null);
|
||||
|
||||
const handlePreview = (id: string) => {
|
||||
const url = `https://netidhub.com/api/media/report/view?id=${id}`;
|
||||
setPreviewData({ url });
|
||||
setOpenPreview(true);
|
||||
};
|
||||
|
||||
const columns = useTableColumns({ handlePreview });
|
||||
const table = useReactTable({
|
||||
data: dataTable,
|
||||
columns,
|
||||
onSortingChange: setSorting,
|
||||
onColumnFiltersChange: setColumnFilters,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
onColumnVisibilityChange: setColumnVisibility,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
onPaginationChange: setPagination,
|
||||
state: {
|
||||
sorting,
|
||||
columnFilters,
|
||||
columnVisibility,
|
||||
rowSelection,
|
||||
pagination,
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const pageFromUrl = searchParams?.get("page");
|
||||
if (pageFromUrl) {
|
||||
setPage(Number(pageFromUrl));
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchData();
|
||||
getCategories();
|
||||
}, [categoryFilter, statusFilter, page, showData, search]);
|
||||
|
||||
async function fetchData() {
|
||||
try {
|
||||
const res = await paginationReport(
|
||||
showData,
|
||||
page - 1,
|
||||
search,
|
||||
categoryFilter,
|
||||
statusFilter
|
||||
);
|
||||
const data = res?.data?.data;
|
||||
const contentData = data?.content;
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * Number(showData) + index + 1;
|
||||
});
|
||||
|
||||
console.log("contentData : ", contentData);
|
||||
|
||||
setDataTable(contentData);
|
||||
setTotalData(data?.totalElements);
|
||||
setTotalPage(data?.totalPages);
|
||||
} catch (error) {
|
||||
console.error("Error fetching tasks:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function getCategories() {
|
||||
const category = await getBlogCategory();
|
||||
const resCategory = category?.data?.data?.content;
|
||||
setCategories(resCategory || []);
|
||||
}
|
||||
|
||||
const handleCheckboxChange = (categoryId: number) => {
|
||||
setSelectedCategories((prev: any) =>
|
||||
prev.includes(categoryId)
|
||||
? prev.filter((id: any) => id !== categoryId)
|
||||
: [...prev, categoryId]
|
||||
);
|
||||
|
||||
// Perbarui filter kategori
|
||||
setCategoryFilter((prev) => {
|
||||
const updatedCategories = prev.split(",").filter(Boolean).map(Number);
|
||||
|
||||
const newCategories = updatedCategories.includes(categoryId)
|
||||
? updatedCategories.filter((id) => id !== categoryId)
|
||||
: [...updatedCategories, categoryId];
|
||||
|
||||
return newCategories.join(",");
|
||||
});
|
||||
};
|
||||
|
||||
function handleStatusCheckboxChange(value: any) {
|
||||
setStatusFilter((prev: any) =>
|
||||
prev.includes(value)
|
||||
? prev.filter((status: any) => status !== value)
|
||||
: [...prev, value]
|
||||
);
|
||||
}
|
||||
|
||||
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearch(e.target.value); // Perbarui state search
|
||||
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
|
||||
};
|
||||
|
||||
const handleGenerateReport = async () => {
|
||||
const today = new Date();
|
||||
const formattedDate = format(today, "dd-MM-yyyy"); // Hasil: 22-04-2025
|
||||
const title = `Report ${formattedDate}`;
|
||||
|
||||
const requestData = {
|
||||
title,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await saveReport(requestData);
|
||||
|
||||
if (response?.error) {
|
||||
MySwal.fire(
|
||||
"Error",
|
||||
response?.message || "Gagal menyimpan laporan",
|
||||
"error"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Laporan berhasil dibuat.",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then(() => {
|
||||
fetchData(); // Refresh tabel setelah generate
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Generate report error:", error);
|
||||
MySwal.fire("Error", "Terjadi kesalahan saat membuat laporan", "error");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Dialog open={openPreview} onOpenChange={setOpenPreview}>
|
||||
<DialogContent className="min-w-max h-[500px] p-0 overflow-hidden">
|
||||
<DialogHeader className="p-4 border-b">
|
||||
<DialogTitle>Preview Laporan</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="h-full w-[1000px] overflow-auto">
|
||||
{previewData ? (
|
||||
<iframe
|
||||
src={previewData.url}
|
||||
className="w-full h-[calc(100vh-100px)]"
|
||||
/>
|
||||
) : (
|
||||
<div className="p-4">Loading preview...</div>
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<CardHeader className="border-b border-solid border-default-200 mb-6">
|
||||
<CardTitle>
|
||||
<div className="flex items-center">
|
||||
<div className="flex-1 text-xl font-medium text-default-900">
|
||||
{t("table")} {t("report")}
|
||||
</div>
|
||||
<div className="flex-none">
|
||||
<Button fullWidth color="primary" onClick={handleGenerateReport}>
|
||||
<Plus size={18} className=" me-1.5" />
|
||||
{t("generate-report")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="flex flex-col md:flex-row lg:flex-row md:justify-between lg:justify-between items-center md:px-5 lg:px-5">
|
||||
<div className="w-full md:w-[200px] lg:w-[200px] px-2">
|
||||
<InputGroup merged>
|
||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||
<Search className=" h-4 w-4 dark:text-white" />
|
||||
</InputGroupText>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search Title"
|
||||
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||
value={search}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<div className="flex items-center py-4">
|
||||
<div className="mx-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="50">
|
||||
1 - 50 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="100">
|
||||
1 - 100 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="250">
|
||||
1 - 250 Data
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="ml-auto w-full sm:w-[100px]"
|
||||
size="md"
|
||||
>
|
||||
Filter <ChevronDown />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
className="w-64 h-[200px] 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>
|
||||
{/* <div className="mx-2 my-1">
|
||||
<Label>Code</Label>
|
||||
<Input
|
||||
placeholder="Filter Status..."
|
||||
value={filterByCode}
|
||||
// onChange={handleSearchFilterByCode}
|
||||
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-1"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(1)}
|
||||
onChange={() => handleStatusCheckboxChange(1)}
|
||||
/>
|
||||
<label htmlFor="status-1" className="text-sm">
|
||||
{t("wait-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">
|
||||
{t("acc")}
|
||||
</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>
|
||||
<Table className="overflow-hidden mt-3">
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id} className="bg-default-200">
|
||||
{headerGroup.headers.map((header) => (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
className="h-[75px]"
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext()
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination
|
||||
table={table}
|
||||
totalData={totalData}
|
||||
totalPage={totalPage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReportTable;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
export const metadata = {
|
||||
title: "Blog",
|
||||
};
|
||||
|
||||
const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import BlogTable from "./components/report-table";
|
||||
import { Plus } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Link } from "@/components/navigation";
|
||||
import ReportTable from "./components/report-table";
|
||||
|
||||
const ReportPage = async () => {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
<Card>
|
||||
<CardContent className="p-0">
|
||||
<ReportTable />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReportPage;
|
||||
|
|
@ -22,7 +22,7 @@ import {
|
|||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Search, UploadIcon } from "lucide-react";
|
||||
import { ChevronDown, Search, UploadIcon } from "lucide-react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
|
|
@ -34,6 +34,14 @@ import { CardHeader, CardTitle } from "@/components/ui/card";
|
|||
import { Link } from "@/i18n/routing";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import useTableColumns from "./columns";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
const EventTable = () => {
|
||||
const router = useRouter();
|
||||
|
|
@ -48,14 +56,16 @@ const EventTable = () => {
|
|||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
const [showData, setShowData] = React.useState("50");
|
||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
pageSize: Number(showData),
|
||||
});
|
||||
const [page, setPage] = React.useState(1);
|
||||
const [totalPage, setTotalPage] = React.useState(1);
|
||||
const [limit, setLimit] = React.useState(10);
|
||||
const [search, setSearch] = React.useState<string>("");
|
||||
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
|
||||
const columns = useTableColumns();
|
||||
const table = useReactTable({
|
||||
data: dataTable,
|
||||
|
|
@ -87,15 +97,21 @@ const EventTable = () => {
|
|||
|
||||
React.useEffect(() => {
|
||||
fetchData();
|
||||
}, [page, limit, search]);
|
||||
}, [page, showData, search, statusFilter]);
|
||||
|
||||
async function fetchData() {
|
||||
try {
|
||||
const res = await paginationSchedule(limit, page - 1, 2, search);
|
||||
const res = await paginationSchedule(
|
||||
showData,
|
||||
page - 1,
|
||||
2,
|
||||
search,
|
||||
statusFilter
|
||||
);
|
||||
const data = res?.data?.data;
|
||||
const contentData = data?.content;
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * limit + index + 1;
|
||||
item.no = (page - 1) * Number(showData) + index + 1;
|
||||
});
|
||||
|
||||
console.log("contentData : ", contentData);
|
||||
|
|
@ -108,6 +124,14 @@ const EventTable = () => {
|
|||
}
|
||||
}
|
||||
|
||||
const handleStatusCheckboxChange = (statusId: number) => {
|
||||
const updatedFilter = statusFilter.includes(statusId)
|
||||
? statusFilter.filter((id) => id !== statusId)
|
||||
: [...statusFilter, statusId];
|
||||
|
||||
setStatusFilter(updatedFilter);
|
||||
};
|
||||
|
||||
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearch(e.target.value); // Perbarui state search
|
||||
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
|
||||
|
|
@ -124,7 +148,7 @@ const EventTable = () => {
|
|||
<div className="flex-none">
|
||||
<Link href={"/contributor/schedule/event/create"}>
|
||||
<Button color="primary" className="text-white">
|
||||
<UploadIcon />
|
||||
<UploadIcon size={18} className="mr-2" />
|
||||
{t("create-schedule")}
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
@ -133,8 +157,8 @@ const EventTable = () => {
|
|||
</CardTitle>
|
||||
</CardHeader>
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="flex justify-between items-center px-5 gap-2">
|
||||
<div className="w-[150px] md:w-[250px] lg:w-[250px]">
|
||||
<div className="flex flex-col sm:flex-row md:flex-row lg:flex-row justify-between items-center px-3 gap-y-2">
|
||||
<div className="w-full sm:w-[150px] md:w-[250px] lg:w-[250px]">
|
||||
<InputGroup merged>
|
||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||
<Search className=" h-4 w-4 dark:text-white" />
|
||||
|
|
@ -148,17 +172,78 @@ const EventTable = () => {
|
|||
/>
|
||||
</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">
|
||||
<div className="mx-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="50">
|
||||
1 - 50 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="100">
|
||||
1 - 100 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="250">
|
||||
1 - 250 Data
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<div className="">
|
||||
<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>
|
||||
|
||||
<Label className="ml-2 mt-2">Status</Label>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-1"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(1)}
|
||||
onChange={() => handleStatusCheckboxChange(1)}
|
||||
/>
|
||||
<label htmlFor="status-1" 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>
|
||||
</div>
|
||||
<Table className="overflow-hidden mt-3">
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import {
|
|||
} from "@/components/ui/table";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Eye,
|
||||
|
|
@ -47,6 +48,14 @@ import { CardHeader, CardTitle } from "@/components/ui/card";
|
|||
import { Link } from "@/i18n/routing";
|
||||
import { useTranslations } from "next-intl";
|
||||
import useTableColumns from "./columns";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
const PressConferenceTable = () => {
|
||||
const router = useRouter();
|
||||
|
|
@ -61,14 +70,16 @@ const PressConferenceTable = () => {
|
|||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
const [showData, setShowData] = React.useState("50");
|
||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
pageSize: Number(showData),
|
||||
});
|
||||
const [page, setPage] = React.useState(1);
|
||||
const [totalPage, setTotalPage] = React.useState(1);
|
||||
const [limit, setLimit] = React.useState(10);
|
||||
const [search, setSearch] = React.useState<string>("");
|
||||
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
|
||||
const columns = useTableColumns();
|
||||
const table = useReactTable({
|
||||
data: dataTable,
|
||||
|
|
@ -100,15 +111,21 @@ const PressConferenceTable = () => {
|
|||
|
||||
React.useEffect(() => {
|
||||
fetchData();
|
||||
}, [page, limit, search]);
|
||||
}, [page, showData, search, statusFilter]);
|
||||
|
||||
async function fetchData() {
|
||||
try {
|
||||
const res = await paginationSchedule(limit, page - 1, 1, search);
|
||||
const res = await paginationSchedule(
|
||||
showData,
|
||||
page - 1,
|
||||
1,
|
||||
search,
|
||||
statusFilter
|
||||
);
|
||||
const data = res?.data?.data;
|
||||
const contentData = data?.content;
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * limit + index + 1;
|
||||
item.no = (page - 1) * Number(showData) + index + 1;
|
||||
});
|
||||
|
||||
console.log("contentData : ", contentData);
|
||||
|
|
@ -121,6 +138,14 @@ const PressConferenceTable = () => {
|
|||
}
|
||||
}
|
||||
|
||||
const handleStatusCheckboxChange = (statusId: number) => {
|
||||
const updatedFilter = statusFilter.includes(statusId)
|
||||
? statusFilter.filter((id) => id !== statusId)
|
||||
: [...statusFilter, statusId];
|
||||
|
||||
setStatusFilter(updatedFilter);
|
||||
};
|
||||
|
||||
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearch(e.target.value); // Perbarui state search
|
||||
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
|
||||
|
|
@ -137,7 +162,7 @@ const PressConferenceTable = () => {
|
|||
<div className="flex-none">
|
||||
<Link href={"/contributor/schedule/press-conference/create"}>
|
||||
<Button color="primary" className="text-white">
|
||||
<UploadIcon />
|
||||
<UploadIcon size={18} className="mr-2" />
|
||||
{t("create-schedule")}
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
@ -146,8 +171,8 @@ const PressConferenceTable = () => {
|
|||
</CardTitle>
|
||||
</CardHeader>
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="flex justify-between items-center px-5 gap-2">
|
||||
<div className="w-[150px] md:w-[250px] lg:w-[250px]">
|
||||
<div className="flex flex-col sm:flex-row md:flex-row lg:flex-row justify-between items-center px-3 gap-y-2">
|
||||
<div className="w-full sm:w-[150px] md:w-[250px] lg:w-[250px]">
|
||||
<InputGroup merged>
|
||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||
<Search className=" h-4 w-4 dark:text-white" />
|
||||
|
|
@ -161,17 +186,78 @@ const PressConferenceTable = () => {
|
|||
/>
|
||||
</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 gap-3">
|
||||
<div className="mx-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="50">
|
||||
1 - 50 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="100">
|
||||
1 - 100 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="250">
|
||||
1 - 250 Data
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<div className="">
|
||||
<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>
|
||||
|
||||
<Label className="ml-2 mt-2">Status</Label>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-1"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(1)}
|
||||
onChange={() => handleStatusCheckboxChange(1)}
|
||||
/>
|
||||
<label htmlFor="status-1" 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>
|
||||
</div>
|
||||
<Table className="overflow-hidden mt-3">
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import {
|
|||
} from "@/components/ui/table";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Eye,
|
||||
|
|
@ -48,6 +49,14 @@ import { useTranslations } from "next-intl";
|
|||
import { CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import useTableColumns from "./columns";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
const PressReleaseTable = () => {
|
||||
const router = useRouter();
|
||||
|
|
@ -62,14 +71,16 @@ const PressReleaseTable = () => {
|
|||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
const [showData, setShowData] = React.useState("50");
|
||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
pageSize: Number(showData),
|
||||
});
|
||||
const [page, setPage] = React.useState(1);
|
||||
const [totalPage, setTotalPage] = React.useState(1);
|
||||
const [limit, setLimit] = React.useState(10);
|
||||
const [search, setSearch] = React.useState<string>("");
|
||||
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
|
||||
const columns = useTableColumns();
|
||||
const table = useReactTable({
|
||||
data: dataTable,
|
||||
|
|
@ -101,15 +112,21 @@ const PressReleaseTable = () => {
|
|||
|
||||
React.useEffect(() => {
|
||||
fetchData();
|
||||
}, [page, limit, search]);
|
||||
}, [page, showData, search, statusFilter]);
|
||||
|
||||
async function fetchData() {
|
||||
try {
|
||||
const res = await paginationSchedule(limit, page - 1, 3, search);
|
||||
const res = await paginationSchedule(
|
||||
showData,
|
||||
page - 1,
|
||||
3,
|
||||
search,
|
||||
statusFilter
|
||||
);
|
||||
const data = res?.data?.data;
|
||||
const contentData = data?.content;
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * limit + index + 1;
|
||||
item.no = (page - 1) * Number(showData) + index + 1;
|
||||
});
|
||||
|
||||
console.log("contentData : ", contentData);
|
||||
|
|
@ -122,6 +139,14 @@ const PressReleaseTable = () => {
|
|||
}
|
||||
}
|
||||
|
||||
const handleStatusCheckboxChange = (statusId: number) => {
|
||||
const updatedFilter = statusFilter.includes(statusId)
|
||||
? statusFilter.filter((id) => id !== statusId)
|
||||
: [...statusFilter, statusId];
|
||||
|
||||
setStatusFilter(updatedFilter);
|
||||
};
|
||||
|
||||
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearch(e.target.value); // Perbarui state search
|
||||
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
|
||||
|
|
@ -138,7 +163,7 @@ const PressReleaseTable = () => {
|
|||
<div className="flex-none">
|
||||
<Link href={"/contributor/schedule/press-release/create"}>
|
||||
<Button color="primary" className="text-white">
|
||||
<UploadIcon />
|
||||
<UploadIcon size={18} className="mr-2" />
|
||||
{t("create-schedule")}
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
@ -147,8 +172,8 @@ const PressReleaseTable = () => {
|
|||
</CardTitle>
|
||||
</CardHeader>
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="flex justify-between items-center px-5 gap-2">
|
||||
<div className="w-[150px] md:w-[250px] lg:w-[250px]">
|
||||
<div className="flex flex-col sm:flex-row md:flex-row lg:flex-row justify-between items-center px-3 gap-y-2">
|
||||
<div className="w-full sm:w-[150px] md:w-[250px] lg:w-[250px]">
|
||||
<InputGroup merged>
|
||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||
<Search className=" h-4 w-4 dark:text-white" />
|
||||
|
|
@ -162,17 +187,78 @@ const PressReleaseTable = () => {
|
|||
/>
|
||||
</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="w-full "
|
||||
/>
|
||||
<div className="flex flex-row">
|
||||
<div className="mx-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="50">
|
||||
1 - 50 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="100">
|
||||
1 - 100 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="250">
|
||||
1 - 250 Data
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<div className="">
|
||||
<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>
|
||||
|
||||
<Label className="ml-2 mt-2">Status</Label>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-1"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(1)}
|
||||
onChange={() => handleStatusCheckboxChange(1)}
|
||||
/>
|
||||
<label htmlFor="status-1" 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>
|
||||
</div>
|
||||
<Table className="overflow-hidden mt-3">
|
||||
|
|
@ -228,7 +314,6 @@ const PressReleaseTable = () => {
|
|||
totalPage={totalPage}
|
||||
/>
|
||||
</div>
|
||||
\{" "}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,190 @@
|
|||
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 {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuItem,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { format } from "date-fns";
|
||||
import { Link } from "@/components/navigation";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { deleteCategory } from "@/service/settings/settings";
|
||||
import { deleteTask } from "@/service/task";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import Swal from "sweetalert2";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const useTableColumns = () => {
|
||||
const t = useTranslations("Table"); // Panggil di dalam hook
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
accessorKey: "no",
|
||||
header: t("no"),
|
||||
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
||||
},
|
||||
{
|
||||
accessorKey: "title",
|
||||
header: t("title"),
|
||||
cell: ({ row }) => (
|
||||
<div>
|
||||
<span>{row.getValue("title")}</span>
|
||||
{row.original.isForward && (
|
||||
<Button
|
||||
variant={"outline"}
|
||||
color="primary"
|
||||
size="sm"
|
||||
className="ml-3 rounded-xl"
|
||||
>
|
||||
Forward
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
accessorKey: "createdAt",
|
||||
header: t("upload-date"),
|
||||
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: "status",
|
||||
header: "Status",
|
||||
cell: ({ row }) => {
|
||||
const isActive = row.original.isActive;
|
||||
const isDone = row.original.isDone;
|
||||
|
||||
let statusText = "";
|
||||
if (isDone) {
|
||||
statusText = "Selesai";
|
||||
} else if (isActive) {
|
||||
statusText = "Aktif";
|
||||
} else {
|
||||
statusText = "Nonaktif";
|
||||
}
|
||||
|
||||
const statusColors: Record<string, string> = {
|
||||
Aktif: "bg-primary/20 text-primary",
|
||||
Selesai: "bg-success/20 text-success",
|
||||
Nonaktif: "bg-gray-200 text-gray-500",
|
||||
};
|
||||
|
||||
const statusStyles = statusColors[statusText] || "default";
|
||||
|
||||
return (
|
||||
<Badge className={cn("rounded-full px-5", statusStyles)}>
|
||||
{statusText}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
accessorKey: "action",
|
||||
header: t("action"),
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const router = useRouter();
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
async function deleteProcess(id: any) {
|
||||
loading();
|
||||
const resDelete = await deleteTask(id);
|
||||
|
||||
if (resDelete?.error) {
|
||||
error(resDelete.message);
|
||||
return false;
|
||||
}
|
||||
success();
|
||||
}
|
||||
|
||||
function success() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const TaskDelete = (id: any) => {
|
||||
MySwal.fire({
|
||||
title: "Hapus Data",
|
||||
text: "",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Hapus",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
deleteProcess(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
|
||||
>
|
||||
<span className="sr-only">Open menu</span>
|
||||
<MoreVertical className="h-4 w-4 text-default-800" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="p-0" align="end">
|
||||
<Link href={`/contributor/task-ta/detail/${row.original.id}`}>
|
||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||
<Eye className="w-4 h-4 me-1.5" />
|
||||
View
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<Link href={`/contributor/task-ta/update/${row.original.id}`}>
|
||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||
<SquarePen className="w-4 h-4 me-1.5" />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<DropdownMenuItem
|
||||
onClick={() => TaskDelete(row.original.id)}
|
||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||
>
|
||||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return columns;
|
||||
};
|
||||
|
||||
export default useTableColumns;
|
||||
|
|
@ -0,0 +1,405 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
PaginationState,
|
||||
SortingState,
|
||||
VisibilityState,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Eye,
|
||||
MoreVertical,
|
||||
Search,
|
||||
SquarePen,
|
||||
Trash2,
|
||||
TrendingDown,
|
||||
TrendingUp,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||
import { paginationBlog } from "@/service/blog/blog";
|
||||
import { ticketingPagination } from "@/service/ticketing/ticketing";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import TablePagination from "@/components/table/table-pagination";
|
||||
import columns from "./columns";
|
||||
import { listTask, listTaskTa } from "@/service/task";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { format } from "date-fns";
|
||||
import { useTranslations } from "next-intl";
|
||||
import useTableColumns from "./columns";
|
||||
|
||||
const TaskTaTable = () => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const t = useTranslations("AnalyticsDashboard");
|
||||
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
||||
const [totalData, setTotalData] = React.useState<number>(1);
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
[]
|
||||
);
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
const [showData, setShowData] = React.useState("50");
|
||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: Number(showData),
|
||||
});
|
||||
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
|
||||
const [dateFilter, setDateFilter] = React.useState("");
|
||||
const [endDate, setEndDate] = React.useState("");
|
||||
const [filterByCode, setFilterByCode] = React.useState<string>("");
|
||||
const [page, setPage] = React.useState(1);
|
||||
const [totalPage, setTotalPage] = React.useState(1);
|
||||
const [limit, setLimit] = React.useState(10);
|
||||
const [isSpecificAttention, setIsSpecificAttention] = React.useState(true);
|
||||
const [search, setSearch] = React.useState<string>("");
|
||||
const columns = useTableColumns();
|
||||
const table = useReactTable({
|
||||
data: dataTable,
|
||||
columns,
|
||||
onSortingChange: setSorting,
|
||||
onColumnFiltersChange: setColumnFilters,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
onColumnVisibilityChange: setColumnVisibility,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
onPaginationChange: setPagination,
|
||||
state: {
|
||||
sorting,
|
||||
columnFilters,
|
||||
columnVisibility,
|
||||
rowSelection,
|
||||
pagination,
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const pageFromUrl = searchParams?.get("page");
|
||||
if (pageFromUrl) {
|
||||
setPage(Number(pageFromUrl));
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchData();
|
||||
}, [
|
||||
page,
|
||||
showData,
|
||||
isSpecificAttention,
|
||||
search,
|
||||
dateFilter,
|
||||
filterByCode,
|
||||
statusFilter,
|
||||
]);
|
||||
|
||||
async function fetchData() {
|
||||
const formattedStartDate = dateFilter
|
||||
? format(new Date(dateFilter), "yyyy-MM-dd")
|
||||
: "";
|
||||
try {
|
||||
const res = await listTaskTa(
|
||||
page - 1,
|
||||
search,
|
||||
showData,
|
||||
filterByCode,
|
||||
formattedStartDate,
|
||||
isSpecificAttention ? "atensi-khusus" : "tugas-harian",
|
||||
statusFilter
|
||||
);
|
||||
|
||||
const data = res?.data?.data;
|
||||
const contentData = data?.content;
|
||||
|
||||
// let contentDataFilter = res?.data?.data?.content || [];
|
||||
|
||||
// Filter berdasarkan status
|
||||
// contentDataFilter = contentDataFilter.filter((item: any) => {
|
||||
// const isSelesai = statusFilter.includes(1) ? item.isDone : true;
|
||||
// const isAktif = statusFilter.includes(2) ? item.isActive : true;
|
||||
// return isSelesai && isAktif;
|
||||
// });
|
||||
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * Number(showData) + index + 1;
|
||||
});
|
||||
|
||||
console.log("contentData : ", contentData);
|
||||
|
||||
setDataTable(contentData);
|
||||
setTotalData(data?.totalElements);
|
||||
setTotalPage(data?.totalPages);
|
||||
} catch (error) {
|
||||
console.error("Error fetching tasks:", error);
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFilterByCode(e.target.value);
|
||||
setSearch(e.target.value);
|
||||
table.getColumn("judul")?.setFilterValue(e.target.value);
|
||||
};
|
||||
|
||||
function handleStatusCheckboxChange(value: number) {
|
||||
setStatusFilter((prev) =>
|
||||
prev.includes(value)
|
||||
? prev.filter((status) => status !== value)
|
||||
: [...prev, value]
|
||||
);
|
||||
}
|
||||
|
||||
// const handleSearchFilterByCode = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
// const value = e.target.value;
|
||||
// console.log("code :", value);
|
||||
// setFilterByCode(value);
|
||||
// fetchData();
|
||||
// };
|
||||
|
||||
return (
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="mx-5 mb-3">
|
||||
<div className="">
|
||||
<div className="row">
|
||||
<div className="flex justify-between mb-6">
|
||||
<label className="inline-flex text-md cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
onChange={() => setIsSpecificAttention(!isSpecificAttention)}
|
||||
hidden
|
||||
/>
|
||||
<span
|
||||
className={` ${
|
||||
isSpecificAttention
|
||||
? "bg-default-900 text-white"
|
||||
: "dark:text-default-700 border-2"
|
||||
}
|
||||
px-[18px] py-1 transition duration-100 rounded`}
|
||||
>
|
||||
{t("special-attention")}
|
||||
</span>
|
||||
<span
|
||||
className={`
|
||||
${
|
||||
!isSpecificAttention
|
||||
? "bg-default-900 text-white"
|
||||
: " dark:text-default-700 border-2"
|
||||
}
|
||||
px-[18px] py-1 transition duration-100 rounded
|
||||
`}
|
||||
>
|
||||
{t("daily-tasks")}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row lg:flex-row justify-between sm:items-center md:items-center lg:items-center px-5">
|
||||
<div className="mb-3 sm:mb-0 lg-mb-0">
|
||||
<InputGroup merged>
|
||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||
<Search className=" h-4 w-4 dark:text-white" />
|
||||
</InputGroupText>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search Title dan Code"
|
||||
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white w-full"
|
||||
value={search}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
|
||||
<div className=" flex flex-row items-center gap-3">
|
||||
<div className="flex items-center py-4">
|
||||
<div className="mx-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="50">
|
||||
1 - 50 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="100">
|
||||
1 - 100 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="250">
|
||||
1 - 250 Data
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="ml-auto w-full sm:w-[100px]"
|
||||
size="md"
|
||||
>
|
||||
Filter <ChevronDown />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
className="w-64 h-[200px] 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>
|
||||
{/* <div className="mx-2 my-1">
|
||||
<Label>Code</Label>
|
||||
<Input
|
||||
placeholder="Filter Status..."
|
||||
value={filterByCode}
|
||||
// onChange={handleSearchFilterByCode}
|
||||
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-1"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(1)}
|
||||
onChange={() => handleStatusCheckboxChange(1)}
|
||||
/>
|
||||
<label htmlFor="status-1" className="text-sm">
|
||||
{t("done")}
|
||||
</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">
|
||||
{t("active")}
|
||||
</label>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
{/* <div className="flex-none">
|
||||
<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>
|
||||
<Table className="overflow-hidden mt-3">
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id} className="bg-default-200">
|
||||
{headerGroup.headers.map((header) => (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
className="h-[75px]"
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination
|
||||
table={table}
|
||||
totalData={totalData}
|
||||
totalPage={totalPage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskTaTable;
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import FormTaskTa from "@/components/form/task-ta/task-ta-form";
|
||||
|
||||
const TaskTaCreatePage = () => {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
<FormTaskTa />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskTaCreatePage;
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import FormTask from "@/components/form/task/task-form";
|
||||
import FormTaskDetail from "@/components/form/task/task-detail-form";
|
||||
import FormTaskTaDetail from "@/components/form/task-ta/task-ta-detail-form";
|
||||
|
||||
const TaskTaDetailPage = async () => {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
<FormTaskTaDetail />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskTaDetailPage;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
export const metadata = {
|
||||
title: "Task",
|
||||
};
|
||||
|
||||
const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
"use client";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import TaskTable from "./components/task-ta-table";
|
||||
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 React, { useEffect } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import TaskTaTable from "./components/task-ta-table";
|
||||
|
||||
const TaskTaPage = () => {
|
||||
const t = useTranslations("AnalyticsDashboard");
|
||||
useEffect(() => {
|
||||
function initState() {
|
||||
checkAuthorization("admin"); // Specify the page, e.g., "admin" or another value
|
||||
checkLoginSession();
|
||||
}
|
||||
|
||||
initState();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader className="border-b border-solid border-default-200 mb-6">
|
||||
<CardTitle>
|
||||
<div className="flex flex-col sm:flex-row lg:flex-row lg:items-center">
|
||||
<div className="flex-1 text-xl font-medium text-default-900">
|
||||
{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>
|
||||
</div>
|
||||
</div>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<TaskTaTable />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskTaPage;
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import FormTask from "@/components/form/task/task-form";
|
||||
import FormTaskDetail from "@/components/form/task/task-detail-form";
|
||||
import FormTaskEdit from "@/components/form/task/task-edit-form";
|
||||
import FormTaskTaEdit from "@/components/form/task-ta/task-ta-edit-form";
|
||||
|
||||
const TaskTaDetailPage = async () => {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
<FormTaskTaEdit />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskTaDetailPage;
|
||||
|
|
@ -42,6 +42,8 @@ import {
|
|||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
|
@ -71,9 +73,10 @@ const TaskTable = () => {
|
|||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
const [showData, setShowData] = React.useState("50");
|
||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
pageSize: Number(showData),
|
||||
});
|
||||
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
|
||||
const [dateFilter, setDateFilter] = React.useState("");
|
||||
|
|
@ -117,7 +120,7 @@ const TaskTable = () => {
|
|||
fetchData();
|
||||
}, [
|
||||
page,
|
||||
limit,
|
||||
showData,
|
||||
isSpecificAttention,
|
||||
search,
|
||||
dateFilter,
|
||||
|
|
@ -133,7 +136,7 @@ const TaskTable = () => {
|
|||
const res = await listTask(
|
||||
page - 1,
|
||||
search,
|
||||
limit,
|
||||
showData,
|
||||
filterByCode,
|
||||
formattedStartDate,
|
||||
isSpecificAttention ? "atensi-khusus" : "tugas-harian",
|
||||
|
|
@ -153,7 +156,7 @@ const TaskTable = () => {
|
|||
// });
|
||||
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * limit + index + 1;
|
||||
item.no = (page - 1) * Number(showData) + index + 1;
|
||||
});
|
||||
|
||||
console.log("contentData : ", contentData);
|
||||
|
|
@ -241,36 +244,64 @@ const TaskTable = () => {
|
|||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
<div className="w-full sm:w-[100px] items-center gap-2">
|
||||
<div className=" gap-3">
|
||||
<div className="flex items-center py-4">
|
||||
|
||||
<div className=" flex flex-row items-center gap-3">
|
||||
<div className="flex items-center py-4">
|
||||
<div className="mx-3">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="ml-auto w-full sm:w-[100px]"
|
||||
size="md"
|
||||
>
|
||||
Filter <ChevronDown />
|
||||
<Button size="md" variant="outline">
|
||||
1 - {showData} Data
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
className="w-64 h-[200px] overflow-y-auto"
|
||||
<DropdownMenuContent className="w-56 text-sm">
|
||||
<DropdownMenuRadioGroup
|
||||
value={showData}
|
||||
onValueChange={setShowData}
|
||||
>
|
||||
<DropdownMenuRadioItem value="10">
|
||||
1 - 10 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="50">
|
||||
1 - 50 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="100">
|
||||
1 - 100 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="250">
|
||||
1 - 250 Data
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="ml-auto w-full sm:w-[100px]"
|
||||
size="md"
|
||||
>
|
||||
<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>
|
||||
{/* <div className="mx-2 my-1">
|
||||
Filter <ChevronDown />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
className="w-64 h-[200px] 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>
|
||||
{/* <div className="mx-2 my-1">
|
||||
<Label>Code</Label>
|
||||
<Input
|
||||
placeholder="Filter Status..."
|
||||
|
|
@ -279,36 +310,36 @@ const TaskTable = () => {
|
|||
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-1"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(1)}
|
||||
onChange={() => handleStatusCheckboxChange(1)}
|
||||
/>
|
||||
<label htmlFor="status-1" className="text-sm">
|
||||
{t("done")}
|
||||
</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">
|
||||
{t("active")}
|
||||
</label>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<Label className="ml-2 mt-2">Status</Label>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-1"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(1)}
|
||||
onChange={() => handleStatusCheckboxChange(1)}
|
||||
/>
|
||||
<label htmlFor="status-1" className="text-sm">
|
||||
{t("done")}
|
||||
</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">
|
||||
{t("active")}
|
||||
</label>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
{/* <div className="flex-none">
|
||||
</div>
|
||||
{/* <div className="flex-none">
|
||||
<Input
|
||||
placeholder="Filter Status..."
|
||||
value={
|
||||
|
|
@ -320,7 +351,6 @@ const TaskTable = () => {
|
|||
className="max-w-sm "
|
||||
/>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
<Table className="overflow-hidden mt-3">
|
||||
<TableHeader>
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ const TaskPage = () => {
|
|||
<div className="flex-none">
|
||||
<Link href={"/contributor/task/create"}>
|
||||
<Button color="primary" className="text-white">
|
||||
<UploadIcon />
|
||||
<UploadIcon size={18} className="mr-2" />
|
||||
{t("create-task")}
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -168,8 +168,8 @@ export default function ExecutiveDashboard() {
|
|||
const [ticket6, setTicket6] = useState("");
|
||||
const [isInternational, setIsInternational] = useState([false, false, false]);
|
||||
|
||||
const baseUrl = "https://db-mediahub.polri.go.id/";
|
||||
const url = "https://db-mediahub.polri.go.id/trusted/";
|
||||
const baseUrl = "https://analytic.sitani.info/";
|
||||
const url = "https://analytic.sitani.info/trusted/";
|
||||
|
||||
const view1 =
|
||||
levelName == "MABES POLRI"
|
||||
|
|
@ -182,15 +182,15 @@ export default function ExecutiveDashboard() {
|
|||
levelName == "MABES POLRI"
|
||||
? isInternational[1]
|
||||
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-published-produksi?"
|
||||
: "views/2023_04_MediaHUB-Viz-POLDA_Rev202/db-published-produksi-executive?"
|
||||
: `views/2023_04_MediaHUB-Viz-POLDA_Rev202/db-konten-publisher-polda-executive?provinsi-polda=${poldaState}&`;
|
||||
: "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}&`;
|
||||
|
||||
const view3 =
|
||||
levelName == "MABES POLRI"
|
||||
? isInternational[2]
|
||||
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-waktu-akses-pengguna?"
|
||||
: "views/2023_04_MediaHUB-Viz-POLDA_Rev202/db-waktu-akses-pengguna-executive?"
|
||||
: `views/2023_04_MediaHUB-Viz-POLDA_Rev202/db-waktu-akses-pengguna-polda-executive?provinsi-polda=${poldaState}&`;
|
||||
: "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}&`;
|
||||
|
||||
const view4 =
|
||||
levelName == "MABES POLRI"
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import BlogTable from "../contributor/blog/components/blog-table";
|
|||
import ContentTable from "./routine-task/components/content-table";
|
||||
import RecentActivity from "./routine-task/components/recent-activity";
|
||||
import { Link } from "@/components/navigation";
|
||||
import ReportTable from "../contributor/report/components/report-table";
|
||||
|
||||
const DashboardPage = () => {
|
||||
const t = useTranslations("AnalyticsDashboard");
|
||||
|
|
@ -59,6 +60,12 @@ const DashboardPage = () => {
|
|||
>
|
||||
{t("indeks")}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="report"
|
||||
className="data-[state=active]:bg-primary data-[state=active]:text-primary-foreground rounded-md px-6"
|
||||
>
|
||||
{t("report")}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Card>
|
||||
<TabsContent value="routine-task">
|
||||
|
|
@ -107,7 +114,7 @@ const DashboardPage = () => {
|
|||
<Card>
|
||||
<CardHeader className="flex flex-row items-center">
|
||||
<CardTitle className="flex-1">{t("tabel")}</CardTitle>
|
||||
<DashboardDropdown />
|
||||
{/* <DashboardDropdown /> */}
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<ContentTable />
|
||||
|
|
@ -157,7 +164,7 @@ const DashboardPage = () => {
|
|||
<div className="grid grid-cols-12 gap-5">
|
||||
<div className="lg:col-span-12 col-span-12">
|
||||
<Card>
|
||||
<Card className="py-4 px-3">
|
||||
{/* <Card className="py-4 px-3">
|
||||
<div className="flex flex-col md:flex-row md:justify-between md:items-center lg:flex-row lg:justify-between lg:items-center">
|
||||
<div className="flex-1 text-xl font-medium text-default-900 mb-2">
|
||||
Table Indeks
|
||||
|
|
@ -171,7 +178,7 @@ const DashboardPage = () => {
|
|||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Card> */}
|
||||
<CardContent className="p-0 mt-3">
|
||||
<BlogTable />
|
||||
</CardContent>
|
||||
|
|
@ -179,6 +186,17 @@ const DashboardPage = () => {
|
|||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="report">
|
||||
<div className="grid grid-cols-12 gap-5">
|
||||
<div className="lg:col-span-12 col-span-12">
|
||||
<Card>
|
||||
<CardContent className="p-0 mt-3">
|
||||
<ReportTable />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ const CommunicationPage = () => {
|
|||
{tab === "Pertanyaan Internal" && (
|
||||
<Link href="/shared/communication/internal/create">
|
||||
<Button color="primary" size="md">
|
||||
<PlusIcon />
|
||||
<PlusIcon size={18} className="mr-2" />
|
||||
{t("new-question")}
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
@ -32,7 +32,7 @@ const CommunicationPage = () => {
|
|||
{tab === "Kolaborasi" && (
|
||||
<Link href="/shared/communication/collaboration/create">
|
||||
<Button color="primary" size="md">
|
||||
<PlusIcon />
|
||||
<PlusIcon size={18} className="mr-2" />
|
||||
{t("new-collaboration")}
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import {
|
|||
} from "@/components/ui/table";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Eye,
|
||||
|
|
@ -43,6 +44,14 @@ import TablePagination from "@/components/table/table-pagination";
|
|||
import columns from "./columns";
|
||||
import { listContest } from "@/service/contest/contest";
|
||||
import useTableColumns from "./columns";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
const TaskTable = () => {
|
||||
const router = useRouter();
|
||||
|
|
@ -56,14 +65,16 @@ const TaskTable = () => {
|
|||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
const [showData, setShowData] = React.useState("50");
|
||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
pageSize: Number(showData),
|
||||
});
|
||||
const [page, setPage] = React.useState(1);
|
||||
const [totalPage, setTotalPage] = React.useState(1);
|
||||
const [limit, setLimit] = React.useState(10);
|
||||
const [search, setSearch] = React.useState<string>("");
|
||||
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
|
||||
const columns = useTableColumns();
|
||||
const table = useReactTable({
|
||||
data: dataTable,
|
||||
|
|
@ -95,15 +106,30 @@ const TaskTable = () => {
|
|||
|
||||
React.useEffect(() => {
|
||||
fetchData();
|
||||
}, [page, limit, search]);
|
||||
}, [page, showData, search, statusFilter]);
|
||||
|
||||
async function fetchData() {
|
||||
try {
|
||||
const res = await listContest(search, limit, page - 1);
|
||||
const res = await listContest(search, showData, page - 1);
|
||||
const data = res?.data?.data;
|
||||
const contentData = data?.content;
|
||||
let contentData = data?.content;
|
||||
|
||||
if (statusFilter.length > 0) {
|
||||
contentData = contentData.filter((item: any) => {
|
||||
const { isPublishForAll, isPublishForMabes } = item;
|
||||
|
||||
const status = (() => {
|
||||
if (isPublishForAll && isPublishForMabes) return 1; // Publish
|
||||
if (!isPublishForAll && isPublishForMabes) return 3; // Terkirim
|
||||
return 2; // Pending
|
||||
})();
|
||||
|
||||
return statusFilter.includes(status);
|
||||
});
|
||||
}
|
||||
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * limit + index + 1;
|
||||
item.no = (page - 1) * Number(showData) + index + 1;
|
||||
});
|
||||
|
||||
console.log("contentData : ", contentData);
|
||||
|
|
@ -116,6 +142,14 @@ const TaskTable = () => {
|
|||
}
|
||||
}
|
||||
|
||||
const handleStatusCheckboxChange = (status: number) => {
|
||||
setStatusFilter((prev) =>
|
||||
prev.includes(status)
|
||||
? prev.filter((item) => item !== status)
|
||||
: [...prev, status]
|
||||
);
|
||||
};
|
||||
|
||||
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearch(e.target.value);
|
||||
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
|
||||
|
|
@ -123,8 +157,8 @@ const TaskTable = () => {
|
|||
|
||||
return (
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="flex justify-between items-center px-5">
|
||||
<div className="w-[150px] md:w-[250px] lg:w-[250px]">
|
||||
<div className="flex flex-col sm:flex-row md:flex-row lg:flex-row justify-between items-center px-3 gap-y-2">
|
||||
<div className="w-full sm:w-[150px] md:w-[250px] lg:w-[250px]">
|
||||
<InputGroup merged>
|
||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||
<Search className=" h-4 w-4 dark:text-white" />
|
||||
|
|
@ -138,17 +172,90 @@ const TaskTable = () => {
|
|||
/>
|
||||
</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">
|
||||
<div className="mx-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="50">
|
||||
1 - 50 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="100">
|
||||
1 - 100 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="250">
|
||||
1 - 250 Data
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<div className="">
|
||||
<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>
|
||||
|
||||
<Label className="ml-2 mt-2">Status</Label>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-1"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(1)}
|
||||
onChange={() => handleStatusCheckboxChange(1)}
|
||||
/>
|
||||
<label htmlFor="status-1" className="text-sm">
|
||||
Publish
|
||||
</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">
|
||||
Pending
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="status-3"
|
||||
className="mr-2"
|
||||
checked={statusFilter.includes(3)}
|
||||
onChange={() => handleStatusCheckboxChange(3)}
|
||||
/>
|
||||
<label htmlFor="status-3" className="text-sm">
|
||||
Terkirim
|
||||
</label>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Table className="overflow-hidden mt-3">
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ const ContestPage = () => {
|
|||
<div className="flex-none">
|
||||
<Link href={"/shared/contest/create"}>
|
||||
<Button color="primary" className="text-white">
|
||||
<UploadIcon />
|
||||
<UploadIcon size={18} className="mr-2" />
|
||||
{t("create-contest")}
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -113,19 +113,18 @@ const CuratedContentPage = () => {
|
|||
</div>
|
||||
</div>
|
||||
<div className="ml-5 pb-3">
|
||||
<Label>Audio Visual</Label>
|
||||
<div className="px-5 my-5">
|
||||
<VideoSliderPage />
|
||||
</div>
|
||||
<Label>Audio</Label>
|
||||
|
||||
<div className="px-5 my-5">
|
||||
<AudioSliderPage />
|
||||
</div>
|
||||
<Label>Foto</Label>
|
||||
|
||||
<div className="px-5 my-5">
|
||||
<ImageSliderPage />
|
||||
</div>
|
||||
<Label>Teks</Label>
|
||||
|
||||
<div className="px-5 my-5">
|
||||
<TeksSliderPage />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,7 @@
|
|||
|
||||
import * as React from "react";
|
||||
import {
|
||||
ColumnDef,
|
||||
} from "@tanstack/react-table";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
|
||||
import {
|
||||
Eye,
|
||||
MoreVertical,
|
||||
SquarePen,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
DropdownMenu,
|
||||
|
|
@ -27,50 +19,60 @@ const columns: ColumnDef<any>[] = [
|
|||
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
||||
},
|
||||
{
|
||||
accessorKey: "question",
|
||||
header: "Question",
|
||||
cell: ({ row }) => <span>{row.getValue("question")}</span>,
|
||||
accessorKey: "report",
|
||||
header: "Pelapor",
|
||||
cell: ({ row }) => <span>{row.getValue("report")}</span>,
|
||||
},
|
||||
{
|
||||
accessorKey: "answer",
|
||||
header: "Answer",
|
||||
cell: ({ row }) => <span>{row.getValue("answer")}</span>,
|
||||
accessorKey: "reportDetail",
|
||||
header: "Detail Laporan",
|
||||
cell: ({ row }) => <span>{row.getValue("reportDetail")}</span>,
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
accessorKey: "action",
|
||||
header: "Actions",
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
|
||||
>
|
||||
<span className="sr-only">Open menu</span>
|
||||
<MoreVertical className="h-4 w-4 text-default-800" />
|
||||
</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">
|
||||
<Eye className="w-4 h-4 me-1.5" />
|
||||
View
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||
<SquarePen className="w-4 h-4 me-1.5" />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
||||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
},
|
||||
accessorKey: "reportAccount",
|
||||
header: "Terlapor",
|
||||
cell: ({ row }) => <span>{row.getValue("reportAccount")}</span>,
|
||||
},
|
||||
{
|
||||
accessorKey: "status",
|
||||
header: "Status",
|
||||
cell: ({ row }) => <span>{row.getValue("status")}</span>,
|
||||
},
|
||||
// {
|
||||
// id: "actions",
|
||||
// accessorKey: "action",
|
||||
// header: "Actions",
|
||||
// enableHiding: false,
|
||||
// cell: ({ row }) => {
|
||||
// return (
|
||||
// <DropdownMenu>
|
||||
// <DropdownMenuTrigger asChild>
|
||||
// <Button
|
||||
// size="icon"
|
||||
// className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
|
||||
// >
|
||||
// <span className="sr-only">Open menu</span>
|
||||
// <MoreVertical className="h-4 w-4 text-default-800" />
|
||||
// </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">
|
||||
// <Eye className="w-4 h-4 me-1.5" />
|
||||
// View
|
||||
// </DropdownMenuItem>
|
||||
// <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||
// <SquarePen className="w-4 h-4 me-1.5" />
|
||||
// Edit
|
||||
// </DropdownMenuItem>
|
||||
// <DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
||||
// <Trash2 className="w-4 h-4 me-1.5" />
|
||||
// Delete
|
||||
// </DropdownMenuItem>
|
||||
// </DropdownMenuContent>
|
||||
// </DropdownMenu>
|
||||
// );
|
||||
// },
|
||||
// },
|
||||
];
|
||||
|
||||
export default columns;
|
||||
export default columns;
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ import TablePagination from "@/components/table/table-pagination";
|
|||
import { getFaqList } from "@/service/master/faq";
|
||||
import columns from "./column";
|
||||
|
||||
const FaqTable = () => {
|
||||
const ReportTable = () => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
|
|
@ -94,11 +94,11 @@ const FaqTable = () => {
|
|||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const pageFromUrl = searchParams?.get('page');
|
||||
const pageFromUrl = searchParams?.get("page");
|
||||
if (pageFromUrl) {
|
||||
setPage(Number(pageFromUrl));
|
||||
}
|
||||
}, [searchParams]);
|
||||
}, [searchParams]);
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchData();
|
||||
|
|
@ -191,9 +191,14 @@ const FaqTable = () => {
|
|||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination table={table} totalData={totalData} totalPage={totalPage} visiblePageCount={5} />
|
||||
<TablePagination
|
||||
table={table}
|
||||
totalData={totalData}
|
||||
totalPage={totalPage}
|
||||
visiblePageCount={5}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FaqTable;
|
||||
export default ReportTable;
|
||||
|
|
|
|||
|
|
@ -3,35 +3,44 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|||
import FaqTable from "./components/table";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Plus } from "lucide-react";
|
||||
import { StatisticsBlock } from "@/components/blocks/statistics-block";
|
||||
import ReportTable from "./components/table";
|
||||
|
||||
const FaqPage = async () => {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-1 text-xl font-medium text-default-900">
|
||||
Pelaporan Akun
|
||||
</div>
|
||||
<div className="flex-none"></div>
|
||||
</div>
|
||||
<Card>
|
||||
<CardHeader className="border-b border-solid border-default-200 mb-6">
|
||||
<CardTitle>
|
||||
<div className="flex items-center">
|
||||
<div className="flex-1 text-xl font-medium text-default-900">
|
||||
FAQ Data
|
||||
</div>
|
||||
<div className="flex-none">
|
||||
<Button
|
||||
fullWidth
|
||||
size="md"
|
||||
>
|
||||
<Plus className="w-6 h-6 me-1.5"/>
|
||||
New FAQ
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<FaqTable />
|
||||
<CardContent className="p-4">
|
||||
<div className="grid md:grid-cols-3 gap-4">
|
||||
<StatisticsBlock
|
||||
total="64"
|
||||
title="Total Pelaporan"
|
||||
className=" border shadow-none"
|
||||
/>
|
||||
<StatisticsBlock
|
||||
title="Total Disetujui"
|
||||
total="564"
|
||||
className=" border shadow-none"
|
||||
chartColor="#FB8F65"
|
||||
/>
|
||||
<StatisticsBlock
|
||||
title="Pelaporan Pending"
|
||||
total="+5.0%"
|
||||
className=" border shadow-none"
|
||||
chartColor="#2563eb"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<ReportTable />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
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 {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuItem,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { format } from "date-fns";
|
||||
import { Link } from "@/components/navigation";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
accessorKey: "no",
|
||||
header: "No",
|
||||
cell: ({ row }) => <span> {row.getValue("no")}</span>,
|
||||
},
|
||||
{
|
||||
accessorKey: "title",
|
||||
header: "Nama",
|
||||
cell: ({ row }) => (
|
||||
<span className="normal-case"> {row.getValue("title")}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "phoneNumber",
|
||||
header: "No.Telp",
|
||||
cell: ({ row }) => {
|
||||
const createdBy = row.original.createdBy; // Akses properti category
|
||||
return (
|
||||
<span className="normal-case">{createdBy?.fullname || "N/A"}</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "email",
|
||||
header: "Email",
|
||||
cell: ({ row }) => {
|
||||
const sendTo = row.original.sendTo; // Akses properti category
|
||||
return <span className="normal-case">{sendTo?.fullname || "N/A"}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "createdName",
|
||||
header: "Admin",
|
||||
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>;
|
||||
},
|
||||
},
|
||||
// {
|
||||
// id: "actions",
|
||||
// accessorKey: "action",
|
||||
// header: "Actions",
|
||||
// enableHiding: false,
|
||||
// cell: ({ row }) => {
|
||||
// return (
|
||||
// <DropdownMenu>
|
||||
// <DropdownMenuTrigger asChild>
|
||||
// <Button
|
||||
// size="icon"
|
||||
// className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
|
||||
// >
|
||||
// <span className="sr-only">Open menu</span>
|
||||
// <MoreVertical className="h-4 w-4 text-default-800" />
|
||||
// </Button>
|
||||
// </DropdownMenuTrigger>
|
||||
// <DropdownMenuContent className="p-0" align="end">
|
||||
// <Link
|
||||
// href={`/supervisor/communications/internal/detail/${row.original.id}`}
|
||||
// >
|
||||
// <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||
// <Eye className="w-4 h-4 me-1.5" />
|
||||
// View
|
||||
// </DropdownMenuItem>
|
||||
// </Link>
|
||||
// <Link
|
||||
// href={`/supervisor/communications/internal/update/${row.original.id}`}
|
||||
// >
|
||||
// <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||
// <SquarePen className="w-4 h-4 me-1.5" />
|
||||
// Edit
|
||||
// </DropdownMenuItem>
|
||||
// </Link>
|
||||
// <DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
||||
// <Trash2 className="w-4 h-4 me-1.5" />
|
||||
// Delete
|
||||
// </DropdownMenuItem>
|
||||
// </DropdownMenuContent>
|
||||
// </DropdownMenu>
|
||||
// );
|
||||
// },
|
||||
// },
|
||||
];
|
||||
|
||||
export default columns;
|
||||
|
|
@ -0,0 +1,299 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
PaginationState,
|
||||
SortingState,
|
||||
VisibilityState,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Eye,
|
||||
MoreVertical,
|
||||
Search,
|
||||
SquarePen,
|
||||
Trash2,
|
||||
TrendingDown,
|
||||
TrendingUp,
|
||||
UploadIcon,
|
||||
} from "lucide-react";
|
||||
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||
import { paginationBlog } from "@/service/blog/blog";
|
||||
import { ticketingPagination } from "@/service/ticketing/ticketing";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import TablePagination from "@/components/table/table-pagination";
|
||||
import columns from "./columns";
|
||||
import {
|
||||
listDataAudio,
|
||||
listDataImage,
|
||||
listDataVideo,
|
||||
} from "@/service/content/content";
|
||||
import { listTicketingInternal } from "@/service/communication/communication";
|
||||
import { Link } from "@/components/navigation";
|
||||
import { Card } from "nextra-theme-docs";
|
||||
import { CardContent } from "@/components/ui/card";
|
||||
|
||||
const ContactTable = () => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
||||
const [totalData, setTotalData] = React.useState<number>(1);
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
[]
|
||||
);
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
const [showData, setShowData] = React.useState("10");
|
||||
|
||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: Number(showData),
|
||||
});
|
||||
const [page, setPage] = React.useState(1);
|
||||
const [totalPage, setTotalPage] = React.useState(1);
|
||||
const [search, setSearch] = React.useState<string>("");
|
||||
const userId = getCookiesDecrypt("uie");
|
||||
const userLevelId = getCookiesDecrypt("ulie");
|
||||
const [activeCategory, setActiveCategory] = React.useState<string | null>(
|
||||
null
|
||||
);
|
||||
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
|
||||
const table = useReactTable({
|
||||
data: dataTable,
|
||||
columns,
|
||||
onSortingChange: setSorting,
|
||||
onColumnFiltersChange: setColumnFilters,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
onColumnVisibilityChange: setColumnVisibility,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
onPaginationChange: setPagination,
|
||||
state: {
|
||||
sorting,
|
||||
columnFilters,
|
||||
columnVisibility,
|
||||
rowSelection,
|
||||
pagination,
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const pageFromUrl = searchParams?.get("page");
|
||||
if (pageFromUrl) {
|
||||
setPage(Number(pageFromUrl));
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
// React.useEffect(() => {
|
||||
// fetchData();
|
||||
// setPagination({
|
||||
// pageIndex: 0,
|
||||
// pageSize: Number(showData),
|
||||
// });
|
||||
// }, [page, showData]);
|
||||
React.useEffect(() => {
|
||||
if (activeCategory) {
|
||||
fetchData();
|
||||
setPagination({
|
||||
pageIndex: 0,
|
||||
pageSize: Number(showData),
|
||||
});
|
||||
}
|
||||
}, [page, showData, activeCategory]);
|
||||
|
||||
let typingTimer: any;
|
||||
const doneTypingInterval = 1500;
|
||||
|
||||
const handleKeyUp = () => {
|
||||
clearTimeout(typingTimer);
|
||||
typingTimer = setTimeout(doneTyping, doneTypingInterval);
|
||||
};
|
||||
|
||||
const handleKeyDown = () => {
|
||||
clearTimeout(typingTimer);
|
||||
};
|
||||
|
||||
async function doneTyping() {
|
||||
fetchData();
|
||||
}
|
||||
|
||||
// async function fetchData() {
|
||||
// try {
|
||||
// const res = await listTicketingInternal(
|
||||
// page - 1,
|
||||
// Number(showData),
|
||||
// search
|
||||
// );
|
||||
// const data = res?.data?.data;
|
||||
// const contentData = data?.content;
|
||||
// contentData.forEach((item: any, index: number) => {
|
||||
// item.no = (page - 1) * Number(showData) + index + 1;
|
||||
// });
|
||||
|
||||
// console.log("contentData : ", contentData);
|
||||
|
||||
// setDataTable(contentData);
|
||||
// setTotalData(data?.totalElements);
|
||||
// setTotalPage(data?.totalPages);
|
||||
// } catch (error) {
|
||||
// console.error("Error fetching tasks:", error);
|
||||
// }
|
||||
// }
|
||||
|
||||
async function fetchData() {
|
||||
try {
|
||||
const res = await listTicketingInternal(
|
||||
page - 1,
|
||||
Number(showData),
|
||||
search,
|
||||
activeCategory
|
||||
);
|
||||
const data = res?.data?.data;
|
||||
const contentData = data?.content || [];
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * Number(showData) + index + 1;
|
||||
});
|
||||
setDataTable(contentData);
|
||||
setTotalData(data?.totalElements);
|
||||
setTotalPage(data?.totalPages);
|
||||
} catch (error) {
|
||||
console.error("Error fetching tasks:", error);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full overflow-x-auto ">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="mt-3 flex flex-row items-center gap-2">
|
||||
<InputGroup merged>
|
||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||
<Search className=" h-4 w-4 dark:text-white" />
|
||||
</InputGroupText>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search Judul..."
|
||||
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
onKeyUp={handleKeyUp}
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row items-center justify-around my-3">
|
||||
{["Polda", "Satker Humas", "Satker Mabes"].map((category, index) => (
|
||||
<CardContent
|
||||
key={index}
|
||||
className="shadow-lg border p-2 cursor-pointer"
|
||||
onClick={() => setActiveCategory(category)}
|
||||
>
|
||||
<div className="flex flex-col gap-3 text-center py-5 w-[250px]">
|
||||
<p className="text-3xl font-bold">
|
||||
{index === 0 ? 34 : index === 1 ? 1 : 43}
|
||||
</p>
|
||||
<p>{category}</p>
|
||||
<p className="text-blue-700">Lihat Kontak</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
))}
|
||||
</div>
|
||||
{activeCategory && (
|
||||
<div>
|
||||
<Table className="overflow-hidden mt-3">
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id} className="bg-default-200">
|
||||
{headerGroup.headers.map((header) => (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
className="h-[75px]"
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext()
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination
|
||||
table={table}
|
||||
totalData={totalData}
|
||||
totalPage={totalPage}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContactTable;
|
||||
|
|
@ -0,0 +1,359 @@
|
|||
"use client";
|
||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { z } from "zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import Swal from "sweetalert2";
|
||||
import { Link, useRouter } from "@/i18n/routing";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
AdministrationLevelList,
|
||||
getListCompetencies,
|
||||
getListExperiences,
|
||||
saveUserInternal,
|
||||
saveUserRolePlacements,
|
||||
} from "@/service/management-user/management-user";
|
||||
import { loading } from "@/config/swal";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
const FormSchema = z.object({
|
||||
name: z.string({
|
||||
required_error: "Required",
|
||||
}),
|
||||
username: z.string({
|
||||
required_error: "Required",
|
||||
}),
|
||||
password: z.string({
|
||||
required_error: "Required",
|
||||
}),
|
||||
phoneNumber: z.string({
|
||||
required_error: "Required",
|
||||
}),
|
||||
email: z.string({
|
||||
required_error: "Required",
|
||||
}),
|
||||
skills: z.string({
|
||||
required_error: "Required",
|
||||
}),
|
||||
experiences: z.string({
|
||||
required_error: "Required",
|
||||
}),
|
||||
company: z.string({
|
||||
required_error: "Required",
|
||||
}),
|
||||
});
|
||||
|
||||
export type Placements = {
|
||||
index: number;
|
||||
roleId?: string;
|
||||
userLevelId?: number;
|
||||
};
|
||||
|
||||
export default function AddContactForm() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
});
|
||||
const [incrementId, setIncrementId] = useState(1);
|
||||
const [placementRows, setPlacementRows] = useState<Placements[]>([
|
||||
{ index: 0, roleId: "", userLevelId: 0 },
|
||||
]);
|
||||
const [userCompetencies, setUserCompetencies] = useState<any>();
|
||||
const [userExperiences, setUserExperiences] = useState<any>();
|
||||
const [userLevels, setUserLevels] = useState<any>();
|
||||
|
||||
const roleSelection = [
|
||||
{
|
||||
id: "11",
|
||||
name: "Polda",
|
||||
},
|
||||
{
|
||||
id: "12",
|
||||
name: "Satker Humas",
|
||||
},
|
||||
{
|
||||
id: "13",
|
||||
name: "Satker Mabes",
|
||||
},
|
||||
];
|
||||
|
||||
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
|
||||
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>) => {
|
||||
console.log("data", data);
|
||||
|
||||
const dataReq = {
|
||||
firstName: data.name,
|
||||
username: data.username,
|
||||
email: data.email,
|
||||
password: data.password,
|
||||
adress: "",
|
||||
roleId: "EXP-ID",
|
||||
phoneNumber: data.phoneNumber,
|
||||
userCompetencyId: data.skills,
|
||||
userExperienceId: data.experiences,
|
||||
companyName: data.company,
|
||||
};
|
||||
|
||||
loading();
|
||||
const res = await saveUserInternal(dataReq);
|
||||
const resData = res?.data?.data;
|
||||
const userProfileId = resData?.id;
|
||||
|
||||
var placementArr: any[] = [];
|
||||
placementRows.forEach((row: any) => {
|
||||
placementArr.push({
|
||||
roleId: Number(row.roleId),
|
||||
userLevelId: Number(row.userLevelId),
|
||||
userProfileId: userProfileId,
|
||||
});
|
||||
});
|
||||
|
||||
const dataReq2 = {
|
||||
userId: userProfileId,
|
||||
placements: placementArr,
|
||||
};
|
||||
const res2 = await saveUserRolePlacements(dataReq2);
|
||||
const resData2 = res2?.data?.data;
|
||||
|
||||
success("/admin/add-experts");
|
||||
};
|
||||
|
||||
function success(redirect: string): void {
|
||||
MySwal.fire({
|
||||
title: '<p class="text-green-600 font-bold">Sukses</p>',
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: '<span class="text-white">OK</span>',
|
||||
allowOutsideClick: false,
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
router.push(redirect);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function successSubmit() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
router.push("/admin/add-experts");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const handleSelectionChange = (
|
||||
index: number,
|
||||
type: "roleId" | "userLevelId",
|
||||
value: string
|
||||
) => {
|
||||
setPlacementRows((prevRows) =>
|
||||
prevRows.map((row) =>
|
||||
row.index === index ? { ...row, [type]: value } : row
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const handleRemoveRow = (index: number) => {
|
||||
console.log(index);
|
||||
console.log(placementRows);
|
||||
const newPlacements = placementRows.filter((row) => row.index != index);
|
||||
console.log(newPlacements);
|
||||
setPlacementRows(newPlacements);
|
||||
};
|
||||
|
||||
const handleAddRow = () => {
|
||||
setPlacementRows((prevRows: any) => [
|
||||
...prevRows,
|
||||
{ index: incrementId, roleId: "", userLevelId: 0 },
|
||||
]);
|
||||
setIncrementId((prevId) => prevId + 1);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-3 bg-white rounded-sm p-4"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<Input
|
||||
type="email"
|
||||
value={field.value}
|
||||
placeholder="Masukkan Nama Lengkap"
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Nama Lengkap</FormLabel>
|
||||
<Input
|
||||
value={field.value}
|
||||
placeholder="Masukkan Nama Lengkap"
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="phoneNumber"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>No. HP</FormLabel>
|
||||
<Input
|
||||
type="number"
|
||||
value={field.value}
|
||||
placeholder="Masukkan Nama Lengkap"
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-2 mb-0 pb-0">
|
||||
<FormLabel>
|
||||
Kategori Akun <span className="text-red-500">*</span>
|
||||
</FormLabel>
|
||||
<Select
|
||||
// onValueChange={(e) =>
|
||||
// handleSelectionChange(row.index, "roleId", e)
|
||||
// }
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Pilih Role" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{roleSelection?.map((item: any) => (
|
||||
<SelectItem key={item.id} value={String(item.id)}>
|
||||
{item.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Username Admin</FormLabel>
|
||||
<Input
|
||||
value={field.value}
|
||||
placeholder="Masukkan Nama Lengkap"
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Username Approver</FormLabel>
|
||||
<Input
|
||||
value={field.value}
|
||||
placeholder="Masukkan Nama Lengkap"
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Username Kontributor</FormLabel>
|
||||
<Input
|
||||
value={field.value}
|
||||
placeholder="Masukkan Nama Lengkap"
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex flex-row justify-end gap-2 mt-4 pt-4">
|
||||
<Link href="/supervisor/communications/contact">
|
||||
<Button
|
||||
size="md"
|
||||
type="button"
|
||||
variant="outline"
|
||||
color="destructive"
|
||||
className="text-xs"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Link>
|
||||
<Button size="md" type="submit" color="primary" className="text-xs">
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
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 {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuItem,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { format } from "date-fns";
|
||||
import { Link } from "@/components/navigation";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
accessorKey: "no",
|
||||
header: "No",
|
||||
cell: ({ row }) => <span> {row.getValue("no")}</span>,
|
||||
},
|
||||
{
|
||||
accessorKey: "title",
|
||||
header: "Nama",
|
||||
cell: ({ row }) => (
|
||||
<span className="normal-case"> {row.getValue("title")}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "phoneNumber",
|
||||
header: "No.Telp",
|
||||
cell: ({ row }) => {
|
||||
const createdBy = row.original.createdBy; // Akses properti category
|
||||
return (
|
||||
<span className="normal-case">{createdBy?.fullname || "N/A"}</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "email",
|
||||
header: "Email",
|
||||
cell: ({ row }) => {
|
||||
const sendTo = row.original.sendTo; // Akses properti category
|
||||
return <span className="normal-case">{sendTo?.fullname || "N/A"}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "createdName",
|
||||
header: "Admin",
|
||||
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: "approver",
|
||||
header: "Approver",
|
||||
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: "contributor",
|
||||
header: "Contributor",
|
||||
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>;
|
||||
},
|
||||
},
|
||||
// {
|
||||
// id: "actions",
|
||||
// accessorKey: "action",
|
||||
// header: "Actions",
|
||||
// enableHiding: false,
|
||||
// cell: ({ row }) => {
|
||||
// return (
|
||||
// <DropdownMenu>
|
||||
// <DropdownMenuTrigger asChild>
|
||||
// <Button
|
||||
// size="icon"
|
||||
// className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
|
||||
// >
|
||||
// <span className="sr-only">Open menu</span>
|
||||
// <MoreVertical className="h-4 w-4 text-default-800" />
|
||||
// </Button>
|
||||
// </DropdownMenuTrigger>
|
||||
// <DropdownMenuContent className="p-0" align="end">
|
||||
// <Link
|
||||
// href={`/supervisor/communications/internal/detail/${row.original.id}`}
|
||||
// >
|
||||
// <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||
// <Eye className="w-4 h-4 me-1.5" />
|
||||
// View
|
||||
// </DropdownMenuItem>
|
||||
// </Link>
|
||||
// <Link
|
||||
// href={`/supervisor/communications/internal/update/${row.original.id}`}
|
||||
// >
|
||||
// <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||
// <SquarePen className="w-4 h-4 me-1.5" />
|
||||
// Edit
|
||||
// </DropdownMenuItem>
|
||||
// </Link>
|
||||
// <DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
||||
// <Trash2 className="w-4 h-4 me-1.5" />
|
||||
// Delete
|
||||
// </DropdownMenuItem>
|
||||
// </DropdownMenuContent>
|
||||
// </DropdownMenu>
|
||||
// );
|
||||
// },
|
||||
// },
|
||||
];
|
||||
|
||||
export default columns;
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
PaginationState,
|
||||
SortingState,
|
||||
VisibilityState,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Eye,
|
||||
MoreVertical,
|
||||
Search,
|
||||
SquarePen,
|
||||
Trash2,
|
||||
TrendingDown,
|
||||
TrendingUp,
|
||||
UploadIcon,
|
||||
} from "lucide-react";
|
||||
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||
import { paginationBlog } from "@/service/blog/blog";
|
||||
import { ticketingPagination } from "@/service/ticketing/ticketing";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import TablePagination from "@/components/table/table-pagination";
|
||||
import columns from "./columns";
|
||||
import {
|
||||
listDataAudio,
|
||||
listDataImage,
|
||||
listDataVideo,
|
||||
} from "@/service/content/content";
|
||||
import { listTicketingInternal } from "@/service/communication/communication";
|
||||
import { Link } from "@/components/navigation";
|
||||
import { Card } from "nextra-theme-docs";
|
||||
import { CardContent } from "@/components/ui/card";
|
||||
|
||||
const ImportTable = () => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
||||
const [totalData, setTotalData] = React.useState<number>(1);
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
[]
|
||||
);
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
const [showData, setShowData] = React.useState("10");
|
||||
|
||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: Number(showData),
|
||||
});
|
||||
const [page, setPage] = React.useState(1);
|
||||
const [totalPage, setTotalPage] = React.useState(1);
|
||||
const [search, setSearch] = React.useState<string>("");
|
||||
const userId = getCookiesDecrypt("uie");
|
||||
const userLevelId = getCookiesDecrypt("ulie");
|
||||
const [activeCategory, setActiveCategory] = React.useState<string | null>(
|
||||
null
|
||||
);
|
||||
|
||||
const [selectedFile, setSelectedFile] = React.useState(null);
|
||||
|
||||
const handleFileChange = (event: any) => {
|
||||
const file = event.target.files[0];
|
||||
setSelectedFile(file);
|
||||
};
|
||||
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
|
||||
const table = useReactTable({
|
||||
data: dataTable,
|
||||
columns,
|
||||
onSortingChange: setSorting,
|
||||
onColumnFiltersChange: setColumnFilters,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
onColumnVisibilityChange: setColumnVisibility,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
onPaginationChange: setPagination,
|
||||
state: {
|
||||
sorting,
|
||||
columnFilters,
|
||||
columnVisibility,
|
||||
rowSelection,
|
||||
pagination,
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const pageFromUrl = searchParams?.get("page");
|
||||
if (pageFromUrl) {
|
||||
setPage(Number(pageFromUrl));
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
// React.useEffect(() => {
|
||||
// fetchData();
|
||||
// setPagination({
|
||||
// pageIndex: 0,
|
||||
// pageSize: Number(showData),
|
||||
// });
|
||||
// }, [page, showData]);
|
||||
React.useEffect(() => {
|
||||
if (activeCategory) {
|
||||
fetchData();
|
||||
setPagination({
|
||||
pageIndex: 0,
|
||||
pageSize: Number(showData),
|
||||
});
|
||||
}
|
||||
}, [page, showData, activeCategory]);
|
||||
|
||||
let typingTimer: any;
|
||||
const doneTypingInterval = 1500;
|
||||
|
||||
const handleKeyUp = () => {
|
||||
clearTimeout(typingTimer);
|
||||
typingTimer = setTimeout(doneTyping, doneTypingInterval);
|
||||
};
|
||||
|
||||
const handleKeyDown = () => {
|
||||
clearTimeout(typingTimer);
|
||||
};
|
||||
|
||||
async function doneTyping() {
|
||||
fetchData();
|
||||
}
|
||||
|
||||
// async function fetchData() {
|
||||
// try {
|
||||
// const res = await listTicketingInternal(
|
||||
// page - 1,
|
||||
// Number(showData),
|
||||
// search
|
||||
// );
|
||||
// const data = res?.data?.data;
|
||||
// const contentData = data?.content;
|
||||
// contentData.forEach((item: any, index: number) => {
|
||||
// item.no = (page - 1) * Number(showData) + index + 1;
|
||||
// });
|
||||
|
||||
// console.log("contentData : ", contentData);
|
||||
|
||||
// setDataTable(contentData);
|
||||
// setTotalData(data?.totalElements);
|
||||
// setTotalPage(data?.totalPages);
|
||||
// } catch (error) {
|
||||
// console.error("Error fetching tasks:", error);
|
||||
// }
|
||||
// }
|
||||
|
||||
async function fetchData() {
|
||||
try {
|
||||
const res = await listTicketingInternal(
|
||||
page - 1,
|
||||
Number(showData),
|
||||
search,
|
||||
activeCategory
|
||||
);
|
||||
const data = res?.data?.data;
|
||||
const contentData = data?.content || [];
|
||||
contentData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * Number(showData) + index + 1;
|
||||
});
|
||||
setDataTable(contentData);
|
||||
setTotalData(data?.totalElements);
|
||||
setTotalPage(data?.totalPages);
|
||||
} catch (error) {
|
||||
console.error("Error fetching tasks:", error);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full overflow-x-auto ">
|
||||
<div className="flex">
|
||||
<input
|
||||
type="file"
|
||||
id="fileInput"
|
||||
onChange={handleFileChange}
|
||||
className="p-2 border rounded-md"
|
||||
/>
|
||||
{/* {selectedFile && (
|
||||
<p className="text-sm text-gray-600">File: {selectedFile.name}</p>
|
||||
)} */}
|
||||
</div>
|
||||
<div className="my-3">
|
||||
<Button type="button" variant={"outline"} color="success">
|
||||
Import Data
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Table className="overflow-hidden mt-3">
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id} className="bg-default-200">
|
||||
{headerGroup.headers.map((header) => (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
className="h-[75px]"
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext()
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination
|
||||
table={table}
|
||||
totalData={totalData}
|
||||
totalPage={totalPage}
|
||||
/>
|
||||
</div>
|
||||
<div className="my-3">
|
||||
<Link href="/supervisor/communications/contact">
|
||||
<Button variant={"outline"} color="primary">
|
||||
Kembali
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImportTable;
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { PlusIcon, User } from "lucide-react";
|
||||
import ContactTable from "../components/contact-table";
|
||||
import ImportTable from "./components/contact-table";
|
||||
|
||||
const ImportPage = async () => {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
<div className="w-full overflow-x-auto p-4 rounded-sm space-y-3">
|
||||
<div className="flex justify-between py-3 border rounded-md px-3">
|
||||
<p className="text-lg font-semibold">Kontak</p>
|
||||
</div>
|
||||
<ImportTable />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImportPage;
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Dashboard Media Hub",
|
||||
description: "Dashboard Media Hub.",
|
||||
};
|
||||
const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { PlusIcon, User } from "lucide-react";
|
||||
import InternalSpvTable from "../internal/components/internal-table";
|
||||
import ContactTable from "./components/contact-table";
|
||||
|
||||
const ContactPage = async () => {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
<div className="w-full overflow-x-auto p-4 rounded-sm space-y-3">
|
||||
<div className="flex justify-between py-3 border rounded-md px-3">
|
||||
<p className="text-lg font-semibold">Kontak</p>
|
||||
</div>
|
||||
<div className="flex justify-between py-3">
|
||||
<p className="text-lg pl-3 font-semibold">Semua Kontak</p>
|
||||
<div>
|
||||
<Link href="/supervisor/communications/contact/import-contact">
|
||||
<Button color="success" size="md">
|
||||
<PlusIcon />
|
||||
Import
|
||||
</Button>
|
||||
</Link>{" "}
|
||||
<Link href="/supervisor/communications/contact/create">
|
||||
<Button color="primary" size="md">
|
||||
<User />
|
||||
Tambah Kontak
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ContactTable />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContactPage;
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
"use client";
|
||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Search } from "lucide-react";
|
||||
|
||||
const messages = [
|
||||
{ name: "Miles Esther", initials: "ME", time: "06.00" },
|
||||
{ name: "Flores Juanita", initials: "FJ", time: "06.00" },
|
||||
{ name: "Henry Arthur", initials: "HA", time: "06.00" },
|
||||
{ name: "Polres Jakarta Selatan", initials: "PJ", time: "06.00" },
|
||||
];
|
||||
|
||||
const WebChatPage = () => {
|
||||
const [selectedChat, setSelectedChat] = useState<any>(null);
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
{!selectedChat ? (
|
||||
// Tampilan daftar pesan
|
||||
<>
|
||||
<div className="flex justify-between py-3 border rounded-md px-3">
|
||||
<p className="text-lg font-semibold">Web Chat</p>
|
||||
</div>
|
||||
<div className="flex flex-row justify-start rounded-lg ">
|
||||
<div className="flex flex-row items-center">
|
||||
<Input placeholder="Cari..." className=" w-[200px] mr-3" />
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="default" size="md" color="primary">
|
||||
Add
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-[350px] bg-black text-white rounded-lg p-4">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-lg font-bold text-white">
|
||||
New Chat
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="relative">
|
||||
<Search
|
||||
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"
|
||||
size={16}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Search here..."
|
||||
className="w-full pl-10 text-white placeholder-gray-400 border-none rounded-md focus:ring-0"
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<p className="text-sm font-semibold">+ Group Chat</p>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<p className="text-sm text-gray-400">
|
||||
Frequently contacted
|
||||
</p>
|
||||
<div className="mt-2 space-y-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 flex items-center justify-center bg-white text-black font-bold rounded-full">
|
||||
ME
|
||||
</div>
|
||||
<span className="text-sm font-medium">
|
||||
Miles Esther
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 flex items-center justify-center bg-white text-black font-bold rounded-full">
|
||||
HA
|
||||
</div>
|
||||
<span className="text-sm font-medium">
|
||||
Henry Arthur
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
<Card>
|
||||
<CardContent className="p-4 bg-gray-100 rounded-lg">
|
||||
<div className="space-y-4">
|
||||
{messages.map((msg: any, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center justify-between p-4 bg-white rounded-lg shadow cursor-pointer hover:bg-gray-200"
|
||||
onClick={() => setSelectedChat(msg)}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 flex items-center justify-center bg-black text-white rounded-full text-lg font-bold">
|
||||
{msg.initials}
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-bold text-lg">{msg.name}</p>
|
||||
<p className="text-gray-600 text-sm">
|
||||
✅ Hallo, untuk patroli hari ini sudah bisa di akses
|
||||
di daily tasks, silahkan anda periksa
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-gray-500 text-sm">{msg.time}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
) : (
|
||||
// Tampilan chat
|
||||
<div>
|
||||
<div className="flex justify-between py-3 border rounded-md px-3 my-3">
|
||||
<p className="text-lg font-semibold">Web Chat</p>
|
||||
</div>
|
||||
<Card className="h-[100vh]">
|
||||
<CardContent className="p-0 flex flex-col h-full">
|
||||
{/* Header Chat */}
|
||||
<div className="flex items-center bg-black text-white p-4">
|
||||
<button
|
||||
className="mr-4 text-white text-xl"
|
||||
onClick={() => setSelectedChat(null)}
|
||||
>
|
||||
←
|
||||
</button>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-10 h-10 flex items-center justify-center bg-white text-black rounded-full text-lg font-bold">
|
||||
{selectedChat.initials}
|
||||
</div>
|
||||
<p className="font-bold text-lg">{selectedChat.name}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Body Chat */}
|
||||
<div className="flex-1 bg-gray-300 p-4 flex flex-col justify-end">
|
||||
<p className="text-center text-gray-500 mb-4">Today</p>
|
||||
|
||||
{/* Pesan masuk */}
|
||||
<div className="flex items-start mb-4">
|
||||
<div className="bg-white p-3 rounded-lg shadow max-w-xs">
|
||||
Hallo, untuk patroli hari ini sudah bisa di akses di daily
|
||||
tasks, silahkan anda periksa
|
||||
</div>
|
||||
<p className="text-gray-500 text-xs ml-2">06.00</p>
|
||||
</div>
|
||||
|
||||
{/* Pesan keluar */}
|
||||
<div className="flex items-end justify-end mb-4">
|
||||
<p className="text-gray-500 text-xs mr-2">06.00</p>
|
||||
<div className="bg-blue-500 text-white p-3 rounded-lg shadow max-w-xs">
|
||||
Hallo, bisakah mengirimkan rute patroli untuk hari ini?
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Pesan keluar lainnya */}
|
||||
<div className="flex items-end justify-end mb-4">
|
||||
<p className="text-gray-500 text-xs mr-2">06.00</p>
|
||||
<div className="bg-blue-500 text-white p-3 rounded-lg shadow max-w-xs">
|
||||
Terima kasih banyak
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Input Chat */}
|
||||
<div className="flex items-center p-4 bg-black">
|
||||
<Button className="bg-white text-black p-2 rounded-full mr-2">
|
||||
+
|
||||
</Button>
|
||||
<Input
|
||||
placeholder="Tulis pesan..."
|
||||
className="flex-1 rounded-lg"
|
||||
/>
|
||||
<Button className="ml-2 bg-white text-black p-2 rounded-full">
|
||||
🎤
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WebChatPage;
|
||||
|
|
@ -8,13 +8,29 @@ import NewContent from "@/components/landing-page/new-content";
|
|||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
import { close, error, loading, successCallback, warning } from "@/config/swal";
|
||||
import { checkWishlistStatus, createPublicSuggestion, deletePublicSuggestion, deleteWishlist, getDetail, getPublicSuggestionList, saveWishlist } from "@/service/landing/landing";
|
||||
import {
|
||||
checkWishlistStatus,
|
||||
createPublicSuggestion,
|
||||
deletePublicSuggestion,
|
||||
deleteWishlist,
|
||||
getDetail,
|
||||
getPublicSuggestionList,
|
||||
saveWishlist,
|
||||
} from "@/service/landing/landing";
|
||||
import { Link, useRouter } from "@/i18n/routing";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { sendMediaUploadToEmail } from "@/service/media-tracking/media-tracking";
|
||||
import { checkMaliciousText, formatDateToIndonesian, getPublicLocaleTimestamp } from "@/utils/globals";
|
||||
import {
|
||||
checkMaliciousText,
|
||||
formatDateToIndonesian,
|
||||
getPublicLocaleTimestamp,
|
||||
} from "@/utils/globals";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import Swal from "sweetalert2";
|
||||
import parse from "html-react-parser";
|
||||
|
|
@ -22,7 +38,6 @@ import { Skeleton } from "@/components/ui/skeleton";
|
|||
import { useTranslations } from "next-intl";
|
||||
import Image from "next/image";
|
||||
|
||||
|
||||
interface Size {
|
||||
label: string;
|
||||
value: string;
|
||||
|
|
@ -64,8 +79,11 @@ const DetailInfo = () => {
|
|||
const poldaName = params?.polda_name;
|
||||
const satkerName = params?.satker_name;
|
||||
|
||||
let prefixPath = poldaName ? `/polda/${poldaName}` : satkerName ? `/satker/${satkerName}` : "/";
|
||||
|
||||
let prefixPath = poldaName
|
||||
? `/polda/${poldaName}`
|
||||
: satkerName
|
||||
? `/satker/${satkerName}`
|
||||
: "/";
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
|
|
@ -178,14 +196,14 @@ const DetailInfo = () => {
|
|||
const sizes: Size[] = Object.entries(scaleFactors).map(([label, factor]) => {
|
||||
const width = Number(main?.widthPixel);
|
||||
const height = Number(main?.heightPixel);
|
||||
|
||||
|
||||
if (isNaN(width) || isNaN(height) || width === 0) {
|
||||
return { label, value: "Invalid size" };
|
||||
}
|
||||
|
||||
|
||||
const newWidth = Math.round(width * factor);
|
||||
const newHeight = Math.round((width * factor) / (width / height));
|
||||
|
||||
|
||||
return { label, value: `${newWidth} x ${newHeight} px` };
|
||||
});
|
||||
|
||||
|
|
@ -263,7 +281,8 @@ const DetailInfo = () => {
|
|||
|
||||
xhr.addEventListener("readystatechange", () => {
|
||||
if (xhr.readyState === 4 && xhr.status === 200) {
|
||||
const contentType = xhr.getResponseHeader("content-type") || "application/octet-stream";
|
||||
const contentType =
|
||||
xhr.getResponseHeader("content-type") || "application/octet-stream";
|
||||
const extension = contentType.split("/")[1];
|
||||
const filename = `${name}.${extension}`;
|
||||
|
||||
|
|
@ -297,7 +316,11 @@ const DetailInfo = () => {
|
|||
if (type == "wa" && width <= 768) {
|
||||
window.open(`whatsapp://send?${url}`, "_blank");
|
||||
} else if (type == "wa" && width > 768) {
|
||||
window.open(`https://web.whatsapp.com/send?${url}`, "_blank", "noreferrer");
|
||||
window.open(
|
||||
`https://web.whatsapp.com/send?${url}`,
|
||||
"_blank",
|
||||
"noreferrer"
|
||||
);
|
||||
} else {
|
||||
window.open(url);
|
||||
}
|
||||
|
|
@ -386,7 +409,9 @@ const DetailInfo = () => {
|
|||
}
|
||||
|
||||
async function sendSuggestionChild(parentId: any) {
|
||||
const inputElement = document.querySelector(`#input-comment-${parentId}`) as HTMLInputElement;
|
||||
const inputElement = document.querySelector(
|
||||
`#input-comment-${parentId}`
|
||||
) as HTMLInputElement;
|
||||
|
||||
if (inputElement && inputElement.value.length > 3) {
|
||||
loading();
|
||||
|
|
@ -399,7 +424,9 @@ const DetailInfo = () => {
|
|||
console.log(data);
|
||||
const response = await createPublicSuggestion(data);
|
||||
console.log(response);
|
||||
const responseGet: any = await getPublicSuggestionList(slug?.split("-")?.[0]);
|
||||
const responseGet: any = await getPublicSuggestionList(
|
||||
slug?.split("-")?.[0]
|
||||
);
|
||||
console.log(responseGet.data?.data);
|
||||
setListSuggestion(responseGet.data?.data);
|
||||
|
||||
|
|
@ -477,9 +504,11 @@ const DetailInfo = () => {
|
|||
<rect id="r" width="${w}" height="${h}" fill="url(#g)" />
|
||||
<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 (
|
||||
<>
|
||||
|
|
@ -494,7 +523,16 @@ const DetailInfo = () => {
|
|||
</div>
|
||||
) : (
|
||||
<div className="relative">
|
||||
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={2560} height={1440} src={detailDataImage?.files[selectedImage]?.url} alt="Main" className="rounded-lg w-auto h-fit" />
|
||||
<Image
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||
shimmer(700, 475)
|
||||
)}`}
|
||||
width={2560}
|
||||
height={1440}
|
||||
src={detailDataImage?.files[selectedImage]?.url}
|
||||
alt="Main"
|
||||
className="rounded-lg w-auto h-fit"
|
||||
/>
|
||||
<div className="absolute top-4 left-4"></div>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -510,7 +548,16 @@ const DetailInfo = () => {
|
|||
<div className="py-4 flex flex-row gap-3">
|
||||
{detailDataImage?.files?.map((file: any, index: number) => (
|
||||
<a onClick={() => setSelectedImage(index)} key={file?.id}>
|
||||
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={1920} height={1080} alt="image-small" src={file?.url} className="w-[120px] h-[90px] object-cover rounded-md cursor-pointer hover:ring-2 hover:ring-red-600" />
|
||||
<Image
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||
shimmer(700, 475)
|
||||
)}`}
|
||||
width={1920}
|
||||
height={1080}
|
||||
alt="image-small"
|
||||
src={file?.url}
|
||||
className="w-[120px] h-[90px] object-cover rounded-md cursor-pointer hover:ring-2 hover:ring-red-600"
|
||||
/>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -520,7 +567,10 @@ const DetailInfo = () => {
|
|||
<div className="text-gray-500 flex flex-col lg:flex-row justify-between items-center border-t mt-4">
|
||||
<div className="flex flex-col lg:flex-row items-center mt-3 lg:justify-between">
|
||||
<p className="text-xs lg:text-sm">
|
||||
{t("by")} <span className="font-semibold text-black dark:text-white">{detailDataImage?.uploadedBy?.userLevel?.name}</span>
|
||||
{t("by")}
|
||||
<span className="font-semibold text-black dark:text-white">
|
||||
{detailDataImage?.uploadedBy?.userLevel?.name}
|
||||
</span>
|
||||
</p>
|
||||
{/* <p className="text-xs lg:text-sm">
|
||||
| {t("updatedOn")}
|
||||
|
|
@ -528,7 +578,10 @@ const DetailInfo = () => {
|
|||
</p> */}
|
||||
<p className="text-xs lg:text-sm">
|
||||
| {t("updatedOn")}
|
||||
{formatDateToIndonesian(new Date(detailDataImage?.updatedAt))} {"WIB"}
|
||||
{formatDateToIndonesian(
|
||||
new Date(detailDataImage?.updatedAt)
|
||||
)}{" "}
|
||||
{"WIB"}
|
||||
</p>
|
||||
<p className="text-xs lg:text-sm flex justify-center items-center">
|
||||
|
|
||||
|
|
@ -546,51 +599,86 @@ const DetailInfo = () => {
|
|||
|
||||
{/* Keterangan */}
|
||||
<div className="w-full">
|
||||
<h1 className="flex flex-row font-bold text-lg lg:text-2xl my-8">{detailDataImage?.title}</h1>
|
||||
<div className="font-light text-justify mb-5 space-y-4 lg:mb-0" dangerouslySetInnerHTML={{ __html: detailDataImage?.htmlDescription }} />
|
||||
<h1 className="flex flex-row font-bold text-lg lg:text-2xl my-8">
|
||||
{detailDataImage?.title}
|
||||
</h1>
|
||||
<div
|
||||
className="font-light text-justify mb-5 space-y-4 lg:mb-0"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: detailDataImage?.htmlDescription,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bagian Kanan */}
|
||||
<div className="md:w-1/4 p-4 bg-[#f7f7f7] dark:bg-slate-600 h-fit rounded-lg mx-4">
|
||||
{isSaved ? (
|
||||
<a onClick={() => handleDeleteWishlist()} className="flex flex-col mb-3 items-center justify-center cursor-pointer">
|
||||
<a
|
||||
onClick={() => handleDeleteWishlist()}
|
||||
className="flex flex-col mb-3 items-center justify-center cursor-pointer"
|
||||
>
|
||||
<Icon icon="material-symbols:bookmark" width={40} />
|
||||
<p className="text-base lg:text-lg">{t("delete")}</p>
|
||||
</a>
|
||||
) : (
|
||||
<a onClick={() => doBookmark()} className="flex flex-col mb-3 items-center justify-center cursor-pointer">
|
||||
<Icon icon="material-symbols:bookmark-outline" width={40} />
|
||||
<p className="text-base lg:text-lg">{t("save")}</p>
|
||||
<a
|
||||
onClick={() => doBookmark()}
|
||||
className="flex flex-col mb-3 items-center justify-center cursor-pointer"
|
||||
>
|
||||
<Icon icon="material-symbols:bookmark-outline" width={25} />
|
||||
<p className="text-base lg:text-sm">{t("save")}</p>
|
||||
</a>
|
||||
)}
|
||||
|
||||
{/* garis */}
|
||||
<div className="border-t border-black my-4"></div>
|
||||
<div className="flex flex-col justify-center items-center gap-3">
|
||||
<div className="w-auto mb-3">
|
||||
<Link
|
||||
href={`/all/filter?title=polda&category=${detailDataImage?.category.id}`}
|
||||
className="bg-red-600 text-white text-xs font-bold px-3 py-3 my-3 rounded w-auto"
|
||||
>
|
||||
{detailDataImage?.category?.name}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<Link href={`/all/filter?title=polda&category=${detailDataImage?.category.id}`} className="bg-red-600 text-white text-xs font-bold px-3 py-3 my-3 flex justify-center items-center rounded">
|
||||
{detailDataImage?.category?.name}
|
||||
</Link>
|
||||
|
||||
<div className="flex justify-center flex-wrap gap-2 mb-4">
|
||||
{detailDataImage?.tags?.split(",").map((tag: string) => (
|
||||
<a onClick={() => router.push(`/all/filter?tag=${tag}`)} key={tag} className="bg-gray-200 text-gray-700 text-xs px-3 py-1 rounded-full cursor-pointer hover:bg-gray-500">
|
||||
{tag}
|
||||
</a>
|
||||
))}
|
||||
<div className="flex justify-center flex-wrap gap-2 mb-4">
|
||||
{detailDataImage?.tags?.split(",").map((tag: string) => (
|
||||
<a
|
||||
onClick={() => router.push(`/all/filter?tag=${tag}`)}
|
||||
key={tag}
|
||||
className="bg-gray-200 text-gray-700 text-xs px-3 py-3 font-semibold rounded-full cursor-pointer hover:bg-gray-500"
|
||||
>
|
||||
{tag}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-black my-4"></div>
|
||||
|
||||
{/* Opsi Ukuran Foto */}
|
||||
<h4 className="flex text-lg justify-center items-center font-semibold my-3">{t("imageSize")}</h4>
|
||||
<h4 className="flex text-lg justify-center items-center font-semibold my-3">
|
||||
{t("imageSize")}
|
||||
</h4>
|
||||
<div className="border-t border-black my-4"></div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{sizes.map((size: any) => (
|
||||
<div className="flex flex-row justify-between">
|
||||
<div key={size?.label} className="items-center flex flex-row gap-2 cursor-pointer">
|
||||
<input type="radio" name="size" value={size?.label} checked={selectedSize === size?.label} onChange={(e) => setImageSizeSelected(e.target.value)} className="text-red-600 focus:ring-red-600" />
|
||||
<div
|
||||
key={size?.label}
|
||||
className="items-center flex flex-row gap-2 cursor-pointer"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="size"
|
||||
value={size?.label}
|
||||
checked={selectedSize === size?.label}
|
||||
onChange={(e) => setImageSizeSelected(e.target.value)}
|
||||
className="text-red-600 focus:ring-red-600"
|
||||
/>
|
||||
<div className="text-sm">{size?.label}</div>
|
||||
</div>
|
||||
<div className="text-sm">{size?.value}</div>
|
||||
|
|
@ -601,15 +689,30 @@ const DetailInfo = () => {
|
|||
{/* Download Semua */}
|
||||
<div className="mt-4">
|
||||
<label className="flex items-center space-x-2 text-sm">
|
||||
<input type="checkbox" className="text-red-600 focus:ring-red-600" onChange={() => setIsDownloadAll(!isDownloadAll)} />
|
||||
<input
|
||||
type="checkbox"
|
||||
className="text-red-600 focus:ring-red-600"
|
||||
onChange={() => setIsDownloadAll(!isDownloadAll)}
|
||||
/>
|
||||
<span>{t("downloadAll")}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Tombol Download */}
|
||||
<button onClick={handleDownload} className="mt-4 bg-red-600 text-white w-full py-2 flex justify-center items-center gap-1 rounded-md text-sm hover:bg-red-700">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||
<path fill="white" d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z" />
|
||||
<button
|
||||
onClick={handleDownload}
|
||||
className="mt-4 bg-red-600 text-white w-full py-2 flex justify-center items-center gap-1 rounded-md text-sm hover:bg-red-700"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="white"
|
||||
d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z"
|
||||
/>
|
||||
</svg>
|
||||
{t("download")}
|
||||
</button>
|
||||
|
|
@ -617,29 +720,90 @@ const DetailInfo = () => {
|
|||
{/* Tombol Bagikan */}
|
||||
<div className="flex flex-row mt-5 justify-center">
|
||||
<p className="text-base font-semibold">{t("share")}</p>
|
||||
<a className="ml-8 cursor-pointer" onClick={() => handleShare("fb", `https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}"e=${content?.title}`)}>
|
||||
<Icon icon="brandico:facebook" height="20" className="px-auto text-red-600 text-center" />
|
||||
<a
|
||||
className="ml-8 cursor-pointer"
|
||||
onClick={() =>
|
||||
handleShare(
|
||||
"fb",
|
||||
`https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}"e=${content?.title}`
|
||||
)
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
icon="brandico:facebook"
|
||||
height="20"
|
||||
className="px-auto text-red-600 text-center"
|
||||
/>
|
||||
</a>
|
||||
<a className="ml-5 cursor-pointer" onClick={() => handleShare("tw", `https://twitter.com/share?url=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}&text=${content?.title}`)}>
|
||||
<Icon icon="mdi:twitter" width="23" className="text-red-600 text-center" />
|
||||
<a
|
||||
className="ml-5 cursor-pointer"
|
||||
onClick={() =>
|
||||
handleShare(
|
||||
"tw",
|
||||
`https://twitter.com/share?url=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}&text=${content?.title}`
|
||||
)
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
icon="mdi:twitter"
|
||||
width="23"
|
||||
className="text-red-600 text-center"
|
||||
/>
|
||||
</a>
|
||||
<a className="ml-5 cursor-pointer" onClick={() => handleShare("wa", `text=${content?.title}%0D%0A%0D%0Ahttps%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}`)}>
|
||||
<Icon icon="ri:whatsapp-fill" width="23" className="text-red-600 text-center" />
|
||||
<a
|
||||
className="ml-5 cursor-pointer"
|
||||
onClick={() =>
|
||||
handleShare(
|
||||
"wa",
|
||||
`text=${content?.title}%0D%0A%0D%0Ahttps%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}`
|
||||
)
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
icon="ri:whatsapp-fill"
|
||||
width="23"
|
||||
className="text-red-600 text-center"
|
||||
/>
|
||||
</a>
|
||||
<Popover>
|
||||
<PopoverTrigger className="flex justify-end gap-1 cursor-pointer" asChild>
|
||||
<a className="ml-5 cursor-pointer" data-toggle="dropdown" href="#" aria-expanded="false">
|
||||
<Icon icon="material-symbols-light:mail" width="23" className="text-red-600 text-center" />
|
||||
<PopoverTrigger
|
||||
className="flex justify-end gap-1 cursor-pointer"
|
||||
asChild
|
||||
>
|
||||
<a
|
||||
className="ml-5 cursor-pointer"
|
||||
data-toggle="dropdown"
|
||||
href="#"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<Icon
|
||||
icon="material-symbols-light:mail"
|
||||
width="23"
|
||||
className="text-red-600 text-center"
|
||||
/>
|
||||
</a>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<div className="flex flex-col">
|
||||
<h1 className="mb-2">{t("shareTo")}</h1>
|
||||
<div className="flex flex-col mb-2">
|
||||
<p className="text-base font-semibold mb-1">{t("destinationEmail")}</p>
|
||||
<Input value={emailShareInput} onChange={(event) => setEmailShareInput(event.target.value)} onKeyPress={handleEmailList} type="email" placeholder={t("pressEnter")} />
|
||||
<p className="text-base font-semibold mb-1">
|
||||
{t("destinationEmail")}
|
||||
</p>
|
||||
<Input
|
||||
value={emailShareInput}
|
||||
onChange={(event) =>
|
||||
setEmailShareInput(event.target.value)
|
||||
}
|
||||
onKeyPress={handleEmailList}
|
||||
type="email"
|
||||
placeholder={t("pressEnter")}
|
||||
/>
|
||||
</div>
|
||||
<Button className="bg-blue-500 text-white p-2 w-fit rounded-lg" onClick={() => shareToEmail()}>
|
||||
<Button
|
||||
className="bg-blue-500 text-white p-2 w-fit rounded-lg"
|
||||
onClick={() => shareToEmail()}
|
||||
>
|
||||
{t("send")}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -655,8 +819,15 @@ const DetailInfo = () => {
|
|||
<div className="flex flex-col my-16 p-4 lg:p-10 bg-[#f7f7f7] dark:bg-slate-600">
|
||||
<div className="gap-5 flex flex-col px-4 lg:px-14">
|
||||
<p className="flex items-start text-lg">{t("comment")}</p>
|
||||
<Textarea placeholder={t("leaveComment")} className="flex w-full pb-12" onChange={getInputValue} />
|
||||
<button onClick={() => postData()} className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-4 py-1">
|
||||
<Textarea
|
||||
placeholder={t("leaveComment")}
|
||||
className="flex w-full pb-12"
|
||||
onChange={getInputValue}
|
||||
/>
|
||||
<button
|
||||
onClick={() => postData()}
|
||||
className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-4 py-1"
|
||||
>
|
||||
{t("send")}
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -667,13 +838,29 @@ const DetailInfo = () => {
|
|||
{listSuggestion?.map((data: any) => (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row mt-2 px-4 lg:px-14">
|
||||
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={1080} height={1080} src={data?.suggestionFrom?.profilePictureUrl} className="h-12 lg:h-16 w-12 lg:w-16 mr-2" onError={addDefaultProfile} alt="" />
|
||||
<Image
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||
shimmer(700, 475)
|
||||
)}`}
|
||||
width={1080}
|
||||
height={1080}
|
||||
src={data?.suggestionFrom?.profilePictureUrl}
|
||||
className="h-12 lg:h-16 w-12 lg:w-16 mr-2"
|
||||
onError={addDefaultProfile}
|
||||
alt=""
|
||||
/>
|
||||
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
|
||||
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
|
||||
{Number(data.suggestionFrom?.roleId) == 2 || Number(data.suggestionFrom?.roleId) == 3 || Number(data.suggestionFrom?.roleId) == 4 ? "HUMAS POLRI" : data.suggestionFrom?.fullname}
|
||||
{Number(data.suggestionFrom?.roleId) == 2 ||
|
||||
Number(data.suggestionFrom?.roleId) == 3 ||
|
||||
Number(data.suggestionFrom?.roleId) == 4
|
||||
? "HUMAS POLRI"
|
||||
: data.suggestionFrom?.fullname}
|
||||
{getPublicLocaleTimestamp(new Date(data.createdAt))}
|
||||
</p>
|
||||
<p className="text-slate-500 text-[13px] lg:text-sm mb-4">{data?.message}</p>
|
||||
<p className="text-slate-500 text-[13px] lg:text-sm mb-4">
|
||||
{data?.message}
|
||||
</p>
|
||||
<div>
|
||||
<a
|
||||
style={
|
||||
|
|
@ -686,11 +873,16 @@ const DetailInfo = () => {
|
|||
onClick={() => showInput(`comment-id-${data.id}`)}
|
||||
className="mr-2"
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("reply")}</small>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
|
||||
{t("reply")}
|
||||
</small>
|
||||
</a>
|
||||
{Number(data.suggestionFrom?.id) == Number(userId) || Number(userRoleId) == 2 ? (
|
||||
{Number(data.suggestionFrom?.id) == Number(userId) ||
|
||||
Number(userRoleId) == 2 ? (
|
||||
<a onClick={() => deleteData(data.id)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("delete")}</small>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
|
||||
{t("delete")}
|
||||
</small>
|
||||
</a>
|
||||
) : (
|
||||
""
|
||||
|
|
@ -699,14 +891,25 @@ const DetailInfo = () => {
|
|||
</div>
|
||||
</div>
|
||||
{visibleInput === `comment-id-${data.id}` && (
|
||||
<div id={`comment-id-${data.id}`} className="px-4 pl-[72px] lg:px-14 lg:pl-32 mt-2 ">
|
||||
<Textarea id={`input-comment-${data.id}`} className="p-4 focus:outline-none focus:border-sky-500" placeholder={t("enterReply")} />
|
||||
<div
|
||||
id={`comment-id-${data.id}`}
|
||||
className="px-4 pl-[72px] lg:px-14 lg:pl-32 mt-2 "
|
||||
>
|
||||
<Textarea
|
||||
id={`input-comment-${data.id}`}
|
||||
className="p-4 focus:outline-none focus:border-sky-500"
|
||||
placeholder={t("enterReply")}
|
||||
/>
|
||||
<div className="flex flex-row gap-3">
|
||||
<a onClick={() => postDataChild(data.id)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 mt-2 cursor-pointer">{t("send")}</small>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 mt-2 cursor-pointer">
|
||||
{t("send")}
|
||||
</small>
|
||||
</a>
|
||||
<a onClick={() => showInput(`comment-id-${data.id}`)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg mt-2 w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("cancel")}</small>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg mt-2 w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
|
||||
{t("cancel")}
|
||||
</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -716,30 +919,56 @@ const DetailInfo = () => {
|
|||
? data.children?.map((child1: any) => (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-16 lg:pl-32">
|
||||
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={1080} height={1080} src={child1.suggestionFrom?.profilePictureUrl} onError={addDefaultProfile} alt="" className="h-10 lg:h-16 w-10 lg:w-16 mr-2" />
|
||||
<Image
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||
shimmer(700, 475)
|
||||
)}`}
|
||||
width={1080}
|
||||
height={1080}
|
||||
src={child1.suggestionFrom?.profilePictureUrl}
|
||||
onError={addDefaultProfile}
|
||||
alt=""
|
||||
className="h-10 lg:h-16 w-10 lg:w-16 mr-2"
|
||||
/>
|
||||
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
|
||||
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
|
||||
{" "}
|
||||
<b>{Number(child1.suggestionFrom?.roleId) == 2 || Number(child1.suggestionFrom?.roleId) == 3 || Number(child1.suggestionFrom?.roleId) == 4 ? "HUMAS POLRI" : child1.suggestionFrom?.fullname}</b>{" "}
|
||||
{getPublicLocaleTimestamp(new Date(child1.createdAt))}
|
||||
<b>
|
||||
{Number(child1.suggestionFrom?.roleId) == 2 ||
|
||||
Number(child1.suggestionFrom?.roleId) == 3 ||
|
||||
Number(child1.suggestionFrom?.roleId) == 4
|
||||
? "HUMAS POLRI"
|
||||
: child1.suggestionFrom?.fullname}
|
||||
</b>{" "}
|
||||
{getPublicLocaleTimestamp(
|
||||
new Date(child1.createdAt)
|
||||
)}
|
||||
</p>
|
||||
<p className="text-slate-500 text-[13px] lg:text-sm mb-4">
|
||||
{parse(String(child1?.message))}
|
||||
</p>
|
||||
<p className="text-slate-500 text-[13px] lg:text-sm mb-4">{parse(String(child1?.message))}</p>
|
||||
<div>
|
||||
<a
|
||||
style={
|
||||
Number(child1.suggestionFrom?.id) == Number(userId)
|
||||
Number(child1.suggestionFrom?.id) ==
|
||||
Number(userId)
|
||||
? {
|
||||
display: "none",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
onClick={() => showInput(`comment-id-${child1.id}`)}
|
||||
onClick={() =>
|
||||
showInput(`comment-id-${child1.id}`)
|
||||
}
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("reply")}</small>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
|
||||
{t("reply")}
|
||||
</small>
|
||||
</a>
|
||||
<a
|
||||
style={
|
||||
Number(child1.suggestionFrom?.id) == Number(userId)
|
||||
Number(child1.suggestionFrom?.id) ==
|
||||
Number(userId)
|
||||
? {}
|
||||
: {
|
||||
display: "none",
|
||||
|
|
@ -747,21 +976,39 @@ const DetailInfo = () => {
|
|||
}
|
||||
onClick={() => deleteData(child1.id)}
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("delete")}</small>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
|
||||
{t("delete")}
|
||||
</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{visibleInput === `comment-id-${child1.id}` && (
|
||||
<div id={`comment-id-${child1.id}`} className="px-4 lg:px-14 pl-28 lg:pl-[200px]">
|
||||
<Textarea name="" className="mt-2 " id={`input-comment-${child1.id}`} placeholder={t("enterReply")} />
|
||||
<div
|
||||
id={`comment-id-${child1.id}`}
|
||||
className="px-4 lg:px-14 pl-28 lg:pl-[200px]"
|
||||
>
|
||||
<Textarea
|
||||
name=""
|
||||
className="mt-2 "
|
||||
id={`input-comment-${child1.id}`}
|
||||
placeholder={t("enterReply")}
|
||||
/>
|
||||
<div className="flex flex-row mt-2 gap-3">
|
||||
<a onClick={() => postDataChild(child1.id)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("send")}</small>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
|
||||
{t("send")}
|
||||
</small>
|
||||
</a>
|
||||
<a onClick={() => showInput(`comment-id-${child1.id}`)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("cancel")}</small>
|
||||
<a
|
||||
onClick={() =>
|
||||
showInput(`comment-id-${child1.id}`)
|
||||
}
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
|
||||
{t("cancel")}
|
||||
</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -771,30 +1018,62 @@ const DetailInfo = () => {
|
|||
? child1.children?.map((child2: any) => (
|
||||
<div className="">
|
||||
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-28 lg:pl-48">
|
||||
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={1080} height={1080} src={child2.suggestionFrom?.profilePictureUrl} className="h-9 lg:h-16 w-9 lg:w-16 mr-2" onError={addDefaultProfile} alt="" />
|
||||
<Image
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||
shimmer(700, 475)
|
||||
)}`}
|
||||
width={1080}
|
||||
height={1080}
|
||||
src={
|
||||
child2.suggestionFrom?.profilePictureUrl
|
||||
}
|
||||
className="h-9 lg:h-16 w-9 lg:w-16 mr-2"
|
||||
onError={addDefaultProfile}
|
||||
alt=""
|
||||
/>
|
||||
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
|
||||
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
|
||||
{" "}
|
||||
<b>{Number(child2.suggestionFrom?.roleId) == 2 || Number(child2.suggestionFrom?.roleId) == 3 || Number(child2.suggestionFrom?.roleId) == 4 ? "HUMAS POLRI" : child2.suggestionFrom?.fullname}</b>{" "}
|
||||
{getPublicLocaleTimestamp(new Date(child2.createdAt))}
|
||||
<b>
|
||||
{Number(
|
||||
child2.suggestionFrom?.roleId
|
||||
) == 2 ||
|
||||
Number(child2.suggestionFrom?.roleId) ==
|
||||
3 ||
|
||||
Number(child2.suggestionFrom?.roleId) ==
|
||||
4
|
||||
? "HUMAS POLRI"
|
||||
: child2.suggestionFrom?.fullname}
|
||||
</b>{" "}
|
||||
{getPublicLocaleTimestamp(
|
||||
new Date(child2.createdAt)
|
||||
)}
|
||||
</p>
|
||||
<p className="text-slate-500 text-sm mb-4">
|
||||
{parse(String(child2?.message))}
|
||||
</p>
|
||||
<p className="text-slate-500 text-sm mb-4">{parse(String(child2?.message))}</p>
|
||||
<div>
|
||||
<a
|
||||
style={
|
||||
Number(child2.suggestionFrom?.id) == Number(userId)
|
||||
Number(child2.suggestionFrom?.id) ==
|
||||
Number(userId)
|
||||
? {
|
||||
display: "none",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
onClick={() => showInput(`comment-id-${child2.id}`)}
|
||||
onClick={() =>
|
||||
showInput(`comment-id-${child2.id}`)
|
||||
}
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("reply")}</small>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
|
||||
{t("reply")}
|
||||
</small>
|
||||
</a>
|
||||
<a
|
||||
style={
|
||||
Number(child2.suggestionFrom?.id) == Number(userId)
|
||||
Number(child2.suggestionFrom?.id) ==
|
||||
Number(userId)
|
||||
? {}
|
||||
: {
|
||||
display: "none",
|
||||
|
|
@ -802,20 +1081,40 @@ const DetailInfo = () => {
|
|||
}
|
||||
onClick={() => deleteData(child2.id)}
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("delete")}</small>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
|
||||
{t("delete")}
|
||||
</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{visibleInput === `comment-id-${child2.id}` && (
|
||||
<div id={`comment-id-${child2.id}`} className="px-4 lg:px-14 pl-40 lg:pl-[265px]">
|
||||
<Textarea name="" id={`input-comment-${child2.id}`} className="my-2" placeholder="Masukkan balasan anda" />
|
||||
<div
|
||||
id={`comment-id-${child2.id}`}
|
||||
className="px-4 lg:px-14 pl-40 lg:pl-[265px]"
|
||||
>
|
||||
<Textarea
|
||||
name=""
|
||||
id={`input-comment-${child2.id}`}
|
||||
className="my-2"
|
||||
placeholder="Masukkan balasan anda"
|
||||
/>
|
||||
<div className="flex flex-row gap-3">
|
||||
<a onClick={() => postDataChild(child2.id)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("send")}</small>
|
||||
<a
|
||||
onClick={() => postDataChild(child2.id)}
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
|
||||
{t("send")}
|
||||
</small>
|
||||
</a>
|
||||
<a onClick={() => showInput(`comment-id-${child2.id}`)}>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("cancel")}</small>
|
||||
<a
|
||||
onClick={() =>
|
||||
showInput(`comment-id-${child2.id}`)
|
||||
}
|
||||
>
|
||||
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
|
||||
{t("cancel")}
|
||||
</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,10 +3,30 @@ import React, { useEffect, useState } from "react";
|
|||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { formatDateToIndonesian, getOnlyDate, getOnlyMonthAndYear } from "@/utils/globals";
|
||||
import {
|
||||
formatDateToIndonesian,
|
||||
getOnlyDate,
|
||||
getOnlyMonthAndYear,
|
||||
} from "@/utils/globals";
|
||||
import { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||
import { getUserLevelListByParent, listCategory, listData, listDataRegional } from "@/service/landing/landing";
|
||||
import { ColumnDef, ColumnFiltersState, PaginationState, SortingState, VisibilityState, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
|
||||
import {
|
||||
getUserLevelListByParent,
|
||||
listCategory,
|
||||
listData,
|
||||
listDataRegional,
|
||||
} from "@/service/landing/landing";
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
PaginationState,
|
||||
SortingState,
|
||||
VisibilityState,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import LandingPagination from "@/components/landing-page/pagination";
|
||||
import { Reveal } from "@/components/landing-page/Reveal";
|
||||
import { Link, useRouter } from "@/i18n/routing";
|
||||
|
|
@ -38,8 +58,11 @@ const FilterPage = () => {
|
|||
const [totalData, setTotalData] = React.useState<number>(1);
|
||||
const [totalPage, setTotalPage] = React.useState<number>(1);
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);
|
||||
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
[]
|
||||
);
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
|
|
@ -60,7 +83,9 @@ const FilterPage = () => {
|
|||
const [categoryFilter, setCategoryFilter] = useState<any>([]);
|
||||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(sortBy === "popular" ? "clickCount" : "createdAt");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "clickCount" : "createdAt"
|
||||
);
|
||||
const isRegional = asPath?.includes("regional");
|
||||
const isSatker = asPath?.includes("satker");
|
||||
const [formatFilter, setFormatFilter] = useState<any>([]);
|
||||
|
|
@ -73,6 +98,7 @@ const FilterPage = () => {
|
|||
const [categories, setCategories] = useState([]);
|
||||
const [userLevels, setUserLevels] = useState([]);
|
||||
const t = useTranslations("FilterPage");
|
||||
const [isFilterOpen, setIsFilterOpen] = useState(true);
|
||||
|
||||
// const [startDate, endDate] = dateRange;
|
||||
|
||||
|
|
@ -105,8 +131,14 @@ const FilterPage = () => {
|
|||
|
||||
useEffect(() => {
|
||||
if (categorie) {
|
||||
setCategoryFilter(categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]);
|
||||
console.log("Kategori", categorie, categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]);
|
||||
setCategoryFilter(
|
||||
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]
|
||||
);
|
||||
console.log(
|
||||
"Kategori",
|
||||
categorie,
|
||||
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]
|
||||
);
|
||||
}
|
||||
}, [categorie]);
|
||||
|
||||
|
|
@ -124,7 +156,19 @@ const FilterPage = () => {
|
|||
}
|
||||
console.log(monthYearFilter, "monthFilter");
|
||||
initState();
|
||||
}, [change, asPath, monthYearFilter, page, sortBy, sortByOpt, title, startDateString, endDateString, categorie, formatFilter]);
|
||||
}, [
|
||||
change,
|
||||
asPath,
|
||||
monthYearFilter,
|
||||
page,
|
||||
sortBy,
|
||||
sortByOpt,
|
||||
title,
|
||||
startDateString,
|
||||
endDateString,
|
||||
categorie,
|
||||
formatFilter,
|
||||
]);
|
||||
|
||||
async function getCategories() {
|
||||
const category = await listCategory("1");
|
||||
|
|
@ -147,7 +191,10 @@ const FilterPage = () => {
|
|||
async function getDataAll() {
|
||||
if (asPath?.includes("/polda/") == true) {
|
||||
if (asPath?.split("/")[2] !== "[polda_name]") {
|
||||
const filter = categoryFilter?.length > 0 ? categoryFilter?.sort().join(",") : categorie || "";
|
||||
const filter =
|
||||
categoryFilter?.length > 0
|
||||
? categoryFilter?.sort().join(",")
|
||||
: categorie || "";
|
||||
|
||||
const name = title == undefined ? "" : title;
|
||||
const format = formatFilter == undefined ? "" : formatFilter?.join(",");
|
||||
|
|
@ -165,8 +212,14 @@ const FilterPage = () => {
|
|||
filterGroup,
|
||||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") : "",
|
||||
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : "",
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)
|
||||
?.split("/")[0]
|
||||
?.replace("", "")
|
||||
: "",
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
|
||||
: "",
|
||||
locale == "en" ? true : false
|
||||
);
|
||||
close();
|
||||
|
|
@ -181,7 +234,10 @@ const FilterPage = () => {
|
|||
setTotalContent(response?.data?.data?.totalElements);
|
||||
}
|
||||
} else {
|
||||
const filter = categoryFilter?.length > 0 ? categoryFilter?.sort().join(",") : categorie || "";
|
||||
const filter =
|
||||
categoryFilter?.length > 0
|
||||
? categoryFilter?.sort().join(",")
|
||||
: categorie || "";
|
||||
|
||||
const name = title == undefined ? "" : title;
|
||||
const format = formatFilter == undefined ? "" : formatFilter?.join(",");
|
||||
|
|
@ -198,8 +254,12 @@ const FilterPage = () => {
|
|||
"",
|
||||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") : "",
|
||||
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : "",
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "")
|
||||
: "",
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
|
||||
: "",
|
||||
locale == "en" ? true : false
|
||||
);
|
||||
close();
|
||||
|
|
@ -250,7 +310,10 @@ const FilterPage = () => {
|
|||
};
|
||||
|
||||
async function getDataRegional() {
|
||||
const filter = categoryFilter?.length > 0 ? categoryFilter?.sort().join(",") : categorie || "";
|
||||
const filter =
|
||||
categoryFilter?.length > 0
|
||||
? categoryFilter?.sort().join(",")
|
||||
: categorie || "";
|
||||
|
||||
const name = title == undefined ? "" : title;
|
||||
const format = formatFilter == undefined ? "" : formatFilter?.join(",");
|
||||
|
|
@ -263,8 +326,12 @@ const FilterPage = () => {
|
|||
"",
|
||||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") : "",
|
||||
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : "",
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "")
|
||||
: "",
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
|
||||
: "",
|
||||
12,
|
||||
pages,
|
||||
sortByOpt
|
||||
|
|
@ -381,16 +448,20 @@ const FilterPage = () => {
|
|||
<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="flex flex-col">
|
||||
{/* Header */}
|
||||
|
||||
<div className="flex flex-col md:flex-row items-start gap-5 p-10 bg-[#f7f7f7] dark:bg-black">
|
||||
<div className="flex flex-row md:flex-row items-start gap-3 p-10 bg-[#f7f7f7] dark:bg-black">
|
||||
<p> {t("image")}</p>
|
||||
{">"}
|
||||
<p>
|
||||
{" "}
|
||||
{t("image")} {">"} <span className="font-bold">{t("allImage")}</span>
|
||||
<span className="font-bold">{t("allImage")}</span>
|
||||
</p>
|
||||
<p className="font-bold">|</p>
|
||||
<p>{`${t("thereIs")} ${totalContent} ${t("downloadableImage")}`}</p>
|
||||
|
|
@ -398,106 +469,182 @@ const FilterPage = () => {
|
|||
|
||||
{/* Left */}
|
||||
<div className="flex flex-col lg:flex-row gap-6 p-4">
|
||||
<div className="lg:w-[25%] h-fit w-full bg-[#f7f7f7] dark:bg-black p-4 rounded-lg shadow-md">
|
||||
<h2 className="text-lg font-semibold mb-4 flex items-center gap-1">
|
||||
<Icon icon="stash:filter-light" fontSize={30} />
|
||||
Filter
|
||||
</h2>
|
||||
<div className="border-t border-black my-4 dark:border-white"></div>
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<label htmlFor="search" className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("search")}
|
||||
</label>
|
||||
<Input
|
||||
value={searchTitle}
|
||||
onChange={(e) => setSearchTitle(e.target.value)}
|
||||
onKeyUp={handleKeyUp}
|
||||
onKeyDown={handleKeyDown}
|
||||
type="text"
|
||||
id="search"
|
||||
placeholder={t("searchTitle")}
|
||||
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="lg:hidden flex justify-end mb-2">
|
||||
<button
|
||||
onClick={() => setIsFilterOpen(!isFilterOpen)}
|
||||
className="text-sm text-white bg-[#bb3523] px-4 py-1 rounded-md shadow"
|
||||
>
|
||||
{isFilterOpen ? "Hide Filter" : "Show Filter"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-white">{t("monthYear")}</label>
|
||||
<ReactDatePicker
|
||||
selected={monthYearFilter}
|
||||
className="mt-1 w-full text-xs border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
||||
onChange={(date) => setMonthYearFilter(date)}
|
||||
dateFormat="MM | yyyy"
|
||||
placeholderText={t("selectYear")}
|
||||
showMonthYearPicker
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-white">{t("date")}</label>
|
||||
<div className="flex flex-row justify justify-between gap-2">
|
||||
<ReactDatePicker
|
||||
selectsRange
|
||||
className="mt-1 w-full border text-sm rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
||||
startDate={dateRange[0]}
|
||||
endDate={dateRange[1]}
|
||||
onChange={(update) => {
|
||||
setDateRange(update);
|
||||
}}
|
||||
placeholderText={t("selectDate")}
|
||||
onCalendarClose={() => setCalenderState(!calenderState)}
|
||||
{isFilterOpen && (
|
||||
<div className="lg:w-[25%] h-fit w-full bg-[#f7f7f7] dark:bg-black p-4 rounded-lg shadow-md">
|
||||
<h2 className="text-lg font-semibold mb-4 flex items-center gap-1">
|
||||
<Icon icon="stash:filter-light" fontSize={30} />
|
||||
Filter
|
||||
</h2>
|
||||
<div className="border-t border-black my-4 dark:border-white"></div>
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="search"
|
||||
className="block text-sm font-medium text-gray-700 dark:text-white"
|
||||
>
|
||||
{t("search")}
|
||||
</label>
|
||||
<Input
|
||||
value={searchTitle}
|
||||
onChange={(e) => setSearchTitle(e.target.value)}
|
||||
onKeyUp={handleKeyUp}
|
||||
onKeyDown={handleKeyDown}
|
||||
type="text"
|
||||
id="search"
|
||||
placeholder={t("searchTitle")}
|
||||
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
||||
/>
|
||||
<div className="flex items-center">{handleClose ? <Icon icon="carbon:close-filled" onClick={handleDeleteDate} width="20" inline color="#216ba5" /> : ""}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">{t("categories")}</h3>
|
||||
<ul className="mt-2 space-y-2">
|
||||
{categories.map((category: any) => (
|
||||
<li key={category?.id}>
|
||||
<label className="inline-flex items-center" htmlFor={`${category.id}`}>
|
||||
<Checkbox id={`${category.id}`} value={category.id} checked={categoryFilter.includes(String(category.id))} onCheckedChange={(e) => handleCategoryFilter(Boolean(e), category.id)} />
|
||||
<span className="ml-2 text-gray-700 dark:text-white">{category?.name}</span>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("monthYear")}
|
||||
</label>
|
||||
<ReactDatePicker
|
||||
selected={monthYearFilter}
|
||||
className="mt-1 w-full text-xs border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
||||
onChange={(date) => setMonthYearFilter(date)}
|
||||
dateFormat="MM | yyyy"
|
||||
placeholderText={t("selectYear")}
|
||||
showMonthYearPicker
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("date")}
|
||||
</label>
|
||||
<div className="flex flex-row justify justify-between gap-2">
|
||||
<ReactDatePicker
|
||||
selectsRange
|
||||
className="mt-1 w-full border text-sm rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
||||
startDate={dateRange[0]}
|
||||
endDate={dateRange[1]}
|
||||
onChange={(update) => {
|
||||
setDateRange(update);
|
||||
}}
|
||||
placeholderText={t("selectDate")}
|
||||
onCalendarClose={() => setCalenderState(!calenderState)}
|
||||
/>
|
||||
<div className="flex items-center">
|
||||
{handleClose ? (
|
||||
<Icon
|
||||
icon="carbon:close-filled"
|
||||
onClick={handleDeleteDate}
|
||||
width="20"
|
||||
inline
|
||||
color="#216ba5"
|
||||
/>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("categories")}
|
||||
</h3>
|
||||
<ul className="mt-2 space-y-2">
|
||||
{categories.map((category: any) => (
|
||||
<li key={category?.id}>
|
||||
<label
|
||||
className="inline-flex items-center"
|
||||
htmlFor={`${category.id}`}
|
||||
>
|
||||
<Checkbox
|
||||
id={`${category.id}`}
|
||||
value={category.id}
|
||||
checked={categoryFilter.includes(String(category.id))}
|
||||
onCheckedChange={(e) =>
|
||||
handleCategoryFilter(Boolean(e), category.id)
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
{category?.name}
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
{/* Garis */}
|
||||
<div className="border-t border-black my-4 dark:border-white"></div>
|
||||
{/* Garis */}
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
||||
Format
|
||||
</h3>
|
||||
<ul className="mt-2 space-y-2">
|
||||
<li>
|
||||
<label className="inline-flex items-center">
|
||||
<Checkbox
|
||||
id="png"
|
||||
value="png"
|
||||
checked={formatFilter.includes("png")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "png")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
PNG
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
{/* Garis */}
|
||||
<div className="border-t border-black my-4 dark:border-white"></div>
|
||||
{/* Garis */}
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">Format</h3>
|
||||
<ul className="mt-2 space-y-2">
|
||||
<li>
|
||||
<label className="inline-flex items-center">
|
||||
<Checkbox id="png" value="png" checked={formatFilter.includes("png")} onCheckedChange={(e) => handleFormatFilter(Boolean(e), "png")} />
|
||||
<span className="ml-2 text-gray-700 dark:text-white">PNG</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label className="inline-flex items-center">
|
||||
<Checkbox id="jpeg" value="jpeg" checked={formatFilter.includes("jpeg")} onCheckedChange={(e) => handleFormatFilter(Boolean(e), "jpeg")} />
|
||||
<span className="ml-2 text-gray-700 dark:text-white">JPEG</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label className="inline-flex items-center">
|
||||
<Checkbox id="jpg" value="jpg" checked={formatFilter.includes("jpg")} onCheckedChange={(e) => handleFormatFilter(Boolean(e), "jpg")} />
|
||||
<span className="ml-2 text-gray-700 dark:text-white">JPG</span>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="border-t border-black dark:border-white my-4"></div>
|
||||
<div className="text-center">
|
||||
<a onClick={cleanCheckbox} className="text-[#bb3523] cursor-pointer">
|
||||
<b>Reset Filter</b>
|
||||
</a>
|
||||
<li>
|
||||
<label className="inline-flex items-center">
|
||||
<Checkbox
|
||||
id="jpeg"
|
||||
value="jpeg"
|
||||
checked={formatFilter.includes("jpeg")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "jpeg")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
JPEG
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label className="inline-flex items-center">
|
||||
<Checkbox
|
||||
id="jpg"
|
||||
value="jpg"
|
||||
checked={formatFilter.includes("jpg")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "jpg")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
JPG
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="border-t border-black dark:border-white my-4"></div>
|
||||
<div className="text-center">
|
||||
<a
|
||||
onClick={cleanCheckbox}
|
||||
className="text-[#bb3523] cursor-pointer"
|
||||
>
|
||||
<b>Reset Filter</b>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Right */}
|
||||
<div className="w-full lg:w-[75%]">
|
||||
|
|
@ -505,7 +652,11 @@ const FilterPage = () => {
|
|||
<div className="w-full">
|
||||
<div className="flex flex-col items-end mb-4">
|
||||
<h2 className="text-lg font-semibold">{t("sortBy")}</h2>
|
||||
<select defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"} onChange={(e) => handleSorting(e)} className="border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500">
|
||||
<select
|
||||
defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"}
|
||||
onChange={(e) => handleSorting(e)}
|
||||
className="border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
||||
>
|
||||
<option value="latest">{t("latest")}</option>
|
||||
<option value="popular">{t("mostPopular")}</option>
|
||||
</select>
|
||||
|
|
@ -529,24 +680,50 @@ const FilterPage = () => {
|
|||
{imageData?.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{imageData?.map((image: any) => (
|
||||
<Card key={image?.id} className="hover:scale-105 transition-transform duration-300">
|
||||
<Card
|
||||
key={image?.id}
|
||||
className="hover:scale-105 transition-transform duration-300"
|
||||
>
|
||||
<CardContent className="flex flex-col text-xs lg:text-sm w-full p-0">
|
||||
<Link href={`/image/detail/${image?.slug}`}>
|
||||
{/* <img src={image?.thumbnailLink} className="h-60 object-cover items-center justify-center cursor-pointer rounded-lg" /> */}
|
||||
<div className="img-container h-60 bg-[#e9e9e9] cursor-pointer rounded-lg">
|
||||
<ImageBlurry src={image?.thumbnailLink} alt={image?.title} style={{ objectFit: "contain", width: "100%", height: "100%" }} />
|
||||
<ImageBlurry
|
||||
src={image?.thumbnailLink}
|
||||
alt={image?.title}
|
||||
style={{
|
||||
objectFit: "contain",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-2 text-[10px] mx-2 mt-2">
|
||||
{formatDateToIndonesian(new Date(image?.createdAt))} {image?.timezone ? image?.timezone : "WIB"}| <Icon icon="formkit:eye" width="15" height="15" />
|
||||
{formatDateToIndonesian(
|
||||
new Date(image?.createdAt)
|
||||
)}{" "}
|
||||
{image?.timezone ? image?.timezone : "WIB"}|{" "}
|
||||
<Icon
|
||||
icon="formkit:eye"
|
||||
width="15"
|
||||
height="15"
|
||||
/>
|
||||
{image?.clickCount}{" "}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 20 20">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fill="#f00"
|
||||
d="M7.707 10.293a1 1 0 1 0-1.414 1.414l3 3a1 1 0 0 0 1.414 0l3-3a1 1 0 0 0-1.414-1.414L11 11.586V6h5a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h5v5.586zM9 4a1 1 0 0 1 2 0v2H9z"
|
||||
/>
|
||||
</svg>{" "}
|
||||
</div>
|
||||
<div className="font-semibold pr-3 pb-3 mx-2 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible w-full">{image?.title}</div>
|
||||
<div className="font-semibold pr-3 pb-3 mx-2 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible w-full">
|
||||
{image?.title}
|
||||
</div>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
@ -554,12 +731,24 @@ const FilterPage = () => {
|
|||
</div>
|
||||
) : (
|
||||
<p className="flex items-center justify-center text-black">
|
||||
<Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-60 w-60 my-4" />
|
||||
<Image
|
||||
width={1920}
|
||||
height={1080}
|
||||
src="/assets/empty-data.png"
|
||||
alt="empty"
|
||||
className="h-60 w-60 my-4"
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{totalData > 1 && <LandingPagination table={table} totalData={totalData} totalPage={totalPage} />}
|
||||
{totalData > 1 && (
|
||||
<LandingPagination
|
||||
table={table}
|
||||
totalData={totalData}
|
||||
totalPage={totalPage}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Reveal>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,9 +2,22 @@
|
|||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "@/components/ui/carousel";
|
||||
import { Link, useRouter } from "@/i18n/routing";
|
||||
import { deleteBlogComments, getBlogComments, getDetailIndeks, getPublicSuggestionList, postBlogComments, publicDetailBlog } from "@/service/landing/landing";
|
||||
import {
|
||||
deleteBlogComments,
|
||||
getBlogComments,
|
||||
getDetailIndeks,
|
||||
getPublicSuggestionList,
|
||||
postBlogComments,
|
||||
publicDetailBlog,
|
||||
} from "@/service/landing/landing";
|
||||
import { formatDateToIndonesian } from "@/utils/globals";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
|
|
@ -94,7 +107,9 @@ const IndeksDetail = () => {
|
|||
// }
|
||||
|
||||
async function sendCommentChild(parentId: any) {
|
||||
const inputMsg = document.querySelector(`#input-comment-${parentId}`) as HTMLInputElement;
|
||||
const inputMsg = document.querySelector(
|
||||
`#input-comment-${parentId}`
|
||||
) as HTMLInputElement;
|
||||
|
||||
if (inputMsg && inputMsg.value.length > 3) {
|
||||
loading();
|
||||
|
|
@ -166,32 +181,54 @@ const IndeksDetail = () => {
|
|||
<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="p-4 lg:px-60 lg:p-12">
|
||||
<div className="p-4 lg:px-28 lg:p-12">
|
||||
{/* Judul */}
|
||||
<div className="flex flex-col mb-5">
|
||||
<h1 className="text-base lg:text-lg mb-2">Index / Detail</h1>
|
||||
<h1 className="flex flex-row font-bold text-center text-lg lg:text-2xl">{indeksData?.title}</h1>
|
||||
<h1 className="flex flex-row font-bold text-center text-lg lg:text-2xl">
|
||||
{indeksData?.title}
|
||||
</h1>
|
||||
</div>
|
||||
{/* Gambar Utama */}
|
||||
<div className="flex items-center justify-center">
|
||||
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={2560} height={1440} src={indeksData?.thumbnailLink} alt="Main" className="h-fit lg:h-[550px] w-full rounded-lg" />
|
||||
<Image
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||
shimmer(700, 475)
|
||||
)}`}
|
||||
width={2560}
|
||||
height={1440}
|
||||
src={indeksData?.thumbnailLink}
|
||||
alt="Main"
|
||||
className="w-full max-h-[550px] object-contain rounded-xl "
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Footer Informasi */}
|
||||
<div className="text-gray-500 flex border-t mt-4">
|
||||
<div className="flex mt-2">
|
||||
<p className="text-xs lg:text-sm mb-2 ">
|
||||
{t("by")} <span className="font-semibold text-gray-500">{indeksData?.uploaderName}</span> | {t("updatedOn")} {indeksData?.createdAt} WIB
|
||||
{t("by")}
|
||||
<span className="font-semibold text-gray-500">
|
||||
{indeksData?.uploaderName}
|
||||
</span>
|
||||
| {t("updatedOn")} {indeksData?.createdAt} WIB
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Keterangan */}
|
||||
<div className="w-auto">
|
||||
<p className="font-light text-base lg:text-lg text-justify" dangerouslySetInnerHTML={{ __html: indeksData?.description }} />
|
||||
<p
|
||||
className="font-light text-base lg:text-lg text-justify"
|
||||
dangerouslySetInnerHTML={{ __html: indeksData?.description }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -199,9 +236,19 @@ const IndeksDetail = () => {
|
|||
<div className="w-full">
|
||||
<div className="flex flex-col py-5 p-0 lg:p-10 bg-[#f7f7f7] dark:bg-slate-600">
|
||||
<div className="gap-5 flex flex-col px-4 lg:px-16">
|
||||
<p className="flex items-start text-bases lg:text-lg">{t("comment")}</p>
|
||||
<Textarea placeholder="Type your comments here." className="flex w-full" onChange={getInputValue} value={message} />
|
||||
<button className="flex items-start bg-[#bb3523] text-white rounded-lg text-sm lg:text-base w-fit px-3 lg:px-4 py-1" onClick={() => postData()}>
|
||||
<p className="flex items-start text-bases lg:text-lg">
|
||||
{t("comment")}
|
||||
</p>
|
||||
<Textarea
|
||||
placeholder="Type your comments here."
|
||||
className="flex w-full"
|
||||
onChange={getInputValue}
|
||||
value={message}
|
||||
/>
|
||||
<button
|
||||
className="flex items-start bg-[#bb3523] text-white rounded-lg text-sm lg:text-base w-fit px-3 lg:px-4 py-1"
|
||||
onClick={() => postData()}
|
||||
>
|
||||
{t("send")}
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -212,15 +259,40 @@ const IndeksDetail = () => {
|
|||
{listComments?.map((data: any) => (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row mt-2 px-4 lg:px-14">
|
||||
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={512} height={512} className="h-10 lg:h-20 w-10 lg:w-20" src="/assets/img/user-avatar-yellow.svg" alt="#" />
|
||||
<Image
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||
shimmer(700, 475)
|
||||
)}`}
|
||||
width={512}
|
||||
height={512}
|
||||
className="h-10 lg:h-20 w-10 lg:w-20"
|
||||
src="/assets/img/user-avatar-yellow.svg"
|
||||
alt="#"
|
||||
/>
|
||||
<div className="border border-slate-300 w-full p-4 bg-white gap-1">
|
||||
<p className="flex justify-between text-sm text-slate-500 lg:text-base border-b-2 border-slate-200 mb-2">
|
||||
<b>{Number(data.commentFrom?.roleId) == 2 || Number(data.commentFrom?.roleId) == 3 || Number(data.commentFrom?.roleId) == 4 ? "HUMAS POLRI" : data.commentFrom?.fullname}</b>
|
||||
{`${new Date(data.createdAt).getDate()}/${new Date(data.createdAt).getMonth() + 1}/${new Date(data.createdAt).getFullYear()} ${new Date(data.createdAt).getHours()}:${new Date(data.createdAt).getMinutes()}`}
|
||||
<b>
|
||||
{Number(data.commentFrom?.roleId) == 2 ||
|
||||
Number(data.commentFrom?.roleId) == 3 ||
|
||||
Number(data.commentFrom?.roleId) == 4
|
||||
? "HUMAS POLRI"
|
||||
: data.commentFrom?.fullname}
|
||||
</b>
|
||||
{`${new Date(data.createdAt).getDate()}/${
|
||||
new Date(data.createdAt).getMonth() + 1
|
||||
}/${new Date(data.createdAt).getFullYear()} ${new Date(
|
||||
data.createdAt
|
||||
).getHours()}:${new Date(data.createdAt).getMinutes()}`}
|
||||
</p>
|
||||
<p className="text-slate-500 text-sm lg:text-base mb-4">
|
||||
{data.message}
|
||||
</p>
|
||||
<p className="text-slate-500 text-sm lg:text-base mb-4">{data.message}</p>
|
||||
<div className="gap-3">
|
||||
<a href="javascript:void(0)" className="text-xs lg:text-sm mr-2 bg-blue-500 text-white py-1 px-2 hover:bg-blue-300 hover:text-black rounded-md" onClick={() => showInput(`comment-id-${data.id}`)}>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
className="text-xs lg:text-sm mr-2 bg-blue-500 text-white py-1 px-2 hover:bg-blue-300 hover:text-black rounded-md"
|
||||
onClick={() => showInput(`comment-id-${data.id}`)}
|
||||
>
|
||||
{t("reply")}
|
||||
</a>
|
||||
<a
|
||||
|
|
@ -240,9 +312,21 @@ const IndeksDetail = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row px-4 pl-[55px] lg:px-14 lg:pl-[135px] mt-2" id={`comment-id-${data.id}`}>
|
||||
<Input type="text" id={`input-comment-${data.id}`} className="p-4 focus:outline-none focus:border-sky-500" placeholder={t("enterReply")} />
|
||||
<a href="javascript:void(0)" className="flex py-1 px-2 rounded-md items-center ml-2 bg-[#f7b357] text-white text-sm lg:text-base" onClick={() => postDataChild(data.id)}>
|
||||
<div
|
||||
className="flex flex-row px-4 pl-[55px] lg:px-14 lg:pl-[135px] mt-2"
|
||||
id={`comment-id-${data.id}`}
|
||||
>
|
||||
<Input
|
||||
type="text"
|
||||
id={`input-comment-${data.id}`}
|
||||
className="p-4 focus:outline-none focus:border-sky-500"
|
||||
placeholder={t("enterReply")}
|
||||
/>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
className="flex py-1 px-2 rounded-md items-center ml-2 bg-[#f7b357] text-white text-sm lg:text-base"
|
||||
onClick={() => postDataChild(data.id)}
|
||||
>
|
||||
{t("send")}
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -250,24 +334,54 @@ const IndeksDetail = () => {
|
|||
? data.children?.map((child1: any) => (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-12 lg:pl-32">
|
||||
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={512} height={512} className="h-10 lg:h-20 w-10 lg:w-20" src="/assets/img/user-avatar-yellow.svg" alt="#" />
|
||||
<Image
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||
shimmer(700, 475)
|
||||
)}`}
|
||||
width={512}
|
||||
height={512}
|
||||
className="h-10 lg:h-20 w-10 lg:w-20"
|
||||
src="/assets/img/user-avatar-yellow.svg"
|
||||
alt="#"
|
||||
/>
|
||||
<div className="border border-slate-300 w-full p-4 bg-white gap-1">
|
||||
<p className="flex justify-between text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
|
||||
<b>{Number(child1.commentFrom?.roleId) == 2 || Number(child1.commentFrom?.roleId) == 3 || Number(child1.commentFrom?.roleId) == 4 ? "HUMAS POLRI" : child1.commentFrom?.fullname}</b>
|
||||
{`${new Date(child1.createdAt).getDate()}/${new Date(child1.createdAt).getMonth() + 1}/${new Date(child1.createdAt).getFullYear()} ${new Date(child1.createdAt).getHours()}:${new Date(
|
||||
<b>
|
||||
{Number(child1.commentFrom?.roleId) == 2 ||
|
||||
Number(child1.commentFrom?.roleId) == 3 ||
|
||||
Number(child1.commentFrom?.roleId) == 4
|
||||
? "HUMAS POLRI"
|
||||
: child1.commentFrom?.fullname}
|
||||
</b>
|
||||
{`${new Date(child1.createdAt).getDate()}/${
|
||||
new Date(child1.createdAt).getMonth() + 1
|
||||
}/${new Date(
|
||||
child1.createdAt
|
||||
).getFullYear()} ${new Date(
|
||||
child1.createdAt
|
||||
).getHours()}:${new Date(
|
||||
child1.createdAt
|
||||
).getMinutes()}`}
|
||||
</p>
|
||||
<p className="text-slate-500 text-sm mb-4">{child1.message}</p>
|
||||
<p className="text-slate-500 text-sm mb-4">
|
||||
{child1.message}
|
||||
</p>
|
||||
<div className="gap-3">
|
||||
<a href="javascript:void(0)" className="mr-2 text-xs lg:text-sm bg-blue-500 text-white py-1 px-2 hover:bg-blue-300 hover:text-black rounded-md" onClick={() => showInput(`comment-id-${child1.id}`)}>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
className="mr-2 text-xs lg:text-sm bg-blue-500 text-white py-1 px-2 hover:bg-blue-300 hover:text-black rounded-md"
|
||||
onClick={() =>
|
||||
showInput(`comment-id-${child1.id}`)
|
||||
}
|
||||
>
|
||||
{t("reply")}
|
||||
</a>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
className="text-xs lg:text-sm bg-red-500 text-white py-1 px-2 hover:bg-red-300 hover:text-black rounded-md"
|
||||
style={
|
||||
Number(child1.commentFrom?.id) == Number(userId)
|
||||
Number(child1.commentFrom?.id) ==
|
||||
Number(userId)
|
||||
? {}
|
||||
: {
|
||||
display: "none",
|
||||
|
|
@ -280,9 +394,21 @@ const IndeksDetail = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-center px-4 pl-[87px] lg:px-14 lg:pl-[205px] mt-2" id={`comment-id-${child1.id}`}>
|
||||
<Input type="text" id={`input-comment-${child1.id}`} className="p-4 focus:outline-none focus:border-sky-500" placeholder={t("enterReply")} />
|
||||
<a href="javascript:void(0)" className="flex text-sm lg:text-base py-1 px-2 rounded-md items-center ml-2 bg-[#f7b357] text-white" onClick={() => postDataChild(child1.id)}>
|
||||
<div
|
||||
className="flex flex-row justify-center px-4 pl-[87px] lg:px-14 lg:pl-[205px] mt-2"
|
||||
id={`comment-id-${child1.id}`}
|
||||
>
|
||||
<Input
|
||||
type="text"
|
||||
id={`input-comment-${child1.id}`}
|
||||
className="p-4 focus:outline-none focus:border-sky-500"
|
||||
placeholder={t("enterReply")}
|
||||
/>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
className="flex text-sm lg:text-base py-1 px-2 rounded-md items-center ml-2 bg-[#f7b357] text-white"
|
||||
onClick={() => postDataChild(child1.id)}
|
||||
>
|
||||
{t("send")}
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -290,24 +416,59 @@ const IndeksDetail = () => {
|
|||
? child1.children?.map((child2: any) => (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-20 lg:pl-48">
|
||||
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={512} height={512} className="h-10 lg:h-20 w-10 lg:w-20" src="/assets/img/user-avatar-yellow.svg" alt="#" />
|
||||
<Image
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||
shimmer(700, 475)
|
||||
)}`}
|
||||
width={512}
|
||||
height={512}
|
||||
className="h-10 lg:h-20 w-10 lg:w-20"
|
||||
src="/assets/img/user-avatar-yellow.svg"
|
||||
alt="#"
|
||||
/>
|
||||
<div className="border border-slate-300 w-full p-4 bg-white gap-2">
|
||||
<p className="flex justify-between text-slate-500 text-xs lg:text-base border-b-2 border-slate-200 mb-2">
|
||||
<b>{Number(child2.commentFrom?.roleId) == 2 || Number(child2.commentFrom?.roleId) == 3 || Number(child2.commentFrom?.roleId) == 4 ? "HUMAS POLRI" : child2.commentFrom?.fullname}</b>
|
||||
{`${new Date(child2.createdAt).getDate()}/${new Date(child2.createdAt).getMonth() + 1}/${new Date(child2.createdAt).getFullYear()} ${new Date(child2.createdAt).getHours()}:${new Date(
|
||||
<b>
|
||||
{Number(child2.commentFrom?.roleId) ==
|
||||
2 ||
|
||||
Number(child2.commentFrom?.roleId) ==
|
||||
3 ||
|
||||
Number(child2.commentFrom?.roleId) == 4
|
||||
? "HUMAS POLRI"
|
||||
: child2.commentFrom?.fullname}
|
||||
</b>
|
||||
{`${new Date(
|
||||
child2.createdAt
|
||||
).getDate()}/${
|
||||
new Date(child2.createdAt).getMonth() +
|
||||
1
|
||||
}/${new Date(
|
||||
child2.createdAt
|
||||
).getFullYear()} ${new Date(
|
||||
child2.createdAt
|
||||
).getHours()}:${new Date(
|
||||
child2.createdAt
|
||||
).getMinutes()}`}
|
||||
</p>
|
||||
<p className="text-slate-500 text-sm mb-4">{child2.message}</p>
|
||||
<p className="text-slate-500 text-sm mb-4">
|
||||
{child2.message}
|
||||
</p>
|
||||
<div>
|
||||
<a href="javascript:void(0)" className="mr-2 text-xs lg:text-sm bg-blue-500 text-white py-1 px-2 hover:bg-blue-300 hover:text-black rounded-md" onClick={() => showInput("comment-id-" + child2.id)}>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
className="mr-2 text-xs lg:text-sm bg-blue-500 text-white py-1 px-2 hover:bg-blue-300 hover:text-black rounded-md"
|
||||
onClick={() =>
|
||||
showInput("comment-id-" + child2.id)
|
||||
}
|
||||
>
|
||||
{t("reply")}
|
||||
</a>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
className="text-xs lg:text-sm bg-red-500 text-white py-1 px-2 hover:bg-red-300 hover:text-black rounded-md"
|
||||
style={
|
||||
Number(child2.commentFrom?.id) == Number(userId)
|
||||
Number(child2.commentFrom?.id) ==
|
||||
Number(userId)
|
||||
? {}
|
||||
: {
|
||||
display: "none",
|
||||
|
|
@ -320,9 +481,21 @@ const IndeksDetail = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row px-4 pl-[120px] lg:px-14 lg:pl-[270px] mt-2" id={`comment-id-${child2.id}`}>
|
||||
<Input type="text" id={`comment-id-${child2.id}`} className="p-4 focus:outline-none focus:border-sky-500" placeholder={t("enterReply")} />
|
||||
<a href="javascript:void(0)" className="flex text-sm lg:text-base py-1 px-2 rounded-md items-center ml-2 bg-[#f7b357] text-white" onClick={() => postDataChild(child1.id)}>
|
||||
<div
|
||||
className="flex flex-row px-4 pl-[120px] lg:px-14 lg:pl-[270px] mt-2"
|
||||
id={`comment-id-${child2.id}`}
|
||||
>
|
||||
<Input
|
||||
type="text"
|
||||
id={`comment-id-${child2.id}`}
|
||||
className="p-4 focus:outline-none focus:border-sky-500"
|
||||
placeholder={t("enterReply")}
|
||||
/>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
className="flex text-sm lg:text-base py-1 px-2 rounded-md items-center ml-2 bg-[#f7b357] text-white"
|
||||
onClick={() => postDataChild(child1.id)}
|
||||
>
|
||||
{t("send")}
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -339,14 +512,25 @@ const IndeksDetail = () => {
|
|||
|
||||
{/* Konten Serupa */}
|
||||
<div className="space-x-5 flex flex-col px-4 lg:px-16 py-16 gap-5">
|
||||
<h1 className="font-bold text-base lg:text-xl px-4 lg:px-8"> {t("relatedPosts")}</h1>
|
||||
<h1 className="font-bold text-base lg:text-xl px-4 lg:px-8">
|
||||
{" "}
|
||||
{t("relatedPosts")}
|
||||
</h1>
|
||||
<Carousel>
|
||||
<CarouselContent className="w-full max-w-7xl">
|
||||
{indexData?.map((relate: any) => (
|
||||
<CarouselItem key={relate?.id} className="md:basis-1/2 lg:basis-1/3">
|
||||
<Link href={`/indeks/detail/${relate?.slug}`} className="relative group overflow-hidden shadow-md hover:shadow-lg">
|
||||
<CarouselItem
|
||||
key={relate?.id}
|
||||
className="md:basis-1/2 lg:basis-1/3"
|
||||
>
|
||||
<Link
|
||||
href={`/indeks/detail/${relate?.slug}`}
|
||||
className="relative group overflow-hidden shadow-md hover:shadow-lg"
|
||||
>
|
||||
<Image
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`}
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||
shimmer(700, 475)
|
||||
)}`}
|
||||
alt=""
|
||||
width={2560}
|
||||
height={1440}
|
||||
|
|
@ -354,10 +538,17 @@ const IndeksDetail = () => {
|
|||
className="w-full rounded-lg h-40 lg:h-60 object-cover group-hover:scale-100 transition-transform duration-300"
|
||||
/>
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-gray-600 border-l-4 border-[#bb3523] rounded-lg backdrop-blur-sm text-white p-2">
|
||||
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-sm px-4 py-1">{relate?.categoryName}</span>
|
||||
<h1 className="text-sm lg:text-lg mb-2 font-semibold h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">{relate?.title}</h1>
|
||||
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-sm px-4 py-1">
|
||||
{relate?.categoryName}
|
||||
</span>
|
||||
<h1 className="text-sm lg:text-lg mb-2 font-semibold h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">
|
||||
{relate?.title}
|
||||
</h1>
|
||||
<p className="flex flex-row items-center text-[10px] gap-2">
|
||||
{formatDateToIndonesian(new Date(relate?.createdAt))} {relate?.timezone ? relate?.timezone : "WIB"} | <Icon icon="formkit:eye" width="15" height="15" /> {relate.clickCount}{" "}
|
||||
{formatDateToIndonesian(new Date(relate?.createdAt))}{" "}
|
||||
{relate?.timezone ? relate?.timezone : "WIB"} |{" "}
|
||||
<Icon icon="formkit:eye" width="15" height="15" />{" "}
|
||||
{relate.clickCount}{" "}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { getIndeksData } from "@/service/landing/landing";
|
||||
import { formatDateToIndonesian } from "@/utils/globals";
|
||||
import { formatDateToIndonesian, htmlToString } from "@/utils/globals";
|
||||
import Image from "next/image";
|
||||
import { usePathname } from "next/navigation";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
|
@ -53,9 +53,11 @@ const Indeks: React.FC = () => {
|
|||
<rect id="r" width="${w}" height="${h}" fill="url(#g)" />
|
||||
<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-14">
|
||||
|
|
@ -70,22 +72,44 @@ const Indeks: React.FC = () => {
|
|||
{indeksData?.map(
|
||||
(indeks: any, index: number) =>
|
||||
index == count && (
|
||||
<div key={indeks?.id} className="relative h-[310px] lg:h-[435px]">
|
||||
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={2560} height={1440} src={indeks?.thumbnailLink} alt="image" className="w-full h-[310px] lg:h-[435px] rounded-lg object-cover" />
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-transparent backdrop-blur-sm text-white p-4 rounded-b-lg">
|
||||
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-xs px-2 py-1">{indeks?.categoryName}</span>
|
||||
<div
|
||||
key={indeks?.id}
|
||||
className="relative h-[310px] lg:h-[435px]"
|
||||
>
|
||||
<Image
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||
shimmer(700, 475)
|
||||
)}`}
|
||||
width={2560}
|
||||
height={1440}
|
||||
src={indeks?.thumbnailLink}
|
||||
alt="image"
|
||||
className="w-full h-[310px] lg:h-[435px] rounded-lg object-cover"
|
||||
/>
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-black/15 backdrop-brightness-50 text-white p-4 rounded-b-lg">
|
||||
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-xs px-2 py-1">
|
||||
{indeks?.categoryName}
|
||||
</span>
|
||||
<Link href={`/indeks/detail/${indeks?.slug}`}>
|
||||
<h2 className="text-2xl font-bold mt-2">{indeks?.title}</h2>
|
||||
<h2 className="text-2xl font-bold mt-2">
|
||||
{indeks?.title}
|
||||
</h2>
|
||||
</Link>
|
||||
<p className="text-xs flex flex-row items-center gap-1 mt-1">
|
||||
{formatDateToIndonesian(new Date(indeks?.createdAt))} {indeks?.timezone ? indeks?.timezone : "WIB"} |{" "}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24">
|
||||
{formatDateToIndonesian(new Date(indeks?.createdAt))}{" "}
|
||||
{indeks?.timezone ? indeks?.timezone : "WIB"} |{" "}
|
||||
{/* <svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1.2em"
|
||||
height="1.2em"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
|
||||
/>
|
||||
</svg>{" "}
|
||||
{indeks?.clickCount}
|
||||
</svg>{" "} */}
|
||||
{/* {indeks?.clickCount} */}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -108,22 +132,49 @@ const Indeks: React.FC = () => {
|
|||
{indeksData?.map(
|
||||
(indeksRight: any, index: number) =>
|
||||
(index == count + 1 || index == count + 2) && (
|
||||
<div key={indeksRight?.id} className="relative h-[310px] lg:h-[215px]">
|
||||
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={1920} height={1080} src={indeksRight?.thumbnailLink} alt="image" className="w-full h-[310px] lg:h-[215px] rounded-lg " />
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-transparent backdrop-blur-sm text-white p-4 rounded-b-lg">
|
||||
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-xs px-2 py-1">{indeksRight?.categoryName}</span>
|
||||
<div
|
||||
key={indeksRight?.id}
|
||||
className="relative h-[310px] lg:h-[215px]"
|
||||
>
|
||||
<Image
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||
shimmer(700, 475)
|
||||
)}`}
|
||||
width={1920}
|
||||
height={1080}
|
||||
src={indeksRight?.thumbnailLink}
|
||||
alt="image"
|
||||
className="w-full h-[310px] lg:h-[215px] rounded-lg "
|
||||
/>
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-black/15 backdrop-brightness-50 text-white p-4 rounded-b-lg">
|
||||
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-xs px-2 py-1">
|
||||
{indeksRight?.categoryName}
|
||||
</span>
|
||||
<Link href={`/indeks/detail/${indeksRight?.slug}`}>
|
||||
<h2 className="text-xl font-bold mt-2">{indeksRight?.title}</h2>
|
||||
<h2 className="text-xl font-bold mt-2">
|
||||
{indeksRight?.title}
|
||||
</h2>
|
||||
</Link>
|
||||
<p className="text-xs flex flex-row items-center gap-1 mt-1 ml-2">
|
||||
{formatDateToIndonesian(new Date(indeksRight?.createdAt))} {indeksRight?.timezone ? indeksRight?.timezone : "WIB"}|{" "}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24">
|
||||
{formatDateToIndonesian(
|
||||
new Date(indeksRight?.createdAt)
|
||||
)}{" "}
|
||||
{indeksRight?.timezone
|
||||
? indeksRight?.timezone
|
||||
: "WIB"}
|
||||
|{" "}
|
||||
{/* <svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1.2em"
|
||||
height="1.2em"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
|
||||
/>
|
||||
</svg>{" "}
|
||||
{indeksRight?.clickCount}
|
||||
{indeksRight?.clickCount} */}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -165,14 +216,31 @@ const Indeks: React.FC = () => {
|
|||
{indeksData?.map(
|
||||
(indeksBottom: any, index: number) =>
|
||||
index < 3 && (
|
||||
<div key={indeksBottom?.id} className="flex flex-col md:flex-row items-start p-4 gap-4">
|
||||
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={2560} height={1440} src={indeksBottom?.thumbnailLink} alt="" className="h-40 object-cover rounded-lg w-full lg:w-full lg:h-[300px]" />
|
||||
<div
|
||||
key={indeksBottom?.id}
|
||||
className="flex flex-col md:flex-row items-start p-4 gap-4"
|
||||
>
|
||||
<Image
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||
shimmer(700, 475)
|
||||
)}`}
|
||||
width={500}
|
||||
height={250}
|
||||
src={indeksBottom?.thumbnailLink}
|
||||
alt=""
|
||||
className="h-40 object-cover rounded-lg w-full lg:w-[550px] lg:h-[200px]"
|
||||
/>
|
||||
<div className="flex flex-col justify-between w-full">
|
||||
<p className="text-sm">{indeksBottom?.date}</p>
|
||||
<Link href={`/indeks/detail/${indeksBottom?.slug}`} className="text-2xl font-semibold text-gray-800 dark:text-white">
|
||||
<Link
|
||||
href={`/indeks/detail/${indeksBottom?.slug}`}
|
||||
className="text-2xl font-semibold text-gray-800 dark:text-white"
|
||||
>
|
||||
{indeksBottom?.title}
|
||||
</Link>
|
||||
<p className="text-sm text-gray-600 dark:text-white mt-2">{indeksBottom?.description}</p>
|
||||
<p className="text-sm text-gray-600 dark:text-white mt-2">
|
||||
{htmlToString(indeksBottom?.description)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,18 +1,50 @@
|
|||
"use client";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
||||
import { Popover, PopoverArrow, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
Popover,
|
||||
PopoverArrow,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { format } from "date-fns";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { detailSchedule, listSchedule, listScheduleNextPublic, listSchedulePrevPublic, listScheduleTodayPublic, searchSchedules } from "@/service/schedule/schedule";
|
||||
import {
|
||||
detailSchedule,
|
||||
listSchedule,
|
||||
listScheduleNextPublic,
|
||||
listSchedulePrevPublic,
|
||||
listScheduleTodayPublic,
|
||||
searchSchedules,
|
||||
} from "@/service/schedule/schedule";
|
||||
import { usePathname, useRouter } from "@/i18n/routing";
|
||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog";
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion";
|
||||
import { close, loading } from "@/config/swal";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
|
@ -292,13 +324,21 @@ const Schedule = (props: any) => {
|
|||
|
||||
function getLastWeek(today: Date | undefined) {
|
||||
if (today) {
|
||||
return new Date(today.getFullYear(), today.getMonth(), today.getDate() - 7);
|
||||
return new Date(
|
||||
today.getFullYear(),
|
||||
today.getMonth(),
|
||||
today.getDate() - 7
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getNextWeek(today: Date | undefined) {
|
||||
if (today) {
|
||||
return new Date(today.getFullYear(), today.getMonth(), today.getDate() + 7);
|
||||
return new Date(
|
||||
today.getFullYear(),
|
||||
today.getMonth(),
|
||||
today.getDate() + 7
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -423,13 +463,22 @@ const Schedule = (props: any) => {
|
|||
};
|
||||
|
||||
function setItemSchedule(id: string, date: string) {
|
||||
const itemFound: any = schedules?.filter((s: any) => s.dateInRange.includes(date) && s.timeIndex.split(",").includes(id));
|
||||
const itemFound: any = schedules?.filter(
|
||||
(s: any) =>
|
||||
s.dateInRange.includes(date) && s.timeIndex.split(",").includes(id)
|
||||
);
|
||||
|
||||
if (itemFound?.length > 0) {
|
||||
if (itemFound?.length == 1) {
|
||||
return (
|
||||
<a
|
||||
className={`cursor-pointer text-center ${Number(itemFound[0]?.uploaderLevelNumber) == 1 ? "bg-yellow-300" : Number(itemFound[0]?.uploaderLevelNumber) == 2 ? "bg-blue-400" : "bg-gray-500"}`}
|
||||
className={`cursor-pointer text-center ${
|
||||
Number(itemFound[0]?.uploaderLevelNumber) == 1
|
||||
? "bg-yellow-300"
|
||||
: Number(itemFound[0]?.uploaderLevelNumber) == 2
|
||||
? "bg-blue-400"
|
||||
: "bg-gray-500"
|
||||
}`}
|
||||
onClick={() => {
|
||||
getItem(itemFound[0]);
|
||||
}}
|
||||
|
|
@ -452,7 +501,9 @@ const Schedule = (props: any) => {
|
|||
<b>{`${itemFound?.length} Jadwal Bersamaan`}</b>
|
||||
</p>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger className="font-bold text-blue-300">Lihat Jadwal</DropdownMenuTrigger>
|
||||
<DropdownMenuTrigger className="font-bold text-blue-300">
|
||||
Lihat Jadwal
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
{itemFound?.map((list: any) => (
|
||||
<DropdownMenuItem
|
||||
|
|
@ -471,7 +522,10 @@ const Schedule = (props: any) => {
|
|||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<div className="border-0 dropdown-menu schedule-list" aria-labelledby="view-schedule"></div>
|
||||
<div
|
||||
className="border-0 dropdown-menu schedule-list"
|
||||
aria-labelledby="view-schedule"
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -480,73 +534,121 @@ const Schedule = (props: any) => {
|
|||
return (
|
||||
<>
|
||||
{/* Awal Komponen Kiri */}
|
||||
<div className="relative px-4 lg:px-10 lg:py-10 py-4 bg-[#f7f7f7] dark:bg-slate-800">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant={"outline"} className={cn("w-[240px] py-4 justify-start text-left font-normal", !startDate && "text-muted-foreground")}>
|
||||
<CalendarIcon />
|
||||
{startDate ? format(startDate, "MMM yyyy") : <span>Pick a date</span>}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={startDate}
|
||||
onSelect={(e) => {
|
||||
handleChangeDate(e);
|
||||
}}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
<div className="container relative py-4">
|
||||
<div className="relative pl-4 lg:px-8 lg:py-10 py-4 bg-[#f7f7f7] dark:bg-slate-800">
|
||||
<div className="flex flex-row items-center">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<a className="text-black dark:text-white flex flex-row w-fit gap-2 py-4 items-center cursor-pointer">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M20 3H4a1 1 0 0 0-1 1v2.227l.008.223a3 3 0 0 0 .772 1.795L8 12.886V21a1 1 0 0 0 1.316.949l6-2l.108-.043A1 1 0 0 0 16 19v-6.586l4.121-4.12A3 3 0 0 0 21 6.171V4a1 1 0 0 0-1-1" />
|
||||
</svg>
|
||||
Filter
|
||||
<svg className="flex items-center justify-center" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" fill-rule="evenodd" d="m6 7l6 6l6-6l2 2l-8 8l-8-8z" />
|
||||
</svg>
|
||||
</a>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-[240px] py-3 justify-start text-left font-normal",
|
||||
!startDate && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<CalendarIcon />
|
||||
{startDate ? (
|
||||
format(startDate, "MMM yyyy")
|
||||
) : (
|
||||
<span>Pick a date</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="start" className="flex p-0 rounded-md w-fit">
|
||||
<div className="flex flex-col items-center justify-between gap-1.5 p-2 border-b text-default-600 rounded-none">
|
||||
<div className="gap-6 flex flex-row justify-end">
|
||||
<p className="font-semibold">Filter</p>
|
||||
<button className="text-blue-400" onClick={doFilter}>
|
||||
{t("save")}
|
||||
</button>
|
||||
</div>
|
||||
<div className="border w-full border-t border-slate-500"></div>
|
||||
<div className="overflow-y-auto flex flex-col gap-2 h-[200px] ">
|
||||
<p className="text-center font-semibold">Region Filter</p>
|
||||
{city?.map((list) => (
|
||||
<div className="mt-2 gap-2 flex flex-row">
|
||||
{" "}
|
||||
<input type="checkbox" className="" id={`filterCategory-${list.key}`} value={list.id} checked={regionFilter?.includes(list.id)} onChange={handleRegionFilter} />
|
||||
<p>{list?.name}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={startDate}
|
||||
onSelect={(e) => {
|
||||
handleChangeDate(e);
|
||||
}}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<div className="flex flex-col lg:flex-row gap-3">
|
||||
{regionName?.map((list: any) => (
|
||||
<div className="text-left">
|
||||
<button onClick={() => deleteFilterhandler(list.id)} key={list.key} id={list.id} className="text-black bg-yellow-300 w-fit p-3 flex justify-center items-center rounded-lg">
|
||||
{list.name}
|
||||
<Icon icon="icon-park-outline:delete-two" className="items-center" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="container relative py-4 flex flex-row items-center gap-4">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<a className="text-black dark:text-white flex flex-row w-fit gap-3 py-2 px-3 items-center cursor-pointer border border-black rounded-md ">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M20 3H4a1 1 0 0 0-1 1v2.227l.008.223a3 3 0 0 0 .772 1.795L8 12.886V21a1 1 0 0 0 1.316.949l6-2l.108-.043A1 1 0 0 0 16 19v-6.586l4.121-4.12A3 3 0 0 0 21 6.171V4a1 1 0 0 0-1-1"
|
||||
/>
|
||||
</svg>
|
||||
Filter
|
||||
<svg
|
||||
className="flex items-center justify-center"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
d="m6 7l6 6l6-6l2 2l-8 8l-8-8z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
align="start"
|
||||
className="flex p-0 rounded-md w-fit"
|
||||
>
|
||||
<div className="flex flex-col items-center justify-between gap-1.5 p-2 border-b text-default-600 rounded-none">
|
||||
<div className="gap-6 flex flex-row justify-end">
|
||||
<p className="font-semibold">Filter</p>
|
||||
<button className="text-blue-400" onClick={doFilter}>
|
||||
{t("save")}
|
||||
</button>
|
||||
</div>
|
||||
<div className="border w-full border-t border-slate-500"></div>
|
||||
<div className="overflow-y-auto flex flex-col gap-2 h-[200px] ">
|
||||
<p className="text-center font-semibold">Region Filter</p>
|
||||
{city?.map((list) => (
|
||||
<div className="mt-2 gap-2 flex flex-row">
|
||||
{" "}
|
||||
<input
|
||||
type="checkbox"
|
||||
className=""
|
||||
id={`filterCategory-${list.key}`}
|
||||
value={list.id}
|
||||
checked={regionFilter?.includes(list.id)}
|
||||
onChange={handleRegionFilter}
|
||||
/>
|
||||
<p>{list?.name}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<div className="flex flex-col lg:flex-row gap-3">
|
||||
{regionName?.map((list: any) => (
|
||||
<div className="text-left">
|
||||
<button
|
||||
onClick={() => deleteFilterhandler(list.id)}
|
||||
key={list.key}
|
||||
id={list.id}
|
||||
className="text-black bg-yellow-300 w-fit p-3 flex justify-center items-center rounded-lg"
|
||||
>
|
||||
{list.name}
|
||||
<Icon
|
||||
icon="icon-park-outline:delete-two"
|
||||
className="items-center"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col lg:flex-row gap-6">
|
||||
<div className="flex flex-col lg:flex-row gap-1">
|
||||
<div className="h-[500px] overflow-y-auto md:overflow-y-auto w-full lg:w-3/4 ">
|
||||
<div className="container-fluid relative">
|
||||
<div className="grid grid-cols-1 mt-8">
|
||||
|
|
@ -554,53 +656,125 @@ const Schedule = (props: any) => {
|
|||
<table className="w-full text-sm text-start">
|
||||
<thead className="text-md">
|
||||
<tr className="h-full">
|
||||
<th className="text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[120px]">{t("timeTable")}</th>
|
||||
<th className="text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[210px]">
|
||||
{t("timeTable")}
|
||||
</th>
|
||||
<th className="text-center border border-r-0 border-gray-100 dark:border-gray-700 py-6 w-[20px]">
|
||||
<a onClick={() => changePrevWeek()} className="cursor-pointer h-fit self-center bottom-0">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="40px" height="40px" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M12.29 8.71L9.7 11.3a.996.996 0 0 0 0 1.41l2.59 2.59c.63.63 1.71.18 1.71-.71V9.41c0-.89-1.08-1.33-1.71-.7" />
|
||||
<a
|
||||
onClick={() => changePrevWeek()}
|
||||
className="cursor-pointer h-fit self-center bottom-0"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="40px"
|
||||
height="40px"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12.29 8.71L9.7 11.3a.996.996 0 0 0 0 1.41l2.59 2.59c.63.63 1.71.18 1.71-.71V9.41c0-.89-1.08-1.33-1.71-.7"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</th>
|
||||
<th className={`text-center cursor-pointer border border-l-0 h-full border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${new Date().toISOString().slice(0, 10) == dateAWeek[0] ? "bg-red-600 text-white" : ""}`}>
|
||||
<th
|
||||
className={`text-center cursor-pointer border border-l-0 h-full border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${
|
||||
new Date().toISOString().slice(0, 10) ==
|
||||
dateAWeek[0]
|
||||
? "bg-red-600 text-white"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{/* <a className="cursor-pointer h-fit self-center bottom-0" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M12.29 8.71L9.7 11.3a.996.996 0 0 0 0 1.41l2.59 2.59c.63.63 1.71.18 1.71-.71V9.41c0-.89-1.08-1.33-1.71-.7" />
|
||||
</svg>
|
||||
</a>{" "} */}
|
||||
<div className="flex flex-col ">
|
||||
<p className="text-2xl">{dateAWeek[0]?.split("-")[2]}</p>
|
||||
<p className="text-2xl">
|
||||
{dateAWeek[0]?.split("-")[2]}
|
||||
</p>
|
||||
<p>{t("monday")}</p>
|
||||
</div>
|
||||
</th>
|
||||
<th className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${new Date().toISOString().slice(0, 10) == dateAWeek[1] ? "bg-[#BE0106] text-white rounded-lg" : ""}`}>
|
||||
<div className="text-2xl">{dateAWeek[1]?.split("-")[2]}</div>
|
||||
<th
|
||||
className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${
|
||||
new Date().toISOString().slice(0, 10) ==
|
||||
dateAWeek[1]
|
||||
? "bg-[#BE0106] text-white rounded-lg"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div className="text-2xl">
|
||||
{dateAWeek[1]?.split("-")[2]}
|
||||
</div>
|
||||
{t("tuesday")}
|
||||
</th>
|
||||
<th className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${new Date().toISOString().slice(0, 10) == dateAWeek[2] ? "bg-[#BE0106] text-white rounded-lg" : ""}`}>
|
||||
<div className="text-2xl">{dateAWeek[2]?.split("-")[2]}</div>
|
||||
<th
|
||||
className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${
|
||||
new Date().toISOString().slice(0, 10) ==
|
||||
dateAWeek[2]
|
||||
? "bg-[#BE0106] text-white rounded-lg"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div className="text-2xl">
|
||||
{dateAWeek[2]?.split("-")[2]}
|
||||
</div>
|
||||
{t("wednesday")}
|
||||
</th>
|
||||
<th className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${new Date().toISOString().slice(0, 10) == dateAWeek[3] ? "bg-[#BE0106] text-white rounded-lg" : ""}`}>
|
||||
<div className="text-2xl">{dateAWeek[3]?.split("-")[2]}</div>
|
||||
<th
|
||||
className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${
|
||||
new Date().toISOString().slice(0, 10) ==
|
||||
dateAWeek[3]
|
||||
? "bg-[#BE0106] text-white rounded-lg"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div className="text-2xl">
|
||||
{dateAWeek[3]?.split("-")[2]}
|
||||
</div>
|
||||
{t("thursday")}
|
||||
</th>
|
||||
<th className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${new Date().toISOString().slice(0, 10) == dateAWeek[4] ? "bg-[#BE0106] text-white rounded-lg" : ""}`}>
|
||||
<div className="text-2xl">{dateAWeek[4]?.split("-")[2]}</div>
|
||||
<th
|
||||
className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${
|
||||
new Date().toISOString().slice(0, 10) ==
|
||||
dateAWeek[4]
|
||||
? "bg-[#BE0106] text-white rounded-lg"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div className="text-2xl">
|
||||
{dateAWeek[4]?.split("-")[2]}
|
||||
</div>
|
||||
{t("friday")}
|
||||
</th>
|
||||
<th className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${new Date().toISOString().slice(0, 10) == dateAWeek[5] ? "bg-[#BE0106] text-white rounded-lg" : ""}`}>
|
||||
<div className="text-2xl">{dateAWeek[5]?.split("-")[2]}</div>
|
||||
<th
|
||||
className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${
|
||||
new Date().toISOString().slice(0, 10) ==
|
||||
dateAWeek[5]
|
||||
? "bg-[#BE0106] text-white rounded-lg"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div className="text-2xl">
|
||||
{dateAWeek[5]?.split("-")[2]}
|
||||
</div>
|
||||
{t("saturday")}
|
||||
</th>
|
||||
<th
|
||||
onClick={() => changeNextWeek()}
|
||||
className={`text-center border cursor-pointer border-r-0 border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${
|
||||
new Date().toISOString().slice(0, 10) == dateAWeek[6] ? "bg-[#BE0106] text-white rounded-lg" : ""
|
||||
new Date().toISOString().slice(0, 10) ==
|
||||
dateAWeek[6]
|
||||
? "bg-[#BE0106] text-white rounded-lg"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div className="flex flex-col ">
|
||||
<p className="text-2xl">{dateAWeek[6]?.split("-")[2]}</p>
|
||||
<p className="text-2xl">
|
||||
{dateAWeek[6]?.split("-")[2]}
|
||||
</p>
|
||||
<p>{t("sunday")}</p>
|
||||
</div>
|
||||
{/* <a className="cursor-pointer h-fit p-0 m-0 self-center">
|
||||
|
|
@ -610,9 +784,20 @@ const Schedule = (props: any) => {
|
|||
</a> */}
|
||||
</th>
|
||||
<th className="text-center border-l-0 border border-gray-100 dark:border-gray-700 py-6 w-[20px]">
|
||||
<a onClick={() => changeNextWeek()} className="cursor-pointer">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="40px" height="40px" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="m11.71 15.29l2.59-2.59a.996.996 0 0 0 0-1.41L11.71 8.7c-.63-.62-1.71-.18-1.71.71v5.17c0 .9 1.08 1.34 1.71.71" />
|
||||
<a
|
||||
onClick={() => changeNextWeek()}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="40px"
|
||||
height="40px"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m11.71 15.29l2.59-2.59a.996.996 0 0 0 0-1.41L11.71 8.7c-.63-.62-1.71-.18-1.71.71v5.17c0 .9 1.08 1.34 1.71.71"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</th>
|
||||
|
|
@ -621,16 +806,34 @@ const Schedule = (props: any) => {
|
|||
<tbody>
|
||||
{timeList.map((times) => (
|
||||
<tr key={times.id}>
|
||||
<th className="text-center border border-gray-100 dark:border-gray-700 py-5">{times.time}</th>
|
||||
<td colSpan={2} className="p-3 border border-gray-100 dark:border-gray-700 ">
|
||||
<th className="text-center border border-gray-100 dark:border-gray-700 py-5">
|
||||
{times.time}
|
||||
</th>
|
||||
<td
|
||||
colSpan={2}
|
||||
className="p-3 border border-gray-100 dark:border-gray-700 "
|
||||
>
|
||||
{setItemSchedule(times.id, dateList[0])}
|
||||
</td>
|
||||
<td className="border border-gray-100 dark:border-gray-700">{setItemSchedule(times.id, dateList[1])}</td>
|
||||
<td className="border border-gray-100 dark:border-gray-700">{setItemSchedule(times.id, dateList[2])}</td>
|
||||
<td className="border border-gray-100 dark:border-gray-700">{setItemSchedule(times.id, dateList[3])}</td>
|
||||
<td className="p-3 border border-gray-100 dark:border-gray-700">{setItemSchedule(times.id, dateList[4])}</td>
|
||||
<td className="border border-gray-100 dark:border-gray-700">{setItemSchedule(times.id, dateList[5])}</td>
|
||||
<td colSpan={2} className="border border-gray-100 dark:border-gray-700">
|
||||
<td className="border border-gray-100 dark:border-gray-700">
|
||||
{setItemSchedule(times.id, dateList[1])}
|
||||
</td>
|
||||
<td className="border border-gray-100 dark:border-gray-700">
|
||||
{setItemSchedule(times.id, dateList[2])}
|
||||
</td>
|
||||
<td className="border border-gray-100 dark:border-gray-700">
|
||||
{setItemSchedule(times.id, dateList[3])}
|
||||
</td>
|
||||
<td className="p-3 border border-gray-100 dark:border-gray-700">
|
||||
{setItemSchedule(times.id, dateList[4])}
|
||||
</td>
|
||||
<td className="border border-gray-100 dark:border-gray-700">
|
||||
{setItemSchedule(times.id, dateList[5])}
|
||||
</td>
|
||||
<td
|
||||
colSpan={2}
|
||||
className="border border-gray-100 dark:border-gray-700"
|
||||
>
|
||||
{setItemSchedule(times.id, dateList[6])}
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -654,10 +857,18 @@ const Schedule = (props: any) => {
|
|||
className="pl-8 pr-4 py-1 w-full border rounded-full text-sm focus:outline-none"
|
||||
/>
|
||||
<span className="absolute left-2 top-1/2 transform -translate-y-1/2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z" />
|
||||
<path fill="currentColor" d="M10.5 2a8.5 8.5 0 1 0 5.262 15.176l3.652 3.652a1 1 0 0 0 1.414-1.414l-3.652-3.652A8.5 8.5 0 0 0 10.5 2M4 10.5a6.5 6.5 0 1 1 13 0a6.5 6.5 0 0 1-13 0" />
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.5 2a8.5 8.5 0 1 0 5.262 15.176l3.652 3.652a1 1 0 0 0 1.414-1.414l-3.652-3.652A8.5 8.5 0 0 0 10.5 2M4 10.5a6.5 6.5 0 1 1 13 0a6.5 6.5 0 0 1-13 0"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</span>
|
||||
|
|
@ -700,8 +911,13 @@ const Schedule = (props: any) => {
|
|||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>{t("todaySchedule")}</AccordionTrigger>
|
||||
{todayList?.map((list: any) => (
|
||||
<AccordionContent key={list?.id} className="flex flex-row gap-3">
|
||||
<div className="border-l-4 border-red-700 pl-1 h-fit font-bold text-lg">{new Date(list.startDate).getDate()}</div>
|
||||
<AccordionContent
|
||||
key={list?.id}
|
||||
className="flex flex-row gap-3"
|
||||
>
|
||||
<div className="border-l-4 border-red-700 pl-1 h-fit font-bold text-lg">
|
||||
{new Date(list.startDate).getDate()}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="font-bold">{list?.title}</h3>
|
||||
<p className="flex flex-row items-center gap-2">
|
||||
|
|
@ -725,8 +941,13 @@ const Schedule = (props: any) => {
|
|||
<AccordionItem value="item-2">
|
||||
<AccordionTrigger>{t("previousSchedule")}</AccordionTrigger>
|
||||
{prevdayList?.map((list: any) => (
|
||||
<AccordionContent key={list?.id} className="flex flex-row gap-3">
|
||||
<div className="border-l-4 border-red-700 pl-1 h-fit font-bold text-lg">{new Date(list.startDate).getDate()}</div>
|
||||
<AccordionContent
|
||||
key={list?.id}
|
||||
className="flex flex-row gap-3"
|
||||
>
|
||||
<div className="border-l-4 border-red-700 pl-1 h-fit font-bold text-lg">
|
||||
{new Date(list.startDate).getDate()}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="font-bold">{list?.title}</h3>
|
||||
<p className="flex flex-row items-center gap-2">
|
||||
|
|
@ -750,8 +971,13 @@ const Schedule = (props: any) => {
|
|||
<AccordionItem value="item-3">
|
||||
<AccordionTrigger>{t("nextSchedule")}</AccordionTrigger>
|
||||
{nextdayList?.map((list: any) => (
|
||||
<AccordionContent key={list?.id} className="flex flex-row gap-3">
|
||||
<div className="border-l-4 border-red-700 pl-1 h-fit font-bold text-lg">{new Date(list.startDate).getDate()}</div>
|
||||
<AccordionContent
|
||||
key={list?.id}
|
||||
className="flex flex-row gap-3"
|
||||
>
|
||||
<div className="border-l-4 border-red-700 pl-1 h-fit font-bold text-lg">
|
||||
{new Date(list.startDate).getDate()}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="font-bold">{list?.title}</h3>
|
||||
<p className="flex flex-row items-center gap-2">
|
||||
|
|
@ -846,13 +1072,17 @@ const Schedule = (props: any) => {
|
|||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
<h1 className="my-4 font-light">JADWAL / {detail?.isYoutube == true ? "LIVE STREAMING" : "DETAIL"}</h1>
|
||||
<h1 className="my-4 font-light">
|
||||
JADWAL /{" "}
|
||||
{detail?.isYoutube == true ? "LIVE STREAMING" : "DETAIL"}
|
||||
</h1>
|
||||
<p className="font-bold">{detail?.title}</p>
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
<p className="flex flex-row items-center gap-2">
|
||||
<Icon icon="iconamoon:clock-thin" />
|
||||
{detail?.date} {detail?.startTime} - {detail?.endTime} {detail?.timezone ? detail.timezone : "WIB"}
|
||||
{detail?.date} {detail?.startTime} - {detail?.endTime}{" "}
|
||||
{detail?.timezone ? detail.timezone : "WIB"}
|
||||
</p>
|
||||
</AlertDialogDescription>
|
||||
<AlertDialogDescription>
|
||||
|
|
|
|||
|
|
@ -25,11 +25,23 @@ const Login = ({ params: { locale } }: { params: { locale: string } }) => {
|
|||
<div className="lg:block hidden flex-1 overflow-hidden text-[40px] leading-[48px] text-default-600 relative z-[1] bg-default-50">
|
||||
<div className="max-w-[520px] pt-16 ps-20 ">
|
||||
<Link href="/" className="mb-6 inline-block">
|
||||
<Image src="/assets/mediahub-logo.png" alt="" width={250} height={250} className="mb-10 w-full h-full" />
|
||||
<Image
|
||||
src="/assets/mediahub-logo.png"
|
||||
alt=""
|
||||
width={250}
|
||||
height={250}
|
||||
className="mb-10 w-full h-full"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="absolute left-0 2xl:bottom-[-160px] bottom-[-130px] h-full w-full z-[-1]">
|
||||
<Image src="/assets/vector-login.svg" alt="" width={300} height={300} className="mb-10 w-full h-full" />
|
||||
<Image
|
||||
src="/assets/vector-login.svg"
|
||||
alt=""
|
||||
width={300}
|
||||
height={300}
|
||||
className="mb-10 w-full h-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 relative">
|
||||
|
|
@ -40,12 +52,12 @@ const Login = ({ params: { locale } }: { params: { locale: string } }) => {
|
|||
<Logo />
|
||||
</Link>
|
||||
</div> */}
|
||||
<div className="text-left 2xl:mb-10 mb-4 mt-10">
|
||||
{/* <div className="text-left 2xl:mb-10 mb-4 mt-10">
|
||||
<h4 className="font-semibold text-3xl text-left">{t("logInPlease")}</h4>
|
||||
<div className="text-default-500 text-base">
|
||||
{t("acc")} <span className="text-red-500">{t("reg")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
<LoginForm />
|
||||
{/* <div className="relative border-b-[#9AA2AF] border-opacity-[16%] border-b pt-6">
|
||||
<div className="absolute inline-block bg-default-50 dark:bg-default-100 left-1/2 top-1/2 transform -translate-x-1/2 px-4 min-w-max text-sm text-default-500 font-normal">
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import {
|
|||
SelectItem,
|
||||
} from "@radix-ui/react-select";
|
||||
import { SelectGroup } from "@/components/ui/select";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -43,6 +44,13 @@ interface Option {
|
|||
userLevelId: string;
|
||||
}
|
||||
|
||||
const CustomEditor = dynamic(
|
||||
() => {
|
||||
return import("@/components/editor/custom-editor");
|
||||
},
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export default function FormCollaboration() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -257,12 +265,7 @@ export default function FormCollaboration() {
|
|||
control={control}
|
||||
name="naration"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<JoditEditor
|
||||
ref={editor}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className="dark:text-black"
|
||||
/>
|
||||
<CustomEditor onChange={onChange} initialData={value} />
|
||||
)}
|
||||
/>
|
||||
{errors.naration?.message && (
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import {
|
|||
SelectItem,
|
||||
} from "@radix-ui/react-select";
|
||||
import { SelectGroup } from "@/components/ui/select";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -43,6 +44,13 @@ interface Option {
|
|||
userLevelId: string;
|
||||
}
|
||||
|
||||
const CustomEditor = dynamic(
|
||||
() => {
|
||||
return import("@/components/editor/custom-editor");
|
||||
},
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export default function FormInternal() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -253,12 +261,7 @@ export default function FormInternal() {
|
|||
control={control}
|
||||
name="naration"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<JoditEditor
|
||||
ref={editor}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className="dark:text-black"
|
||||
/>
|
||||
<CustomEditor onChange={onChange} initialData={value} />
|
||||
)}
|
||||
/>
|
||||
{errors.naration?.message && (
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ import { error } from "@/lib/swal";
|
|||
import dynamic from "next/dynamic";
|
||||
import WavesurferPlayer from "@wavesurfer/react";
|
||||
import WaveSurfer from "wavesurfer.js";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const imageSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -135,6 +136,7 @@ export default function FormAudioDetail() {
|
|||
const [detailThumb, setDetailThumb] = useState<any>([]);
|
||||
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
||||
|
||||
const t = useTranslations("Form");
|
||||
const [selectedTarget, setSelectedTarget] = useState("");
|
||||
const [files, setFiles] = useState<FileType[]>([]);
|
||||
const [rejectedFiles, setRejectedFiles] = useState<number[]>([]);
|
||||
|
|
@ -452,11 +454,11 @@ export default function FormAudioDetail() {
|
|||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Konten Audio</p>
|
||||
<p className="text-lg font-semibold mb-3">{t("form-audio")}</p>
|
||||
<div className="gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2 py-3">
|
||||
<Label>Judul</Label>
|
||||
<Label>{t("title")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
|
|
@ -477,8 +479,8 @@ export default function FormAudioDetail() {
|
|||
)}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="py-3 w-full">
|
||||
<Label>Kategori</Label>
|
||||
<div className="py-3 w-full space-y-2">
|
||||
<Label>{t("category")}</Label>
|
||||
<Select
|
||||
value={detail?.category.name} // Nilai default berdasarkan detail
|
||||
onValueChange={(id) => {
|
||||
|
|
@ -500,8 +502,8 @@ export default function FormAudioDetail() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-3">
|
||||
<Label>Deskripsi</Label>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("description")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
|
|
@ -516,7 +518,7 @@ export default function FormAudioDetail() {
|
|||
)}
|
||||
</div>
|
||||
|
||||
<Label className="text-xl text-black">File Mediaaa</Label>
|
||||
<Label className="text-xl space-y-2">{t("file-media")}</Label>
|
||||
<div className="w-full">
|
||||
<div className={"container example"}>
|
||||
{detailThumb?.map((url: any, index: number) => (
|
||||
|
|
@ -561,7 +563,7 @@ export default function FormAudioDetail() {
|
|||
<Card className=" h-[600px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Kreator</Label>
|
||||
<Label>{t("creator")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="creatorName"
|
||||
|
|
@ -585,7 +587,7 @@ export default function FormAudioDetail() {
|
|||
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<Label>{t("tags")}</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{detail?.tags
|
||||
?.split(",")
|
||||
|
|
@ -601,8 +603,8 @@ export default function FormAudioDetail() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<div className="flex flex-col gap-6">
|
||||
<Label>Target Publish</Label>
|
||||
<div className="flex flex-col gap-6 space-y-2">
|
||||
<Label>{t("publish-target")}</Label>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="5"
|
||||
|
|
@ -639,10 +641,10 @@ export default function FormAudioDetail() {
|
|||
</div>
|
||||
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
||||
<MailIcon />
|
||||
<p className="">Kotak Saran (0)</p>
|
||||
<p className="">{t("suggestion-box")}(0)</p>
|
||||
</div>
|
||||
<div className="px-3 py-3 border mx-3">
|
||||
<p>Keterangan:</p>
|
||||
<p>{t("information")}:</p>
|
||||
<p className="text-sm text-slate-400">{detail?.statusName}</p>
|
||||
</div>
|
||||
{/* {detail?.isPublish == false ? (
|
||||
|
|
@ -662,14 +664,15 @@ export default function FormAudioDetail() {
|
|||
color="primary"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:check" className="mr-3" /> Setujui
|
||||
<Icon icon="fa:check" className="mr-3" /> {t("accept")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => actionApproval("3")}
|
||||
className="bg-orange-400 hover:bg-orange-300"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:comment-o" className="mr-3" /> Revisi
|
||||
<Icon icon="fa:comment-o" className="mr-3" />{" "}
|
||||
{t("revision")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => actionApproval("4")}
|
||||
|
|
@ -677,7 +680,7 @@ export default function FormAudioDetail() {
|
|||
type="button"
|
||||
>
|
||||
<Icon icon="fa:times" className="mr-3" />
|
||||
Tolak
|
||||
{t("reject")}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
|
@ -688,7 +691,7 @@ export default function FormAudioDetail() {
|
|||
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
||||
<DialogContent size="md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Berikan Komentar</DialogTitle>
|
||||
<DialogTitle>{t("leave-comment")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
{status == "2"
|
||||
? files?.map((file, index) => (
|
||||
|
|
@ -725,7 +728,7 @@ export default function FormAudioDetail() {
|
|||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Semua
|
||||
{t("all")}
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
|
|
@ -870,7 +873,7 @@ export default function FormAudioDetail() {
|
|||
color="primary"
|
||||
onClick={() => submit()}
|
||||
>
|
||||
Submit
|
||||
{t("submit")}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
|
|
@ -879,7 +882,7 @@ export default function FormAudioDetail() {
|
|||
setModalOpen(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ import { Item } from "@radix-ui/react-dropdown-menu";
|
|||
import dynamic from "next/dynamic";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface FileWithPreview extends File {
|
||||
preview: string;
|
||||
|
|
@ -88,6 +89,7 @@ export default function FormAudio() {
|
|||
const scheduleType = Cookies.get("scheduleType");
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
|
||||
const t = useTranslations("Form");
|
||||
const [categories, setCategories] = useState<Category[]>([]);
|
||||
const [selectedCategory, setSelectedCategory] = useState<any>();
|
||||
const [tags, setTags] = useState<any[]>([]);
|
||||
|
|
@ -708,11 +710,11 @@ export default function FormAudio() {
|
|||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Konten Audio</p>
|
||||
<p className="text-lg font-semibold mb-3">{t("form-audio")}</p>
|
||||
<div className="gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2 py-3">
|
||||
<Label>Judul</Label>
|
||||
<Label>{t("title")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
|
|
@ -732,8 +734,8 @@ export default function FormAudio() {
|
|||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<div className="py-3 w-full">
|
||||
<Label>Kategori</Label>
|
||||
<div className="py-3 w-full space-y-2">
|
||||
<Label>{t("category")}</Label>
|
||||
<Select
|
||||
value={selectedCategory} // Ensure selectedTarget is updated correctly
|
||||
onValueChange={(id) => {
|
||||
|
|
@ -758,7 +760,7 @@ export default function FormAudio() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-3 py-2">
|
||||
<Label>Bantuan AI</Label>
|
||||
<Label>{t("ai-assistance")}</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
defaultChecked={isSwitchOn}
|
||||
|
|
@ -774,7 +776,7 @@ export default function FormAudio() {
|
|||
<div>
|
||||
<div className="flex flex-row gap-3">
|
||||
<div className="space-y-2 py-3 w-4/12">
|
||||
<Label>Bahasa</Label>
|
||||
<Label>{t("language")}</Label>
|
||||
<Select onValueChange={setSelectedLanguage}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
|
|
@ -786,7 +788,7 @@ export default function FormAudio() {
|
|||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2 py-3 w-4/12">
|
||||
<Label>Writing Style</Label>
|
||||
<Label>{t("writing-style")}</Label>
|
||||
<Select onValueChange={setSelectedWritingStyle}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
|
|
@ -805,7 +807,7 @@ export default function FormAudio() {
|
|||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2 py-3 w-4/12">
|
||||
<Label>Article Size</Label>
|
||||
<Label>{t("article-size")}</Label>
|
||||
<Select onValueChange={setSelectedSize}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
|
|
@ -826,7 +828,7 @@ export default function FormAudio() {
|
|||
</div>
|
||||
<div className="mt-5">
|
||||
<div className="flex flex-row items-center gap-3 mb-3">
|
||||
<Label>Main Keyword</Label>
|
||||
<Label>{t("main-keyword")}</Label>
|
||||
<Button
|
||||
variant="outline"
|
||||
color="primary"
|
||||
|
|
@ -850,7 +852,7 @@ export default function FormAudio() {
|
|||
</div>
|
||||
<div className="mt-5">
|
||||
<div className="flex flex-row items-center gap-3 mb-3">
|
||||
<Label>Judul</Label>
|
||||
<Label>{t("title")}</Label>
|
||||
<Button
|
||||
variant="outline"
|
||||
color="primary"
|
||||
|
|
@ -872,7 +874,7 @@ export default function FormAudio() {
|
|||
</div>
|
||||
<div className="mt-5">
|
||||
<div className="flex flex-row items-center gap-3 mb-3">
|
||||
<Label>SEO</Label>
|
||||
<Label>{t("seo")}</Label>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
color="primary"
|
||||
|
|
@ -883,15 +885,9 @@ export default function FormAudio() {
|
|||
</Button>
|
||||
</div>
|
||||
<p className="font-semibold">
|
||||
Kata kunci untuk disertakan dalam teks
|
||||
</p>
|
||||
<p className="text-sm">
|
||||
JIka Anda tidak Memberikan kata kunci, kami akan secara
|
||||
otomatis membuat kata kunci yang relevan dari kata kunci
|
||||
utama untuk setiap bagian dan menggunakannya untuk membuat
|
||||
artikel. Untuk menambahkan kata kunci baru, ketik ',
|
||||
+ kata kunci'.
|
||||
{t("Keywords to include in the text")}
|
||||
</p>
|
||||
<p className="text-sm">{t("title-key")}</p>
|
||||
<div className="mt-3">
|
||||
<Textarea
|
||||
value={selectedSEO}
|
||||
|
|
@ -901,7 +897,7 @@ export default function FormAudio() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Label>Instruksi Khusus (Optional)</Label>
|
||||
<Label>{t("special-instructions")} (Optional)</Label>
|
||||
<div className="mt-3">
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -959,7 +955,7 @@ export default function FormAudio() {
|
|||
variant={"outline"}
|
||||
color="primary"
|
||||
>
|
||||
Edit
|
||||
{t("update")}
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
|
|
@ -968,8 +964,8 @@ export default function FormAudio() {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="">
|
||||
<Label>Deskripsi</Label>
|
||||
<div className="space-y-2">
|
||||
<Label>{t("description")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
|
|
@ -992,8 +988,8 @@ export default function FormAudio() {
|
|||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="py-3">
|
||||
<Label>Pilih File</Label>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("select-file")}</Label>
|
||||
{/* <Input
|
||||
id="fileInput"
|
||||
type="file"
|
||||
|
|
@ -1006,11 +1002,10 @@ export default function FormAudio() {
|
|||
<CloudUpload className="text-default-300 w-10 h-10" />
|
||||
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
|
||||
{/* Drop files here or click to upload. */}
|
||||
Tarik file disini atau klik untuk upload.
|
||||
{t("drag-file")}
|
||||
</h4>
|
||||
<div className=" text-xs text-muted-foreground">
|
||||
( Upload file dengan mp3 atau wav Ukuran maksimal
|
||||
100mb.)
|
||||
{t("upload-file-audio-max")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1028,7 +1023,7 @@ export default function FormAudio() {
|
|||
color="destructive"
|
||||
onClick={handleRemoveAllFiles}
|
||||
>
|
||||
Remove All
|
||||
{t("remove-all")}
|
||||
</Button>
|
||||
</div>
|
||||
</Fragment>
|
||||
|
|
@ -1042,7 +1037,7 @@ export default function FormAudio() {
|
|||
<Card className=" h-[500px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Kreator</Label>
|
||||
<Label>{t("creator")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="creatorName"
|
||||
|
|
@ -1063,8 +1058,8 @@ export default function FormAudio() {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<Label htmlFor="tags">Tags</Label>
|
||||
<div className="px-3 py-3 space-y-2">
|
||||
<Label htmlFor="tags">{t("tags")}</Label>
|
||||
|
||||
<Input
|
||||
type="text"
|
||||
|
|
@ -1092,8 +1087,8 @@ export default function FormAudio() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label>Target Publish</Label>
|
||||
<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
|
||||
|
|
@ -1116,12 +1111,12 @@ export default function FormAudio() {
|
|||
<div className="flex flex-row justify-end gap-3">
|
||||
<div className="mt-4">
|
||||
<Button type="submit" color="primary">
|
||||
Submit
|
||||
{t("submit")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<Button type="submit" color="primary" variant="outline">
|
||||
Cancel
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ import { Icon } from "@iconify/react/dist/iconify.js";
|
|||
import { error } from "@/lib/swal";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
import { Upload } from "tus-js-client";
|
||||
import { useTranslations } from "next-intl";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
const audioSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -87,6 +89,13 @@ type Option = {
|
|||
name: string;
|
||||
};
|
||||
|
||||
const CustomEditor = dynamic(
|
||||
() => {
|
||||
return import("@/components/editor/custom-editor");
|
||||
},
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export default function FormAudioUpdate() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -108,6 +117,7 @@ export default function FormAudioUpdate() {
|
|||
const scheduleId = Cookies.get("scheduleId");
|
||||
const scheduleType = Cookies.get("scheduleType");
|
||||
|
||||
const t = useTranslations("Form");
|
||||
const [categories, setCategories] = useState<Category[]>([]);
|
||||
const [selectedCategory, setSelectedCategory] = useState<any>();
|
||||
const [tags, setTags] = useState<any[]>([]);
|
||||
|
|
@ -590,11 +600,11 @@ export default function FormAudioUpdate() {
|
|||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Konten Audio</p>
|
||||
<p className="text-lg font-semibold mb-3">{t("form-audio")}</p>
|
||||
<div className="gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2 py-3">
|
||||
<Label>Judul</Label>
|
||||
<Label>{t("title")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
|
|
@ -615,8 +625,8 @@ export default function FormAudioUpdate() {
|
|||
)}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="py-3 w-full">
|
||||
<Label>Kategori</Label>
|
||||
<div className="py-3 w-full space-y-2">
|
||||
<Label>{t("category")}</Label>
|
||||
<Select
|
||||
defaultValue={detail?.categoryId.name} // Nilai default berdasarkan detail
|
||||
onValueChange={(id) => {
|
||||
|
|
@ -638,17 +648,15 @@ export default function FormAudioUpdate() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-3">
|
||||
<Label>Deskripsi</Label>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("description")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<JoditEditor
|
||||
ref={editor}
|
||||
value={detail?.description}
|
||||
<CustomEditor
|
||||
onChange={onChange}
|
||||
className="dark:text-black"
|
||||
initialData={detail?.description || value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
@ -658,8 +666,8 @@ export default function FormAudioUpdate() {
|
|||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="py-3">
|
||||
<Label>Pilih File</Label>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("select-file")}</Label>
|
||||
{/* <Input
|
||||
id="fileInput"
|
||||
type="file"
|
||||
|
|
@ -672,11 +680,10 @@ export default function FormAudioUpdate() {
|
|||
<CloudUpload className="text-default-300 w-10 h-10" />
|
||||
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
|
||||
{/* Drop files here or click to upload. */}
|
||||
Tarik file disini atau klik untuk upload.
|
||||
{t("drag-file")}
|
||||
</h4>
|
||||
<div className=" text-xs text-muted-foreground">
|
||||
( Upload file dengan format .jpg, .jpeg, atau .png.
|
||||
Ukuran maksimal 100mb.)
|
||||
{t("upload-file-audio-max")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -685,7 +692,7 @@ export default function FormAudioUpdate() {
|
|||
<div>{fileList}</div>
|
||||
<div className=" flex justify-between gap-2">
|
||||
<div className="flex flex-row items-center gap-3 py-3">
|
||||
<Label>Gunakan Watermark</Label>
|
||||
<Label>{t("watermark")}</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch defaultChecked color="primary" id="c2" />
|
||||
</div>
|
||||
|
|
@ -701,7 +708,10 @@ export default function FormAudioUpdate() {
|
|||
) : null}
|
||||
{files.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<Label className="text-lg font-semibold">File</Label>
|
||||
<Label className="text-lg font-semibold">
|
||||
{" "}
|
||||
{t("file-media")}
|
||||
</Label>
|
||||
<div className="grid gap-4">
|
||||
{files.map((file: any) => (
|
||||
<div
|
||||
|
|
@ -722,7 +732,7 @@ export default function FormAudioUpdate() {
|
|||
rel="noopener noreferrer"
|
||||
className="text-blue-500 text-sm"
|
||||
>
|
||||
Lihat File
|
||||
{t("view-file")}
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -740,7 +750,7 @@ export default function FormAudioUpdate() {
|
|||
}
|
||||
className="form-checkbox"
|
||||
/>
|
||||
<span>Semua</span>
|
||||
<span>{t("all")}</span>
|
||||
</Label>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -812,7 +822,7 @@ export default function FormAudioUpdate() {
|
|||
<Card className=" h-[800px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Kreator</Label>
|
||||
<Label>{t("creator")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="creatorName"
|
||||
|
|
@ -835,7 +845,7 @@ export default function FormAudioUpdate() {
|
|||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<Label>{t("tags")}</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="tags"
|
||||
|
|
@ -869,8 +879,8 @@ export default function FormAudioUpdate() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<div className="flex flex-col gap-6">
|
||||
<Label>Target Publish</Label>
|
||||
<div className="flex flex-col gap-6 space-y-2">
|
||||
<Label>{t("publish-target")}</Label>
|
||||
{options.map((option: Option) => (
|
||||
<div key={option.id} className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
|
|
@ -890,22 +900,22 @@ export default function FormAudioUpdate() {
|
|||
</div>
|
||||
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
||||
<MailIcon />
|
||||
<p className="">Kotak Saran (0)</p>
|
||||
<p className="">{t("suggestion-box")} (0)</p>
|
||||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<p>Keterangan:</p>
|
||||
<p>{t("information")}:</p>
|
||||
{/* <p>{detail?.status}</p> */}
|
||||
</div>
|
||||
</Card>
|
||||
<div className="flex flex-row justify-end gap-3">
|
||||
<div className="mt-4">
|
||||
<Button type="submit" color="primary">
|
||||
Submit
|
||||
{t("submit")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<Button type="submit" color="primary" variant="outline">
|
||||
Cancel
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ import { Icon } from "@iconify/react/dist/iconify.js";
|
|||
import { error } from "@/lib/swal";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useRouter } from "@/i18n/routing";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const imageSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -119,6 +120,7 @@ export default function FormImageDetail() {
|
|||
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");
|
||||
|
|
@ -418,11 +420,11 @@ export default function FormImageDetail() {
|
|||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Konten Foto</p>
|
||||
<p className="text-lg font-semibold mb-3">{t("form-image")}</p>
|
||||
<div className="gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2 py-3">
|
||||
<Label>Judul</Label>
|
||||
<Label>{t("title")} </Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
|
|
@ -443,8 +445,8 @@ export default function FormImageDetail() {
|
|||
)}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="py-3 w-full">
|
||||
<Label>Kategori</Label>
|
||||
<div className="py-3 w-full space-y-2">
|
||||
<Label>{t("category")}</Label>
|
||||
<Select
|
||||
value={detail?.category.name} // Nilai default berdasarkan detail
|
||||
onValueChange={(id) => {
|
||||
|
|
@ -466,8 +468,8 @@ export default function FormImageDetail() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-3">
|
||||
<Label>Deskripsi</Label>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("description")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
|
|
@ -481,46 +483,47 @@ export default function FormImageDetail() {
|
|||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Label className="text-xl text-black">File Media</Label>
|
||||
<div className="w-full ">
|
||||
<Swiper
|
||||
thumbs={{ swiper: thumbsSwiper }}
|
||||
modules={[FreeMode, Navigation, Thumbs]}
|
||||
navigation={false}
|
||||
className="w-full"
|
||||
>
|
||||
{detailThumb?.map((data: any) => (
|
||||
<SwiperSlide key={data.id}>
|
||||
<img
|
||||
className="object-fill h-full w-full rounded-md"
|
||||
src={data}
|
||||
alt={` ${data.id}`}
|
||||
/>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
<div className=" mt-2 ">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xl ">{t("file-media")}</Label>
|
||||
<div className="w-full ">
|
||||
<Swiper
|
||||
onSwiper={setThumbsSwiper}
|
||||
slidesPerView={6}
|
||||
spaceBetween={8}
|
||||
pagination={{
|
||||
clickable: true,
|
||||
}}
|
||||
modules={[Pagination, Thumbs]}
|
||||
// className="mySwiper2"
|
||||
thumbs={{ swiper: thumbsSwiper }}
|
||||
modules={[FreeMode, Navigation, Thumbs]}
|
||||
navigation={false}
|
||||
className="w-full"
|
||||
>
|
||||
{detailThumb?.map((data: any) => (
|
||||
<SwiperSlide key={data.id}>
|
||||
<img
|
||||
className="object-cover h-[60px] w-[80px]"
|
||||
className="object-fill h-full w-full rounded-md"
|
||||
src={data}
|
||||
alt={` ${data.id}`}
|
||||
/>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
<div className=" mt-2 ">
|
||||
<Swiper
|
||||
onSwiper={setThumbsSwiper}
|
||||
slidesPerView={6}
|
||||
spaceBetween={8}
|
||||
pagination={{
|
||||
clickable: true,
|
||||
}}
|
||||
modules={[Pagination, Thumbs]}
|
||||
// className="mySwiper2"
|
||||
>
|
||||
{detailThumb?.map((data: any) => (
|
||||
<SwiperSlide key={data.id}>
|
||||
<img
|
||||
className="object-cover h-[60px] w-[80px]"
|
||||
src={data}
|
||||
alt={` ${data.id}`}
|
||||
/>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -530,7 +533,7 @@ export default function FormImageDetail() {
|
|||
<Card className=" h-[1050px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Kreator</Label>
|
||||
<Label>{t("creator")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="creatorName"
|
||||
|
|
@ -551,8 +554,8 @@ export default function FormImageDetail() {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 px-3">
|
||||
<Label>Pratinjau Gambar Utama</Label>
|
||||
<div className="mt-3 px-3 space-y-2">
|
||||
<Label>{t("preview")}</Label>
|
||||
<Card className="mt-2">
|
||||
<img
|
||||
src={detail.thumbnailLink}
|
||||
|
|
@ -563,7 +566,7 @@ export default function FormImageDetail() {
|
|||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<Label>{t("tags")}</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{detail?.tags
|
||||
?.split(",")
|
||||
|
|
@ -579,8 +582,8 @@ export default function FormImageDetail() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<div className="flex flex-col gap-6">
|
||||
<Label>Target Publish</Label>
|
||||
<div className="flex flex-col gap-6 space-y-2">
|
||||
<Label>{t("publish-target")}</Label>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="5"
|
||||
|
|
@ -617,10 +620,10 @@ export default function FormImageDetail() {
|
|||
</div>
|
||||
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
||||
<MailIcon />
|
||||
<p className="">Kotak Saran (0)</p>
|
||||
<p className="">{t("suggestion-box")} (0)</p>
|
||||
</div>
|
||||
<div className="px-3 py-3 border mx-3">
|
||||
<p>Keterangan:</p>
|
||||
<p>{t("information")}:</p>
|
||||
<p className="text-sm text-slate-400">{detail?.statusName}</p>
|
||||
</div>
|
||||
{/* {detail?.isPublish == false ? (
|
||||
|
|
@ -672,14 +675,16 @@ export default function FormImageDetail() {
|
|||
color="primary"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:check" className="mr-3" /> Setujui
|
||||
<Icon icon="fa:check" className="mr-3" />
|
||||
{t("accept")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => actionApproval("3")}
|
||||
className="bg-orange-400 hover:bg-orange-300"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:comment-o" className="mr-3" /> Revisi
|
||||
<Icon icon="fa:comment-o" className="mr-3" />{" "}
|
||||
{t("revision")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => actionApproval("4")}
|
||||
|
|
@ -687,7 +692,7 @@ export default function FormImageDetail() {
|
|||
type="button"
|
||||
>
|
||||
<Icon icon="fa:times" className="mr-3" />
|
||||
Tolak
|
||||
{t("reject")}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
|
@ -698,7 +703,7 @@ export default function FormImageDetail() {
|
|||
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
||||
<DialogContent size="md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Berikan Komentar</DialogTitle>
|
||||
<DialogTitle>{t("leave-comment")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
{status == "2"
|
||||
? files?.map((file, index) => (
|
||||
|
|
@ -735,7 +740,7 @@ export default function FormImageDetail() {
|
|||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Semua
|
||||
{t("all")}
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
|
|
@ -880,7 +885,7 @@ export default function FormImageDetail() {
|
|||
color="primary"
|
||||
onClick={() => submit()}
|
||||
>
|
||||
Submit
|
||||
{t("submit")}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
|
|
@ -889,7 +894,7 @@ export default function FormImageDetail() {
|
|||
setModalOpen(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
|
|
|||
|
|
@ -57,6 +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";
|
||||
|
||||
interface FileWithPreview extends File {
|
||||
preview: string;
|
||||
|
|
@ -85,6 +86,7 @@ export default function FormImage() {
|
|||
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");
|
||||
|
|
@ -714,11 +716,11 @@ export default function FormImage() {
|
|||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Konten Foto</p>
|
||||
<p className="text-lg font-semibold mb-3">{t("form-image")}</p>
|
||||
<div className="gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2 py-3">
|
||||
<Label>Judul</Label>
|
||||
<Label>{t("title")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
|
|
@ -738,8 +740,8 @@ export default function FormImage() {
|
|||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<div className="py-3 w-full">
|
||||
<Label>Kategori</Label>
|
||||
<div className="py-3 space-y-2 w-full">
|
||||
<Label>{t("category")}</Label>
|
||||
<Select
|
||||
value={selectedCategory} // Ensure selectedTarget is updated correctly
|
||||
onValueChange={(id) => {
|
||||
|
|
@ -763,8 +765,8 @@ export default function FormImage() {
|
|||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-3 py-2">
|
||||
<Label>Bantuan AI</Label>
|
||||
<div className="flex flex-row items-center gap-3 py-3 ">
|
||||
<Label>{t("ai-assistance")}</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
defaultChecked={isSwitchOn}
|
||||
|
|
@ -780,7 +782,7 @@ export default function FormImage() {
|
|||
<div>
|
||||
<div className="flex flex-row gap-3">
|
||||
<div className="space-y-2 py-3 w-4/12">
|
||||
<Label>Bahasa</Label>
|
||||
<Label>{t("language")}</Label>
|
||||
<Select onValueChange={setSelectedLanguage}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
|
|
@ -792,7 +794,7 @@ export default function FormImage() {
|
|||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2 py-3 w-4/12">
|
||||
<Label>Writing Style</Label>
|
||||
<Label>{t("writing-style")}</Label>
|
||||
<Select onValueChange={setSelectedWritingStyle}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
|
|
@ -811,7 +813,7 @@ export default function FormImage() {
|
|||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2 py-3 w-4/12">
|
||||
<Label>Article Size</Label>
|
||||
<Label>{t("article-size")}</Label>
|
||||
<Select onValueChange={setSelectedSize}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
|
|
@ -832,7 +834,7 @@ export default function FormImage() {
|
|||
</div>
|
||||
<div className="mt-5">
|
||||
<div className="flex flex-row items-center gap-3 mb-3">
|
||||
<Label>Main Keyword</Label>
|
||||
<Label>{t("main-keyword")}</Label>
|
||||
<Button
|
||||
variant="outline"
|
||||
color="primary"
|
||||
|
|
@ -856,7 +858,7 @@ export default function FormImage() {
|
|||
</div>
|
||||
<div className="mt-5">
|
||||
<div className="flex flex-row items-center gap-3 mb-3">
|
||||
<Label>Judul</Label>
|
||||
<Label>{t("title")}</Label>
|
||||
<Button
|
||||
variant="outline"
|
||||
color="primary"
|
||||
|
|
@ -878,7 +880,7 @@ export default function FormImage() {
|
|||
</div>
|
||||
<div className="mt-5">
|
||||
<div className="flex flex-row items-center gap-3 mb-3">
|
||||
<Label>SEO</Label>
|
||||
<Label>{t("seo")}</Label>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
color="primary"
|
||||
|
|
@ -889,15 +891,9 @@ export default function FormImage() {
|
|||
</Button>
|
||||
</div>
|
||||
<p className="font-semibold">
|
||||
Kata kunci untuk disertakan dalam teks
|
||||
</p>
|
||||
<p className="text-sm">
|
||||
JIka Anda tidak Memberikan kata kunci, kami akan secara
|
||||
otomatis membuat kata kunci yang relevan dari kata kunci
|
||||
utama untuk setiap bagian dan menggunakannya untuk membuat
|
||||
artikel. Untuk menambahkan kata kunci baru, ketik ',
|
||||
+ kata kunci'.
|
||||
{t("Keywords to include in the text")}
|
||||
</p>
|
||||
<p className="text-sm">{t("title-key")}</p>
|
||||
<div className="mt-3">
|
||||
<Textarea
|
||||
value={selectedSEO}
|
||||
|
|
@ -907,7 +903,7 @@ export default function FormImage() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Label>Instruksi Khusus (Optional)</Label>
|
||||
<Label>{t("special-instructions")}(Optional)</Label>
|
||||
<div className="mt-3">
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -964,7 +960,7 @@ export default function FormImage() {
|
|||
variant={"outline"}
|
||||
color="primary"
|
||||
>
|
||||
Edit
|
||||
{t("update")}
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
|
|
@ -973,8 +969,8 @@ export default function FormImage() {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="">
|
||||
<Label>Deskripsi</Label>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("description")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
|
|
@ -997,8 +993,8 @@ export default function FormImage() {
|
|||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="py-3">
|
||||
<Label>Pilih File</Label>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("select-file")}</Label>
|
||||
{/* <Input
|
||||
id="fileInput"
|
||||
type="file"
|
||||
|
|
@ -1011,11 +1007,10 @@ export default function FormImage() {
|
|||
<CloudUpload className="text-default-300 w-10 h-10" />
|
||||
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
|
||||
{/* Drop files here or click to upload. */}
|
||||
Tarik file disini atau klik untuk upload.
|
||||
{t("drag-file")}
|
||||
</h4>
|
||||
<div className=" text-xs text-muted-foreground">
|
||||
( Upload file dengan format .jpg, .jpeg, atau .png.
|
||||
Ukuran maksimal 100mb.)
|
||||
{t("upload-file-max")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1024,7 +1019,7 @@ export default function FormImage() {
|
|||
<div>{fileList}</div>
|
||||
<div className=" flex justify-between gap-2">
|
||||
<div className="flex flex-row items-center gap-3 py-3">
|
||||
<Label>Gunakan Watermark</Label>
|
||||
<Label>{t("watermark")}</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch defaultChecked color="primary" id="c2" />
|
||||
</div>
|
||||
|
|
@ -1033,7 +1028,7 @@ export default function FormImage() {
|
|||
color="destructive"
|
||||
onClick={handleRemoveAllFiles}
|
||||
>
|
||||
Remove All
|
||||
{t("remove-all")}
|
||||
</Button>
|
||||
</div>
|
||||
</Fragment>
|
||||
|
|
@ -1049,7 +1044,7 @@ export default function FormImage() {
|
|||
<Card className=" h-[500px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Kreator</Label>
|
||||
<Label>{t("creator")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="creatorName"
|
||||
|
|
@ -1070,9 +1065,8 @@ export default function FormImage() {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<Label htmlFor="tags">Tags</Label>
|
||||
|
||||
<div className="px-3 py-3 space-y-2">
|
||||
<Label htmlFor="tags">{t("tags")}</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="tags"
|
||||
|
|
@ -1099,8 +1093,8 @@ export default function FormImage() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label>Target Publish</Label>
|
||||
<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
|
||||
|
|
@ -1123,13 +1117,15 @@ export default function FormImage() {
|
|||
<div className="flex flex-row justify-end gap-3">
|
||||
<div className="mt-4">
|
||||
<Button type="submit" color="primary">
|
||||
Submit
|
||||
{t("submit")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<Button type="submit" color="primary" variant="outline">
|
||||
Cancel
|
||||
</Button>
|
||||
<Link href={"/contributor/content/image"}>
|
||||
<Button type="submit" color="primary" variant="outline">
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import Image from "next/image";
|
|||
import { error, loading } from "@/lib/swal";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
import { Upload } from "tus-js-client";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const imageSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -114,7 +115,7 @@ export default function FormImageUpdate() {
|
|||
let uploadPersen = 0;
|
||||
const [isStartUpload, setIsStartUpload] = useState(false);
|
||||
const [counterProgress, setCounterProgress] = useState(0);
|
||||
|
||||
const t = useTranslations("Form");
|
||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||
const taskId = Cookies.get("taskId");
|
||||
const scheduleId = Cookies.get("scheduleId");
|
||||
|
|
@ -628,11 +629,11 @@ export default function FormImageUpdate() {
|
|||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Konten Foto</p>
|
||||
<p className="text-lg font-semibold mb-3">{t("form-image")}</p>
|
||||
<div className="gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2 py-3">
|
||||
<Label>Judul</Label>
|
||||
<Label>{t("title")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
|
|
@ -653,8 +654,8 @@ export default function FormImageUpdate() {
|
|||
)}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="py-3 w-full">
|
||||
<Label>Kategori</Label>
|
||||
<div className="py-3 w-full space-y-2">
|
||||
<Label>{t("category")}</Label>
|
||||
<Select
|
||||
defaultValue={detail?.category.id} // Gunakan ID sebagai defaultValue
|
||||
onValueChange={(id) => {
|
||||
|
|
@ -679,8 +680,8 @@ export default function FormImageUpdate() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-3">
|
||||
<Label>Deskripsi</Label>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("description")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
|
|
@ -697,8 +698,8 @@ export default function FormImageUpdate() {
|
|||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="py-3">
|
||||
<Label>Pilih File</Label>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("select-file")}</Label>
|
||||
{/* <Input
|
||||
id="fileInput"
|
||||
type="file"
|
||||
|
|
@ -711,11 +712,10 @@ export default function FormImageUpdate() {
|
|||
<CloudUpload className="text-default-300 w-10 h-10" />
|
||||
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
|
||||
{/* Drop files here or click to upload. */}
|
||||
Tarik file disini atau klik untuk upload.
|
||||
{t("drag-file")}
|
||||
</h4>
|
||||
<div className=" text-xs text-muted-foreground">
|
||||
( Upload file dengan format .jpg, .jpeg, atau .png.
|
||||
Ukuran maksimal 100mb.)
|
||||
{t("upload-file-max")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -724,7 +724,7 @@ export default function FormImageUpdate() {
|
|||
<div>{fileList}</div>
|
||||
<div className=" flex justify-between gap-2">
|
||||
<div className="flex flex-row items-center gap-3 py-3">
|
||||
<Label>Gunakan Watermark</Label>
|
||||
<Label>{t("watermark")}</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch defaultChecked color="primary" id="c2" />
|
||||
</div>
|
||||
|
|
@ -740,7 +740,10 @@ export default function FormImageUpdate() {
|
|||
) : null}
|
||||
{files.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<Label className="text-lg font-semibold">File</Label>
|
||||
<Label className="text-lg font-semibold">
|
||||
{" "}
|
||||
{t("file-media")}
|
||||
</Label>
|
||||
<div className="grid gap-4">
|
||||
{files.map((file: any) => (
|
||||
<div
|
||||
|
|
@ -761,7 +764,7 @@ export default function FormImageUpdate() {
|
|||
rel="noopener noreferrer"
|
||||
className="text-blue-500 text-sm"
|
||||
>
|
||||
Lihat File
|
||||
{t("view-file")}
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -779,7 +782,7 @@ export default function FormImageUpdate() {
|
|||
}
|
||||
className="form-checkbox"
|
||||
/>
|
||||
<span>Semua</span>
|
||||
<span>{t("all")}</span>
|
||||
</Label>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -851,7 +854,7 @@ export default function FormImageUpdate() {
|
|||
<Card className="h-[900px] md:h-[1100px] lg:h-[800px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Kreator</Label>
|
||||
<Label>{t("creator")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="creatorName"
|
||||
|
|
@ -872,8 +875,8 @@ export default function FormImageUpdate() {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 px-3">
|
||||
<Label>Pratinjau Gambar Utama</Label>
|
||||
<div className="mt-3 px-3 space-y-2">
|
||||
<Label>{t("preview")}</Label>
|
||||
<Card className="mt-2">
|
||||
<img
|
||||
src={detail.thumbnailLink}
|
||||
|
|
@ -884,7 +887,7 @@ export default function FormImageUpdate() {
|
|||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<Label>{t("tags")}</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="tags"
|
||||
|
|
@ -928,8 +931,8 @@ export default function FormImageUpdate() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<div className="flex flex-col gap-6">
|
||||
<Label>Target Publish</Label>
|
||||
<div className="flex flex-col gap-6 space-y-2">
|
||||
<Label>{t("publish-target")}</Label>
|
||||
{options.map((option: Option) => (
|
||||
<div key={option.id} className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
|
|
@ -949,22 +952,22 @@ export default function FormImageUpdate() {
|
|||
</div>
|
||||
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
||||
<MailIcon />
|
||||
<p className="">Kotak Saran (0)</p>
|
||||
<p className="">{t("suggestion-box")} (0)</p>
|
||||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<p>Keterangan:</p>
|
||||
<p>{t("information")}:</p>
|
||||
{/* <p>{detail?.status}</p> */}
|
||||
</div>
|
||||
</Card>
|
||||
<div className="flex flex-row justify-end gap-3">
|
||||
<div className="mt-4">
|
||||
<Button type="submit" color="primary">
|
||||
Submit
|
||||
{t("submit")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<Button type="submit" color="primary" variant="outline">
|
||||
Cancel
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ import { generateDataArticle, getDetailArticle } from "@/service/content/ai";
|
|||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
import dynamic from "next/dynamic";
|
||||
import { error } from "@/lib/swal";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const imageSchema = z.object({
|
||||
contentTitle: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -144,6 +145,7 @@ export default function FormConvertSPIT() {
|
|||
const [selectedArticleId, setSelectedArticleId] = useState<string | null>(
|
||||
null
|
||||
);
|
||||
const t = useTranslations("Form");
|
||||
const [detailData, setDetailData] = useState<any>(null);
|
||||
const [selectedFileType, setSelectedFileType] = useState("original");
|
||||
const [isLoadingData, setIsLoadingData] = useState<boolean>(false);
|
||||
|
|
@ -616,11 +618,11 @@ export default function FormConvertSPIT() {
|
|||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Konten Foto</p>
|
||||
<p className="text-lg font-semibold mb-3">{t("form-spit")}</p>
|
||||
<div className="gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2 py-3">
|
||||
<Label>Judul</Label>
|
||||
<Label>{t("title")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="contentTitle"
|
||||
|
|
@ -641,8 +643,8 @@ export default function FormConvertSPIT() {
|
|||
)}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="py-3 w-full">
|
||||
<Label>Kategori</Label>
|
||||
<div className="py-3 w-full space-y-2">
|
||||
<Label>{t("category")}</Label>
|
||||
<Select
|
||||
defaultValue={detail?.content?.name}
|
||||
onValueChange={(id) => {
|
||||
|
|
@ -678,8 +680,8 @@ export default function FormConvertSPIT() {
|
|||
Select Original File
|
||||
</Label>
|
||||
</div>
|
||||
<div className="py-3 ">
|
||||
<Label>Deskripsi</Label>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("description")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="contentDescription"
|
||||
|
|
@ -733,11 +735,11 @@ export default function FormConvertSPIT() {
|
|||
<div className="flex items-center space-x-2 mt-3">
|
||||
<RadioGroupItem value="rewrite" id="rewrite-file" />
|
||||
<Label htmlFor="rewrite-file">
|
||||
Select File Hasil Rewrite
|
||||
Select File Rewrite
|
||||
</Label>
|
||||
</div>
|
||||
<div className="py-3 ">
|
||||
<Label>File hasil Rewrite</Label>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("file-rewrite")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="contentRewriteDescription"
|
||||
|
|
@ -766,8 +768,8 @@ export default function FormConvertSPIT() {
|
|||
)}
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-xl">File Media</Label>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xl">{t("file-media")}</Label>
|
||||
<div className="w-full ">
|
||||
<Swiper
|
||||
thumbs={{ swiper: thumbsSwiper }}
|
||||
|
|
@ -810,10 +812,10 @@ export default function FormConvertSPIT() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<Label className="text-xl">Penempatan file</Label>
|
||||
<Label className="text-xl">{t("file-placement")}</Label>
|
||||
</div>
|
||||
{files?.length > 1 && (
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
<div className="flex flex-wrap gap-2 mt-2 justify-end mr-24 pr-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="all-content"
|
||||
|
|
@ -825,7 +827,7 @@ export default function FormConvertSPIT() {
|
|||
htmlFor="all-content"
|
||||
className="text-xs font-medium"
|
||||
>
|
||||
All
|
||||
{t("all")}
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
|
|
@ -839,7 +841,7 @@ export default function FormConvertSPIT() {
|
|||
htmlFor="all-nasional"
|
||||
className="text-xs font-medium"
|
||||
>
|
||||
All Nasional
|
||||
{t("all")} Nasional
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
|
|
@ -853,7 +855,7 @@ export default function FormConvertSPIT() {
|
|||
htmlFor="all-wilayah"
|
||||
className="text-xs font-medium"
|
||||
>
|
||||
All Wilayah
|
||||
{t("all")} Wilayah
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
|
|
@ -867,7 +869,7 @@ export default function FormConvertSPIT() {
|
|||
htmlFor="all-international"
|
||||
className="text-xs font-medium"
|
||||
>
|
||||
All Internasional
|
||||
{t("all")} Internasional
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -879,20 +881,12 @@ export default function FormConvertSPIT() {
|
|||
>
|
||||
<img
|
||||
src={file.contentFile}
|
||||
className="w-[150px] rounded-md"
|
||||
className="w-[180px] rounded-md"
|
||||
/>
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<div className="flex flex-col gap-2 w-full pl-4">
|
||||
<div className="flex justify-between text-sm">
|
||||
{file.fileName}
|
||||
{/* <a
|
||||
onClick={() =>
|
||||
handleDeleteFileApproval(file.id)
|
||||
}
|
||||
>
|
||||
<Icon icon="humbleicons:times" color="red" />
|
||||
</a> */}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
|
|
@ -907,7 +901,7 @@ export default function FormConvertSPIT() {
|
|||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Semua
|
||||
{t("all")}
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
|
|
@ -969,7 +963,7 @@ export default function FormConvertSPIT() {
|
|||
<Card className=" h-[800px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Kreator</Label>
|
||||
<Label>{t("creator")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="contentCreator"
|
||||
|
|
@ -977,7 +971,7 @@ export default function FormConvertSPIT() {
|
|||
<Input
|
||||
size="md"
|
||||
type="text"
|
||||
defaultValue={detail?.contentCreator}
|
||||
value={detail?.contentCreator}
|
||||
onChange={field.onChange}
|
||||
placeholder="Enter Title"
|
||||
/>
|
||||
|
|
@ -991,7 +985,7 @@ export default function FormConvertSPIT() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="mt-3 px-3">
|
||||
<Label>Pratinjau Gambar Utama</Label>
|
||||
<Label>{t("preview")}</Label>
|
||||
<Card className="mt-2">
|
||||
<img
|
||||
src={detail.contentThumbnail}
|
||||
|
|
@ -1002,7 +996,7 @@ export default function FormConvertSPIT() {
|
|||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<Label>{t("tags")}</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{detail?.contentTag
|
||||
?.split(",")
|
||||
|
|
@ -1018,8 +1012,8 @@ export default function FormConvertSPIT() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label>Target Publish</Label>
|
||||
<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
|
||||
|
|
@ -1040,17 +1034,17 @@ export default function FormConvertSPIT() {
|
|||
</div>
|
||||
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
||||
<MailIcon />
|
||||
<p className="">Kotak Saran (0)</p>
|
||||
<p className="">{t("suggestion-box")} (0)</p>
|
||||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<p>Keterangan:</p>
|
||||
<p>{t("information")}:</p>
|
||||
{/* <p>{detail?.status}</p> */}
|
||||
</div>
|
||||
</Card>
|
||||
<div className="flex flex-row justify-end gap-3">
|
||||
<div className="mt-4">
|
||||
<Button type="submit" color="primary">
|
||||
Submit
|
||||
{t("submit")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
|
|
@ -1059,7 +1053,7 @@ export default function FormConvertSPIT() {
|
|||
className="bg-red-500 hover:bg-red-700"
|
||||
onClick={() => deleteSpitContent()}
|
||||
>
|
||||
Delete
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ import { getCookiesDecrypt } from "@/lib/utils";
|
|||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { error } from "@/lib/swal";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const imageSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -133,6 +134,7 @@ export default function FormTeksDetail() {
|
|||
const [detailThumb, setDetailThumb] = useState<any>([]);
|
||||
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
||||
|
||||
const t = useTranslations("Form");
|
||||
const [selectedTarget, setSelectedTarget] = useState("");
|
||||
const [files, setFiles] = useState<FileType[]>([]);
|
||||
const [rejectedFiles, setRejectedFiles] = useState<number[]>([]);
|
||||
|
|
@ -421,13 +423,11 @@ export default function FormTeksDetail() {
|
|||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">
|
||||
Form detail Konten Teks
|
||||
</p>
|
||||
<p className="text-lg font-semibold mb-3">{t("form-text")}</p>
|
||||
<div className="gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2 py-3">
|
||||
<Label>Judul</Label>
|
||||
<Label>{t("title")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
|
|
@ -448,8 +448,8 @@ export default function FormTeksDetail() {
|
|||
)}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="py-3 w-full">
|
||||
<Label>Kategori</Label>
|
||||
<div className="py-3 w-full space-y-2">
|
||||
<Label>{t("category")}</Label>
|
||||
<Select
|
||||
value={detail?.category.name} // Nilai default berdasarkan detail
|
||||
onValueChange={(id) => {
|
||||
|
|
@ -471,8 +471,8 @@ export default function FormTeksDetail() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-3">
|
||||
<Label>Deskripsi</Label>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("description")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
|
|
@ -486,81 +486,85 @@ export default function FormTeksDetail() {
|
|||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Label className="text-xl text-black">File Media</Label>
|
||||
<div className="w-full">
|
||||
<Swiper
|
||||
thumbs={{ swiper: thumbsSwiper }}
|
||||
modules={[FreeMode, Navigation, Thumbs]}
|
||||
navigation={false}
|
||||
className="w-full"
|
||||
>
|
||||
{detailThumb?.map((data: any, index: number) => (
|
||||
<SwiperSlide key={index}>
|
||||
{[".jpg", ".jpeg", ".png", ".webp"].includes(
|
||||
data.format
|
||||
) ? (
|
||||
// Menampilkan gambar
|
||||
<img
|
||||
className="object-fill h-full w-full rounded-md"
|
||||
src={data.url}
|
||||
alt={data.fileName || "File"}
|
||||
/>
|
||||
) : data.format === ".pdf" ? (
|
||||
// Menampilkan PDF menggunakan iframe
|
||||
<iframe
|
||||
className="w-full h-96 rounded-md"
|
||||
src={data.url}
|
||||
title={data.fileName || "PDF File"}
|
||||
/>
|
||||
) : [".docx", ".ppt", ".pptx"].includes(data.format) ? (
|
||||
// Menampilkan file dokumen menggunakan Office Viewer
|
||||
<iframe
|
||||
className="w-full h-96 rounded-md"
|
||||
src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
|
||||
data.url
|
||||
)}`}
|
||||
title={data.fileName || "Document"}
|
||||
/>
|
||||
) : (
|
||||
// Menampilkan link jika format tidak dikenali
|
||||
<a
|
||||
href={data.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block text-blue-500 underline"
|
||||
>
|
||||
View {data.fileName || "File"}
|
||||
</a>
|
||||
)}
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
<div className="mt-2 ">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xl">{t("file-media")} </Label>
|
||||
<div className="w-full">
|
||||
<Swiper
|
||||
onSwiper={setThumbsSwiper}
|
||||
slidesPerView={8}
|
||||
spaceBetween={8}
|
||||
pagination={{ clickable: true }}
|
||||
modules={[Pagination, Thumbs]}
|
||||
thumbs={{ swiper: thumbsSwiper }}
|
||||
modules={[FreeMode, Navigation, Thumbs]}
|
||||
navigation={false}
|
||||
className="w-full"
|
||||
>
|
||||
{detailThumb?.map((data: any, index: number) => (
|
||||
<SwiperSlide key={index}>
|
||||
{[".jpg", ".jpeg", ".png", ".webp"].includes(
|
||||
data.format
|
||||
) ? (
|
||||
// Menampilkan gambar
|
||||
<img
|
||||
className="object-cover h-[60px] w-[80px]"
|
||||
className="object-fill h-full w-full rounded-md"
|
||||
src={data.url}
|
||||
alt={data.fileName}
|
||||
alt={data.fileName || "File"}
|
||||
/>
|
||||
) : data.format === ".pdf" ? (
|
||||
// Menampilkan PDF menggunakan iframe
|
||||
<iframe
|
||||
className="w-full h-96 rounded-md"
|
||||
src={data.url}
|
||||
title={data.fileName || "PDF File"}
|
||||
/>
|
||||
) : [".docx", ".ppt", ".pptx"].includes(
|
||||
data.format
|
||||
) ? (
|
||||
// Menampilkan file dokumen menggunakan Office Viewer
|
||||
<iframe
|
||||
className="w-full h-96 rounded-md"
|
||||
src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
|
||||
data.url
|
||||
)}`}
|
||||
title={data.fileName || "Document"}
|
||||
/>
|
||||
) : (
|
||||
<div className="h-[60px] w-[80px] flex items-center justify-center bg-gray-200 text-sm text-center text-gray-700 rounded-md">
|
||||
{data?.format?.replace(".", "").toUpperCase()}
|
||||
</div>
|
||||
// Menampilkan link jika format tidak dikenali
|
||||
<a
|
||||
href={data.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block text-blue-500 underline"
|
||||
>
|
||||
View {data.fileName || "File"}
|
||||
</a>
|
||||
)}
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
<div className="mt-2 ">
|
||||
<Swiper
|
||||
onSwiper={setThumbsSwiper}
|
||||
slidesPerView={8}
|
||||
spaceBetween={8}
|
||||
pagination={{ clickable: true }}
|
||||
modules={[Pagination, Thumbs]}
|
||||
>
|
||||
{detailThumb?.map((data: any, index: number) => (
|
||||
<SwiperSlide key={index}>
|
||||
{[".jpg", ".jpeg", ".png", ".webp"].includes(
|
||||
data.format
|
||||
) ? (
|
||||
<img
|
||||
className="object-cover h-[60px] w-[80px]"
|
||||
src={data.url}
|
||||
alt={data.fileName}
|
||||
/>
|
||||
) : (
|
||||
<div className="h-[60px] w-[80px] flex items-center justify-center bg-gray-200 text-sm text-center text-gray-700 rounded-md">
|
||||
{data?.format?.replace(".", "").toUpperCase()}
|
||||
</div>
|
||||
)}
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -570,7 +574,7 @@ export default function FormTeksDetail() {
|
|||
<Card className=" h-[800px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Kreator</Label>
|
||||
<Label>{t("creator")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="creatorName"
|
||||
|
|
@ -603,7 +607,7 @@ export default function FormTeksDetail() {
|
|||
</div> */}
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<Label>{t("tags")}</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{detail?.tags
|
||||
?.split(",")
|
||||
|
|
@ -619,8 +623,8 @@ export default function FormTeksDetail() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<div className="flex flex-col gap-6">
|
||||
<Label>Target Publish</Label>
|
||||
<div className="flex flex-col gap-6 space-y-2">
|
||||
<Label>{t("publish-target")}</Label>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="5"
|
||||
|
|
@ -657,10 +661,10 @@ export default function FormTeksDetail() {
|
|||
</div>
|
||||
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
||||
<MailIcon />
|
||||
<p className="">Kotak Saran (0)</p>
|
||||
<p className="">{t("suggestion-box")} (0)</p>
|
||||
</div>
|
||||
<div className="px-3 py-3 border mx-3">
|
||||
<p>Keterangan:</p>
|
||||
<p>{t("information")}:</p>
|
||||
<p className="text-sm text-slate-400">{detail?.statusName}</p>
|
||||
</div>
|
||||
{/* {detail?.isPublish == false ? (
|
||||
|
|
@ -680,14 +684,15 @@ export default function FormTeksDetail() {
|
|||
color="primary"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:check" className="mr-3" /> Setujui
|
||||
<Icon icon="fa:check" className="mr-3" /> {t("accept")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => actionApproval("3")}
|
||||
className="bg-orange-400 hover:bg-orange-300"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:comment-o" className="mr-3" /> Revisi
|
||||
<Icon icon="fa:comment-o" className="mr-3" />{" "}
|
||||
{t("revision")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => actionApproval("4")}
|
||||
|
|
@ -695,7 +700,7 @@ export default function FormTeksDetail() {
|
|||
type="button"
|
||||
>
|
||||
<Icon icon="fa:times" className="mr-3" />
|
||||
Tolak
|
||||
{t("reject")}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
|
@ -706,7 +711,7 @@ export default function FormTeksDetail() {
|
|||
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
||||
<DialogContent size="md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Berikan Komentar</DialogTitle>
|
||||
<DialogTitle>{t("leave-comment")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
{status == "2"
|
||||
? files?.map((file, index) => (
|
||||
|
|
@ -743,7 +748,7 @@ export default function FormTeksDetail() {
|
|||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Semua
|
||||
{t("all")}
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
|
|
@ -888,7 +893,7 @@ export default function FormTeksDetail() {
|
|||
color="primary"
|
||||
onClick={() => submit()}
|
||||
>
|
||||
Submit
|
||||
{t("submit")}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
|
|
@ -897,7 +902,7 @@ export default function FormTeksDetail() {
|
|||
setModalOpen(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ import { Item } from "@radix-ui/react-dropdown-menu";
|
|||
import dynamic from "next/dynamic";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface FileWithPreview extends File {
|
||||
preview: string;
|
||||
|
|
@ -115,7 +116,7 @@ export default function FormTeks() {
|
|||
const [articleImages, setArticleImages] = useState<string[]>([]);
|
||||
const [isSwitchOn, setIsSwitchOn] = useState<boolean>(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const t = useTranslations("Form");
|
||||
const [selectedTarget, setSelectedTarget] = useState("");
|
||||
const [unitSelection, setUnitSelection] = useState({
|
||||
allUnit: false,
|
||||
|
|
@ -707,11 +708,11 @@ export default function FormTeks() {
|
|||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Konten Teks</p>
|
||||
<p className="text-lg font-semibold mb-3">{t("form-text")}</p>
|
||||
<div className="gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2 py-3">
|
||||
<Label>Judul</Label>
|
||||
<Label>{t("title")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
|
|
@ -731,8 +732,8 @@ export default function FormTeks() {
|
|||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<div className="py-3 w-full">
|
||||
<Label>Kategori</Label>
|
||||
<div className="py-3 w-full space-y-2">
|
||||
<Label>{t("category")}</Label>
|
||||
<Select
|
||||
value={selectedCategory} // Ensure selectedTarget is updated correctly
|
||||
onValueChange={(id) => {
|
||||
|
|
@ -757,7 +758,7 @@ export default function FormTeks() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-3 py-2">
|
||||
<Label>Bantuan AI</Label>
|
||||
<Label>{t("ai-assistance")}</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
defaultChecked={isSwitchOn}
|
||||
|
|
@ -773,7 +774,7 @@ export default function FormTeks() {
|
|||
<div>
|
||||
<div className="flex flex-row gap-3">
|
||||
<div className="space-y-2 py-3 w-4/12">
|
||||
<Label>Bahasa</Label>
|
||||
<Label>{t("language")}</Label>
|
||||
<Select onValueChange={setSelectedLanguage}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
|
|
@ -785,7 +786,7 @@ export default function FormTeks() {
|
|||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2 py-3 w-4/12">
|
||||
<Label>Writing Style</Label>
|
||||
<Label>{t("writing-style")}</Label>
|
||||
<Select onValueChange={setSelectedWritingStyle}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
|
|
@ -804,7 +805,7 @@ export default function FormTeks() {
|
|||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2 py-3 w-4/12">
|
||||
<Label>Article Size</Label>
|
||||
<Label>{t("article-size")}</Label>
|
||||
<Select onValueChange={setSelectedSize}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
|
|
@ -825,7 +826,7 @@ export default function FormTeks() {
|
|||
</div>
|
||||
<div className="mt-5">
|
||||
<div className="flex flex-row items-center gap-3 mb-3">
|
||||
<Label>Main Keyword</Label>
|
||||
<Label>{t("main-keyword")}</Label>
|
||||
<Button
|
||||
variant="outline"
|
||||
color="primary"
|
||||
|
|
@ -849,7 +850,7 @@ export default function FormTeks() {
|
|||
</div>
|
||||
<div className="mt-5">
|
||||
<div className="flex flex-row items-center gap-3 mb-3">
|
||||
<Label>Judul</Label>
|
||||
<Label>{t("title")}</Label>
|
||||
<Button
|
||||
variant="outline"
|
||||
color="primary"
|
||||
|
|
@ -871,7 +872,7 @@ export default function FormTeks() {
|
|||
</div>
|
||||
<div className="mt-5">
|
||||
<div className="flex flex-row items-center gap-3 mb-3">
|
||||
<Label>SEO</Label>
|
||||
<Label>{t("seo")}</Label>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
color="primary"
|
||||
|
|
@ -882,15 +883,9 @@ export default function FormTeks() {
|
|||
</Button>
|
||||
</div>
|
||||
<p className="font-semibold">
|
||||
Kata kunci untuk disertakan dalam teks
|
||||
</p>
|
||||
<p className="text-sm">
|
||||
JIka Anda tidak Memberikan kata kunci, kami akan secara
|
||||
otomatis membuat kata kunci yang relevan dari kata kunci
|
||||
utama untuk setiap bagian dan menggunakannya untuk membuat
|
||||
artikel. Untuk menambahkan kata kunci baru, ketik ',
|
||||
+ kata kunci'.
|
||||
{t("Keywords to include in the text")}
|
||||
</p>
|
||||
<p className="text-sm">{t("title-key")}</p>
|
||||
<div className="mt-3">
|
||||
<Textarea
|
||||
value={selectedSEO}
|
||||
|
|
@ -900,7 +895,7 @@ export default function FormTeks() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Label>Instruksi Khusus (Optional)</Label>
|
||||
<Label>{t("special-instructions")} (Optional)</Label>
|
||||
<div className="mt-3">
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -957,7 +952,7 @@ export default function FormTeks() {
|
|||
variant={"outline"}
|
||||
color="primary"
|
||||
>
|
||||
Edit
|
||||
{t("update")}
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
|
|
@ -966,8 +961,8 @@ export default function FormTeks() {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="">
|
||||
<Label>Deskripsi</Label>
|
||||
<div className="space-y-2">
|
||||
<Label>{t("description")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
|
|
@ -990,8 +985,8 @@ export default function FormTeks() {
|
|||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="py-3">
|
||||
<Label>Pilih File</Label>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("select-file")}</Label>
|
||||
{/* <Input
|
||||
id="fileInput"
|
||||
type="file"
|
||||
|
|
@ -1004,11 +999,10 @@ export default function FormTeks() {
|
|||
<CloudUpload className="text-default-300 w-10 h-10" />
|
||||
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
|
||||
{/* Drop files here or click to upload. */}
|
||||
Tarik file disini atau klik untuk upload.
|
||||
{t("drag-file")}
|
||||
</h4>
|
||||
<div className=" text-xs text-muted-foreground">
|
||||
( Upload file dengan format .doc, .docx, .pdf, .ppt,
|
||||
atau .pptx Ukuran maksimal 100mb.)
|
||||
{t("upload-file-text-max")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1017,7 +1011,7 @@ export default function FormTeks() {
|
|||
<div>{fileList}</div>
|
||||
<div className=" flex justify-between gap-2">
|
||||
<div className="flex flex-row items-center gap-3 py-3">
|
||||
<Label>Gunakan Watermark</Label>
|
||||
<Label>{t("watermark")}</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch defaultChecked color="primary" id="c2" />
|
||||
</div>
|
||||
|
|
@ -1026,7 +1020,7 @@ export default function FormTeks() {
|
|||
color="destructive"
|
||||
onClick={handleRemoveAllFiles}
|
||||
>
|
||||
Remove All
|
||||
{t("remove-all")}
|
||||
</Button>
|
||||
</div>
|
||||
</Fragment>
|
||||
|
|
@ -1042,7 +1036,7 @@ export default function FormTeks() {
|
|||
<Card className=" h-[500px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Kreator</Label>
|
||||
<Label>{t("creator")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="creatorName"
|
||||
|
|
@ -1063,8 +1057,8 @@ export default function FormTeks() {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<Label htmlFor="tags">Tags</Label>
|
||||
<div className="px-3 py-3 space-y-2">
|
||||
<Label htmlFor="tags">{t("tags")}</Label>
|
||||
|
||||
<Input
|
||||
type="text"
|
||||
|
|
@ -1092,8 +1086,8 @@ export default function FormTeks() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label>Target Publish</Label>
|
||||
<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
|
||||
|
|
@ -1116,12 +1110,12 @@ export default function FormTeks() {
|
|||
<div className="flex flex-row justify-end gap-3">
|
||||
<div className="mt-4">
|
||||
<Button type="submit" color="primary">
|
||||
Submit
|
||||
{t("submit")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<Button type="submit" color="primary" variant="outline">
|
||||
Cancel
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ import Image from "next/image";
|
|||
import { error, loading } from "@/lib/swal";
|
||||
import { Upload } from "tus-js-client";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
import { useTranslations } from "next-intl";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
const teksSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -87,6 +89,13 @@ type Option = {
|
|||
label: string;
|
||||
};
|
||||
|
||||
const CustomEditor = dynamic(
|
||||
() => {
|
||||
return import("@/components/editor/custom-editor");
|
||||
},
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export default function FormTeksUpdate() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -108,6 +117,7 @@ export default function FormTeksUpdate() {
|
|||
const scheduleId = Cookies.get("scheduleId");
|
||||
const scheduleType = Cookies.get("scheduleType");
|
||||
|
||||
const t = useTranslations("Form");
|
||||
const [categories, setCategories] = useState<Category[]>([]);
|
||||
const [selectedCategory, setSelectedCategory] = useState<any>();
|
||||
const [tags, setTags] = useState<any[]>([]);
|
||||
|
|
@ -555,13 +565,11 @@ export default function FormTeksUpdate() {
|
|||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">
|
||||
Form Update Konten Teks
|
||||
</p>
|
||||
<p className="text-lg font-semibold mb-3">{t("form-text")}</p>
|
||||
<div className="gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2 py-3">
|
||||
<Label>Judul</Label>
|
||||
<Label>{t("title")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
|
|
@ -582,8 +590,8 @@ export default function FormTeksUpdate() {
|
|||
)}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="py-3 w-full">
|
||||
<Label>Kategori</Label>
|
||||
<div className="py-3 w-full space-y-2">
|
||||
<Label>{t("category")}</Label>
|
||||
<Select
|
||||
defaultValue={detail?.categoryId.name} // Nilai default berdasarkan detail
|
||||
onValueChange={(id) => {
|
||||
|
|
@ -604,17 +612,15 @@ export default function FormTeksUpdate() {
|
|||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="py-3">
|
||||
<Label>Deskripsi</Label>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("description")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<JoditEditor
|
||||
ref={editor}
|
||||
value={detail?.description}
|
||||
<CustomEditor
|
||||
onChange={onChange}
|
||||
className="dark:text-black"
|
||||
initialData={detail?.description || value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
@ -624,8 +630,8 @@ export default function FormTeksUpdate() {
|
|||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="py-3">
|
||||
<Label>Pilih File</Label>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("select-file")}</Label>
|
||||
{/* <Input
|
||||
id="fileInput"
|
||||
type="file"
|
||||
|
|
@ -638,11 +644,10 @@ export default function FormTeksUpdate() {
|
|||
<CloudUpload className="text-default-300 w-10 h-10" />
|
||||
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
|
||||
{/* Drop files here or click to upload. */}
|
||||
Tarik file disini atau klik untuk upload.
|
||||
{t("drag-file")}
|
||||
</h4>
|
||||
<div className=" text-xs text-muted-foreground">
|
||||
( Upload file dengan format .doc, .docx, .pdf, .ppt,
|
||||
atau .pptx Ukuran maksimal 100mb.)
|
||||
{t("upload-file-text-max")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -651,7 +656,7 @@ export default function FormTeksUpdate() {
|
|||
<div>{fileList}</div>
|
||||
<div className=" flex justify-between gap-2">
|
||||
<div className="flex flex-row items-center gap-3 py-3">
|
||||
<Label>Gunakan Watermark</Label>
|
||||
<Label>{t("watermark")}</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch defaultChecked color="primary" id="c2" />
|
||||
</div>
|
||||
|
|
@ -660,14 +665,17 @@ export default function FormTeksUpdate() {
|
|||
color="destructive"
|
||||
onClick={handleRemoveAllFiles}
|
||||
>
|
||||
Remove All
|
||||
{t("remove-all")}
|
||||
</Button>
|
||||
</div>
|
||||
</Fragment>
|
||||
) : null}
|
||||
{files.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<Label className="text-lg font-semibold">File</Label>
|
||||
<div className="mt-4 space-y-2">
|
||||
<Label className="text-lg font-semibold">
|
||||
{" "}
|
||||
{t("file-media")}
|
||||
</Label>
|
||||
<div className="grid gap-4">
|
||||
{files.map((file: any) => (
|
||||
<div
|
||||
|
|
@ -688,7 +696,7 @@ export default function FormTeksUpdate() {
|
|||
rel="noopener noreferrer"
|
||||
className="text-blue-500 text-sm"
|
||||
>
|
||||
Lihat File
|
||||
{t("view-file")}
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -706,7 +714,7 @@ export default function FormTeksUpdate() {
|
|||
}
|
||||
className="form-checkbox"
|
||||
/>
|
||||
<span>Semua</span>
|
||||
<span>{t("all")}</span>
|
||||
</Label>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -778,7 +786,7 @@ export default function FormTeksUpdate() {
|
|||
<Card className=" h-[800px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Kreator</Label>
|
||||
<Label>{t("creator")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="creatorName"
|
||||
|
|
@ -811,7 +819,7 @@ export default function FormTeksUpdate() {
|
|||
</div> */}
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<Label>{t("tags")}</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="tags"
|
||||
|
|
@ -846,7 +854,7 @@ export default function FormTeksUpdate() {
|
|||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<div className="flex flex-col gap-6">
|
||||
<Label>Target Publish</Label>
|
||||
<Label>{t("publish-target")}</Label>
|
||||
{options.map((option) => (
|
||||
<div key={option.id} className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
|
|
@ -867,22 +875,22 @@ export default function FormTeksUpdate() {
|
|||
</div>
|
||||
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
||||
<MailIcon />
|
||||
<p className="">Kotak Saran (0)</p>
|
||||
<p className="">{t("suggestion-box")} (0)</p>
|
||||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<p>Keterangan:</p>
|
||||
<p>{t("information")}:</p>
|
||||
{/* <p>{detail?.status}</p> */}
|
||||
</div>
|
||||
</Card>
|
||||
<div className="flex flex-row justify-end gap-3">
|
||||
<div className="mt-4">
|
||||
<Button type="submit" color="primary">
|
||||
Submit
|
||||
{t("submit")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<Button type="submit" color="primary" variant="outline">
|
||||
Cancel
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ import { Icon } from "@iconify/react/dist/iconify.js";
|
|||
import { error } from "@/lib/swal";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useRouter } from "@/i18n/routing";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const imageSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -135,6 +136,7 @@ export default function FormVideoDetail() {
|
|||
const [detailVideo, setDetailVideo] = useState<any>([]);
|
||||
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
||||
|
||||
const t = useTranslations("Form");
|
||||
const [filePlacements, setFilePlacements] = useState<string[][]>([]);
|
||||
const [selectedTarget, setSelectedTarget] = useState("");
|
||||
const [files, setFiles] = useState<FileType[]>([]);
|
||||
|
|
@ -412,13 +414,11 @@ export default function FormVideoDetail() {
|
|||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">
|
||||
Form Detail Konten Video
|
||||
</p>
|
||||
<p className="text-lg font-semibold mb-3">{t("form-video")}</p>
|
||||
<div className="gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2 py-3">
|
||||
<Label>Judul</Label>
|
||||
<Label>{t("title")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
|
|
@ -439,8 +439,8 @@ export default function FormVideoDetail() {
|
|||
)}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="py-3 w-full">
|
||||
<Label>Kategori</Label>
|
||||
<div className="py-3 w-full space-y-2">
|
||||
<Label>{t("category")}</Label>
|
||||
<Select
|
||||
value={detail?.category.name} // Nilai default berdasarkan detail
|
||||
onValueChange={(id) => {
|
||||
|
|
@ -462,8 +462,8 @@ export default function FormVideoDetail() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-3">
|
||||
<Label>Deskripsi</Label>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("description")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
|
|
@ -477,48 +477,49 @@ export default function FormVideoDetail() {
|
|||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Label className="text-xl text-black">File Mediaa</Label>
|
||||
<div className="w-full ">
|
||||
<Swiper
|
||||
thumbs={{ swiper: thumbsSwiper }}
|
||||
modules={[FreeMode, Navigation, Thumbs]}
|
||||
navigation={false}
|
||||
className="w-full"
|
||||
>
|
||||
{detailVideo?.map((data: any) => (
|
||||
<SwiperSlide key={data.id}>
|
||||
<video
|
||||
className="object-fill h-full w-full"
|
||||
src={data}
|
||||
controls
|
||||
title={`Video ${data.id}`} // Mengganti alt dengan title
|
||||
/>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
<div className=" mt-2 ">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xl "> {t("file-media")}</Label>
|
||||
<div className="w-full ">
|
||||
<Swiper
|
||||
onSwiper={setThumbsSwiper}
|
||||
slidesPerView={6}
|
||||
spaceBetween={8}
|
||||
pagination={{
|
||||
clickable: true,
|
||||
}}
|
||||
modules={[Pagination, Thumbs]}
|
||||
// className="mySwiper2"
|
||||
thumbs={{ swiper: thumbsSwiper }}
|
||||
modules={[FreeMode, Navigation, Thumbs]}
|
||||
navigation={false}
|
||||
className="w-full"
|
||||
>
|
||||
{detailVideo?.map((data: any) => (
|
||||
<SwiperSlide key={data.id}>
|
||||
<video
|
||||
className="object-cover h-[60px] w-[80px]"
|
||||
className="object-fill h-full w-full"
|
||||
src={data}
|
||||
muted
|
||||
controls
|
||||
title={`Video ${data.id}`} // Mengganti alt dengan title
|
||||
/>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
<div className=" mt-2 ">
|
||||
<Swiper
|
||||
onSwiper={setThumbsSwiper}
|
||||
slidesPerView={6}
|
||||
spaceBetween={8}
|
||||
pagination={{
|
||||
clickable: true,
|
||||
}}
|
||||
modules={[Pagination, Thumbs]}
|
||||
// className="mySwiper2"
|
||||
>
|
||||
{detailVideo?.map((data: any) => (
|
||||
<SwiperSlide key={data.id}>
|
||||
<video
|
||||
className="object-cover h-[60px] w-[80px]"
|
||||
src={data}
|
||||
muted
|
||||
title={`Video ${data.id}`} // Mengganti alt dengan title
|
||||
/>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -528,7 +529,7 @@ export default function FormVideoDetail() {
|
|||
<Card className=" h-[800px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Kreator</Label>
|
||||
<Label>{t("creator")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="creatorName"
|
||||
|
|
@ -549,8 +550,8 @@ export default function FormVideoDetail() {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 px-3">
|
||||
<Label>Pratinjau Gambar Utama</Label>
|
||||
<div className="mt-3 px-3 space-y-2">
|
||||
<Label>{t("preview")}</Label>
|
||||
<Card className="mt-2">
|
||||
<img
|
||||
src={detail.thumbnailLink}
|
||||
|
|
@ -561,7 +562,7 @@ export default function FormVideoDetail() {
|
|||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<Label>{t("tags")}</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{detail?.tags
|
||||
?.split(",")
|
||||
|
|
@ -577,8 +578,8 @@ export default function FormVideoDetail() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<div className="flex flex-col gap-6">
|
||||
<Label>Target Publish</Label>
|
||||
<div className="flex flex-col gap-6 space-y-2">
|
||||
<Label>{t("publish-target")}</Label>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="5"
|
||||
|
|
@ -615,10 +616,10 @@ export default function FormVideoDetail() {
|
|||
</div>
|
||||
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
||||
<MailIcon />
|
||||
<p className="">Kotak Saran (0)</p>
|
||||
<p className="">{t("suggestion-box")} (0)</p>
|
||||
</div>
|
||||
<div className="px-3 py-3 border mx-3">
|
||||
<p>Keterangan:</p>
|
||||
<p>{t("information")}:</p>
|
||||
<p className="text-sm text-slate-400">{detail?.statusName}</p>
|
||||
</div>
|
||||
{/* {detail?.isPublish == false ? (
|
||||
|
|
@ -638,14 +639,15 @@ export default function FormVideoDetail() {
|
|||
color="primary"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:check" className="mr-3" /> Setujui
|
||||
<Icon icon="fa:check" className="mr-3" /> {t("accept")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => actionApproval("3")}
|
||||
className="bg-orange-400 hover:bg-orange-300"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:comment-o" className="mr-3" /> Revisi
|
||||
<Icon icon="fa:comment-o" className="mr-3" />{" "}
|
||||
{t("revision")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => actionApproval("4")}
|
||||
|
|
@ -653,7 +655,7 @@ export default function FormVideoDetail() {
|
|||
type="button"
|
||||
>
|
||||
<Icon icon="fa:times" className="mr-3" />
|
||||
Tolak
|
||||
{t("reject")}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
|
@ -664,7 +666,7 @@ export default function FormVideoDetail() {
|
|||
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
||||
<DialogContent size="md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Berikan Komentar</DialogTitle>
|
||||
<DialogTitle>{t("leave-comment")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
{status == "2"
|
||||
? files?.map((file, index) => (
|
||||
|
|
@ -701,7 +703,7 @@ export default function FormVideoDetail() {
|
|||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Semua
|
||||
{t("all")}
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
|
|
@ -846,14 +848,14 @@ export default function FormVideoDetail() {
|
|||
color="primary"
|
||||
onClick={() => submit()}
|
||||
>
|
||||
Submit
|
||||
{t("submit")}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="destructive"
|
||||
onClick={() => setModalOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ import { Item } from "@radix-ui/react-dropdown-menu";
|
|||
import dynamic from "next/dynamic";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const CustomEditor = dynamic(
|
||||
() => {
|
||||
|
|
@ -88,6 +89,7 @@ export default function FormVideo() {
|
|||
const scheduleType = Cookies.get("scheduleType");
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
|
||||
const t = useTranslations("Form");
|
||||
const [categories, setCategories] = useState<Category[]>([]);
|
||||
const [selectedCategory, setSelectedCategory] = useState<any>();
|
||||
const [tags, setTags] = useState<any[]>([]);
|
||||
|
|
@ -706,10 +708,10 @@ export default function FormVideo() {
|
|||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Konten Video</p>
|
||||
<p className="text-lg font-semibold mb-3">{t("form-video")}</p>
|
||||
<div className="gap-5 mb-5">
|
||||
<div className="space-y-2 py-3">
|
||||
<Label>Judul</Label>
|
||||
<Label>{t("title")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
|
|
@ -729,8 +731,8 @@ export default function FormVideo() {
|
|||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<div className="py-3 w-full">
|
||||
<Label>Kategori</Label>
|
||||
<div className="py-3 w-full space-y-2">
|
||||
<Label>{t("category")}</Label>
|
||||
<Select
|
||||
value={selectedCategory} // Ensure selectedTarget is updated correctly
|
||||
onValueChange={(id) => {
|
||||
|
|
@ -755,7 +757,7 @@ export default function FormVideo() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-3 py-2">
|
||||
<Label>Bantuan AI</Label>
|
||||
<Label>{t("ai-assistance")}</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch
|
||||
defaultChecked={isSwitchOn}
|
||||
|
|
@ -771,7 +773,7 @@ export default function FormVideo() {
|
|||
<div>
|
||||
<div className="flex flex-row gap-3">
|
||||
<div className="space-y-2 py-3 w-4/12">
|
||||
<Label>Bahasa</Label>
|
||||
<Label>{t("language")}</Label>
|
||||
<Select onValueChange={setSelectedLanguage}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
|
|
@ -783,7 +785,7 @@ export default function FormVideo() {
|
|||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2 py-3 w-4/12">
|
||||
<Label>Writing Style</Label>
|
||||
<Label>{t("writing-style")}</Label>
|
||||
<Select onValueChange={setSelectedWritingStyle}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
|
|
@ -802,7 +804,7 @@ export default function FormVideo() {
|
|||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2 py-3 w-4/12">
|
||||
<Label>Article Size</Label>
|
||||
<Label>{t("article-size")}</Label>
|
||||
<Select onValueChange={setSelectedSize}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
|
|
@ -823,7 +825,7 @@ export default function FormVideo() {
|
|||
</div>
|
||||
<div className="mt-5">
|
||||
<div className="flex flex-row items-center gap-3 mb-3">
|
||||
<Label>Main Keyword</Label>
|
||||
<Label>{t("main-keyword")}</Label>
|
||||
<Button
|
||||
variant="outline"
|
||||
color="primary"
|
||||
|
|
@ -847,7 +849,7 @@ export default function FormVideo() {
|
|||
</div>
|
||||
<div className="mt-5">
|
||||
<div className="flex flex-row items-center gap-3 mb-3">
|
||||
<Label>Judul</Label>
|
||||
<Label>{t("title")}</Label>
|
||||
<Button
|
||||
variant="outline"
|
||||
color="primary"
|
||||
|
|
@ -869,7 +871,7 @@ export default function FormVideo() {
|
|||
</div>
|
||||
<div className="mt-5">
|
||||
<div className="flex flex-row items-center gap-3 mb-3">
|
||||
<Label>SEO</Label>
|
||||
<Label>{t("seo")}</Label>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
color="primary"
|
||||
|
|
@ -880,15 +882,9 @@ export default function FormVideo() {
|
|||
</Button>
|
||||
</div>
|
||||
<p className="font-semibold">
|
||||
Kata kunci untuk disertakan dalam teks
|
||||
</p>
|
||||
<p className="text-sm">
|
||||
JIka Anda tidak Memberikan kata kunci, kami akan secara
|
||||
otomatis membuat kata kunci yang relevan dari kata kunci
|
||||
utama untuk setiap bagian dan menggunakannya untuk membuat
|
||||
artikel. Untuk menambahkan kata kunci baru, ketik ',
|
||||
+ kata kunci'.
|
||||
{t("Keywords to include in the text")}
|
||||
</p>
|
||||
<p className="text-sm">{t("title-key")}</p>
|
||||
<div className="mt-3">
|
||||
<Textarea
|
||||
value={selectedSEO}
|
||||
|
|
@ -898,7 +894,7 @@ export default function FormVideo() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Label>Instruksi Khusus (Optional)</Label>
|
||||
<Label>{t("special-instructions")} (Optional)</Label>
|
||||
<div className="mt-3">
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -955,7 +951,7 @@ export default function FormVideo() {
|
|||
variant={"outline"}
|
||||
color="primary"
|
||||
>
|
||||
Edit
|
||||
{t("update")}
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
|
|
@ -964,8 +960,8 @@ export default function FormVideo() {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="">
|
||||
<Label>Deskripsi</Label>
|
||||
<div className="space-y-2">
|
||||
<Label>{t("description")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
|
|
@ -988,8 +984,8 @@ export default function FormVideo() {
|
|||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="py-3">
|
||||
<Label>Pilih File</Label>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("select-file")}</Label>
|
||||
{/* <Input
|
||||
id="fileInput"
|
||||
type="file"
|
||||
|
|
@ -1002,11 +998,10 @@ export default function FormVideo() {
|
|||
<CloudUpload className="text-default-300 w-10 h-10" />
|
||||
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
|
||||
{/* Drop files here or click to upload. */}
|
||||
Tarik file disini atau klik untuk upload.
|
||||
{t("drag-file")}
|
||||
</h4>
|
||||
<div className=" text-xs text-muted-foreground">
|
||||
( Upload file dengan mp4 atau mov Ukuran maksimal
|
||||
100mb.)
|
||||
{t("upload-file-video-max")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1015,7 +1010,7 @@ export default function FormVideo() {
|
|||
<div>{fileList}</div>
|
||||
<div className=" flex justify-between gap-2">
|
||||
<div className="flex flex-row items-center gap-3 py-3">
|
||||
<Label>Gunakan Watermark</Label>
|
||||
<Label>{t("watermark")}</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch defaultChecked color="primary" id="c2" />
|
||||
</div>
|
||||
|
|
@ -1024,7 +1019,7 @@ export default function FormVideo() {
|
|||
color="destructive"
|
||||
onClick={handleRemoveAllFiles}
|
||||
>
|
||||
Remove All
|
||||
{t("remove-all")}
|
||||
</Button>
|
||||
</div>
|
||||
</Fragment>
|
||||
|
|
@ -1040,7 +1035,7 @@ export default function FormVideo() {
|
|||
<Card className=" h-[800px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Kreator</Label>
|
||||
<Label>{t("creator")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="creatorName"
|
||||
|
|
@ -1074,8 +1069,8 @@ export default function FormVideo() {
|
|||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="px-3 py-3">
|
||||
<Label htmlFor="tags">Tags</Label>
|
||||
<div className="px-3 py-3 space-y-2">
|
||||
<Label htmlFor="tags">{t("tags")}</Label>
|
||||
|
||||
<Input
|
||||
type="text"
|
||||
|
|
@ -1103,8 +1098,8 @@ export default function FormVideo() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label>Target Publish</Label>
|
||||
<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
|
||||
|
|
@ -1127,12 +1122,12 @@ export default function FormVideo() {
|
|||
<div className="flex flex-row justify-end gap-3">
|
||||
<div className="mt-4">
|
||||
<Button type="submit" color="primary">
|
||||
Submit
|
||||
{t("submit")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<Button type="submit" color="primary" variant="outline">
|
||||
Cancel
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ import { Icon } from "@iconify/react/dist/iconify.js";
|
|||
import { Upload } from "tus-js-client";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
import { useTranslations } from "next-intl";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
const videoSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -94,6 +96,13 @@ type Option = {
|
|||
name: string;
|
||||
};
|
||||
|
||||
const CustomEditor = dynamic(
|
||||
() => {
|
||||
return import("@/components/editor/custom-editor");
|
||||
},
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export default function FormVideoUpdate() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -110,6 +119,7 @@ export default function FormVideoUpdate() {
|
|||
const [isStartUpload, setIsStartUpload] = useState(false);
|
||||
const [counterProgress, setCounterProgress] = useState(0);
|
||||
|
||||
const t = useTranslations("Form");
|
||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||
const taskId = Cookies.get("taskId");
|
||||
const scheduleId = Cookies.get("scheduleId");
|
||||
|
|
@ -609,13 +619,11 @@ export default function FormVideoUpdate() {
|
|||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">
|
||||
Form Update Konten Video
|
||||
</p>
|
||||
<p className="text-lg font-semibold mb-3">{t("form-video")}</p>
|
||||
<div className="gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2 py-3">
|
||||
<Label>Judul</Label>
|
||||
<Label>{t("title")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
|
|
@ -636,8 +644,8 @@ export default function FormVideoUpdate() {
|
|||
)}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="py-3 w-full">
|
||||
<Label>Kategori</Label>
|
||||
<div className="py-3 w-full space-y-2">
|
||||
<Label>{t("category")}</Label>
|
||||
<Select
|
||||
defaultValue={detail?.categoryId.name} // Nilai default berdasarkan detail
|
||||
onValueChange={(id) => {
|
||||
|
|
@ -659,17 +667,15 @@ export default function FormVideoUpdate() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-3">
|
||||
<Label>Deskripsi</Label>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("description")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<JoditEditor
|
||||
ref={editor}
|
||||
value={detail?.description}
|
||||
<CustomEditor
|
||||
onChange={onChange}
|
||||
className="dark:text-black"
|
||||
initialData={detail?.description || value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
@ -679,8 +685,8 @@ export default function FormVideoUpdate() {
|
|||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="py-3">
|
||||
<Label>Pilih File</Label>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("select-file")}</Label>
|
||||
{/* <Input
|
||||
id="fileInput"
|
||||
type="file"
|
||||
|
|
@ -693,11 +699,10 @@ export default function FormVideoUpdate() {
|
|||
<CloudUpload className="text-default-300 w-10 h-10" />
|
||||
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
|
||||
{/* Drop files here or click to upload. */}
|
||||
Tarik file disini atau klik untuk upload.
|
||||
{t("drag-file")}
|
||||
</h4>
|
||||
<div className=" text-xs text-muted-foreground">
|
||||
( Upload file dengan format .jpg, .jpeg, atau .png.
|
||||
Ukuran maksimal 100mb.)
|
||||
{t("upload-file-video-max")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -706,7 +711,7 @@ export default function FormVideoUpdate() {
|
|||
<div>{fileList}</div>
|
||||
<div className=" flex justify-between gap-2">
|
||||
<div className="flex flex-row items-center gap-3 py-3">
|
||||
<Label>Gunakan Watermark</Label>
|
||||
<Label>{t("watermark")}</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch defaultChecked color="primary" id="c2" />
|
||||
</div>
|
||||
|
|
@ -721,8 +726,11 @@ export default function FormVideoUpdate() {
|
|||
</Fragment>
|
||||
) : null}
|
||||
{files.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<Label className="text-lg font-semibold">File</Label>
|
||||
<div className="mt-4 space-y-2">
|
||||
<Label className="text-lg font-semibold">
|
||||
{" "}
|
||||
{t("file-media")}
|
||||
</Label>
|
||||
<div className="grid gap-4">
|
||||
{files.map((file: any) => (
|
||||
<div
|
||||
|
|
@ -743,7 +751,7 @@ export default function FormVideoUpdate() {
|
|||
rel="noopener noreferrer"
|
||||
className="text-blue-500 text-sm"
|
||||
>
|
||||
Lihat File
|
||||
{t("view-file")}
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -761,7 +769,7 @@ export default function FormVideoUpdate() {
|
|||
}
|
||||
className="form-checkbox"
|
||||
/>
|
||||
<span>Semua</span>
|
||||
<span>{t("all")}</span>
|
||||
</Label>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -833,7 +841,7 @@ export default function FormVideoUpdate() {
|
|||
<Card className=" h-[800px]">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Kreator</Label>
|
||||
<Label>{t("creator")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="creatorName"
|
||||
|
|
@ -854,8 +862,8 @@ export default function FormVideoUpdate() {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 px-3">
|
||||
<Label>Pratinjau Gambar Utama</Label>
|
||||
<div className="mt-3 px-3 space-y-2">
|
||||
<Label>{t("preview")}</Label>
|
||||
<Card className="mt-2">
|
||||
<img
|
||||
src={detail.thumbnailLink}
|
||||
|
|
@ -866,7 +874,7 @@ export default function FormVideoUpdate() {
|
|||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<Label>{t("tags")}</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="tags"
|
||||
|
|
@ -900,8 +908,8 @@ export default function FormVideoUpdate() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<div className="flex flex-col gap-6">
|
||||
<Label>Target Publish</Label>
|
||||
<div className="flex flex-col gap-6 space-y-2">
|
||||
<Label>{t("publish-target")}</Label>
|
||||
{options.map((option: Option) => (
|
||||
<div key={option.id} className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
|
|
@ -921,22 +929,22 @@ export default function FormVideoUpdate() {
|
|||
</div>
|
||||
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
||||
<MailIcon />
|
||||
<p className="">Kotak Saran (0)</p>
|
||||
<p className="">{t("suggestion-box")} (0)</p>
|
||||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<p>Keterangan:</p>
|
||||
<p>{t("information")}:</p>
|
||||
{/* <p>{detail?.status}</p> */}
|
||||
</div>
|
||||
</Card>
|
||||
<div className="flex flex-row justify-end gap-3">
|
||||
<div className="mt-4">
|
||||
<Button type="submit" color="primary">
|
||||
Submit
|
||||
{t("submit")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<Button type="submit" color="primary" variant="outline">
|
||||
Cancel
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import {
|
|||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
||||
import { CalendarIcon, ChevronDown, ChevronUp } from "lucide-react";
|
||||
import { CalendarIcon, ChevronDown, ChevronUp, Trash2 } from "lucide-react";
|
||||
import { format, parseISO } from "date-fns";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import { DateRange } from "react-day-picker";
|
||||
|
|
@ -898,10 +898,10 @@ export default function FormContestDetail() {
|
|||
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
|
||||
{/* Display remaining time */}
|
||||
<div className="mt-4">
|
||||
<h2 className="text-lg font-bold">Link Berita</h2>
|
||||
<Label className="">Link Berita</Label>
|
||||
{links.map((link, index) => (
|
||||
<div key={index} className="flex items-center gap-2 mt-2">
|
||||
<input
|
||||
<Input
|
||||
type="url"
|
||||
className="border rounded p-2 w-full"
|
||||
placeholder={`Masukkan link berita ${index + 1}`}
|
||||
|
|
@ -916,18 +916,19 @@ export default function FormContestDetail() {
|
|||
className="bg-red-500 text-white px-3 py-1 rounded"
|
||||
onClick={() => handleRemoveRow(index)}
|
||||
>
|
||||
Hapus
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
size="md"
|
||||
className="mt-2 bg-blue-500 text-white px-4 py-2 rounded"
|
||||
onClick={handleAddRow}
|
||||
>
|
||||
Tambah Link
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ import {
|
|||
} from "@/components/ui/dialog";
|
||||
import { AlertDialogHeader } from "@/components/ui/alert-dialog";
|
||||
import { Description } from "@radix-ui/react-toast";
|
||||
import { useTranslations } from "next-intl";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -61,6 +63,13 @@ export type mediahubDetail = {
|
|||
is_active: string;
|
||||
};
|
||||
|
||||
const CustomEditor = dynamic(
|
||||
() => {
|
||||
return import("@/components/editor/custom-editor");
|
||||
},
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export default function PublishMediahub() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -75,6 +84,7 @@ export default function PublishMediahub() {
|
|||
image: false,
|
||||
text: false,
|
||||
});
|
||||
const t = useTranslations("Form");
|
||||
const [mainType, setMainType] = useState<number>(1);
|
||||
const [taskType, setTaskType] = useState<string>("atensi-khusus");
|
||||
const [broadcastType, setBroadcastType] = useState<string>("all"); // untuk Tipe Penugasan
|
||||
|
|
@ -304,13 +314,13 @@ export default function PublishMediahub() {
|
|||
return (
|
||||
<Card>
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Perencanaan Mediahub</p>
|
||||
<p className="text-lg font-semibold mb-3">{t("planning-mediahub")}</p>
|
||||
{detail !== undefined ? (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2">
|
||||
<Label>Judul</Label>
|
||||
<Label>{t("title")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
|
|
@ -328,8 +338,8 @@ export default function PublishMediahub() {
|
|||
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<Label>Output Tugas</Label>
|
||||
<div className="mt-6 space-y-2">
|
||||
<Label>{t("output-tugas")}</Label>
|
||||
<div className="flex flex-wrap gap-3 mt-1">
|
||||
{Object.keys(taskOutput).map((key) => (
|
||||
<div className="flex items-center gap-2" key={key}>
|
||||
|
|
@ -347,8 +357,8 @@ export default function PublishMediahub() {
|
|||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<Label>Pelaksana Tugas</Label>
|
||||
<div className="mt-6 space-y-2">
|
||||
<Label>{t("executive-task")}</Label>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<div className="">
|
||||
|
|
@ -451,8 +461,8 @@ export default function PublishMediahub() {
|
|||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<Label>Jenis Penugasan</Label>
|
||||
<div className="mt-6 space-y-2">
|
||||
<Label>{t("assignment-type")}</Label>
|
||||
<RadioGroup
|
||||
value={detail?.assignmentType?.id.toString()}
|
||||
onValueChange={(value) => setType(value)}
|
||||
|
|
@ -473,8 +483,8 @@ export default function PublishMediahub() {
|
|||
</RadioGroup>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<div className="flex flex-col">
|
||||
<Label>Date</Label>
|
||||
<div className="flex flex-col space-y-2">
|
||||
<Label>{t("date")}</Label>
|
||||
<div>
|
||||
<Button
|
||||
defaultValue={detail?.date}
|
||||
|
|
@ -495,17 +505,15 @@ export default function PublishMediahub() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<Label>Narasi Penugasan</Label>
|
||||
<div className="mt-6 space-y-2">
|
||||
<Label>{t("description-task")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<JoditEditor
|
||||
ref={editor}
|
||||
value={detail?.description}
|
||||
<CustomEditor
|
||||
onChange={onChange}
|
||||
className="dark:text-black"
|
||||
initialData={detail?.description || value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
@ -520,7 +528,7 @@ export default function PublishMediahub() {
|
|||
{/* Submit Button */}
|
||||
<div className="mt-4">
|
||||
<Button type="submit" color="primary">
|
||||
Submit
|
||||
{t("submit")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ import {
|
|||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { useTranslations } from "next-intl";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -55,6 +57,13 @@ export type medsosDetail = {
|
|||
is_active: string;
|
||||
};
|
||||
|
||||
const CustomEditor = dynamic(
|
||||
() => {
|
||||
return import("@/components/editor/custom-editor");
|
||||
},
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export default function PublishMedsos() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -69,6 +78,7 @@ export default function PublishMedsos() {
|
|||
image: false,
|
||||
text: false,
|
||||
});
|
||||
const t = useTranslations("Form");
|
||||
const [mainType, setMainType] = useState<number>(1);
|
||||
const [taskType, setTaskType] = useState<string>("atensi-khusus");
|
||||
const [broadcastType, setBroadcastType] = useState<string>("all"); // untuk Tipe Penugasan
|
||||
|
|
@ -297,13 +307,13 @@ export default function PublishMedsos() {
|
|||
return (
|
||||
<Card>
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Perencanaan Mediahub</p>
|
||||
<p className="text-lg font-semibold mb-3">{t("planning-medsos")}</p>
|
||||
{detail !== undefined ? (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2">
|
||||
<Label>Judul</Label>
|
||||
<Label>{t("title")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
|
|
@ -322,7 +332,7 @@ export default function PublishMedsos() {
|
|||
)}
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<Label>Output Tugas</Label>
|
||||
<Label>{t("output-tugas")}</Label>
|
||||
<div className="flex flex-wrap gap-3 mt-1">
|
||||
{Object.keys(taskOutput).map((key) => (
|
||||
<div className="flex items-center gap-2" key={key}>
|
||||
|
|
@ -340,8 +350,8 @@ export default function PublishMedsos() {
|
|||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<Label>Pelaksana Tugas</Label>
|
||||
<div className="mt-6 space-y-2">
|
||||
<Label>{t("executive-task")}</Label>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<div>
|
||||
|
|
@ -367,7 +377,7 @@ export default function PublishMedsos() {
|
|||
<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]">
|
||||
|
|
@ -444,8 +454,8 @@ export default function PublishMedsos() {
|
|||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<Label>Jenis Penugasan</Label>
|
||||
<div className="mt-6 space-y-2">
|
||||
<Label>{t("assignment-type")}</Label>
|
||||
<RadioGroup
|
||||
value={detail.assignmentType.id.toString()} // State yang dipetakan ke value RadioGroup
|
||||
onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
|
||||
|
|
@ -465,9 +475,9 @@ export default function PublishMedsos() {
|
|||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<div className="mt-6 space-y-2">
|
||||
<div className="flex flex-col">
|
||||
<Label>Date</Label>
|
||||
<Label>{t("date")}</Label>
|
||||
<div>
|
||||
<Button
|
||||
value={detail.date}
|
||||
|
|
@ -488,17 +498,15 @@ export default function PublishMedsos() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<Label>Narasi Penugasan</Label>
|
||||
<div className="mt-6 space-y-2">
|
||||
<Label>{t("description-task")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<JoditEditor
|
||||
ref={editor}
|
||||
value={detail?.description}
|
||||
<CustomEditor
|
||||
onChange={onChange}
|
||||
className="dark:text-black"
|
||||
initialData={detail?.description || value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
@ -513,7 +521,7 @@ export default function PublishMedsos() {
|
|||
{/* Submit Button */}
|
||||
<div className="mt-4">
|
||||
<Button type="submit" color="primary">
|
||||
Submit
|
||||
{t("submit")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -237,7 +237,7 @@ export default function FormEventDetail() {
|
|||
)}
|
||||
|
||||
<div className="flex flex-col lg:flex-row mt-6 items-start lg:items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<Label className="mr-3 mb-1">Tanggal</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
|
|
@ -245,11 +245,11 @@ export default function FormEventDetail() {
|
|||
id="date"
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-[280px] lg:w-[300px] justify-start text-left font-normal",
|
||||
"w-[280px] lg:w-[250px] justify-start text-left font-normal px-0 md:px-0 lg:px-4",
|
||||
!date && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<CalendarIcon />
|
||||
<CalendarIcon size={15} className="mr-3" />
|
||||
{date?.from ? (
|
||||
date.to ? (
|
||||
<>
|
||||
|
|
@ -276,7 +276,7 @@ export default function FormEventDetail() {
|
|||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="title">Rentang Waktu</Label>
|
||||
<div>
|
||||
<div className="flex flex-row items-center">
|
||||
|
|
@ -326,7 +326,7 @@ export default function FormEventDetail() {
|
|||
DI SAMPAIKAN OLEH
|
||||
</p>
|
||||
<div className="flex flex-col ">
|
||||
<div className="mt-1">
|
||||
<div className="mt-1 space-y-2">
|
||||
<Label>Nama Pangkat</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -349,7 +349,7 @@ export default function FormEventDetail() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col my-3">
|
||||
<div className="mt-1">
|
||||
<div className="mt-1 space-y-2">
|
||||
<Label>Nama Lengkap</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
|
|||
|
|
@ -234,7 +234,7 @@ export default function FormEvent() {
|
|||
)}
|
||||
|
||||
<div className="flex flex-col lg:flex-row mt-3 items-start lg:items-center justify-between">
|
||||
<div className="flex flex-col ">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<Label className="mr-3 mb-1">Tanggal</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
|
|
@ -242,11 +242,11 @@ export default function FormEvent() {
|
|||
id="date"
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-[280px] lg:w-[300px] justify-start text-left font-normal",
|
||||
"w-[280px] lg:w-[250px] justify-start text-left font-normal px-0 md:px-0 lg:px-4",
|
||||
!date && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<CalendarIcon />
|
||||
<CalendarIcon size={15} className="mr-3" />
|
||||
{date?.from ? (
|
||||
date.to ? (
|
||||
<>
|
||||
|
|
@ -273,7 +273,7 @@ export default function FormEvent() {
|
|||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="title">Rentang Waktu</Label>
|
||||
<div className="">
|
||||
<div className="flex flex-row items-center">
|
||||
|
|
@ -321,7 +321,7 @@ export default function FormEvent() {
|
|||
</div>
|
||||
<p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p>
|
||||
<div className="flex flex-col ">
|
||||
<div className="mt-1">
|
||||
<div className="mt-1 space-y-2">
|
||||
<Label>Nama Pangkat</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -344,7 +344,7 @@ export default function FormEvent() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col my-3">
|
||||
<div className="mt-1">
|
||||
<div className="mt-1 space-y-2">
|
||||
<Label>Nama Lengkap</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ export default function FormEventUpdate() {
|
|||
)}
|
||||
|
||||
<div className="flex flex-col lg:flex-row mt-3 items-start lg:items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<Label className="mr-3 mb-1">Tanggal</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
|
|
@ -258,11 +258,11 @@ export default function FormEventUpdate() {
|
|||
id="date"
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-[280px] lg:w-[300px] justify-start text-left font-normal",
|
||||
"w-[280px] lg:w-[250px] justify-start text-left font-normal px-0 md:px-0 lg:px-4",
|
||||
!date && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<CalendarIcon />
|
||||
<CalendarIcon size={15} className="mr-3" />
|
||||
{date?.from ? (
|
||||
date.to ? (
|
||||
<>
|
||||
|
|
@ -289,7 +289,7 @@ export default function FormEventUpdate() {
|
|||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="title">Rentang Waktu</Label>
|
||||
<div>
|
||||
<div className="flex flex-row items-center">
|
||||
|
|
@ -337,7 +337,7 @@ export default function FormEventUpdate() {
|
|||
</div>
|
||||
<p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p>
|
||||
<div className="flex flex-col ">
|
||||
<div className="mt-1">
|
||||
<div className="mt-1 space-y-2">
|
||||
<Label>Nama Pangkat</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -360,7 +360,7 @@ export default function FormEventUpdate() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col my-3">
|
||||
<div className="mt-1">
|
||||
<div className="mt-1 space-y-2">
|
||||
<Label>Nama Lengkap</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ export default function FormDetailPressRillis() {
|
|||
)}
|
||||
|
||||
<div className="flex flex-col lg:flex-row mt-6 items-start lg:items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<Label className="mr-3 mb-1">Tanggal</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
|
|
@ -255,11 +255,11 @@ export default function FormDetailPressRillis() {
|
|||
id="date"
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-[280px] lg:w-[300px] justify-start text-left font-normal",
|
||||
"w-[280px] lg:w-[250px] justify-start text-left font-normal px-0 md:px-0 lg:px-4",
|
||||
!date && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<CalendarIcon />
|
||||
<CalendarIcon size={15} className="mr-3" />
|
||||
{date?.from ? (
|
||||
date.to ? (
|
||||
<>
|
||||
|
|
@ -286,7 +286,7 @@ export default function FormDetailPressRillis() {
|
|||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="title">Rentang Waktu</Label>
|
||||
<div>
|
||||
<div className="flex flex-row items-center">
|
||||
|
|
@ -332,7 +332,7 @@ export default function FormDetailPressRillis() {
|
|||
{errors.location?.message}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<div className="mt-6 space-y-2">
|
||||
<Label>Invitation</Label>
|
||||
<Select onValueChange={setSelectedTarget}>
|
||||
<SelectTrigger size="md">
|
||||
|
|
@ -353,7 +353,7 @@ export default function FormDetailPressRillis() {
|
|||
DI SAMPAIKAN OLEH
|
||||
</p>
|
||||
<div className="flex flex-col ">
|
||||
<div className="mt-1">
|
||||
<div className="mt-1 space-y-2">
|
||||
<Label>Nama Pangkat</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -376,7 +376,7 @@ export default function FormDetailPressRillis() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col my-3">
|
||||
<div className="mt-1">
|
||||
<div className="mt-1 space-y-2">
|
||||
<Label>Nama Lengkap</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@ export default function FormUpdatePressRelease() {
|
|||
)}
|
||||
|
||||
<div className="flex flex-col lg:flex-row mt-3 items-start lg:items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<Label className="mr-3 mb-1">Tanggal</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
|
|
@ -265,11 +265,11 @@ export default function FormUpdatePressRelease() {
|
|||
id="date"
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-[280px] lg:w-[300px] justify-start text-left font-normal",
|
||||
"w-[280px] lg:w-[250px] justify-start text-left font-normal px-0 md:px-0 lg:px-4",
|
||||
!date && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<CalendarIcon />
|
||||
<CalendarIcon size={15} className="mr-3" />
|
||||
{date?.from ? (
|
||||
date.to ? (
|
||||
<>
|
||||
|
|
@ -296,7 +296,7 @@ export default function FormUpdatePressRelease() {
|
|||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="title">Rentang Waktu</Label>
|
||||
<div>
|
||||
<div className="flex flex-row items-center">
|
||||
|
|
@ -342,7 +342,7 @@ export default function FormUpdatePressRelease() {
|
|||
{errors.location?.message}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<div className="mt-5 space-y-2">
|
||||
<Label>Invitation</Label>
|
||||
<Select onValueChange={setSelectedTarget}>
|
||||
<SelectTrigger size="md">
|
||||
|
|
@ -361,7 +361,7 @@ export default function FormUpdatePressRelease() {
|
|||
</div>
|
||||
<p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p>
|
||||
<div className="flex flex-col ">
|
||||
<div className="mt-1">
|
||||
<div className="mt-1 space-y-2">
|
||||
<Label>Nama Pangkat</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -384,7 +384,7 @@ export default function FormUpdatePressRelease() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col my-3">
|
||||
<div className="mt-1">
|
||||
<div className="mt-1 space-y-2">
|
||||
<Label>Nama Lengkap</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
|
|||
|
|
@ -234,7 +234,7 @@ export default function FormPressRelease() {
|
|||
)}
|
||||
|
||||
<div className="flex flex-col lg:flex-row mt-3 items-start lg:items-center justify-between">
|
||||
<div className="flex flex-col ">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<Label className="mr-3 mb-1">Tanggal</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
|
|
@ -242,11 +242,11 @@ export default function FormPressRelease() {
|
|||
id="date"
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-[280px] lg:w-[300px] justify-start text-left font-normal",
|
||||
"w-[280px] lg:w-[250px] justify-start text-left font-normal px-0 md:px-0 lg:px-4",
|
||||
!date && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<CalendarIcon />
|
||||
<CalendarIcon size={15} className="mr-3" />
|
||||
{date?.from ? (
|
||||
date.to ? (
|
||||
<>
|
||||
|
|
@ -273,7 +273,7 @@ export default function FormPressRelease() {
|
|||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="title">Rentang Waktu</Label>
|
||||
<div className="">
|
||||
<div className="flex flex-row items-center">
|
||||
|
|
@ -319,7 +319,7 @@ export default function FormPressRelease() {
|
|||
{errors.location?.message}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<div className="mt-5 space-y-2">
|
||||
<Label>Invitation</Label>
|
||||
<Select onValueChange={setSelectedTarget}>
|
||||
<SelectTrigger size="md">
|
||||
|
|
@ -338,7 +338,7 @@ export default function FormPressRelease() {
|
|||
</div>
|
||||
<p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p>
|
||||
<div className="flex flex-col ">
|
||||
<div className="mt-1">
|
||||
<div className="mt-1 space-y-2">
|
||||
<Label>Nama Pangkat</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -361,7 +361,7 @@ export default function FormPressRelease() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col my-3">
|
||||
<div className="mt-1">
|
||||
<div className="mt-1 space-y-2">
|
||||
<Label>Nama Lengkap</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ export default function FormDetailPressConference() {
|
|||
)}
|
||||
|
||||
<div className="flex flex-col lg:flex-row items-start lg:items-center justify-between mt-6">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<Label className="mr-3 mb-1">Tanggal</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
|
|
@ -202,11 +202,11 @@ export default function FormDetailPressConference() {
|
|||
id="date"
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-[280px] lg:w-[300px] justify-start text-left font-normal",
|
||||
"w-[280px] lg:w-[250px] justify-start text-left font-normal px-0 md:px-0 lg:px-4",
|
||||
!date && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<CalendarIcon />
|
||||
<CalendarIcon size={15} className="mr-3" />
|
||||
{date?.from ? (
|
||||
date.to ? (
|
||||
<>
|
||||
|
|
@ -233,7 +233,7 @@ export default function FormDetailPressConference() {
|
|||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="title">Rentang Waktu</Label>
|
||||
<div>
|
||||
<div className="flex flex-row items-center">
|
||||
|
|
@ -283,7 +283,7 @@ export default function FormDetailPressConference() {
|
|||
DI SAMPAIKAN OLEH
|
||||
</p>
|
||||
<div className="flex flex-col ">
|
||||
<div className="mt-1">
|
||||
<div className="mt-1 space-y-2">
|
||||
<Label>Nama Pangkat</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -306,7 +306,7 @@ export default function FormDetailPressConference() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col my-3">
|
||||
<div className="mt-1">
|
||||
<div className="mt-1 space-y-2">
|
||||
<Label>Nama Lengkap</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
|
|||
|
|
@ -207,20 +207,20 @@ export default function FormPressConference() {
|
|||
)}
|
||||
|
||||
<div className="flex flex-col lg:flex-row mb-4 mt-2 items-start lg:items-center justify-between">
|
||||
<div className="flex flex-col ">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<Label className="mr-3 mb-1">Tanggal</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<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",
|
||||
"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 />
|
||||
<CalendarIcon size={15} className="mr-3" />
|
||||
{date?.from ? (
|
||||
date.to ? (
|
||||
<>
|
||||
|
|
@ -247,7 +247,7 @@ export default function FormPressConference() {
|
|||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="title">Rentang Waktu</Label>
|
||||
<div className="">
|
||||
<div className="flex flex-row items-center">
|
||||
|
|
@ -295,7 +295,7 @@ export default function FormPressConference() {
|
|||
</div>
|
||||
<p className="text-sm mt-4 font-semibold">DI SAMPAIKAN OLEH</p>
|
||||
<div className="flex flex-col ">
|
||||
<div className="mt-1">
|
||||
<div className="mt-1 space-y-2">
|
||||
<Label>Nama Pangkat</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -318,7 +318,7 @@ export default function FormPressConference() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col my-3">
|
||||
<div className="mt-1">
|
||||
<div className="mt-1 space-y-2">
|
||||
<Label>Nama Lengkap</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ export default function FormUpdatePressConference() {
|
|||
)}
|
||||
|
||||
<div className="flex flex-col lg:flex-row mt-3 items-start lg:items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<Label className="mr-3 mb-1">Tanggal</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
|
|
@ -262,11 +262,11 @@ export default function FormUpdatePressConference() {
|
|||
id="date"
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-[280px] lg:w-[300px] justify-start text-left font-normal",
|
||||
"w-[280px] lg:w-[250px] justify-start text-left font-normal px-0 md:px-0 lg:px-4",
|
||||
!date && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<CalendarIcon />
|
||||
<CalendarIcon size={15} className="mr-3" />
|
||||
{date?.from ? (
|
||||
date.to ? (
|
||||
<>
|
||||
|
|
@ -293,7 +293,7 @@ export default function FormUpdatePressConference() {
|
|||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="title">Rentang Waktu</Label>
|
||||
<div>
|
||||
<div className="flex flex-row items-center">
|
||||
|
|
@ -343,7 +343,7 @@ export default function FormUpdatePressConference() {
|
|||
</div>
|
||||
<p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p>
|
||||
<div className="flex flex-col ">
|
||||
<div className="mt-1">
|
||||
<div className="mt-1 space-y-2">
|
||||
<Label>Nama Pangkat</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -366,7 +366,7 @@ export default function FormUpdatePressConference() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col my-3">
|
||||
<div className="mt-1">
|
||||
<div className="mt-1 space-y-2">
|
||||
<Label>Nama Lengkap</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,944 @@
|
|||
"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 { 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";
|
||||
|
||||
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 FormTaskTa() {
|
||||
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 [platformTypeVisible, setPlatformTypeVisible] = useState(false);
|
||||
const [unitSelection, setUnitSelection] = useState({
|
||||
semua: false,
|
||||
mabes: false,
|
||||
polda: false,
|
||||
polres: false,
|
||||
satker: 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 = ["video", "audio", "image", "text"].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);
|
||||
}
|
||||
};
|
||||
|
||||
const save = async (data: TaskSchema) => {
|
||||
const fileTypeMapping = {
|
||||
all: "1",
|
||||
video: "2",
|
||||
audio: "4",
|
||||
image: "3",
|
||||
text: "5",
|
||||
};
|
||||
|
||||
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(expertise)
|
||||
.filter((key) => expertise[key as keyof typeof expertise])
|
||||
.map((key) => fileTypeMapping[key as keyof typeof fileTypeMapping])
|
||||
.join(",");
|
||||
|
||||
const requestData: {
|
||||
id?: number;
|
||||
title: string;
|
||||
assignedToLevel: any;
|
||||
assignedToUsers: any;
|
||||
assignmentTypeId: string;
|
||||
fileTypeOutput: string;
|
||||
narration: string;
|
||||
platformType: string | null;
|
||||
assignmentMainTypeId: any;
|
||||
assignmentType: string;
|
||||
assignedToRole: string;
|
||||
broadcastType: string;
|
||||
expertCompetencies: string;
|
||||
attachmentUrl: string[];
|
||||
} = {
|
||||
...data,
|
||||
// assignmentType,
|
||||
// assignmentCategory,
|
||||
assignedToLevel: handlePoldaPolresChange(),
|
||||
assignedToUsers: assignmentPurposeString,
|
||||
assignedToRole: selectedTarget,
|
||||
assignmentType: taskType,
|
||||
broadcastType: broadcastType,
|
||||
assignmentMainTypeId: mainType,
|
||||
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); // Reset the timer to 2 minutes for the next recording
|
||||
};
|
||||
|
||||
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/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");
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
<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"
|
||||
value={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">
|
||||
<div className="mt-5 space-y-2">
|
||||
<Label>{t("assignment-selection")}</Label>
|
||||
<Select onValueChange={setSelectedTarget}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Choose" />
|
||||
</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-5 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">
|
||||
[{t("custom")}]
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Daftar Wilayah Polda dan Polres</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="grid grid-cols-2 gap-2 max-h-[400px] overflow-y-auto">
|
||||
{listDest.map((polda: any) => (
|
||||
<div key={polda.id} className="border p-2">
|
||||
<Label className="flex items-center">
|
||||
<Checkbox
|
||||
checked={checkedLevels.has(polda.id)}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(polda.id)
|
||||
}
|
||||
className="mr-3"
|
||||
/>
|
||||
{polda.name}
|
||||
<button
|
||||
onClick={() => toggleExpand(polda.id)}
|
||||
className="ml-2 focus:outline-none"
|
||||
>
|
||||
{expandedPolda[polda.id] ? (
|
||||
<ChevronUp size={16} />
|
||||
) : (
|
||||
<ChevronDown size={16} />
|
||||
)}
|
||||
</button>
|
||||
</Label>
|
||||
{expandedPolda[polda.id] && (
|
||||
<div className="ml-6 mt-2">
|
||||
<Label className="block">
|
||||
<Checkbox
|
||||
checked={polda?.subDestination?.every(
|
||||
(polres: any) =>
|
||||
checkedLevels.has(polres.id)
|
||||
)}
|
||||
onCheckedChange={(isChecked) => {
|
||||
const updatedLevels = new Set(
|
||||
checkedLevels
|
||||
);
|
||||
polda?.subDestination?.forEach(
|
||||
(polres: any) => {
|
||||
if (isChecked) {
|
||||
updatedLevels.add(polres.id);
|
||||
} else {
|
||||
updatedLevels.delete(polres.id);
|
||||
}
|
||||
}
|
||||
);
|
||||
setCheckedLevels(updatedLevels);
|
||||
}}
|
||||
className="mr-2"
|
||||
/>
|
||||
Pilih Semua Polres
|
||||
</Label>
|
||||
{polda?.subDestination?.map((polres: any) => (
|
||||
<Label key={polres.id} className="block mt-1">
|
||||
<Checkbox
|
||||
checked={checkedLevels.has(polres.id)}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(polres.id)
|
||||
}
|
||||
className="mr-2"
|
||||
/>
|
||||
{polres.name}
|
||||
</Label>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 space-y-2">
|
||||
<Label>{t("assigment-type")} </Label>
|
||||
<RadioGroup
|
||||
value={taskType}
|
||||
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">
|
||||
{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="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
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
|
@ -66,6 +66,7 @@ import { Icon } from "@iconify/react/dist/iconify.js";
|
|||
import WavesurferPlayer from "@wavesurfer/react";
|
||||
import WaveSurfer from "wavesurfer.js";
|
||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const taskSchema = z.object({
|
||||
uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -206,6 +207,7 @@ export default function FormTaskDetail() {
|
|||
text: false,
|
||||
});
|
||||
|
||||
const t = useTranslations("Form");
|
||||
const [uploadResults, setUploadResults] = useState<UploadResult[]>([]);
|
||||
const [isTableResult, setIsTableResult] = useState(false);
|
||||
const [isSentResult] = useState(false);
|
||||
|
|
@ -812,7 +814,7 @@ export default function FormTaskDetail() {
|
|||
{detail !== undefined ? (
|
||||
<div className="px-6 py-6">
|
||||
<div className="flex flex-col sm:flex-row lg:flex-row justify-between">
|
||||
<p className="text-lg font-semibold mb-3">Detail Penugasan</p>
|
||||
<p className="text-lg font-semibold mb-3">{t("detail-task")}</p>
|
||||
<div
|
||||
className="flex gap-3"
|
||||
style={
|
||||
|
|
@ -830,7 +832,7 @@ export default function FormTaskDetail() {
|
|||
color="primary"
|
||||
onClick={() => setModalType("terkirim")}
|
||||
>
|
||||
{sentAcceptance?.length} Terkirim
|
||||
{sentAcceptance?.length} {t("sent")}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
|
|
@ -840,13 +842,15 @@ export default function FormTaskDetail() {
|
|||
onClick={() => setModalType("diterima")}
|
||||
className="ml-3"
|
||||
>
|
||||
{acceptAcceptance?.length} Diterima
|
||||
{acceptAcceptance?.length} {t("accepted")}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px] overflow-y-auto max-h-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Detail Status Penugasan</DialogTitle>
|
||||
<DialogTitle>
|
||||
{t("assignment-status-details")}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{modalType === "terkirim" && getModalContent("terkirim")}
|
||||
|
|
@ -860,7 +864,7 @@ export default function FormTaskDetail() {
|
|||
<form>
|
||||
<div className="gap-5 mb-5">
|
||||
<div className="space-y-2">
|
||||
<Label>Kode Unik</Label>
|
||||
<Label>{t("unique-code")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="uniqueCode"
|
||||
|
|
@ -877,7 +881,7 @@ export default function FormTaskDetail() {
|
|||
/>
|
||||
</div>
|
||||
<div className="space-y-2 mt-6">
|
||||
<Label>Judul</Label>
|
||||
<Label>{t("title")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
|
|
@ -896,8 +900,8 @@ export default function FormTaskDetail() {
|
|||
)}
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row lg:flex-row sm:items-center lg:items-center">
|
||||
<div className="mt-6">
|
||||
<Label>Tujuan Pemilihan Tugas</Label>
|
||||
<div className="mt-6 space-y-2">
|
||||
<Label>{t("assignment-selection")}</Label>
|
||||
<Select
|
||||
onValueChange={setSelectedTarget}
|
||||
value={detail.assignedToRole}
|
||||
|
|
@ -935,7 +939,7 @@ export default function FormTaskDetail() {
|
|||
<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]">
|
||||
|
|
@ -1018,8 +1022,8 @@ export default function FormTaskDetail() {
|
|||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<Label>Tipe Penugasan</Label>
|
||||
<div className="mt-6 space-y-2">
|
||||
<Label>{t("type-task")}</Label>
|
||||
<RadioGroup
|
||||
value={detail.assignmentMainType.id.toString()}
|
||||
onValueChange={(value) => setMainType(value)}
|
||||
|
|
@ -1033,8 +1037,8 @@ export default function FormTaskDetail() {
|
|||
<Label htmlFor="medsos-mediahub">Medsos Mediahub</Label>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<Label>Jenis Tugas </Label>
|
||||
<div className="mt-6 space-y-2">
|
||||
<Label>{t("assigment-type")} </Label>
|
||||
<RadioGroup
|
||||
value={detail.taskType.toString()}
|
||||
onValueChange={(value) => setTaskType(String(value))}
|
||||
|
|
@ -1047,8 +1051,8 @@ export default function FormTaskDetail() {
|
|||
</RadioGroup>
|
||||
</div>
|
||||
{/* RadioGroup Assignment Category */}
|
||||
<div className="mt-6">
|
||||
<Label>Jenis Penugasan</Label>
|
||||
<div className="mt-6 space-y-2">
|
||||
<Label>{t("type-of-task")}</Label>
|
||||
<RadioGroup
|
||||
value={detail.assignmentType.id.toString()}
|
||||
onValueChange={(value) => setType(value)}
|
||||
|
|
@ -1068,8 +1072,8 @@ export default function FormTaskDetail() {
|
|||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<Label>Output Tugas</Label>
|
||||
<div className="mt-6 space-y-2">
|
||||
<Label>{t("output-task")}</Label>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{Object.keys(taskOutput).map((key) => (
|
||||
<div className="flex items-center gap-2" key={key}>
|
||||
|
|
@ -1089,8 +1093,8 @@ export default function FormTaskDetail() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<Label>Narasi Penugasan</Label>
|
||||
<div className="mt-6 space-y-2">
|
||||
<Label>{t("description")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="naration"
|
||||
|
|
@ -1104,11 +1108,13 @@ export default function FormTaskDetail() {
|
|||
</p>
|
||||
)} */}
|
||||
</div>
|
||||
<div className="space-y-1.5 mt-5">
|
||||
<Label htmlFor="attachment">Lampiran</Label>
|
||||
<div className=" mt-5 space-y-2">
|
||||
<Label htmlFor="attachment">{t("attachment")}</Label>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
{videoUploadedFiles?.length > 0 && <Label>Video</Label>}
|
||||
{videoUploadedFiles?.length > 0 && (
|
||||
<Label>{t("audio-visual")}</Label>
|
||||
)}
|
||||
<div>
|
||||
{selectedVideo && (
|
||||
<Card className="mt-2">
|
||||
|
|
@ -1154,7 +1160,9 @@ export default function FormTaskDetail() {
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{imageUploadedFiles?.length > 0 && <Label>Foto</Label>}
|
||||
{imageUploadedFiles?.length > 0 && (
|
||||
<Label>{t("image")}</Label>
|
||||
)}
|
||||
<div>
|
||||
{selectedImage && (
|
||||
<Card className="mt-2">
|
||||
|
|
@ -1200,7 +1208,9 @@ export default function FormTaskDetail() {
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{textUploadedFiles?.length > 0 && <Label>Teks</Label>}
|
||||
{textUploadedFiles?.length > 0 && (
|
||||
<Label>{t("text")}</Label>
|
||||
)}
|
||||
<div>
|
||||
{selectedText && (
|
||||
<Card className="mt-2">
|
||||
|
|
@ -1247,7 +1257,9 @@ export default function FormTaskDetail() {
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{audioUploadedFiles?.length > 0 && <Label>Audio</Label>}
|
||||
{audioUploadedFiles?.length > 0 && (
|
||||
<Label>{t("audio")}</Label>
|
||||
)}
|
||||
<div>
|
||||
{selectedAudio && (
|
||||
<Card className="mt-2">
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ import WavesurferPlayer from "@wavesurfer/react";
|
|||
import { getCsrfToken } from "@/service/auth";
|
||||
import { Upload } from "tus-js-client";
|
||||
import { error, loading } from "@/lib/swal";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const taskSchema = z.object({
|
||||
// uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -93,6 +95,13 @@ type Url = {
|
|||
attachmentUrl: string;
|
||||
};
|
||||
|
||||
const CustomEditor = dynamic(
|
||||
() => {
|
||||
return import("@/components/editor/custom-editor");
|
||||
},
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export default function FormTaskEdit() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -110,6 +119,7 @@ export default function FormTaskEdit() {
|
|||
text: false,
|
||||
});
|
||||
|
||||
const t = useTranslations("Form");
|
||||
const [imageFiles, setImageFiles] = useState<FileWithPreview[]>([]);
|
||||
const [videoFiles, setVideoFiles] = useState<FileWithPreview[]>([]);
|
||||
const [textFiles, setTextFiles] = useState<FileWithPreview[]>([]);
|
||||
|
|
@ -294,6 +304,63 @@ export default function FormTaskEdit() {
|
|||
return Array.from(checkedLevels).join(","); // Mengonversi Set ke string
|
||||
};
|
||||
|
||||
const handleUnitChange = (
|
||||
key: keyof typeof unitSelection,
|
||||
value: boolean
|
||||
) => {
|
||||
if (key === "allUnit") {
|
||||
const newState = {
|
||||
allUnit: 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.allUnit = allChecked;
|
||||
|
||||
setUnitSelection(updatedSelection);
|
||||
}
|
||||
};
|
||||
|
||||
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",
|
||||
|
|
@ -624,7 +691,7 @@ export default function FormTaskEdit() {
|
|||
{detail !== undefined ? (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="gap-5 mb-5">
|
||||
<div className="space-y-2 mt-6">
|
||||
<div className="space-y-2">
|
||||
<Label>Judul</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -644,7 +711,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-6">
|
||||
<div className="mt-5 space-y-2">
|
||||
<Label>Tujuan Pemilihan Tugas</Label>
|
||||
<Select
|
||||
onValueChange={setSelectedTarget}
|
||||
|
|
@ -660,7 +727,7 @@ export default function FormTaskEdit() {
|
|||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3 mt-6 lg:pt-5 lg:ml-3">
|
||||
<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
|
||||
|
|
@ -669,7 +736,10 @@ export default function FormTaskEdit() {
|
|||
unitSelection[key as keyof typeof unitSelection]
|
||||
}
|
||||
onCheckedChange={(value) =>
|
||||
setUnitSelection({ ...unitSelection, [key]: value })
|
||||
handleUnitChange(
|
||||
key as keyof typeof unitSelection,
|
||||
value as boolean
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Label htmlFor={key}>
|
||||
|
|
@ -678,7 +748,7 @@ export default function FormTaskEdit() {
|
|||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-6 lg:pt-5 lg:pl-3">
|
||||
<div className="mt-6 lg:pt-6 lg:pl-3">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="soft" size="sm" color="primary">
|
||||
|
|
@ -762,8 +832,8 @@ export default function FormTaskEdit() {
|
|||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<Label>Tipe Penugasan</Label>
|
||||
<div className="mt-5 space-y-2">
|
||||
<Label>{t("type-task")}</Label>
|
||||
<RadioGroup
|
||||
defaultValue={detail.assignmentMainType.id.toString()} // State yang dipetakan ke value RadioGroup
|
||||
onValueChange={(value) => setMainType(value)}
|
||||
|
|
@ -777,8 +847,8 @@ export default function FormTaskEdit() {
|
|||
<Label htmlFor="medsos-mediahub">Medsos Mediahub</Label>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<Label>Jenis Tugas </Label>
|
||||
<div className="mt-5 space-y-2">
|
||||
<Label>{t("assigment-type")} </Label>
|
||||
<RadioGroup
|
||||
defaultValue={detail.taskType.toString()}
|
||||
onValueChange={(value) => setTaskType(String(value))}
|
||||
|
|
@ -791,8 +861,8 @@ export default function FormTaskEdit() {
|
|||
</RadioGroup>
|
||||
</div>
|
||||
{/* RadioGroup Assignment Category */}
|
||||
<div className="mt-6">
|
||||
<Label>Jenis Penugasan</Label>
|
||||
<div className="mt-5 space-y-2">
|
||||
<Label>{t("type-of-task")}</Label>
|
||||
<RadioGroup
|
||||
defaultValue={detail.assignmentType.id.toString()} // State yang dipetakan ke value RadioGroup
|
||||
onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
|
||||
|
|
@ -812,16 +882,19 @@ export default function FormTaskEdit() {
|
|||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<Label>Output Tugas</Label>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<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) =>
|
||||
setTaskOutput({ ...taskOutput, [key]: value })
|
||||
handleTaskOutputChange(
|
||||
key as keyof typeof taskOutput,
|
||||
value as boolean
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Label htmlFor={key}>
|
||||
|
|
@ -831,17 +904,15 @@ export default function FormTaskEdit() {
|
|||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<Label>Narasi Penugasan</Label>
|
||||
<div className="mt-5 space-y-2">
|
||||
<Label>{t("description")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="naration"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<JoditEditor
|
||||
ref={editor}
|
||||
value={detail?.narration}
|
||||
<CustomEditor
|
||||
onChange={onChange}
|
||||
className="dark:text-black"
|
||||
initialData={detail?.narration || value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
@ -851,11 +922,11 @@ export default function FormTaskEdit() {
|
|||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-1.5 mt-5">
|
||||
<Label htmlFor="attachments">Lampiran</Label>
|
||||
<div className="space-y-2.5 mt-5">
|
||||
<Label htmlFor="attachments">{t("attachment")}</Label>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<Label>Video</Label>
|
||||
<div className="space-y-2">
|
||||
<Label>{t("audio-visual")}</Label>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"mp4/*": [],
|
||||
|
|
@ -895,8 +966,8 @@ export default function FormTaskEdit() {
|
|||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<Label>Foto</Label>
|
||||
<div className="space-y-2">
|
||||
<Label>{t("image")}</Label>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"image/*": [],
|
||||
|
|
@ -935,8 +1006,8 @@ export default function FormTaskEdit() {
|
|||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<Label>Teks</Label>
|
||||
<div className="space-y-2">
|
||||
<Label>{t("text")}</Label>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"pdf/*": [],
|
||||
|
|
@ -975,8 +1046,8 @@ export default function FormTaskEdit() {
|
|||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<Label>Audio</Label>
|
||||
<div className="space-y-2">
|
||||
<Label>{t("audio")}</Label>
|
||||
<AudioRecorder
|
||||
onRecordingComplete={addAudioElement}
|
||||
audioTrackConstraints={{
|
||||
|
|
@ -1030,7 +1101,7 @@ export default function FormTaskEdit() {
|
|||
</div>
|
||||
{audioFile && (
|
||||
<div className="flex flex-row justify-between items-center">
|
||||
<p>Voice Note</p>
|
||||
<p>{t("voice-note")}</p>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleDeleteAudio}
|
||||
|
|
@ -1043,8 +1114,8 @@ export default function FormTaskEdit() {
|
|||
)}
|
||||
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
|
||||
{/* Display remaining time */}
|
||||
<div className="mt-4">
|
||||
<h2 className="text-lg font-bold">Link Berita</h2>
|
||||
<div className="mt-4 space-y-2">
|
||||
<h2 className="text-lg font-bold">{t("news-links")}</h2>
|
||||
{urlInputs.map((url: any, index: any) => (
|
||||
<div
|
||||
key={url.id}
|
||||
|
|
@ -1066,7 +1137,7 @@ export default function FormTaskEdit() {
|
|||
className="mt-4 bg-green-500 text-white px-4 py-2 rounded"
|
||||
onClick={handleAddLink}
|
||||
>
|
||||
Tambah Link
|
||||
{t("add-links")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -32,13 +32,15 @@ import {
|
|||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { ChevronDown, ChevronUp } from "lucide-react";
|
||||
import { 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";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -73,6 +75,13 @@ export type taskDetail = {
|
|||
is_active: string;
|
||||
};
|
||||
|
||||
const CustomEditor = dynamic(
|
||||
() => {
|
||||
return import("@/components/editor/custom-editor");
|
||||
},
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export default function FormTask() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -107,6 +116,7 @@ export default function FormTask() {
|
|||
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[]>([]);
|
||||
|
|
@ -186,6 +196,63 @@ export default function FormTask() {
|
|||
return Array.from(checkedLevels).join(","); // Mengonversi Set ke string
|
||||
};
|
||||
|
||||
const handleUnitChange = (
|
||||
key: keyof typeof unitSelection,
|
||||
value: boolean
|
||||
) => {
|
||||
if (key === "allUnit") {
|
||||
const newState = {
|
||||
allUnit: 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.allUnit = allChecked;
|
||||
|
||||
setUnitSelection(updatedSelection);
|
||||
}
|
||||
};
|
||||
|
||||
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",
|
||||
|
|
@ -478,13 +545,13 @@ export default function FormTask() {
|
|||
return (
|
||||
<Card>
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Penugasan</p>
|
||||
<p className="text-lg font-semibold mb-3">{t("form-task")}</p>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2">
|
||||
<Label>Judul</Label>
|
||||
<Label>{t("title")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
|
|
@ -503,11 +570,11 @@ export default function FormTask() {
|
|||
)}
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row lg:flex-row sm:items-center lg:items-center">
|
||||
<div className="mt-5">
|
||||
<Label>Tujuan Pemilihan Tugas</Label>
|
||||
<div className="mt-5 space-y-2">
|
||||
<Label>{t("assignment-selection")}</Label>
|
||||
<Select onValueChange={setSelectedTarget}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
<SelectValue placeholder="Choose" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="3,4">Semua Pengguna</SelectItem>
|
||||
|
|
@ -516,14 +583,17 @@ export default function FormTask() {
|
|||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3 mt-5 lg:pt-5 lg:ml-3">
|
||||
<div className="flex flex-wrap gap-3 mt-5 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) =>
|
||||
setUnitSelection({ ...unitSelection, [key]: value })
|
||||
handleUnitChange(
|
||||
key as keyof typeof unitSelection,
|
||||
value as boolean
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Label htmlFor={key}>
|
||||
|
|
@ -532,11 +602,11 @@ export default function FormTask() {
|
|||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-6 lg:pt-5 lg:pl-3">
|
||||
<div className="mt-6 lg:pt-6 lg:pl-3">
|
||||
<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]">
|
||||
|
|
@ -614,10 +684,10 @@ export default function FormTask() {
|
|||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Label>Tipe Penugasan</Label>
|
||||
<div className="mt-5 space-y-2">
|
||||
<Label>{t("type-task")}</Label>
|
||||
<RadioGroup
|
||||
value={mainType} // State yang dipetakan ke value RadioGroup
|
||||
value={mainType}
|
||||
onValueChange={(value) => setMainType(value)}
|
||||
// value={String(mainType)}
|
||||
// onValueChange={(value) => setMainType(Number(value))}
|
||||
|
|
@ -629,8 +699,8 @@ export default function FormTask() {
|
|||
<Label htmlFor="medsos-mediahub">Medsos Mediahub</Label>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Label>Jenis Tugas </Label>
|
||||
<div className="mt-5 space-y-2">
|
||||
<Label>{t("assigment-type")} </Label>
|
||||
<RadioGroup
|
||||
value={taskType}
|
||||
onValueChange={(value) => setTaskType(String(value))}
|
||||
|
|
@ -643,8 +713,8 @@ export default function FormTask() {
|
|||
</RadioGroup>
|
||||
</div>
|
||||
{/* RadioGroup Assignment Category */}
|
||||
<div className="mt-5">
|
||||
<Label>Jenis Penugasan</Label>
|
||||
<div className="mt-5 space-y-2">
|
||||
<Label>{t("type-of-task")}</Label>
|
||||
<RadioGroup
|
||||
value={type} // State yang dipetakan ke value RadioGroup
|
||||
onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
|
||||
|
|
@ -664,16 +734,19 @@ export default function FormTask() {
|
|||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Label>Output Tugas</Label>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<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) =>
|
||||
setTaskOutput({ ...taskOutput, [key]: value })
|
||||
handleTaskOutputChange(
|
||||
key as keyof typeof taskOutput,
|
||||
value as boolean
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Label htmlFor={key}>
|
||||
|
|
@ -704,18 +777,13 @@ export default function FormTask() {
|
|||
</div>
|
||||
</RadioGroup>
|
||||
</div> */}
|
||||
<div className="mt-5">
|
||||
<Label>Narasi Penugasan</Label>
|
||||
<div className="mt-5 space-y-2">
|
||||
<Label>{t("description")}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="naration"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<JoditEditor
|
||||
ref={editor}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className="dark:text-black"
|
||||
/>
|
||||
<CustomEditor onChange={onChange} initialData={value} />
|
||||
)}
|
||||
/>
|
||||
{errors.naration?.message && (
|
||||
|
|
@ -724,11 +792,11 @@ export default function FormTask() {
|
|||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-1.5 mt-5">
|
||||
<Label htmlFor="attachments">Lampiran</Label>
|
||||
<div className="space-y-2.5 mt-5">
|
||||
<Label htmlFor="attachments">{t("attachment")}</Label>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<Label>Video</Label>
|
||||
<Label>{t("audio-visual")}</Label>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"mp4/*": [],
|
||||
|
|
@ -739,8 +807,8 @@ export default function FormTask() {
|
|||
onDrop={(files) => setVideoFiles(files)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Foto</Label>
|
||||
<div className="space-y-2">
|
||||
<Label>{t("image")}</Label>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"image/*": [],
|
||||
|
|
@ -750,8 +818,8 @@ export default function FormTask() {
|
|||
onDrop={(files) => setImageFiles(files)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Teks</Label>
|
||||
<div className="space-y-2">
|
||||
<Label>{t("text")}</Label>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"pdf/*": [],
|
||||
|
|
@ -761,8 +829,8 @@ export default function FormTask() {
|
|||
onDrop={(files) => setTextFiles(files)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Audio</Label>
|
||||
<div className="space-y-2">
|
||||
<Label>{t("audio")}</Label>
|
||||
<AudioRecorder
|
||||
onRecordingComplete={addAudioElement}
|
||||
audioTrackConstraints={{
|
||||
|
|
@ -790,7 +858,7 @@ export default function FormTask() {
|
|||
key={idx}
|
||||
className="flex flex-row justify-between items-center"
|
||||
>
|
||||
<p>Voice Note</p>
|
||||
<p>{t("voice-note")}</p>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => handleDeleteAudio(idx)}
|
||||
|
|
@ -803,11 +871,11 @@ export default function FormTask() {
|
|||
))}
|
||||
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
|
||||
{/* Display remaining time */}
|
||||
<div className="mt-4">
|
||||
<h2 className="text-lg font-bold">Link Berita</h2>
|
||||
<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
|
||||
<Input
|
||||
type="url"
|
||||
className="border rounded p-2 w-full"
|
||||
placeholder={`Masukkan link berita ${index + 1}`}
|
||||
|
|
@ -822,18 +890,19 @@ export default function FormTask() {
|
|||
className="bg-red-500 text-white px-3 py-1 rounded"
|
||||
onClick={() => handleRemoveRow(index)}
|
||||
>
|
||||
Hapus
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
className="mt-2 bg-blue-500 text-white px-4 py-2 rounded"
|
||||
onClick={handleAddRow}
|
||||
size="sm"
|
||||
>
|
||||
Tambah Link
|
||||
</button>
|
||||
{t("add-links")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -842,7 +911,7 @@ export default function FormTask() {
|
|||
{/* Submit Button */}
|
||||
<div className="mt-4">
|
||||
<Button type="submit" color="primary">
|
||||
Submit
|
||||
{t("submit")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
@ -21,7 +24,15 @@ const ContentCategory = (props: { group?: string }) => {
|
|||
}, []);
|
||||
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
|
||||
);
|
||||
|
|
@ -47,7 +58,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">
|
||||
|
|
@ -84,10 +98,16 @@ const ContentCategory = (props: { group?: string }) => {
|
|||
// <h3 className="text-sm font-semibold truncate">{category?.name}</h3>
|
||||
// </div>
|
||||
// </Link>
|
||||
<Link key={category?.id} href={`all/filter?category=${category?.id}`} className="relative group rounded-md overflow-hidden shadow-md hover:shadow-lg">
|
||||
<Link
|
||||
key={category?.id}
|
||||
href={`all/filter?category=${category?.id}`}
|
||||
className="relative group rounded-md overflow-hidden shadow-md hover:shadow-lg"
|
||||
>
|
||||
{/* 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}
|
||||
|
|
@ -99,8 +119,10 @@ const ContentCategory = (props: { group?: string }) => {
|
|||
<div className="absolute inset-0 bg-black bg-opacity-25 group-hover:bg-opacity-35 transition-all duration-300 rounded-md"></div>
|
||||
|
||||
{/* 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">
|
||||
<h3 className="text-sm font-semibold truncate">{category?.name}</h3>
|
||||
<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>
|
||||
</div>
|
||||
</Link>
|
||||
) : (
|
||||
|
|
@ -120,10 +142,16 @@ const ContentCategory = (props: { group?: string }) => {
|
|||
// <h3 className="text-sm font-semibold truncate">{category?.name}</h3>
|
||||
// </div>
|
||||
// </Link>
|
||||
<Link key={category?.id} href={`all/filter?category=${category?.id}`} className="relative group rounded-md overflow-hidden shadow-md hover:shadow-lg">
|
||||
<Link
|
||||
key={category?.id}
|
||||
href={`all/filter?category=${category?.id}`}
|
||||
className="relative group rounded-md overflow-hidden shadow-md hover:shadow-lg"
|
||||
>
|
||||
{/* 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}
|
||||
|
|
@ -136,14 +164,19 @@ 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">
|
||||
<h3 className="text-sm font-semibold truncate">{category?.name}</h3>
|
||||
<h3 className="text-sm font-semibold truncate">
|
||||
{category?.name}
|
||||
</h3>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center flex-row justify-center">
|
||||
<Button onClick={() => setSeeAllValue(!seeAllValue)} className="bg-white hover:bg-[#bb3523] text-[#bb3523] hover:text-white border-2 border-[#bb3523]">
|
||||
<Button
|
||||
onClick={() => setSeeAllValue(!seeAllValue)}
|
||||
className="bg-white hover:bg-[#bb3523] text-[#bb3523] hover:text-white border-2 border-[#bb3523]"
|
||||
>
|
||||
{seeAllValue ? t("seeLess") : t("seeMore")}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -28,23 +28,33 @@ const Footer = () => {
|
|||
|
||||
return (
|
||||
<footer className="bg-[#bb3523] text-white text-xs lg:text-sm py-4 space-y-3 lg:space-y-0 h-">
|
||||
<div className="mx-auto flex flex-col md:flex-row justify-between items-center px-4">
|
||||
<div className="mx-auto flex flex-col md:flex-row justify-between items-center px-4 gap-3">
|
||||
{/* Hak Cipta */}
|
||||
<div className="text-center md:text-left">
|
||||
{t("copyright")} © {new Date().getFullYear()} {t("publicRelation")} {t("reserved")}
|
||||
{t("copyright")} © {new Date().getFullYear()}{" "}
|
||||
{t("publicRelation")} {t("reserved")}
|
||||
</div>
|
||||
|
||||
{/* Menu Links */}
|
||||
<div className="flex flex-wrap justify-center items-center space-x-3">
|
||||
<Link href={generateLocalizedPath("/feedback", String(locale))} className="hover:underline">
|
||||
<Link
|
||||
href={generateLocalizedPath("/feedback", String(locale))}
|
||||
className="hover:underline"
|
||||
>
|
||||
{t("feedback")}
|
||||
</Link>
|
||||
<span className="hidden md:inline-block ">|</span>
|
||||
<Link href={generateLocalizedPath("/contact", String(locale))} className="hover:underline">
|
||||
<Link
|
||||
href={generateLocalizedPath("/contact", String(locale))}
|
||||
className="hover:underline"
|
||||
>
|
||||
{t("contact")}
|
||||
</Link>
|
||||
<span className="hidden md:inline-block">|</span>
|
||||
<Link href={generateLocalizedPath("/faqs", String(locale))} className="hover:underline">
|
||||
<Link
|
||||
href={generateLocalizedPath("/faqs", String(locale))}
|
||||
className="hover:underline"
|
||||
>
|
||||
FAQ
|
||||
</Link>
|
||||
|
||||
|
|
@ -55,12 +65,17 @@ const Footer = () => {
|
|||
{t("privacy")}
|
||||
</a>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="flex flex-col overflow-y-scroll h-[80%]" size="md">
|
||||
<DialogContent
|
||||
className="flex flex-col overflow-y-scroll h-[80%]"
|
||||
size="md"
|
||||
>
|
||||
<div className="flex flex-row items-center justify-center gap-4">
|
||||
<img src="/assets/icon-privacy.png" alt="Privacy" />
|
||||
<p className="font-semibold text-lg">{t("privacy")}</p>
|
||||
</div>
|
||||
<div className="container text-black dark:text-white space-y-2">{parse(String(privacy))}</div>
|
||||
<div className="container text-black dark:text-white space-y-2">
|
||||
{parse(String(privacy))}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
|
@ -69,13 +84,25 @@ const Footer = () => {
|
|||
<div className="flex justify-center items-center space-x-3">
|
||||
<span className="text-sm">Follow Us:</span>
|
||||
<a href="#" aria-label="Facebook">
|
||||
<img src="/assets/facebook.svg" alt="Facebook" className="w-5 h-5" />
|
||||
<img
|
||||
src="/assets/facebook.svg"
|
||||
alt="Facebook"
|
||||
className="w-5 h-5"
|
||||
/>
|
||||
</a>
|
||||
<a href="#" aria-label="Instagram">
|
||||
<img src="/assets/instagram.svg" alt="Instagram" className="w-5 h-5" />
|
||||
<img
|
||||
src="/assets/instagram.svg"
|
||||
alt="Instagram"
|
||||
className="w-5 h-5"
|
||||
/>
|
||||
</a>
|
||||
<a href="#" aria-label="Twitter">
|
||||
<img src="/assets/twitter.svg" alt="Instagram" className="w-5 h-5" />
|
||||
<img
|
||||
src="/assets/twitter.svg"
|
||||
alt="Instagram"
|
||||
className="w-5 h-5"
|
||||
/>
|
||||
</a>
|
||||
<a href="#" aria-label="TikTok">
|
||||
<img src="/assets/tiktok.svg" alt="TikTok" className="w-5 h-5" />
|
||||
|
|
|
|||
|
|
@ -5,9 +5,372 @@ import "swiper/css/navigation";
|
|||
import { getHeroData } from "@/service/landing/landing";
|
||||
import Link from "next/link";
|
||||
import { useParams, usePathname, useRouter } from "next/navigation";
|
||||
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "@/components/ui/carousel";
|
||||
import { Skeleton } from "../ui/skeleton";
|
||||
import Image from "next/image";
|
||||
import Cookies from "js-cookie";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "../ui/card";
|
||||
import { Label } from "../ui/label";
|
||||
import { Input } from "../ui/input";
|
||||
import { Button } from "../ui/button";
|
||||
import { Textarea } from "../ui/textarea";
|
||||
import { Checkbox } from "../ui/checkbox";
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "../ui/dialog";
|
||||
import { Autoplay, Pagination } from "swiper/modules";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import "swiper/css";
|
||||
import "swiper/css/pagination";
|
||||
|
||||
const HeroModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const [heroData, setHeroData] = useState<any>();
|
||||
const params = useParams();
|
||||
const locale = params?.locale;
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchCategories() {
|
||||
const url = "https://netidhub.com/api/csrf";
|
||||
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Fetch error: ", error);
|
||||
}
|
||||
}
|
||||
|
||||
fetchCategories();
|
||||
initFetch();
|
||||
}, []);
|
||||
|
||||
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="relative dark:bg-gray-900 rounded-lg w-[90%] md:w-[600px] p-4 shadow-none">
|
||||
<Swiper
|
||||
pagination={{ dynamicBullets: true }}
|
||||
modules={[Pagination, Autoplay]}
|
||||
className="mySwiper w-full"
|
||||
>
|
||||
{heroData?.map((list: any, index: number) => (
|
||||
<SwiperSlide key={list?.id}>
|
||||
<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"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
<Image
|
||||
src={list?.thumbnailLink}
|
||||
alt="gambar-utama"
|
||||
width={1920}
|
||||
height={1080}
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||
shimmer(700, 475)
|
||||
)}`}
|
||||
className="w-full h-[310px] lg:h-[420px] rounded-lg object-cover"
|
||||
/>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<Link href={`${locale}/image/detail/${list?.slug}`}>
|
||||
<h2 className="text-lg leading-tight">{list?.title}</h2>
|
||||
</Link>
|
||||
|
||||
<p className="text-xs flex items-center gap-1 mt-2 opacity-80">
|
||||
{formatDateToIndonesian(new Date(list?.createdAt))}{" "}
|
||||
{list?.timezone || "WIB"} |{" "}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
className="inline-block"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
|
||||
/>
|
||||
</svg>
|
||||
{list?.clickCount}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
<style jsx global>{`
|
||||
.swiper-pagination-bullet {
|
||||
background: white !important;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.swiper-pagination-bullet-active {
|
||||
background: white !important;
|
||||
opacity: 1;
|
||||
}
|
||||
`}</style>
|
||||
</Swiper>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SurveyIntroModal = ({ onNext }: { onNext: () => void }) => {
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||
<div className="relative bg-white rounded-xl p-6 w-[90%] max-w-md text-center">
|
||||
<button
|
||||
onClick={onNext}
|
||||
className="absolute top-3 right-3 text-gray-500 hover:text-black"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
|
||||
<Image
|
||||
src="/assets/survey.jpg"
|
||||
alt="Survey Illustration"
|
||||
width={300}
|
||||
height={200}
|
||||
className="mx-auto my-4"
|
||||
/>
|
||||
|
||||
<button
|
||||
onClick={onNext}
|
||||
className="mt-4 bg-red-600 hover:bg-red-700 text-white font-semibold py-3 px-6 rounded w-full"
|
||||
>
|
||||
Lihat Selengkapnya
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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 = () => {
|
||||
const router = useRouter();
|
||||
|
|
@ -16,6 +379,9 @@ const Hero: React.FC = () => {
|
|||
const locale = params?.locale;
|
||||
const [isLoading, setIsLoading] = useState<any>(true);
|
||||
const [heroData, setHeroData] = useState<any>();
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [showSurveyModal, setShowSurveyModal] = useState(false);
|
||||
const [showFormModal, setShowFormModal] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
|
|
@ -25,6 +391,29 @@ const Hero: React.FC = () => {
|
|||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const roleId = Cookies.get("urie");
|
||||
if (!roleId) {
|
||||
setShowModal(true);
|
||||
}
|
||||
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(() => {
|
||||
async function fetchCategories() {
|
||||
const url = "https://netidhub.com/api/csrf";
|
||||
|
|
@ -37,7 +426,7 @@ const Hero: React.FC = () => {
|
|||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data; // Menampilkan data yang diterima dari API
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Fetch error: ", error);
|
||||
}
|
||||
|
|
@ -67,11 +456,28 @@ const Hero: React.FC = () => {
|
|||
<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="flex flex-col lg:flex-row items-start justify-center gap-8 px-4 lg:px-20 py-4 mx-auto w-auto mt-6">
|
||||
{/* Section Gambar Utama */}
|
||||
<div className="relative">
|
||||
{showModal && <HeroModal onClose={() => setShowModal(false)} />}
|
||||
{showSurveyModal && !showFormModal && (
|
||||
<SurveyIntroModal
|
||||
onNext={() => {
|
||||
setShowSurveyModal(false);
|
||||
setShowFormModal(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showFormModal && (
|
||||
<SurveyFormModal onClose={() => setShowFormModal(false)} />
|
||||
)}
|
||||
</div>
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col space-y-3 mx-auto w-full lg:w-2/3">
|
||||
<Skeleton className="h-[310px] lg:h-[420px] rounded-xl" />
|
||||
|
|
@ -85,30 +491,44 @@ const Hero: React.FC = () => {
|
|||
<CarouselContent>
|
||||
{heroData?.map((list: any) => (
|
||||
<CarouselItem key={list?.id}>
|
||||
<div className="relative h-[310px] lg:h-[420px]">
|
||||
<div className="relative h-[310px] lg:h-[460px] mt-1">
|
||||
<Image
|
||||
src={list?.thumbnailLink}
|
||||
alt="gambar-utama"
|
||||
width={1920}
|
||||
height={1080}
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`}
|
||||
className="w-full h-[310px] lg:h-[420px] rounded-lg object-cover"
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||
shimmer(700, 475)
|
||||
)}`}
|
||||
className="w-full h-[320px] lg:h-[460px] rounded-lg object-cover"
|
||||
/>
|
||||
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-transparent backdrop-blur-sm text-black dark:text-white p-4 rounded-b-lg">
|
||||
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-xs px-2 py-1">{list?.categoryName}</span>
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<Link href={`${locale}/image/detail/${list?.slug}`}>
|
||||
<h2 className="text-lg text-slate-500 dark:text-white font-bold mt-2">{list?.title}</h2>
|
||||
<h2 className="text-lg leading-tight">{list?.title}</h2>
|
||||
</Link>
|
||||
<p className="text-xs flex flex-row items-center text-slate-500 dark:text-white gap-1 mt-1">
|
||||
{formatDateToIndonesian(new Date(list?.createdAt))} {list?.timezone ? list?.timezone : "WIB"}|{" "}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24">
|
||||
|
||||
<p className="text-xs flex items-center gap-1 mt-2 opacity-80">
|
||||
{formatDateToIndonesian(new Date(list?.createdAt))}{" "}
|
||||
{list?.timezone || "WIB"} |{" "}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
className="inline-block"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
|
||||
/>
|
||||
</svg>{" "}
|
||||
{list?.clickCount}{" "}
|
||||
</svg>
|
||||
{list?.clickCount}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -120,7 +540,6 @@ const Hero: React.FC = () => {
|
|||
</Carousel>
|
||||
)}
|
||||
|
||||
{/* Section Kanan */}
|
||||
<div>
|
||||
{isLoading ? (
|
||||
<>
|
||||
|
|
@ -161,30 +580,157 @@ const Hero: React.FC = () => {
|
|||
</div>
|
||||
</>
|
||||
) : (
|
||||
<ul className="py-4 lg:py-0 flex flex-row lg:flex-col gap-4 flex-nowrap w-[95vw] lg:w-auto overflow-x-auto">
|
||||
{heroData?.map((item: any) => (
|
||||
<li key={item?.id} className="flex gap-4 flex-row lg:w-full ">
|
||||
<div className="flex-shrink-0 w-24 rounded-lg">
|
||||
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={720} height={480} src={item?.thumbnailLink} alt={item?.title} className="w-full h-[73px] object-cover rounded-lg" />
|
||||
</div>
|
||||
<div className="w-[280px] lg:w-auto">
|
||||
<span className="text-white bg-[#bb3523] px-4 py-1 rounded-lg flex text-[8px] font-bold uppercase w-fit">{item?.categoryName}</span>
|
||||
<Link href={`${locale}/image/detail/${item?.slug}`}>
|
||||
<h3 className="text-base font-bold mt-2 h-6 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">{item?.title}</h3>
|
||||
</Link>
|
||||
<p className="text-[10px] flex flex-row items-center gap-1 text-gray-500 mt-1">
|
||||
{formatDateToIndonesian(new Date(item?.createdAt))} {item?.timezone ? item?.timezone : "WIB"} |{" "}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
|
||||
<ul className="py-4 lg:py-0 flex flex-row lg:flex-col gap-4 flex-nowrap w-[95vw] lg:w-auto">
|
||||
<Tabs defaultValue="national" className="w-[350px]">
|
||||
<TabsList className="grid w-full grid-cols-3 border">
|
||||
<TabsTrigger value="national">Nasional</TabsTrigger>
|
||||
<TabsTrigger value="polda">Polda</TabsTrigger>
|
||||
<TabsTrigger value="satker">Satker</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="national">
|
||||
{heroData?.map((item: any) => (
|
||||
<li
|
||||
key={item?.id}
|
||||
className="flex gap-4 flex-row lg:w-full mx-2"
|
||||
>
|
||||
<div className="flex-shrink-0 w-24 rounded-lg">
|
||||
<Image
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||
shimmer(700, 475)
|
||||
)}`}
|
||||
width={720}
|
||||
height={480}
|
||||
src={item?.thumbnailLink}
|
||||
alt={item?.title}
|
||||
className="w-full h-[73px] object-cover rounded-lg"
|
||||
/>
|
||||
</svg>{" "}
|
||||
{item?.clickCount}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</div>
|
||||
<div className="w-[280px] lg:w-auto">
|
||||
<span className="text-white bg-[#bb3523] px-4 py-1 rounded-lg flex text-[8px] font-bold uppercase w-fit">
|
||||
{item?.categoryName}
|
||||
</span>
|
||||
<Link href={`${locale}/image/detail/${item?.slug}`}>
|
||||
<h3 className="text-base font-bold mt-2 h-6 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">
|
||||
{item?.title}
|
||||
</h3>
|
||||
</Link>
|
||||
<p className="text-[10px] flex flex-row items-center gap-1 text-gray-500 mt-1">
|
||||
{formatDateToIndonesian(new Date(item?.createdAt))}{" "}
|
||||
{item?.timezone ? item?.timezone : "WIB"} |{" "}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1.2em"
|
||||
height="1.2em"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
|
||||
/>
|
||||
</svg>{" "}
|
||||
{item?.clickCount}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</TabsContent>
|
||||
<TabsContent value="polda">
|
||||
{heroData
|
||||
?.filter((item: any) => item.isPublishOnPolda === true)
|
||||
.map((item: any, index: any) => (
|
||||
<li
|
||||
key={item?.id}
|
||||
className="flex gap-4 flex-row lg:w-full mx-2"
|
||||
>
|
||||
<div className="flex-shrink-0 w-24 rounded-lg">
|
||||
<Image
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||
shimmer(700, 475)
|
||||
)}`}
|
||||
width={720}
|
||||
height={480}
|
||||
src={item?.thumbnailLink}
|
||||
alt={item?.title}
|
||||
className="w-full h-[73px] object-cover rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-[280px] lg:w-auto">
|
||||
<span className="text-white bg-[#bb3523] px-4 py-1 rounded-lg flex text-[8px] font-bold uppercase w-fit">
|
||||
{item?.categoryName}
|
||||
</span>
|
||||
<Link href={`${locale}/image/detail/${item?.slug}`}>
|
||||
<h3 className="text-base font-bold mt-2 h-6 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">
|
||||
{item?.title}
|
||||
</h3>
|
||||
</Link>
|
||||
<p className="text-[10px] flex flex-row items-center gap-1 text-gray-500 mt-1">
|
||||
{formatDateToIndonesian(new Date(item?.createdAt))}{" "}
|
||||
{item?.timezone ? item?.timezone : "WIB"} |{" "}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1.2em"
|
||||
height="1.2em"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
|
||||
/>
|
||||
</svg>{" "}
|
||||
{item?.clickCount}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</TabsContent>
|
||||
<TabsContent value="satker">
|
||||
{heroData?.map((item: any) => (
|
||||
<li
|
||||
key={item?.id}
|
||||
className="flex gap-4 flex-row lg:w-full mx-2"
|
||||
>
|
||||
<div className="flex-shrink-0 w-24 rounded-lg">
|
||||
<Image
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||
shimmer(700, 475)
|
||||
)}`}
|
||||
width={720}
|
||||
height={480}
|
||||
src={item?.thumbnailLink}
|
||||
alt={item?.title}
|
||||
className="w-full h-[73px] object-cover rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-[280px] lg:w-auto">
|
||||
<span className="text-white bg-[#bb3523] px-4 py-1 rounded-lg flex text-[8px] font-bold uppercase w-fit">
|
||||
{item?.categoryName}
|
||||
</span>
|
||||
<Link href={`${locale}/image/detail/${item?.slug}`}>
|
||||
<h3 className="text-base font-bold mt-2 h-6 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">
|
||||
{item?.title}
|
||||
</h3>
|
||||
</Link>
|
||||
<p className="text-[10px] flex flex-row items-center gap-1 text-gray-500 mt-1">
|
||||
{formatDateToIndonesian(new Date(item?.createdAt))}{" "}
|
||||
{item?.timezone ? item?.timezone : "WIB"} |{" "}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1.2em"
|
||||
height="1.2em"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
|
||||
/>
|
||||
</svg>{" "}
|
||||
{item?.clickCount}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ const HeaderBannerKaltara = () => {
|
|||
<>
|
||||
<Reveal>
|
||||
{/* Header Left */}
|
||||
<div className="flex flex-col lg:flex-row items-start justify-center gap-[25px] px-4 lg:px-18 py-4 w-auto mt-6">
|
||||
<div className=" flex flex-col lg:flex-row items-start justify-center gap-[25px] px-4 lg:px-18 py-4 w-auto mt-6">
|
||||
{isBannerLoading ? (
|
||||
<div className="flex flex-col space-y-3 mx-auto w-full lg:w-2/3">
|
||||
<Skeleton className="h-[310px] lg:h-[420px] rounded-xl" />
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue