little update

This commit is contained in:
Rama Priyanto 2025-01-21 14:31:30 +07:00
parent eda6921ae2
commit 91f34586cf
4 changed files with 357 additions and 241 deletions

View File

@ -1,12 +1,13 @@
"use client"; "use client";
import { AddIcon } from "@/components/icons"; import { AddIcon } from "@/components/icons";
import ArticleTable from "@/components/table/article-table"; import ArticleTable from "@/components/table/article-table";
import MagazineTable from "@/components/table/magazine/magazine-table";
import generatedArticleIds from "@/store/generated-article-store"; import generatedArticleIds from "@/store/generated-article-store";
import { Button, Card } from "@nextui-org/react"; import { Button, Card } from "@nextui-org/react";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
export default function MagazineTable() { export default function MagazineTablePage() {
const router = useRouter(); const router = useRouter();
const setGeneratedArticleIdStore = generatedArticleIds( const setGeneratedArticleIdStore = generatedArticleIds(
(state) => state.setArticleIds (state) => state.setArticleIds
@ -33,7 +34,7 @@ export default function MagazineTable() {
</Button> */} </Button> */}
</div> </div>
<div className="bg-white shadow-lg dark:bg-[#18181b] rounded-xl p-2"> <div className="bg-white shadow-lg dark:bg-[#18181b] rounded-xl p-2">
<ArticleTable /> <MagazineTable />
</div> </div>
</div> </div>
</div> </div>

View File

@ -132,29 +132,6 @@ export default function NewCreateMagazineForm() {
clearErrors, clearErrors,
} = useForm<UserSettingSchema>(formOptions); } = useForm<UserSettingSchema>(formOptions);
useEffect(() => {
fetchCategory();
}, []);
const fetchCategory = async () => {
const res = await getArticleByCategory();
if (res?.data?.data) {
setupCategory(res?.data?.data);
}
};
const setupCategory = (data: any) => {
const temp = [];
for (const element of data) {
temp.push({
id: element.id,
label: element.title,
value: element.id,
});
}
setListCategory(temp);
};
const onSubmit = async (values: z.infer<typeof createArticleSchema>) => { const onSubmit = async (values: z.infer<typeof createArticleSchema>) => {
MySwal.fire({ MySwal.fire({
title: "Simpan Data", title: "Simpan Data",
@ -171,15 +148,6 @@ export default function NewCreateMagazineForm() {
}); });
}; };
function removeImgTags(htmlString: string) {
const parser = new DOMParser();
const doc = parser.parseFromString(String(htmlString), "text/html");
const images = doc.querySelectorAll("img");
images.forEach((img) => img.remove());
return doc.body.innerHTML;
}
const save = async (values: z.infer<typeof createArticleSchema>) => { const save = async (values: z.infer<typeof createArticleSchema>) => {
loading(); loading();
const formData = { const formData = {
@ -189,15 +157,15 @@ export default function NewCreateMagazineForm() {
statusId: 1, statusId: 1,
// description: htmlToString(removeImgTags(values.description)), // description: htmlToString(removeImgTags(values.description)),
description: values.description, description: values.description,
rows: values.rows, // rows: values.rows,
}; };
console.log("formd", formData); console.log("formd", formData);
// const response = await createMagazine(formData); const response = await createMagazine(formData);
// if (response?.error) { if (response?.error) {
// error(response.message); error(response.message);
// return false; return false;
// } }
// const magazineId = response?.data?.data?.id; // const magazineId = response?.data?.data?.id;
// if (files?.length > 0) { // if (files?.length > 0) {
// const formFiles = new FormData(); // const formFiles = new FormData();

View File

@ -1,222 +1,355 @@
"use client"; "use client";
import { import {
TableCell, CreateIconIon,
TableRow, DeleteIcon,
Table, DotsYIcon,
TableHeader, EyeIconMdi,
TableColumn, SearchIcon,
TableBody,
Pagination,
Dropdown,
DropdownTrigger,
DropdownMenu,
DropdownItem,
Input,
User,
Card,
Divider,
Chip,
ChipProps,
} from "@nextui-org/react";
import { Button } from "@nextui-org/button";
import React, { Key, useCallback, useMemo, useState } from "react";
import {
AddIcon,
CreateIconIon,
DeleteIcon,
DotsYIcon,
EyeFilledIcon,
EyeIconMdi,
} from "@/components/icons"; } from "@/components/icons";
import { error, success } from "@/config/swal";
import {
deleteArticle,
getArticleByCategory,
getListArticle,
} from "@/service/article";
import { getListMagazine } from "@/service/magazine";
import { Article } from "@/types/globals";
import { convertDateFormat } from "@/utils/global";
import { Button } from "@nextui-org/button";
import {
Chip,
ChipProps,
Dropdown,
DropdownItem,
DropdownMenu,
DropdownTrigger,
Input,
Pagination,
Select,
SelectItem,
Spinner,
Table,
TableBody,
TableCell,
TableColumn,
TableHeader,
TableRow,
} from "@nextui-org/react";
import Link from "next/link"; import Link from "next/link";
import { Key, useCallback, useEffect, useState } from "react";
import Datepicker from "react-tailwindcss-datepicker";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
type UserObject = { const columns = [
id: number; { name: "No", uid: "no" },
title: string; { name: "Judul", uid: "title" },
status: string; { name: "Kategori", uid: "categoryName" },
description: string; { name: "Tanggal Unggah", uid: "createdAt" },
avatar: string; { name: "Kreator", uid: "createdByName" },
{ name: "Aksi", uid: "actions" },
];
type ArticleData = Article & {
no: number;
createdAt: string;
}; };
const statusColorMap = {
active: "success",
paused: "danger",
vacation: "warning",
};
export default function MagazineTable() { export default function MagazineTable() {
type TableRow = (typeof magazineTable)[0]; const MySwal = withReactContent(Swal);
const [page, setPage] = useState(1);
const [totalPage, setTotalPage] = useState(1);
const [article, setArticle] = useState<ArticleData[]>([]);
const [showData, setShowData] = useState("10");
const [search, setSearch] = useState("");
const [categories, setCategoies] = useState<any>([]);
const [selectedCategories, setSelectedCategories] = useState<any>([]);
const [startDateValue, setStartDateValue] = useState({
startDate: null,
endDate: null,
});
const columns = [ useEffect(() => {
initState();
}, [page, showData, startDateValue]);
{ name: "Title", uid: "title" }, useEffect(() => {
{ name: "Description", uid: "description" }, getCategories();
{ name: "Action", uid: "actions" }, }, []);
];
const magazineTable = [ async function getCategories() {
{ const res = await getArticleByCategory();
id: 1, const data = res?.data?.data;
title: "Proses pembuatan website humas ", console.log("datass", res?.data?.data);
status: "active", setCategoies(data);
description: "Pembuatan website Humas adalah sebuah proses yang strategis untuk membangun identitas digital sebuah organisasi atau entitas, yang bertujuan untuk menyebarkan informasi kepada publik, memperkuat citra merek, serta menjaga keterbukaan dan transparansi. Proses ini melibatkan beberapa tahapan yang terstruktur dan terkoordinasi dengan baik", }
avatar: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSa8Luglga9J2R3Bxt_PsWZISUHQWODD6_ZTAJ5mIQgxYCAE-YbkY81faTqp-hSA_jVPTs&usqp=CAU",
},
{
id: 2,
title: "Proses pembuatan website humas ",
status: "active",
description: "Pembuatan website Humas adalah sebuah proses yang strategis untuk membangun identitas digital sebuah organisasi atau entitas, yang bertujuan untuk menyebarkan informasi kepada publik, memperkuat citra merek, serta menjaga keterbukaan dan transparansi. Proses ini melibatkan beberapa tahapan yang terstruktur dan terkoordinasi dengan baik",
avatar: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSa8Luglga9J2R3Bxt_PsWZISUHQWODD6_ZTAJ5mIQgxYCAE-YbkY81faTqp-hSA_jVPTs&usqp=CAU",
},
{
id: 3,
title: "Proses pembuatan website humas ",
status: "active",
description: "Pembuatan website Humas adalah sebuah proses yang strategis untuk membangun identitas digital sebuah organisasi atau entitas, yang bertujuan untuk menyebarkan informasi kepada publik, memperkuat citra merek, serta menjaga keterbukaan dan transparansi. Proses ini melibatkan beberapa tahapan yang terstruktur dan terkoordinasi dengan baik",
avatar: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSa8Luglga9J2R3Bxt_PsWZISUHQWODD6_ZTAJ5mIQgxYCAE-YbkY81faTqp-hSA_jVPTs&usqp=CAU",
},
{
id: 4,
title: "Proses pembuatan website humas ",
status: "active",
description: "Pembuatan website Humas adalah sebuah proses yang strategis untuk membangun identitas digital sebuah organisasi atau entitas, yang bertujuan untuk menyebarkan informasi kepada publik, memperkuat citra merek, serta menjaga keterbukaan dan transparansi. Proses ini melibatkan beberapa tahapan yang terstruktur dan terkoordinasi dengan baik",
avatar: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSa8Luglga9J2R3Bxt_PsWZISUHQWODD6_ZTAJ5mIQgxYCAE-YbkY81faTqp-hSA_jVPTs&usqp=CAU",
},
{
id: 5,
title: "Proses pembuatan website humas ",
status: "active",
description: "Pembuatan website Humas adalah sebuah proses yang strategis untuk membangun identitas digital sebuah organisasi atau entitas, yang bertujuan untuk menyebarkan informasi kepada publik, memperkuat citra merek, serta menjaga keterbukaan dan transparansi. Proses ini melibatkan beberapa tahapan yang terstruktur dan terkoordinasi dengan baik",
avatar: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSa8Luglga9J2R3Bxt_PsWZISUHQWODD6_ZTAJ5mIQgxYCAE-YbkY81faTqp-hSA_jVPTs&usqp=CAU",
},
];
const renderCell = useCallback((magazine: TableRow, columnKey: Key) => { async function initState() {
const cellValue = magazine[columnKey as keyof UserObject]; const req = {
const statusColorMap: Record<string, ChipProps["color"]> = { limit: showData,
active: "primary", page: page,
cancel: "danger", search: search,
pending: "success", startDate:
}; startDateValue.startDate === null ? "" : startDateValue.startDate,
endDate: startDateValue.endDate === null ? "" : startDateValue.endDate,
};
const res = await getListMagazine(req);
getTableNumber(parseInt(showData), res.data?.data);
console.log("res.data?.data magz", res.data);
setTotalPage(res?.data?.meta?.totalPage);
}
switch (columnKey) { const getTableNumber = (limit: number, data: Article[]) => {
case "no": if (data) {
return ( const startIndex = limit * (page - 1);
<div>{magazine.id}</div> let iterate = 0;
) const newData = data.map((value: any) => {
iterate++;
value.no = startIndex + iterate;
return value;
});
console.log("daata", data);
setArticle(newData);
}
};
case "title": async function doDelete(id: any) {
return ( // loading();
<div className="w-[350px]">{magazine.title}</div> const resDelete = await deleteArticle(id);
)
case "description": if (resDelete?.error) {
return ( error(resDelete.message);
<div className="">{magazine.description}</div> return false;
) }
close();
success("Berhasil Hapus");
initState();
}
case "status": const handleDelete = (id: any) => {
return ( MySwal.fire({
<Chip title: "Hapus Data",
className="capitalize " icon: "warning",
color={statusColorMap[magazine.status]} showCancelButton: true,
size="lg" cancelButtonColor: "#3085d6",
variant="flat" confirmButtonColor: "#d33",
> confirmButtonText: "Hapus",
<div className="flex flex-row items-center gap-2 justify-center"> }).then((result) => {
{cellValue} if (result.isConfirmed) {
</div> doDelete(id);
</Chip> }
); });
};
case "actions": const renderCell = useCallback((article: ArticleData, columnKey: Key) => {
return ( const cellValue = article[columnKey as keyof ArticleData];
<div className="relative flex justify-star items-center gap-2"> const statusColorMap: Record<string, ChipProps["color"]> = {
<Dropdown className="lg:min-w-[150px] bg-black text-white shadow border "> active: "primary",
<DropdownTrigger> cancel: "danger",
<Button isIconOnly size="lg" variant="light"> pending: "success",
<DotsYIcon className="text-default-300" /> };
</Button>
</DropdownTrigger>
<DropdownMenu>
<DropdownItem
>
<Link
href={`/admin/magazine/detail`}
>
<EyeIconMdi className="inline mr-2 mb-1" />
Detail
</Link>
</DropdownItem>
<DropdownItem
>
<Link
href={`#`}
>
<CreateIconIon className="inline mr-2 mb-1" />
Edit
</Link>
</DropdownItem>
<DropdownItem
>
<Link
href={`#`}
>
<DeleteIcon
width={20}
height={16}
className="inline mr-2 mb-1"
/>
Delete
</Link>
</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
);
default: switch (columnKey) {
return cellValue; case "status":
} return (
}, []); <Chip
className="capitalize "
return ( color={statusColorMap[article.status]}
<> size="lg"
<div className="mx-3 my-5"> variant="flat"
<Link href="/admin/magazine/create" > >
<Button className="my-3 bg-blue-600 text-white" ><CreateIconIon />Create New Magazine</Button> <div className="flex flex-row items-center gap-2 justify-center">
</Link> {cellValue}
<div className="flex flex-col items-center rounded-2xl">
<Table
// selectionMode="multiple"
aria-label="micro issue table"
className="rounded-xl"
classNames={{
th: "bg-white dark:bg-black text-black dark:text-white border-b-1 text-md",
base: "bg-white dark:bg-black border",
wrapper: "min-h-[50px] bg-transpararent text-black dark:text-white ",
}}
>
<TableHeader columns={columns}>
{(column) => (
<TableColumn key={column.uid}>{column.name}</TableColumn>
)}
</TableHeader>
<TableBody items={magazineTable} emptyContent={"No data to display."}>
{(item) => (
<TableRow key={item.id}>
{(columnKey) => (
<TableCell>{renderCell(item, columnKey)}</TableCell>
)}
</TableRow>
)}
</TableBody>
</Table>
</div>
</div> </div>
</Chip>
);
case "createdAt":
return <p>{convertDateFormat(article.createdAt)}</p>;
</> case "actions":
); return (
<div className="relative flex justify-star items-center gap-2">
<Dropdown className="lg:min-w-[150px] bg-black text-white shadow border ">
<DropdownTrigger>
<Button isIconOnly size="lg" variant="light">
<DotsYIcon className="text-default-300" />
</Button>
</DropdownTrigger>
<DropdownMenu>
<DropdownItem>
<Link href={`/admin/article/detail/${article.id}`}>
<EyeIconMdi className="inline mr-2 mb-1" />
Detail
</Link>
</DropdownItem>
<DropdownItem>
<Link href={`/admin/article/edit/${article.id}`}>
<CreateIconIon className="inline mr-2 mb-1" />
Edit
</Link>
</DropdownItem>
<DropdownItem onClick={() => handleDelete(article.id)}>
<DeleteIcon
color="red"
width={20}
height={16}
className="inline mr-2 mb-1"
/>
Delete
</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
);
default:
return cellValue;
}
}, []);
let typingTimer: NodeJS.Timeout;
const doneTypingInterval = 1500;
const handleKeyUp = () => {
clearTimeout(typingTimer);
typingTimer = setTimeout(doneTyping, doneTypingInterval);
};
const handleKeyDown = () => {
clearTimeout(typingTimer);
};
async function doneTyping() {
initState();
}
return (
<>
<div className="p-3">
<div className="flex flex-col items-start rounded-2xl gap-3">
<div className="flex flex-col md:flex-row gap-3 w-full">
<div className="flex flex-col gap-1 w-1/3">
<p className="font-semibold text-sm">Pencarian</p>
<Input
aria-label="Search"
classNames={{
inputWrapper: "bg-default-100",
input: "text-sm",
}}
labelPlacement="outside"
startContent={
<SearchIcon className="text-base text-default-400 pointer-events-none flex-shrink-0" />
}
type="text"
onChange={(e) => setSearch(e.target.value)}
onKeyUp={handleKeyUp}
onKeyDown={handleKeyDown}
/>
</div>
<div className="flex flex-col gap-1 w-[72px]">
<p className="font-semibold text-sm">Data</p>
<Select
label=""
variant="bordered"
labelPlacement="outside"
placeholder="Select"
selectedKeys={[showData]}
className="w-full"
classNames={{ trigger: "border-1" }}
onChange={(e) =>
e.target.value === "" ? "" : setShowData(e.target.value)
}
>
<SelectItem key="5" value="5">
5
</SelectItem>
<SelectItem key="10" value="10">
10
</SelectItem>
</Select>
</div>
<div className="flex flex-col gap-1 w-[230px]">
<p className="font-semibold text-sm">Kategori</p>
<Select
label=""
variant="bordered"
labelPlacement="outside"
placeholder="Select"
selectionMode="multiple"
selectedKeys={[selectedCategories]}
className="w-full"
classNames={{ trigger: "border-1" }}
onChange={(e) => {
e.target.value === ""
? ""
: setSelectedCategories(e.target.value);
console.log("eeess", e.target.value);
}}
>
{categories?.map((category: any) => (
<SelectItem key={category?.id} value={category?.id}>
{category?.title}
</SelectItem>
))}
</Select>
</div>
<div className="flex flex-col gap-1 w-full md:w-[240px]">
<p className="font-semibold text-sm">Tanggal</p>
<Datepicker
value={startDateValue}
displayFormat="DD/MM/YYYY"
onChange={(e: any) => setStartDateValue(e)}
inputClassName="z-50 w-full text-sm bg-transparent border-1 border-gray-200 px-2 py-[6px] rounded-xl h-[40px] text-gray-600 dark:text-gray-300"
/>
</div>
</div>
<Table
aria-label="micro issue table"
className="rounded-3xl"
classNames={{
th: "bg-white dark:bg-black text-black dark:text-white border-b-1 text-md",
base: "bg-white dark:bg-black border",
wrapper:
"min-h-[50px] bg-transpararent text-black dark:text-white ",
}}
>
<TableHeader columns={columns}>
{(column) => (
<TableColumn key={column.uid}>{column.name}</TableColumn>
)}
</TableHeader>
<TableBody
items={article}
emptyContent={"No data to display."}
loadingContent={<Spinner label="Loading..." />}
>
{(item) => (
<TableRow key={item.id}>
{(columnKey) => (
<TableCell>{renderCell(item, columnKey)}</TableCell>
)}
</TableRow>
)}
</TableBody>
</Table>
<div className="my-2 w-full flex justify-center">
<Pagination
isCompact
showControls
showShadow
color="primary"
classNames={{
base: "bg-transparent",
wrapper: "bg-transparent",
}}
page={page}
total={totalPage}
onChange={(page) => setPage(page)}
/>
</div>
</div>
</div>
</>
);
} }

View File

@ -1,3 +1,4 @@
import { PaginationRequest } from "@/types/globals";
import { import {
httpDeleteInterceptor, httpDeleteInterceptor,
httpGet, httpGet,
@ -16,3 +17,16 @@ export async function createMagazine(data: any) {
const pathUrl = `/magazines`; const pathUrl = `/magazines`;
return await httpPost(pathUrl, headers, data); return await httpPost(pathUrl, headers, data);
} }
export async function getListMagazine(props: PaginationRequest) {
const { page, limit, search, startDate, endDate } = props;
const headers = {
"content-type": "application/json",
};
return await httpGet(
`/magazines?limit=${limit}&page=${page}&title=${search}&startDate=${
startDate || ""
}&endDate=${endDate || ""}`,
headers
);
}