fix: landing magazine, article aiId, feat:setting form

This commit is contained in:
Rama Priyanto 2025-01-22 21:22:22 +07:00
parent 7ad85eb8a9
commit a738e61277
38 changed files with 1300 additions and 782 deletions

View File

@ -4,7 +4,7 @@ import { Card } from "@nextui-org/react";
export default function CreateArticle() {
return (
<div className="h-[96vh] bg-transparent p-8 !bg-slate-100 dark:!bg-black overflow-y-auto">
<div className="bg-transparent p-8 !bg-slate-100 dark:!bg-black overflow-y-auto">
{/* <FormArticle /> */}
<CreateArticleForm />
</div>

View File

@ -22,25 +22,14 @@ export default function BasicPage() {
return (
<div className="overflow-x-hidden overflow-y-scroll">
<div className="px-2 md:px-4 w-full">
<div className="rounded-md my-5 px-5 py-2 shadow-lg bg-white dark:bg-[#18181b] flex flex-row gap-3">
<Link href="/admin/article/create">
<div className="px-2 md:px-4 md:py-4 w-full">
<div className="bg-white shadow-lg dark:bg-[#18181b] rounded-xl py-3">
<Link href="/admin/article/create" className="mx-3">
<Button size="md" className="bg-[#F07C00] text-white">
New Article
Tambah Artikel
<AddIcon />
</Button>
</Link>
{/* <Button
size="md"
color="primary"
className="bg-[#F07C00] text-white"
onPress={goGenerate}
>
<AddIcon />
Generate Article
</Button> */}
</div>
<div className="bg-white shadow-lg dark:bg-[#18181b] rounded-xl p-2">
<ArticleTable />
</div>
</div>

View File

@ -1,39 +1,20 @@
"use client";
import { AddIcon } from "@/components/icons";
import ArticleTable from "@/components/table/article-table";
import MagazineTable from "@/components/table/magazine/magazine-table";
import generatedArticleIds from "@/store/generated-article-store";
import { Button, Card } from "@nextui-org/react";
import { Button } from "@nextui-org/react";
import Link from "next/link";
import { useRouter } from "next/navigation";
export default function MagazineTablePage() {
const router = useRouter();
const setGeneratedArticleIdStore = generatedArticleIds(
(state) => state.setArticleIds
);
return (
<div className="overflow-x-hidden overflow-y-scroll">
<div className="px-2 md:px-4 w-full">
<div className="rounded-md my-5 px-5 py-2 shadow-lg bg-white dark:bg-[#18181b] flex flex-row gap-3">
<Link href="/admin/magazine/create">
<div className="px-2 md:px-4 md:py-4 w-full">
<div className="bg-white shadow-lg dark:bg-[#18181b] rounded-xl py-3">
<Link href="/admin/magazine/create" className="mx-3">
<Button size="md" className="bg-[#F07C00] text-white">
Tambah Majalah
<AddIcon />
</Button>
</Link>
{/* <Button
size="md"
color="primary"
className="bg-[#F07C00] text-white"
onPress={goGenerate}
>
<AddIcon />
Generate Article
</Button> */}
</div>
<div className="bg-white shadow-lg dark:bg-[#18181b] rounded-xl p-2">
<MagazineTable />
</div>
</div>

View File

@ -163,218 +163,209 @@ export default function MasterCategoryTable() {
return (
<div className="overflow-x-hidden overflow-y-scroll">
<div className="px-2 md:px-4 w-full">
<div className="rounded-md my-5 px-5 py-2 shadow-lg bg-white dark:bg-[#18181b] flex flex-row gap-3">
<div className="px-2 md:px-4 md:py-4 w-full">
<div className="bg-white shadow-lg dark:bg-[#18181b] rounded-xl py-3">
<Button
size="md"
className="bg-[#F07C00] text-white"
className="bg-[#F07C00] text-white mx-3"
onPress={onOpen}
>
Tambah Kategori
<AddIcon />
</Button>
<Modal isOpen={isOpen} onOpenChange={onOpenChange} size="3xl">
<ModalContent>
{() => (
<>
<ModalHeader className="flex flex-col gap-1">
Kategori Baru
</ModalHeader>
<ModalBody>
<form
onSubmit={handleSubmit(onSubmit)}
className="flex flex-col gap-3"
>
<div className="flex flex-col gap-1">
<p className="text-sm">Nama Kategori</p>
<Controller
control={control}
name="title"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="title"
placeholder=""
label=""
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full "
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
variant="bordered"
/>
)}
/>
{errors?.title && (
<p className="text-red-400 text-sm">
{errors.title?.message}
</p>
)}
</div>
<div className="flex flex-col gap-1">
<p className="text-sm">Deskripsi</p>
<Controller
control={control}
name="description"
render={({ field: { onChange, value } }) => (
<Textarea
type="text"
id="description"
placeholder=""
label=""
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full "
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
variant="bordered"
/>
)}
/>
{errors?.title && (
<p className="text-red-400 text-sm">
{errors.title?.message}
</p>
)}
</div>
<div className="flex flex-col gap-1">
<p className="text-sm mt-3">Tag Terkait</p>
<Controller
control={control}
name="tags"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="tags"
placeholder=""
label=""
value={tag}
onValueChange={setTag}
startContent={
<div className="flex flex-row gap-1">
{value.map((item, index) => (
<Chip
color="primary"
key={index}
className=""
onClose={() => {
const filteredTags = value.filter(
(tag) => tag !== item
);
if (filteredTags.length === 0) {
setError("tags", {
type: "manual",
message: "Tags tidak boleh kosong",
});
} else {
clearErrors("tags");
setValue(
"tags",
filteredTags as [
string,
...string[]
]
);
}
}}
>
{item}
</Chip>
))}
</div>
}
onKeyDown={(e) => {
if (e.key === "Enter") {
if (tag.trim() !== "") {
setValue("tags", [...value, tag.trim()]);
setTag("");
e.preventDefault();
}
}
}}
labelPlacement="outside"
className="w-full h-fit"
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
variant="bordered"
/>
)}
/>
</div>
<div className="flex flex-col gap-1">
<p className="text-sm mt-3">Thumbnail</p>
{files.length < 1 && (
<Fragment>
<div {...getRootProps({ className: "dropzone" })}>
<input {...getInputProps()} />
<div className=" w-full text-center border-dashed border border-default-200 dark:border-default-300 rounded-md py-[52px] flex items-center flex-col">
<CloudUploadIcon />
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
Tarik file disini atau klik untuk upload.
</h4>
<div className=" text-xs text-muted-foreground">
( Upload file dengan format .jpg, .jpeg, atau
.png. Ukuran maksimal 100mb.)
</div>
</div>
</div>
</Fragment>
)}
{files.length > 0 && (
<div className="flex flex-row gap-2">
<img
src={URL.createObjectURL(files[0])}
className="w-[30%]"
alt="thumbnail"
/>
<Button
className=" border-none rounded-full"
variant="bordered"
onClick={() => handleRemoveFile(files[0])}
>
<TimesIcon />
</Button>
</div>
)}
</div>
<ModalFooter className="self-end grow items-end">
<Button color="primary" type="submit">
Simpan
</Button>
<Button
color="danger"
variant="light"
onPress={onClose}
>
Tutup
</Button>
</ModalFooter>
</form>
</ModalBody>
</>
)}
</ModalContent>
</Modal>
</div>
<div className="bg-white shadow-lg dark:bg-[#18181b] rounded-xl p-2">
<CategoriesTable triggerRefresh={refresh} />
</div>
</div>
<Modal isOpen={isOpen} onOpenChange={onOpenChange} size="3xl">
<ModalContent>
{() => (
<>
<ModalHeader className="flex flex-col gap-1">
Kategori Baru
</ModalHeader>
<ModalBody>
<form
onSubmit={handleSubmit(onSubmit)}
className="flex flex-col gap-3"
>
<div className="flex flex-col gap-1">
<p className="text-sm">Nama Kategori</p>
<Controller
control={control}
name="title"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="title"
placeholder=""
label=""
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full "
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
variant="bordered"
/>
)}
/>
{errors?.title && (
<p className="text-red-400 text-sm">
{errors.title?.message}
</p>
)}
</div>
<div className="flex flex-col gap-1">
<p className="text-sm">Deskripsi</p>
<Controller
control={control}
name="description"
render={({ field: { onChange, value } }) => (
<Textarea
type="text"
id="description"
placeholder=""
label=""
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full "
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
variant="bordered"
/>
)}
/>
{errors?.title && (
<p className="text-red-400 text-sm">
{errors.title?.message}
</p>
)}
</div>
<div className="flex flex-col gap-1">
<p className="text-sm mt-3">Tag Terkait</p>
<Controller
control={control}
name="tags"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="tags"
placeholder=""
label=""
value={tag}
onValueChange={setTag}
startContent={
<div className="flex flex-row gap-1">
{value.map((item, index) => (
<Chip
color="primary"
key={index}
className=""
onClose={() => {
const filteredTags = value.filter(
(tag) => tag !== item
);
if (filteredTags.length === 0) {
setError("tags", {
type: "manual",
message: "Tags tidak boleh kosong",
});
} else {
clearErrors("tags");
setValue(
"tags",
filteredTags as [string, ...string[]]
);
}
}}
>
{item}
</Chip>
))}
</div>
}
onKeyDown={(e) => {
if (e.key === "Enter") {
if (tag.trim() !== "") {
setValue("tags", [...value, tag.trim()]);
setTag("");
e.preventDefault();
}
}
}}
labelPlacement="outside"
className="w-full h-fit"
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
variant="bordered"
/>
)}
/>
</div>
<div className="flex flex-col gap-1">
<p className="text-sm mt-3">Thumbnail</p>
{files.length < 1 && (
<Fragment>
<div {...getRootProps({ className: "dropzone" })}>
<input {...getInputProps()} />
<div className=" w-full text-center border-dashed border border-default-200 dark:border-default-300 rounded-md py-[52px] flex items-center flex-col">
<CloudUploadIcon />
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
Tarik file disini atau klik untuk upload.
</h4>
<div className=" text-xs text-muted-foreground">
( Upload file dengan format .jpg, .jpeg, atau
.png. Ukuran maksimal 100mb.)
</div>
</div>
</div>
</Fragment>
)}
{files.length > 0 && (
<div className="flex flex-row gap-2">
<img
src={URL.createObjectURL(files[0])}
className="w-[30%]"
alt="thumbnail"
/>
<Button
className=" border-none rounded-full"
variant="bordered"
onClick={() => handleRemoveFile(files[0])}
>
<TimesIcon />
</Button>
</div>
)}
</div>
<ModalFooter className="self-end grow items-end">
<Button color="primary" type="submit">
Simpan
</Button>
<Button color="danger" variant="light" onPress={onClose}>
Tutup
</Button>
</ModalFooter>
</form>
</ModalBody>
</>
)}
</ModalContent>
</Modal>
</div>
);
}

