From 1cdb0cd0865b70b24955d5b45e56790586dff3a4 Mon Sep 17 00:00:00 2001 From: Anang Yusman Date: Tue, 17 Feb 2026 17:05:22 +0800 Subject: [PATCH] Initial commit --- .gitignore | 41 + README.md | 36 + app/(admin)/admin/dashboard/page.tsx | 34 + app/(admin)/admin/layout.tsx | 7 + app/audio/filter/page.tsx | 85 + app/auth/layout.tsx | 7 + app/auth/page.tsx | 9 + app/details/[slug]/page.tsx | 46 + app/document/filter/page.tsx | 83 + app/favicon.ico | Bin 0 -> 25931 bytes app/globals.css | 140 + app/image/filter/page.tsx | 87 + app/layout.tsx | 34 + app/news-services/page.tsx | 20 + app/page.tsx | 24 + app/video/filter/page.tsx | 83 + components.json | 22 + components/audio/audio-card.tsx | 51 + components/audio/filter-sidebar.tsx | 99 + components/details/audio-selections.tsx | 105 + components/details/audio-sidebar-details.tsx | 177 + components/details/document-selections.tsx | 13 + .../details/document-sidebar-details.tsx | 180 + components/details/image-selections.tsx | 13 + components/details/image-sidebar-details.tsx | 186 + components/details/video-meta.tsx | 80 + components/details/video-sections.tsx | 36 + components/details/video-sidebar-details.tsx | 191 + components/document/document-card.tsx | 51 + components/document/filter-sidebar.tsx | 99 + components/form/login.tsx | 206 + components/icons.tsx | 2734 +++++++ components/icons/dashboard-icon.tsx | 214 + components/icons/globals.tsx | 196 + components/icons/sidebar-icon.tsx | 487 ++ components/image/filter-sidebar.tsx | 99 + components/image/image-card.tsx | 51 + components/landing-page/about.tsx | 74 + components/landing-page/category-content.tsx | 53 + components/landing-page/content-latest.tsx | 158 + components/landing-page/content-popular.tsx | 158 + components/landing-page/floating-news.tsx | 122 + components/landing-page/floating.tsx | 73 + components/landing-page/footer.tsx | 93 + .../landing-page/headers-news-services.tsx | 270 + components/landing-page/headers.tsx | 77 + components/landing-page/option.tsx | 120 + components/landing-page/product.tsx | 189 + .../landing-page/retracting-sidedar.tsx | 427 + components/landing-page/service.tsx | 189 + components/landing-page/technology.tsx | 44 + components/layout/admin-layout.tsx | 89 + components/layout/breadcrumbs.tsx | 154 + components/layout/chunk-error-boundary.tsx | 104 + components/layout/circular-progress.tsx | 36 + .../layout/costum-circular-progress.tsx | 54 + components/layout/custom-pagination.tsx | 125 + components/layout/dashboard-admin.tsx | 59 + components/layout/dashboard-contributor.tsx | 0 components/layout/sidebar-context.tsx | 58 + components/layout/theme-context.tsx | 67 + .../main/dashboard/chart/column-chart.tsx | 157 + .../main/dashboard/dashboard-container.tsx | 481 ++ components/ui/accordion.tsx | 66 + components/ui/badge.tsx | 46 + components/ui/breadcrumb.tsx | 109 + components/ui/button.tsx | 59 + components/ui/calendar.tsx | 75 + components/ui/card.tsx | 92 + components/ui/checkbox.tsx | 32 + components/ui/chip.tsx | 23 + components/ui/dialog.tsx | 143 + components/ui/dropdown-menu.tsx | 257 + components/ui/input.tsx | 21 + components/ui/label.tsx | 24 + components/ui/pagination.tsx | 127 + components/ui/popover.tsx | 48 + components/ui/radio-group.tsx | 45 + components/ui/scroll-area.tsx | 58 + components/ui/select.tsx | 185 + components/ui/switch.tsx | 31 + components/ui/table.tsx | 116 + components/ui/tabs.tsx | 91 + components/ui/textarea.tsx | 18 + components/ui/tooltip.tsx | 61 + components/video/filter-sidebar.tsx | 99 + components/video/video-card.tsx | 52 + config/swal.ts | 92 + eslint.config.mjs | 18 + lib/utils.ts | 6 + next.config.ts | 7 + package-lock.json | 7102 +++++++++++++++++ package.json | 42 + postcss.config.mjs | 7 + public/file.svg | 1 + public/globe.svg | 1 + public/image/audio.png | Bin 0 -> 1370 bytes public/image/aws.png | Bin 0 -> 43761 bytes public/image/bharatu.jpg | Bin 0 -> 403692 bytes public/image/dell.png | Bin 0 -> 35499 bytes public/image/document.png | Bin 0 -> 2000 bytes public/image/fb.png | Bin 0 -> 7175 bytes public/image/ig.png | Bin 0 -> 12588 bytes public/image/img1.png | Bin 0 -> 69137 bytes public/image/lestari2.png | Bin 0 -> 727822 bytes public/image/login.jpg | Bin 0 -> 1013096 bytes public/image/novita2.png | Bin 0 -> 840417 bytes public/image/p1.png | Bin 0 -> 18766 bytes public/image/p2.png | Bin 0 -> 47021 bytes public/image/p3.png | Bin 0 -> 29584 bytes public/image/phone.png | Bin 0 -> 33211 bytes public/image/qudo1.png | Bin 0 -> 40287 bytes public/image/s1.png | Bin 0 -> 85196 bytes public/image/s2.png | Bin 0 -> 119117 bytes public/image/s3.png | Bin 0 -> 153588 bytes public/image/s4.png | Bin 0 -> 140096 bytes public/image/s5.png | Bin 0 -> 194763 bytes public/image/tableu.png | Bin 0 -> 8133 bytes public/image/tt.png | Bin 0 -> 6174 bytes public/image/tvu.png | Bin 0 -> 20940 bytes public/image/uipath.png | Bin 0 -> 16495 bytes public/image/x.png | Bin 0 -> 6148 bytes public/image/yt.png | Bin 0 -> 7372 bytes public/image/zen.png | Bin 0 -> 67805 bytes public/next.svg | 1 + public/vercel.svg | 1 + public/window.svg | 1 + tsconfig.json | 34 + types/globals.tsx | 316 + types/index.ts | 5 + 130 files changed, 18969 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 app/(admin)/admin/dashboard/page.tsx create mode 100644 app/(admin)/admin/layout.tsx create mode 100644 app/audio/filter/page.tsx create mode 100644 app/auth/layout.tsx create mode 100644 app/auth/page.tsx create mode 100644 app/details/[slug]/page.tsx create mode 100644 app/document/filter/page.tsx create mode 100644 app/favicon.ico create mode 100644 app/globals.css create mode 100644 app/image/filter/page.tsx create mode 100644 app/layout.tsx create mode 100644 app/news-services/page.tsx create mode 100644 app/page.tsx create mode 100644 app/video/filter/page.tsx create mode 100644 components.json create mode 100644 components/audio/audio-card.tsx create mode 100644 components/audio/filter-sidebar.tsx create mode 100644 components/details/audio-selections.tsx create mode 100644 components/details/audio-sidebar-details.tsx create mode 100644 components/details/document-selections.tsx create mode 100644 components/details/document-sidebar-details.tsx create mode 100644 components/details/image-selections.tsx create mode 100644 components/details/image-sidebar-details.tsx create mode 100644 components/details/video-meta.tsx create mode 100644 components/details/video-sections.tsx create mode 100644 components/details/video-sidebar-details.tsx create mode 100644 components/document/document-card.tsx create mode 100644 components/document/filter-sidebar.tsx create mode 100644 components/form/login.tsx create mode 100644 components/icons.tsx create mode 100644 components/icons/dashboard-icon.tsx create mode 100644 components/icons/globals.tsx create mode 100644 components/icons/sidebar-icon.tsx create mode 100644 components/image/filter-sidebar.tsx create mode 100644 components/image/image-card.tsx create mode 100644 components/landing-page/about.tsx create mode 100644 components/landing-page/category-content.tsx create mode 100644 components/landing-page/content-latest.tsx create mode 100644 components/landing-page/content-popular.tsx create mode 100644 components/landing-page/floating-news.tsx create mode 100644 components/landing-page/floating.tsx create mode 100644 components/landing-page/footer.tsx create mode 100644 components/landing-page/headers-news-services.tsx create mode 100644 components/landing-page/headers.tsx create mode 100644 components/landing-page/option.tsx create mode 100644 components/landing-page/product.tsx create mode 100644 components/landing-page/retracting-sidedar.tsx create mode 100644 components/landing-page/service.tsx create mode 100644 components/landing-page/technology.tsx create mode 100644 components/layout/admin-layout.tsx create mode 100644 components/layout/breadcrumbs.tsx create mode 100644 components/layout/chunk-error-boundary.tsx create mode 100644 components/layout/circular-progress.tsx create mode 100644 components/layout/costum-circular-progress.tsx create mode 100644 components/layout/custom-pagination.tsx create mode 100644 components/layout/dashboard-admin.tsx create mode 100644 components/layout/dashboard-contributor.tsx create mode 100644 components/layout/sidebar-context.tsx create mode 100644 components/layout/theme-context.tsx create mode 100644 components/main/dashboard/chart/column-chart.tsx create mode 100644 components/main/dashboard/dashboard-container.tsx create mode 100644 components/ui/accordion.tsx create mode 100644 components/ui/badge.tsx create mode 100644 components/ui/breadcrumb.tsx create mode 100644 components/ui/button.tsx create mode 100644 components/ui/calendar.tsx create mode 100644 components/ui/card.tsx create mode 100644 components/ui/checkbox.tsx create mode 100644 components/ui/chip.tsx create mode 100644 components/ui/dialog.tsx create mode 100644 components/ui/dropdown-menu.tsx create mode 100644 components/ui/input.tsx create mode 100644 components/ui/label.tsx create mode 100644 components/ui/pagination.tsx create mode 100644 components/ui/popover.tsx create mode 100644 components/ui/radio-group.tsx create mode 100644 components/ui/scroll-area.tsx create mode 100644 components/ui/select.tsx create mode 100644 components/ui/switch.tsx create mode 100644 components/ui/table.tsx create mode 100644 components/ui/tabs.tsx create mode 100644 components/ui/textarea.tsx create mode 100644 components/ui/tooltip.tsx create mode 100644 components/video/filter-sidebar.tsx create mode 100644 components/video/video-card.tsx create mode 100644 config/swal.ts create mode 100644 eslint.config.mjs create mode 100644 lib/utils.ts create mode 100644 next.config.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 postcss.config.mjs create mode 100644 public/file.svg create mode 100644 public/globe.svg create mode 100644 public/image/audio.png create mode 100644 public/image/aws.png create mode 100644 public/image/bharatu.jpg create mode 100644 public/image/dell.png create mode 100644 public/image/document.png create mode 100644 public/image/fb.png create mode 100644 public/image/ig.png create mode 100644 public/image/img1.png create mode 100644 public/image/lestari2.png create mode 100644 public/image/login.jpg create mode 100644 public/image/novita2.png create mode 100644 public/image/p1.png create mode 100644 public/image/p2.png create mode 100644 public/image/p3.png create mode 100644 public/image/phone.png create mode 100644 public/image/qudo1.png create mode 100644 public/image/s1.png create mode 100644 public/image/s2.png create mode 100644 public/image/s3.png create mode 100644 public/image/s4.png create mode 100644 public/image/s5.png create mode 100644 public/image/tableu.png create mode 100644 public/image/tt.png create mode 100644 public/image/tvu.png create mode 100644 public/image/uipath.png create mode 100644 public/image/x.png create mode 100644 public/image/yt.png create mode 100644 public/image/zen.png create mode 100644 public/next.svg create mode 100644 public/vercel.svg create mode 100644 public/window.svg create mode 100644 tsconfig.json create mode 100644 types/globals.tsx create mode 100644 types/index.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ef6a52 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/README.md b/README.md new file mode 100644 index 0000000..e215bc4 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/app/(admin)/admin/dashboard/page.tsx b/app/(admin)/admin/dashboard/page.tsx new file mode 100644 index 0000000..92cce1c --- /dev/null +++ b/app/(admin)/admin/dashboard/page.tsx @@ -0,0 +1,34 @@ +"use client"; + +import DashboardContainer from "@/components/main/dashboard/dashboard-container"; +import { motion } from "framer-motion"; +import { useEffect, useState } from "react"; + +export default function AdminPage() { + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); + + if (!mounted) { + return ( +
+
+
+ ); + } + + return ( + +
+ +
+
+ ); +} diff --git a/app/(admin)/admin/layout.tsx b/app/(admin)/admin/layout.tsx new file mode 100644 index 0000000..4c1eb03 --- /dev/null +++ b/app/(admin)/admin/layout.tsx @@ -0,0 +1,7 @@ +"use client"; + +import { AdminLayout } from "@/components/layout/admin-layout"; + +export default function AdminPageLayout({ children }: { children: React.ReactNode }) { + return {children}; +} diff --git a/app/audio/filter/page.tsx b/app/audio/filter/page.tsx new file mode 100644 index 0000000..1be7bc6 --- /dev/null +++ b/app/audio/filter/page.tsx @@ -0,0 +1,85 @@ +"use client"; + +import AudioCard from "@/components/audio/audio-card"; +import FilterAudioSidebar from "@/components/audio/filter-sidebar"; +import FloatingMenuNews from "@/components/landing-page/floating-news"; +import Footer from "@/components/landing-page/footer"; +import FilterSidebar from "@/components/video/filter-sidebar"; +import VideoCard from "@/components/video/video-card"; +import { Menu } from "lucide-react"; +import { useState } from "react"; + +export default function AudioFilterPage() { + const [openFilter, setOpenFilter] = useState(false); + + return ( +
+
+
+ {/* ===== TOP BAR ===== */} + + {/* ===== CONTENT ===== */} +
+ {/* Sidebar */} +
+ +
+ + {/* Mobile Sidebar */} + {openFilter && ( +
+
+ + +
+
+ )} + + {/* Cards */} +
+
+
+ Audio   >   + Lihat Semua + {"|"} + + Terdapat 1636 berita + +
+ +
+ Urutkan: + + +
+
+
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+
+
+ + {/* ===== PAGINATION ===== */} +
+ + + ... + + +
+
+
+
+
+ ); +} diff --git a/app/auth/layout.tsx b/app/auth/layout.tsx new file mode 100644 index 0000000..c9e99f9 --- /dev/null +++ b/app/auth/layout.tsx @@ -0,0 +1,7 @@ +export default function AuthLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <> {children}; +} diff --git a/app/auth/page.tsx b/app/auth/page.tsx new file mode 100644 index 0000000..64d9e6e --- /dev/null +++ b/app/auth/page.tsx @@ -0,0 +1,9 @@ +import Login from "@/components/form/login"; + +export default function AuthPage() { + return ( + <> + + + ); +} diff --git a/app/details/[slug]/page.tsx b/app/details/[slug]/page.tsx new file mode 100644 index 0000000..b198ce7 --- /dev/null +++ b/app/details/[slug]/page.tsx @@ -0,0 +1,46 @@ +"use client"; + +import { useSearchParams } from "next/navigation"; + +import VideoPlayerSection from "@/components/details/video-sections"; +import VideoSidebar from "@/components/details/video-sidebar-details"; + +import ImageSidebar from "@/components/details/image-sidebar-details"; +import ImageDetailSection from "@/components/details/image-selections"; +import DocumentDetailSection from "@/components/details/document-selections"; +import AudioPlayerSection from "@/components/details/audio-selections"; +import DocumentSidebar from "@/components/details/document-sidebar-details"; +import AudioSidebar from "@/components/details/audio-sidebar-details"; +import FloatingMenuNews from "@/components/landing-page/floating-news"; +import Footer from "@/components/landing-page/footer"; + +export default function DetailsPage() { + const params = useSearchParams(); + const type = params.get("type"); + + return ( +
+
+
+ {/* LEFT */} +
+ {type === "video" && } + {type === "image" && } + {type === "text" && } + {type === "audio" && } +
+ + {/* RIGHT */} +
+ {type === "video" && } + {type === "image" && } + {type === "text" && } + {type === "audio" && } +
+ +
+
+
+
+ ); +} diff --git a/app/document/filter/page.tsx b/app/document/filter/page.tsx new file mode 100644 index 0000000..724a7d4 --- /dev/null +++ b/app/document/filter/page.tsx @@ -0,0 +1,83 @@ +"use client"; + +import DocumentCard from "@/components/document/document-card"; +import FilterDocumentSidebar from "@/components/document/filter-sidebar"; +import FloatingMenuNews from "@/components/landing-page/floating-news"; +import Footer from "@/components/landing-page/footer"; + +import { useState } from "react"; + +export default function DocumentFilterPage() { + const [openFilter, setOpenFilter] = useState(false); + + return ( +
+
+
+ {/* ===== TOP BAR ===== */} + + {/* ===== CONTENT ===== */} +
+ {/* Sidebar */} +
+ +
+ + {/* Mobile Sidebar */} + {openFilter && ( +
+
+ + +
+
+ )} + + {/* Cards */} +
+
+
+ Document   >   + Lihat Semua + {"|"} + + Terdapat 1636 berita + +
+ +
+ Urutkan: + + +
+
+
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+
+
+ + {/* ===== PAGINATION ===== */} +
+ + + ... + + +
+
+
+
+
+ ); +} diff --git a/app/favicon.ico b/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..718d6fea4835ec2d246af9800eddb7ffb276240c GIT binary patch literal 25931 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 LrJugUA?W`A8`#=m literal 0 HcmV?d00001 diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..b30e457 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,140 @@ +@import "tailwindcss"; +@import "tw-animate-css"; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --radius-2xl: calc(var(--radius) + 8px); + --radius-3xl: calc(var(--radius) + 12px); + --radius-4xl: calc(var(--radius) + 16px); +} + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} + +@layer utilities { + @keyframes tech-scroll { + 0% { + transform: translateX(0); + } + 100% { + transform: translateX(-50%); + } + } + + .animate-tech-scroll { + animation: tech-scroll 40s linear infinite; + } +} diff --git a/app/image/filter/page.tsx b/app/image/filter/page.tsx new file mode 100644 index 0000000..92385b7 --- /dev/null +++ b/app/image/filter/page.tsx @@ -0,0 +1,87 @@ +"use client"; + +import AudioCard from "@/components/audio/audio-card"; +import FilterAudioSidebar from "@/components/audio/filter-sidebar"; +import FilterImageSidebar from "@/components/image/filter-sidebar"; +import ImageCard from "@/components/image/image-card"; +import FloatingMenuNews from "@/components/landing-page/floating-news"; +import Footer from "@/components/landing-page/footer"; +import FilterSidebar from "@/components/video/filter-sidebar"; +import VideoCard from "@/components/video/video-card"; +import { Menu } from "lucide-react"; +import { useState } from "react"; + +export default function ImageFilterPage() { + const [openFilter, setOpenFilter] = useState(false); + + return ( +
+
+
+ {/* ===== TOP BAR ===== */} + + {/* ===== CONTENT ===== */} +
+ {/* Sidebar */} +
+ +
+ + {/* Mobile Sidebar */} + {openFilter && ( +
+
+ + +
+
+ )} + + {/* Cards */} +
+
+
+ Foto   >   + Lihat Semua + {"|"} + + Terdapat 1636 berita + +
+ +
+ Urutkan: + + +
+
+
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+
+
+ + {/* ===== PAGINATION ===== */} +
+ + + ... + + +
+
+
+
+
+ ); +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..f7fa87e --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,34 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import "./globals.css"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + + ); +} diff --git a/app/news-services/page.tsx b/app/news-services/page.tsx new file mode 100644 index 0000000..a33fd49 --- /dev/null +++ b/app/news-services/page.tsx @@ -0,0 +1,20 @@ +import Footer from "@/components/landing-page/footer"; +import FloatingMenu from "@/components/landing-page/floating"; +import NewsAndServicesHeader from "@/components/landing-page/headers-news-services"; +import ContentLatest from "@/components/landing-page/content-latest"; +import ContentPopular from "@/components/landing-page/content-popular"; +import ContentCategory from "@/components/landing-page/category-content"; +import FloatingMenuNews from "@/components/landing-page/floating-news"; + +export default function NewsAndServicesPage() { + return ( +
+ + + + + +
+
+ ); +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..06f1c2e --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,24 @@ +import Header from "@/components/landing-page/headers"; +import AboutSection from "@/components/landing-page/about"; +import ProductSection from "@/components/landing-page/product"; +import ServiceSection from "@/components/landing-page/service"; +import Technology from "@/components/landing-page/technology"; +import Footer from "@/components/landing-page/footer"; +import FloatingMenu from "@/components/landing-page/floating"; + +export default function Home() { + return ( +
+ {/* FIXED MENU */} + + + {/* PAGE CONTENT */} +
+ + + + +
+
+ ); +} diff --git a/app/video/filter/page.tsx b/app/video/filter/page.tsx new file mode 100644 index 0000000..2dbf37a --- /dev/null +++ b/app/video/filter/page.tsx @@ -0,0 +1,83 @@ +"use client"; + +import FloatingMenuNews from "@/components/landing-page/floating-news"; +import Footer from "@/components/landing-page/footer"; +import FilterSidebar from "@/components/video/filter-sidebar"; +import VideoCard from "@/components/video/video-card"; +import { Menu } from "lucide-react"; +import { useState } from "react"; + +export default function VideoFilterPage() { + const [openFilter, setOpenFilter] = useState(false); + + return ( +
+
+
+ {/* ===== TOP BAR ===== */} + + {/* ===== CONTENT ===== */} +
+ {/* Sidebar */} +
+ +
+ + {/* Mobile Sidebar */} + {openFilter && ( +
+
+ + +
+
+ )} + + {/* Cards */} +
+
+
+ Audio Visual   >   + Lihat Semua + {"|"} + + Terdapat 1636 berita + +
+ +
+ Urutkan: + + +
+
+
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+
+
+ + {/* ===== PAGINATION ===== */} +
+ + + ... + + +
+
+
+
+
+ ); +} diff --git a/components.json b/components.json new file mode 100644 index 0000000..b7b9791 --- /dev/null +++ b/components.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": {} +} diff --git a/components/audio/audio-card.tsx b/components/audio/audio-card.tsx new file mode 100644 index 0000000..bee72c9 --- /dev/null +++ b/components/audio/audio-card.tsx @@ -0,0 +1,51 @@ +"use client"; + +import Image from "next/image"; +import Link from "next/link"; + +export default function DocumentCard() { + const slug = "bharatu-mardi-hadji-gugur-saat-bertugas"; + return ( + +
+ {/* IMAGE */} +
+ news +
+ + {/* CONTENT */} +
+ {/* BADGE + TAG */} +
+ + POLRI + + + SEPUTAR PRESTASI + +
+ + {/* DATE */} +

02 Februari 2024

+ + {/* TITLE */} +

+ Bharatu Mardi Hadji Gugur Saat Bertugas, Diganjar Kenaikan Pangkat + Luar Biasa +

+ + {/* EXCERPT */} +

+ Jakarta - Kapolri Jenderal Polisi Drs. Listyo Sigit Prabowo + memberikan kenaikan pangkat luar biasa anumerta kepada... +

+
+
+ + ); +} diff --git a/components/audio/filter-sidebar.tsx b/components/audio/filter-sidebar.tsx new file mode 100644 index 0000000..202f459 --- /dev/null +++ b/components/audio/filter-sidebar.tsx @@ -0,0 +1,99 @@ +"use client"; + +import { ChevronLeft } from "lucide-react"; + +export default function FilterAudioSidebar() { + return ( +
+ {/* HEADER */} +
+

+ Filter +

+ +
+ + {/* CONTENT */} +
+ {/* KATEGORI */} + + + + + + + + + + + {/* JENIS FILE */} + + + + + + + + + + + {/* FORMAT */} + + + + + {/* RESET */} +
+ +
+
+
+ ); +} + +/* ===== COMPONENTS ===== */ + +function FilterSection({ + title, + children, +}: { + title: string; + children: React.ReactNode; +}) { + return ( +
+

{title}

+
{children}
+
+ ); +} + +function Checkbox({ + label, + count, + defaultChecked, +}: { + label: string; + count: number; + defaultChecked?: boolean; +}) { + return ( + + ); +} + +function Divider() { + return
; +} diff --git a/components/details/audio-selections.tsx b/components/details/audio-selections.tsx new file mode 100644 index 0000000..8005d2c --- /dev/null +++ b/components/details/audio-selections.tsx @@ -0,0 +1,105 @@ +"use client"; + +import { useState } from "react"; +import { Play, Pause, Volume2 } from "lucide-react"; + +export default function AudioPlayerSection() { + const [playing, setPlaying] = useState(false); + const [progress, setProgress] = useState(30); + + return ( +
+ {/* ===== AUDIO PLAYER CARD ===== */} +
+
+ {/* PLAY BUTTON */} + + + {/* WAVEFORM + DURATION */} +
+ {/* FAKE WAVEFORM */} +
+ {Array.from({ length: 70 }).map((_, i) => ( +
+ ))} +
+ + {/* TIME */} +
+ 2:14 + 5:00 +
+ + {/* PROGRESS */} +
+ + + setProgress(Number(e.target.value))} + className="w-full accent-blue-600" + /> +
+
+
+
+ + {/* ===== META INFO ===== */} +
+ + POLRI + + + 02 Februari 2024 + 61 + Kreator: BPKH Jurnalis +
+ + {/* ===== TITLE ===== */} +

+ Bharatu Mardi Hadji Gugur Saat Bertugas, Diganjar Kenaikan Pangkat Luar + Biasa +

+ + {/* ===== ARTICLE ===== */} +
+

+ Jakarta - Kapolri Jenderal Polisi Drs. Listyo Sigit Prabowo memberikan + kenaikan pangkat luar biasa anumerta kepada almarhum Bharatu Mardi + Hadji... +

+ +

+ Dengan penghargaan ini, almarhum resmi dinaikkan pangkatnya satu + tingkat lebih tinggi menjadi Bharaka Anumerta... +

+ +

+ Karo Penmas Divisi Humas Polri, Brigjen Pol. Trunoyudo Wisnu Andiko, + menyatakan bahwa kenaikan pangkat anumerta ini merupakan bentuk + penghormatan... +

+
+
+ ); +} diff --git a/components/details/audio-sidebar-details.tsx b/components/details/audio-sidebar-details.tsx new file mode 100644 index 0000000..3a4a60e --- /dev/null +++ b/components/details/audio-sidebar-details.tsx @@ -0,0 +1,177 @@ +"use client"; + +import { useState } from "react"; +import { Download, Facebook, Twitter } from "lucide-react"; + +export default function AudioSidebar() { + const [selected, setSelected] = useState("4K"); + + const options = [ + { + title: "4K", + size: "3840 x 2160 px", + file: "138 Mb", + format: "mov", + }, + { + title: "HD", + size: "1920 x 1080 px", + file: "100 Mb", + format: "mov", + }, + ]; + + return ( +
+ {/* TAG */} +
+ + POLRI + + +
+ + + +
+
+ +
+ + {/* OPTIONS */} +
+

Opsi Ukuran Audio

+ +
+ {options.map((item) => ( +
setSelected(item.title)} + className="cursor-pointer" + > +
+ {/* LEFT */} +
+ {/* CUSTOM RADIO */} +
+ {selected === item.title && ( +
+ )} +
+ +
+

{item.title}

+
+
+ + {/* RIGHT */} +
+

{item.size}

+

+ {item.file}   |   {item.format} +

+
+
+ +
+
+ ))} +
+
+ + {/* DOWNLOAD */} +
+ + + +
+ + {/* SHARE */} +
+

Bagikan:

+
+ +
+ + + +
+
+ +
+ + + +
+
+ +
+ + + +
+
+
+
+
+ ); +} + +/* COMPONENTS */ + +function Tag({ label }: { label: string }) { + return ( + + {label} + + ); +} + +function ShareIcon({ children }: { children: React.ReactNode }) { + return ( +
+ {children} +
+ ); +} diff --git a/components/details/document-selections.tsx b/components/details/document-selections.tsx new file mode 100644 index 0000000..1cb386d --- /dev/null +++ b/components/details/document-selections.tsx @@ -0,0 +1,13 @@ +export default function DocumentDetailSection() { + return ( +
+ + +

+ Novita Hardini: Jangan Sampai Pariwisata Meminggirkan Warga Lokal +

+ +

PARLEMENTARIA, Mandalika...

+
+ ); +} diff --git a/components/details/document-sidebar-details.tsx b/components/details/document-sidebar-details.tsx new file mode 100644 index 0000000..faf6119 --- /dev/null +++ b/components/details/document-sidebar-details.tsx @@ -0,0 +1,180 @@ +"use client"; + +import { useState } from "react"; +import { Download, Facebook, Twitter } from "lucide-react"; + +export default function DocumentSidebar() { + const [selected, setSelected] = useState("4K"); + + const options = [ + { + title: "DOC", + size: "296KB", + file: "138 Mb", + format: "mov", + }, + { + title: "PPT", + size: "296KB", + file: "100 Mb", + format: "mov", + }, + { + title: "PDF", + size: "296KB", + file: "80 Mb", + format: "mp4", + }, + ]; + + return ( +
+ {/* TAG */} +
+ + POLRI + + +
+ + + +
+
+ +
+ + {/* OPTIONS */} +
+

Opsi Ukuran Document

+ +
+ {options.map((item) => ( +
setSelected(item.title)} + className="cursor-pointer" + > +
+ {/* LEFT */} +
+ {/* CUSTOM RADIO */} +
+ {selected === item.title && ( +
+ )} +
+ +
+

{item.title}

+
+
+ + {/* RIGHT */} +
+

{item.size}

+
+
+ +
+
+ ))} +
+
+ + {/* DOWNLOAD */} +
+ + + +
+ + {/* SHARE */} +
+

Bagikan:

+
+ +
+ + + +
+
+ +
+ + + +
+
+ +
+ + + +
+
+
+
+
+ ); +} + +/* COMPONENTS */ + +function Tag({ label }: { label: string }) { + return ( + + {label} + + ); +} + +function ShareIcon({ children }: { children: React.ReactNode }) { + return ( +
+ {children} +
+ ); +} diff --git a/components/details/image-selections.tsx b/components/details/image-selections.tsx new file mode 100644 index 0000000..67644aa --- /dev/null +++ b/components/details/image-selections.tsx @@ -0,0 +1,13 @@ +export default function ImageDetailSection() { + return ( +
+ + +

+ Novita Hardini: Jangan Sampai Pariwisata Meminggirkan Warga Lokal +

+ +

PARLEMENTARIA, Mandalika...

+
+ ); +} diff --git a/components/details/image-sidebar-details.tsx b/components/details/image-sidebar-details.tsx new file mode 100644 index 0000000..396dfef --- /dev/null +++ b/components/details/image-sidebar-details.tsx @@ -0,0 +1,186 @@ +"use client"; + +import { useState } from "react"; +import { Download, Facebook, Twitter } from "lucide-react"; + +export default function ImageSidebar() { + const [selected, setSelected] = useState("4K"); + + const options = [ + { + title: "XL", + size: "3840 x 2160 px", + file: "138 Mb", + format: "mov", + }, + { + title: "L", + size: "1920 x 1080 px", + file: "100 Mb", + format: "mov", + }, + { + title: "M", + size: "1280 x 720 px", + file: "80 Mb", + format: "mp4", + }, + { + title: "S", + size: "640 x 360 px", + file: "40 Mb", + format: "mp4", + }, + ]; + + return ( +
+ {/* TAG */} +
+ + POLRI + + +
+ + + +
+
+ +
+ + {/* OPTIONS */} +
+

Opsi Ukuran Foto

+ +
+ {options.map((item) => ( +
setSelected(item.title)} + className="cursor-pointer" + > +
+ {/* LEFT */} +
+ {/* CUSTOM RADIO */} +
+ {selected === item.title && ( +
+ )} +
+ +
+

{item.title}

+
+
+ + {/* RIGHT */} +
+

{item.size}

+
+
+ +
+
+ ))} +
+
+ + {/* DOWNLOAD */} +
+ + + +
+ + {/* SHARE */} +
+

Bagikan:

+
+ +
+ + + +
+
+ +
+ + + +
+
+ +
+ + + +
+
+
+
+
+ ); +} + +/* COMPONENTS */ + +function Tag({ label }: { label: string }) { + return ( + + {label} + + ); +} + +function ShareIcon({ children }: { children: React.ReactNode }) { + return ( +
+ {children} +
+ ); +} diff --git a/components/details/video-meta.tsx b/components/details/video-meta.tsx new file mode 100644 index 0000000..b889fa1 --- /dev/null +++ b/components/details/video-meta.tsx @@ -0,0 +1,80 @@ +"use client"; + +import { Eye, Calendar } from "lucide-react"; + +export default function VideoMeta() { + return ( +
+ {/* INFO */} +
+ + POLRI + + +
+ + 02 Februari 2024 +
+ +
+ + 61 +
+ + Kreator: POLRI Jurnalis +
+ + {/* TITLE */} +

+ Bharatu Mardi Hadji Gugur Saat Bertugas, Diganjar Kenaikan Pangkat Luar + Biasa +

+ + {/* ARTICLE */} +
+

+ Jakarta - Kapolri Jenderal Polisi Drs. Listyo Sigit Prabowo memberikan + kenaikan pangkat luar biasa anumerta kepada almarhum Bharatu Mardi + Hadji, yang gugur dalam misi kemanusiaan saat melakukan pencarian dua + nelayan yang mengalami mati mesin di perairan Desa Gita, Kecamatan + Oba, Kota Tidore Kepulauan, Minggu (2/2/2025). Dengan penghargaan ini, + almarhum resmi dinaikkan pangkatnya satu tingkat lebih tinggi menjadi + Bharaka Anumerta, terhitung mulai 3 Februari 2025, berdasarkan + Keputusan Kapolri Nomor: Kep/208/II/2025. Karo Penmas Divisi Humas + Polri, Brigjen Pol. Trunoyudo Wisnu Andiko, menyatakan bahwa kenaikan + pangkat anumerta ini merupakan bentuk penghormatan dan apresiasi atas + dedikasi serta pengorbanan almarhum dalam menjalankan tugasnya. + "Penghargaan kenaikan pangkat luar biasa anumerta ini diberikan + sebagai bentuk apresiasi Polri atas dedikasi dan pengorbanan almarhum + dalam menjalankan tugas kemanusiaan. Ini juga sebagai wujud + penghormatan atas pengabdiannya dalam melayani masyarakat," ujar + Brigjen Pol. Trunoyudo Wisnu Andiko, Selasa (4/2). Almarhum Bharaka + Anumerta Mardi Hadji merupakan anggota Direktorat Polairud Polda + Maluku Utara, yang gugur dalam insiden meledaknya speedboat milik + Basarnas Ternate saat menjalankan misi pencarian nelayan hilang. Dalam + insiden tersebut, tiga korban dinyatakan meninggal dunia, yakni + Bharatu Mardi Hadji, serta dua anggota Basarnas, Fadli Malagapi dan + Riski Esa, sementara satu wartawan Kontributor Metro TV dinyatakan + hilang dan masih dalam proses pencarian. Jenazah Bharaka Anumerta + Mardi Hadji dimakamkan dengan upacara penghormatan militer yang + dipimpin langsung oleh Direktur Polairud Polda Malut, Kombes Pol. + Azhari Juanda, di Kelurahan Moya, Kota Ternate, pada pukul 15.00 WIT. + Selain itu, santunan dari Kapolda Maluku Utara juga diserahkan kepada + keluarga almarhum sebagai bentuk duka cita dan penghargaan atas jasa + pengabdiannya. Wakapolda Maluku Utara, Brigjen Pol. Stephen M. Napiun, + sebelumnya telah mengusulkan kenaikan pangkat luar biasa bagi almarhum + kepada Mabes Polri sebagai bentuk penghormatan atas pengorbanannya. + "Polri menghormati jasa almarhum yang telah mengutamakan keselamatan + orang lain. Namun, kondisi cuaca dan ombak yang tidak menentu + menyebabkan insiden ini terjadi. Kami turut berbelasungkawa dan + berharap keluarga yang ditinggalkan diberi kekuatan," ujar Brigjen + Pol. Stephen M. Napiun saat berkunjung ke rumah duka di Ternate. + Dengan penghargaan ini, Polri menegaskan komitmennya untuk selalu + menghargai dedikasi dan pengabdian personel yang gugur dalam tugas + serta memastikan hak-hak keluarga almarhum terpenuhi sesuai aturan + yang berlaku. +

+
+
+ ); +} diff --git a/components/details/video-sections.tsx b/components/details/video-sections.tsx new file mode 100644 index 0000000..6a8d0ff --- /dev/null +++ b/components/details/video-sections.tsx @@ -0,0 +1,36 @@ +"use client"; + +import Image from "next/image"; +import { Play } from "lucide-react"; +import VideoMeta from "./video-meta"; + +export default function VideoPlayerSection() { + return ( +
+ {/* VIDEO THUMB */} +
+ video + + {/* Play Button */} +
+
+ +
+
+ + {/* Duration */} +
+ 1:58 / 3:00 +
+
+ + {/* META & CONTENT */} + +
+ ); +} diff --git a/components/details/video-sidebar-details.tsx b/components/details/video-sidebar-details.tsx new file mode 100644 index 0000000..cf5ba36 --- /dev/null +++ b/components/details/video-sidebar-details.tsx @@ -0,0 +1,191 @@ +"use client"; + +import { useState } from "react"; +import { Download, Facebook, Twitter } from "lucide-react"; + +export default function VideoSidebar() { + const [selected, setSelected] = useState("4K"); + + const options = [ + { + title: "4K", + size: "3840 x 2160 px", + file: "138 Mb", + format: "mov", + }, + { + title: "HD", + size: "1920 x 1080 px", + file: "100 Mb", + format: "mov", + }, + { + title: "HD Web", + size: "1280 x 720 px", + file: "80 Mb", + format: "mp4", + }, + { + title: "Web", + size: "640 x 360 px", + file: "40 Mb", + format: "mp4", + }, + ]; + + return ( +
+ {/* TAG */} +
+ + POLRI + + +
+ + + +
+
+ +
+ + {/* OPTIONS */} +
+

+ Opsi Ukuran Audio Visual +

+ +
+ {options.map((item) => ( +
setSelected(item.title)} + className="cursor-pointer" + > +
+ {/* LEFT */} +
+ {/* CUSTOM RADIO */} +
+ {selected === item.title && ( +
+ )} +
+ +
+

{item.title}

+
+
+ + {/* RIGHT */} +
+

{item.size}

+

+ {item.file}   |   {item.format} +

+
+
+ +
+
+ ))} +
+
+ + {/* DOWNLOAD */} +
+ + + +
+ + {/* SHARE */} +
+

Bagikan:

+
+ +
+ + + +
+
+ +
+ + + +
+
+ +
+ + + +
+
+
+
+
+ ); +} + +/* COMPONENTS */ + +function Tag({ label }: { label: string }) { + return ( + + {label} + + ); +} + +function ShareIcon({ children }: { children: React.ReactNode }) { + return ( +
+ {children} +
+ ); +} diff --git a/components/document/document-card.tsx b/components/document/document-card.tsx new file mode 100644 index 0000000..f94ee86 --- /dev/null +++ b/components/document/document-card.tsx @@ -0,0 +1,51 @@ +"use client"; + +import Image from "next/image"; +import Link from "next/link"; + +export default function DocumentCard() { + const slug = "bharatu-mardi-hadji-gugur-saat-bertugas"; + return ( + +
+ {/* IMAGE */} +
+ news +
+ + {/* CONTENT */} +
+ {/* BADGE + TAG */} +
+ + POLRI + + + SEPUTAR PRESTASI + +
+ + {/* DATE */} +

02 Februari 2024

+ + {/* TITLE */} +

+ Bharatu Mardi Hadji Gugur Saat Bertugas, Diganjar Kenaikan Pangkat + Luar Biasa +

+ + {/* EXCERPT */} +

+ Jakarta - Kapolri Jenderal Polisi Drs. Listyo Sigit Prabowo + memberikan kenaikan pangkat luar biasa anumerta kepada... +

+
+
+ + ); +} diff --git a/components/document/filter-sidebar.tsx b/components/document/filter-sidebar.tsx new file mode 100644 index 0000000..858e17d --- /dev/null +++ b/components/document/filter-sidebar.tsx @@ -0,0 +1,99 @@ +"use client"; + +import { ChevronLeft } from "lucide-react"; + +export default function FilterDocumentSidebar() { + return ( +
+ {/* HEADER */} +
+

+ Filter +

+ +
+ + {/* CONTENT */} +
+ {/* KATEGORI */} + + + + + + + + + + + {/* JENIS FILE */} + + + + + + + + + + + {/* FORMAT */} + + + + + {/* RESET */} +
+ +
+
+
+ ); +} + +/* ===== COMPONENTS ===== */ + +function FilterSection({ + title, + children, +}: { + title: string; + children: React.ReactNode; +}) { + return ( +
+

{title}

+
{children}
+
+ ); +} + +function Checkbox({ + label, + count, + defaultChecked, +}: { + label: string; + count: number; + defaultChecked?: boolean; +}) { + return ( + + ); +} + +function Divider() { + return
; +} diff --git a/components/form/login.tsx b/components/form/login.tsx new file mode 100644 index 0000000..3673b74 --- /dev/null +++ b/components/form/login.tsx @@ -0,0 +1,206 @@ +"use client"; +import React, { useState } from "react"; +import Link from "next/link"; +import Cookies from "js-cookie"; +import { close, error, loading } from "@/config/swal"; +import { useRouter } from "next/navigation"; +import Swal from "sweetalert2"; +import withReactContent from "sweetalert2-react-content"; +import { Input } from "../ui/input"; +import { Button } from "../ui/button"; +import { Label } from "../ui/label"; +import { EyeSlashFilledIcon, EyeFilledIcon } from "../icons"; +import Image from "next/image"; +import { EyeOff, Eye } from "lucide-react"; + +export default function Login() { + const router = useRouter(); + const [isVisible, setIsVisible] = useState(false); + const [isVisibleSetup, setIsVisibleSetup] = useState([false, false]); + const [oldEmail, setOldEmail] = useState(""); + const [newEmail, setNewEmail] = useState(""); + + const toggleVisibility = () => setIsVisible(!isVisible); + const [needOtp, setNeedOtp] = useState(false); + const [isFirstLogin, setFirstLogin] = useState(false); + const [otpValue, setOtpValue] = useState(""); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [showPassword, setShowPassword] = useState(false); + + const setValUsername = (e: any) => { + const uname = e.replaceAll(/[^\w.-]/g, ""); + setUsername(uname.toLowerCase()); + }; + + const onSubmit = async () => { + if (!username || !password) { + error("Username & Password Wajib Diisi !"); + return; + } + + loading(); + + setTimeout(() => { + const users = [ + { + username: "admin", + password: "admin123", + role: "Admin", + redirect: "/admin/dashboard", + }, + { + username: "approver", + password: "approver123", + role: "Approver", + redirect: "/admin/dashboard", + }, + { + username: "kontributor", + password: "kontributor123", + role: "Kontributor", + redirect: "/admin/dashboard", + }, + ]; + + const foundUser = users.find( + (u) => u.username === username && u.password === password, + ); + + if (!foundUser) { + close(); + error("Username / Password Tidak Sesuai"); + return; + } + + // Dummy Token + const fakeToken = `dummy-token-${foundUser.role}`; + const fakeRefresh = `dummy-refresh-${foundUser.role}`; + + const newTime = (new Date().getTime() + 10 * 60 * 1000).toString(); + + Cookies.set("time_refresh", newTime, { expires: 1 }); + + Cookies.set("access_token", fakeToken, { expires: 1 }); + Cookies.set("refresh_token", fakeRefresh, { expires: 1 }); + Cookies.set("time_refresh", newTime, { expires: 1 }); + + Cookies.set("username", foundUser.username); + Cookies.set("fullname", foundUser.role); + Cookies.set("roleName", foundUser.role); + Cookies.set("status", "login"); + + close(); + + router.push(foundUser.redirect); + }, 1000); + }; + + return ( +
+ {/* LEFT IMAGE SECTION */} +
+ Login Illustration +
+ + {/* RIGHT FORM SECTION */} +
+
+ {/* LOGO */} +
+ Qudoco Logo +
+ + {/* TITLE */} +

+ Welcome Back +

+ + {/* FORM */} +
+ {/* Username */} +
+ + setValUsername(e.target.value)} + className="w-full border-b border-gray-300 focus:border-[#9c6b16] outline-none py-2 bg-transparent transition" + /> +
+ + {/* Password */} +
+ +
+ setPassword(e.target.value)} + className="w-full border-b border-gray-300 focus:border-[#9c6b16] outline-none py-2 pr-10 bg-transparent transition" + /> + + +
+
+ + {/* BUTTON */} + +
+ + {/* REGISTER */} +

+ Don’t have an account?{" "} + + Register + +

+ + {/* FOOTER */} +
+ Terms + + Privacy Policy + + Security + +
+ © 2024 Copyrights by company. All Rights Reserved. +
+ Designed by Qudoco Team +
+
+
+
+
+ ); +} diff --git a/components/icons.tsx b/components/icons.tsx new file mode 100644 index 0000000..7f3a70a --- /dev/null +++ b/components/icons.tsx @@ -0,0 +1,2734 @@ +import * as React from "react"; +import { IconSvgProps } from "@/types"; + +export const Logo: React.FC = ({ + size = 36, + width, + height, + ...props +}) => ( + + + +); + +export const DiscordIcon: React.FC = ({ + size = 24, + width, + height, + ...props +}) => { + return ( + + + + ); +}; + +export const TwitterIcon: React.FC = ({ + size = 30, + width, + height, + color = "white", + ...props +}) => { + return ( + + + + ); +}; + +export const IconX: React.FC = ({ + size = 30, + width, + height, + color = "white", + ...props +}) => { + return ( + + + + ); +}; + +export const SendIcon: React.FC = ({ + size, + width, + height, + color = "currentColor", + ...props +}) => { + return ( + + + + + + + + + + + + + + + + ); +}; + +export const GithubIcon: React.FC = ({ + size = 24, + width, + height, + ...props +}) => { + return ( + + + + ); +}; + +export const MoonFilledIcon = ({ + size = 24, + width, + height, + ...props +}: IconSvgProps) => ( + +); + +export const SunFilledIcon = ({ + size = 24, + width, + height, + ...props +}: IconSvgProps) => ( + +); + +export const HeartFilledIcon = ({ + size = 24, + width, + height, + ...props +}: IconSvgProps) => ( + +); + +export const SearchIcon = (props: IconSvgProps) => ( + +); + +export const NextUILogo: React.FC = (props) => { + const { width, height = 40 } = props; + + return ( + + + + + + ); +}; + +export const FbIcon: React.FC = (props) => { + return ( + + + + + + + + + + + + ); +}; + +export const ChevronUpIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const ChevronDownIcon = ({ + size, + height = 24, + width = 14, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const ChevronRightIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const ChevronLeftWhite = ({ + size, + height = 24, + width = 24, + color = "white", + ...props +}: IconSvgProps & { color?: string }) => ( + + + +); + +export const ChevronRightWhite = ({ + size, + height = 24, + width = 24, + color = "white", + ...props +}: IconSvgProps) => ( + + + +); + +export const IgIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + +); + +export const FbIconNav = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + +); + +export const YtIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + + + + + + + + +); + +export const IdnIcon = ({ + size, + height = 24, + width = 14, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + + + + + + + + + + +); + +export const UKIcon = ({ + size, + height = 24, + width = 14, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + + + + + + + + + + + + + + + + +); + +export const TwIcon = ({ + size, + height = 24, + width = 14, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + +); + +export const TtIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + + + + + + + + +); + +export const EyeIcon = ({ + size, + height = 24, + width = 14, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const DotsIcon = ({ + size, + height = 24, + width = 24, + fill = "none", + ...props +}: IconSvgProps) => ( + + + +); + +export const MailIcon = (props: any) => ( + +); + +export const SearchIcons = (props: any) => ( + +); +export const UnderLine = (props: any) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); + +export const EyeSlashFilledIcon = (props: any) => ( + +); + +export const EyeFilledIcon = (props: any) => ( + +); + +export const ArrowIcons: React.FC = ({ + size = 30, + width, + height, + color = "white", + ...props +}) => { + return ( + + + + + + + + + + + ); +}; + +export const Hotline = ({ + size = 24, + width, + height, + ...props +}: IconSvgProps) => ( + + + + +); + +export const CustomerService = ({ + size = 24, + width, + height, + ...props +}: IconSvgProps) => ( + + + + +); + +export const Mail = ({ size = 24, width, height, ...props }: IconSvgProps) => ( + + + +); + +export const Location = ({ + size = 24, + width, + height, + ...props +}: IconSvgProps) => ( + + + +); + +export const Calender = ({ + size = 24, + width, + height, + ...props +}: IconSvgProps) => ( + + + + + + + + + + +); + +export const WorldIcon = ({ + size = 24, + width, + height, + ...props +}: IconSvgProps) => ( + + + + + + + + + + +); + +export const Checklist = ({ + size = 24, + width, + height, + ...props +}: IconSvgProps) => ( + + + + + + + + + + +); + +export const ChevronLeftIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const DotsYIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const DotsXIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const EyeIconMdi = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const OnlineIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const OfflineIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + +); + +export const CreateIconIon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + +); + +export const DeleteIcon = ({ + size, + height = 12, + width = 10, + fill = "none", + ...props +}: IconSvgProps) => ( + + + +); +export const BannerIcon = ({ + size, + height = 12, + width = 10, + fill = "none", + ...props +}: IconSvgProps) => ( + + + +); + +export const AccIcon = ({ + size, + height = 12, + width = 10, + fill = "none", + ...props +}: IconSvgProps) => ( + + + +); + +export const CloseIcon = ({ + size, + height = 12, + width = 10, + fill = "none", + ...props +}: IconSvgProps) => ( + + + +); + +export const RefundIcon = ({ + size, + height = 12, + width = 10, + fill = "none", + ...props +}: IconSvgProps) => ( + + + +); + +export const AddIcon = ({ + size, + height = 12, + width = 12, + fill = "none", + ...props +}: IconSvgProps) => ( + + + +); + +export const CompanyIcon = ({ + size, + height = 12, + width = 12, + fill = "none", + ...props +}: IconSvgProps) => ( + + + +); + +export const EmailIcon = ({ + size, + height = 12, + width = 12, + fill = "none", + ...props +}: IconSvgProps) => ( + + + +); + +export const PhoneIcon = ({ + size, + height = 12, + width = 12, + fill = "none", + ...props +}: IconSvgProps) => ( + + + +); + +export const MessageIcon = ({ + size, + height = 12, + width = 12, + fill = "none", + ...props +}: IconSvgProps) => ( + + + +); + +export const UserIcon = ({ + size, + height = 12, + width = 12, + fill = "none", + ...props +}: IconSvgProps) => ( + + + +); + +export const EyeOffIconMdi = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const DateIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + +); + +export const WarningIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const PasswordIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const TimeIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + +); + +export const VolumeLowIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const VolumeHighIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + + +); + +export const FormVerticalIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const FormHorizontalIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const FormCustomIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const FormLayoutIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const FormValidationIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + +); + +export const FormWizardIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + +); + +export const FacebookIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + +); + +export const GoogleIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + + + + +); + +export const TimesIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const CalendarIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + + + +); +export const ClockIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const SquareFacebookIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const SquareXIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const SquareLinkedInIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const SquareWhatsappIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const CloudUploadIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + + +); +export const BurgerButtonIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const XLandingIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const InstagramLandingIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const FacebookLandingIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const TiktokLandingIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const YoutubeLandingIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const LandingEmailIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const LandingCallIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const LandingLocationIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const LandingAppleIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const LandingPlayStoreIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const LandingAnalyticIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const CopyIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + + +); +export const PlayIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + + + + + + + + + +); +export const ExportIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + + +); +export const VideoIcon = ({ + size, + height = 37, + width = 32, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + + + +); diff --git a/components/icons/dashboard-icon.tsx b/components/icons/dashboard-icon.tsx new file mode 100644 index 0000000..329ea16 --- /dev/null +++ b/components/icons/dashboard-icon.tsx @@ -0,0 +1,214 @@ +import * as React from "react"; +import { IconSvgProps } from "@/types/globals"; + +export const DashboardUserIcon = ({ + size, + height = 48, + width = 48, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const DashboardBriefcaseIcon = ({ + size, + height = 48, + width = 48, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + + + + + + +); +export const DashboardMailboxIcon = ({ + size, + height = 48, + width = 48, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + + + + +); +export const DashboardShareIcon = ({ + size, + height = 48, + width = 48, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const DashboardSpeecIcon = ({ + size, + height = 48, + width = 48, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + +); + +export const DashboardConnectIcon = ({ + size, + height = 48, + width = 48, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const DashboardTopLeftPointIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const DashboardRightDownPointIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const DashboardCommentIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + + + + + + + + +); diff --git a/components/icons/globals.tsx b/components/icons/globals.tsx new file mode 100644 index 0000000..0d11c16 --- /dev/null +++ b/components/icons/globals.tsx @@ -0,0 +1,196 @@ +import * as React from "react"; +import { IconSvgProps } from "@/types/globals"; + +export const PdfIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + +); +export const CsvIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const ExcelIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + +); +export const WordIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + +); + +export const PptIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + +); +export const FileIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const UserProfileIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + +); +export const SettingsIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); diff --git a/components/icons/sidebar-icon.tsx b/components/icons/sidebar-icon.tsx new file mode 100644 index 0000000..2a8eee9 --- /dev/null +++ b/components/icons/sidebar-icon.tsx @@ -0,0 +1,487 @@ +import * as React from "react"; +import { IconSvgProps } from "@/types/globals"; + +export const MenuBurgerIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const DashboardIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const HomeIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + + + +); + +export const Submenu1Icon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + + + + + + + + + +); + +export const Submenu2Icon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); + +export const InfoCircleIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + +); + +export const MinusCircleIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + + + +); + +export const TableIcon = ({ + size, + height = 24, + width = 22, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const ArticleIcon = ({ + size, + height = 20, + width = 20, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const MagazineIcon = ({ + size, + height = 20, + width = 20, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); + +export const StaticPageIcon = ({ + size, + height = 20, + width = 20, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const MasterUsersIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const MasterRoleIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + + +); +export const MasterUserLevelIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const MasterCategoryIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const AddvertiseIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const SuggestionsIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); +export const CommentIcon = ({ + size, + height = 24, + width = 24, + fill = "currentColor", + ...props +}: IconSvgProps) => ( + + + +); diff --git a/components/image/filter-sidebar.tsx b/components/image/filter-sidebar.tsx new file mode 100644 index 0000000..b5310e9 --- /dev/null +++ b/components/image/filter-sidebar.tsx @@ -0,0 +1,99 @@ +"use client"; + +import { ChevronLeft } from "lucide-react"; + +export default function FilterImageSidebar() { + return ( +
+ {/* HEADER */} +
+

+ Filter +

+ +
+ + {/* CONTENT */} +
+ {/* KATEGORI */} + + + + + + + + + + + {/* JENIS FILE */} + + + + + + + + + + + {/* FORMAT */} + + + + + {/* RESET */} +
+ +
+
+
+ ); +} + +/* ===== COMPONENTS ===== */ + +function FilterSection({ + title, + children, +}: { + title: string; + children: React.ReactNode; +}) { + return ( +
+

{title}

+
{children}
+
+ ); +} + +function Checkbox({ + label, + count, + defaultChecked, +}: { + label: string; + count: number; + defaultChecked?: boolean; +}) { + return ( + + ); +} + +function Divider() { + return
; +} diff --git a/components/image/image-card.tsx b/components/image/image-card.tsx new file mode 100644 index 0000000..74b334d --- /dev/null +++ b/components/image/image-card.tsx @@ -0,0 +1,51 @@ +"use client"; + +import Image from "next/image"; +import Link from "next/link"; + +export default function ImageCard() { + const slug = "bharatu-mardi-hadji-gugur-saat-bertugas"; + return ( + +
+ {/* IMAGE */} +
+ news +
+ + {/* CONTENT */} +
+ {/* BADGE + TAG */} +
+ + POLRI + + + SEPUTAR PRESTASI + +
+ + {/* DATE */} +

02 Februari 2024

+ + {/* TITLE */} +

+ Bharatu Mardi Hadji Gugur Saat Bertugas, Diganjar Kenaikan Pangkat + Luar Biasa +

+ + {/* EXCERPT */} +

+ Jakarta - Kapolri Jenderal Polisi Drs. Listyo Sigit Prabowo + memberikan kenaikan pangkat luar biasa anumerta kepada... +

+
+
+ + ); +} diff --git a/components/landing-page/about.tsx b/components/landing-page/about.tsx new file mode 100644 index 0000000..29008d6 --- /dev/null +++ b/components/landing-page/about.tsx @@ -0,0 +1,74 @@ +import Image from "next/image"; + +export default function AboutSection() { + const socials = [ + { name: "Facebook", icon: "/image/fb.png" }, + { name: "Instagram", icon: "/image/ig.png" }, + { name: "X", icon: "/image/x.png" }, + { name: "Youtube", icon: "/image/yt.png" }, + { name: "Tiktok", icon: "/image/tt.png" }, + ]; + + return ( +
+ {/* TOP CENTER CONTENT */} +
+

+ Manage All your channels from Multipool +

+ + {/* SOCIAL ICONS */} +
+ {socials.map((item) => ( +
+ {item.name} +
+ ))} +
+
+ +
+ {/* PHONE IMAGE */} +
+ App Preview +
+ + {/* TEXT CONTENT */} +
+

+ About Us +

+ +

+ Helping you find the right{" "} + + + Solution + +

+ +

+ PT Qudo Buana Nawakara adalah perusahaan nasional Indonesia yang + berfokus pada pengembangan aplikasi untuk mendukung kegiatan + reputasi manajemen institusi, organisasi dan publik figur. Dengan + dukungan teknologi otomatisasi dan kecerdasan buatan (AI) untuk + mengoptimalkan proses. Perusahaan didukung oleh team SDM nasional + yang sudah berpengalaman serta memiliki sertifikasi internasional, + untuk memastikan produk yang dihasilkan handal dan berkualitas + tinggi. PT Qudo Buana Nawakara berkantor pusat di Jakarta dengan + support office di Bandung, Indonesia – India – USA – Oman. +

+
+
+
+ ); +} diff --git a/components/landing-page/category-content.tsx b/components/landing-page/category-content.tsx new file mode 100644 index 0000000..ed994d3 --- /dev/null +++ b/components/landing-page/category-content.tsx @@ -0,0 +1,53 @@ +"use client"; + +import { Card, CardContent } from "@/components/ui/card"; + +const categories = [ + { name: "Investment", total: 45 }, + { name: "Technology", total: 32 }, + { name: "Partnership", total: 28 }, + { name: "Report", total: 23 }, + { name: "Event", total: 19 }, + { name: "CSR", total: 15 }, +]; + +export default function ContentCategory() { + return ( +
+
+ {/* ===== Title ===== */} +

+ Kategori Konten +

+ + {/* ===== Card ===== */} + + + {categories.map((item, index) => ( +
+ {/* Left */} +
+ {/* Bullet */} +
+ + + {item.name} + +
+ + {/* Right total */} + {item.total} +
+ ))} + + +
+
+ ); +} diff --git a/components/landing-page/content-latest.tsx b/components/landing-page/content-latest.tsx new file mode 100644 index 0000000..f641797 --- /dev/null +++ b/components/landing-page/content-latest.tsx @@ -0,0 +1,158 @@ +"use client"; + +import Image from "next/image"; +import { Badge } from "@/components/ui/badge"; +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; + +const data = [ + { + id: 1, + image: "/image/bharatu.jpg", + category: "POLRI", + categoryColor: "bg-red-600", + tag: "SEPUTAR PRESTASI", + date: "02 Februari 2024", + title: + "Bharatu Mardi Hadji Gugur Saat Bertugas, Diganjar Kenaikan Pangkat Luar Biasa", + }, + { + id: 2, + image: "/image/novita2.png", + category: "DPR", + categoryColor: "bg-yellow-500", + tag: "BERITA KOMISI 7", + date: "02 Februari 2024", + title: "Novita Hardini: Jangan Sampai Pariwisata Meminggirkan Warga Lokal", + }, + { + id: 3, + image: "/dummy/news-3.jpg", + category: "MPR", + categoryColor: "bg-yellow-600", + tag: "KEGIATAN EDUKASI", + date: "02 Februari 2024", + title: + "Lestari Moerdijat: Butuh Afirmasi dan Edukasi untuk Dorong Perempuan Aktif di Dunia Politik", + }, + { + id: 4, + image: "/dummy/news-2.jpg", + category: "MAHKAMAH AGUNG", + categoryColor: "bg-yellow-700", + tag: "HOT NEWS", + date: "02 Februari 2024", + title: "SEKRETARIS MAHKAMAH AGUNG LANTIK HAKIM TINGGI PENGAWAS", + }, +]; + +export default function ContentLatest() { + return ( +
+
+ {/* ===== HEADER ===== */} +
+

Konten Terbaru

+ + + {/* Tabs + Explore */} +
+ {/* Tabs Center */} +
+ + + Audio Visual + + + Audio + + + Foto + + + Teks + + +
+ + {/* Explore Right */} +
+ Explore more Trending +
+
+ + {/* ===== CONTENT ===== */} + + + + + + + + + + + + + + + +
+
+
+
+ ); +} + +/* ================= CARD GRID ================= */ + +function CardGrid() { + return ( +
+ {data.map((item) => ( +
+
+ {item.title} +
+ +
+
+ + {item.category} + + + {item.tag} +
+ +

{item.date}

+ +

+ {item.title} +

+
+
+ ))} +
+ ); +} diff --git a/components/landing-page/content-popular.tsx b/components/landing-page/content-popular.tsx new file mode 100644 index 0000000..0d0165a --- /dev/null +++ b/components/landing-page/content-popular.tsx @@ -0,0 +1,158 @@ +"use client"; + +import Image from "next/image"; +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; +import { Badge } from "@/components/ui/badge"; + +const data = [ + { + id: 1, + image: "/image/bharatu.jpg", + category: "POLRI", + categoryColor: "bg-red-600", + tag: "SEPUTAR PRESTASI", + date: "02 Februari 2024", + title: + "Bharatu Mardi Hadji Gugur Saat Bertugas, Diganjar Kenaikan Pangkat Luar Biasa", + }, + { + id: 2, + image: "/image/novita2.png", + category: "DPR", + categoryColor: "bg-yellow-500", + tag: "BERITA KOMISI 7", + date: "02 Februari 2024", + title: "Novita Hardini: Jangan Sampai Pariwisata Meminggirkan Warga Lokal", + }, + { + id: 3, + image: "/dummy/news-3.jpg", + category: "MPR", + categoryColor: "bg-yellow-600", + tag: "KEGIATAN EDUKASI", + date: "02 Februari 2024", + title: + "Lestari Moerdijat: Butuh Afirmasi dan Edukasi untuk Dorong Perempuan Aktif di Dunia Politik", + }, + { + id: 4, + image: "/dummy/news-2.jpg", + category: "MAHKAMAH AGUNG", + categoryColor: "bg-yellow-700", + tag: "HOT NEWS", + date: "02 Februari 2024", + title: "SEKRETARIS MAHKAMAH AGUNG LANTIK HAKIM TINGGI PENGAWAS", + }, +]; + +export default function ContentLatest() { + return ( +
+
+ {/* ===== HEADER ===== */} +
+

Konten Terpopuler

+ + + {/* Tabs + Explore */} +
+ {/* Tabs Center */} +
+ + + Audio Visual + + + Audio + + + Foto + + + Teks + + +
+ + {/* Explore Right */} +
+ Explore more Trending +
+
+ + {/* ===== CONTENT ===== */} + + + + + + + + + + + + + + + +
+
+
+
+ ); +} + +/* ================= CARD GRID ================= */ + +function CardGrid() { + return ( +
+ {data.map((item) => ( +
+
+ {item.title} +
+ +
+
+ + {item.category} + + + {item.tag} +
+ +

{item.date}

+ +

+ {item.title} +

+
+
+ ))} +
+ ); +} diff --git a/components/landing-page/floating-news.tsx b/components/landing-page/floating-news.tsx new file mode 100644 index 0000000..ea3fecc --- /dev/null +++ b/components/landing-page/floating-news.tsx @@ -0,0 +1,122 @@ +"use client"; + +import { useState } from "react"; +import { + Menu, + X, + Home, + ChevronDown, + Video, + Music, + Image as ImageIcon, + FileText, +} from "lucide-react"; +import Link from "next/link"; + +export default function FloatingMenuNews() { + const [open, setOpen] = useState(false); + const [openKonten, setOpenKonten] = useState(false); + + return ( + <> + {/* FLOATING BUTTON */} + + + {/* OVERLAY */} + {open && ( +
setOpen(false)} + className="fixed inset-0 z-[90] bg-black/40" + /> + )} + + {/* SIDEBAR */} + + + ); +} + +/* ================= SUBMENU ================= */ + +function SubMenuItem({ + icon, + label, +}: { + icon: React.ReactNode; + label: string; +}) { + return ( +
+ {label} + {icon} +
+ ); +} diff --git a/components/landing-page/floating.tsx b/components/landing-page/floating.tsx new file mode 100644 index 0000000..6e6ae4c --- /dev/null +++ b/components/landing-page/floating.tsx @@ -0,0 +1,73 @@ +"use client"; + +import { useState } from "react"; +import { Menu, X, Home, Box, Briefcase, Newspaper, LogIn } from "lucide-react"; +import Link from "next/link"; + +export default function FloatingMenu() { + const [open, setOpen] = useState(false); + + return ( + <> + {/* FLOATING BUTTON */} + + + {/* OVERLAY */} + {open && ( +
setOpen(false)} + className="fixed inset-0 z-[90] bg-black/40" + /> + )} + + {/* SIDEBAR */} + + + ); +} + +function MenuItem({ icon, label }: { icon: React.ReactNode; label: string }) { + return ( +
+ {label} + {icon} +
+ ); +} diff --git a/components/landing-page/footer.tsx b/components/landing-page/footer.tsx new file mode 100644 index 0000000..6cc8ec2 --- /dev/null +++ b/components/landing-page/footer.tsx @@ -0,0 +1,93 @@ +import Image from "next/image"; +import { Mail, Facebook, Twitter, Youtube, Instagram } from "lucide-react"; + +export default function Footer() { + return ( +
+
+
+ {/* Logo */} +
+
+ Qudoco +
+
+ + {/* Information */} +
+

Information

+
    +
  • Home
  • +
  • Blog
  • +
  • Document
  • +
+
+ + {/* Product */} +
+

Product

+
    +
  • MediaHUB Content Aggregator
  • +
  • Multipool Reputation Management
  • +
  • PR Room Opinion Management
  • +
+
+ + {/* Service */} +
+

Service

+
    +
  • Artifintel
  • +
  • Produksi Video Animasi
  • +
  • Reelithic
  • +
  • Qudoin
  • +
  • Talkshow AI
  • +
+
+ + {/* Get in Touch */} +
+

Get in Touch.

+

+ Indonesia – India – USA – Oman +

+ + {/* Email */} +
+ + sales@qudoco.com + +
+ + {/* Social */} +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ + {/* Divider */} +
+ © 2024 Copyrights by company. All Rights Reserved. Designed by{" "} + Qudoco Team +
+
+
+ ); +} diff --git a/components/landing-page/headers-news-services.tsx b/components/landing-page/headers-news-services.tsx new file mode 100644 index 0000000..e718c80 --- /dev/null +++ b/components/landing-page/headers-news-services.tsx @@ -0,0 +1,270 @@ +"use client"; + +import Image from "next/image"; +import { motion, AnimatePresence } from "framer-motion"; +import { X, ChevronLeft, ChevronRight } from "lucide-react"; +import { useEffect, useState } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; + +const data = [ + { + id: 1, + title: + "Bharatu Mardi Hadji Gugur Saat Bertugas, Diganjar Kenaikan Pangkat Luar Biasa", + image: "/image/bharatu.jpg", + }, + { + id: 2, + title: "Pelayanan Publik Terus Ditingkatkan Demi Kenyamanan Masyarakat", + image: "/dummy/news-2.jpg", + }, + { + id: 3, + title: "Inovasi Teknologi Jadi Fokus Pengembangan Layanan", + image: "/dummy/news-3.jpg", + }, +]; + +const data1 = [ + { + id: 1, + title: "Novita Hardini: Jangan Sampai Pariwisata Meminggirkan Warga Lokal", + image: "/image/novita2.png", + excerpt: + "PARLEMENTARIA, Mandalika – Anggota Komisi VII DPR RI, Novita Hardini, menyoroti dampak sosial ekonomi dari pembangunan kawasan pariwisata...", + date: "7 November 2024", + category: "BERITA KOMISI 7", + tag: "DPR", + }, + { + id: 2, + title: "Pelayanan Publik Terus Ditingkatkan Demi Kenyamanan Masyarakat", + image: "/dummy/news-2.jpg", + excerpt: + "Pelayanan publik terus ditingkatkan untuk menjawab kebutuhan masyarakat...", + date: "6 November 2024", + category: "BERITA", + tag: "NASIONAL", + }, + { + id: 3, + title: "Inovasi Teknologi Jadi Fokus Pengembangan Layanan", + image: "/dummy/news-3.jpg", + excerpt: + "Transformasi digital menjadi fokus utama pengembangan layanan publik...", + date: "5 November 2024", + category: "TEKNOLOGI", + tag: "INOVASI", + }, +]; + +export default function NewsAndServicesHeader() { + // 🔹 STATE DIPISAH + const [activeHeader, setActiveHeader] = useState(0); + const [activeModal, setActiveModal] = useState(0); + + const [open, setOpen] = useState(false); + const [mounted, setMounted] = useState(false); + + const searchParams = useSearchParams(); + const router = useRouter(); + + useEffect(() => { + setMounted(true); + }, []); + + // 🔹 AUTO OPEN MODAL + useEffect(() => { + if (!mounted) return; + + const highlight = searchParams.get("highlight"); + if (highlight === "1") { + setActiveModal(activeHeader); // clone posisi header + setOpen(true); + } + }, [mounted, searchParams, activeHeader]); + + const closeModal = () => { + setOpen(false); + router.replace("/news-services"); + }; + + // ===== HEADER NAV ===== + const headerPrev = () => + setActiveHeader((p) => (p === 0 ? data.length - 1 : p - 1)); + const headerNext = () => + setActiveHeader((p) => (p === data.length - 1 ? 0 : p + 1)); + + // ===== MODAL NAV ===== + const modalPrev = () => + setActiveModal((p) => (p === 0 ? data.length - 1 : p - 1)); + const modalNext = () => + setActiveModal((p) => (p === data.length - 1 ? 0 : p + 1)); + + if (!mounted) return null; + + return ( + <> + {/* ================= HEADER ================= */} + {/* ================= HEADER ================= */} +
+
+ {/* ===== OUTER NAVIGATION ===== */} + + + + +
+ {/* IMAGE */} +
+
+ {data1[activeHeader].title} +
+ + {/* DOTS */} +
+ {data1.map((_, i) => ( + + ))} +
+
+ + {/* CONTENT */} +
+

+ {data1[activeHeader].title} +

+ +
+ {data1[activeHeader].date} + + {data1[activeHeader].category} + + + {data1[activeHeader].tag} + +
+ +

+ {data1[activeHeader].excerpt} +

+ + +
+
+ + {/* ===== SEARCH SECTION ===== */} +
+
+
🔍
+ + +
+
+
+
+ + {/* ================= MODAL ================= */} + + {open && ( + + + + +
+ {data[activeModal].title} + +
+

+ {data[activeModal].title} +

+
+ + + + +
+ +
+ {data.map((_, i) => ( +
+
+
+ )} +
+ + ); +} diff --git a/components/landing-page/headers.tsx b/components/landing-page/headers.tsx new file mode 100644 index 0000000..4ce9f61 --- /dev/null +++ b/components/landing-page/headers.tsx @@ -0,0 +1,77 @@ +"use client"; + +import Image from "next/image"; +import { Button } from "@/components/ui/button"; +import { useState } from "react"; +import { Menu, X, Home, Box, Briefcase, Newspaper } from "lucide-react"; + +export default function Header() { + const [open, setOpen] = useState(false); + + return ( +
+ + +
+
+

+ + + Beyond Expectations + +
+ Build Reputation. +

+ + +
+ +
+ Illustration +
+
+
+ ); +} + +function MenuItem({ icon, label }: { icon: React.ReactNode; label: string }) { + return ( +
+ {label} + {icon} +
+ ); +} diff --git a/components/landing-page/option.tsx b/components/landing-page/option.tsx new file mode 100644 index 0000000..512232d --- /dev/null +++ b/components/landing-page/option.tsx @@ -0,0 +1,120 @@ +import { motion } from "framer-motion"; +import { useState, Dispatch, SetStateAction } from "react"; + +export type OptionProps = { + Icon: any; + title: string; + selected?: string; + setSelected?: Dispatch>; + open: boolean; + notifs?: number; + active?: boolean; +}; + +const Option = ({ Icon, title, selected, setSelected, open, notifs, active }: OptionProps) => { + const [hovered, setHovered] = useState(false); + const isActive = active ?? selected === title; + + return ( + setSelected?.(title)} + onMouseEnter={() => setHovered(true)} + onMouseLeave={() => setHovered(false)} + className={`relative flex h-12 w-full px-3 items-center rounded-xl transition-all duration-200 cursor-pointer group ${ + isActive + ? "bg-gradient-to-r from-emerald-500 to-green-500 text-white shadow-lg shadow-emerald-500/25" + : "text-slate-600 hover:bg-gradient-to-r hover:from-slate-100 hover:to-slate-200/50 hover:text-slate-800" + }`} + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.98 }} + > + {/* Active indicator */} + {isActive && ( + + )} + + +
+ +
+
+ + {open && ( + + {title} + + )} + + {/* Tooltip for collapsed state */} + {!open && hovered && ( + +
+ {title} + {/* Tooltip arrow */} +
+
+
+ )} + + {/* Notification badge */} + {notifs && open && ( + + {notifs} + + )} + + {/* Hover effect overlay */} + {hovered && !isActive && ( + + )} +
+ ); +}; + +export default Option; diff --git a/components/landing-page/product.tsx b/components/landing-page/product.tsx new file mode 100644 index 0000000..d841849 --- /dev/null +++ b/components/landing-page/product.tsx @@ -0,0 +1,189 @@ +import Image from "next/image"; +import { Check } from "lucide-react"; + +export default function ProductSection() { + const features = [ + "Content Creation: Producing creative and engaging content such as posts, images, videos, and stories that align with the brand and attract audience attention.", + "Social Media Account Management: Managing business social media accounts, including scheduling posts, monitoring interactions, and engaging with followers.", + "Paid Advertising Campaigns: Designing, executing, and managing paid advertising campaigns on various social media platforms to reach a more specific target audience and improve ROI (Return on Investment).", + ]; + return ( +
+
+ {/* TITLE */} +
+

+ Our Product +

+ +

+ The product we offer is{" "} + + + designed + {" "} + to meet your business needs. +

+
+ + {/* CONTENT */} +
+ {/* LEFT IMAGE */} +
+ Product Illustration +
+ + {/* RIGHT CONTENT */} +
+ {/* ICON */} +
+ Product Icon +
+ +

+ MediaHUB Content Aggregator +

+ +

+ Social media marketing services are provided by companies or + individuals who specialize in marketing strategies through social + media platforms. +

+ + {/* FEATURES */} +
    + {features.map((item) => ( +
  • + + + + {item} +
  • + ))} +
+ + {/* CTA */} + +
+
+
+ {/* LEFT IMAGE */} + + {/* RIGHT CONTENT */} +
+ {/* ICON */} +
+ Product Icon +
+ +

+ Multipool Reputation Management +

+ +

+ Social media marketing services are provided by companies or + individuals who specialize in marketing strategies through social + media platforms. +

+ + {/* FEATURES */} +
    + {features.map((item) => ( +
  • + + + + {item} +
  • + ))} +
+ + {/* CTA */} + +
+
+ Product Illustration +
+
+
+ {/* LEFT IMAGE */} +
+ Product Illustration +
+ + {/* RIGHT CONTENT */} +
+ {/* ICON */} +
+ Product Icon +
+ +

+ PR Room Opinion Management +

+ +

+ Social media marketing services are provided by companies or + individuals who specialize in marketing strategies through social + media platforms. +

+ + {/* FEATURES */} +
    + {features.map((item) => ( +
  • + + + + {item} +
  • + ))} +
+ + {/* CTA */} + +
+
+
+
+ ); +} diff --git a/components/landing-page/retracting-sidedar.tsx b/components/landing-page/retracting-sidedar.tsx new file mode 100644 index 0000000..33fdf2b --- /dev/null +++ b/components/landing-page/retracting-sidedar.tsx @@ -0,0 +1,427 @@ +"use client"; + +import React, { Dispatch, SetStateAction, useState, useEffect } from "react"; +import Image from "next/image"; +import { Icon } from "@iconify/react"; +import Link from "next/link"; +import DashboardContainer from "../main/dashboard/dashboard-container"; +import { usePathname } from "next/navigation"; +import { useTheme } from "../layout/theme-context"; +import { AnimatePresence, motion } from "framer-motion"; +import Option from "./option"; + +interface RetractingSidebarProps { + sidebarData: boolean; + updateSidebarData: (newData: boolean) => void; +} + +const getSidebarByRole = (role: string) => { + if (role === "Admin") { + return [ + { + title: "Dashboard", + items: [ + { + title: "Dashboard", + icon: () => ( + + ), + link: "/admin/dashboard", + }, + ], + }, + ]; + } + + if (role === "Approver" || role === "Kontributor") { + return [ + { + title: "Dashboard", + items: [ + { + title: "Dashboard", + icon: () => ( + + ), + link: "/admin/dashboard", + }, + ], + }, + { + title: "Content Management", + items: [ + { + title: "Articles", + icon: () => , + link: "/admin/article", + }, + ], + }, + ]; + } + + // fallback kalau role tidak dikenal + return []; +}; + +export const RetractingSidebar = ({ + sidebarData, + updateSidebarData, +}: RetractingSidebarProps) => { + const pathname = usePathname(); + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); + + if (!mounted) { + return null; + } + + return ( + <> + {/* DESKTOP SIDEBAR */} + + + + + + + {/* Desktop Toggle Button - appears when sidebar is collapsed */} + + {!sidebarData && ( + updateSidebarData(true)} + > + + + )} + + + {/* Mobile Toggle Button */} + + {!sidebarData && ( + updateSidebarData(true)} + > + + + )} + + + {/* MOBILE SIDEBAR */} + + {sidebarData && ( + + {/* */} + + + )} + + + ); +}; + +const SidebarContent = ({ + open, + pathname, + updateSidebarData, +}: { + open: boolean; + pathname: string; + updateSidebarData: (newData: boolean) => void; +}) => { + const { theme, toggleTheme } = useTheme(); + + const [username, setUsername] = useState("Guest"); + const [roleName, setRoleName] = useState(""); + + useEffect(() => { + const getCookie = (name: string) => { + const match = document.cookie.match( + new RegExp("(^| )" + name + "=([^;]+)"), + ); + return match ? decodeURIComponent(match[2]) : null; + }; + + const cookieUsername = getCookie("username"); + const cookieRole = getCookie("roleName"); // pastikan nama cookie sesuai + + if (cookieUsername) { + setUsername(cookieUsername); + } + + if (cookieRole) { + setRoleName(cookieRole); + } + }, []); + + const sidebarSections = getSidebarByRole(roleName); + + return ( +
+ {/* SCROLLABLE TOP SECTION */} +
+ {/* HEADER SECTION */} +
+ {/* Logo and Toggle */} +
+ +
+ +
+
+ {open && ( + + + Arah Negeri + + Admin Panel + + )} + + + {open && ( + updateSidebarData(false)} + > + + + )} +
+ + {/* Navigation Sections */} +
+ {sidebarSections.map((section, sectionIndex) => ( + + {open && ( + + {section.title} + + )} +
+ {section.items.map((item, itemIndex) => ( + +
+
+ ))} +
+
+
+ + {/* FIXED BOTTOM SECTION */} +
+ {/* Divider */} + {/*
+
+
*/} + + {/* Theme Toggle */} +
+ + +
+ {theme === "dark" ? ( + + ) : ( + + )} +
+
+ + {open && ( + + {theme === "dark" ? "Light Mode" : "Dark Mode"} + + )} +
+
+ + {/* Settings */} +
+ +
+ + {/* User Profile */} + +
+
+
+ A +
+
+
+ {open && ( + +

+ {username} +

+ +

+ Sign out +

+ +
+ )} +
+
+ + {/* Expand Button for Collapsed State */} + {/* {!open && ( + + + + )} */} +
+
+ ); +}; diff --git a/components/landing-page/service.tsx b/components/landing-page/service.tsx new file mode 100644 index 0000000..8000805 --- /dev/null +++ b/components/landing-page/service.tsx @@ -0,0 +1,189 @@ +import Image from "next/image"; + +export default function ServiceSection() { + return ( +
+
+ {/* Heading */} +
+

+ Our Services +

+

+ Innovative solutions for your{" "} + + business growth + + + . +

+
+ + {/* Service 1 */} +
+ {/* Image */} +
+ Artifintel Soundworks +
+ + {/* Content */} +
+

+ Artifintel +

+

+ Artifintel Soundworks adalah pionir musik AI yang menghadirkan + karya dan kolaborasi dari musisi AI penuh kreativitas dan emosi. + Sejak 2024, Artifintel telah merilis belasan hingga puluhan lagu + dengan melodi memukau dan ritme inovatif. +

+ +
    +
  • ✔ AI Music Composition & Songwriting
  • +
  • ✔ Vocal Synthesis & AI Musicians
  • +
  • ✔ Genre Exploration & Sound Innovation
  • +
  • ✔ AI Collaboration & Creative Experimentation
  • +
  • ✔ Music Release & Digital Distribution
  • +
+
+
+ + {/* Service 2 */} +
+ {/* Content */} +
+

+ Produksi Video Animasi +

+

+ Professional animation production services that bring your ideas + to life. From explainer videos to brand storytelling, we create + engaging animated content that resonates with your audience. +

+ +
    +
  • ✔ 2D & 3D Animation Production
  • +
  • ✔ Motion Graphics & Visual Effects
  • +
  • ✔ Character Design & Storyboarding
  • +
+
+ + {/* Image */} +
+ Animasee +
+
+
+ {/* Image */} +
+ Artifintel Soundworks +
+ + {/* Content */} +
+

+ Reelithic +

+

+ Artifintel Soundworks adalah pionir musik AI yang menghadirkan + karya dan kolaborasi dari musisi AI penuh kreativitas dan emosi. + Sejak 2024, Artifintel telah merilis belasan hingga puluhan lagu + dengan melodi memukau dan ritme inovatif. +

+ +
    +
  • ✔ AI Music Composition & Songwriting
  • +
  • ✔ Vocal Synthesis & AI Musicians
  • +
  • ✔ Genre Exploration & Sound Innovation
  • +
  • ✔ AI Collaboration & Creative Experimentation
  • +
  • ✔ Music Release & Digital Distribution
  • +
+
+
+ + {/* Service 3 */} +
+ {/* Content */} +
+

+ Qudoin +

+

+ Professional animation production services that bring your ideas + to life. From explainer videos to brand storytelling, we create + engaging animated content that resonates with your audience. +

+ +
    +
  • ✔ 2D & 3D Animation Production
  • +
  • ✔ Motion Graphics & Visual Effects
  • +
  • ✔ Character Design & Storyboarding
  • +
+
+ + {/* Image */} +
+ Animasee +
+
+
+ {/* Image */} +
+ Artifintel Soundworks +
+ + {/* Content */} +
+

+ Talkshow AI +

+

+ Artifintel Soundworks adalah pionir musik AI yang menghadirkan + karya dan kolaborasi dari musisi AI penuh kreativitas dan emosi. + Sejak 2024, Artifintel telah merilis belasan hingga puluhan lagu + dengan melodi memukau dan ritme inovatif. +

+ +
    +
  • ✔ AI Music Composition & Songwriting
  • +
  • ✔ Vocal Synthesis & AI Musicians
  • +
  • ✔ Genre Exploration & Sound Innovation
  • +
  • ✔ AI Collaboration & Creative Experimentation
  • +
  • ✔ Music Release & Digital Distribution
  • +
+
+
+
+
+ ); +} diff --git a/components/landing-page/technology.tsx b/components/landing-page/technology.tsx new file mode 100644 index 0000000..637b7db --- /dev/null +++ b/components/landing-page/technology.tsx @@ -0,0 +1,44 @@ +import Image from "next/image"; + +const technologies = [ + { name: "Tableau", src: "/image/tableu.png" }, + { name: "TVU Networks", src: "/image/tvu.png" }, + { name: "AWS", src: "/image/aws.png" }, + { name: "Dell", src: "/image/dell.png" }, + { name: "Zenlayer", src: "/image/zen.png" }, + { name: "Ui", src: "/image/uipath.png" }, +]; + +export default function Technology() { + return ( +
+
+ {/* Title */} +

+ TECHNOLOGY PARTNERS +

+ + {/* Slider */} +
+
+ {/* duplicated for seamless loop */} + {[...technologies, ...technologies].map((tech, index) => ( +
+ {tech.name} +
+ ))} +
+
+
+
+ ); +} diff --git a/components/layout/admin-layout.tsx b/components/layout/admin-layout.tsx new file mode 100644 index 0000000..e3ce929 --- /dev/null +++ b/components/layout/admin-layout.tsx @@ -0,0 +1,89 @@ +"use client"; + +import { useEffect, useState } from "react"; +import React, { ReactNode } from "react"; +import { SidebarProvider } from "./sidebar-context"; +import { ThemeProvider } from "./theme-context"; +import { Breadcrumbs } from "./breadcrumbs"; +import { BurgerButtonIcon } from "../icons"; + +import { motion, AnimatePresence } from "framer-motion"; +import { RetractingSidebar } from "../landing-page/retracting-sidedar"; + +export const AdminLayout = ({ children }: { children: ReactNode }) => { + const [isOpen, setIsOpen] = useState(true); + const [hasMounted, setHasMounted] = useState(false); + + const updateSidebarData = (newData: boolean) => { + setIsOpen(newData); + }; + + // Hooks + useEffect(() => { + setHasMounted(true); + }, []); + + // Render loading state until mounted + if (!hasMounted) { + return ( +
+
+
+ ); + } + + return ( + + +
+
+ + + + + {/* Header */} + +
+
+ + +
+
+
+ + {/* Main Content */} + +
{children}
+
+
+
+
+
+
+
+ ); +}; diff --git a/components/layout/breadcrumbs.tsx b/components/layout/breadcrumbs.tsx new file mode 100644 index 0000000..0d43a17 --- /dev/null +++ b/components/layout/breadcrumbs.tsx @@ -0,0 +1,154 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { usePathname, useRouter } from "next/navigation"; +import { + ArticleIcon, + DashboardIcon, + MagazineIcon, + MasterCategoryIcon, + MasterRoleIcon, + MasterUsersIcon, + StaticPageIcon, +} from "../icons/sidebar-icon"; +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbSeparator, +} from "../ui/breadcrumb"; +import React from "react"; +import { motion } from "framer-motion"; + +export const Breadcrumbs = () => { + const [currentPage, setCurrentPage] = useState(""); + const [mounted, setMounted] = useState(false); + const router = useRouter(); + const pathname = usePathname(); + const pathnameSplit = pathname.split("/"); + + pathnameSplit.shift(); + const pathnameTransformed = pathnameSplit.map((item) => { + const words = item.split("-"); + const capitalizedWords = words.map( + (word) => word.charAt(0).toUpperCase() + word.slice(1), + ); + return capitalizedWords.join(" "); + }); + + useEffect(() => { + setMounted(true); + }, []); + + useEffect(() => { + setCurrentPage(pathnameSplit[pathnameSplit.length - 1]); + }, [pathnameSplit]); + + const handleAction = (key: any) => { + const keyIndex = pathnameSplit.indexOf(key); + const combinedPath = pathnameSplit.slice(0, keyIndex + 1).join("/"); + router.push("/" + combinedPath); + }; + + const getPageIcon = () => { + if (pathname.includes("dashboard")) return ; + if (pathname.includes("article")) return ; + if (pathname.includes("master-category")) + return ; + if (pathname.includes("magazine")) return ; + if (pathname.includes("static-page")) return ; + if (pathname.includes("master-user")) return ; + if (pathname.includes("master-role")) return ; + return null; + }; + + if (!mounted) { + return ( +
+
+
+
+
+
+
+ ); + } + + return ( + + {/* Page Icon */} + + {getPageIcon()} + + + {/* Page Title and Breadcrumbs */} +
+ + {pathnameTransformed[pathnameTransformed.length - 1]} + + + + + + {pathnameTransformed + ?.filter((item) => item !== "Admin") + .map((item, index, array) => ( + + + handleAction(pathnameSplit[index])} + className={`text-sm transition-all duration-200 hover:text-blue-600 ${ + pathnameSplit[index] === currentPage + ? "font-semibold text-blue-600" + : "text-slate-500 hover:text-slate-700" + }`} + > + {item} + + + {index < array.length - 1 && ( + + + + + + )} + + ))} + + + +
+
+ ); +}; diff --git a/components/layout/chunk-error-boundary.tsx b/components/layout/chunk-error-boundary.tsx new file mode 100644 index 0000000..fda4d6f --- /dev/null +++ b/components/layout/chunk-error-boundary.tsx @@ -0,0 +1,104 @@ +"use client"; + +import React, { Component, ErrorInfo, ReactNode } from 'react'; +import { Button } from '@/components/ui/button'; +import { RefreshCw } from 'lucide-react'; + +interface Props { + children: ReactNode; + fallback?: ReactNode; +} + +interface State { + hasError: boolean; + error?: Error; +} + +class ChunkErrorBoundary extends Component { + constructor(props: Props) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(error: Error): State { + // Check if it's a chunk loading error + if (error.name === 'ChunkLoadError' || error.message.includes('Loading chunk')) { + return { hasError: true, error }; + } + return { hasError: false }; + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + console.error('Chunk loading error:', error, errorInfo); + + // If it's a chunk loading error, try to reload the page + if (error.name === 'ChunkLoadError' || error.message.includes('Loading chunk')) { + this.setState({ hasError: true, error }); + } + } + + handleRetry = () => { + // Clear the error state and reload the page + this.setState({ hasError: false, error: undefined }); + window.location.reload(); + }; + + render() { + if (this.state.hasError) { + if (this.props.fallback) { + return this.props.fallback; + } + + return ( +
+
+
+
+ +
+

+ Chunk Loading Error +

+

+ There was an issue loading a part of the application. This usually happens when the application has been updated. +

+
+ +
+ + + +
+ + {process.env.NODE_ENV === 'development' && this.state.error && ( +
+ + Error Details (Development) + +
+                  {this.state.error.message}
+                
+
+ )} +
+
+ ); + } + + return this.props.children; + } +} + +export default ChunkErrorBoundary; \ No newline at end of file diff --git a/components/layout/circular-progress.tsx b/components/layout/circular-progress.tsx new file mode 100644 index 0000000..82ffbba --- /dev/null +++ b/components/layout/circular-progress.tsx @@ -0,0 +1,36 @@ +import React from "react"; + +interface CircularProgressProps { + size?: number; + strokeWidth?: number; + value: number; // 0 to 100 + className?: string; +} + +export function CircularProgress({ size = 48, strokeWidth = 4, value, className }: CircularProgressProps) { + const radius = (size - strokeWidth) / 2; + const circumference = 2 * Math.PI * radius; + const offset = circumference - (value / 100) * circumference; + + return ( + + + + + {Math.round(value)}% + + + ); +} diff --git a/components/layout/costum-circular-progress.tsx b/components/layout/costum-circular-progress.tsx new file mode 100644 index 0000000..bb4f04f --- /dev/null +++ b/components/layout/costum-circular-progress.tsx @@ -0,0 +1,54 @@ +import React from "react"; + +type CircularProgressProps = { + value: number; // antara 0 - 100 + size?: number; // diameter lingkaran (px) + strokeWidth?: number; + color?: string; + bgColor?: string; + label?: string; +}; + +export const CustomCircularProgress = ({ + value, + size = 80, + strokeWidth = 8, + color = "#f59e0b", // shadcn's warning color + bgColor = "#e5e7eb", // gray-200 + label, +}: CircularProgressProps) => { + const radius = (size - strokeWidth) / 2; + const circumference = 2 * Math.PI * radius; + const progress = Math.min(Math.max(value, 0), 100); // jaga antara 0 - 100 + const offset = circumference - (progress / 100) * circumference; + + return ( +
+ + + + + + {label ?? `${Math.round(progress)}%`} + +
+ ); +}; diff --git a/components/layout/custom-pagination.tsx b/components/layout/custom-pagination.tsx new file mode 100644 index 0000000..80dd848 --- /dev/null +++ b/components/layout/custom-pagination.tsx @@ -0,0 +1,125 @@ +"use client"; +import { + Pagination, + PaginationContent, + PaginationEllipsis, + PaginationItem, + PaginationLink, + PaginationNext, + PaginationPrevious, +} from "@/components/ui/pagination"; + +import { useEffect, useState } from "react"; + +export default function CustomPagination(props: { + totalPage: number; + onPageChange: (data: number) => void; +}) { + const { totalPage, onPageChange } = props; + const [page, setPage] = useState(1); + + useEffect(() => { + onPageChange(page); + }, [page]); + + const renderPageNumbers = () => { + const pageNumbers = []; + const halfWindow = Math.floor(5 / 2); + let startPage = Math.max(2, page - halfWindow); + let endPage = Math.min(totalPage - 1, page + halfWindow); + + if (endPage - startPage + 1 < 5) { + if (page <= halfWindow) { + endPage = Math.min( + totalPage - 1, + endPage + (5 - (endPage - startPage + 1)) + ); + } else if (page + halfWindow >= totalPage) { + startPage = Math.max(2, startPage - (5 - (endPage - startPage + 1))); + } + } + + for (let i = startPage; i <= endPage; i++) { + pageNumbers.push( + setPage(i)}> + + {i} + + + ); + } + + return pageNumbers; + }; + return ( + + + + (page > 10 ? setPage(page - 10) : "")} + > + {/* */} + + + + (page > 1 ? setPage(page - 1) : "")} + /> + + + setPage(1)} + isActive={page === 1} + > + {1} + + + {page > 4 && ( + + setPage(page - 1)} + /> + + )} + {renderPageNumbers()} + {page < totalPage - 3 && ( + + setPage(page + 1)} + /> + + )} + {totalPage > 1 && ( + + setPage(totalPage)} + isActive={page === totalPage} + > + {totalPage} + + + )} + + + (page < totalPage ? setPage(page + 1) : "")} + /> + + + (page < totalPage - 10 ? setPage(page + 10) : "")} + > + {/* */} + + + + + ); +} diff --git a/components/layout/dashboard-admin.tsx b/components/layout/dashboard-admin.tsx new file mode 100644 index 0000000..c49a9b4 --- /dev/null +++ b/components/layout/dashboard-admin.tsx @@ -0,0 +1,59 @@ +const AdminDashboard = () => { + return ( +
+
+

Admin Dashboard

+

Review and manage content submissions

+
+ + {/* STAT CARDS */} +
+ {[ + { + title: "Open Tasks", + value: 2, + color: "bg-yellow-500", + growth: "+3", + }, + { + title: "Closed Tasks", + value: 2, + color: "bg-green-600", + growth: "+5", + }, + { + title: "Total Submissions", + value: 4, + color: "bg-blue-600", + growth: "+12%", + }, + { + title: "Rejected", + value: 7, + color: "bg-red-600", + growth: "-1", + }, + ].map((card, i) => ( +
+
+

{card.title}

+

+ {card.value} +

+
+ +
+

+ {card.growth} +

+
+
+
+ ))} +
+
+ ); +}; diff --git a/components/layout/dashboard-contributor.tsx b/components/layout/dashboard-contributor.tsx new file mode 100644 index 0000000..e69de29 diff --git a/components/layout/sidebar-context.tsx b/components/layout/sidebar-context.tsx new file mode 100644 index 0000000..a566005 --- /dev/null +++ b/components/layout/sidebar-context.tsx @@ -0,0 +1,58 @@ +'use client' +import React, { createContext, useContext, useEffect, useState } from 'react'; + +interface SidebarContextType { + isOpen: boolean; + toggleSidebar: () => void; +} + +const SidebarContext = createContext(undefined); + +export const SidebarProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [isOpen, setIsOpen] = useState(() => { + if (typeof window !== 'undefined') { + const storedValue = localStorage.getItem('sidebarOpen'); + return storedValue ? JSON.parse(storedValue) : false; + } + }); + + const toggleSidebar = () => { + setIsOpen(!isOpen); + }; + + useEffect(() => { + localStorage.setItem('sidebarOpen', JSON.stringify(isOpen)); + }, [isOpen]); + + useEffect(() => { + const handleResize = () => { + setIsOpen(window.innerWidth > 768); // Ganti 768 dengan lebar yang sesuai dengan breakpoint Anda + }; + + handleResize(); // Pastikan untuk memanggil fungsi handleResize saat komponen dimuat + + window.addEventListener('resize', handleResize); + + return () => { + window.removeEventListener('resize', handleResize); + }; + }, []); + + return ( + + {children} + + ); +}; + +export const useSidebar = () => { + const context = useContext(SidebarContext); + if (!context) { + throw new Error('useSidebar must be used within a SidebarProvider'); + } + return context; +}; + +export const useSidebarContext = () => { + return useContext(SidebarContext); +}; \ No newline at end of file diff --git a/components/layout/theme-context.tsx b/components/layout/theme-context.tsx new file mode 100644 index 0000000..b498a01 --- /dev/null +++ b/components/layout/theme-context.tsx @@ -0,0 +1,67 @@ +"use client"; + +import React, { createContext, useContext, useEffect, useState } from 'react'; + +type Theme = 'light' | 'dark'; + +interface ThemeContextType { + theme: Theme; + toggleTheme: () => void; + setTheme: (theme: Theme) => void; +} + +const ThemeContext = createContext(undefined); + +export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [theme, setThemeState] = useState('light'); + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + // Get theme from localStorage or default to 'light' + const savedTheme = localStorage.getItem('theme') as Theme; + if (savedTheme) { + setThemeState(savedTheme); + } else { + // Check system preference + const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; + setThemeState(systemTheme); + } + }, []); + + useEffect(() => { + if (!mounted) return; + + // Update document class and localStorage + document.documentElement.classList.remove('light', 'dark'); + document.documentElement.classList.add(theme); + localStorage.setItem('theme', theme); + }, [theme, mounted]); + + const toggleTheme = () => { + setThemeState(prev => prev === 'light' ? 'dark' : 'light'); + }; + + const setTheme = (newTheme: Theme) => { + setThemeState(newTheme); + }; + + // Prevent hydration mismatch + if (!mounted) { + return <>{children}; + } + + return ( + + {children} + + ); +}; + +export const useTheme = () => { + const context = useContext(ThemeContext); + if (context === undefined) { + throw new Error('useTheme must be used within a ThemeProvider'); + } + return context; +}; \ No newline at end of file diff --git a/components/main/dashboard/chart/column-chart.tsx b/components/main/dashboard/chart/column-chart.tsx new file mode 100644 index 0000000..db77733 --- /dev/null +++ b/components/main/dashboard/chart/column-chart.tsx @@ -0,0 +1,157 @@ +"use client"; + +import React, { useEffect, useState } from "react"; + +type WeekData = { + week: number; + days: number[]; + total: number; +}; + +type RemainingDays = { + days: number[]; + total: number; +}; + +function processMonthlyData(count: number[]): { + weeks: WeekData[]; + remaining_days: RemainingDays; +} { + const weeks: WeekData[] = []; + let weekIndex = 1; + + for (let i = 0; i < count.length; i += 7) { + const weekData = count.slice(i, i + 7); + weeks.push({ + week: weekIndex, + days: weekData, + total: weekData.reduce((sum, day) => sum + day, 0), + }); + weekIndex++; + } + + 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), + }; + + return { + weeks, + remaining_days: remainingDays, + }; +} + +const ApexChartColumn = (props: { + type: string; + date: string; + view: string[]; +}) => { + const { date, type, view } = props; + const [categories, setCategories] = useState([]); + const [series, setSeries] = useState<{ name: string; data: number[] }[]>([]); + const [seriesComment, setSeriesComment] = useState([]); + const [seriesView, setSeriesView] = useState([]); + const [seriesShare, setSeriesShare] = useState([]); + + // useEffect(() => { + // initFetch(); + // }, [date, type, view]); + + // const initFetch = async () => { + // const splitDate = date.split(" "); + + // const res = await getStatisticMonthly(splitDate[1]); + // const data = res?.data?.data; + // const getDatas = data?.find( + // (a: any) => + // a.month == Number(splitDate[0]) && a.year === Number(splitDate[1]) + // ); + // if (getDatas) { + // const temp1 = processMonthlyData(getDatas?.comment); + // const temp2 = processMonthlyData(getDatas?.view); + // const temp3 = processMonthlyData(getDatas?.share); + + // if (type == "weekly") { + // setSeriesComment( + // temp1.weeks.map((list) => { + // return list.total; + // }) + // ); + // setSeriesView( + // temp2.weeks.map((list) => { + // return list.total; + // }) + // ); + // setSeriesShare( + // temp3.weeks.map((list) => { + // return list.total; + // }) + // ); + // } else { + // setSeriesComment(getDatas.comment); + // setSeriesView(getDatas.view); + // setSeriesShare(getDatas.share); + // } + // if (type === "weekly") { + // const category = []; + // for (let i = 1; i <= temp1.weeks.length; i++) { + // category.push(`Week ${i}`); + // } + // setCategories(category); + // } + // } else { + // setSeriesComment([]); + // } + // }; + + useEffect(() => { + const temp = [ + { + name: "Comment", + data: view.includes("comment") ? seriesComment : [], + }, + { + name: "View", + data: view.includes("view") ? seriesView : [], + }, + { + name: "Share", + data: view.includes("share") ? seriesShare : [], + }, + ]; + + console.log("temp", temp); + + setSeries(temp); + }, [view, seriesShare, seriesView, seriesComment]); + + return ( +
+
+ {/* */} +
+
+
+ ); +}; + +export default ApexChartColumn; diff --git a/components/main/dashboard/dashboard-container.tsx b/components/main/dashboard/dashboard-container.tsx new file mode 100644 index 0000000..468e23f --- /dev/null +++ b/components/main/dashboard/dashboard-container.tsx @@ -0,0 +1,481 @@ +"use client"; + +import Cookies from "js-cookie"; +import Link from "next/link"; +import { useEffect, useState } from "react"; +import { Article } from "@/types/globals"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import "react-datepicker/dist/react-datepicker.css"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Label } from "@/components/ui/label"; +import { Calendar } from "@/components/ui/calendar"; +import ApexChartColumn from "@/components/main/dashboard/chart/column-chart"; +import CustomPagination from "@/components/layout/custom-pagination"; +import { motion } from "framer-motion"; +import { Input } from "@/components/ui/input"; +import { + SelectTrigger, + SelectValue, + SelectContent, + SelectItem, + Select, +} from "@/components/ui/select"; +import { Badge } from "lucide-react"; + +type ArticleData = Article & { + no: number; + createdAt: string; +}; + +interface TopPages { + id: number; + no: number; + title: string; + viewCount: number; +} + +interface PostCount { + userLevelId: number; + no: number; + userLevelName: string; + totalArticle: number; +} + +export default function DashboardContainer() { + const [roleName, setRoleName] = useState(); + useEffect(() => { + const role = Cookies.get("roleName"); + setRoleName(role); + }, []); + + const username = Cookies.get("username"); + const fullname = Cookies.get("ufne"); + const [page, setPage] = useState(1); + const [totalPage, setTotalPage] = useState(1); + const [topPagesTotalPage, setTopPagesTotalPage] = useState(1); + const [article, setArticle] = useState([]); + // const [analyticsView, setAnalyticView] = useState(["comment", "view", "share"]); + // const [startDateValue, setStartDateValue] = useState(parseDate(convertDateFormatNoTimeV2(new Date()))); + // const [postContentDate, setPostContentDate] = useState({ + // startDate: parseDate(convertDateFormatNoTimeV2(new Date(new Date().setDate(new Date().getDate() - 7)))), + // endDate: parseDate(convertDateFormatNoTimeV2(new Date())), + // }); + + const [startDateValue, setStartDateValue] = useState(new Date()); + const [analyticsView, setAnalyticView] = useState([]); + const options = [ + { label: "Comment", value: "comment" }, + { label: "View", value: "view" }, + { label: "Share", value: "share" }, + ]; + const handleChange = (value: string, checked: boolean) => { + if (checked) { + setAnalyticView([...analyticsView, value]); + } else { + setAnalyticView(analyticsView.filter((v) => v !== value)); + } + }; + const [postContentDate, setPostContentDate] = useState({ + startDate: new Date(new Date().setDate(new Date().getDate() - 7)), + endDate: new Date(), + }); + + const [typeDate, setTypeDate] = useState("monthly"); + const [summary, setSummary] = useState(); + + const [topPages, setTopPages] = useState([]); + const [postCount, setPostCount] = useState([]); + + // useEffect(() => { + // fetchSummary(); + // }, []); + + // useEffect(() => { + // initState(); + // }, [page]); + + // async function initState() { + // const req = { + // limit: "5", + // page: page, + // search: "", + // }; + // const res = await getListArticle(req); + // setArticle(res.data?.data); + // setTotalPage(res?.data?.meta?.totalPage); + // } + + // async function fetchSummary() { + // const res = await getStatisticSummary(); + // setSummary(res?.data?.data); + // } + + // useEffect(() => { + // fetchTopPages(); + // }, [page]); + + // async function fetchTopPages() { + // const req = { + // limit: "10", + // page: page, + // search: "", + // }; + // const res = await getTopArticles(req); + // setTopPages(getTableNumber(10, res.data?.data)); + // setTopPagesTotalPage(res?.data?.meta?.totalPage); + // } + + // useEffect(() => { + // fetchPostCount(); + // }, [postContentDate]); + // async function fetchPostCount() { + // const getDate = (data: any) => { + // return `${data.year}-${data.month < 10 ? `0${data.month}` : data.month}-${ + // data.day < 10 ? `0${data.day}` : data.day + // }`; + // }; + // const res = await getUserLevelDataStat( + // getDate(postContentDate.startDate), + // getDate(postContentDate.endDate) + // ); + // setPostCount(getTableNumber(10, res?.data?.data)); + // } + + const getTableNumber = (limit: number, data: any) => { + if (data) { + const startIndex = limit * (page - 1); + let iterate = 0; + const newData = data.map((value: any) => { + iterate++; + value.no = startIndex + iterate; + return value; + }); + return newData; + } + }; + + const getMonthYear = (date: any) => { + return date.month + " " + date.year; + }; + const getMonthYearName = (date: any) => { + const newDate = new Date(date); + + const months = [ + "Januari", + "Februari", + "Maret", + "April", + "Mei", + "Juni", + "Juli", + "Agustus", + "September", + "Oktober", + "November", + "Desember", + ]; + const year = newDate.getFullYear(); + + const month = months[newDate.getMonth()]; + return month + " " + year; + }; + + if (!roleName) return null; + const AdminDashboard = () => { + const tasks = [ + { + id: "1", + title: "MediaHUB Content Aggregator", + author: "John Kontributor", + category: "Product", + date: "2026-02-13", + status: "OPEN", + }, + { + id: "2", + title: + "Mudik Nyaman Bersama Pertamina: Layanan 24 Jam, Motoris, dan Fasilitas Lengkap", + author: "Jane Kontributor", + category: "Service", + date: "2026-02-13", + status: "OPEN", + }, + { + id: "3", + title: "Artifintel Services Update", + author: "Alex Approver", + category: "Event", + date: "2026-02-13", + status: "CLOSED", + }, + ]; + + return ( +
+ {/* HEADER */} +
+

Admin Dashboard

+

+ Review and manage content submissions +

+
+ + {/* STAT CARDS */} +
+ {[ + { title: "Open Tasks", value: 2, color: "bg-yellow-500" }, + { title: "Closed Tasks", value: 2, color: "bg-green-600" }, + { title: "Total Submissions", value: 4, color: "bg-blue-600" }, + { title: "Rejected", value: 7, color: "bg-red-600" }, + ].map((card, i) => ( +
+
+

{card.title}

+

+ {card.value} +

+
+
+
+ ))} +
+ + {/* TASK LIST */} +
+ {/* Title */} +
+

+ Task List{" "} + + {tasks.length} Tasks + +

+
+ + {/* Filters */} +
+ + + +
+ + {/* Accordion List */} + + {tasks.map((task) => ( + + +
+
+

{task.title}

+

+ {task.author} • {task.category} • {task.date} +

+
+ +
+ + {task.status} + + + {/* Mini Progress */} +
+
+
+
+
+
+
+
+ + + +
+ {/* Title */} +
+
+
+
+

+ Document Flow Status +

+
+ + {/* Stepper */} +
+ {/* Line */} +
+ +
+ {/* STEP 1 */} +
+
+ ✓ +
+

+ Submission +

+

+ Feb 13, 2026 09:00 +

+
+ + {/* STEP 2 */} +
+
+ ● +
+

+ Technical Review +

+

+ Feb 13, 2026 11:00 +

+
+ + {/* STEP 3 */} +
+
+

+ Admin Verification +

+

Waiting

+
+ + {/* STEP 4 */} +
+
+

+ Final Approval +

+

Waiting

+
+
+
+
+ + + ))} + +
+
+ ); + }; + + const ContributorDashboard = () => { + return ( +
+
+

Dashboard

+
+ + {/* STAT CARDS */} +
+ {[ + { + title: "Total Content", + value: 24, + color: "bg-blue-600", + growth: "+12%", + }, + { + title: "Pending Approval", + value: 8, + color: "bg-yellow-500", + growth: "+3", + }, + { + title: "Published", + value: 16, + color: "bg-green-600", + growth: "+5", + }, + { + title: "Rejected", + value: 2, + color: "bg-red-600", + growth: "-1", + }, + ].map((card, i) => ( +
+
+

{card.title}

+

+ {card.value} +

+
+ +
+

+ {card.growth} +

+
+
+
+ ))} +
+ + {/* CONTENT + QUICK ACTIONS */} +
+
+

Recent Content

+ {/* isi list content di sini */} +
+ +
+

Quick Actions

+ + + + + + +
+
+
+ ); + }; + + return ( + <>{roleName === "Admin" ? : } + ); +} diff --git a/components/ui/accordion.tsx b/components/ui/accordion.tsx new file mode 100644 index 0000000..aa972da --- /dev/null +++ b/components/ui/accordion.tsx @@ -0,0 +1,66 @@ +"use client" + +import * as React from "react" +import { ChevronDownIcon } from "lucide-react" +import { Accordion as AccordionPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" + +function Accordion({ + ...props +}: React.ComponentProps) { + return +} + +function AccordionItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AccordionTrigger({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + svg]:rotate-180", + className + )} + {...props} + > + {children} + + + + ) +} + +function AccordionContent({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + +
{children}
+
+ ) +} + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx new file mode 100644 index 0000000..83750ed --- /dev/null +++ b/components/ui/badge.tsx @@ -0,0 +1,46 @@ +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const badgeVariants = cva( + "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", + secondary: + "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", + destructive: + "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +); + +function Badge({ + className, + variant, + asChild = false, + ...props +}: React.ComponentProps<"span"> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : "span"; + + return ( + + ); +} + +export { Badge, badgeVariants }; diff --git a/components/ui/breadcrumb.tsx b/components/ui/breadcrumb.tsx new file mode 100644 index 0000000..eb88f32 --- /dev/null +++ b/components/ui/breadcrumb.tsx @@ -0,0 +1,109 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { ChevronRight, MoreHorizontal } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Breadcrumb({ ...props }: React.ComponentProps<"nav">) { + return