From 2af3b88fc5aff2af8532363e11ff06ea6835e8aa Mon Sep 17 00:00:00 2001 From: Anang Yusman Date: Thu, 26 Dec 2024 18:29:22 +0700 Subject: [PATCH 1/4] feat:add notification, profile --- .../notifications/components/columns.tsx | 122 +++++ .../components/notifications-table.tsx | 138 +++++ .../(protected)/notifications/layout.tsx | 9 + .../(protected)/notifications/page.tsx | 33 ++ .../(public)/profile/change-password/page.tsx | 193 +++++++ .../(public)/profile/change-profile/page.tsx | 96 ++++ app/[locale]/(public)/profile/layout.tsx | 22 + app/[locale]/(public)/profile/page.tsx | 301 +++++++++++ .../schedule/press-conference-update-form.tsx | 29 +- components/landing-page/navbar.tsx | 482 ++++++++++++++++-- components/partials/header/index.tsx | 6 +- components/partials/header/notifications.tsx | 222 +++++--- components/partials/header/profile-info.tsx | 61 +-- service/auth.ts | 29 +- service/notifications/notifications.ts | 6 + 15 files changed, 1573 insertions(+), 176 deletions(-) create mode 100644 app/[locale]/(protected)/notifications/components/columns.tsx create mode 100644 app/[locale]/(protected)/notifications/components/notifications-table.tsx create mode 100644 app/[locale]/(protected)/notifications/layout.tsx create mode 100644 app/[locale]/(protected)/notifications/page.tsx create mode 100644 app/[locale]/(public)/profile/change-password/page.tsx create mode 100644 app/[locale]/(public)/profile/change-profile/page.tsx create mode 100644 app/[locale]/(public)/profile/layout.tsx create mode 100644 app/[locale]/(public)/profile/page.tsx create mode 100644 service/notifications/notifications.ts diff --git a/app/[locale]/(protected)/notifications/components/columns.tsx b/app/[locale]/(protected)/notifications/components/columns.tsx new file mode 100644 index 00000000..3838aaf3 --- /dev/null +++ b/app/[locale]/(protected)/notifications/components/columns.tsx @@ -0,0 +1,122 @@ +import * as React from "react"; +import { ColumnDef } from "@tanstack/react-table"; + +import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuTrigger, + DropdownMenuItem, +} from "@/components/ui/dropdown-menu"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { format } from "date-fns"; +import { Link } from "@/components/navigation"; + +const columns: ColumnDef[] = [ + { + accessorKey: "no", + header: "No", + cell: ({ row }) => {row.getValue("no")}, + }, + { + accessorKey: "title", + header: "Title", + cell: ({ row }) => ( + {row.getValue("title")} + ), + }, + { + accessorKey: "categoryName", + header: "Category", + cell: ({ row }) => {row.getValue("categoryName")}, + }, + { + accessorKey: "createdAt", + header: "Upload Date", + cell: ({ row }) => { + const createdAt = row.getValue("createdAt") as + | string + | number + | undefined; + + const formattedDate = + createdAt && !isNaN(new Date(createdAt).getTime()) + ? format(new Date(createdAt), "dd-MM-yyyy HH:mm:ss") + : "-"; + return {formattedDate}; + }, + }, + { + accessorKey: "tags", + header: "Tag", + cell: ({ row }) => {row.getValue("tags")}, + }, + { + accessorKey: "statusName", + header: "Status", + cell: ({ row }) => { + const statusColors: Record = { + diterima: "bg-green-100 text-green-600", + "menunggu review": "bg-orange-100 text-orange-600", + }; + + // Mengambil `statusName` dari data API + const status = row.getValue("statusName") as string; + const statusName = status?.toLocaleLowerCase(); // Ubah ke huruf kecil + + // Gunakan `statusName` untuk pencocokan + const statusStyles = + statusColors[statusName] || "bg-gray-100 text-gray-600"; + + return ( + + {status} {/* Tetap tampilkan nilai asli */} + + ); + }, + }, + + { + id: "actions", + accessorKey: "action", + header: "Actions", + enableHiding: false, + cell: ({ row }) => { + return ( + + + + + + + + + View + + + + + + Edit + + + + + Delete + + + + ); + }, + }, +]; + +export default columns; diff --git a/app/[locale]/(protected)/notifications/components/notifications-table.tsx b/app/[locale]/(protected)/notifications/components/notifications-table.tsx new file mode 100644 index 00000000..d0b3c261 --- /dev/null +++ b/app/[locale]/(protected)/notifications/components/notifications-table.tsx @@ -0,0 +1,138 @@ +"use client"; + +import React, { useState, useEffect } from "react"; +import { Button } from "@/components/ui/button"; +import { getNotifications } from "@/service/notifications/notifications"; +import { + CalendarCheck, + CheckCheck, + ChevronLeft, + ChevronRight, + CircleAlert, + Clock7, + MessageCircle, + SquareCheck, + UploadIcon, +} from "lucide-react"; +import { useRouter } from "next/navigation"; + +export type Notification = { + id: number; + notificationTypeId: number; + message: string; + createdAt: string; + isActive: boolean; + isPublic: boolean; + isRead: boolean; + redirectUrl: string; + userGroupIdDst: string | null; + userIdDst: string | null; + userLevelIdDst: string; + userLevelNumberDst: string | null; + userRoleIdDst: string; +}; + +const getNotificationIcon = (notificationTypeId: number) => { + switch (notificationTypeId) { + case 2: + return ; + case 3: + return ; + case 4: + return ; + case 5: + return ; + case 6: + return ; + case 7: + return ; + case 8: + return ; + default: + return ; + } +}; + +const NotificationsList: React.FC = () => { + const router = useRouter(); + const [notifications, setNotifications] = useState([]); + const [totalData, setTotalData] = useState(0); + const [page, setPage] = useState(1); + const [pageSize] = useState(10); + const [totalPage, setTotalPage] = useState(1); + + useEffect(() => { + async function fetchNotifications() { + const response = await getNotifications(page - 1, pageSize); + setNotifications(response.data?.data?.content || []); + setTotalData(response.data?.data?.totalElements || 0); + setTotalPage(response.data?.data?.totalPages); + } + fetchNotifications(); + }, [page, pageSize]); + + const handleNotificationClick = (redirectUrl: string) => { + router.push(redirectUrl); + }; + + const handlePageChange = (newPage: number) => { + setPage(newPage); + }; + + return ( +
+
+ {notifications.length > 0 ? ( + notifications.map((notification) => ( +
handleNotificationClick(notification.redirectUrl)} + > +
+
+ {getNotificationIcon(notification.notificationTypeId)} +
+
+
+

{notification.message}

+

+ {new Date(notification.createdAt).toLocaleString()} +

+
+
+ )) + ) : ( +

Tidak ada notifikasi.

+ )} +
+ + {/* Pagination */} +
+ +

+ Page {page} of {totalPage} +

+ +
+
+ ); +}; + +export default NotificationsList; diff --git a/app/[locale]/(protected)/notifications/layout.tsx b/app/[locale]/(protected)/notifications/layout.tsx new file mode 100644 index 00000000..67e45121 --- /dev/null +++ b/app/[locale]/(protected)/notifications/layout.tsx @@ -0,0 +1,9 @@ +export const metadata = { + title: "Notifications", +}; + +const Layout = ({ children }: { children: React.ReactNode }) => { + return <>{children}; +}; + +export default Layout; diff --git a/app/[locale]/(protected)/notifications/page.tsx b/app/[locale]/(protected)/notifications/page.tsx new file mode 100644 index 00000000..3558a409 --- /dev/null +++ b/app/[locale]/(protected)/notifications/page.tsx @@ -0,0 +1,33 @@ +import SiteBreadcrumb from "@/components/site-breadcrumb"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Plus } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Link } from "@/components/navigation"; +import BlogTable from "../contributor/blog/components/blog-table"; +import NotificationsTable from "./components/notifications-table"; + +const NotificationsPage = async () => { + return ( +
+ +
+ + + +
+
+ Table List Notifications +
+
+
+
+ + + +
+
+
+ ); +}; + +export default NotificationsPage; diff --git a/app/[locale]/(public)/profile/change-password/page.tsx b/app/[locale]/(public)/profile/change-password/page.tsx new file mode 100644 index 00000000..ef2e5923 --- /dev/null +++ b/app/[locale]/(public)/profile/change-password/page.tsx @@ -0,0 +1,193 @@ +"use client"; + +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { control } from "leaflet"; +import React, { useEffect, useState } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import * as z from "zod"; +import Swal from "sweetalert2"; +import { getBlog, postBlog } from "@/service/blog/blog"; +import { id } from "date-fns/locale"; +import router from "next/router"; +import { getInfoProfile, saveUser, setupPassword } from "@/service/auth"; +import withReactContent from "sweetalert2-react-content"; +import { Textarea } from "@/components/ui/textarea"; +import { Button } from "@/components/ui/button"; +import { Link } from "@/components/navigation"; + +const profileSchema = z.object({ + password: z.string().min(1, { message: "Judul diperlukan" }), + retypePassword: z.string().min(1, { message: "Judul diperlukan" }), +}); + +type Detail = { + id: number; + userId: any; + password: string; + retypePassword: string; +}; + +const ChangePassword: React.FC = () => { + const MySwal = withReactContent(Swal); + const [detail, setDetail] = useState(); + const [refresh, setRefresh] = useState(false); + + type ProfileSchema = z.infer; + const { + control, + handleSubmit, + setValue, + formState: { errors }, + } = useForm({ + resolver: zodResolver(profileSchema), + }); + + useEffect(() => { + async function initState() { + const response = await getInfoProfile(); + const details = response.data?.data; + + setDetail(details); + console.log("data", details); + } + + initState(); + }, []); + + const save = async (data: ProfileSchema) => { + const requestData = { + ...data, + // userId: detail?.userKeycloakId, + password: data.password, + retypePassword: detail?.retypePassword, + }; + + const response = await setupPassword(requestData); + console.log("Form Data Submitted:", requestData); + console.log("response", response); + + MySwal.fire({ + title: "Sukses", + text: "Data berhasil disimpan.", + icon: "success", + confirmButtonColor: "#3085d6", + confirmButtonText: "OK", + }).then(() => { + router.push("/en/auth"); + }); + }; + + const onSubmit = (data: ProfileSchema) => { + MySwal.fire({ + title: "Simpan Data", + text: "Apakah Anda yakin ingin menyimpan data ini?", + icon: "warning", + showCancelButton: true, + cancelButtonColor: "#d33", + confirmButtonColor: "#3085d6", + confirmButtonText: "Simpan", + }).then((result) => { + if (result.isConfirmed) { + save(data); + } + }); + }; + return ( +
+
+
+ 👤 +
+

Ubah Profile

+
+ +
+ + + + + + + + + +
+ +
+

+ Silahkan ubah data pribadi Anda pada form berikut. +

+
+
+
+ + ( + + )} + /> + {errors.password?.message && ( +

+ {errors.password.message} +

+ )} +
+
+ + ( + + )} + /> + {errors.retypePassword?.message && ( +

+ {errors.retypePassword.message} +

+ )} +
+
+
+ +
+
+
+
+
+
+ ); +}; + +export default ChangePassword; diff --git a/app/[locale]/(public)/profile/change-profile/page.tsx b/app/[locale]/(public)/profile/change-profile/page.tsx new file mode 100644 index 00000000..f5c8688f --- /dev/null +++ b/app/[locale]/(public)/profile/change-profile/page.tsx @@ -0,0 +1,96 @@ +"use client"; +import { Link } from "@/components/navigation"; +import React, { useState } from "react"; + +const ChangeProfile: React.FC = () => { + const [selectedImage, setSelectedImage] = useState(null); + + const handleImageChange = (e: React.ChangeEvent) => { + if (e.target.files && e.target.files[0]) { + setSelectedImage(e.target.files[0]); + } + }; + + const handleSave = () => { + if (selectedImage) { + console.log("Foto berhasil diganti:", selectedImage.name); + alert("Foto berhasil diganti."); + } else { + alert("Silakan pilih foto terlebih dahulu."); + } + }; + + const handleDelete = () => { + setSelectedImage(null); + alert("Foto berhasil dihapus."); + }; + + return ( +
+
+
+ 👤 +
+

Ubah Photo

+
+ +
+ + + + + + + + + +
+ +
+
+
+ {selectedImage ? ( + Preview + ) : ( + No Image + )} +
+
+ +
+ + + +
+
+
+ ); +}; + +export default ChangeProfile; diff --git a/app/[locale]/(public)/profile/layout.tsx b/app/[locale]/(public)/profile/layout.tsx new file mode 100644 index 00000000..fab820cf --- /dev/null +++ b/app/[locale]/(public)/profile/layout.tsx @@ -0,0 +1,22 @@ +import LayoutProvider from "@/providers/layout.provider"; +import LayoutContentProvider from "@/providers/content.provider"; +import DashCodeSidebar from "@/components/partials/sidebar"; +import DashCodeFooter from "@/components/partials/footer"; +import ThemeCustomize from "@/components/partials/customizer"; +import DashCodeHeader from "@/components/partials/header"; + +import { redirect } from "@/components/navigation"; +import Footer from "@/components/landing-page/footer"; +import Navbar from "@/components/landing-page/navbar"; + +const layout = async ({ children }: { children: React.ReactNode }) => { + return ( + <> + + {children} +