View File

@ -6,23 +6,21 @@ import Link from "next/link";
export default function MasterRolePage() {
return (
<div className="h-[96vh] overflow-x-hidden overflow-y-scroll gap-0 grid rounded-lg">
<div className="px-4">
<Card className="rounded-md my-5 pl-5 py-2">
<Link href="/admin/master-role/create">
<div className="overflow-x-hidden overflow-y-scroll">
<div className="px-2 md:px-4 md:py-4 w-full">
<div className="bg-white shadow-lg dark:bg-[#18181b] rounded-xl py-3">
<Link href="/admin/master-role/create" className="mx-3">
<Button
size="md"
color="primary"
className="bg-[#F07C00] text-white"
>
Peran Baru
<AddIcon />
New Role
</Button>
</Link>
</Card>
<Card className="rounded-md my-5">
<MasterRoleTable />
</Card>
</div>
</div>
</div>
);

View File

@ -6,23 +6,21 @@ import Link from "next/link";
export default function MasterUserPage() {
return (
<div className="h-[96vh] overflow-x-hidden overflow-y-scroll gap-0 grid rounded-lg">
<div className="px-4">
<Card className="rounded-md my-5 pl-5 py-2">
<Link href="/admin/master-user/create">
<div className="overflow-x-hidden overflow-y-scroll">
<div className="px-2 md:px-4 md:py-4 w-full">
<div className="bg-white shadow-lg dark:bg-[#18181b] rounded-xl py-3">
<Link href="/admin/master-user/create" className="mx-3">
<Button
size="md"
color="primary"
className="bg-[#F07C00] text-white"
>
Pengguna Baru
<AddIcon />
New User
</Button>
</Link>
</Card>
<Card className="rounded-md my-5">
<MasterUserTable />
</Card>
</div>
</div>
</div>
);

View File

@ -6,20 +6,18 @@ import Link from "next/link";
export default function StaticPageGeneratorList() {
return (
<div className="overflow-x-hidden overflow-y-scroll rounded-lg">
<div className="px-2 md:px-4 w-full">
<div className="rounded-md mt-4 px-5 py-2 bg-white dark:bg-[#18181b] flex flex-row gap-3 shadow-lg">
<Link href="/admin/static-page/create">
<div className="px-2 md:px-4 md:py-4 w-full">
<div className="bg-white shadow-lg dark:bg-[#18181b] rounded-xl py-3">
<Link href="/admin/static-page/create" className="mx-3">
<Button
size="md"
color="primary"
className="bg-[#F07C00] text-white"
>
Tambah Halaman
<AddIcon />
Create Page
</Button>
</Link>
</div>
<div className="bg-white shadow-lg dark:bg-[#18181b] rounded-xl p-2 mt-4">
<StaticPageTable />
</div>
</div>

View File

@ -0,0 +1,15 @@
"use client";
import { AdminLayout } from "@/components/layout/admin-layout";
export default function AdminPageLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<AdminLayout>
{children}
</AdminLayout>
);
}

View File

@ -0,0 +1,21 @@
"use client";
import PasswordForm from "@/components/form/settings/password";
import ProfileForm from "@/components/form/settings/profile";
import { Tab, Tabs } from "@nextui-org/react";
export default function Settings() {
return (
<div className="w-full lg:w-[60%] p-5">
<div className="flex flex-col bg-gray-100 dark:bg-stone-900 text-black dark:text-white rounded-md p-5">
<Tabs aria-label="Tabs radius" radius="sm">
<Tab key="profile" title="Profile">
<ProfileForm />
</Tab>
<Tab key="music" title="Password">
<PasswordForm />
</Tab>
</Tabs>
</div>
</div>
);
}

View File

@ -1,12 +1,22 @@
"use client";
import { HumasLayout } from "@/components/layout/humas-layout";
import ListEnewsPolri from "@/components/table/tabel-emajalah-polri";
import React from "react";
import React, { Suspense, useEffect, useState } from "react";
export default function ListEnewsPage() {
const [hasMounted, setHasMounted] = useState(false);
useEffect(() => {
setHasMounted(true);
}, []);
// Render
if (!hasMounted) return null;
return (
<HumasLayout>
<ListEnewsPolri />
<Suspense>
<ListEnewsPolri />
</Suspense>
</HumasLayout>
);
}

View File

@ -1,12 +1,22 @@
"use client";
import { HumasLayout } from "@/components/layout/humas-layout";
import EMagazineDetail from "@/components/main/detail/e-magazine-detail";
import React from "react";
import React, { Suspense, useEffect, useState } from "react";
export default function EnewsDetailPage() {
const [hasMounted, setHasMounted] = useState(false);
useEffect(() => {
setHasMounted(true);
}, []);
// Render
if (!hasMounted) return null;
return (
<HumasLayout>
<EMagazineDetail />
<Suspense>
<EMagazineDetail />
</Suspense>
</HumasLayout>
);
}

View File

@ -64,13 +64,13 @@ export default function RootLayout({ children }: { children: ReactNode }) {
</head>
<body
className={clsx(
"bg-background font-sans antialiased",
"bg-background font-sans antialiased overflow-hidden",
fontSans.variable
)}
>
<NextIntlClientProvider locale={localeNow} messages={messages}>
<Providers themeProps={{ attribute: "class", defaultTheme: "dark" }}>
<main className="">{children}</main>
<main className="overflow-y-auto">{children}</main>
</Providers>
</NextIntlClientProvider>
</body>

View File

@ -15,13 +15,27 @@ function CustomEditor(props) {
props.onChange(data);
}}
config={{
toolbar: [ 'heading', 'fontsize', 'bold', 'italic', 'link', 'numberedList', 'bulletedList', 'undo', 'redo', 'alignment', 'outdent', 'indent', 'blockQuote', 'insertTable', 'codeBlock', 'sourceEditing']
toolbar: [
"heading",
"fontsize",
"bold",
"italic",
"link",
"numberedList",
"bulletedList",
"undo",
"redo",
"alignment",
"outdent",
"indent",
"blockQuote",
"insertTable",
"codeBlock",
"sourceEditing",
],
}}
/>
);
}
export default CustomEditor;

View File

