Merge branch 'main' of https://gitlab.com/hanifsalafi/mediahub_redesign into prod
This commit is contained in:
commit
2fae4027ee
|
|
@ -1,4 +1,4 @@
|
|||
'use client'
|
||||
"use client";
|
||||
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import { Icon } from "@iconify/react";
|
||||
|
|
@ -33,9 +33,9 @@ import {
|
|||
} from "@/components/ui/table";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import TablePagination from "@/components/table/table-pagination";
|
||||
import {
|
||||
import {
|
||||
getMediaBlastCampaignById,
|
||||
getMediaBlastBroadcastList
|
||||
getMediaBlastBroadcastList,
|
||||
} from "@/service/broadcast/broadcast";
|
||||
|
||||
// Types
|
||||
|
|
@ -69,319 +69,342 @@ interface PageProps {
|
|||
};
|
||||
}
|
||||
|
||||
export default function BroadcastCampaignDetail({ params, searchParams }: PageProps) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const { id, locale } = params;
|
||||
const [getData, setGetData] = useState<CampaignData[]>([]);
|
||||
const [totalPage, setTotalPage] = useState<number>(0);
|
||||
const [totalData, setTotalData] = useState<number>(0);
|
||||
const [activeTab, setActiveTab] = useState<"sent" | "schedule" | "account-list">("sent");
|
||||
const { page, size } = searchParams;
|
||||
export default function BroadcastCampaignDetail({
|
||||
params,
|
||||
searchParams,
|
||||
}: PageProps) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const { id, locale } = params;
|
||||
const [getData, setGetData] = useState<CampaignData[]>([]);
|
||||
const [totalPage, setTotalPage] = useState<number>(0);
|
||||
const [totalData, setTotalData] = useState<number>(0);
|
||||
const [activeTab, setActiveTab] = useState<
|
||||
"sent" | "schedule" | "account-list"
|
||||
>("sent");
|
||||
const { page, size } = searchParams;
|
||||
|
||||
const [calenderState, setCalenderState] = useState<boolean>(false);
|
||||
const [typeFilter, setTypeFilter] = useState<string>("email");
|
||||
const [dateRange, setDateRange] = useState<[Date, Date]>([new Date(), new Date()]);
|
||||
const [startDate, endDate] = dateRange;
|
||||
const [calenderState, setCalenderState] = useState<boolean>(false);
|
||||
const [typeFilter, setTypeFilter] = useState<string>("email");
|
||||
const [dateRange, setDateRange] = useState<[Date, Date]>([
|
||||
new Date(),
|
||||
new Date(),
|
||||
]);
|
||||
const [startDate, endDate] = dateRange;
|
||||
|
||||
const [startDateString, setStartDateString] = useState<string | undefined>();
|
||||
const [endDateString, setEndDateString] = useState<string | undefined>();
|
||||
const [startDateString, setStartDateString] = useState<string | undefined>();
|
||||
const [endDateString, setEndDateString] = useState<string | undefined>();
|
||||
|
||||
// Table state
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = useState({});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: parseInt(size || "10"),
|
||||
});
|
||||
// Table state
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = useState({});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: parseInt(size || "10"),
|
||||
});
|
||||
|
||||
const pages = page ? parseInt(page) - 1 : 0;
|
||||
const currentPage = page ? parseInt(page) : 1;
|
||||
const pageSize = parseInt(size || "10");
|
||||
const pages = page ? parseInt(page) - 1 : 0;
|
||||
const currentPage = page ? parseInt(page) : 1;
|
||||
const pageSize = parseInt(size || "10");
|
||||
|
||||
const isFHD = useMediaQuery({
|
||||
minWidth: 1920,
|
||||
});
|
||||
const isFHD = useMediaQuery({
|
||||
minWidth: 1920,
|
||||
});
|
||||
|
||||
const setCurrentPage = (pageNumber: number) => {
|
||||
const params = new URLSearchParams(searchParams);
|
||||
params.set('page', pageNumber.toString());
|
||||
router.push(`${pathname}?${params.toString()}`);
|
||||
};
|
||||
const setCurrentPage = (pageNumber: number) => {
|
||||
const params = new URLSearchParams(searchParams);
|
||||
params.set("page", pageNumber.toString());
|
||||
router.push(`${pathname}?${params.toString()}`);
|
||||
};
|
||||
|
||||
async function getListPaginationData() {
|
||||
loading();
|
||||
console.log("Type : ", typeFilter);
|
||||
console.log("Date : ", startDateString, endDateString);
|
||||
|
||||
try {
|
||||
const res = await getMediaBlastBroadcastList(
|
||||
pages,
|
||||
activeTab === "schedule",
|
||||
startDateString || "",
|
||||
endDateString || "",
|
||||
typeFilter,
|
||||
id
|
||||
);
|
||||
|
||||
close();
|
||||
if (res?.data?.data) {
|
||||
setupData(res.data.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
close();
|
||||
}
|
||||
async function getListPaginationData() {
|
||||
loading();
|
||||
console.log("Type : ", typeFilter);
|
||||
console.log("Date : ", startDateString, endDateString);
|
||||
|
||||
try {
|
||||
const res = await getMediaBlastBroadcastList(
|
||||
pages,
|
||||
activeTab === "schedule",
|
||||
startDateString || "",
|
||||
endDateString || "",
|
||||
typeFilter,
|
||||
id
|
||||
);
|
||||
|
||||
close();
|
||||
if (res?.data?.data) {
|
||||
setupData(res.data.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getListPaginationData();
|
||||
}, [currentPage, pageSize, activeTab, endDateString, startDateString, typeFilter]);
|
||||
useEffect(() => {
|
||||
getListPaginationData();
|
||||
}, [
|
||||
currentPage,
|
||||
pageSize,
|
||||
activeTab,
|
||||
endDateString,
|
||||
startDateString,
|
||||
typeFilter,
|
||||
]);
|
||||
|
||||
function setupData(rawData: PaginatedResponse) {
|
||||
console.log("raw", rawData);
|
||||
if (rawData !== undefined) {
|
||||
const dataContent = rawData?.content;
|
||||
const data: CampaignData[] = [];
|
||||
function setupData(rawData: PaginatedResponse) {
|
||||
console.log("raw", rawData);
|
||||
if (rawData !== undefined) {
|
||||
const dataContent = rawData?.content;
|
||||
const data: CampaignData[] = [];
|
||||
|
||||
dataContent.forEach((element, i) => {
|
||||
element.no = (currentPage - 1) * pageSize + i + 1;
|
||||
data.push(element);
|
||||
});
|
||||
dataContent.forEach((element, i) => {
|
||||
element.no = (currentPage - 1) * pageSize + i + 1;
|
||||
data.push(element);
|
||||
});
|
||||
|
||||
setGetData(data);
|
||||
setTotalPage(rawData?.totalPages);
|
||||
setTotalData(rawData?.totalElements);
|
||||
}
|
||||
setGetData(data);
|
||||
setTotalPage(rawData?.totalPages);
|
||||
setTotalData(rawData?.totalElements);
|
||||
}
|
||||
}
|
||||
|
||||
const columns: ColumnDef<CampaignData>[] = [
|
||||
{
|
||||
accessorKey: "no",
|
||||
header: "No",
|
||||
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
||||
},
|
||||
{
|
||||
accessorKey: "mediaBlastCampaign.title",
|
||||
header: "Campaign",
|
||||
cell: ({ row }) => (
|
||||
<Link href={`/${locale}/admin/broadcast/campaign-list/detail/${row.original.mediaBlastCampaignId}`} className="text-dark">
|
||||
<span className="font-weight-bold">{row.original.mediaBlastCampaign?.title}</span>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "subject",
|
||||
header: "Judul",
|
||||
cell: ({ row }) => (
|
||||
<Link href={`/${locale}/admin/broadcast/content/detail/${row.original.id}`} className="text-dark">
|
||||
<span className="font-weight-bold">{row.getValue("subject")}</span>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "type",
|
||||
header: "Tipe",
|
||||
cell: ({ row }) => (
|
||||
<div className="text-right text-black">
|
||||
{row.getValue("type")}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
accessorKey: "status",
|
||||
header: "Status",
|
||||
cell: ({ row }) => (
|
||||
<div className="text-right text-black">
|
||||
{row.getValue("status")}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
accessorKey: "sendDate",
|
||||
header: "Tanggal & Waktu",
|
||||
cell: ({ row }) => (
|
||||
<div className="text-black">
|
||||
{row.getValue("sendDate")}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
];
|
||||
const columns: ColumnDef<CampaignData>[] = [
|
||||
{
|
||||
accessorKey: "no",
|
||||
header: "No",
|
||||
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
||||
},
|
||||
{
|
||||
accessorKey: "mediaBlastCampaign.title",
|
||||
header: "Campaign",
|
||||
cell: ({ row }) => (
|
||||
<Link
|
||||
href={`/${locale}/admin/broadcast/campaign-list/detail/${row.original.mediaBlastCampaignId}`}
|
||||
className="text-dark"
|
||||
>
|
||||
<span className="font-weight-bold">
|
||||
{row.original.mediaBlastCampaign?.title}
|
||||
</span>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "subject",
|
||||
header: "Judul",
|
||||
cell: ({ row }) => (
|
||||
<Link
|
||||
href={`/${locale}/admin/broadcast/content/detail/${row.original.id}`}
|
||||
className="text-dark"
|
||||
>
|
||||
<span className="font-weight-bold">{row.getValue("subject")}</span>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "type",
|
||||
header: "Tipe",
|
||||
cell: ({ row }) => (
|
||||
<div className="text-right text-black">{row.getValue("type")}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "status",
|
||||
header: "Status",
|
||||
cell: ({ row }) => (
|
||||
<div className="text-right text-black">{row.getValue("status")}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "sendDate",
|
||||
header: "Tanggal & Waktu",
|
||||
cell: ({ row }) => (
|
||||
<div className="text-black">{row.getValue("sendDate")}</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const table = useReactTable({
|
||||
data: getData,
|
||||
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,
|
||||
},
|
||||
});
|
||||
const table = useReactTable({
|
||||
data: getData,
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
function initState() {
|
||||
if (startDate != null && endDate != null) {
|
||||
setStartDateString(getOnlyDate(startDate));
|
||||
setEndDateString(getOnlyDate(endDate));
|
||||
}
|
||||
}
|
||||
console.log('date range', dateRange);
|
||||
initState();
|
||||
}, [calenderState, startDate, endDate]);
|
||||
useEffect(() => {
|
||||
function initState() {
|
||||
if (startDate != null && endDate != null) {
|
||||
setStartDateString(getOnlyDate(startDate));
|
||||
setEndDateString(getOnlyDate(endDate));
|
||||
}
|
||||
}
|
||||
console.log("date range", dateRange);
|
||||
initState();
|
||||
}, [calenderState, startDate, endDate]);
|
||||
|
||||
const handleTypeFilter = (type: string) => {
|
||||
setTypeFilter(type);
|
||||
};
|
||||
const handleTypeFilter = (type: string) => {
|
||||
setTypeFilter(type);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-white container-fluid rounded rounded-xl">
|
||||
<div className="mt-1 p-4">
|
||||
<div className="flex flex-row gap-1 border-2 rounded-md w-fit mb-4">
|
||||
<Button
|
||||
onClick={() => setActiveTab("sent")}
|
||||
size="md"
|
||||
className={`hover:text-white ${
|
||||
activeTab === "sent"
|
||||
? "bg-indigo-600 text-white "
|
||||
: "bg-white text-black "
|
||||
}`}
|
||||
>
|
||||
Sent
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setActiveTab("schedule")}
|
||||
size="md"
|
||||
className={`hover:text-white ${
|
||||
activeTab === "schedule"
|
||||
? "bg-indigo-600 text-white "
|
||||
: "bg-white text-black "
|
||||
}`}
|
||||
>
|
||||
Schedule
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setActiveTab("account-list")}
|
||||
size="md"
|
||||
className={`hover:text-white ${
|
||||
activeTab === "account-list"
|
||||
? "bg-indigo-600 text-white "
|
||||
: "bg-white text-black "
|
||||
}`}
|
||||
>
|
||||
List Akun
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{activeTab === "account-list" ? (
|
||||
<AccountListTable />
|
||||
) : (
|
||||
<>
|
||||
<div className="broadcast-filter flex flex-column gap-3 mb-4">
|
||||
<div className="flex flex-row gap-1 border-2 rounded-md w-fit h-fit">
|
||||
<Button
|
||||
onClick={() => handleTypeFilter("email")}
|
||||
className={`hover:text-white ${
|
||||
typeFilter === "email"
|
||||
? "bg-black text-white "
|
||||
: "bg-white text-black "
|
||||
}`}
|
||||
size='sm'
|
||||
>
|
||||
Email Blast
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleTypeFilter("wa")}
|
||||
className={`hover:text-white ${
|
||||
typeFilter === "wa"
|
||||
? "bg-black text-white "
|
||||
: "bg-white text-black "
|
||||
}`}
|
||||
size='sm'
|
||||
>
|
||||
WhatsApp Blast
|
||||
</Button>
|
||||
</div>
|
||||
<div className="dashboard-date-picker">
|
||||
<div className="mx-6 my-1">
|
||||
<ReactDatePicker
|
||||
selectsRange
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
onChange={(update) => {
|
||||
setDateRange(update as [Date, Date]);
|
||||
}}
|
||||
placeholderText="Pilih Tanggal"
|
||||
onCalendarClose={() => setCalenderState(!calenderState)}
|
||||
className="form-control rounded-pill"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full overflow-x-auto">
|
||||
<Table className="overflow-hidden mt-3">
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id} className="bg-default-200">
|
||||
{headerGroup.headers.map((header) => (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
className="h-[75px]"
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination
|
||||
table={table}
|
||||
totalData={totalData}
|
||||
totalPage={totalPage}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
return (
|
||||
<div className="bg-white container-fluid rounded ">
|
||||
<div className="mt-1 p-4">
|
||||
<div className="flex flex-row gap-1 border-2 rounded-md w-fit mb-4">
|
||||
<Button
|
||||
onClick={() => setActiveTab("sent")}
|
||||
size="md"
|
||||
className={`hover:text-white ${
|
||||
activeTab === "sent"
|
||||
? "bg-indigo-600 text-white "
|
||||
: "bg-white text-black "
|
||||
}`}
|
||||
>
|
||||
Sent
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setActiveTab("schedule")}
|
||||
size="md"
|
||||
className={`hover:text-white ${
|
||||
activeTab === "schedule"
|
||||
? "bg-indigo-600 text-white "
|
||||
: "bg-white text-black "
|
||||
}`}
|
||||
>
|
||||
Schedule
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setActiveTab("account-list")}
|
||||
size="md"
|
||||
className={`hover:text-white ${
|
||||
activeTab === "account-list"
|
||||
? "bg-indigo-600 text-white "
|
||||
: "bg-white text-black "
|
||||
}`}
|
||||
>
|
||||
List Akun
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
{activeTab === "account-list" ? (
|
||||
<AccountListTable />
|
||||
) : (
|
||||
<>
|
||||
<div className="broadcast-filter flex flex-column gap-3 mb-4">
|
||||
<div className="flex flex-row gap-1 border-2 rounded-md w-fit h-fit">
|
||||
<Button
|
||||
onClick={() => handleTypeFilter("email")}
|
||||
className={`hover:text-white ${
|
||||
typeFilter === "email"
|
||||
? "bg-black text-white "
|
||||
: "bg-white text-black "
|
||||
}`}
|
||||
size="sm"
|
||||
>
|
||||
Email Blast
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleTypeFilter("wa")}
|
||||
className={`hover:text-white ${
|
||||
typeFilter === "wa"
|
||||
? "bg-black text-white "
|
||||
: "bg-white text-black "
|
||||
}`}
|
||||
size="sm"
|
||||
>
|
||||
WhatsApp Blast
|
||||
</Button>
|
||||
</div>
|
||||
<div className="dashboard-date-picker">
|
||||
<div className="mx-6 my-1">
|
||||
<ReactDatePicker
|
||||
selectsRange
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
onChange={(update) => {
|
||||
setDateRange(update as [Date, Date]);
|
||||
}}
|
||||
placeholderText="Pilih Tanggal"
|
||||
onCalendarClose={() => setCalenderState(!calenderState)}
|
||||
className="form-control rounded-pill"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full overflow-x-auto">
|
||||
<Table className="overflow-hidden mt-3">
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id} className="bg-default-200">
|
||||
{headerGroup.headers.map((header) => (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
className="h-[75px]"
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext()
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination
|
||||
table={table}
|
||||
totalData={totalData}
|
||||
totalPage={totalPage}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
import DetailContentBlast from "@/components/form/broadcast/content-blast--detail-form";
|
||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
|
||||
export default function DetailEmailBlast() {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<DetailContentBlast />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import { Checkbox } from "@/components/ui/checkbox";
|
|||
import { Label } from "@/components/ui/label";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { useTranslations } from "next-intl";
|
||||
import {
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
|
|
@ -27,7 +27,7 @@ export const LoginForm: React.FC<LoginFormProps> = ({
|
|||
}) => {
|
||||
const t = useTranslations("LandingPage");
|
||||
const { login } = useAuth();
|
||||
|
||||
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [rememberMe, setRememberMe] = useState(true);
|
||||
const [roles, setRoles] = useState<Role[]>([]);
|
||||
|
|
@ -210,4 +210,4 @@ export const LoginForm: React.FC<LoginFormProps> = ({
|
|||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -94,39 +94,39 @@ export const OTPForm: React.FC<OTPFormProps> = ({
|
|||
className="gap-2"
|
||||
>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot
|
||||
index={0}
|
||||
<InputOTPSlot
|
||||
index={0}
|
||||
onKeyDown={handleTypeOTP}
|
||||
className="w-12 h-12 text-lg"
|
||||
/>
|
||||
<InputOTPSlot
|
||||
index={1}
|
||||
<InputOTPSlot
|
||||
index={1}
|
||||
onKeyDown={handleTypeOTP}
|
||||
className="w-12 h-12 text-lg"
|
||||
/>
|
||||
</InputOTPGroup>
|
||||
<InputOTPSeparator />
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot
|
||||
index={2}
|
||||
<InputOTPSlot
|
||||
index={2}
|
||||
onKeyDown={handleTypeOTP}
|
||||
className="w-12 h-12 text-lg"
|
||||
/>
|
||||
<InputOTPSlot
|
||||
index={3}
|
||||
<InputOTPSlot
|
||||
index={3}
|
||||
onKeyDown={handleTypeOTP}
|
||||
className="w-12 h-12 text-lg"
|
||||
/>
|
||||
</InputOTPGroup>
|
||||
<InputOTPSeparator />
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot
|
||||
index={4}
|
||||
<InputOTPSlot
|
||||
index={4}
|
||||
onKeyDown={handleTypeOTP}
|
||||
className="w-12 h-12 text-lg"
|
||||
/>
|
||||
<InputOTPSlot
|
||||
index={5}
|
||||
<InputOTPSlot
|
||||
index={5}
|
||||
onKeyDown={handleTypeOTP}
|
||||
className="w-12 h-12 text-lg"
|
||||
/>
|
||||
|
|
@ -166,4 +166,4 @@ export const OTPForm: React.FC<OTPFormProps> = ({
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { getMediaBlastBroadCast } from "@/service/broadcast/broadcast";
|
||||
import { loading, close } from "@/config/swal";
|
||||
|
||||
interface BroadcastDetail {
|
||||
id: number;
|
||||
body: string;
|
||||
subject: string;
|
||||
sendTime: string;
|
||||
thumbnail: string;
|
||||
contentUrl: string;
|
||||
}
|
||||
|
||||
export default function DetailContentBlast() {
|
||||
const params = useParams();
|
||||
const { id } = params as { id: string };
|
||||
|
||||
const [detail, setDetail] = useState<BroadcastDetail | null>(null);
|
||||
const [notFound, setNotFound] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchDetailData();
|
||||
}, [id]);
|
||||
|
||||
async function fetchDetailData() {
|
||||
loading();
|
||||
try {
|
||||
const res = await getMediaBlastBroadCast(id);
|
||||
close();
|
||||
|
||||
const detailData = res?.data?.data;
|
||||
|
||||
if (detailData && detailData.id === Number(id)) {
|
||||
setDetail({
|
||||
id: detailData.id,
|
||||
body: detailData.body,
|
||||
subject: detailData.subject,
|
||||
sendTime: detailData.sendTime,
|
||||
thumbnail: detailData.thumbnail,
|
||||
contentUrl: detailData.contentUrl,
|
||||
});
|
||||
} else {
|
||||
setNotFound(true);
|
||||
}
|
||||
} catch (error) {
|
||||
close();
|
||||
console.error("Failed to fetch broadcast detail:", error);
|
||||
setNotFound(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (notFound) {
|
||||
return <div className="text-red-500 p-4">Data tidak ditemukan.</div>;
|
||||
}
|
||||
|
||||
if (!detail) {
|
||||
return <div className="text-gray-500 p-4">Loading preview...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-md w-full p-4">
|
||||
<p className="text-xl font-semibold py-1">Preview</p>
|
||||
<div className="bg-[#ddf7c8] p-4 rounded-xl mx-auto space-y-4">
|
||||
<div className="flex gap-4">
|
||||
<Image
|
||||
src={detail.thumbnail}
|
||||
alt="Media Thumbnail"
|
||||
width={250}
|
||||
height={200}
|
||||
className="rounded-md object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-black font-bold">{detail.subject}</div>
|
||||
<p>
|
||||
Selengkapnya silakan cek di sini:{" "}
|
||||
<a
|
||||
href={detail.contentUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 underline"
|
||||
>
|
||||
{detail.contentUrl}
|
||||
</a>
|
||||
</p>
|
||||
<div className="text-right text-xs text-gray-500">
|
||||
{detail.sendTime}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -69,6 +69,7 @@ import { getCookiesDecrypt } from "@/lib/utils";
|
|||
import { error } from "@/lib/swal";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { close, loading } from "@/config/swal";
|
||||
|
||||
// Types
|
||||
interface Category {
|
||||
|
|
@ -163,6 +164,7 @@ export default function FormConvertSPIT() {
|
|||
const router = useRouter();
|
||||
const t = useTranslations("Form");
|
||||
const { id } = useParams() as { id: string };
|
||||
const [isAlreadySaved, setIsAlreadySaved] = useState(false);
|
||||
|
||||
// Form state
|
||||
const {
|
||||
|
|
@ -198,35 +200,33 @@ export default function FormConvertSPIT() {
|
|||
const [showRewriteEditor, setShowRewriteEditor] = useState(false);
|
||||
const [isGeneratingRewrite, setIsGeneratingRewrite] = useState(false);
|
||||
const [isLoadingRewrite, setIsLoadingRewrite] = useState(false);
|
||||
|
||||
// Media state
|
||||
const [detailThumb, setDetailThumb] = useState<string[]>([]);
|
||||
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
||||
const [files, setFiles] = useState<FileType[]>([]);
|
||||
const [filePlacements, setFilePlacements] = useState<string[][]>([]);
|
||||
|
||||
// Content rewrite state
|
||||
const [articleIds, setArticleIds] = useState<string[]>([]);
|
||||
const [selectedArticleId, setSelectedArticleId] = useState<string | null>(
|
||||
null
|
||||
);
|
||||
const [articleBody, setArticleBody] = useState<string>("");
|
||||
|
||||
// Form data state
|
||||
const [tags, setTags] = useState<string[]>([]);
|
||||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||
const [inputRef] = useState(useRef<HTMLInputElement>(null));
|
||||
|
||||
// User permissions
|
||||
const userLevelId = getCookiesDecrypt("ulie");
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
|
||||
|
||||
// Initialize component
|
||||
useEffect(() => {
|
||||
initializeComponent();
|
||||
}, []);
|
||||
|
||||
// useEffect(() => {
|
||||
// const savedFlag = localStorage.getItem(`spit_saved_${id}`);
|
||||
// if (savedFlag === "true") {
|
||||
// setIsAlreadySaved(true);
|
||||
// }
|
||||
// }, [id]);
|
||||
|
||||
useEffect(() => {
|
||||
checkUserPermissions();
|
||||
}, [userLevelId, roleId]);
|
||||
|
|
@ -297,9 +297,12 @@ export default function FormConvertSPIT() {
|
|||
};
|
||||
|
||||
const loadDetail = async () => {
|
||||
loading()
|
||||
try {
|
||||
const response = await detailSPIT(id);
|
||||
const details = response?.data?.data;
|
||||
setIsAlreadySaved(details?.isPublish ? true : false)
|
||||
|
||||
|
||||
if (!details) {
|
||||
throw new Error("Detail not found");
|
||||
|
|
@ -341,9 +344,9 @@ export default function FormConvertSPIT() {
|
|||
console.error("Failed to load detail:", error);
|
||||
throw error;
|
||||
}
|
||||
close()
|
||||
};
|
||||
|
||||
// Event handlers
|
||||
const handleCategoryChange = async (categoryId: string) => {
|
||||
const id = Number(categoryId);
|
||||
setSelectedCategoryId(id);
|
||||
|
|
@ -602,6 +605,9 @@ export default function FormConvertSPIT() {
|
|||
|
||||
await convertSPIT(requestData);
|
||||
|
||||
// localStorage.setItem(`spit_saved_${id}`, "true");
|
||||
// setIsAlreadySaved(true);
|
||||
|
||||
MySwal.fire({
|
||||
title: "Success",
|
||||
text: "Data saved successfully",
|
||||
|
|
@ -609,7 +615,8 @@ export default function FormConvertSPIT() {
|
|||
confirmButtonColor: "#3085d6",
|
||||
}).then(() => {
|
||||
// router.push("/in/contributor/content/spit");
|
||||
router.replace(`${window.location.pathname}?id=${id}`);
|
||||
// router.replace(`${window.location.pathname}?id=${id}`);
|
||||
loadDetail()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to save:", error);
|
||||
|
|
@ -1164,16 +1171,29 @@ export default function FormConvertSPIT() {
|
|||
<CardContent className="pt-6">
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full"
|
||||
disabled={isSubmitting || isSaving}
|
||||
className="w-full mb-4"
|
||||
disabled={isSubmitting || isSaving || isAlreadySaved}
|
||||
>
|
||||
{isSubmitting || isSaving ? (
|
||||
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
||||
) : (
|
||||
<Save className="h-4 w-4 mr-2" />
|
||||
)}
|
||||
{isSubmitting || isSaving ? "Saving..." : "Save Changes"}
|
||||
{isAlreadySaved
|
||||
? "Already Saved"
|
||||
: isSubmitting || isSaving
|
||||
? "Saving..."
|
||||
: "Save Changes"}
|
||||
</Button>
|
||||
|
||||
{isAlreadySaved && (
|
||||
<Alert variant="soft">
|
||||
<CheckCircle className="h-4 w-4 text-red-500" />
|
||||
<AlertDescription className="text-red-500">
|
||||
Konten sudah disimpan. Anda tidak dapat menyimpan ulang.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -218,8 +218,23 @@ const HeroModal = ({
|
|||
};
|
||||
}, []);
|
||||
|
||||
// const initFetch = async () => {
|
||||
// const response = await listPopUp(
|
||||
// group === "mabes"
|
||||
// ? ""
|
||||
// : group === "polda" && poldaName
|
||||
// ? poldaName
|
||||
// : group === "satker" && satkerName
|
||||
// ? "satker-" + satkerName
|
||||
// : "",
|
||||
// locale == "en"
|
||||
// );
|
||||
// const interstitial = response?.data?.data || [];
|
||||
// setHeroData(interstitial);
|
||||
// };
|
||||
|
||||
const initFetch = async () => {
|
||||
const response = await listPopUp(
|
||||
const response = await listStaticBanner(
|
||||
group === "mabes"
|
||||
? ""
|
||||
: group === "polda" && poldaName
|
||||
|
|
@ -227,10 +242,17 @@ const HeroModal = ({
|
|||
: group === "satker" && satkerName
|
||||
? "satker-" + satkerName
|
||||
: "",
|
||||
locale == "en"
|
||||
locale === "en"
|
||||
);
|
||||
const interstitial = response?.data?.data || [];
|
||||
setHeroData(interstitial);
|
||||
|
||||
const banners = response?.data?.data || [];
|
||||
|
||||
const enrichedData = banners.map((item: any) => ({
|
||||
...item,
|
||||
fileTypeId: item?.fileType?.id ?? null,
|
||||
}));
|
||||
|
||||
setHeroData(enrichedData);
|
||||
};
|
||||
|
||||
const handleClickOutside = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,11 @@ import { formatDateToIndonesian, shimmer, toBase64 } from "@/utils/globals";
|
|||
import React, { useEffect, useRef, useState } from "react";
|
||||
import "swiper/css/bundle";
|
||||
import "swiper/css/navigation";
|
||||
import { getHeroData, listPopUp, listStaticBanner } from "@/service/landing/landing";
|
||||
import {
|
||||
getHeroData,
|
||||
listPopUp,
|
||||
listStaticBanner,
|
||||
} from "@/service/landing/landing";
|
||||
import Link from "next/link";
|
||||
import { useParams, usePathname, useRouter } from "next/navigation";
|
||||
import {
|
||||
|
|
@ -207,8 +211,23 @@ const HeroModal = ({
|
|||
};
|
||||
}, []);
|
||||
|
||||
// const initFetch = async () => {
|
||||
// const response = await listPopUp(
|
||||
// group === "mabes"
|
||||
// ? ""
|
||||
// : group === "polda" && poldaName
|
||||
// ? poldaName
|
||||
// : group === "satker" && satkerName
|
||||
// ? "satker-" + satkerName
|
||||
// : "",
|
||||
// locale == "en"
|
||||
// );
|
||||
// const interstitial = response?.data?.data || [];
|
||||
// setHeroData(interstitial);
|
||||
// };
|
||||
|
||||
const initFetch = async () => {
|
||||
const response = await listPopUp(
|
||||
const response = await listStaticBanner(
|
||||
group === "mabes"
|
||||
? ""
|
||||
: group === "polda" && poldaName
|
||||
|
|
@ -216,10 +235,17 @@ const HeroModal = ({
|
|||
: group === "satker" && satkerName
|
||||
? "satker-" + satkerName
|
||||
: "",
|
||||
locale == "en"
|
||||
locale === "en"
|
||||
);
|
||||
const interstitial = response?.data?.data || [];
|
||||
setHeroData(interstitial);
|
||||
|
||||
const banners = response?.data?.data || [];
|
||||
|
||||
const enrichedData = banners.map((item: any) => ({
|
||||
...item,
|
||||
fileTypeId: item?.fileType?.id ?? null,
|
||||
}));
|
||||
|
||||
setHeroData(enrichedData);
|
||||
};
|
||||
|
||||
const handleClickOutside = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
|
|
|
|||
|
|
@ -348,12 +348,12 @@ const HeroNew = (props: { group?: string }) => {
|
|||
initFetch();
|
||||
}, []);
|
||||
|
||||
// Show hero modal after 20 seconds when website is fully loaded
|
||||
// Show hero modal after 5 seconds when website is fully loaded
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
console.log("Show modal popup");
|
||||
setShowModal(true);
|
||||
}, 30000); // 30 seconds
|
||||
}, 5000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
|
|
|||
|
|
@ -101,17 +101,22 @@ export async function saveMediaBlastBroadcast(data: any) {
|
|||
}
|
||||
|
||||
export async function getMediaBlastBroadcastList(
|
||||
page: number,
|
||||
isScheduled: boolean,
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
type: string,
|
||||
page: number,
|
||||
isScheduled: boolean,
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
type: string,
|
||||
campaignId: string
|
||||
) {
|
||||
const url = `media/blast/broadcast/list?enablePage=1&page=${page}&isScheduled=${isScheduled}&type=${type}&startDate=${startDate}&endDate=${endDate}&campaignId=${campaignId}`;
|
||||
return httpGetInterceptor(url);
|
||||
}
|
||||
|
||||
export async function getMediaBlastBroadCast(id: string) {
|
||||
const url = `media/blast/broadcast?id=${id}`;
|
||||
return httpGetInterceptor(url);
|
||||
}
|
||||
|
||||
export async function listDataPopUp(
|
||||
page: number,
|
||||
limit: string,
|
||||
|
|
|
|||
Loading…
Reference in New Issue