fix landing navbar, dashboard, reset & forgot password

This commit is contained in:
Rama Priyanto 2025-01-24 22:12:23 +07:00
parent c4c0b20bc6
commit 270fa658a7
16 changed files with 828 additions and 254 deletions

View File

@ -8,21 +8,21 @@ import { useEffect, useState } from "react";
export default function Settings() {
const [profile, setProfile] = useState<any>();
useEffect(() => {
const initFetch = async () => {
const profile = await getProfile();
setProfile(profile?.data?.data);
};
initFetch();
}, []);
const initFetch = async () => {
const profile = await getProfile();
setProfile(profile?.data?.data);
};
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">
<div className="flex flex-col bg-gray-50 shadow-md 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 profile={profile} />
<ProfileForm profile={profile} doFetch={() => initFetch()} />
</Tab>
<Tab key="music" title="Password">
<PasswordForm />
<PasswordForm doFetch={() => initFetch()} />
</Tab>
</Tabs>
</div>

View File

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

View File

@ -5,7 +5,7 @@ import { Button } from "@nextui-org/button";
import Link from "next/link";
import Cookies from "js-cookie";
import { close, error, loading } from "@/config/swal";
import { getProfile, postSignIn } from "@/service/master-user";
import { getProfile, postSignIn, resetPassword } from "@/service/master-user";
import { useRouter, useSearchParams } from "next/navigation";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
@ -17,18 +17,37 @@ export default function Login() {
const [isVisible, setIsVisible] = useState([false, false]);
const searchParams = useSearchParams();
const userId = searchParams.get("id");
const code = searchParams.get("code");
const [passwordConf, setPasswordConf] = useState("");
const [password, setPassword] = useState("");
const [isValidPassword, setIsValidPassword] = useState(false);
const onSubmit = async () => {
const data = {
codeRequest: code,
userId: String(userId),
password: password,
confirmPassword: passwordConf,
};
console.log("data", data);
const res = await resetPassword(data);
if (res?.error) {
error(res?.message);
return false;
}
MySwal.fire({
title: "Sukses reset password",
icon: "success",
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Oke",
}).then((result) => {
if (result.isConfirmed) {
router.push("/auth");
}
});
};
const MySwal = withReactContent(Swal);

View File

@ -6,7 +6,7 @@ import { Button } from "@nextui-org/button";
import Link from "next/link";
import Cookies from "js-cookie";
import { close, error, loading } from "@/config/swal";
import { getProfile, postSignIn } from "@/service/master-user";
import { checkUsernames, getProfile, postSignIn } from "@/service/master-user";
import { useRouter } from "next/navigation";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
@ -105,6 +105,11 @@ export default function Login() {
const MySwal = withReactContent(Swal);
const checkUsername = async () => {
const res = await checkUsernames(checkUsernameValue);
if (res?.error) {
error("Username tidak ditemukan");
return false;
}
MySwal.fire({
title: "",
text: "",

View File

@ -6,8 +6,10 @@ import { Input } from "@nextui-org/input";
import { EyeFilledIcon, EyeSlashFilledIcon } from "@/components/icons";
import { Button } from "@nextui-org/button";
import PasswordChecklist from "react-password-checklist";
import { savePassword } from "@/service/master-user";
import { close, error, loading } from "@/config/swal";
export default function PasswordForm() {
export default function PasswordForm(props: { doFetch: () => void }) {
const MySwal = withReactContent(Swal);
const [isVisible, setIsVisible] = useState([false, false]);
@ -16,11 +18,27 @@ export default function PasswordForm() {
const [isValidPassword, setIsValidPassword] = useState(false);
const onSubmit = async () => {
loading();
const data = {
password: password,
confirmPassword: passwordConf,
};
console.log("data", data);
const res = await savePassword(data);
if (res?.error) {
error(res.message);
return false;
}
close();
props.doFetch();
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
}
});
};
const generatePassword = () => {
@ -152,7 +170,7 @@ export default function PasswordForm() {
onChange={(isValid) => {
setIsValidPassword(isValid);
}}
className="text-white text-sm my-3"
className="text-black dark:text-white text-sm my-3"
messages={{
minLength: "Password must be more than 8 characters",
specialChar: "Password must include a special character",

View File

@ -8,6 +8,8 @@ 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";
import { updateProfile } from "@/service/master-user";
import { close, error, loading } from "@/config/swal";
const formSchema = z.object({
fullname: z.string().min(1, {
@ -38,7 +40,10 @@ const formSchema = z.object({
}),
});
export default function ProfileForm(props: { profile: any }) {
export default function ProfileForm(props: {
profile: any;
doFetch: () => void;
}) {
const MySwal = withReactContent(Swal);
const { profile } = props;
const formOptions = {
@ -46,15 +51,10 @@ export default function ProfileForm(props: { profile: any }) {
};
type UserSettingSchema = z.infer<typeof formSchema>;
const {
register,
control,
handleSubmit,
formState: { errors },
setValue,
getValues,
watch,
setError,
clearErrors,
} = useForm<UserSettingSchema>(formOptions);
useEffect(() => {
@ -68,7 +68,34 @@ export default function ProfileForm(props: { profile: any }) {
}, [profile]);
const onSubmit = async (values: z.infer<typeof formSchema>) => {
console.log("values", values);
loading();
const req = {
address: values.address,
fullname: values.fullname,
username: values.username,
email: values.email,
identityNumber: values.nrp,
phoneNumber: values.phoneNumber,
genderType: values.gender,
userLevelId: profile.userLevelId,
userRoleId: profile.userRoleId,
};
const res = await updateProfile(req);
close();
if (res?.error) {
error(res.message);
return false;
}
props.doFetch();
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
}
});
};
return (

View File

@ -69,7 +69,7 @@ export const DashboardMailboxIcon = ({
<rect width="8" height="3" x="9" y="20" fill="#3dd9eb" />
</svg>
);
export const DashboardArticle = ({
export const DashboardShareIcon = ({
size,
height = 48,
width = 48,
@ -81,11 +81,11 @@ export const DashboardArticle = ({
height={size || height}
width={size || width}
{...props}
viewBox="0 0 20 20"
viewBox="0 0 512 512"
>
<path
fill="currentColor"
d="M3 17a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2zM15 5h-5V4h5zm0 2h-5V6h5zm0 2h-5V8h5zM5 14h10v1H5zm0-2h10v1H5zm0-2h10v1H5zm0-6h4v5H5z"
d="M503.691 189.836L327.687 37.851C312.281 24.546 288 35.347 288 56.015v80.053C127.371 137.907 0 170.1 0 322.326c0 61.441 39.581 122.309 83.333 154.132c13.653 9.931 33.111-2.533 28.077-18.631C66.066 312.814 132.917 274.316 288 272.085V360c0 20.7 24.3 31.453 39.687 18.164l176.004-152c11.071-9.562 11.086-26.753 0-36.328"
/>
</svg>
);
@ -176,9 +176,39 @@ export const DashboardRightDownPointIcon = ({
>
<path
fill="currentColor"
fill-rule="evenodd"
fillRule="evenodd"
d="M5.47 5.47a.75.75 0 0 1 1.06 0l10.72 10.72V9a.75.75 0 0 1 1.5 0v9a.75.75 0 0 1-.75.75H9a.75.75 0 0 1 0-1.5h7.19L5.47 6.53a.75.75 0 0 1 0-1.06"
clip-rule="evenodd"
clipRule="evenodd"
/>
</svg>
);
export const DashboardCommentIcon = ({
size,
height = 24,
width = 24,
fill = "currentColor",
...props
}: IconSvgProps) => (
<svg
xmlns="http://www.w3.org/2000/svg"
height={size || height}
width={size || width}
{...props}
viewBox="0 0 48 48"
>
<defs>
<mask id="ipSComment0">
<g
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="4"
>
<path fill="#fff" stroke="#fff" d="M44 6H4v30h9v5l10-5h21z" />
<path stroke="#000" d="M14 19.5v3m10-3v3m10-3v3" />
</g>
</mask>
</defs>
<path fill="currentColor" d="M0 0h48v48H0z" mask="url(#ipSComment0)" />
</svg>
);

View File

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

View File

@ -18,7 +18,7 @@ import {
} from "@nextui-org/react";
import Image from "next/image";
import Link from "next/link";
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
import {
ChevronDownIcon,
ChevronRightIcon,
@ -104,15 +104,31 @@ export default function NavbarHumas(props: { size: string }) {
/>
);
// useEffect(() => {
// const handleScroll = () => {
// setIsScrolled(window.scrollY > 0);
// };
// window.addEventListener("scroll", handleScroll);
// return () => {
// window.removeEventListener("scroll", handleScroll);
// };
// }, []);
useEffect(() => {
const mainElement = document.querySelector("main");
const handleScroll = () => {
setIsScrolled(window.scrollY > 0);
if (mainElement) {
setIsScrolled(mainElement.scrollTop > 0);
}
};
window.addEventListener("scroll", handleScroll);
mainElement?.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
mainElement?.removeEventListener("scroll", handleScroll);
};
}, []);
@ -178,8 +194,8 @@ export default function NavbarHumas(props: { size: string }) {
}
>
<div className="flex flex-row gap-2">
<img src="at4.png" />
<div className="flex flex-col px-2">
<img src="at4.png" className="w-[35px]" />
<div className="flex flex-col ">
<p className="text-[16px] font-bold">SP2HP</p>
<p className="text-ellipsis text-[12px] font-light overflow-hidden">
Pelayanan Surat Pemberitahuan Perkembangan Hasil
@ -190,8 +206,8 @@ export default function NavbarHumas(props: { size: string }) {
</DropdownItem>
<DropdownItem>
<div className="flex flex-row gap-2">
<img src="pm2.png" />
<div className="flex flex-col px-2">
<img src="pm2.png" className="w-[35px]" />
<div className="flex flex-col ">
<p className="text-[16px] font-bold">
Formulir Permohonan Informasi
</p>
@ -210,8 +226,8 @@ export default function NavbarHumas(props: { size: string }) {
}
>
<div className="flex flex-row gap-2">
<img src="pm3.png" />
<div className="flex flex-col px-2">
<img src="pm3.png" className="w-[35px]" />
<div className="flex flex-col ">
<p className="text-[16px] font-bold">Pelayanan SIM</p>
<p className="text-ellipsis text-[12px] font-light overflow-hidden">
Pelayanan Untuk Pendaftaran SIM dan Perpanjangan SIM
@ -224,8 +240,8 @@ export default function NavbarHumas(props: { size: string }) {
onPress={() => window.open("https://erikkes.id/", "_blank")}
>
<div className="flex flex-row gap-2">
<img src="pm4.png" />
<div className="flex flex-col px-2">
<img src="pm4.png" className="w-[35px]" />
<div className="flex flex-col ">
<p className="text-[16px] font-bold">
Pelayanan e-Rikkes SIM
</p>
@ -240,8 +256,8 @@ export default function NavbarHumas(props: { size: string }) {
onPress={() => window.open("https://eppsi.id/", "_blank")}
>
<div className="flex flex-row gap-2">
<img src="pm5.png" />
<div className="flex flex-col px-2">
<img src="pm5.png" className="w-[35px]" />
<div className="flex flex-col ">
<p className="text-[16px] font-bold">
Pelayanan Test Psikologi SIM
</p>
@ -260,7 +276,7 @@ export default function NavbarHumas(props: { size: string }) {
}
>
<div className="flex flex-row gap-2">
<img src="pm6.png" />
<img src="pm6.png" className="w-[35px]" />
<div className="flex flex-col">
<p className="text-[16px] font-bold">Pelayanan e-Avis</p>
<p className="text-ellipsis text-[12px] font-light overflow-hidden">
@ -275,7 +291,7 @@ export default function NavbarHumas(props: { size: string }) {
}
>
<div className="flex flex-row gap-2">
<img src="pm7.png" />
<img src="pm7.png" className="w-[35px]" />
<div className="flex flex-col">
<p className="text-[16px] font-bold">
Pelayanan Samsat Digital
@ -292,7 +308,7 @@ export default function NavbarHumas(props: { size: string }) {
}
>
<div className="flex flex-row gap-2">
<img src="pm8.png" />
<img src="pm8.png" className="w-[35px]" />
<div className="flex flex-col">
<p className="text-[16px] font-bold">Pelayanan SKCK</p>
<p className="text-ellipsis text-[12px] font-light overflow-hidden">
@ -310,7 +326,7 @@ export default function NavbarHumas(props: { size: string }) {
}
>
<div className="flex flex-row gap-2">
<img src="pm9.png" />
<img src="pm9.png" className="w-[35px]" />
<div className="flex flex-col">
<p className="text-[16px] font-bold">
Pelayanan Propam Presisi
@ -328,7 +344,7 @@ export default function NavbarHumas(props: { size: string }) {
}
>
<div className="flex flex-row gap-2">
<img src="pm10.png" />
<img src="pm10.png" className="w-[35px]" />
<div className="flex flex-col">
<p className="text-[16px] font-bold">
Pelayanan Dumas Presisi{" "}
@ -346,7 +362,7 @@ export default function NavbarHumas(props: { size: string }) {
}
>
<div className="flex flex-row gap-2">
<img src="pm11.png" />
<img src="pm11.png" className="w-[35px]" />
<div className="flex flex-col">
<p className="text-[16px] font-bold">Pelayanan Binmas </p>
<p className="text-ellipsis text-[12px] font-light overflow-hidden">
@ -365,7 +381,7 @@ export default function NavbarHumas(props: { size: string }) {
}
>
<div className="flex flex-row gap-2">
<img src="pm12.png" />
<img src="pm12.png" className="w-[35px]" />
<div className="flex flex-col">
<p className="text-[16px] font-bold">
Clean & Clear Polri{" "}
@ -669,7 +685,7 @@ export default function NavbarHumas(props: { size: string }) {
>
<div className="flex flex-row gap-2">
<img src="at1.png" />
<div className="flex flex-col px-2">
<div className="flex flex-col ">
<p className="text-[16px] font-bold">Polri Super App</p>
<p className="text-ellipsis text-[12px] font-light overflow-hidden">
Aplikasi Layanan Perpanjangan SIM, Pembayaran STNK,
@ -688,7 +704,7 @@ export default function NavbarHumas(props: { size: string }) {
>
<div className="flex flex-row gap-2">
<img src="at2.png" />
<div className="flex flex-col px-2">
<div className="flex flex-col ">
<p className="text-[16px] font-bold">Media Hub</p>
<p className="text-ellipsis text-[12px] font-light overflow-hidden">
Humas Polri dalam Data
@ -706,7 +722,7 @@ export default function NavbarHumas(props: { size: string }) {
>
<div className="flex flex-row gap-2">
<img src="at3.png" />
<div className="flex flex-col px-2">
<div className="flex flex-col ">
<p className="text-[16px] font-bold">Polisiku</p>
<p className="text-ellipsis text-[12px] font-light overflow-hidden">
Membantu anggota Kepolisian untuk mengindetifikasi
@ -725,7 +741,7 @@ export default function NavbarHumas(props: { size: string }) {
>
<div className="flex flex-row gap-2">
<img src="at4.png" />
<div className="flex flex-col px-2">
<div className="flex flex-col ">
<p className="text-[16px] font-bold">SP2HP</p>
<p className="text-ellipsis text-[12px] font-light overflow-hidden">
Surat Pemberitahuan Perkembangan Hasil Penyidikan Online
@ -743,7 +759,7 @@ export default function NavbarHumas(props: { size: string }) {
>
<div className="flex flex-row gap-2">
<img src="at5.png" />
<div className="flex flex-col px-2">
<div className="flex flex-col ">
<p className="text-[16px] font-bold">Polri TV</p>
<p className="text-ellipsis text-[12px] font-light overflow-hidden">
Humas Polri dalam Audio Visual
@ -761,7 +777,7 @@ export default function NavbarHumas(props: { size: string }) {
>
<div className="flex flex-row gap-2">
<img src="at6.png" />
<div className="flex flex-col px-2">
<div className="flex flex-col ">
<p className="text-[16px] font-bold">Polri Radio</p>
<p className="text-ellipsis text-[12px] font-light overflow-hidden">
Humas Polri dalam Audio
@ -792,7 +808,7 @@ export default function NavbarHumas(props: { size: string }) {
>
<div className="flex flex-row gap-2">
<img src="pm4.png" />
<div className="flex flex-col px-2">
<div className="flex flex-col ">
<p className="text-[16px] font-bold">e-Rikkes</p>
<p className="text-ellipsis text-[12px] font-light overflow-hidden">
Pemeriksaan Kesehatan Berbassi Teknologi Secara Online
@ -807,7 +823,7 @@ export default function NavbarHumas(props: { size: string }) {
>
<div className="flex flex-row gap-2">
<img src="pm5.png" />
<div className="flex flex-col px-2">
<div className="flex flex-col ">
<p className="text-[16px] font-bold">e-PPSI</p>
<p className="text-ellipsis text-[12px] font-light overflow-hidden">
Tes Psikologis SIM Secara Online{" "}
@ -990,7 +1006,7 @@ export default function NavbarHumas(props: { size: string }) {
>
<div className="flex flex-row gap-2">
<img src="pm3.png" />
<div className="flex flex-col px-2">
<div className="flex flex-col ">
<p className="text-[16px] font-bold">Sinar</p>
<p className="text-ellipsis text-[12px] font-light overflow-hidden">
Aplikasi dan Website Pelayanan Pendaftaran dan
@ -1089,12 +1105,24 @@ export default function NavbarHumas(props: { size: string }) {
</div>
</div>
</div>
<NavbarContent className="lg:hidden px-2">
<NavbarContent
className={`px-2 lg:hidden transition-all duration-300 ease-in-out backdrop-opacity-10 ${
isScrolled ? "bg-white dark:bg-[#1F1A17] " : "bg-opacity-50"
}`}
>
<div className="flex justify-between w-full">
<Link href={"/"}>
<img src="/logohumas.png" alt="logo" className="w-[78px]" />
</Link>
<div className="flex flex-row gap-3 items-center">
<a
className="cursor-pointer"
onClick={() =>
language === "id" ? setLanguage("en") : setLanguage("id")
}
>
{language === "id" ? <IdnIcon /> : <UKIcon />}
</a>
<ThemeSwitch />
<NavbarMenuToggle />
<NavbarMenu>
@ -1134,7 +1162,7 @@ export default function NavbarHumas(props: { size: string }) {
<span>{item.label}</span>
</Link>
) : (
<span>{item.label}</span>
<span>{t(item.label)}</span>
)}
{item.submenu &&
(dropdownOpen[item.key] ? (
@ -1146,10 +1174,24 @@ export default function NavbarHumas(props: { size: string }) {
)}
</NavbarMenuItem>
{dropdownOpen[item.key] && item.submenu && (
<div className="pl-2">
{item.submenu.map((subItem, subIndex) => (
<div className="grid grid-cols-2 gap-2">
{item.submenu.map((subItem: any, subIndex) => (
<div key={subIndex}>
<Link href={subItem.href}>{subItem.label}</Link>
<Link
href={subItem.href}
target={subItem.blank ? "_black" : ""}
className="flex flex-row gap-2 items-center text-sm"
>
{subItem.img && (
<img
src={subItem.img}
className="w-[35px] h-[35px] object-cover"
/>
)}
{subItem.multi
? t(subItem.multi)
: subItem.label}
</Link>
</div>
))}
</div>

View File

@ -4,35 +4,16 @@ import ReactApexChart from "react-apexcharts";
import dummyData from "../../../../const/dummy.json";
type WeekData = {
week: number; // Minggu ke-
days: number[]; // Data jumlah view per hari dalam minggu tersebut
total: number; // Total jumlah view dalam minggu tersebut
week: number;
days: number[];
total: number;
};
// Struktur untuk sisa hari
type RemainingDays = {
days: number[]; // Data jumlah view untuk sisa hari
total: number; // Total jumlah view untuk sisa hari
days: number[];
total: number;
};
// Struktur data per bulan setelah diolah
type ProcessedMonthlyData = {
id: string; // ID bulan
month: string; // Nama bulan
weeks: WeekData[]; // Data per minggu
remaining_days: RemainingDays; // Data sisa hari
};
// Struktur data input awal
type MonthlyDataInput = {
id: string; // ID bulan
month: string; // Nama bulan
count: number[]; // Jumlah view per hari selama 1 bulan
};
// Struktur array data awal
type MonthlyDataInputArray = MonthlyDataInput[];
function processMonthlyData(count: number[]): {
weeks: WeekData[];
remaining_days: RemainingDays;
@ -40,18 +21,16 @@ function processMonthlyData(count: number[]): {
const weeks: WeekData[] = [];
let weekIndex = 1;
// Kelompokkan data per 7 hari (minggu)
for (let i = 0; i < count.length; i += 7) {
const weekData = count.slice(i, i + 7); // Ambil 7 hari
const weekData = count.slice(i, i + 7);
weeks.push({
week: weekIndex,
days: weekData,
total: weekData.reduce((sum, day) => sum + day, 0), // Total view per minggu
total: weekData.reduce((sum, day) => sum + day, 0),
});
weekIndex++;
}
// Cek sisa hari
const remainingDays: RemainingDays = {
days: count.length % 7 === 0 ? [] : count.slice(-count.length % 7),
total: count.slice(-count.length % 7).reduce((sum, day) => sum + day, 0),
@ -63,10 +42,17 @@ function processMonthlyData(count: number[]): {
};
}
const ApexChartColumn = (props: { type: string; date: string }) => {
const { date, type } = props;
const ApexChartColumn = (props: {
type: string;
date: string;
view: string[];
}) => {
const { date, type, view } = props;
const [categories, setCategories] = useState<string[]>([]);
const [series, setSeries] = useState<number[]>([]);
const [series, setSeries] = useState<{ name: string; data: number[] }[]>([]);
const [seriesVisit, setSeriesVisit] = useState<number[]>([]);
const [seriesView, setSeriesView] = useState<number[]>([]);
const [seriesShare, setSeriesShare] = useState<number[]>([]);
useEffect(() => {
initFetch();
@ -79,86 +65,88 @@ const ApexChartColumn = (props: { type: string; date: string }) => {
(a) => a.month == splitDate[0] && a.year === splitDate[1]
);
if (getDatas) {
const temp = processMonthlyData(getDatas?.count);
setSeries(
temp.weeks.map((list) => {
return list.total;
})
);
const temp1 = processMonthlyData(getDatas?.visit);
const temp2 = processMonthlyData(getDatas?.view);
const temp3 = processMonthlyData(getDatas?.share);
if (type == "weekly") {
setSeriesVisit(
temp1.weeks.map((list) => {
return list.total;
})
);
setSeriesView(
temp2.weeks.map((list) => {
return list.total;
})
);
setSeriesShare(
temp3.weeks.map((list) => {
return list.total;
})
);
} else {
setSeries(getDatas.count);
console.log("sadadad", getDatas.visit, getDatas.view, getDatas.share);
setSeriesVisit(getDatas.visit);
setSeriesView(getDatas.view);
setSeriesShare(getDatas.share);
}
if (type === "weekly") {
const category = [];
for (let i = 1; i <= temp.weeks.length; i++) {
for (let i = 1; i <= temp1.weeks.length; i++) {
category.push(`Week ${i}`);
}
setCategories(category);
}
} else {
setSeries([]);
setSeriesVisit([]);
}
};
const [state, setState] = useState({
series: [
useEffect(() => {
console.log("view", view);
const temp = [
{
name: "Visit",
data: [
15, 23, 19, 14, 18, 20, 22, 17, 21, 19, 23, 16, 25, 20, 18, 19, 22,
24, 15, 18, 21, 26, 28, 23, 17, 20, 19, 22,
],
data: view.includes("visit") ? seriesVisit : [],
},
],
options: {
chart: {
height: 350,
type: "line",
zoom: {
enabled: false,
},
{
name: "View",
data: view.includes("view") ? seriesView : [],
},
dataLabels: {
enabled: false,
},
stroke: {
curve: "straight",
{
name: "Share",
data: view.includes("share") ? seriesShare : [],
},
];
grid: {
row: {
colors: ["#f3f3f3", "transparent"], // takes an array which will be repeated on columns
opacity: 0.5,
},
},
},
});
console.log("temp", temp);
setSeries(temp);
}, [view, seriesShare, seriesView, seriesVisit]);
return (
<div>
<div id="chart">
<div className="h-full">
<div id="chart" className="h-full">
<ReactApexChart
options={{
chart: {
height: 350,
type: "line",
zoom: {
enabled: false,
},
height: "100%",
type: "area",
},
stroke: {
curve: "smooth",
},
dataLabels: {
enabled: false,
},
xaxis: {
categories: type == "weekly" ? categories : [],
},
}}
series={[
{
name: "Visit",
data: series,
},
]}
type="line"
height={350}
series={series}
type="area"
height="100%"
/>
</div>
<div id="html-dist"></div>

View File

@ -1,10 +1,8 @@
"use client";
import {
DashboardArticle,
DashboardBriefcaseIcon,
DashboardCommentIcon,
DashboardConnectIcon,
DashboardMailboxIcon,
DashboardRightDownPointIcon,
DashboardShareIcon,
DashboardSpeecIcon,
DashboardTopLeftPointIcon,
DashboardUserIcon,
@ -12,6 +10,8 @@ import {
import { Submenu1Icon } from "@/components/icons/sidebar-icon";
import {
Button,
Checkbox,
CheckboxGroup,
Pagination,
Select,
SelectItem,
@ -39,6 +39,11 @@ export default function DashboardContainer() {
const [page, setPage] = useState(1);
const [totalPage, setTotalPage] = useState(1);
const [article, setArticle] = useState<ArticleData[]>([]);
const [analyticsView, setAnalyticView] = useState<string[]>([
"visit",
"view",
"share",
]);
const [startDateValue, setStartDateValue] = useState({
startDate: new Date(),
endDate: new Date(),
@ -88,7 +93,7 @@ export default function DashboardContainer() {
<div className="p-8 flex justify-center">
<div className="w-full flex flex-col gap-6">
<div className="w-full flex flex-col md:flex-row gap-6 justify-center">
<div className="px-8 py-4 justify-between w-full md:w-[35%] h-[160px] shadow-md bg-white dark:bg-[#18181b] flex flex-col rounded-lg">
<div className="px-8 py-4 justify-between w-full md:w-[37%] h-[160px] shadow-md bg-white dark:bg-[#18181b] flex flex-col rounded-lg">
<div className="flex justify-between w-full items-center">
<div className="flex flex-col gap-2">
<p className="font-bold text-xl ">{fullname}</p>
@ -98,42 +103,66 @@ export default function DashboardContainer() {
</div>
<div className="flex flex-row gap-5">
<p className="text-lg font-semibold">
4 <span className="text-sm font-normal">Artikel</span>
4 Post <span className="text-sm font-normal">Hari ini</span>
</p>
<p className="text-lg font-semibold">
2 <span className="text-sm font-normal">Majalah</span>
12 Post <span className="text-sm font-normal">Minggu ini</span>
</p>
</div>
</div>
<div className="w-full md:w-[25%] h-[160px] shadow-md bg-white dark:bg-[#18181b] flex flex-col justify-center items-center rounded-lg">
<div className="h-1/2 flex items-center justify-center">
<DashboardArticle />
</div>
<div className="">Minggu ini</div>
<div className="font-semibold text-lg">24</div>
</div>
<div className="w-full md:w-[20%] h-[160px] shadow-md bg-white dark:bg-[#18181b] flex flex-col justify-center items-center rounded-lg">
<div className="h-1/2 flex items-center justify-center">
<DashboardSpeecIcon />
</div>
<div className="">Total post</div>
<div className="font-semibold text-lg">154</div>
<div className="font-semibold text-lg">121</div>
</div>
<div className="w-full md:w-[20%] h-[160px] shadow-md bg-white dark:bg-[#18181b] flex flex-col justify-center items-center rounded-lg">
<div className="w-full md:w-[15%] h-[160px] shadow-md bg-white dark:bg-[#18181b] flex flex-col justify-center items-center rounded-lg">
<div className="h-1/2 flex items-center justify-center">
<DashboardConnectIcon />
</div>
<div className="">Total views</div>
<div className="font-semibold text-lg">154</div>
</div>
<div className="w-full md:w-[15%] h-[160px] shadow-md bg-white dark:bg-[#18181b] flex flex-col justify-center items-center rounded-lg">
<div className="h-1/2 flex items-center justify-center">
<DashboardShareIcon />
</div>
<div className="">Total share</div>
<div className="font-semibold text-lg">154</div>
</div>
<div className="w-full md:w-[15%] h-[160px] shadow-md bg-white dark:bg-[#18181b] flex flex-col justify-center items-center rounded-lg">
<div className="h-1/2 flex items-center justify-center">
<DashboardCommentIcon size={50} />
</div>
<div className="">Total comment</div>
<div className="font-semibold text-lg">530</div>
</div>
</div>
<div className="w-full flex flex-row gap-6 justify-center ">
<div className="border-1 shadow-sm w-full rounded-lg md:w-[60%] p-6 flex flex-col">
<div className="border-1 shadow-sm w-full rounded-lg md:w-[55%] p-6 flex flex-col">
<div className="flex justify-between mb-3">
<div className="font-semibold flex flex-col">
Analytics
<span className="font-normal text-xs text-gray-600">Views</span>
<div className="font-normal text-xs text-gray-600 flex flex-row gap-2">
<CheckboxGroup
label=""
value={analyticsView}
orientation="horizontal"
onValueChange={setAnalyticView}
>
<Checkbox size="sm" value="visit">
Visit
</Checkbox>
<Checkbox size="sm" value="view" color="success">
View
</Checkbox>
<Checkbox size="sm" value="share" color="warning">
Share
</Checkbox>
</CheckboxGroup>
</div>
</div>
<div className="flex flex-row gap-2">
<Button
@ -162,16 +191,17 @@ export default function DashboardContainer() {
</div>
</div>
</div>
<div className="flex flex-row w-full">
<div className="w-full">
<div className="flex flex-row w-full h-full">
<div className="w-full h-full">
<ApexChartColumn
type={typeDate}
date={getMonthYear(startDateValue.startDate)}
view={analyticsView}
/>
</div>
</div>
</div>
<div className="flex flex-col w-[40%] gap-6 shadow-md bg-white dark:bg-[#18181b] rounded-lg p-8">
<div className="flex flex-col w-[45%] gap-6 shadow-md bg-white dark:bg-[#18181b] rounded-lg p-8">
<div className="flex justify-between">
<p>Recent Article</p>
<Link href="/admin/article/create">

View File

@ -1,38 +1,190 @@
import { Button } from "@nextui-org/button";
import { Textarea } from "@nextui-org/input";
import React from "react";
import { Input, Textarea } from "@nextui-org/input";
import React, { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import * as z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { otpRequest, otpValidation } from "@/service/master-user";
import { error } from "@/config/swal";
const commentSchema = z.object({
name: z.string().min(1, {
message: "Judul harus diisi",
}),
email: z
.string()
.email({
message: "Email tidak valid",
})
.min(1, {
message: "Harus diisi",
}),
comment: z.string().min(1, {
message: "Deskripsi harus diisi",
}),
});
export default function Comment() {
const [needOtp, setNeedOtp] = useState(false);
const [otpValue, setOtpValue] = useState("");
const formOptions = {
resolver: zodResolver(commentSchema),
};
type UserSettingSchema = z.infer<typeof commentSchema>;
const {
control,
handleSubmit,
formState: { errors },
setValue,
} = useForm<UserSettingSchema>(formOptions);
const onSubmit = async (values: z.infer<typeof commentSchema>) => {
if (!needOtp) {
const res = await otpRequest(values.email);
if (res?.error) {
error(res.message);
return false;
}
setNeedOtp(true);
} else {
const validation = await otpValidation(values.email, otpValue);
if (validation?.error) {
error("OTP Tidak Sesuai");
return false;
}
const req = {
email: values.email,
name: values.name,
comment: values.comment,
};
console.log("req", req);
}
};
return (
<div className="p-3 lg:p-10 space-y-2 md:spacey-3 lg:space-y-5">
<div>
<b>Berikan Komentar</b>
<form className="p-3 flex flex-col gap-3" onSubmit={handleSubmit(onSubmit)}>
<b>Tinggalkan balasan</b>
<p className="text-xs">
Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai{" "}
<span className="text-red-600">*</span>
</p>
<div className="flex flex-col gap-1">
<p className="text-sm">Komentar</p>
<Controller
control={control}
name="comment"
render={({ field: { onChange, value } }) => (
<Textarea
type="text"
id="comment"
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?.comment && (
<p className="text-red-400 text-sm mb-3">{errors.comment?.message}</p>
)}
</div>
<Textarea
variant="bordered"
placeholder="Masukkan Komentar Anda di sini.."
className="text-black"
classNames={{
input: ["w-full", "bg-transparent", "!text-black"],
mainWrapper: ["w-full", "bg-transparent"],
innerWrapper: ["bg-transparent"],
inputWrapper: [
"bg-transparent",
"dark:bg-transparent",
"!border-1 border-gray-300",
"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",
"border-1",
],
}}
/>
<Button className="bg-[#DD8306] text-white" radius="none">
<div className="flex flex-col gap-1">
<p className="text-sm">
Nama <span className="text-red-600">*</span>
</p>
<Controller
control={control}
name="name"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="name"
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?.name && (
<p className="text-red-400 text-sm mb-3">{errors.name?.message}</p>
)}
</div>
<div className="flex flex-col gap-1">
<p className="text-sm">
Email <span className="text-red-600">*</span>
</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>
{needOtp && (
<div className="flex flex-col gap-1">
<p>OTP</p>
<Input
type="number"
id="otp"
placeholder=""
label=""
value={otpValue}
onValueChange={setOtpValue}
labelPlacement="outside"
className="w-[100px] "
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
variant="bordered"
/>
</div>
)}
<Button className="bg-[#DD8306] text-white" radius="md" type="submit">
Kirim
</Button>
</div>
</form>
);
}

View File

@ -27,100 +27,294 @@ export const siteConfig = {
},
],
humasMenuItems: [
{
key: "home",
label: "Beranda",
href: "/",
},
{
key: "about",
label: "Tentang",
submenu: [
{
label: "Tentang Humas POLRI",
href: "/tentang-humas-polri",
},
{
label: "Profile Pimpinan POLRI",
href: "/profile-pimpinan-polri",
},
{
label: "Struktur Organisasi",
href: "/struktur-organisasi",
},
{
label: "Visi dan Misi",
href: "/visi-misi",
},
{
label: "Tugas dan Fungsi",
href: "/tugas-dan-fungsi",
},
{
label: "Logo",
href: "#",
},
],
},
{
key: "ppid",
label: "Portal PPID",
href: "/portal-ppid",
},
{
key: "service",
label: "Pelayanan Masyarakat",
label: "pelayananMasyarakat",
submenu: [
{
label: "SP2HP",
href: "https://sp2hp.bareskrim.polri.go.id/",
img: "at4.png",
blank: true,
},
{
label: "Formulir Permohonan Informasi",
href: "/form-permohonan-informasi",
href: "#",
img: "pm2.png",
blank: false,
},
{
label: "Pelayanan SIM",
href: "https://www.digitalkorlantas.id/sim/",
img: "pm3.png",
blank: true,
},
{
label: "Pelayanan e-Rikkes SIM",
href: "https://erikkes.id/",
img: "pm4.png",
blank: true,
},
{
label: "Pelayanan Tes Psikologi SIM",
label: "Pelayanan Test Psikologi SIM",
href: "https://eppsi.id/",
img: "pm5.png",
blank: true,
},
{
label: "Pelayanan e-Avis",
href: "https://e-avis.korlantas.polri.go.id/",
img: "pm6.png",
blank: true,
},
{
label: "Pelayanan Samsat Digital",
href: "https://samsatdigital.id/",
img: "pm7.png",
blank: true,
},
{
label: "Pelayanan SKCK",
href: "https://play.google.com/store/apps/details?id=superapps.polri.presisi.presisi&hl=en_US",
href: "https://skck.polri.go.id/",
img: "pm8.png",
blank: true,
},
{
label: "Pelayanan Propam Presisi",
href: "https://play.google.com/store/apps/details?id=com.stk.pengaduanpropam",
href: "https://play.google.com/store/apps/details?id=com.stk.pengaduanpropam&pli=1",
img: "pm9.png",
blank: true,
},
{
label: "Pelayanan Dumas Presisi",
href: "https://dumaspresisi.polri.go.id/",
img: "pm10.png",
blank: true,
},
{
label: "Pelayanan Binmas",
href: "https://bos.polri.go.id/login",
img: "pm11.png",
blank: true,
},
{
label: "Wistle Blower System",
href: "https://play.google.com/store/apps/details?id=id.go.ssdmpolri.pengaduanappsbarupolri2",
label: "Clean & Clear Polri",
href: "#",
img: "pm12.png",
blank: true,
},
],
},
{
key: "contact",
label: "Kontak",
href: "/kontak-kami",
key: "public-information",
label: "informasi_publik",
submenu: [
{
label: "Humas Polri",
href: "/static/humas-polri",
},
{
label: "Profil Pimpinan Polri",
href: "/static/profile-kapolri",
},
{
label: "Struktur Organisasi",
href: "/static/struktur-mabes",
},
{
label: "Visi & Misi",
href: "/static/visi-misi-polri",
},
{
label: "Tugas & Fungsi",
href: "/static/tugas-dan-fungsi-polri",
},
{
label: "Logo",
href: "/static/logo",
},
],
},
{
key: "publication",
label: "publikasi",
submenu: [
{
label: "Humas Polri",
href: "/static/humas-polri",
},
{
label: "Profil Pimpinan Polri",
href: "/static/profile-kapolri",
},
{
label: "Struktur Organisasi",
href: "/static/struktur-mabes",
},
{
label: "Visi & Misi",
href: "/static/visi-misi-polri",
},
{
label: "Tugas & Fungsi",
href: "/static/tugas-dan-fungsi-polri",
},
{
label: "Logo",
href: "/static/logo",
},
],
},
{
key: "about",
label: "tentang",
submenu: [
{
label: "Humas Polri",
href: "/static/humas-polri",
},
{
label: "Profil Pimpinan Polri",
href: "/static/profile-kapolri",
},
{
label: "Struktur Organisasi",
href: "/static/struktur-mabes",
},
{
label: "Visi & Misi",
href: "/static/visi-misi-polri",
},
{
label: "Tugas & Fungsi",
href: "/static/tugas-dan-fungsi-polri",
},
{
label: "Logo",
href: "/static/logo",
},
],
},
{
key: "related-app",
label: "aplikasi_terkait",
submenu: [
{
label: "Polri Super App",
href: "https://play.google.com/store/apps/details?id=superapps.polri.presisi.presisi&hl=en_US&gl=US",
img: "at1.png",
blank: true,
},
{
label: "Media Hub",
href: "https://play.google.com/store/apps/details?id=com.mediahub.mediahub_mobile",
img: "at2.png",
blank: true,
},
{
label: "Polisiku",
href: "https://play.google.com/store/apps/details?id=id.co.qlue.polisiku&hl=id&gl=ID",
img: "at3.png",
blank: true,
},
{
label: "SP2HP",
href: "https://sp2hp.bareskrim.polri.go.id/",
img: "at4.png",
blank: true,
},
{
label: "Polri TV",
href: "https://play.google.com/store/apps/details?id=com.polritv",
img: "at5.png",
blank: true,
},
{
label: "Polri Radio",
href: "https://play.google.com/store/apps/details?id=com.polritv",
img: "at6.png",
blank: true,
},
{
label: "e-Avis",
href: "https://e-avis.korlantas.polri.go.id/",
img: "pm6.png",
blank: true,
},
{
label: "e-Rikkes",
href: "https://erikkes.id/",
img: "pm4.png",
blank: true,
},
{
label: "e-PPSI",
href: "https://eppsi.id/",
img: "pm5.png",
blank: true,
},
{
label: "BOS",
href: "https://bos.polri.go.id/login",
img: "pm11.png",
blank: true,
},
{
label: "Signal",
href: "https://play.google.com/store/apps/details?id=app.signal.id",
img: "pm7.png",
blank: true,
},
{
label: "SKCK Online",
href: "https://skck.polri.go.id/",
img: "pm8.png",
blank: true,
},
{
label: "Propam Presisi",
href: "https://play.google.com/store/apps/details?id=com.stk.pengaduanpropam",
img: "pm9.png",
blank: true,
},
{
label: "Monitoring Presisi",
href: "https://play.google.com/store/apps/details?id=com.stk.pengaduanpropam",
img: "at14.png",
blank: true,
},
{
label: "SDM - Penerimaan POLRI",
href: "https://penerimaan.polri.go.id/",
img: "at15.png",
blank: true,
},
{
label: "Whistle Blowing System",
href: "https://pengaduan-penerimaan.polri.go.id/",
img: "at16.png",
blank: true,
},
{
label: "Dumas Presisi",
href: "https://play.google.com/store/apps/details?id=com.admasolusi.monitoringpresisi",
img: "pm10.png",
blank: true,
},
{
label: "Clean & Clear Polri",
href: "#",
img: "pm12.png",
blank: true,
},
{
label: "Sinar",
href: "https://www.digitalkorlantas.id/sim/",
img: "pm3.png",
blank: true,
},
],
},
{
key: "dashboard",

View File

@ -4,27 +4,51 @@
"id": "1",
"year": "2024",
"month": "november",
"count": [
"visit": [
14, 32, 10, 21, 15, 18, 24, 30, 12, 25, 19, 28, 14, 17, 22, 31, 27, 13,
20, 24, 29, 18, 21, 26, 23, 14, 19, 17, 28, 22
],
"view": [
20, 24, 29, 18, 21, 26, 23, 14, 19, 17, 28, 22, 14, 32, 10, 21, 15, 18,
24, 30, 12, 25, 19, 28, 14, 17, 22, 31, 27, 13
],
"share": [
24, 30, 12, 25, 19, 28, 14, 2, 31, 27, 13, 17, 22, 31, 27, 13, 18, 21,
26, 23, 14, 18, 24, 30, 12, 25, 19, 28, 14, 17
]
},
{
"id": "2",
"year": "2024",
"month": "december",
"count": [
"visit": [
15, 23, 19, 14, 18, 20, 22, 17, 21, 19, 23, 16, 25, 20, 18, 19, 22, 24,
15, 18, 21, 26, 28, 23, 17, 20, 19, 22, 22, 42, 32
],
"view": [
20, 24, 29, 18, 21, 26, 23, 14, 19, 17, 28, 22, 14, 32, 10, 21, 15, 18,
24, 30, 12, 25, 19, 28, 14, 17, 22, 31, 27, 13, 21
],
"share": [
14, 32, 10, 21, 15, 18, 24, 30, 12, 25, 19, 28, 14, 17, 22, 31, 27, 13,
20, 24, 29, 18, 21, 26, 23, 14, 19, 17, 28, 22, 32
]
},
{
"id": "3",
"year": "2025",
"month": "january",
"count": [
"visit": [
21, 24, 19, 27, 29, 23, 18, 20, 26, 22, 24, 30, 25, 19, 17, 21, 27, 23,
29, 25, 22
],
"view": [
21, 24, 19, 25, 19, 17, 21, 27, 23, 29, 25, 22, 27, 29, 23, 18, 20, 26,
22, 24, 30
],
"share": [
22, 24, 30, 25, 19, 17, 21, 27, 23, 29, 25, 22, 21, 24, 19, 27, 29, 23,
18, 20, 26
]
}
]

View File

@ -2,10 +2,12 @@ import {
httpDeleteInterceptor,
httpGet,
httpPost,
httpPut,
} from "./http-config/axios-base-service";
import Cookies from "js-cookie";
const token = Cookies.get("access_token");
const id = Cookies.get("uie");
export async function listMasterUsers(data: any) {
const headers = {
@ -41,3 +43,46 @@ export async function getProfile(code?: string) {
};
return await httpGet(`/users/info`, headers);
}
export async function updateProfile(data: any) {
const headers = {
"content-type": "application/json",
Authorization: `Bearer ${token}`,
};
return await httpPut(`/users/${id}`, headers, data);
}
export async function savePassword(data: any) {
const headers = {
"content-type": "application/json",
Authorization: `Bearer ${token}`,
};
return await httpPost(`/users/save-password`, headers, data);
}
export async function resetPassword(data: any) {
const headers = {
"content-type": "application/json",
};
return await httpPost(`/users/reset-password`, headers, data);
}
export async function checkUsernames(username: string) {
const headers = {
"content-type": "application/json",
};
return await httpPost(`/users/forgot-password`, headers, { username });
}
export async function otpRequest(email: string) {
const headers = {
"content-type": "application/json",
};
return await httpPost(`/users/otp-request`, headers, { email });
}
export async function otpValidation(email: string, otpCode: string) {
const headers = {
"content-type": "application/json",
};
return await httpPost(`/users/otp-validation`, headers, { email, otpCode });
}

View File

@ -84,11 +84,11 @@
}
body {
height: 100%; /* Pastikan tinggi body penuh */
overflow: hidden; /* Nonaktifkan scrolling global */
height: 100%;
overflow: hidden;
}
main {
height: 100vh; /* Pastikan tinggi main sesuai viewport */
overflow-y: auto; /* Aktifkan scrolling hanya di elemen main */
height: 100vh;
overflow-y: auto;
}