-
-
Project ready!
-
You may now add components and start building.
-
We've already added the button component for you.
-
-
-
- (Press d to toggle dark mode)
-
+
+
+
+
+
+ Selamat Datang
di
Silancar!
+
+
+
+
+
)
}
diff --git a/components/layout/dashboard-side-menu.tsx b/components/layout/dashboard-side-menu.tsx
new file mode 100644
index 0000000..aca519c
--- /dev/null
+++ b/components/layout/dashboard-side-menu.tsx
@@ -0,0 +1,69 @@
+import { RefreshCwIcon } from "lucide-react"
+import { Button } from "../ui/button"
+import {
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselNext,
+ CarouselPrevious,
+} from "@/components/ui/carousel"
+
+const dummy = [
+ {
+ name: "Ipda Iwan",
+ message: "Ndan, lokasi di Bundaran HI lalu lintas lancar",
+ time: "16 Oct 2024, 13:44",
+ },
+ {
+ name: "Ipda Iwan",
+ message: "Ndan, lokasi di Bundaran HI lalu lintas lancar",
+ time: "16 Oct 2024, 13:44",
+ },
+]
+
+export default function DashboardSideMenu() {
+ return (
+
+
+
+ Pesan Pribadi Terakhir
+
+
+
+
+ {dummy.map((list, index) => (
+
+
+
{list.name}
+
{list.message}
+
{list.time}
+
+
+ ))}
+
+
+
+
+
+
+ Panggilan Pribadi Terakhir
+
+
+
+ {dummy.map((list, index) => (
+
+
+
{list.name}
+
{list.message}
+
{list.time}
+
+
+ ))}
+
+
+
+
+ )
+}
diff --git a/components/layout/footer.tsx b/components/layout/footer.tsx
new file mode 100644
index 0000000..aba0de4
--- /dev/null
+++ b/components/layout/footer.tsx
@@ -0,0 +1,8 @@
+export default function Footer() {
+ return (
+
+
ver 1.0.0
+
@2024 - Korlantas Hak Cipta Dilindungi Undang-Undang.
+
+ )
+}
diff --git a/components/ui/carousel.tsx b/components/ui/carousel.tsx
new file mode 100644
index 0000000..9deee76
--- /dev/null
+++ b/components/ui/carousel.tsx
@@ -0,0 +1,242 @@
+"use client"
+
+import * as React from "react"
+import useEmblaCarousel, {
+ type UseEmblaCarouselType,
+} from "embla-carousel-react"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"
+
+type CarouselApi = UseEmblaCarouselType[1]
+type UseCarouselParameters = Parameters
+type CarouselOptions = UseCarouselParameters[0]
+type CarouselPlugin = UseCarouselParameters[1]
+
+type CarouselProps = {
+ opts?: CarouselOptions
+ plugins?: CarouselPlugin
+ orientation?: "horizontal" | "vertical"
+ setApi?: (api: CarouselApi) => void
+}
+
+type CarouselContextProps = {
+ carouselRef: ReturnType[0]
+ api: ReturnType[1]
+ scrollPrev: () => void
+ scrollNext: () => void
+ canScrollPrev: boolean
+ canScrollNext: boolean
+} & CarouselProps
+
+const CarouselContext = React.createContext(null)
+
+function useCarousel() {
+ const context = React.useContext(CarouselContext)
+
+ if (!context) {
+ throw new Error("useCarousel must be used within a ")
+ }
+
+ return context
+}
+
+function Carousel({
+ orientation = "horizontal",
+ opts,
+ setApi,
+ plugins,
+ className,
+ children,
+ ...props
+}: React.ComponentProps<"div"> & CarouselProps) {
+ const [carouselRef, api] = useEmblaCarousel(
+ {
+ ...opts,
+ axis: orientation === "horizontal" ? "x" : "y",
+ },
+ plugins
+ )
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false)
+ const [canScrollNext, setCanScrollNext] = React.useState(false)
+
+ const onSelect = React.useCallback((api: CarouselApi) => {
+ if (!api) return
+ setCanScrollPrev(api.canScrollPrev())
+ setCanScrollNext(api.canScrollNext())
+ }, [])
+
+ const scrollPrev = React.useCallback(() => {
+ api?.scrollPrev()
+ }, [api])
+
+ const scrollNext = React.useCallback(() => {
+ api?.scrollNext()
+ }, [api])
+
+ const handleKeyDown = React.useCallback(
+ (event: React.KeyboardEvent) => {
+ if (event.key === "ArrowLeft") {
+ event.preventDefault()
+ scrollPrev()
+ } else if (event.key === "ArrowRight") {
+ event.preventDefault()
+ scrollNext()
+ }
+ },
+ [scrollPrev, scrollNext]
+ )
+
+ React.useEffect(() => {
+ if (!api || !setApi) return
+ setApi(api)
+ }, [api, setApi])
+
+ React.useEffect(() => {
+ if (!api) return
+ onSelect(api)
+ api.on("reInit", onSelect)
+ api.on("select", onSelect)
+
+ return () => {
+ api?.off("select", onSelect)
+ }
+ }, [api, onSelect])
+
+ return (
+
+
+ {children}
+
+
+ )
+}
+
+function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
+ const { carouselRef, orientation } = useCarousel()
+
+ return (
+
+ )
+}
+
+function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
+ const { orientation } = useCarousel()
+
+ return (
+
+ )
+}
+
+function CarouselPrevious({
+ className,
+ variant = "outline",
+ size = "icon-sm",
+ ...props
+}: React.ComponentProps) {
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel()
+
+ return (
+
+ )
+}
+
+function CarouselNext({
+ className,
+ variant = "outline",
+ size = "icon-sm",
+ ...props
+}: React.ComponentProps) {
+ const { orientation, scrollNext, canScrollNext } = useCarousel()
+
+ return (
+
+ )
+}
+
+export {
+ type CarouselApi,
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselPrevious,
+ CarouselNext,
+ useCarousel,
+}
diff --git a/components/ui/drawer.tsx b/components/ui/drawer.tsx
new file mode 100644
index 0000000..ea930ee
--- /dev/null
+++ b/components/ui/drawer.tsx
@@ -0,0 +1,131 @@
+"use client"
+
+import * as React from "react"
+import { Drawer as DrawerPrimitive } from "vaul"
+
+import { cn } from "@/lib/utils"
+
+function Drawer({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DrawerTrigger({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DrawerPortal({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DrawerClose({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DrawerOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DrawerContent({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ {children}
+
+
+ )
+}
+
+function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function DrawerTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DrawerDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ Drawer,
+ DrawerPortal,
+ DrawerOverlay,
+ DrawerTrigger,
+ DrawerClose,
+ DrawerContent,
+ DrawerHeader,
+ DrawerFooter,
+ DrawerTitle,
+ DrawerDescription,
+}
diff --git a/components/ui/input.tsx b/components/ui/input.tsx
new file mode 100644
index 0000000..d763cd9
--- /dev/null
+++ b/components/ui/input.tsx
@@ -0,0 +1,19 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Input({ className, type, ...props }: React.ComponentProps<"input">) {
+ return (
+
+ )
+}
+
+export { Input }
diff --git a/components/ui/sheet.tsx b/components/ui/sheet.tsx
new file mode 100644
index 0000000..7039402
--- /dev/null
+++ b/components/ui/sheet.tsx
@@ -0,0 +1,144 @@
+"use client"
+
+import * as React from "react"
+import { Dialog as SheetPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import { XIcon } from "lucide-react"
+
+function Sheet({ ...props }: React.ComponentProps) {
+ return
+}
+
+function SheetTrigger({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SheetClose({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SheetPortal({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SheetOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SheetContent({
+ className,
+ children,
+ side = "right",
+ showCloseButton = true,
+ ...props
+}: React.ComponentProps & {
+ side?: "top" | "right" | "bottom" | "left"
+ showCloseButton?: boolean
+}) {
+ return (
+
+
+
+ {children}
+ {showCloseButton && (
+
+
+
+ )}
+
+
+ )
+}
+
+function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SheetTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SheetDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ Sheet,
+ SheetTrigger,
+ SheetClose,
+ SheetContent,
+ SheetHeader,
+ SheetFooter,
+ SheetTitle,
+ SheetDescription,
+}
diff --git a/package-lock.json b/package-lock.json
index cbc7b3e..7d4c879 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,15 +10,18 @@
"dependencies": {
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+ "embla-carousel-react": "^8.6.0",
"lucide-react": "^0.577.0",
"next": "16.1.7",
"next-themes": "^0.4.6",
"radix-ui": "^1.4.3",
"react": "^19.2.4",
"react-dom": "^19.2.4",
+ "react-hook-form": "^7.71.2",
"shadcn": "^4.0.8",
"tailwind-merge": "^3.5.0",
- "tw-animate-css": "^1.4.0"
+ "tw-animate-css": "^1.4.0",
+ "vaul": "^1.1.2"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
@@ -5224,6 +5227,31 @@
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz",
"integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA=="
},
+ "node_modules/embla-carousel": {
+ "version": "8.6.0",
+ "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
+ "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="
+ },
+ "node_modules/embla-carousel-react": {
+ "version": "8.6.0",
+ "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.6.0.tgz",
+ "integrity": "sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==",
+ "dependencies": {
+ "embla-carousel": "8.6.0",
+ "embla-carousel-reactive-utils": "8.6.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/embla-carousel-reactive-utils": {
+ "version": "8.6.0",
+ "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.6.0.tgz",
+ "integrity": "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==",
+ "peerDependencies": {
+ "embla-carousel": "8.6.0"
+ }
+ },
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@@ -8923,6 +8951,21 @@
"react": "^19.2.4"
}
},
+ "node_modules/react-hook-form": {
+ "version": "7.71.2",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.2.tgz",
+ "integrity": "sha512-1CHvcDYzuRUNOflt4MOq3ZM46AronNJtQ1S7tnX6YN4y72qhgiUItpacZUAQ0TyWYci3yz1X+rXaSxiuEm86PA==",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-hook-form"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17 || ^18 || ^19"
+ }
+ },
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@@ -10442,6 +10485,18 @@
"node": ">= 0.8"
}
},
+ "node_modules/vaul": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz",
+ "integrity": "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==",
+ "dependencies": {
+ "@radix-ui/react-dialog": "^1.1.1"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
diff --git a/package.json b/package.json
index ac35922..272fd69 100644
--- a/package.json
+++ b/package.json
@@ -14,15 +14,18 @@
"dependencies": {
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+ "embla-carousel-react": "^8.6.0",
"lucide-react": "^0.577.0",
"next": "16.1.7",
"next-themes": "^0.4.6",
"radix-ui": "^1.4.3",
"react": "^19.2.4",
"react-dom": "^19.2.4",
+ "react-hook-form": "^7.71.2",
"shadcn": "^4.0.8",
"tailwind-merge": "^3.5.0",
- "tw-animate-css": "^1.4.0"
+ "tw-animate-css": "^1.4.0",
+ "vaul": "^1.1.2"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
diff --git a/public/icon-ahli.svg b/public/icon-ahli.svg
new file mode 100644
index 0000000..1239a0d
--- /dev/null
+++ b/public/icon-ahli.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/icon-etle.svg b/public/icon-etle.svg
new file mode 100644
index 0000000..970c74e
--- /dev/null
+++ b/public/icon-etle.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/icon-komunikasi.svg b/public/icon-komunikasi.svg
new file mode 100644
index 0000000..e71b8b2
--- /dev/null
+++ b/public/icon-komunikasi.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/icon-map.svg b/public/icon-map.svg
new file mode 100644
index 0000000..f2fd366
--- /dev/null
+++ b/public/icon-map.svg
@@ -0,0 +1,10 @@
+
diff --git a/public/icon-patrol.svg b/public/icon-patrol.svg
new file mode 100644
index 0000000..d83944a
--- /dev/null
+++ b/public/icon-patrol.svg
@@ -0,0 +1,19 @@
+
diff --git a/public/icon-sos.svg b/public/icon-sos.svg
new file mode 100644
index 0000000..61cf23d
--- /dev/null
+++ b/public/icon-sos.svg
@@ -0,0 +1,4 @@
+
diff --git a/public/icon-spklu.svg b/public/icon-spklu.svg
new file mode 100644
index 0000000..faf2b08
--- /dev/null
+++ b/public/icon-spklu.svg
@@ -0,0 +1,4 @@
+
diff --git a/public/icon-tugas.svg b/public/icon-tugas.svg
new file mode 100644
index 0000000..0906cfb
--- /dev/null
+++ b/public/icon-tugas.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/main-background.png b/public/main-background.png
new file mode 100644
index 0000000..cd6efec
Binary files /dev/null and b/public/main-background.png differ
diff --git a/public/main-icon.png b/public/main-icon.png
new file mode 100644
index 0000000..9ee1ed6
Binary files /dev/null and b/public/main-icon.png differ
diff --git a/public/sample-1.jpg b/public/sample-1.jpg
new file mode 100644
index 0000000..0b4e9a5
Binary files /dev/null and b/public/sample-1.jpg differ
diff --git a/public/sample-2.jpg b/public/sample-2.jpg
new file mode 100644
index 0000000..465b85f
Binary files /dev/null and b/public/sample-2.jpg differ
diff --git a/public/sample-3.jpg b/public/sample-3.jpg
new file mode 100644
index 0000000..b226c78
Binary files /dev/null and b/public/sample-3.jpg differ
diff --git a/public/sample-4.jpg b/public/sample-4.jpg
new file mode 100644
index 0000000..201c9de
Binary files /dev/null and b/public/sample-4.jpg differ
diff --git a/public/silancar.jpg b/public/silancar.jpg
new file mode 100644
index 0000000..0955343
Binary files /dev/null and b/public/silancar.jpg differ
diff --git a/tailwind.config.tsx b/tailwind.config.tsx
new file mode 100644
index 0000000..589643d
--- /dev/null
+++ b/tailwind.config.tsx
@@ -0,0 +1,17 @@
+import type { Config } from "tailwindcss"
+
+const config: Config = {
+ content: ["./app/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}"],
+ theme: {
+ extend: {
+ fontFamily: {
+ sans: ["var(--font-inter)"],
+ mono: ["var(--font-mono)"],
+ heading: ["var(--font-roboto)"],
+ },
+ },
+ },
+ plugins: [],
+}
+
+export default config