@ -28,7 +28,10 @@ import { close, error, loading } from "@/config/swal";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { getCategoryById } from "@/service/master-categories";
import { saveManualContext } from "@/service/generate-article";
import {
saveManualContext,
updateManualArticle,
} from "@/service/generate-article";
const CustomEditor = dynamic(
() => {
@ -52,6 +55,17 @@ const categorySchema = z.object({
value: z.number(),
});
interface DiseData {
id: number;
articleBody: string;
title: string;
metaTitle: string;
description: string;
metaDescription: string;
mainKeyword: string;
additionalKeywords: string;
}
const createArticleSchema = z.object({
title: z.string().min(2, {
message: "Judul harus diisi",
@ -81,6 +95,7 @@ export default function CreateArticleForm() {
const [tag, setTag] = useState("");
const [thumbnailImg, setThumbnailImg] = useState<File[]>([]);
const [selectedMainImage, setSelectedMainImage] = useState<number>();
const [diseData, setDiseData] = useState<DiseData>();
const { getRootProps, getInputProps } = useDropzone({
onDrop: (acceptedFiles) => {
@ -148,6 +163,12 @@ export default function CreateArticleForm() {
});
};
useEffect(() => {
if (useAi === false) {
setValue("description", "");
}
}, [useAi]);
function removeImgTags(htmlString: string) {
const parser = new DOMParser();
const doc = parser.parseFromString(String(htmlString), "text/html");
@ -160,26 +181,50 @@ export default function CreateArticleForm() {
const saveArticleToDise = async (
values: z.infer<typeof createArticleSchema>
) => {
const request = {
title: values.title,
articleBody: removeImgTags(values.description),
metaDescription: values.title,
metaTitle: values.title,
mainKeyword: values.title,
additionalKeywords: values.title,
createdBy: "345",
style: "Informational",
projectId: 2,
clientId: "humasClientIdtest",
lang: "id",
};
if (useAi) {
const request = {
id: diseData?.id,
title: values.title,
articleBody: removeImgTags(values.description),
metaDescription: diseData?.metaDescription,
metaTitle: diseData?.metaTitle,
mainKeyword: diseData?.mainKeyword,
additionalKeywords: diseData?.additionalKeywords,
createdBy: "345",
style: "Informational",
projectId: 2,
clientId: "humasClientIdtest",
lang: "id",
};
const res = await updateManualArticle(request);
if (res.error) {
error(res.message);
return false;
}
const res = await saveManualContext(request);
if (res.error) {
res.message;
return 0;
return diseData?.id;
} else {
const request = {
title: values.title,
articleBody: removeImgTags(values.description),
metaDescription: values.title,
metaTitle: values.title,
mainKeyword: values.title,
additionalKeywords: values.title,
createdBy: "345",
style: "Informational",
projectId: 2,
clientId: "humasClientIdtest",
lang: "id",
};
const res = await saveManualContext(request);
if (res.error) {
res.message;
return 0;
}
return res?.data?.data?.id;
}
return res?.data?.data?.id;
};
const save = async (values: z.infer<typeof createArticleSchema>) => {
@ -192,7 +237,7 @@ export default function CreateArticleForm() {
tags: values.tags.join(","),
description: htmlToString(removeImgTags(values.description)),
htmlDescription: removeImgTags(values.description),
// aiArticleId: saveArticleToDise(values),
aiArticleId: await saveArticleToDise(values),
};
const response = await createArticle(formData);
@ -418,7 +463,13 @@ export default function CreateArticleForm() {
{useAi && (
<GenerateSingleArticleForm
content={(data) => setValue("description", data)}
content={(data) => {
setDiseData(data);
setValue(
"description",
data?.articleBody ? data?.articleBody : ""
);
}}
/>
)}
@ -427,18 +478,7 @@ export default function CreateArticleForm() {
control={control}
name="description"
render={({ field: { onChange, value } }) => (
// <CustomEditor onChange={onChange} initialData={value} />
// <JoditEditor
// ref={editor}
// value={value}
// onChange={onChange}
// className="dark:text-black"
// />
<CustomEditor
onChange={onChange}
initialData={value}
/>
<CustomEditor onChange={onChange} initialData={value} />
)}
/>
{errors?.description && (

View File

@ -79,6 +79,17 @@ const createArticleSchema = z.object({
}), // Array berisi string
});
interface DiseData {
id: number;
articleBody: string;
title: string;
metaTitle: string;
description: string;
metaDescription: string;
mainKeyword: string;
additionalKeywords: string;
}
export default function EditArticleForm(props: { isDetail: boolean }) {
const { isDetail } = props;
const params = useParams();
@ -94,6 +105,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
const [detailfiles, setDetailFiles] = useState<any>([]);
const [mainImage, setMainImage] = useState(0);
const [thumbnail, setThumbnail] = useState("");
const [diseId, setDiseId] = useState(0);
const { getRootProps, getInputProps } = useDropzone({
onDrop: (acceptedFiles) => {
@ -137,6 +149,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
setValue("description", data?.htmlDescription);
setValue("tags", data?.tags ? data.tags.split(",") : []);
setThumbnail(data?.thumbnailUrl);
setDiseId(data?.aiArticleId);
setDetailFiles(data?.files);
setupInitCategory(data?.categories);
@ -205,7 +218,6 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
description: htmlToString(values.description),
htmlDescription: values.description,
};
console.log("vals", formData);
const response = await updateArticle(String(id), formData);
if (response?.error) {
@ -345,7 +357,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
onSubmit={handleSubmit(onSubmit)}
>
<div className="w-[65%] bg-white rounded-lg p-8 flex flex-col gap-1">
{isDetail && <GetSeoScore id="1961" />}
{isDetail && <GetSeoScore id={String(diseId)} />}
<p className="text-sm">Judul</p>
<Controller
control={control}
@ -409,7 +421,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
{useAi && (
<GenerateSingleArticleForm
content={(data) => setValue("description", data)}
content={(data) => setValue("description", data?.articleBody)}
/>
)}
@ -417,7 +429,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
<Controller
control={control}
name="description"
render={({ field: { onChange, value } }) => (
render={({ field: { onChange, value } }) =>
// <CustomEditor onChange={onChange} initialData={value} />
// <JoditEditor
// ref={editor}
@ -426,16 +438,12 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
// config={{ readonly: isDetail }}
// className="dark:text-black"
// />
isDetail ?
<ViewEditor
initialData={value}
/>
:
<CustomEditor
onChange={onChange}
initialData={value}
/>
)}
isDetail ? (
<ViewEditor initialData={value} />
) : (
<CustomEditor onChange={onChange} initialData={value} />
)
}
/>
{errors?.description && (
<p className="text-red-400 text-sm mb-3">

View File

@ -60,8 +60,19 @@ const articleSize = [
},
];
interface DiseData {
id: number;
articleBody: string;
title: string;
metaTitle: string;
description: string;
metaDescription: string;
mainKeyword: string;
additionalKeywords: string;
}
export default function GenerateSingleArticleForm(props: {
content: (data: string) => void;
content: (data: DiseData) => void;
}) {
const [selectedWritingSyle, setSelectedWritingStyle] =
useState("Informational");
@ -162,14 +173,23 @@ export default function GenerateSingleArticleForm(props: {
const getArticleDetail = async () => {
if (selectedId) {
const res = await getDetailArticle(selectedId);
const data = res?.data?.data?.articleBody;
checkArticleStatus(data);
if (data !== null) {
const data = res?.data?.data;
checkArticleStatus(data?.articleBody);
if (data?.articleBody !== null) {
setIsLoading(false);
props.content(data);
} else {
setIsLoading(true);
props.content("");
props.content({
id: data?.id,
articleBody: "",
title: "",
metaTitle: "",
description: "",
metaDescription: "",
additionalKeywords: "",
mainKeyword: "",
});
}
}
};

View File

@ -8,7 +8,7 @@ import { useEffect, useRef, useState } from "react";
export default function GetSeoScore(props: { id: string }) {
useEffect(() => {
fetchSeoScore();
}, []);
}, [props.id]);
const [totalScoreSEO, setTotalScoreSEO] = useState();
const [errorSEO, setErrorSEO] = useState<any>([]);
@ -16,9 +16,9 @@ export default function GetSeoScore(props: { id: string }) {
const [optimizedSEO, setOptimizedSEO] = useState<any>([]);
const fetchSeoScore = async () => {
const res = await getSeoScore("1931");
const res = await getSeoScore(props?.id);
if (res.error) {
error(res.message);
// error(res.message);
return false;
}
setTotalScoreSEO(res.data.data?.seo_analysis?.score || 0);
@ -42,97 +42,103 @@ export default function GetSeoScore(props: { id: string }) {
<div className="overflow-y-auto my-2">
<div className="text-black flex flex-col rounded-md gap-3">
<p className="font-semibold text-lg"> SEO Score</p>
<div className="flex flex-row gap-5 w-full">
<CircularProgress
aria-label=""
color="warning"
showValueLabel={true}
size="lg"
value={Number(totalScoreSEO) * 100}
/>
<div>
{/* <ApexChartDonut value={Number(totalScoreSEO) * 100} /> */}
</div>
<div className="flex flex-row gap-5">
<div className="px-2 py-1 border radius-md flex flex-row gap-2 items-center border-red-500 rounded-lg">
{/* <TimesIcon size={15} className="text-danger" /> */}
Error : {errorSEO.length || 0}
{totalScoreSEO ? (
<div className="flex flex-row gap-5 w-full">
<CircularProgress
aria-label=""
color="warning"
showValueLabel={true}
size="lg"
value={Number(totalScoreSEO) * 100}
/>
<div>
{/* <ApexChartDonut value={Number(totalScoreSEO) * 100} /> */}
</div>
<div className="px-2 py-1 border radius-md flex flex-row gap-2 items-center border-yellow-500 rounded-lg">
{/* <p className="text-warning w-[15px] h-[15px] text-center mt-[-10px]">
<div className="flex flex-row gap-5">
<div className="px-2 py-1 border radius-md flex flex-row gap-2 items-center border-red-500 rounded-lg">
{/* <TimesIcon size={15} className="text-danger" /> */}
Error : {errorSEO.length || 0}
</div>
<div className="px-2 py-1 border radius-md flex flex-row gap-2 items-center border-yellow-500 rounded-lg">
{/* <p className="text-warning w-[15px] h-[15px] text-center mt-[-10px]">
!
</p> */}
Warning : {warningSEO.length || 0}
</div>
<div className="px-2 py-1 border radius-md flex flex-row gap-2 items-center border-green-500 rounded-lg">
{/* <CheckIcon size={15} className="text-success" /> */}
Optimize : {optimizedSEO.length || 0}
Warning : {warningSEO.length || 0}
</div>
<div className="px-2 py-1 border radius-md flex flex-row gap-2 items-center border-green-500 rounded-lg">
{/* <CheckIcon size={15} className="text-success" /> */}
Optimize : {optimizedSEO.length || 0}
</div>
</div>
</div>
</div>
<Accordion
variant="splitted"
itemClasses={{
base: "!bg-transparent",
title: "text-black",
}}
>
<AccordionItem
key="1"
aria-label="Error"
// startContent={<TimesIcon size={20} className="text-danger" />}
title={`${errorSEO?.length || 0} Errors`}
) : (
"Belum ada Data"
)}
{totalScoreSEO && (
<Accordion
variant="splitted"
itemClasses={{
base: "!bg-transparent",
title: "text-black",
}}
>
<div className="flex flex-col gap-2">
{errorSEO?.map((item: any) => (
<p
key={item}
className="w-full border border-red-500 rounded-md h-[40px] text-left flex flex-col justify-center px-3"
>
{item}
</p>
))}
</div>
</AccordionItem>
<AccordionItem
key="2"
aria-label="Warning"
// startContent={
// <p className="text-warning w-[20px] h-[20px] text-center mt-[-10px]">
// !
// </p>
// }
title={`${warningSEO?.length || 0} Warnings`}
>
<div className="flex flex-col gap-2">
{warningSEO?.map((item: any) => (
<p
key={item}
className="w-full border border-yellow-500 rounded-md h-[40px] text-left flex flex-col justify-center px-3"
>
{item}
</p>
))}
</div>
</AccordionItem>
<AccordionItem
key="3"
aria-label="Optimized"
// startContent={<CheckIcon size={20} className="text-success" />}
title={`${optimizedSEO?.length || 0} Optimized`}
>
<div className="flex flex-col gap-2">
{optimizedSEO?.map((item: any) => (
<p
key={item}
className="w-full border border-green-500 rounded-md h-[40px] text-left flex flex-col justify-center px-3"
>
{item}
</p>
))}
</div>
</AccordionItem>
</Accordion>
<AccordionItem
key="1"
aria-label="Error"
// startContent={<TimesIcon size={20} className="text-danger" />}
title={`${errorSEO?.length || 0} Errors`}
>
<div className="flex flex-col gap-2">
{errorSEO?.map((item: any) => (
<p
key={item}
className="w-full border border-red-500 rounded-md h-[40px] text-left flex flex-col justify-center px-3"
>
{item}
</p>
))}
</div>
</AccordionItem>
<AccordionItem
key="2"
aria-label="Warning"
// startContent={
// <p className="text-warning w-[20px] h-[20px] text-center mt-[-10px]">
// !
// </p>
// }
title={`${warningSEO?.length || 0} Warnings`}
>
<div className="flex flex-col gap-2">
{warningSEO?.map((item: any) => (
<p
key={item}
className="w-full border border-yellow-500 rounded-md h-[40px] text-left flex flex-col justify-center px-3"
>
{item}
</p>
))}
</div>
</AccordionItem>
<AccordionItem
key="3"
aria-label="Optimized"
// startContent={<CheckIcon size={20} className="text-success" />}
title={`${optimizedSEO?.length || 0} Optimized`}
>
<div className="flex flex-col gap-2">
{optimizedSEO?.map((item: any) => (
<p
key={item}
className="w-full border border-green-500 rounded-md h-[40px] text-left flex flex-col justify-center px-3"
>
{item}
</p>
))}
</div>
</AccordionItem>
</Accordion>
)}
</div>
</div>
);

View File

@ -101,116 +101,7 @@ export default function Login() {
};
return (
// <HumasLayout>
// <div className="bg-white text-black md:flex px-0 md:px-2 lg:px-5">
// <div className="w-auto md:w-1/2 p-2 md:p-5 lg:p-10 space-y-5">
// <div className="text-xl font-bold">
// Selamat Datang di Portal Resmi Humas Polri
// </div>
// <div>
// <Input
// classNames={{
// input: ["w-full", "bg-transparent", "!text-black"],
// mainWrapper: ["w-full", "bg-transparent"],
// innerWrapper: ["bg-transparent"],
// label: ["!text-black", "font-semibold"],
// inputWrapper: [
// "bg-transparent",
// "dark:bg-transparent",
// "hover:bg-transparent",
// "dark:hover:bg-transparent",
// "group-data-[focused=true]:bg-transparent",
// "dark:group-data-[focused=true]:bg-transaparent",
// "group-data-[focused=false]:bg-transparent",
// "focus-within:!bg-transparent",
// ],
// }}
// isRequired
// type="text"
// label="Username"
// placeholder="Masukkan username anda!"
// variant="underlined"
// onChange={(e: any) => {
// setValUsername(e.target.value.trim());
// }}
// onPaste={(e: any) => {
// setValUsername(e.target.value.trim());
// }}
// onCopy={(e: any) => {
// setValUsername(e.target.value.trim());
// }}
// />
// </div>
// <div>
// <Input
// classNames={{
// input: ["w-full", "bg-transparent", "!text-black"],
// mainWrapper: ["w-full", "bg-transparent"],
// innerWrapper: ["bg-transparent"],
// label: ["!text-black", "font-semibold"],
// inputWrapper: [
// "bg-transparent",
// "dark:bg-transparent",
// "hover:bg-transparent",
// "dark:hover:bg-transparent",
// "group-data-[focused=true]:bg-transparent",
// "dark:group-data-[focused=true]:bg-transaparent",
// "group-data-[focused=false]:bg-transparent",
// "focus-within:!bg-transparent",
// ],
// }}
// isRequired
// endContent={
// <button
// className="focus:outline-none"
// type="button"
// onClick={toggleVisibility}
// >
// {isVisible ? (
// <EyeSlashFilledIcon className="text-2xl text-default-400 pointer-events-none" />
// ) : (
// <EyeFilledIcon className="text-2xl text-default-400 pointer-events-none" />
// )}
// </button>
// }
// type={isVisible ? "text" : "password"}
// label="Password"
// placeholder="Masukkan password anda"
// variant="underlined"
// onChange={(event) => setPassword(event.target.value)}
// />
// </div>
// <div>
// <Button
// size="lg"
// className="w-full bg-[#DD8306] rounded-md font-semibold"
// onPress={onSubmit}
// >
// Login
// </Button>
// </div>
// <div className="flex justify-center text-xs font-medium py-3">
// Don&apos;t have account? Register Now
// </div>
// <div>
// <Link href={`/form-permohonan-informasi`}>
// <Button
// size="lg"
// variant="bordered"
// className="w-full text-[#DD8306] borde-2 border-[#DD8306] rounded-md font-semibold"
// >
// Register
// </Button>
// </Link>
// </div>
// </div>
// <div className="hidden sm:flex w-1/2 items-center justify-center p-10">
// <img src="/login.png" alt="logo" />
// </div>
// </div>
// </HumasLayout>
<div className="flex flex-row">
<div className="flex flex-row h-screen">
<div
style={{
backgroundImage: "url(headerbanner1.png)",
@ -220,9 +111,11 @@ export default function Login() {
}}
className="h-screen hidden md:block md:w-3/5"
>
<img src="divhumas.png" className="w-[120px]" />
<Link href="/">
<img src="divhumas.png" className="w-[120px]" />
</Link>
</div>
<div className="bg-[#1F1A17] w-2/5 md:px-24 md:py-36 flex flex-col">
<div className="bg-[#1F1A17] w-full md:w-2/5 p-8 md:px-24 md:py-36 flex flex-col">
<p className="text-[72px] text-[#DD8306] font-semibold mb-10">Login</p>
<p className="my-2 text-white">Username</p>
<Input

View File

@ -35,12 +35,12 @@ import {
} from "@/components/icons/globals";
import { createMagazine, uploadMagazineFile } from "@/service/magazine";
// const CustomEditor = dynamic(
// () => {
// return import("@/components/editor/custom-editor");
// },
// { ssr: false }
// );
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
interface FileWithPreview extends File {
preview: string;
@ -375,13 +375,7 @@ export default function NewCreateMagazineForm() {
control={control}
name="description"
render={({ field: { onChange, value } }) => (
// <CustomEditor onChange={onChange} initialData={value} />
<JoditEditor
ref={editor}
value={value}
onChange={onChange}
className="dark:text-black"
/>
<CustomEditor onChange={onChange} initialData={value} />
)}
/>
{errors?.description && (

View File

@ -42,12 +42,12 @@ import {
uploadMagazineFile,
} from "@/service/magazine";
// const CustomEditor = dynamic(
// () => {
// return import("@/components/editor/custom-editor");
// },
// { ssr: false }
// );
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
interface FileWithPreview extends File {
preview: string;
@ -445,14 +445,7 @@ export default function EditMagazineForm(props: { isDetail: boolean }) {
control={control}
name="description"
render={({ field: { onChange, value } }) => (
// <CustomEditor onChange={onChange} initialData={value} />
<JoditEditor
ref={editor}
value={value}
config={{ readonly: isDetail }}
onChange={onChange}
className="dark:text-black"
/>
<CustomEditor onChange={onChange} initialData={value} />
)}
/>
{errors?.description && (

View File

@ -0,0 +1,108 @@
"use client";
import { FormEvent, Fragment, useEffect, useRef, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import * as z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { Input, Textarea } from "@nextui-org/input";
const formSchema = z.object({
password: z.string().min(2, {
message: "Judul harus diisi",
}),
passwordConf: z.string().min(2, {
message: "Slug harus diisi",
}),
});
export default function PasswordForm() {
const MySwal = withReactContent(Swal);
const formOptions = {
resolver: zodResolver(formSchema),
};
type UserSettingSchema = z.infer<typeof formSchema>;
const {
register,
control,
handleSubmit,
formState: { errors },
setValue,
getValues,
watch,
setError,
clearErrors,
} = useForm<UserSettingSchema>(formOptions);
const onSubmit = async (values: z.infer<typeof formSchema>) => {
console.log("values");
};
return (
<form className="flex flex-col gap-3 " onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-col gap-1">
<p className="text-sm">Password</p>
<Controller
control={control}
name="password"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="password"
placeholder=""
label=""
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full "
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
variant="bordered"
/>
)}
/>
{errors?.password && (
<p className="text-red-400 text-sm mb-3">
{errors.password?.message}
</p>
)}
</div>
<div className="flex flex-col gap-1">
<p className="text-sm">Konfirmasi Password</p>
<Controller
control={control}
name="passwordConf"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="passwordConf"
placeholder=""
label=""
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full "
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
variant="bordered"
/>
)}
/>
{errors?.passwordConf && (
<p className="text-red-400 text-sm mb-3">
{errors.passwordConf?.message}
</p>
)}
</div>
</form>
);
}

View File

@ -0,0 +1,212 @@
"use client";
import { FormEvent, Fragment, useEffect, useRef, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import * as z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { Input, Textarea } from "@nextui-org/input";
import { Button } from "@nextui-org/button";
import { Radio, RadioGroup } from "@nextui-org/react";
const formSchema = z.object({
fullname: z.string().min(1, {
message: "Harus diisi",
}),
username: z.string().min(1, {
message: "Harus diisi",
}),
email: z
.string()
.email({
message: "Email tidak valid",
})
.min(2, {
message: "Harus diisi",
}),
nrp: z.string().min(1, {
message: "Harus diisi",
}),
gender: z.string().min(1, {
message: "Harus diisi",
}),
phoneNumber: z.string().min(1, {
message: "Harus diisi",
}),
});
export default function ProfileForm() {
const MySwal = withReactContent(Swal);
const formOptions = {
resolver: zodResolver(formSchema),
};
type UserSettingSchema = z.infer<typeof formSchema>;
const {
register,
control,
handleSubmit,
formState: { errors },
setValue,
getValues,
watch,
setError,
clearErrors,
} = useForm<UserSettingSchema>(formOptions);
const onSubmit = async (values: z.infer<typeof formSchema>) => {
console.log("values");
};
return (
<form className="flex flex-col gap-3 " onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-col gap-1">
<p className="text-sm">Nama Lengkap</p>
<Controller
control={control}
name="fullname"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="fullname"
placeholder=""
label=""
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full "
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
variant="bordered"
/>
)}
/>
{errors?.fullname && (
<p className="text-red-400 text-sm mb-3">
{errors.fullname?.message}
</p>
)}
</div>
<div className="flex flex-col gap-1">
<p className="text-sm">Username</p>
<Controller
control={control}
name="username"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="username"
placeholder=""
label=""
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full "
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
variant="bordered"
/>
)}
/>
{errors?.username && (
<p className="text-red-400 text-sm mb-3">
{errors.username?.message}
</p>
)}
</div>
<div className="flex flex-col gap-1">
<p className="text-sm">Email</p>
<Controller
control={control}
name="email"
render={({ field: { onChange, value } }) => (
<Input
type="email"
id="email"
placeholder=""
label=""
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full "
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
variant="bordered"
/>
)}
/>
{errors?.email && (
<p className="text-red-400 text-sm mb-3">{errors.email?.message}</p>
)}
</div>
<div className="flex flex-col gap-1">
<p className="text-sm">NRP</p>
<Controller
control={control}
name="nrp"
render={({ field: { onChange, value } }) => (
<Input
type="number"
id="nrp"
placeholder=""
label=""
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full "
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
variant="bordered"
/>
)}
/>
{errors?.nrp && (
<p className="text-red-400 text-sm mb-3">{errors.nrp?.message}</p>
)}
</div>
<div className="flex flex-col gap-1">
<p className="text-sm">Gender</p>
<Controller
control={control}
name="gender"
render={({ field: { onChange, value } }) => (
<RadioGroup
label=""
value={value}
onValueChange={onChange}
orientation="horizontal"
>
<Radio value="male">Laki-laki</Radio>
<Radio value="female">Perempuan</Radio>
</RadioGroup>
)}
/>
{errors?.gender && (
<p className="text-red-400 text-sm mb-3">{errors.gender?.message}</p>
)}
</div>
<Button color="primary" type="submit">
Simpan
</Button>
</form>
);
}

View File

@ -148,3 +148,49 @@ export const FileIcon = ({
/>
</svg>
);
export const UserProfileIcon = ({
size,
height = 24,
width = 24,
fill = "currentColor",
...props
}: IconSvgProps) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size || width}
height={size || height}
{...props}
viewBox="0 0 16 16"
>
<path
fill="currentColor"
d="M11 7c0 1.66-1.34 3-3 3S5 8.66 5 7s1.34-3 3-3s3 1.34 3 3"
/>
<path
fill="currentColor"
fillRule="evenodd"
d="M16 8c0 4.42-3.58 8-8 8s-8-3.58-8-8s3.58-8 8-8s8 3.58 8 8M4 13.75C4.16 13.484 5.71 11 7.99 11c2.27 0 3.83 2.49 3.99 2.75A6.98 6.98 0 0 0 14.99 8c0-3.87-3.13-7-7-7s-7 3.13-7 7c0 2.38 1.19 4.49 3.01 5.75"
clipRule="evenodd"
/>
</svg>
);
export const SettingsIcon = ({
size,
height = 24,
width = 24,
fill = "currentColor",
...props
}: IconSvgProps) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size || width}
height={size || height}
{...props}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="m9.25 22l-.4-3.2q-.325-.125-.612-.3t-.563-.375L4.7 19.375l-2.75-4.75l2.575-1.95Q4.5 12.5 4.5 12.338v-.675q0-.163.025-.338L1.95 9.375l2.75-4.75l2.975 1.25q.275-.2.575-.375t.6-.3l.4-3.2h5.5l.4 3.2q.325.125.613.3t.562.375l2.975-1.25l2.75 4.75l-2.575 1.95q.025.175.025.338v.674q0 .163-.05.338l2.575 1.95l-2.75 4.75l-2.95-1.25q-.275.2-.575.375t-.6.3l-.4 3.2zm2.8-6.5q1.45 0 2.475-1.025T15.55 12t-1.025-2.475T12.05 8.5q-1.475 0-2.488 1.025T8.55 12t1.013 2.475T12.05 15.5"
/>
</svg>
);

View File

@ -12,6 +12,7 @@ import "swiper/css";
import "swiper/css/pagination";
import "swiper/css/effect-fade";
import "swiper/css/navigation";
import { getListMagazine } from "@/service/magazine";
export default function ENewsPolri() {
const [article, setArticle] = useState<any>([]);
@ -20,7 +21,7 @@ export default function ENewsPolri() {
async function getArticle() {
const req = { page: 1, search: "", limit: "10" };
const response = await getListArticle(req);
const response = await getListMagazine(req);
setArticle(response?.data?.data);
}
getArticle();
@ -68,13 +69,13 @@ export default function ENewsPolri() {
<SwiperSlide key={newsItem.id}>
<Card isFooterBlurred radius="lg" className="border-none">
<img
alt="headernews"
src="/headernews.png"
className="h-[25vh] object-cover rounded-none"
alt="thumbnail"
src={newsItem?.thumbnailUrl ? "" : "/no-image.jpg"}
className="!h-[25vh] object-cover rounded-none"
/>
<CardFooter className="before:bg-white/10 border-white/20 border-1 overflow-hidden py-1 md:absolute bottom-1 shadow-small z-10">
<div className="text-white">
<Link href={`news/detail/${newsItem.id}`}>
<Link href={`/e-majalah-polri/detail/${newsItem.id}`}>
<p className="text-left font-semibold text-sm">
{textEllipsis(newsItem.title, 40)}
</p>

View File

@ -30,7 +30,7 @@ export default function SidebarNav() {
onClick={() => setSelectedTab("media")}
className={
selectedTab === "media"
? "text-black border-b-3 border-red-400 cursor-pointer py-2"
? "text-black dark:text-white border-b-3 border-red-400 cursor-pointer py-2"
: "text-slate-300 cursor-pointer py-2"
}
>
@ -40,7 +40,7 @@ export default function SidebarNav() {
onClick={() => setSelectedTab("video")}
className={
selectedTab === "video"
? "text-black border-b-3 border-red-400 cursor-pointer py-2"
? "text-black dark:text-white border-b-3 border-red-400 cursor-pointer py-2"
: "text-slate-300 cursor-pointer py-2"
}
>

View File

@ -27,9 +27,9 @@ export const AdminLayout = ({ children }: Props) => {
return (
<SidebarProvider>
<div className="h-screen flex items-center flex-row">
<div className="!h-screen flex items-center flex-row !overflow-y-hidden">
<Sidebar sidebarData={isOpen} updateSidebarData={updateSidebarData} />
<div className={`w-full h-full flex flex-col`}>
<div className={`w-full h-full flex flex-col overflow-hidden`}>
<Breadcrumb />
{children}
</div>

View File

@ -9,7 +9,7 @@ interface Props {
export const HumasLayout = ({ children }: Props) => {
return (
<section className="flex flex-col">
<section className="flex flex-col overflow-auto">
<NavbarHumas size="sm" />
<NavbarHumas size="lg" />
<NewsTicker />

View File

@ -32,6 +32,7 @@ import { useSidebar } from "./sidebar-context";
import { SidebarMenu } from "./sidebar-menu";
import Image from "next/image";
import Cookies from "js-cookie";
import { SettingsIcon, UserProfileIcon } from "@/components/icons/globals";
interface SubMenuItems {
id: number;
@ -114,13 +115,13 @@ const sideBarDummyData = [
childModule: null,
},
{
id: 4,
name: "Majalah",
moduleId: 652,
moduleName: "Apps",
modulePathUrl: "/admin/magazine",
id: 30,
name: "Category",
moduleId: 654,
moduleName: "Master",
modulePathUrl: "/admin/master-category",
parentId: -1,
icon: <MagazineIcon />,
icon: <MasterCategoryIcon size={22} />,
position: 1,
statusId: 1,
childMenu: [],
@ -128,19 +129,20 @@ const sideBarDummyData = [
childModule: null,
},
{
id: 11,
name: "Static Page",
id: 4,
name: "Majalah",
moduleId: 652,
moduleName: "Dashboard",
modulePathUrl: "/admin/static-page",
moduleName: "Apps",
modulePathUrl: "/admin/magazine",
parentId: -1,
icon: <StaticPageIcon />,
icon: <MagazineIcon size={23} />,
position: 1,
statusId: 1,
childMenu: [],
statusName: "Active",
childModule: null,
},
// {
// id: 4,
// name: "E-Magazine",
@ -199,13 +201,13 @@ const sideBarDummyData = [
// childModule: null,
// },
{
id: 30,
name: "Master Category",
moduleId: 654,
moduleName: "Master",
modulePathUrl: "/admin/master-category",
id: 11,
name: "Master Static Page",
moduleId: 652,
moduleName: "Dashboard",
modulePathUrl: "/admin/static-page",
parentId: -1,
icon: <MasterCategoryIcon />,
icon: <StaticPageIcon size={24} />,
position: 1,
statusId: 1,
childMenu: [],
@ -249,6 +251,7 @@ const Sidebar: React.FC<SidebarProps> = ({ updateSidebarData }) => {
const [sidebarMenu, setSidebarMenu] = useState<SidebarMenuTask[]>();
const { isOpen, toggleSidebar } = useSidebar();
const token = Cookies.get("access_token");
const fullname = Cookies.get("ufne");
const isAuthenticated = Cookies.get("is_authenticated");
useEffect(() => {
@ -270,12 +273,12 @@ const Sidebar: React.FC<SidebarProps> = ({ updateSidebarData }) => {
return (
<div
className={`hidden md:flex h-full flex-grow ${
className={`hidden md:flex h-screen flex-grow ${
isOpen ? "min-w-[240px]" : "min-w-[80px]"
}`}
>
<div
className={` flex h-full flex-col rounded-lg p-4 mb-0 bg-gray-100 dark:bg-stone-900 z-40 transition-width !ease-in-out justify-between ${
className={` flex h-full flex-col p-4 mb-0 bg-gray-950 z-40 transition-width !ease-in-out justify-between ${
isOpen ? "w-[238px]" : "w-[80px]"
}`}
>
@ -304,7 +307,7 @@ const Sidebar: React.FC<SidebarProps> = ({ updateSidebarData }) => {
</Link>
{isOpen && (
<button
className="w-5 h-5 text-zinc-400 dark:text-zinc-400 z-50 border-1 border-zinc-400 rounded-full flex justify-center items-center"
className="w-5 h-5 text-zinc-400 z-50 border-1 border-zinc-400 rounded-full flex justify-center items-center"
onClick={toggleSidebar}
>
<ChevronLeftIcon />
@ -318,7 +321,7 @@ const Sidebar: React.FC<SidebarProps> = ({ updateSidebarData }) => {
list.isGroup ? (
<p
key={list}
className={`font-bold mr-4 ${
className={`font-bold mr-4 text-white ${
!isOpen ? "text-center" : ""
}`}
>
@ -338,8 +341,8 @@ const Sidebar: React.FC<SidebarProps> = ({ updateSidebarData }) => {
<div
className={`px-3.5 py-2 mr-4 rounded-lg flex flex-row gap-2 ${
pathname.includes(list.modulePathUrl)
? "bg-black dark:bg-white text-white dark:text-black font-bold"
: "text-black dark:text-white hover:bg-stone-900 hover:text-white dark:hover:bg-gray-200 dark:hover:text-black"
? "bg-white text-black font-bold"
: "text-white hover:bg-gray-200 hover:text-black"
}`}
>
{list.icon} {isOpen && list.name}
@ -356,8 +359,8 @@ const Sidebar: React.FC<SidebarProps> = ({ updateSidebarData }) => {
<div
className={`py-2 mr-4 rounded-lg hover:bg-zinc-400 dark:hover:text-zinc-600 flex flex-row justify-center gap-1 ${
pathname.includes(list.modulePathUrl)
? "bg-zinc-600 dark:bg-zinc-300 text-zinc-300 dark:text-zinc-500 font-bold"
: "text-zinc-600 dark:text-zinc-400"
? "bg-zinc-300 text-zinc-500 font-bold hover:text-black"
: "text-zinc-400 hover:text-black"
}`}
>
{list.icon} {isOpen && list.name}
@ -399,23 +402,50 @@ const Sidebar: React.FC<SidebarProps> = ({ updateSidebarData }) => {
>
<div className="flex flex-col gap-4">
<div
className={`cursor-pointer flex flex-row ${
className={`flex flex-row ${
isOpen ? "justify-start" : "justify-center"
} gap-2 items-center text-zinc-600 dark:text-zinc-400 hover:font-semibold hover:text-zinc-700 dark:hover:text-zinc-300`}
} gap-2 items-center text-white `}
>
<ThemeSwitch />
{isOpen && "Theme"}
</div>
{isOpen ? (
<div className="flex flex-row gap-3">
<Image
src="/pengaduan.png"
width={72}
height={72}
alt="profile"
/>
<div className="flex flex-col">
<a className="cursor-pointer">Nama User</a>
<Link href="/settings">
<div
className={`py-2 mr-4 rounded-lg flex flex-row gap-2 ${
pathname.includes("/settings")
? "bg-white text-black font-bold px-2"
: "text-white "
}`}
>
<SettingsIcon /> {isOpen && "Settings"}
</div>
</Link>
) : (
<Tooltip
content="Setting"
placement="right"
delay={0}
closeDelay={0}
>
<Link href="/settings">
<div
className={`py-2 rounded-lg hover:text-zinc-600 flex flex-row justify-center gap-1 ${
pathname.includes("/settings")
? "bg-zinc-300 text-zinc-500 font-bold "
: "text-zinc-400 "
}`}
>
<SettingsIcon /> {isOpen && "Settings"}
</div>
</Link>
</Tooltip>
)}
{isOpen ? (
<div className="flex flex-row gap-3 items-center text-white">
<UserProfileIcon size={36} />
<div className="flex flex-col ">
<a className="cursor-pointer ">{fullname}</a>
<a
className="hover:text-red-600 underline text-sm cursor-pointer"
onClick={() => onLogout()}
@ -437,12 +467,7 @@ const Sidebar: React.FC<SidebarProps> = ({ updateSidebarData }) => {
} gap-2 items-center text-zinc-600 dark:text-zinc-400 hover:font-semibold hover:text-zinc-700 dark:hover:text-zinc-300`}
onClick={toggleSidebar}
>
<Image
src="/pengaduan.png"
width={48}
height={48}
alt="profile"
/>
<UserProfileIcon size={28} />
</a>
</Tooltip>
)}

View File

@ -1,60 +1,151 @@
'use client'
import { BreadcrumbItem, Breadcrumbs } from '@nextui-org/breadcrumbs'
import { Button } from '@nextui-org/button'
import React from 'react'
"use client";
import { ChevronRightIcon } from "@/components/icons";
import { getMagazineById } from "@/service/magazine";
import {
convertDateFormat,
formatMonthString,
formatTextToHtmlTag,
} from "@/utils/global";
import { BreadcrumbItem, Breadcrumbs } from "@nextui-org/breadcrumbs";
import { Button } from "@nextui-org/button";
import { Accordion, AccordionItem } from "@nextui-org/react";
import Link from "next/link";
import { useParams } from "next/navigation";
import React, { useEffect, useState } from "react";
export default function EMagazineDetail() {
return (
<div className='w-auto bg-[#E2E2E2] text-black'>
<div className='p-1 md:p-5 lg:px-8'>
<div className='font-bold text-xl'>E-Majalah Polri</div>
<div className="pt-2">
<Breadcrumbs color='primary'>
<BreadcrumbItem href='/'>Beranda</BreadcrumbItem>
<BreadcrumbItem href='/e-majalah-polri/daftar-majalah'>E-Majalah Polri</BreadcrumbItem>
<BreadcrumbItem>Judul</BreadcrumbItem>
</Breadcrumbs>
</div>
<div className='pt-5 space-y-4'>
<div className='font-semibold border-b-4 border-red-700 leading-loose'>MAJALAH TRIBRATA NEWS SUMSEL EDISI 33/ VII-IX/2023</div>
<div className='bg-[#FFFFFF] rounded-lg h-[380px] flex items-center justify-center'>
<img src="/emagazine.jpeg" alt="emagazine" className='h-[380px] rounded-md py-1' />
</div>
<div>
<Button
className='w-full bg-[#DD8306] text-sm font-semibold'
>
Download
</Button>
</div>
<div className='bg-[#FFFFFF] rounded-md text-sm font-medium'>
<div className='flex justify-between text-white border-t-2 border-l border-r rounded-t-md border-gray-300 p-3 bg-[#9D9D9D]'>
<div className='w-5/6'>Judul</div>
<div className='w-1/6 text-center border-l border-gray-300'>Keterangan</div>
</div>
<div className='flex justify-between border border-gray-300 p-3'>
<div className='w-5/6'>Download</div>
<div className='w-1/6 text-center border-l border-gray-300'>51</div>
</div>
<div className='flex justify-between border border-gray-300 p-3'>
<div className='w-5/6'>File Size</div>
<div className='w-1/6 text-center border-l border-gray-300'>12,.58MB</div>
</div>
<div className='flex justify-between border border-gray-300 p-3'>
<div className='w-5/6'>Download</div>
<div className='w-1/6 text-center border-l border-gray-300'>10</div>
</div>
<div className='flex justify-between border border-gray-300 p-3'>
<div className='w-5/6'>Created Date</div>
<div className='w-1/6 text-center border-l border-gray-300'>1 Oktober 2024</div>
</div>
<div className='flex justify-between border border-b border-gray-300 p-3 rounded-b-md'>
<div className='w-5/6'>Last Update</div>
<div className='w-1/6 text-center border-l border-gray-300'>1 Oktober 2024</div>
</div>
</div>
</div>
</div>
const params = useParams();
const id = params?.id;
const [detailData, setDetailData] = useState<any>();
const [detailFiles, setDetailFiles] = useState<any>([]);
useEffect(() => {
initFetch();
}, [id]);
const initFetch = async () => {
const res = await getMagazineById(String(id));
const data = res?.data?.data;
setDetailData(data);
setDetailFiles(data?.files);
};
const doDownload = async (fileName: string, title: string): Promise<void> => {
try {
const response = await fetch(
`http://38.47.180.165:8802/magazine-files/viewer/${fileName}`
);
if (!response.ok) {
throw new Error("File not found or server error");
}
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const anchor = document.createElement("a");
anchor.href = url;
anchor.download = title === "" ? fileName : title;
document.body.appendChild(anchor);
anchor.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(anchor);
} catch (error) {
console.error("Download failed:", error);
}
};
return (
<div className="w-auto bg-[#E2E2E2] text-black">
<div className="p-1 md:p-5 lg:px-36">
<div className="flex flex-row gap-4 items-end text-black">
<Link href="/" className=" font-semibold text-lg">
Beranda
</Link>
<ChevronRightIcon />
<Link
href="/e-majalah-polri/daftar-majalah"
className=" font-semibold text-lg"
>
E-Majalah Polri
</Link>
</div>
)
<div className="pt-5 space-y-4">
<div className="font-semibold border-b-4 border-red-700 leading-loose">
{detailData?.title}
</div>
<div className="rounded-lg h-[380px] flex items-center justify-center">
<img
src="/emagazine.jpeg"
alt="emagazine"
className="h-[380px] rounded-md py-1"
/>
</div>
<div
dangerouslySetInnerHTML={formatTextToHtmlTag(
detailData?.description
)}
className="text-sm lg:text-xl lg:leading-8 justify-center flex"
/>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{detailFiles?.map((file: any, index: number) => (
<Accordion key={file?.id} variant="splitted">
<AccordionItem
key={file?.id}
aria-label={file?.title}
className="p-2"
title={
<p className="text-black dark:text-white font-semibold text-xs md:text-sm">
{file?.title === "" ? `File ${index + 1}` : file?.title}
</p>
}
>
<div className="bg-[#FFFFFF] rounded-md font-medium text-xs md:text-sm">
<div className="flex justify-between items-center border text-black dark:text-white border-gray-300 p-3 rounded-t-md bg-gray-100 dark:bg-stone-900">
<div className="w-[35%] md:w-[20%]">Nama File</div>
<div className="w-[65%] md:w-[80%] text-center border-l border-gray-300">
{file?.fileName}
</div>
</div>
<div className="flex justify-between border items-center border-gray-300 p-3">
<div className="w-[35%] md:w-[20%]">Deskripsi</div>
<div className="w-[65%] md:w-[80%] text-center border-l border-gray-300">
{file?.description == "" ? "-" : file?.description}
</div>
</div>
<div className="flex justify-between border items-center border-gray-300 p-3">
<div className="w-[35%] md:w-[20%]">Ukuran File</div>
<div className="w-[65%] md:w-[80%] text-center border-l border-gray-300">
{Math.round(file?.size / 100) / 10 > 1000 ? (
<>
{(Math.round(file?.size / 100) / 10000).toFixed(1)}
</>
) : (
<>{(Math.round(file?.size / 100) / 10).toFixed(1)}</>
)}
{" kb"}
</div>
</div>
<div className="flex justify-between items-center border rounded-b-md border-gray-300 p-3">
<div className="w-[35%] md:w-[20%]">Tanggal Publish</div>
<div className="w-[65%] md:w-[80%] text-center border-l border-gray-300">
{formatMonthString(file?.createdAt)}
</div>
</div>
</div>
<Button
className="w-full bg-[#DD8306] text-sm font-semibold text-white mt-2"
radius="sm"
onPress={() => doDownload(file?.fileName, file?.title)}
>
Download
</Button>
</AccordionItem>
</Accordion>
))}
</div>
</div>
</div>
</div>
);
}

View File

@ -241,7 +241,7 @@ export default function MasterRoleTable() {
return (
<>
<div className="mx-5 my-5">
<div className="mx-3 my-5">
<div className="flex flex-col items-center rounded-2xl">
<Table
// selectionMode="multiple"

View File

@ -174,7 +174,7 @@ export default function MasterUserTable() {
return (
<>
<div className="mx-5 my-5">
<div className="mx-3 my-5">
<div className="flex flex-col items-center rounded-2xl">
<Table
// selectionMode="multiple"

View File

@ -204,9 +204,9 @@ export default function StaticPageTable() {
<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">Search</p>
<p className="font-semibold text-sm">Pencarian</p>
<Input
aria-label="Search"
aria-label="Pencarian..."
classNames={{
inputWrapper: "bg-default-100",
input: "text-sm",
@ -222,7 +222,7 @@ export default function StaticPageTable() {
/>
</div>
<div className="flex flex-col gap-1 w-[72px]">
<p className="font-semibold text-sm">Show</p>
<p className="font-semibold text-sm">Data</p>
<Select
label=""
variant="bordered"
@ -242,29 +242,9 @@ export default function StaticPageTable() {
</SelectItem>
</Select>
</div>
<div className="flex flex-col gap-1 w-[170px]">
<p className="font-semibold text-sm">Category</p>
<Select
label=""
variant="bordered"
labelPlacement="outside"
placeholder="Select"
selectedKeys={[showData]}
className="w-full"
onChange={(e) =>
e.target.value === "" ? "" : setShowData(e.target.value)
}
>
<SelectItem key="10" value="10">
Polda Metro Jaya
</SelectItem>
<SelectItem key="5" value="5">
Polda Sumatera Utara
</SelectItem>
</Select>
</div>
<div className="flex flex-col gap-1 w-full md:w-[340px]">
<p className="font-semibold text-sm">Date</p>
<p className="font-semibold text-sm">Tanggal</p>
<Datepicker
value={startDateValue}
displayFormat="DD/MM/YYYY"

View File

@ -4,6 +4,7 @@ import {
Breadcrumbs,
Button,
Input,
Pagination,
Select,
SelectItem,
SelectSection,
@ -16,7 +17,21 @@ import {
} from "@nextui-org/react";
import Link from "next/link";
import SidebarNav from "../landing/SidebarNav";
import { EyeFilledIcon, SearchIcon } from "../icons";
import { ChevronRightIcon, EyeFilledIcon, SearchIcon } from "../icons";
import { getListMagazine } from "@/service/magazine";
import { useEffect, useState } from "react";
import Datepicker from "react-tailwindcss-datepicker";
const header = [
{
key: "title",
label: "Daftar E-Majalah Polri",
},
{
key: "icon",
label: "",
},
];
export default function ListEnewsPolri() {
const searchInput = (
@ -57,68 +72,92 @@ export default function ListEnewsPolri() {
/>
);
const tableData = [
{
key: "1",
tahun: "MAJALAH TRIBRATA NEWS SUMSEL EDISI 33/ VII-IX/2023",
icon: <EyeFilledIcon color="#DD8306" />,
},
{
key: "2",
tahun: "Tribrata Edisi 1 2023",
icon: <EyeFilledIcon color="#DD8306" />,
},
{
key: "3",
tahun: "MAJALAH TRIBRATA NEWS SUMSEL EDISI 33/ VII-IX/2023",
icon: <EyeFilledIcon color="#DD8306" />,
},
{
key: "4",
tahun: "MAJALAH TRIBRATA NEWS SUMSEL EDISI 33/ VII-IX/2023",
icon: <EyeFilledIcon color="#DD8306" />,
},
{
key: "5",
tahun: "MAJALAH TRIBRATA NEWS SUMSEL EDISI 33/ VII-IX/2023",
icon: <EyeFilledIcon color="#DD8306" />,
},
];
const [magazines, setMagazines] = useState<any>([]);
const [page, setPage] = useState(1);
const [search, setSearch] = useState("");
const [totalPage, setTotalPage] = useState(1);
const header = [
{
key: "tahun",
label: "Daftar E-Majalah Polri",
},
{
key: "icon",
label: "",
},
];
const [startDateValue, setStartDateValue] = useState({
startDate: null,
endDate: null,
});
useEffect(() => {
getMagazines();
}, [page, startDateValue]);
async function getMagazines() {
const req = {
page: page,
search: search,
limit: "10",
startDate:
startDateValue.startDate === null ? "" : startDateValue.startDate,
endDate: startDateValue.endDate === null ? "" : startDateValue.endDate,
};
const response = await getListMagazine(req);
setMagazines(response?.data?.data);
setTotalPage(response?.data?.meta?.totalPage);
}
const category = [
{ label: "Title", value: "title" },
{ label: "Date", value: "date" },
{ label: "Created At", value: "createdAt" },
];
return (
<div className="md:flex ">
<div className="w-auto bg-[#E2E2E2] md:w-2/3 lg:w-[75%] text-black">
<div className="p-1 md:p-5 lg:p-8">
<div className="font-bold text-xl">E-Majalah Polri</div>
<div className="pt-2">
<Breadcrumbs color="primary">
<BreadcrumbItem href="/">Beranda</BreadcrumbItem>
<BreadcrumbItem>E-Majalah Polri</BreadcrumbItem>
</Breadcrumbs>
<div className="p-1 md:py-5 lg:px-36">
<div className="flex flex-row gap-4 items-end text-black">
<Link href="/" className=" font-semibold text-lg">
Beranda
</Link>
<ChevronRightIcon />
<p className=" font-semibold text-lg">E-Majalah Polri</p>
</div>
<div className="pt-4 space-y-5">
<div className="font-semibold text-lg border-b-4 border-red-700 leading-loose">
<div className="pt-4 space-y-5 ">
{/* <div className="font-semibold text-lg border-b-4 border-red-700 leading-loose">
E-majalah Polri
</div>
<div className="flex items-center gap-3">
{searchInput}
<Select
</div> */}
<div className="flex items-end gap-3 flex-col md:flex-row">
<Input
aria-label="Search"
classNames={{
input: [
"w-full",
"bg-transparent",
"h-[20px]",
"!text-black",
],
mainWrapper: ["w-full", "bg-transparent"],
innerWrapper: ["bg-transparent", "h-[20px]"],
inputWrapper: [
"bg-white",
"dark:bg-white",
"hover:!bg-gray-300",
"dark:hover:bg-gray-300",
"group-data-[focused=true]:bg-transparent",
"dark:group-data-[focused=true]:bg-transaparent",
"group-data-[focused=false]:bg-transparent",
"focus-within:!bg-transparent",
"h-[20px]",
"dark:focus-within:!bg-gray-100",
],
}}
labelPlacement="outside"
type="text"
onChange={(e) => setSearch(e.target.value)}
startContent={
<SearchIcon className="text-base text-default-400 pointer-events-none flex-shrink-0" />
}
endContent={
<Button
size="sm"
className="bg-[#DD8306] font-semibold text-white"
onPress={getMagazines}
>
Cari
</Button>
}
/>
{/* <Select
label="Sort By"
size="sm"
className="w-1/5"
@ -147,44 +186,25 @@ export default function ListEnewsPolri() {
<SelectItem key={list.id}>{list.label}</SelectItem>
))}
</SelectSection>
</Select>
<Select
label="Tanggal Publikasi"
size="sm"
className="w-1/4"
classNames={{
// base: "bg-red-500",
// mainWrapper: "border-2 border-red-500",
label: "text-black",
value: "!text-black",
trigger: "bg-white hover:!bg-gray-100",
// innerWrapper: "bg-red-500"
// selectorIcon: "bg-red-500"
// listboxWrapper: "bg-red-500"
// listbox: "bg-red-500"
popoverContent: "bg-white",
}}
listboxProps={{
itemClasses: {
base: "text-black",
wrapper: "!bg-white ",
},
}}
// onChange={onChangeFilterEnterprising}
>
<SelectSection>
{category.map((list: any) => (
<SelectItem key={list.id}>{list.label}</SelectItem>
))}
</SelectSection>
</Select>
</Select> */}
<div className="flex flex-col gap-1 w-full md:w-[240px]">
<p className="font-semibold text-xs md:text-sm">
Tanggal Publikasi
</p>
<Datepicker
value={startDateValue}
displayFormat="DD/MM/YYYY"
useRange={false}
asSingle={true}
onChange={(e: any) => setStartDateValue(e)}
inputClassName="z-50 w-full text-sm bg-white border-1 border-gray-200 px-2 py-[6px] rounded-xl h-[40px] text-black"
/>
</div>
</div>
<div>
<div className="flex flex-col gap-3">
<Table
color="warning"
selectionMode="single"
defaultSelectedKeys={["1"]}
aria-label="Example static collection table"
classNames={{
wrapper: "bg-white",
@ -203,19 +223,38 @@ export default function ListEnewsPolri() {
</TableColumn>
)}
</TableHeader>
<TableBody items={tableData}>
{(item) => (
<TableRow key={item.key}>
<TableCell>{item.tahun}</TableCell>
<TableBody items={magazines}>
{(item: any) => (
<TableRow key={item.id}>
<TableCell>
<Link href={`/e-majalah-polri/detail/${item.id}`}>
{item.title}
</Link>
</TableCell>
<TableCell>
<Link href={`/e-majalah-polri/detail/${item.key}`}>
{item.icon}
<EyeFilledIcon color="#DD8306" />
</Link>
</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>
</div>

View File

@ -6,6 +6,15 @@ import { BreadcrumbItem, Breadcrumbs } from "@nextui-org/react";
import { usePathname, useRouter } from "next/navigation";
import { Image } from "@nextui-org/react";
import { FormLayoutIcon } from "../icons";
import {
ArticleIcon,
DashboardIcon,
MagazineIcon,
MasterCategoryIcon,
MasterRoleIcon,
MasterUsersIcon,
StaticPageIcon,
} from "../icons/sidebar-icon";
export const Breadcrumb = () => {
const [currentPage, setCurrentPage] = useState<React.Key>("");
@ -58,7 +67,16 @@ export const Breadcrumb = () => {
)}
</Breadcrumbs>
</div>
<FormLayoutIcon width={50} height={50} />
{pathname.includes("dashboard") && <DashboardIcon size={50} />}
{pathname.includes("article") && <ArticleIcon size={50} />}
{pathname.includes("master-category") && (
<MasterCategoryIcon size={50} />
)}
{pathname.includes("magazine") && <MagazineIcon size={50} />}
{pathname.includes("static-page") && <StaticPageIcon size={50} />}
{pathname.includes("master-user") && <MasterUsersIcon size={50} />}
{pathname.includes("master-role") && <MasterRoleIcon size={50} />}
{/* <FormLayoutIcon width={50} height={50} /> */}
</div>
</div>
</div>

BIN
public/files.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/no-image.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -3,6 +3,9 @@ import {
httpGet,
httpPost,
} from "./http-config/axios-base-service";
import Cookies from "js-cookie";
const token = Cookies.get("access_token");
export async function listMasterUsers(data: any) {
const headers = {
@ -34,6 +37,7 @@ export async function postSignIn(data: any) {
export async function getProfile() {
const headers = {
"content-type": "application/json",
Authorization: `Bearer ${token}`,
};
return await httpGet(`/users/info`, headers);
}

View File

@ -77,3 +77,18 @@
clip-path: polygon(0 0, 100% 50%, 0 100%);
}
}
.ck-editor__editable_inline:not(.ck-comment__input *) {
height: 300px;
overflow-y: auto;
}
body {
height: 100%; /* Pastikan tinggi body penuh */
overflow: hidden; /* Nonaktifkan scrolling global */
}
main {
height: 100vh; /* Pastikan tinggi main sesuai viewport */
overflow-y: auto; /* Aktifkan scrolling hanya di elemen main */
}