update flow approve and kontributor
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
0430d15fd2
commit
96e8538e6a
|
|
@ -1,15 +1,20 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import Cookies from "js-cookie";
|
||||||
import ContentWebsite from "@/components/main/content-website";
|
import ContentWebsite from "@/components/main/content-website";
|
||||||
import DashboardContainer from "@/components/main/dashboard/dashboard-container";
|
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import ApproverContentWebsite from "@/components/main/content-website-approver";
|
||||||
|
|
||||||
export default function ContentWebsitePage() {
|
export default function ContentWebsitePage() {
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
|
const [levelId, setLevelId] = useState<string | undefined>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMounted(true);
|
setMounted(true);
|
||||||
|
const ulne = Cookies.get("ulne");
|
||||||
|
setLevelId(ulne);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
|
|
@ -28,7 +33,7 @@ export default function ContentWebsitePage() {
|
||||||
transition={{ duration: 0.3 }}
|
transition={{ duration: 0.3 }}
|
||||||
>
|
>
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<ContentWebsite />
|
{levelId === "2" ? <ApproverContentWebsite /> : <ContentWebsite />}
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,13 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
|
||||||
const [startDateValue, setStartDateValue] = useState<Date | undefined>();
|
const [startDateValue, setStartDateValue] = useState<Date | undefined>();
|
||||||
const [startTimeValue, setStartTimeValue] = useState<string>("");
|
const [startTimeValue, setStartTimeValue] = useState<string>("");
|
||||||
|
|
||||||
|
const [levelId, setLevelId] = useState<string | undefined>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const ulne = Cookies.get("ulne");
|
||||||
|
setLevelId(ulne);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const { getRootProps, getInputProps } = useDropzone({
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
onDrop: (acceptedFiles) => {
|
onDrop: (acceptedFiles) => {
|
||||||
setFiles((prevFiles) => [
|
setFiles((prevFiles) => [
|
||||||
|
|
@ -333,7 +340,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
successSubmit("/admin/article");
|
successSubmit("/admin/news-article/image");
|
||||||
};
|
};
|
||||||
|
|
||||||
const publishScheduled = async () => {
|
const publishScheduled = async () => {
|
||||||
|
|
@ -391,7 +398,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
successSubmit("/admin/article");
|
successSubmit("/admin/news-article/image");
|
||||||
};
|
};
|
||||||
|
|
||||||
const save = async (values: z.infer<typeof createArticleSchema>) => {
|
const save = async (values: z.infer<typeof createArticleSchema>) => {
|
||||||
|
|
@ -521,7 +528,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
successSubmit("/admin/article");
|
successSubmit("/admin/news-article/image");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -793,18 +800,65 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
|
||||||
<Mail size={20} />
|
<Mail size={20} />
|
||||||
<p className="text-sm ">Suggestion Box (0)</p>
|
<p className="text-sm ">Suggestion Box (0)</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="border p-3 border-black rounded-lg space-y-2 ">
|
||||||
|
<h2 className="text-sm text-black font-semibold">
|
||||||
|
Description :
|
||||||
|
</h2>
|
||||||
|
{detailData?.isPublish === true ? (
|
||||||
|
<span className="inline-block bg-green-100 text-green-700 text-xs font-semibold px-3 py-1 rounded-full">
|
||||||
|
Approved
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="inline-block bg-yellow-100 text-yellow-700 text-xs font-semibold px-3 py-1 rounded-full">
|
||||||
|
Pending
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<p className="text-sm text-black font-semibold">Comment</p>
|
||||||
|
<h2 className="text-blue-600 text-xs">View Approver History</h2>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Action Button */}
|
{/* ================= ACTION BUTTON ================= */}
|
||||||
<div className="flex justify-end">
|
<div className="space-y-3">
|
||||||
<Link href="/admin/news-article/image">
|
{levelId === "2" && !detailData?.isPublish && (
|
||||||
<Button
|
<>
|
||||||
variant={"outline"}
|
<Button
|
||||||
className="px-6 py-2 rounded-lg transition"
|
onClick={doPublish}
|
||||||
>
|
className="w-full bg-blue-600 hover:bg-blue-700 text-white py-3 rounded-xl"
|
||||||
Kembali
|
>
|
||||||
</Button>
|
Approve
|
||||||
</Link>
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setApprovalStatus(4);
|
||||||
|
doApproval();
|
||||||
|
}}
|
||||||
|
className="w-full bg-orange-500 hover:bg-orange-600 text-white py-3 rounded-xl"
|
||||||
|
>
|
||||||
|
Revision
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setApprovalStatus(5);
|
||||||
|
doApproval();
|
||||||
|
}}
|
||||||
|
className="w-full bg-red-600 hover:bg-red-700 text-white py-3 rounded-xl"
|
||||||
|
>
|
||||||
|
Reject
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 🔥 Jika levelId 3 → hanya tampilkan Cancel */}
|
||||||
|
{levelId === "3" && (
|
||||||
|
<Link href="/admin/news-article/image">
|
||||||
|
<Button variant="outline" className="w-full py-3 rounded-xl">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ const masterUserSchema = z.object({
|
||||||
genderType: z.string().min(1, { message: "Required" }),
|
genderType: z.string().min(1, { message: "Required" }),
|
||||||
phoneNumber: z.string().min(1, { message: "Required" }),
|
phoneNumber: z.string().min(1, { message: "Required" }),
|
||||||
address: z.string().min(1, { message: "Required" }),
|
address: z.string().min(1, { message: "Required" }),
|
||||||
userLevelType: userSchema,
|
// userLevelType: userSchema,
|
||||||
userRoleType: userSchema,
|
userRoleType: userSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -98,7 +98,7 @@ export default function FormMasterUser() {
|
||||||
identityNumber: data.identityNumber,
|
identityNumber: data.identityNumber,
|
||||||
identityType: "nrp",
|
identityType: "nrp",
|
||||||
phoneNumber: data.phoneNumber,
|
phoneNumber: data.phoneNumber,
|
||||||
userLevelId: data.userLevelType.id,
|
userLevelId: 3,
|
||||||
userRoleId: data.userRoleType.id,
|
userRoleId: data.userRoleType.id,
|
||||||
username: data.username,
|
username: data.username,
|
||||||
};
|
};
|
||||||
|
|
@ -206,6 +206,7 @@ export default function FormMasterUser() {
|
||||||
close();
|
close();
|
||||||
if (res?.data?.data) {
|
if (res?.data?.data) {
|
||||||
setupParent(res?.data?.data, "role");
|
setupParent(res?.data?.data, "role");
|
||||||
|
console.log("role", res?.data?.data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -408,7 +409,7 @@ export default function FormMasterUser() {
|
||||||
{errors.phoneNumber?.message}
|
{errors.phoneNumber?.message}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<Controller
|
{/* <Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="userLevelType"
|
name="userLevelType"
|
||||||
render={({ field: { onChange, value } }) => (
|
render={({ field: { onChange, value } }) => (
|
||||||
|
|
@ -438,7 +439,7 @@ export default function FormMasterUser() {
|
||||||
<p className="text-red-400 text-sm">
|
<p className="text-red-400 text-sm">
|
||||||
{errors.userLevelType?.message}
|
{errors.userLevelType?.message}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)} */}
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="userRoleType"
|
name="userRoleType"
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@ interface SidebarSection {
|
||||||
children?: SidebarItem[];
|
children?: SidebarItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSidebarByRole = (roleId: string | null) => {
|
const getSidebarByLevel = (levelId: string | null) => {
|
||||||
if (roleId === "1") {
|
if (levelId === "1") {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
title: "Dashboard",
|
title: "Dashboard",
|
||||||
|
|
@ -64,7 +64,7 @@ const getSidebarByRole = (roleId: string | null) => {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (roleId === "2" || roleId === "3") {
|
if (levelId === "3") {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
title: "Dashboard",
|
title: "Dashboard",
|
||||||
|
|
@ -141,7 +141,66 @@ const getSidebarByRole = (roleId: string | null) => {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// fallback kalau role tidak dikenal
|
if (levelId === "2") {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: "Dashboard",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "Dashboard",
|
||||||
|
icon: () => (
|
||||||
|
<Icon icon="material-symbols:dashboard" className="text-lg" />
|
||||||
|
),
|
||||||
|
link: "/admin/dashboard",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "Content Website",
|
||||||
|
icon: () => <Icon icon="ri:article-line" className="text-lg" />,
|
||||||
|
link: "/admin/content-website",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "News & Article",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "News & Article",
|
||||||
|
icon: () => (
|
||||||
|
<Icon icon="grommet-icons:article" className="text-lg" />
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
title: "Text",
|
||||||
|
icon: () => <Icon icon="mdi:file-document-outline" />,
|
||||||
|
link: "/admin/news-article/text",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Image",
|
||||||
|
icon: () => <Icon icon="mdi:image-outline" />,
|
||||||
|
link: "/admin/news-article/image",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Video",
|
||||||
|
icon: () => <Icon icon="mdi:video-outline" />,
|
||||||
|
link: "/admin/news-article/video",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Audio",
|
||||||
|
icon: () => <Icon icon="mdi:music-note-outline" />,
|
||||||
|
link: "/admin/news-article/audio",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback kalau Level tidak dikenal
|
||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -260,7 +319,7 @@ const SidebarContent = ({
|
||||||
const { theme, toggleTheme } = useTheme();
|
const { theme, toggleTheme } = useTheme();
|
||||||
|
|
||||||
const [username, setUsername] = useState<string>("Guest");
|
const [username, setUsername] = useState<string>("Guest");
|
||||||
const [roleId, setRoleId] = useState<string | null>(null);
|
const [LevelId, setLevelId] = useState<string | null>(null);
|
||||||
const [openMenus, setOpenMenus] = useState<string[]>([]);
|
const [openMenus, setOpenMenus] = useState<string[]>([]);
|
||||||
|
|
||||||
// ===============================
|
// ===============================
|
||||||
|
|
@ -275,10 +334,10 @@ const SidebarContent = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const cookieUsername = getCookie("username");
|
const cookieUsername = getCookie("username");
|
||||||
const cookieRoleId = getCookie("urie");
|
const cookieLevelId = getCookie("ulne");
|
||||||
|
|
||||||
if (cookieUsername) setUsername(cookieUsername);
|
if (cookieUsername) setUsername(cookieUsername);
|
||||||
if (cookieRoleId) setRoleId(cookieRoleId);
|
if (cookieLevelId) setLevelId(cookieLevelId);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// ===============================
|
// ===============================
|
||||||
|
|
@ -298,7 +357,7 @@ const SidebarContent = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const sidebarSections = getSidebarByRole(roleId);
|
const sidebarSections = getSidebarByLevel(LevelId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,149 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Eye, Pencil, Trash2, Filter } from "lucide-react";
|
||||||
|
|
||||||
|
export default function ApproverContentWebsite() {
|
||||||
|
const tabs = [
|
||||||
|
"Hero Section",
|
||||||
|
"About Us",
|
||||||
|
"Our Products",
|
||||||
|
"Our Services",
|
||||||
|
"Technology Partners",
|
||||||
|
"Pop Up",
|
||||||
|
];
|
||||||
|
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
title: "Beyond Expectations to Build Reputation.",
|
||||||
|
subtitle: "-",
|
||||||
|
author: "John Kontributor",
|
||||||
|
status: "Published",
|
||||||
|
date: "2024-01-15",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Manajemen Reputasi untuk Institusi",
|
||||||
|
subtitle: "-",
|
||||||
|
author: "Sarah Kontributor",
|
||||||
|
status: "Pending",
|
||||||
|
date: "2024-01-14",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* HEADER */}
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl font-bold text-slate-800">Content Website</h1>
|
||||||
|
<p className="text-slate-500">
|
||||||
|
Update homepage content, products, services, and partners
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* TABS */}
|
||||||
|
<div className="bg-white rounded-2xl shadow border p-2 flex flex-wrap gap-2">
|
||||||
|
{tabs.map((tab, i) => (
|
||||||
|
<button
|
||||||
|
key={i}
|
||||||
|
className={`px-4 py-2 rounded-xl text-sm font-medium transition ${
|
||||||
|
i === 0
|
||||||
|
? "bg-blue-600 text-white"
|
||||||
|
: "hover:bg-slate-100 text-slate-600"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{tab}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* SEARCH & FILTER */}
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<Input placeholder="Search Hero Section by title, author, or content..." />
|
||||||
|
<Button variant="outline" className="flex items-center gap-2">
|
||||||
|
<Filter size={16} /> Filters
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* TABLE */}
|
||||||
|
<div className="bg-white rounded-2xl shadow border overflow-hidden">
|
||||||
|
<table className="w-full text-sm">
|
||||||
|
<thead className="bg-slate-50 text-slate-600">
|
||||||
|
<tr>
|
||||||
|
<th className="text-left px-6 py-4">Main Title</th>
|
||||||
|
<th className="text-left px-6 py-4">Subtitle</th>
|
||||||
|
<th className="text-left px-6 py-4">Author</th>
|
||||||
|
<th className="text-left px-6 py-4">Status</th>
|
||||||
|
<th className="text-left px-6 py-4">Date</th>
|
||||||
|
<th className="text-left px-6 py-4">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{data.map((item, i) => (
|
||||||
|
<tr key={i} className="border-t hover:bg-slate-50 transition">
|
||||||
|
<td className="px-6 py-4 font-medium text-slate-800">
|
||||||
|
{item.title}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td className="px-6 py-4">{item.subtitle}</td>
|
||||||
|
|
||||||
|
<td className="px-6 py-4 text-slate-600">{item.author}</td>
|
||||||
|
|
||||||
|
<td className="px-6 py-4">
|
||||||
|
<span
|
||||||
|
className={`text-xs font-medium px-3 py-1 rounded-full ${
|
||||||
|
item.status === "Published"
|
||||||
|
? "bg-green-100 text-green-600"
|
||||||
|
: "bg-yellow-100 text-yellow-600"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{item.status}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td className="px-6 py-4 text-slate-600">{item.date}</td>
|
||||||
|
|
||||||
|
<td className="px-6 py-4">
|
||||||
|
<div className="flex gap-3 text-slate-500">
|
||||||
|
<Eye
|
||||||
|
size={18}
|
||||||
|
className="cursor-pointer hover:text-blue-600"
|
||||||
|
/>
|
||||||
|
<Pencil
|
||||||
|
size={18}
|
||||||
|
className="cursor-pointer hover:text-green-600"
|
||||||
|
/>
|
||||||
|
<Trash2
|
||||||
|
size={18}
|
||||||
|
className="cursor-pointer hover:text-red-600"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{/* FOOTER */}
|
||||||
|
<div className="flex justify-between items-center px-6 py-4 border-t bg-slate-50">
|
||||||
|
<p className="text-sm text-slate-500">Showing 1 to 2 of 2 items</p>
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button className="px-4 py-2 border rounded-lg text-sm">
|
||||||
|
Previous
|
||||||
|
</button>
|
||||||
|
<button className="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm">
|
||||||
|
1
|
||||||
|
</button>
|
||||||
|
<button className="px-4 py-2 border rounded-lg text-sm">2</button>
|
||||||
|
<button className="px-4 py-2 border rounded-lg text-sm">3</button>
|
||||||
|
<button className="px-4 py-2 border rounded-lg text-sm">
|
||||||
|
Next
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -52,10 +52,10 @@ interface PostCount {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DashboardContainer() {
|
export default function DashboardContainer() {
|
||||||
const [roleName, setRoleName] = useState<string | undefined>();
|
const [levelName, setLevelName] = useState<string | undefined>();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const role = Cookies.get("roleName");
|
const levelId = Cookies.get("ulne");
|
||||||
setRoleName(role);
|
setLevelName(levelId);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const username = Cookies.get("username");
|
const username = Cookies.get("username");
|
||||||
|
|
@ -135,7 +135,7 @@ export default function DashboardContainer() {
|
||||||
return month + " " + year;
|
return month + " " + year;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!roleName) return null;
|
if (!levelName) return null;
|
||||||
const AdminDashboard = () => {
|
const AdminDashboard = () => {
|
||||||
const tasks = [
|
const tasks = [
|
||||||
{
|
{
|
||||||
|
|
@ -494,7 +494,199 @@ export default function DashboardContainer() {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ApproverDashboard = () => {
|
||||||
|
const stats = [
|
||||||
|
{
|
||||||
|
title: "Pending Review",
|
||||||
|
value: 12,
|
||||||
|
growth: "+3",
|
||||||
|
color: "bg-yellow-500",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Approved Today",
|
||||||
|
value: 8,
|
||||||
|
growth: "+5",
|
||||||
|
color: "bg-green-600",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Total Published",
|
||||||
|
value: 156,
|
||||||
|
growth: "+12%",
|
||||||
|
color: "bg-blue-600",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Rejected",
|
||||||
|
value: 5,
|
||||||
|
growth: "-1",
|
||||||
|
color: "bg-red-600",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const pendingList = [
|
||||||
|
{
|
||||||
|
title: "MediaHUB Content Aggregator",
|
||||||
|
author: "John Kontributor",
|
||||||
|
category: "Product",
|
||||||
|
time: "2 hours ago",
|
||||||
|
status: "Pending",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Artifintel Services Update",
|
||||||
|
author: "John Kontributor",
|
||||||
|
category: "Service",
|
||||||
|
time: "2 hours ago",
|
||||||
|
status: "Pending",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const activities = [
|
||||||
|
{
|
||||||
|
status: "Approved",
|
||||||
|
title: "Technology Summit Event",
|
||||||
|
time: "10 mins ago",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: "Rejected",
|
||||||
|
title: "Product Update Draft",
|
||||||
|
time: "25 mins ago",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: "Approved",
|
||||||
|
title: "Partner Logo Update",
|
||||||
|
time: "1 hour ago",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-8">
|
||||||
|
{/* HEADER */}
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl font-bold text-slate-800">
|
||||||
|
Approver Dashboard
|
||||||
|
</h1>
|
||||||
|
<p className="text-slate-500">
|
||||||
|
Review and manage content submissions
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ================= STAT CARDS ================= */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
|
{stats.map((card, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="bg-white rounded-2xl shadow border p-6 flex justify-between items-start"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-slate-500">{card.title}</p>
|
||||||
|
<h2 className="text-3xl font-bold text-slate-800 mt-2">
|
||||||
|
{card.value}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-right">
|
||||||
|
<p className="text-sm text-green-600 font-medium">
|
||||||
|
{card.growth}
|
||||||
|
</p>
|
||||||
|
<div className={`w-10 h-10 rounded-xl mt-3 ${card.color}`} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ================= CONTENT SECTION ================= */}
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
|
{/* LEFT - Pending Review */}
|
||||||
|
<div className="lg:col-span-2 bg-white rounded-2xl shadow border p-6 space-y-6">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<h2 className="text-lg font-semibold">
|
||||||
|
Pending Review{" "}
|
||||||
|
<span className="ml-2 text-xs bg-amber-100 text-amber-600 px-3 py-1 rounded-full">
|
||||||
|
{pendingList.length} Items
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
<button className="text-blue-600 text-sm font-medium">
|
||||||
|
View All
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{pendingList.map((item, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="border border-amber-300 bg-amber-50 rounded-xl p-4 space-y-4"
|
||||||
|
>
|
||||||
|
<div className="flex justify-between items-start">
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold text-slate-800">
|
||||||
|
{item.title}
|
||||||
|
</h4>
|
||||||
|
<p className="text-sm text-slate-500 mt-1">
|
||||||
|
{item.author} • {item.category} • {item.time}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span className="text-xs bg-amber-200 text-amber-700 px-3 py-1 rounded-full">
|
||||||
|
{item.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<button className="flex-1 bg-green-600 hover:bg-green-700 text-white py-2 rounded-lg text-sm font-medium">
|
||||||
|
Approve
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button className="flex-1 bg-red-600 hover:bg-red-700 text-white py-2 rounded-lg text-sm font-medium">
|
||||||
|
Reject
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button className="px-4 py-2 bg-gray-200 rounded-lg text-sm">
|
||||||
|
Review
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* RIGHT - Recent Activity */}
|
||||||
|
<div className="bg-white rounded-2xl shadow border p-6 space-y-6">
|
||||||
|
<h2 className="text-lg font-semibold">Recent Activity</h2>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{activities.map((item, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="border rounded-xl p-4 flex justify-between items-center"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p
|
||||||
|
className={`text-sm font-medium ${
|
||||||
|
item.status === "Approved"
|
||||||
|
? "text-green-600"
|
||||||
|
: "text-red-600"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{item.status}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-slate-700">{item.title}</p>
|
||||||
|
<p className="text-xs text-slate-500">{item.time}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button className="w-full bg-[#966314] hover:bg-[#7a4f0f] text-white py-3 rounded-xl text-sm font-medium">
|
||||||
|
View All Activity
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>{roleName === "Admin" ? <AdminDashboard /> : <ContributorDashboard />}</>
|
<>
|
||||||
|
{levelName === "1" && <AdminDashboard />}
|
||||||
|
{levelName === "3" && <ContributorDashboard />}
|
||||||
|
{levelName === "2" && <ApproverDashboard />}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,19 @@ import Link from "next/link";
|
||||||
import { getArticlePagination } from "@/service/article";
|
import { getArticlePagination } from "@/service/article";
|
||||||
import { formatDate } from "@/utils/global";
|
import { formatDate } from "@/utils/global";
|
||||||
import { close, loading } from "@/config/swal";
|
import { close, loading } from "@/config/swal";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
export default function NewsImage() {
|
export default function NewsImage() {
|
||||||
const [articles, setArticles] = useState<any[]>([]);
|
const [articles, setArticles] = useState<any[]>([]);
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [totalPage, setTotalPage] = useState(1);
|
const [totalPage, setTotalPage] = useState(1);
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
const [levelId, setLevelId] = useState<string | undefined>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const ulne = Cookies.get("ulne");
|
||||||
|
setLevelId(ulne);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
|
|
@ -52,18 +59,21 @@ export default function NewsImage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusVariant = (status: string) => {
|
const statusVariant = (status: string) => {
|
||||||
switch (status?.toLowerCase()) {
|
const value = status?.toLowerCase();
|
||||||
case "publish":
|
|
||||||
return "bg-green-100 text-green-700";
|
if (value === "published") {
|
||||||
case "pending":
|
return "bg-green-100 text-green-700";
|
||||||
return "bg-yellow-100 text-yellow-700";
|
|
||||||
case "draft":
|
|
||||||
return "bg-gray-200 text-gray-600";
|
|
||||||
case "reject":
|
|
||||||
return "bg-red-100 text-red-600";
|
|
||||||
default:
|
|
||||||
return "bg-gray-200 text-gray-600";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (value === "pending") {
|
||||||
|
return "bg-yellow-100 text-yellow-700";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === "cancel") {
|
||||||
|
return "bg-red-100 text-red-700";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "bg-gray-200 text-gray-600";
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -78,12 +88,14 @@ export default function NewsImage() {
|
||||||
Create and manage news articles and blog posts
|
Create and manage news articles and blog posts
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Link href={"/admin/news-article/image/create"}>
|
{levelId === "3" && (
|
||||||
<Button className="bg-blue-600 hover:bg-blue-700 rounded-lg">
|
<Link href={"/admin/news-article/image/create"}>
|
||||||
<Plus className="w-4 h-4 mr-2" />
|
<Button className="bg-blue-600 hover:bg-blue-700 rounded-lg">
|
||||||
Create New Article
|
<Plus className="w-4 h-4 mr-2" />
|
||||||
</Button>
|
Create New Article
|
||||||
</Link>
|
</Button>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ================= SEARCH ================= */}
|
{/* ================= SEARCH ================= */}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ export async function getListArticle(props: PaginationRequest) {
|
||||||
}&categoryId=${category || ""}&sortBy=${sortBy || "created_at"}&sort=${
|
}&categoryId=${category || ""}&sortBy=${sortBy || "created_at"}&sort=${
|
||||||
sort || "desc"
|
sort || "desc"
|
||||||
}&category=${categorySlug || ""}&isBanner=${isBanner || ""}`,
|
}&category=${categorySlug || ""}&isBanner=${isBanner || ""}`,
|
||||||
null
|
null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,7 +50,7 @@ export async function getArticlePagination(props: PaginationRequest) {
|
||||||
source,
|
source,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return await httpGetInterceptor(
|
return await httpGet(
|
||||||
`/articles?limit=${limit}&page=${page}&title=${search}&startDate=${
|
`/articles?limit=${limit}&page=${page}&title=${search}&startDate=${
|
||||||
startDate || ""
|
startDate || ""
|
||||||
}&endDate=${endDate || ""}&categoryId=${category || ""}&source=${
|
}&endDate=${endDate || ""}&categoryId=${category || ""}&source=${
|
||||||
|
|
@ -59,7 +59,7 @@ export async function getArticlePagination(props: PaginationRequest) {
|
||||||
sortBy || "created_at"
|
sortBy || "created_at"
|
||||||
}&sort=${sort || "asc"}&category=${categorySlug || ""}&isBanner=${
|
}&sort=${sort || "asc"}&category=${categorySlug || ""}&isBanner=${
|
||||||
isBanner || ""
|
isBanner || ""
|
||||||
}`
|
}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,7 +75,7 @@ export async function getTopArticles(props: PaginationRequest) {
|
||||||
}&title=${search}&startDate=${startDate || ""}&endDate=${
|
}&title=${search}&startDate=${startDate || ""}&endDate=${
|
||||||
endDate || ""
|
endDate || ""
|
||||||
}&category=${category || ""}&sortBy=view_count&sort=desc`,
|
}&category=${category || ""}&sortBy=view_count&sort=desc`,
|
||||||
headers
|
headers,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,11 +121,11 @@ export async function deleteArticle(id: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getArticleByCategory() {
|
export async function getArticleByCategory() {
|
||||||
return await httpGetInterceptor(`/article-categories?limit=1000`);
|
return await httpGet(`/article-categories?limit=1000`);
|
||||||
}
|
}
|
||||||
export async function getCategoryPagination(data: any) {
|
export async function getCategoryPagination(data: any) {
|
||||||
return await httpGet(
|
return await httpGet(
|
||||||
`/article-categories?limit=${data?.limit}&page=${data?.page}&title=${data?.search}`
|
`/article-categories?limit=${data?.limit}&page=${data?.page}&title=${data?.search}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,7 +159,7 @@ export async function deleteArticleFiles(id: number) {
|
||||||
|
|
||||||
export async function getUserLevelDataStat(startDate: string, endDate: string) {
|
export async function getUserLevelDataStat(startDate: string, endDate: string) {
|
||||||
return await httpGet(
|
return await httpGet(
|
||||||
`/articles/statistic/user-levels?startDate=${startDate}&endDate=${endDate}`
|
`/articles/statistic/user-levels?startDate=${startDate}&endDate=${endDate}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export async function getStatisticMonthly(year: string) {
|
export async function getStatisticMonthly(year: string) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue