feat:admin page-> broadcast, tenaga ahli, kategori

This commit is contained in:
Rama Priyanto 2025-01-04 04:18:22 +07:00
parent e5e2ebd366
commit b91f715e46
31 changed files with 3719 additions and 199 deletions

View File

@ -0,0 +1,91 @@
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 {
formatDateToIndonesian,
getOnlyDate,
htmlToString,
} from "@/utils/globals";
import { Link, useRouter } from "@/i18n/routing";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
const columns: ColumnDef<any>[] = [
{
accessorKey: "no",
header: "No",
cell: ({ row }) => <span>{row.getValue("no")}</span>,
},
{
accessorKey: "name",
header: "Nama",
cell: ({ row }) => <span>{row.getValue("name")}</span>,
},
{
accessorKey: "region",
header: "Wilayah",
cell: ({ row }) => <span>{row.getValue("region")}</span>,
},
{
accessorKey: "skills",
header: "Bidang Keahlian",
cell: ({ row }) => <span>{row.getValue("skills")}</span>,
},
{
accessorKey: "experience",
header: "Pengalaman",
cell: ({ row }) => <span>{row.getValue("experience")}</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="#">Detail</Link>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
},
},
];
export default columns;

View File

@ -0,0 +1,276 @@
"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 { 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 { 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";
const dummyData = [
{
id: 1,
name: "Prof. Dr. Ravi",
region: "Nasional",
skills: "Komunikasi",
experience: "Akademisi",
},
{
id: 2,
name: "Prof. Dr. Novan",
region: "DKI Jakarta",
skills: "Hukum",
experience: "Akademisi + Praktisi",
},
];
const AddExpertTable = () => {
const router = useRouter();
const searchParams = useSearchParams();
const [showData, setShowData] = React.useState("10");
const [categories, setCategories] = React.useState<any>();
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 [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize: Number(showData),
});
const [categoryFilter, setCategoryFilter] = React.useState<number[]>([]);
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
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,
},
});
let typingTimer: any;
const doneTypingInterval = 1500;
const handleKeyUp = () => {
clearTimeout(typingTimer);
typingTimer = setTimeout(doneTyping, doneTypingInterval);
};
const handleKeyDown = () => {
clearTimeout(typingTimer);
typingTimer = setTimeout(doneTyping, doneTypingInterval);
};
async function doneTyping() {
fetchData();
}
React.useEffect(() => {
const pageFromUrl = searchParams?.get("page");
if (pageFromUrl) {
setPage(Number(pageFromUrl));
}
}, [searchParams]);
React.useEffect(() => {
fetchData();
setPagination({
pageIndex: 0,
pageSize: Number(showData),
});
}, [page, showData]);
async function fetchData() {
try {
loading();
const contentData = dummyData;
contentData.forEach((item: any, index: number) => {
item.no = (page - 1) * Number(showData) + index + 1;
});
setDataTable(contentData);
setTotalData(contentData?.length);
setTotalPage(1);
close();
} catch (error) {
console.error("Error fetching tasks:", error);
}
}
React.useEffect(() => {
getCategories();
}, []);
async function getCategories() {
const category = await listEnableCategory("");
const resCategory = category.data.data.content;
setCategories(resCategory);
}
return (
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
<div className="flex justify-between mb-10 items-center">
<p className="text-xl font-medium text-default-900">Tenaga Ahli</p>
<Link href="/admin/add-experts/create">
<Button color="primary" size="md" className="text-sm">
<UserIcon />
Tambah Tenaga Ahli
</Button>
</Link>
</div>
<div className="flex justify-between ">
<Input
type="text"
placeholder="Search"
onKeyUp={handleKeyUp}
onKeyDown={handleKeyDown}
className="max-w-[300px]"
/>
<div className="flex flex-row gap-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
1 - {showData} Data
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuRadioGroup
value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
1 - 10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="20">
1 - 20 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="25">
1 - 25 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
1 - 50 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
</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 AddExpertTable;

View File

@ -0,0 +1,258 @@
"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 { useRouter } from "@/i18n/routing";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
const FormSchema = z.object({
name: z.string({
required_error: "Required",
}),
phoneNumber: z.string({
required_error: "Required",
}),
email: z.string({
required_error: "Required",
}),
region: z.string({
required_error: "Required",
}),
skills: z.string({
required_error: "Required",
}),
experience: z.string({
required_error: "Required",
}),
company: z.string({
required_error: "Required",
}),
});
export default function AddExpertForm() {
const MySwal = withReactContent(Swal);
const router = useRouter();
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
});
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);
// successSubmit();
};
function successSubmit() {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
router.push("/admin/add-experts");
}
});
}
return (
<div>
<SiteBreadcrumb />
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-3 bg-white rounded-sm p-4"
>
<p className="fonnt-semibold">Campaign</p>
<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>
)}
/>
<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="region"
render={({ field }) => (
<FormItem>
<FormLabel>Wilayah</FormLabel>
<Select onValueChange={field.onChange} value={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Pilih Region" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="nasional">Nasional</SelectItem>
<SelectItem value="jakarta">DKI Jakarta</SelectItem>
<SelectItem value="jabar">Jawa Barat</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="skills"
render={({ field }) => (
<FormItem>
<FormLabel>Bidang Keahlian</FormLabel>
<Select onValueChange={field.onChange} value={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Pilih Bidang Keahlian" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="komunikasi">Komunikasi</SelectItem>
<SelectItem value="hukum">Hukum</SelectItem>
<SelectItem value="bahasa">Bahasa</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="experience"
render={({ field }) => (
<FormItem>
<FormLabel>Pengalaman</FormLabel>
<Select onValueChange={field.onChange} value={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Pilih Pengalaman" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="akademisi">Akademisi</SelectItem>
<SelectItem value="praktisi">Praktisi</SelectItem>
<SelectItem value="akademisi+praktisi">
Akademisi + Praktisi
</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="company"
render={({ field }) => (
<FormItem>
<FormLabel>Nama Institusi/Perusahaan</FormLabel>
<Input
type="text"
value={field.value}
placeholder="Nama Institusi/Perusahaan"
onChange={field.onChange}
/>
<FormMessage />
</FormItem>
)}
/>
<div className="flex flex-row justify-end gap-2 mt-4 pt-4">
<Button
size="md"
type="button"
variant="outline"
color="destructive"
className="text-xs"
>
Cancel
</Button>
<Button size="md" type="submit" color="primary" className="text-xs">
Submit
</Button>
</div>
</form>
</Form>
</div>
);
}

View File

@ -0,0 +1,11 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import AddExpertTable from "./component/table";
export default function AddExpert() {
return (
<div>
<SiteBreadcrumb />
<AddExpertTable />
</div>
);
}

View File

@ -11,8 +11,13 @@ import {
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import Swal from "sweetalert2";
import { Link, useRouter } from "@/i18n/routing";
import withReactContent from "sweetalert2-react-content";
import { close, error, loading } from "@/config/swal";
import { deleteMediaBlastAccount } from "@/service/broadcast/broadcast";
import { useToast } from "@/components/ui/use-toast";
const columns: ColumnDef<any>[] = [
{
@ -24,22 +29,30 @@ const columns: ColumnDef<any>[] = [
{
accessorKey: "accountName",
header: "Nama",
cell: ({ row }) => <span>{row.getValue("accountName")}</span>,
cell: ({ row }) => (
<span className="normal-case">{row.getValue("accountName")}</span>
),
},
{
accessorKey: "accountType",
header: "Tipe Akun",
cell: ({ row }) => <span>{row.getValue("accountType")}</span>,
cell: ({ row }) => (
<span className="normal-case">{row.getValue("accountType")}</span>
),
},
{
accessorKey: "accountCategory",
header: "Kategory",
cell: ({ row }) => <span>{row.getValue("accountCategory")}</span>,
cell: ({ row }) => (
<span className="uppercase">{row.getValue("accountCategory")}</span>
),
},
{
accessorKey: "emailAddress",
header: "Email",
cell: ({ row }) => <span>{row.getValue("emailAddress")}</span>,
cell: ({ row }) => (
<span className="normal-case">{row.getValue("emailAddress")}</span>
),
},
{
accessorKey: "whatsappNumber",
@ -52,6 +65,41 @@ const columns: ColumnDef<any>[] = [
header: "Actions",
enableHiding: false,
cell: ({ row }) => {
const MySwal = withReactContent(Swal);
const { toast } = useToast();
const router = useRouter();
const handleDelete = (id: string) => {
MySwal.fire({
title: "Apakah anda ingin menghapus data?",
showCancelButton: true,
confirmButtonColor: "#dc3545",
confirmButtonText: "Iya",
cancelButtonText: "Tidak",
}).then((result) => {
if (result.isConfirmed) {
doDeleteAccount(id);
}
});
};
async function succes() {
toast({
title: "Sukses",
description: "Berhasil Delete",
});
window.location.reload();
}
async function doDeleteAccount(id: string) {
loading();
const response = await deleteMediaBlastAccount(id);
close();
if (response?.error) {
error(response.message);
return false;
}
succes();
}
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
@ -65,13 +113,13 @@ const columns: ColumnDef<any>[] = [
<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/edit/${row.original.id}`}
href={`/admin/broadcast/campaign-list/account-list/edit/${row.original.id}`}
>
Edit
</Link>
</DropdownMenuItem>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<a>Delete</a>
<a onClick={() => handleDelete(row.original.id)}>Delete</a>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>

View File

@ -24,51 +24,21 @@ import {
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 { UserIcon } from "lucide-react";
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 {
getMediaBlastAccountPage,
getMediaBlastCampaignPage,
listDataMedia,
} from "@/service/broadcast/broadcast";
import { listEnableCategory } from "@/service/content/content";
import { getMediaBlastAccountPage } from "@/service/broadcast/broadcast";
import { Checkbox } from "@/components/ui/checkbox";
import { close, loading } from "@/config/swal";
import { Link } from "@/i18n/routing";
import { Icon } from "@iconify/react/dist/iconify.js";
const AccountListTable = () => {
const router = useRouter();
@ -89,6 +59,7 @@ const AccountListTable = () => {
const [page, setPage] = React.useState(1);
const [totalPage, setTotalPage] = React.useState(1);
const [filtered, setFiltered] = React.useState<string[]>([]);
const table = useReactTable({
data: dataTable,
columns,
@ -124,15 +95,16 @@ const AccountListTable = () => {
async function fetchData() {
try {
loading();
const res = await getMediaBlastAccountPage(page - 1, "");
const res = await getMediaBlastAccountPage(
page - 1,
filtered ? filtered.join(",") : ""
);
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);
@ -142,23 +114,114 @@ const AccountListTable = () => {
}
}
const handleFilter = (id: string, checked: boolean) => {
let temp = [...filtered];
if (checked) {
temp = [...temp, id];
} else {
temp = temp.filter((a) => a !== id);
}
setFiltered(temp);
console.log("sss", temp);
};
return (
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
<div className="flex flex-row gap-3">
<Link href="/admin/broadcast/campaign-list/account-list">
<Button color="primary" size="md" className="text-sm">
<UserIcon />
Tambah Akun
</Button>
</Link>
<Link href="/admin/broadcast/campaign-list/create">
<Button color="success" size="md" className="text-sm">
<UserIcon />
Import Akun
</Button>
</Link>
<div className="flex justify-between mb-10 items-center">
<p className="text-xl font-medium text-default-900">Daftar Akun</p>
<div className="flex flex-row gap-3">
<Link href="/admin/broadcast/campaign-list/account-list/create">
<Button color="primary" size="md" className="text-sm">
<Icon icon="tdesign:user-add-filled" />
Tambah Akun
</Button>
</Link>
<Link href="/admin/broadcast/campaign-list/import">
<Button color="success" size="md" className="text-sm">
<UserIcon />
Import Akun
</Button>
</Link>
</div>
</div>
<div className="flex justify-end">
<Popover>
<PopoverTrigger asChild>
<Button size="md" variant="outline">
Filter
</Button>
</PopoverTrigger>
<PopoverContent className="w-80 ">
<div className="flex flex-col gap-2 px-2">
<div className="flex justify-between text-sm">
<p>Filter</p>
<a
onClick={() => fetchData()}
className="cursor-pointer text-primary"
>
Simpan
</a>
</div>
<div className="flex flex-col gap-1 overflow-auto max-h-[300px] text-xs custom-scrollbar-table">
<div className="flex items-center space-x-2">
<Checkbox
id="accepted"
checked={filtered.includes("polri")}
onCheckedChange={(e) => handleFilter("polri", Boolean(e))}
/>
<label
htmlFor="accepted"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
POLRI
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="accepted"
checked={filtered.includes("jurnalis")}
onCheckedChange={(e) =>
handleFilter("jurnalis", Boolean(e))
}
/>
<label
htmlFor="accepted"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
JURNALIS
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="accepted"
checked={filtered.includes("umum")}
onCheckedChange={(e) => handleFilter("umum", Boolean(e))}
/>
<label
htmlFor="accepted"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
UMUM
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="accepted"
checked={filtered.includes("ksp")}
onCheckedChange={(e) => handleFilter("ksp", Boolean(e))}
/>
<label
htmlFor="accepted"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
KSP
</label>
</div>
</div>
</div>
</PopoverContent>
</Popover>
</div>
<Table className="overflow-hidden">
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (

View File

@ -0,0 +1,306 @@
"use client";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { format } from "date-fns";
import { CalendarIcon } from "lucide-react";
import { Calendar } from "@/components/ui/calendar";
import { getOnlyDate } from "@/utils/globals";
import {
saveMediaBlastAccount,
saveMediaBlastCampaign,
} from "@/service/broadcast/broadcast";
import { error } from "@/config/swal";
import { useRouter } from "@/i18n/routing";
import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
const FormSchema = z.object({
name: z.string({
required_error: "Required",
}),
accountType: z
.array(z.string())
.refine((value) => value.some((item) => item), {
message: "Required",
}),
accountCategory: z.enum(["polri", "jurnalis", "umumu", "ksp"], {
required_error: "Required",
}),
email: z.string({
required_error: "Required",
}),
whatsapp: z.string({
required_error: "Required",
}),
});
export default function CreateAccountForBroadcast() {
const MySwal = withReactContent(Swal);
const router = useRouter();
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: { accountType: [] },
});
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);
}
});
};
function successSubmit() {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
router.push("/admin/broadcast/campaign-list/account-list");
}
});
}
const save = async (data: z.infer<typeof FormSchema>) => {
const reqData = {
accountName: data.name,
accountType: data.accountType.join(","),
accountCategory: data.accountCategory,
emailAddress: data.email,
whatsappNumber: data.whatsapp,
};
console.log("data", data);
const response = await saveMediaBlastAccount(reqData);
if (response?.error) {
error(response.message);
return false;
}
successSubmit();
};
return (
<div>
<SiteBreadcrumb />
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-3 bg-white rounded-sm p-4"
>
<p className="fonnt-semibold">Account</p>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Nama</FormLabel>
<Input
value={field.value}
placeholder="Masukkan nama"
onChange={field.onChange}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="accountType"
render={() => (
<FormItem>
<FormLabel>Tipe Akun</FormLabel>
<div className="flex flex-row gap-2">
{" "}
<FormField
key="wa"
control={form.control}
name="accountType"
render={({ field }) => {
return (
<FormItem
key="wa"
className="flex flex-row items-start space-x-3 space-y-0"
>
<FormControl>
<Checkbox
checked={field.value?.includes("wa")}
onCheckedChange={(checked) => {
return checked
? field.onChange([...field.value, "wa"])
: field.onChange(
field.value?.filter(
(value) => value !== "wa"
)
);
}}
/>
</FormControl>
<FormLabel className="font-normal">
Whatsapp
</FormLabel>
</FormItem>
);
}}
/>
<FormField
key="email"
control={form.control}
name="accountType"
render={({ field }) => {
return (
<FormItem
key="email"
className="flex flex-row items-start space-x-3 space-y-0"
>
<FormControl>
<Checkbox
checked={field.value?.includes("email")}
onCheckedChange={(checked) => {
return checked
? field.onChange([...field.value, "email"])
: field.onChange(
field.value?.filter(
(value) => value !== "email"
)
);
}}
/>
</FormControl>
<FormLabel className="font-normal">Email</FormLabel>
</FormItem>
);
}}
/>
</div>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="accountCategory"
render={({ field }) => (
<FormItem className="space-y-3">
<FormLabel>Kategori</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
defaultValue={field.value}
className="flex flex-row gap-2"
>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="polri" />
</FormControl>
<FormLabel className="font-normal">POLRI</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="jurnalis" />
</FormControl>
<FormLabel className="font-normal">JURNALIS</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="umum" />
</FormControl>
<FormLabel className="font-normal">UMUM</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="ksp" />
</FormControl>
<FormLabel className="font-normal">KSP</FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Nama</FormLabel>
<Input
type="email"
value={field.value}
placeholder="Masukkan email"
onChange={field.onChange}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="whatsapp"
render={({ field }) => (
<FormItem>
<FormLabel>Nama</FormLabel>
<Input
type="number"
value={field.value}
placeholder="Masukkan whatsapp"
onChange={field.onChange}
/>
<FormMessage />
</FormItem>
)}
/>
<div className="flex flex-row gap-2 mt-4 pt-4">
<Button
size="md"
type="button"
variant="outline"
color="destructive"
className="text-xs"
>
Cancel
</Button>
<Button size="md" type="submit" color="primary" className="text-xs">
Submit
</Button>
</div>
</form>
</Form>
</div>
);
}

View File

@ -0,0 +1,325 @@
"use client";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { format } from "date-fns";
import { CalendarIcon } from "lucide-react";
import { Calendar } from "@/components/ui/calendar";
import { getOnlyDate } from "@/utils/globals";
import {
getMediaBlastAccount,
saveMediaBlastAccount,
saveMediaBlastCampaign,
} from "@/service/broadcast/broadcast";
import { error } from "@/config/swal";
import { useRouter } from "@/i18n/routing";
import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { useParams } from "next/navigation";
import { useEffect } from "react";
const FormSchema = z.object({
name: z.string({
required_error: "Required",
}),
accountType: z
.array(z.string())
.refine((value) => value.some((item) => item), {
message: "Required",
}),
accountCategory: z.enum(["polri", "jurnalis", "umumu", "ksp"], {
required_error: "Required",
}),
email: z.string({
required_error: "Required",
}),
whatsapp: z.string({
required_error: "Required",
}),
});
export default function EditAccountForBroadcast() {
const id = useParams()?.id;
const MySwal = withReactContent(Swal);
const router = useRouter();
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: { accountType: [] },
});
useEffect(() => {
async function getDetailData() {
const response = await getMediaBlastAccount(String(id));
const details = response.data?.data;
console.log("new", details);
form.setValue("name", details.accountName);
form.setValue("email", details?.emailAddress);
form.setValue("whatsapp", details?.whatsappNumber);
form.setValue("accountCategory", details?.accountCategory);
form.setValue("accountType", details?.accountType.split(","));
}
getDetailData();
}, [id]);
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);
}
});
};
function successSubmit() {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
router.push("/admin/broadcast/campaign-list/account-list");
}
});
}
const save = async (data: z.infer<typeof FormSchema>) => {
const reqData = {
id: String(id),
accountName: data.name,
accountType: data.accountType.join(","),
accountCategory: data.accountCategory,
emailAddress: data.email,
whatsappNumber: data.whatsapp,
};
console.log("data", data);
const response = await saveMediaBlastAccount(reqData);
if (response?.error) {
error(response.message);
return false;
}
successSubmit();
};
return (
<div>
<SiteBreadcrumb />
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-3 bg-white rounded-sm p-4"
>
<p className="fonnt-semibold">Account</p>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Nama</FormLabel>
<Input
value={field.value}
placeholder="Masukkan nama"
onChange={field.onChange}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="accountType"
render={() => (
<FormItem>
<FormLabel>Tipe Akun</FormLabel>
<div className="flex flex-row gap-2">
<FormField
key="wa"
control={form.control}
name="accountType"
render={({ field }) => {
return (
<FormItem
key="wa"
className="flex flex-row items-start space-x-3 space-y-0"
>
<FormControl>
<Checkbox
checked={field.value?.includes("wa")}
onCheckedChange={(checked) => {
return checked
? field.onChange([...field.value, "wa"])
: field.onChange(
field.value?.filter(
(value) => value !== "wa"
)
);
}}
/>
</FormControl>
<FormLabel className="font-normal">
Whatsapp
</FormLabel>
</FormItem>
);
}}
/>
<FormField
key="email"
control={form.control}
name="accountType"
render={({ field }) => {
return (
<FormItem
key="email"
className="flex flex-row items-start space-x-3 space-y-0"
>
<FormControl>
<Checkbox
checked={field.value?.includes("email")}
onCheckedChange={(checked) => {
return checked
? field.onChange([...field.value, "email"])
: field.onChange(
field.value?.filter(
(value) => value !== "email"
)
);
}}
/>
</FormControl>
<FormLabel className="font-normal">Email</FormLabel>
</FormItem>
);
}}
/>
</div>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="accountCategory"
render={({ field }) => (
<FormItem className="space-y-3">
<FormLabel>Kategori</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
value={field.value}
className="flex flex-row gap-2"
>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="polri" />
</FormControl>
<FormLabel className="font-normal">POLRI</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="jurnalis" />
</FormControl>
<FormLabel className="font-normal">JURNALIS</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="umum" />
</FormControl>
<FormLabel className="font-normal">UMUM</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="ksp" />
</FormControl>
<FormLabel className="font-normal">KSP</FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Nama</FormLabel>
<Input
type="email"
value={field.value}
placeholder="Masukkan email"
onChange={field.onChange}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="whatsapp"
render={({ field }) => (
<FormItem>
<FormLabel>Nama</FormLabel>
<Input
type="number"
value={field.value}
placeholder="Masukkan whatsapp"
onChange={field.onChange}
/>
<FormMessage />
</FormItem>
)}
/>
<div className="flex flex-row gap-2 mt-4 pt-4">
<Button
size="md"
type="button"
variant="outline"
color="destructive"
className="text-xs"
>
Cancel
</Button>
<Button size="md" type="submit" color="primary" className="text-xs">
Submit
</Button>
</div>
</form>
</Form>
</div>
);
}

View File

@ -24,7 +24,9 @@ const columns: ColumnDef<any>[] = [
{
accessorKey: "title",
header: "Judul",
cell: ({ row }) => <span>{row.getValue("title")}</span>,
cell: ({ row }) => (
<span className="normal-case">{row.getValue("title")}</span>
),
},
{
accessorKey: "sendTime",

View File

@ -68,6 +68,7 @@ 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";
const CampaignListTable = () => {
const router = useRouter();
@ -143,19 +144,22 @@ const CampaignListTable = () => {
return (
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
<div className="flex flex-row gap-3">
<Link href="/admin/broadcast/campaign-list/account-list">
<Button color="primary" size="md" className="text-sm">
<UserIcon />
Daftar Akun
</Button>
</Link>
<Link href="/admin/broadcast/campaign-list/create">
<Button color="primary" size="md" className="text-sm">
<UserIcon />
Buat Campaign Baru
</Button>
</Link>
<div className="flex justify-between mb-10 items-center">
<p className="text-xl font-medium text-default-900">Daftar Campaign</p>
<div className="flex flex-row gap-2">
<Link href="/admin/broadcast/campaign-list/account-list">
<Button color="primary" size="md" className="text-sm">
<UserIcon />
Daftar Akun
</Button>
</Link>
<Link href="/admin/broadcast/campaign-list/create">
<Button color="primary" size="md" className="text-sm">
<NewCampaignIcon size={23} />
Buat Campaign Baru
</Button>
</Link>
</div>
</div>
<Table className="overflow-hidden">

View File

@ -0,0 +1,198 @@
"use client";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { format } from "date-fns";
import { CalendarIcon } from "lucide-react";
import { Calendar } from "@/components/ui/calendar";
import { getOnlyDate } from "@/utils/globals";
import { saveMediaBlastCampaign } from "@/service/broadcast/broadcast";
import { error } from "@/config/swal";
import { useRouter } from "@/i18n/routing";
const FormSchema = z.object({
date: z.date({
required_error: "Required",
}),
title: z.string({
required_error: "Required",
}),
time: z.string({
required_error: "Required",
}),
});
export default function CreateCampaign() {
const MySwal = withReactContent(Swal);
const router = useRouter();
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
});
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);
}
});
};
function successSubmit() {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
router.push("/admin/broadcast/campaign-list");
}
});
}
const save = async (data: z.infer<typeof FormSchema>) => {
const newDate = getOnlyDate(data.date).split("-");
const dates = `${newDate[2]}-${newDate[1]}-${newDate[0]}`;
const reqData = {
title: data.title,
sendTime: `${dates} ${data.time}:00`,
status: "waiting",
};
const response = await saveMediaBlastCampaign(reqData);
if (response?.error) {
error(response.message);
return false;
}
successSubmit();
};
return (
<div>
<SiteBreadcrumb />
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-3 bg-white rounded-sm p-4"
>
<p className="fonnt-semibold">Campaign</p>
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Judul</FormLabel>
<Input
value={field.value}
placeholder="Masukkan Judul"
onChange={field.onChange}
/>
<FormMessage />
</FormItem>
)}
/>
<p className="text-sm">Jadwal Kirim</p>
<div className="flex flex-row gap-2">
<FormField
control={form.control}
name="date"
render={({ field }) => (
<FormItem>
<Popover>
<PopoverTrigger asChild>
<Button
variant={"outline"}
size="md"
className={cn(
"w-[200px] justify-start text-left font-normal border-gray-200",
!field.value && "text-muted-foreground"
)}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{field.value ? (
format(field.value, "dd-MM-yyyy")
) : (
<span>Masukkan Bulan</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar
mode="single"
selected={field.value}
onSelect={field.onChange}
initialFocus
/>
</PopoverContent>
</Popover>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="time"
render={({ field }) => (
<FormItem>
<Input
type="time"
className="max-w-[100px]"
value={field.value}
placeholder="Masukkan Judul Perencanaan"
onChange={field.onChange}
/>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex flex-row gap-2 mt-4 pt-4">
<Button
size="md"
type="button"
variant="outline"
color="destructive"
className="text-xs"
>
Cancel
</Button>
<Button size="md" type="submit" color="primary" className="text-xs">
Submit
</Button>
</div>
</form>
</Form>
</div>
);
}

View File

@ -0,0 +1,227 @@
"use client";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { format } from "date-fns";
import { CalendarIcon } from "lucide-react";
import { Calendar } from "@/components/ui/calendar";
import { getOnlyDate } from "@/utils/globals";
import {
getMediaBlastCampaignById,
saveMediaBlastCampaign,
} from "@/service/broadcast/broadcast";
import { error } from "@/config/swal";
import { useRouter } from "@/i18n/routing";
import { useParams } from "next/navigation";
import { useEffect } from "react";
const FormSchema = z.object({
date: z.date({
required_error: "Required",
}),
title: z.string({
required_error: "Required",
}),
time: z.string({
required_error: "Required",
}),
status: z.string({
required_error: "Required",
}),
});
export default function EditCampaign() {
const id = useParams()?.id;
const MySwal = withReactContent(Swal);
const router = useRouter();
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
});
useEffect(() => {
async function getInitData() {
const response = await getMediaBlastCampaignById(String(id));
const date = response?.data?.data?.sendTime.split(" ");
const dateInput: Date = new Date(date[0]);
const time = date[1].split(":");
form.setValue("title", response?.data?.data?.title);
form.setValue("status", response?.data?.data?.status);
form.setValue("date", new Date(format(dateInput, "dd-MM-yyyy")));
form.setValue("time", `${time[0]}:${time[1]}`);
}
if (id != undefined) {
getInitData();
}
}, [id]);
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);
}
});
};
function successSubmit() {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
router.push("/admin/broadcast/campaign-list");
}
});
}
const save = async (data: z.infer<typeof FormSchema>) => {
const newDate = getOnlyDate(data.date).split("-");
const dates = `${newDate[2]}-${newDate[1]}-${newDate[0]}`;
const reqData = {
id: String(id),
title: data.title,
sendTime: `${dates} ${data.time}:00`,
status: data.status,
};
const response = await saveMediaBlastCampaign(reqData);
if (response?.error) {
error(response.message);
return false;
}
successSubmit();
};
return (
<div>
<SiteBreadcrumb />
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-3 bg-white rounded-sm p-4"
>
<p className="fonnt-semibold">Campaign</p>
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Judul</FormLabel>
<Input
value={field.value}
placeholder="Masukkan Judul"
onChange={field.onChange}
/>
<FormMessage />
</FormItem>
)}
/>
<p className="text-sm">Jadwal Kirim</p>
<div className="flex flex-row gap-2">
<FormField
control={form.control}
name="date"
render={({ field }) => (
<FormItem>
<Popover>
<PopoverTrigger asChild>
<Button
variant={"outline"}
size="md"
className={cn(
"w-[200px] justify-start text-left font-normal border-gray-200",
!field.value && "text-muted-foreground"
)}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{field.value ? (
format(field.value, "dd-MM-yyyy")
) : (
<span>Masukkan Bulan</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar
mode="single"
selected={field.value}
onSelect={field.onChange}
initialFocus
/>
</PopoverContent>
</Popover>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="time"
render={({ field }) => (
<FormItem>
<Input
type="time"
className="max-w-[100px]"
value={field.value}
placeholder="Masukkan Judul Perencanaan"
onChange={field.onChange}
/>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex flex-row gap-2 mt-4 pt-4">
<Button
size="md"
type="button"
variant="outline"
color="destructive"
className="text-xs"
>
Cancel
</Button>
<Button size="md" type="submit" color="primary" className="text-xs">
Submit
</Button>
</div>
</form>
</Form>
</div>
);
}

View File

@ -93,7 +93,7 @@ const columns: ColumnDef<any>[] = [
</Link>
</DropdownMenuItem>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<Link href={`/admin/broadcast/email/${row.original.id}`}>
<Link href={`/admin/broadcast/whatsapp/${row.original.id}`}>
Whatsapp Blast
</Link>
</DropdownMenuItem>

View File

@ -135,6 +135,10 @@ const BroadcastTable = () => {
React.useEffect(() => {
fetchData();
setPagination({
pageIndex: 0,
pageSize: Number(showData),
});
}, [page, showData]);
async function fetchData() {
@ -198,147 +202,153 @@ const BroadcastTable = () => {
return (
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
<Link href="/admin/broadcast/campaign-list">
<Button color="primary" size="md" className="text-sm">
<UserIcon />
Daftar Campaign
</Button>
</Link>
<div className="flex justify-between mb-10 items-center">
<p className="text-xl font-medium text-default-900">Brodcast</p>
<Link href="/admin/broadcast/campaign-list">
<Button color="primary" size="md" className="text-sm">
<UserIcon />
Daftar Campaign
</Button>
</Link>
</div>
<div className="flex flex-row gap-4">
<div className="flex justify-between ">
<Input
type="text"
placeholder="Search"
onKeyUp={handleKeyUp}
onKeyDown={handleKeyDown}
className="max-w-[300px]"
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
1 - {showData} Data
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuRadioGroup
value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
1 - 10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="20">
1 - 20 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="25">
1 - 25 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
1 - 50 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
<Popover>
<PopoverTrigger asChild>
<Button size="md" variant="outline">
Filter
</Button>
</PopoverTrigger>
<PopoverContent className="w-80 ">
<div className="flex flex-col gap-2 px-2">
<div className="flex justify-between text-sm">
<p>Filter</p>
<a
onClick={() => fetchData()}
className="cursor-pointer text-primary"
>
Simpan
</a>
</div>
<div className="flex flex-col gap-1 overflow-auto max-h-[300px] text-xs custom-scrollbar-table">
<p>Kategory</p>
{categories?.map((category: any) => (
<div className="flex flex-row gap-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
1 - {showData} Data
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuRadioGroup
value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
1 - 10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="20">
1 - 20 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="25">
1 - 25 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
1 - 50 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
<Popover>
<PopoverTrigger asChild>
<Button size="md" variant="outline">
Filter
</Button>
</PopoverTrigger>
<PopoverContent className="w-80 ">
<div className="flex flex-col gap-2 px-2">
<div className="flex justify-between text-sm">
<p>Filter</p>
<a
onClick={() => fetchData()}
className="cursor-pointer text-primary"
>
Simpan
</a>
</div>
<div className="flex flex-col gap-1 overflow-auto max-h-[300px] text-xs custom-scrollbar-table">
<p>Kategory</p>
{categories?.map((category: any) => (
<div className="flex items-center space-x-2">
<Checkbox
id={category.id}
checked={categoryFilter.includes(category.id)}
onCheckedChange={(e) =>
handleChange("category", category.id, Boolean(e))
}
/>
<label
htmlFor={category.id}
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{category.name}
</label>
</div>
))}
<p className="mt-3">Status</p>
<div className="flex items-center space-x-2">
<Checkbox
id={category.id}
checked={categoryFilter.includes(category.id)}
id="accepted"
checked={statusFilter.includes(1)}
onCheckedChange={(e) =>
handleChange("category", category.id, Boolean(e))
handleChange("status", 1, Boolean(e))
}
/>
<label
htmlFor={category.id}
htmlFor="accepted"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{category.name}
Menunggu Review
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="accepted"
checked={statusFilter.includes(2)}
onCheckedChange={(e) =>
handleChange("status", 2, Boolean(e))
}
/>
<label
htmlFor="accepted"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Diterima
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="accepted"
checked={statusFilter.includes(3)}
onCheckedChange={(e) =>
handleChange("status", 3, Boolean(e))
}
/>
<label
htmlFor="accepted"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Minta Update{" "}
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="accepted"
checked={statusFilter.includes(4)}
onCheckedChange={(e) =>
handleChange("status", 4, Boolean(e))
}
/>
<label
htmlFor="accepted"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Ditolak
</label>
</div>
))}
<p className="mt-3">Status</p>
<div className="flex items-center space-x-2">
<Checkbox
id="accepted"
checked={statusFilter.includes(1)}
onCheckedChange={(e) =>
handleChange("status", 1, Boolean(e))
}
/>
<label
htmlFor="accepted"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Menunggu Review
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="accepted"
checked={statusFilter.includes(2)}
onCheckedChange={(e) =>
handleChange("status", 2, Boolean(e))
}
/>
<label
htmlFor="accepted"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Diterima
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="accepted"
checked={statusFilter.includes(3)}
onCheckedChange={(e) =>
handleChange("status", 3, Boolean(e))
}
/>
<label
htmlFor="accepted"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Minta Update{" "}
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="accepted"
checked={statusFilter.includes(4)}
onCheckedChange={(e) =>
handleChange("status", 4, Boolean(e))
}
/>
<label
htmlFor="accepted"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Ditolak
</label>
</div>
</div>
</div>
</PopoverContent>
</Popover>
</PopoverContent>
</Popover>
</div>
</div>
<Table className="overflow-hidden">
<TableHeader>

View File

@ -0,0 +1,11 @@
import ContentBlast from "@/components/form/broadcast/content-blast-form";
import SiteBreadcrumb from "@/components/site-breadcrumb";
export default function CreateEmailBlast() {
return (
<div>
<SiteBreadcrumb />
<ContentBlast type="email" />
</div>
);
}

View File

@ -0,0 +1,11 @@
import ContentBlast from "@/components/form/broadcast/content-blast-form";
import SiteBreadcrumb from "@/components/site-breadcrumb";
export default function CreateWABlast() {
return (
<div>
<SiteBreadcrumb />
<ContentBlast type="wa" />
</div>
);
}

View File

@ -0,0 +1,157 @@
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";
import StatusToogle from "./status";
import { error } from "@/config/swal";
import { deleteCategory } from "@/service/settings/settings";
import { useToast } from "@/components/ui/use-toast";
import EditCategoryModal from "./edit";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import {
Menubar,
MenubarContent,
MenubarMenu,
MenubarTrigger,
} from "@/components/ui/menubar";
const columns: ColumnDef<any>[] = [
{
accessorKey: "no",
header: "No",
cell: ({ row }) => <span>{row.getValue("no")}</span>,
},
{
accessorKey: "name",
header: "Nama Kategori",
cell: ({ row }) => (
<span className="normal-case">{row.getValue("name")}</span>
),
},
{
accessorKey: "contentType",
header: "Tipe Konten",
cell: ({ row }) => (
<span className="normal-case">{row.getValue("contentType")}</span>
),
},
{
accessorKey: "isInternational",
header: "Wilayah",
cell: ({ row }) => (
<span className="normal-case">
{row.getValue("isInternational") ? "INT" : "ID"}
</span>
),
},
{
accessorKey: "status",
header: "Status",
cell: ({ row }) => (
<span className="normal-case">
<StatusToogle id={row.original.id} initValue={row.original.isPublish} />
</span>
),
},
{
id: "actions",
accessorKey: "action",
header: "Actions",
enableHiding: false,
cell: ({ row }) => {
const router = useRouter();
const { toast } = useToast();
const categoryDelete = async (id: number) => {
const response = await deleteCategory(id);
console.log(response);
if (response?.error) {
error(response.message);
return false;
}
toast({
title: "Sukses",
description: "Berhasil Delete",
});
router.push("/admin/settings/category?dataChange=true");
};
return (
// <Popover>
// <PopoverTrigger>
// <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>
// </PopoverTrigger>
// <PopoverContent className="w-40">
// <div className="flex flex-col">
// <EditCategoryModal />
// <a onClick={() => categoryDelete(row.original.id)}>Delete</a>
// </div>
// </PopoverContent>
// </Popover>
// <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">
// <EditCategoryModal />
// </DropdownMenuItem>
// <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
// <a onClick={() => categoryDelete(row.original.id)}>Delete</a>
// </DropdownMenuItem>
// </DropdownMenuContent>
// </DropdownMenu>
<Menubar className="border-none">
<MenubarMenu>
<MenubarTrigger>
<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>
</MenubarTrigger>
<MenubarContent className="flex flex-col gap-2 justify-center items-start p-4">
<EditCategoryModal data={row.original} />
<a
onClick={() => categoryDelete(row.original.id)}
className="hover:underline cursor-pointer hover:text-destructive"
>
Delete
</a>
</MenubarContent>
</MenubarMenu>
</Menubar>
);
},
},
];
export default columns;

View File

@ -0,0 +1,430 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { useRouter } from "@/i18n/routing";
import { Input } from "@/components/ui/input";
import { Checkbox } from "@/components/ui/checkbox";
import { getUserRoles, postCategory } from "@/service/settings/settings";
import { useEffect, useState } from "react";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Icon } from "@iconify/react/dist/iconify.js";
import { Textarea } from "@/components/ui/textarea";
import { close, error, loading } from "@/config/swal";
const FormSchema = z.object({
title: z.string({
required_error: "Required",
}),
description: z.string({
required_error: "Required",
}),
contentType: z
.array(z.string())
.refine((value) => value.some((item) => item), {
message: "Required",
}),
selectedUser: z
.array(z.string())
.refine((value) => value.some((item) => item), {
message: "Required",
}),
publishTo: z.string({
required_error: "Required",
}),
file: z
.instanceof(File, {
message: "Invalid file format",
})
.optional()
.refine((file) => file && file.size > 0, {
message: "File is required",
}),
});
const listContent = [
{
id: "1",
name: "Foto",
},
{
id: "2",
name: "Audio Visual",
},
{
id: "3",
name: "Teks",
},
{
id: "4",
name: "Audio",
},
];
export default function CreateCategoryModal() {
const router = useRouter();
const [userList, setUserList] = useState<
{ id: string; name: string; isInternal: boolean }[]
>([]);
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: { contentType: [], selectedUser: [], publishTo: "national" },
});
const contentType = form.watch("contentType");
const isAllContentChecked = listContent.every((item) =>
contentType?.includes(item.id)
);
const users = form.watch("selectedUser");
const isAllUserChecked = userList.every((item) => users?.includes(item.id));
useEffect(() => {
getRoles();
}, []);
async function getRoles() {
const response = await getUserRoles();
let dataRoles = response.data.data;
for (let i = 0; i < dataRoles.length; i++) {
dataRoles[i].id = String(dataRoles[i].id);
}
setUserList(dataRoles);
console.log("dasda", dataRoles);
}
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
if (data.file instanceof Blob) {
const formMedia = new FormData();
loading();
formMedia.append("name", data.title);
formMedia.append("description", data.description);
formMedia.append("mediaTypes", data.contentType.join(","));
formMedia.append("file", data.file);
formMedia.append("publishedFor", data.selectedUser.sort().join(","));
formMedia.append(
"isInt",
data.publishTo === "national" ? "false" : "true"
);
console.log(formMedia);
const response = await postCategory(formMedia);
close();
if (response?.error == true) {
error(response.message);
return false;
}
router.push("/admin/settings/category?dataChange=true");
}
};
return (
<Dialog>
<DialogTrigger asChild>
<Button color="primary" size="md">
Tambah Kategori
</Button>
</DialogTrigger>
<DialogContent size="md">
<DialogHeader>
<DialogTitle>Tambah Kategori</DialogTitle>
</DialogHeader>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-3 bg-white rounded-sm"
>
<FormField
control={form.control}
name="contentType"
render={() => (
<FormItem>
<FormLabel>Tipe Konten</FormLabel>
<div className="flex flex-row items-center gap-2">
<div className="flex items-center gap-3">
<Checkbox
id="all"
checked={isAllContentChecked}
onCheckedChange={(checked) => {
if (checked) {
form.setValue(
"contentType",
listContent.map((item) => item.id)
);
} else {
form.setValue("contentType", []);
}
}}
/>
<label htmlFor="all" className="text-sm">
Semua
</label>
</div>
{listContent.map((item) => (
<FormField
key={item.id}
control={form.control}
name="contentType"
render={({ field }) => {
return (
<FormItem
key={item.id}
className="flex flex-row items-start"
>
<div className="flex items-center gap-3">
<FormControl>
<Checkbox
checked={field.value?.includes(item.id)}
onCheckedChange={(checked) => {
return checked
? field.onChange([
...field.value,
item.id,
])
: field.onChange(
field.value?.filter(
(value) => value !== item.id
)
);
}}
/>
</FormControl>
<FormLabel className="font-normal">
{item.name}
</FormLabel>
</div>
</FormItem>
);
}}
/>
))}
</div>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="selectedUser"
render={() => (
<FormItem>
<FormLabel>Target Publish</FormLabel>
<div className="flex flex-row items-center gap-2">
<div className="flex gap-3 items-center">
<Checkbox
id="all"
checked={isAllUserChecked}
onCheckedChange={(checked) => {
if (checked) {
form.setValue(
"selectedUser",
userList.map((item) => item.id)
);
} else {
form.setValue("selectedUser", []);
}
}}
/>
<label htmlFor="all" className="text-sm">
Semua
</label>
</div>
{userList.map((item) => (
<FormField
key={item.id}
control={form.control}
name="selectedUser"
render={({ field }) => {
return (
<FormItem
key={item.id}
className="flex flex-row items-start "
>
<div className="flex items-center gap-3">
<FormControl>
<Checkbox
checked={field.value?.includes(item.id)}
onCheckedChange={(checked) => {
return checked
? field.onChange([
...field.value,
item.id,
])
: field.onChange(
field.value?.filter(
(value) => value !== item.id
)
);
}}
/>
</FormControl>
<FormLabel className="font-normal">
{item.name}
</FormLabel>
</div>
</FormItem>
);
}}
/>
))}
</div>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="publishTo"
render={({ field }) => (
<FormItem className="space-y-3">
<FormLabel>Wilayah Publish</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
value={field.value}
className="flex flex-row gap-2"
>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="national" />
</FormControl>
<FormLabel className="font-normal">Nasional</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="international" />
</FormControl>
<FormLabel className="font-normal">
Internasional
</FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Nama Kategori</FormLabel>
<Input
value={field.value}
placeholder="Masukkan Nama Kategori"
onChange={field.onChange}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="file"
render={({ field }) => (
<FormItem>
<FormLabel>Nama Kategori</FormLabel>
{!field.value && (
<div className="flex items-center justify-center w-full">
<label
htmlFor="dropzone-file"
className="flex flex-col items-center justify-center w-full h-28 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-gray-800 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600"
>
<div className="flex flex-col items-center justify-center pt-5 pb-6">
<p className="mb-2 text-sm text-gray-500 dark:text-gray-400">
<span className="font-semibold">Unggah Gambar</span>
</p>
</div>
<input
id="dropzone-file"
type="file"
className="hidden"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
field.onChange(file);
}
}}
/>
</label>
</div>
)}
{field.value && (
<div className="flex flex-row gap-2">
<img
src={URL.createObjectURL(field.value)}
className="w-[30%]"
alt="thumbnail"
/>
<a onClick={() => form.setValue("file", undefined)}>
<Icon icon="fa-solid:times" color="red" />
</a>
</div>
)}
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Deskripsi</FormLabel>
<FormControl>
<Textarea
placeholder="Deskripsi"
// className="resize-none"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<DialogFooter>
<Button
type="submit"
color="primary"
size="md"
className="text-xs"
>
Tambah Kategori
</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);
}

View File

@ -0,0 +1,443 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { useRouter } from "@/i18n/routing";
import { Input } from "@/components/ui/input";
import { Checkbox } from "@/components/ui/checkbox";
import { getUserRoles, postCategory } from "@/service/settings/settings";
import { useEffect, useState } from "react";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Icon } from "@iconify/react/dist/iconify.js";
import { Textarea } from "@/components/ui/textarea";
import { close, error, loading } from "@/config/swal";
const FormSchema = z.object({
title: z.string({
required_error: "Required",
}),
description: z.string({
required_error: "Required",
}),
contentType: z
.array(z.string())
.refine((value) => value.some((item) => item), {
message: "Required",
}),
selectedUser: z
.array(z.string())
.refine((value) => value.some((item) => item), {
message: "Required",
}),
publishTo: z.string({
required_error: "Required",
}),
file: z
.instanceof(File, {
message: "Invalid file format",
})
.optional()
.refine((file) => file && file.size > 0, {
message: "File is required",
}),
id: z.string({
required_error: "Required",
}),
});
const listContent = [
{
id: "1",
name: "Foto",
},
{
id: "2",
name: "Audio Visual",
},
{
id: "3",
name: "Teks",
},
{
id: "4",
name: "Audio",
},
];
export default function EditCategoryModal(props: { data: any }) {
const { data } = props;
const router = useRouter();
const [openModal, setOpenModal] = useState(false);
const [userList, setUserList] = useState<
{ id: string; name: string; isInternal: boolean }[]
>([]);
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: { contentType: [], selectedUser: [], publishTo: "national" },
});
useEffect(() => {
form.setValue("id", String(data?.id));
form.setValue("title", String(data?.name));
form.setValue("description", String(data?.description));
form.setValue("contentType", data?.mediaTypes.split(","));
form.setValue("selectedUser", data.publishedFor.split(","));
form.setValue("publishTo", data?.isInt ? "international" : "national");
}, [data]);
const contentType = form.watch("contentType");
const isAllContentChecked = listContent.every((item) =>
contentType?.includes(item.id)
);
const users = form.watch("selectedUser");
const isAllUserChecked = userList.every((item) => users?.includes(item.id));
useEffect(() => {
getRoles();
}, []);
async function getRoles() {
const response = await getUserRoles();
let dataRoles = response.data.data;
for (let i = 0; i < dataRoles.length; i++) {
dataRoles[i].id = String(dataRoles[i].id);
}
setUserList(dataRoles);
console.log("dasda", dataRoles);
}
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
if (data.file instanceof Blob) {
const formMedia = new FormData();
loading();
formMedia.append("id", data.id);
formMedia.append("name", data.title);
formMedia.append("description", data.description);
formMedia.append("mediaTypes", data.contentType.join(","));
formMedia.append("file", data.file);
formMedia.append("publishedFor", data.selectedUser.sort().join(","));
formMedia.append(
"isInt",
data.publishTo === "national" ? "false" : "true"
);
console.log(formMedia);
const response = await postCategory(formMedia);
close();
if (response?.error == true) {
error(response.message);
return false;
}
router.push("/admin/settings/category?dataChange=true");
}
};
return (
<Dialog>
<DialogTrigger>
<a className="hover:underline">Edit Kategori</a>
</DialogTrigger>
<DialogContent size="md">
<DialogHeader>
<DialogTitle>Edit Kategori</DialogTitle>
</DialogHeader>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-3 bg-white rounded-sm"
>
<FormField
control={form.control}
name="contentType"
render={() => (
<FormItem>
<FormLabel>Tipe Konten</FormLabel>
<div className="flex flex-row items-center gap-2">
<div className="flex items-center gap-3">
<Checkbox
id="all"
checked={isAllContentChecked}
onCheckedChange={(checked) => {
if (checked) {
form.setValue(
"contentType",
listContent.map((item) => item.id)
);
} else {
form.setValue("contentType", []);
}
}}
/>
<label htmlFor="all" className="text-sm">
Semua
</label>
</div>
{listContent.map((item) => (
<FormField
key={item.id}
control={form.control}
name="contentType"
render={({ field }) => {
return (
<FormItem
key={item.id}
className="flex flex-row items-start"
>
<div className="flex items-center gap-3">
<FormControl>
<Checkbox
checked={field.value?.includes(item.id)}
onCheckedChange={(checked) => {
return checked
? field.onChange([
...field.value,
item.id,
])
: field.onChange(
field.value?.filter(
(value) => value !== item.id
)
);
}}
/>
</FormControl>
<FormLabel className="font-normal">
{item.name}
</FormLabel>
</div>
</FormItem>
);
}}
/>
))}
</div>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="selectedUser"
render={() => (
<FormItem>
<FormLabel>Target Publish</FormLabel>
<div className="flex flex-row items-center gap-2">
<div className="flex gap-3 items-center">
<Checkbox
id="all"
checked={isAllUserChecked}
onCheckedChange={(checked) => {
if (checked) {
form.setValue(
"selectedUser",
userList.map((item) => item.id)
);
} else {
form.setValue("selectedUser", []);
}
}}
/>
<label htmlFor="all" className="text-sm">
Semua
</label>
</div>
{userList.map((item) => (
<FormField
key={item.id}
control={form.control}
name="selectedUser"
render={({ field }) => {
return (
<FormItem
key={item.id}
className="flex flex-row items-start "
>
<div className="flex items-center gap-3">
<FormControl>
<Checkbox
checked={field.value?.includes(item.id)}
onCheckedChange={(checked) => {
return checked
? field.onChange([
...field.value,
item.id,
])
: field.onChange(
field.value?.filter(
(value) => value !== item.id
)
);
}}
/>
</FormControl>
<FormLabel className="font-normal">
{item.name}
</FormLabel>
</div>
</FormItem>
);
}}
/>
))}
</div>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="publishTo"
render={({ field }) => (
<FormItem className="space-y-3">
<FormLabel>Wilayah Publish</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
value={field.value}
className="flex flex-row gap-2"
>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="national" />
</FormControl>
<FormLabel className="font-normal">Nasional</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="international" />
</FormControl>
<FormLabel className="font-normal">
Internasional
</FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Nama Kategori</FormLabel>
<Input
value={field.value}
placeholder="Masukkan Nama Kategori"
onChange={field.onChange}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="file"
render={({ field }) => (
<FormItem>
<FormLabel>Nama Kategori</FormLabel>
{!field.value && (
<div className="flex items-center justify-center w-full">
<label
htmlFor="dropzone-file"
className="flex flex-col items-center justify-center w-full h-28 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-gray-800 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600"
>
<div className="flex flex-col items-center justify-center pt-5 pb-6">
<p className="mb-2 text-sm text-gray-500 dark:text-gray-400">
<span className="font-semibold">Unggah Gambar</span>
</p>
</div>
<input
id="dropzone-file"
type="file"
className="hidden"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
field.onChange(file);
}
}}
/>
</label>
</div>
)}
{field.value && (
<div className="flex flex-row gap-2">
<img
src={URL.createObjectURL(field.value)}
className="w-[30%]"
alt="thumbnail"
/>
<a onClick={() => form.setValue("file", undefined)}>
<Icon icon="fa-solid:times" color="red" />
</a>
</div>
)}
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Deskripsi</FormLabel>
<FormControl>
<Textarea
placeholder="Deskripsi"
// className="resize-none"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<DialogFooter>
<Button
type="submit"
color="primary"
size="md"
className="text-xs"
>
Edit Kategori
</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);
}

View File

@ -0,0 +1,29 @@
"use client";
import { Switch } from "@/components/ui/switch";
import { error } from "@/config/swal";
import { useRouter } from "@/i18n/routing";
import { publishUnpublishCategory } from "@/service/settings/settings";
export default function StatusToogle(props: {
id: number;
initValue: boolean;
}) {
const { id, initValue } = props;
const router = useRouter();
const publishCategory = async (id: number, status: string) => {
const response = await publishUnpublishCategory(id, status);
console.log(response);
if (response?.error) {
error(response.message);
return false;
}
router.push("/admin/settings/category?dataChange=true");
};
return (
<Switch
id={String(id)}
checked={initValue}
onCheckedChange={(e) => publishCategory(id, String(e))}
/>
);
}

View File

@ -0,0 +1,184 @@
"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 { useSearchParams } from "next/navigation";
import TablePagination from "@/components/table/table-pagination";
import columns from "./column";
import { listEnableCategory } from "@/service/content/content";
import { Checkbox } from "@/components/ui/checkbox";
import { close, loading } from "@/config/swal";
import { Link, useRouter } from "@/i18n/routing";
import { NewCampaignIcon } from "@/components/icon";
import { getCategories } from "@/service/settings/settings";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import CreateCategoryModal from "./create";
const AdminCategoryTable = () => {
const router = useRouter();
const searchParams = useSearchParams();
const dataChange = searchParams?.get("dataChange");
const [openModal, setOpenModal] = React.useState(false);
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 [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize: 50,
});
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(() => {
if (dataChange) {
router.push("/admin/settings/category");
}
fetchData();
}, [dataChange]);
React.useEffect(() => {
const pageFromUrl = searchParams?.get("page");
if (pageFromUrl) {
setPage(Number(pageFromUrl));
}
}, [searchParams]);
React.useEffect(() => {
fetchData();
}, [page]);
async function fetchData() {
try {
loading();
const response = await getCategories();
const data = response.data?.data;
const contentData = data?.content;
contentData.forEach((item: any, index: number) => {
item.no = (page - 1) * 50 + index + 1;
});
setDataTable(contentData);
setTotalData(data?.totalElements);
setTotalPage(1);
close();
} catch (error) {
console.error("Error fetching tasks:", error);
}
}
return (
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
<div className="flex justify-between mb-10 items-center">
<p className="text-xl font-medium text-default-900">Kategori</p>
<CreateCategoryModal />
</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 AdminCategoryTable;

View File

@ -1,10 +1,12 @@
"use client";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import AdminCategoryTable from "./component/table";
export default function AdminCategory() {
return (
<>
<SiteBreadcrumb />
<AdminCategoryTable />
</>
);
}

View File

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

View File

@ -128,3 +128,37 @@ export const XIcon = ({
/>
</svg>
);
export const NewCampaignIcon = ({
size,
height = 24,
width = 24,
fill = "currentColor",
...props
}: IconSvgProps) => (
<svg
height={size || height}
width={size || width}
viewBox="0 0 18 18"
fill={fill}
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g clipPath="url(#clip0_4_200)">
<path
d="M12 2.25C12.1989 2.25 12.3897 2.32902 12.5303 2.46967C12.671 2.61032 12.75 2.80109 12.75 3V3.75H14.25C14.6284 3.74988 14.9929 3.8928 15.2704 4.15012C15.5479 4.40744 15.7179 4.76013 15.7463 5.1375L15.75 5.25V14.25C15.7501 14.6284 15.6072 14.9929 15.3499 15.2704C15.0926 15.5479 14.7399 15.7179 14.3625 15.7463L14.25 15.75H3.75C3.37157 15.7501 3.00708 15.6072 2.72959 15.3499C2.4521 15.0926 2.28213 14.7399 2.25375 14.3625L2.25 14.25V5.25C2.24988 4.87157 2.3928 4.50708 2.65012 4.22959C2.90744 3.9521 3.26013 3.78213 3.6375 3.75375L3.75 3.75H5.25V3C5.25 2.80109 5.32902 2.61032 5.46967 2.46967C5.61032 2.32902 5.80109 2.25 6 2.25C6.19891 2.25 6.38968 2.32902 6.53033 2.46967C6.67098 2.61032 6.75 2.80109 6.75 3V3.75H11.25V3C11.25 2.80109 11.329 2.61032 11.4697 2.46967C11.6103 2.32902 11.8011 2.25 12 2.25ZM11.118 7.03425L7.9365 10.2158L6.87525 9.15525C6.7338 9.01863 6.54435 8.94304 6.3477 8.94474C6.15105 8.94645 5.96294 9.02533 5.82389 9.16439C5.68483 9.30344 5.60595 9.49155 5.60424 9.6882C5.60254 9.88485 5.67813 10.0743 5.81475 10.2158L7.40025 11.802C7.4706 11.8724 7.55414 11.9283 7.64608 11.9664C7.73803 12.0045 7.83659 12.0241 7.93612 12.0241C8.03566 12.0241 8.13422 12.0045 8.22617 11.9664C8.31811 11.9283 8.40165 11.8724 8.472 11.802L12.1785 8.09475C12.2501 8.02557 12.3073 7.94281 12.3466 7.8513C12.3859 7.7598 12.4066 7.66139 12.4074 7.5618C12.4083 7.46222 12.3893 7.36346 12.3516 7.27129C12.3139 7.17911 12.2582 7.09537 12.1878 7.02495C12.1174 6.95453 12.0336 6.89884 11.9415 6.86113C11.8493 6.82342 11.7505 6.80445 11.6509 6.80531C11.5514 6.80618 11.4529 6.82687 11.3614 6.86617C11.2699 6.90548 11.1872 6.96262 11.118 7.03425Z"
fill="white"
/>
<rect width="10" height="10" transform="translate(4 5)" fill="white" />
<path
d="M13.2857 10.7143H9.71429V14.2857C9.71429 14.6786 9.39286 15 9 15C8.60714 15 8.28571 14.6786 8.28571 14.2857V10.7143H4.71429C4.32143 10.7143 4 10.3929 4 10C4 9.60714 4.32143 9.28571 4.71429 9.28571H8.28571V5.71429C8.28571 5.32143 8.60714 5 9 5C9.39286 5 9.71429 5.32143 9.71429 5.71429V9.28571H13.2857C13.6786 9.28571 14 9.60714 14 10C14 10.3929 13.6786 10.7143 13.2857 10.7143Z"
fill="#0d6efd"
/>
</g>
<defs>
<clipPath id="clip0_4_200">
<rect width="18" height="18" fill="white" />
</clipPath>
</defs>
</svg>
);

View File

@ -297,7 +297,7 @@
"colors": "Colors",
"performance-polda": "Performa Polda",
"analysis": "Analisa",
"content-management": "Manajemen Konten",
"management-content": "Manajemen Konten",
"add-experts": "Tambah Tenaga Ahli",
"category": "Kategori",
"privacy": "Kebijakan Privacy"

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -1,4 +1,8 @@
import { httpGetInterceptor } from "../http-config/http-interceptor-service";
import {
httpDeleteInterceptor,
httpGetInterceptor,
httpPostInterceptor,
} from "../http-config/http-interceptor-service";
export async function listDataMedia(
page: number,
@ -21,3 +25,55 @@ export async function getMediaBlastAccountPage(page: number, category: string) {
const url = `media/blast/account/list?enablePage=1&size=10&page=${page}&category=${category}`;
return httpGetInterceptor(url);
}
export async function saveMediaBlastCampaign(data: {
title: string;
sendTime: string;
status: string;
id?: string;
}) {
const url = `media/blast/campaign`;
return httpPostInterceptor(url, data);
}
export async function getMediaBlastCampaignById(id: string) {
const url = `media/blast/campaign?id=${id}`;
return httpGetInterceptor(url);
}
export async function getMediaBlastCampaignList() {
const url = `media/blast/campaign/list?enablePage=0&status=waiting`;
return httpGetInterceptor(url);
}
export async function detailMediaSummary(id: string) {
const url = `media?id=${id}&isSummary=true`;
return httpGetInterceptor(url);
}
export async function saveMediaBlastAccount(data: {
accountName: string;
accountType: string;
accountCategory: string;
emailAddress: string;
whatsappNumber: string;
id?: string;
}) {
const url = `media/blast/account`;
return httpPostInterceptor(url, data);
}
export async function getMediaBlastAccount(id: string) {
const url = `media/blast/account?id=${id}`;
return httpGetInterceptor(url);
}
export async function deleteMediaBlastAccount(id: string) {
const url = `media/blast/account?id=${id}`;
return httpDeleteInterceptor(url);
}
export async function saveMediaBlastBroadcast(data: any) {
const url = `media/blast/broadcast`;
return httpPostInterceptor(url, data);
}

View File

@ -30,7 +30,7 @@ export async function httpGetInterceptor(pathUrl: any) {
export async function httpPostInterceptor(
pathUrl: any,
data: any,
data?: any,
headers?: any
) {
const response = await axiosInterceptorInstance

View File

@ -0,0 +1,30 @@
import {
httpDeleteInterceptor,
httpGetInterceptor,
httpPostInterceptor,
} from "../http-config/http-interceptor-service";
export async function getCategories() {
const url = "media/categories/list?enablePage=1&size=50&sort=desc&sortBy=id";
return httpGetInterceptor(url);
}
export async function publishUnpublishCategory(id: number, status: string) {
const url = `media/categories/publish?id=${id}&status=${status}`;
return httpPostInterceptor(url);
}
export async function deleteCategory(id: number) {
const url = `media/categories/${id}`;
return httpDeleteInterceptor(url);
}
export async function getUserRoles() {
const url = "users/roles";
return httpGetInterceptor(url);
}
export async function postCategory(data: any) {
const url = "media/categories";
return httpPostInterceptor(url, data);
}

View File

@ -8,7 +8,11 @@ export const generateLocalizedPath = (href: string, locale: string): string => {
return `/${locale}${href}`;
};
export function textEllipsis(str: string, maxLength: number, { side = "end", ellipsis = "..." } = {}) {
export function textEllipsis(
str: string,
maxLength: number,
{ side = "end", ellipsis = "..." } = {}
) {
if (str !== undefined && str?.length > maxLength) {
switch (side) {
case "start":
@ -67,7 +71,10 @@ export function getOnlyMonthAndYear(d: Date) {
export function getPublicLocaleTimestamp(d: any) {
const pad = (n: any, s = 2) => `${new Array(s).fill(0)}${n}`.slice(-s);
return `${pad(d.getDate())}/${pad(d.getMonth() + 1)}/${pad(d.getFullYear(), 4)} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
return `${pad(d.getDate())}/${pad(d.getMonth() + 1)}/${pad(
d.getFullYear(),
4
)} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
export function capitalize(s: any) {
@ -79,3 +86,17 @@ export function capitalize(s: any) {
return splitStr.join(" ");
}
export function getLocaleTimestamp(d: Date): string {
const pad = (n: number, s: number = 2): string =>
`${new Array(s).fill(0)}${n}`.slice(-s);
return `${pad(d.getDate())}-${pad(d.getMonth() + 1)}-${pad(
d.getFullYear(),
4
)} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
}
export function getLocaleTime(d: Date) {
const pad = (n: number, s = 2) => `${new Array(s).fill(0)}${n}`.slice(-s);
return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
}