diff --git a/Dockerfile b/Dockerfile index 5f41ba9..6743868 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ RUN bun x prisma generate # Generate API types RUN bun run gen:api -# Build the application frontend +# Build the application frontend using our custom build script RUN bun run build # Stage 2: Runtime diff --git a/package.json b/package.json index b835530..4ed17fa 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "test": "bun test __tests__/api", "test:ui": "bun test --ui __tests__/api", "test:e2e": "bun run build && playwright test", - "build": "bun build ./src/index.html --outdir=dist --sourcemap --target=browser --minify --define:process.env.NODE_ENV='\"production\"' --env='VITE_*' && cp -r public/* dist/ 2>/dev/null || true", + "build": "bun run scripts/build.ts", "start": "NODE_ENV=production bun src/index.ts", "seed": "bun prisma/seed.ts" }, diff --git a/scripts/build.ts b/scripts/build.ts new file mode 100644 index 0000000..e80eade --- /dev/null +++ b/scripts/build.ts @@ -0,0 +1,58 @@ +#!/usr/bin/env bun + +/** + * Build script for production + * 1. Build CSS with PostCSS/Tailwind + * 2. Bundle JS with Bun (without CSS) + * 3. Replace CSS reference in HTML + */ + +import { $ } from "bun"; +import fs from "node:fs"; +import postcss from "postcss"; +import tailwindcss from "@tailwindcss/postcss"; +import autoprefixer from "autoprefixer"; + +console.log("🔨 Starting production build..."); + +// Ensure dist directory exists +if (!fs.existsSync("./dist")) { + fs.mkdirSync("./dist", { recursive: true }); +} + +// Step 1: Build CSS with PostCSS +console.log("🎨 Building CSS..."); +const cssInput = fs.readFileSync("./src/index.css", "utf-8"); +const cssResult = await postcss([tailwindcss(), autoprefixer()]).process( + cssInput, + { + from: "./src/index.css", + to: "./dist/index.css", + }, +); + +fs.writeFileSync("./dist/index.css", cssResult.css); +console.log("✅ CSS built successfully!"); + +// Step 2: Build JS with Bun (build HTML too, we'll fix CSS link later) +console.log("📦 Bundling JavaScript..."); +await $`bun build ./src/index.html --outdir=dist --sourcemap --target=browser --minify --define:process.env.NODE_ENV='"production"' --env='VITE_*'`; + +// Step 3: Copy public assets +console.log("📁 Copying public assets..."); +if (fs.existsSync("./public")) { + await $`cp -r public/* dist/ 2>/dev/null || true`; +} + +// Step 4: Ensure HTML references the correct CSS +// Bun build might have renamed the CSS, we want to use our own index.css +console.log("🔧 Fixing HTML CSS reference..."); +const htmlPath = "./dist/index.html"; +if (fs.existsSync(htmlPath)) { + let html = fs.readFileSync(htmlPath, "utf-8"); + // Replace any bundled CSS reference with our index.css + html = html.replace(/href="[^"]*\.css"/g, 'href="/index.css"'); + fs.writeFileSync(htmlPath, html); +} + +console.log("✅ Build completed successfully!"); diff --git a/src/components/bumdes-page.tsx b/src/components/bumdes-page.tsx index 5a34605..605a8e8 100644 --- a/src/components/bumdes-page.tsx +++ b/src/components/bumdes-page.tsx @@ -149,16 +149,40 @@ const BumdesPage = () => { return (
-
+
{/* Row 1: Top 4 Metrics Cards */} -
+
{kpiData.map((kpi, index) => (
@@ -197,7 +221,12 @@ const BumdesPage = () => { {/* Row 2: Sales Update Header */}
{
{/* Row 3: Main Content Grid */} -
+
{/* Left Column (30%) */}
{/* Produk Unggulan Section */}

{

{stat.subtitle && (

{ {product.umkmOwner}

-

+

{product.sales}

{

{ 200 - ? "bg-green-100 text-green-800" - : "bg-red-100 text-red-800" - }`} + className="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium" + style={{ + backgroundColor: parseInt(product.stok) > 200 ? "#DCFCE7" : "#FEE2E2", + color: parseInt(product.stok) > 200 ? "#166534" : "#991B1B", + }} > {product.stok} diff --git a/src/components/demografi-pekerjaan.tsx b/src/components/demografi-pekerjaan.tsx index ea71b2e..81776ef 100644 --- a/src/components/demografi-pekerjaan.tsx +++ b/src/components/demografi-pekerjaan.tsx @@ -140,16 +140,40 @@ const DemografiPekerjaan = () => { return (
-
+
{/* Row 1: 4 Statistic Cards */} -
+
{kpiData.map((kpi) => (
@@ -186,11 +210,24 @@ const DemografiPekerjaan = () => {
{/* Row 2: 2 Chart Cards */} -
+
{/* Age Distribution Bar Chart */}

{ {/* Job Distribution Bar Chart */}

{

{/* Row 3: 3 Insight Cards */} -
+
{/* Religion Distribution Pie Chart */}

{ {/* Population per Banjar Table */}

{ {/* Population Dynamics Stats */}

{ > Statistik Dinamika Penduduk

-
+
{dynamicStats.map((stat, index) => (
diff --git a/src/components/jenna-analytic.tsx b/src/components/jenna-analytic.tsx index 578d2a5..4fc8032 100644 --- a/src/components/jenna-analytic.tsx +++ b/src/components/jenna-analytic.tsx @@ -98,16 +98,40 @@ const JennaAnalytic = () => { return (
-
+
{/* Row 1: 4 Statistic Cards */} -
+
{kpiData.map((kpi) => (
@@ -146,7 +170,13 @@ const JennaAnalytic = () => { {/* Row 2: Full Width Weekly Bar Chart */}

{

{/* Row 3: Two Insight Cards */} -
+
{/* Left: Frequently Asked Topics */}

{ > {item.topic} - + {item.count}x

@@ -231,7 +273,12 @@ const JennaAnalytic = () => { {/* Right: Busy Hour Distribution */}

{ return (
-
+
{/* Row 1: 4 Summary Metrics Cards */} -
+
{kpiData.map((kpi) => (
@@ -155,7 +179,7 @@ const KeuanganAnggaran = () => { {kpi.subtitle}

{kpi.delta && ( -

+

{kpi.delta}

)} @@ -176,7 +200,13 @@ const KeuanganAnggaran = () => { {/* Row 2: Line Chart Section */}

{ {/* Row 3: Analytics Section */} -
+
{/* Left: Horizontal Bar Chart */}

{ {/* Right: Assistance Funds List */}

{ {/* Row 4: Report Section */}

{ > Laporan APBDes

-
+
{/* Left: Pendapatan */}

{ const { colorScheme } = useMantineColorScheme(); @@ -76,10 +77,22 @@ const KinerjaDivisi = () => { return (
{/* Top Row - 4 Activity Cards */} -
+
{activities.map((activity, index) => (
{ style={{ backgroundColor: dark ? "#141D34" : "white", border: `1px solid ${dark ? "#141D34" : "white"}`, + borderRadius: "12px", + boxShadow: "0 1px 3px rgba(0,0,0,0.1)", + padding: "1.25rem", }} > {/* Dark blue title bar */} @@ -125,13 +141,24 @@ const KinerjaDivisi = () => {
{/* Second Row - Charts */} -
+
{/* Left Card - Jumlah Dokumen (Bar Chart) */}

{ style={{ backgroundColor: dark ? "#141D34" : "white", border: `1px solid ${dark ? "#141D34" : "white"}`, + borderRadius: "12px", + boxShadow: "0 1px 3px rgba(0,0,0,0.1)", + padding: "1.25rem", }} >

{

{/* Bottom Row */} -
+
{/* Left Card - Diskusi */}

{ className="w-8 h-8 rounded-full flex items-center justify-center" style={{ backgroundColor: "#DBEAFE" }} > - - - + stroke={2} + />

@@ -331,6 +362,9 @@ const KinerjaDivisi = () => { style={{ backgroundColor: dark ? "#141D34" : "white", border: `1px solid ${dark ? "#141D34" : "white"}`, + borderRadius: "12px", + boxShadow: "0 1px 3px rgba(0,0,0,0.1)", + padding: "1.25rem", }} >

{ type: "KTP Elektronik", date: "10 Mar 2025", status: "Selesai", - statusColor: "green", - statusText: "bg-green-100 text-green-800", + statusBg: "bg-darmasaba-success-100", + statusText: "text-darmasaba-success-800", }, { id: 2, @@ -87,8 +87,8 @@ const PengaduanLayananPublik = () => { type: "Surat Domisili", date: "10 Mar 2025", status: "Diproses", - statusColor: "yellow", - statusText: "bg-yellow-100 text-yellow-800", + statusBg: "bg-darmasaba-warning-100", + statusText: "text-darmasaba-warning-800", }, { id: 3, @@ -96,8 +96,8 @@ const PengaduanLayananPublik = () => { type: "Kartu Keluarga", date: "9 Mar 2025", status: "Baru", - statusColor: "blue", - statusText: "bg-blue-100 text-blue-800", + statusBg: "bg-darmasaba-blue-100", + statusText: "text-darmasaba-blue-800", }, { id: 4, @@ -105,8 +105,8 @@ const PengaduanLayananPublik = () => { type: "Surat Usaha", date: "9 Mar 2025", status: "Selesai", - statusColor: "green", - statusText: "bg-green-100 text-green-800", + statusBg: "bg-darmasaba-success-100", + statusText: "text-darmasaba-success-800", }, { id: 5, @@ -114,8 +114,8 @@ const PengaduanLayananPublik = () => { type: "SKCK", date: "8 Mar 2025", status: "Diproses", - statusColor: "yellow", - statusText: "bg-yellow-100 text-yellow-800", + statusBg: "bg-darmasaba-warning-100", + statusText: "text-darmasaba-warning-800", }, ]; @@ -165,16 +165,40 @@ const PengaduanLayananPublik = () => { return (
-
+
{/* Row 1: 4 Statistic Cards */} -
+
{statsData.map((stat, index) => (
@@ -213,7 +237,13 @@ const PengaduanLayananPublik = () => { {/* Row 2: Full Width Line Chart */}

{

{/* Row 3: 3 Column Grid */} -
+
{/* Left: Most Requested Documents (Horizontal Bar Chart) */}

{ {/* Middle: Recent Applications */}

{

{app.status} @@ -368,7 +415,12 @@ const PengaduanLayananPublik = () => { {/* Right: Innovation Ideas */}

{ }, ]; - const cardStyle = { - backgroundColor: dark ? "#1E293B" : "white", - border: `1px solid ${dark ? "#1E293B" : "white"}`, - }; - - const textStyle = { - color: dark ? "white" : "#1F2937", - }; - - const subtitleStyle = { - color: dark ? "#9CA3AF" : "#6B7280", - }; - return (
-
+
{/* Row 1: Top 4 Metrics Cards */}
{healthStats.map((stat, index) => (

{stat.title}

{stat.value}

{stat.subtitle}

@@ -182,12 +175,14 @@ const SosialPage = () => { {/* Row 2: Statistik Kesehatan */}

Statistik Kesehatan

@@ -196,34 +191,34 @@ const SosialPage = () => {
{item.label} {item.value}%
@@ -232,15 +227,17 @@ const SosialPage = () => {
{/* Row 3: Jadwal Posyandu & Pendidikan */} -
+
{/* Jadwal Posyandu */}

Jadwal Posyandu

@@ -248,32 +245,29 @@ const SosialPage = () => { {posyanduSchedule.map((item, index) => (

{item.nama}

{item.tanggal}

{item.jam} @@ -285,12 +279,14 @@ const SosialPage = () => { {/* Pendidikan Section */}

Pendidikan

@@ -298,17 +294,17 @@ const SosialPage = () => { {educationStats.map((item, index) => (
- + {item.level} {item.value} @@ -318,8 +314,9 @@ const SosialPage = () => { {/* Info Sekolah */}

Info Sekolah

@@ -327,17 +324,17 @@ const SosialPage = () => { {schoolInfo.map((item, index) => (
- + {item.label} {item.value} @@ -348,22 +345,23 @@ const SosialPage = () => {
{/* Row 4: Beasiswa Desa & Kalender Event Budaya */} -
+
{/* Beasiswa Desa */}

Beasiswa Desa

@@ -372,39 +370,39 @@ const SosialPage = () => { {/* Two centered metrics */}

{scholarshipData.penerima}

Penerima Beasiswa

{scholarshipData.dana}

Dana Tersalurkan

@@ -413,8 +411,9 @@ const SosialPage = () => { {/* Footer text */}

Tahun Ajaran {scholarshipData.tahunAjaran}

@@ -422,12 +421,14 @@ const SosialPage = () => { {/* Kalender Event Budaya */}

Kalender Event Budaya

@@ -435,36 +436,34 @@ const SosialPage = () => { {culturalEvents.map((event, index) => (

{event.nama}

{event.tanggal}

Location: {event.lokasi}

diff --git a/src/index.css b/src/index.css index f1d8c73..3dc36b0 100644 --- a/src/index.css +++ b/src/index.css @@ -1 +1,100 @@ @import "tailwindcss"; + +/* Custom CSS variables for Tailwind */ +:root { + /* Darmasaba Navy Colors */ + --darmasaba-navy-50: #E1E4F2; + --darmasaba-navy-100: #B9C2DD; + --darmasaba-navy-200: #91A0C9; + --darmasaba-navy-300: #697EBA; + --darmasaba-navy-400: #4C6CAE; + --darmasaba-navy-500: #3B5B97; + --darmasaba-navy-600: #2C497F; + --darmasaba-navy-700: #1E3766; + --darmasaba-navy-800: #12264D; + --darmasaba-navy-900: #071833; + --darmasaba-navy: #1E3A5F; + + /* Darmasaba Blue Colors */ + --darmasaba-blue-50: #E3F0FF; + --darmasaba-blue-100: #B6D9FF; + --darmasaba-blue-200: #89C2FF; + --darmasaba-blue-300: #5CA9FF; + --darmasaba-blue-400: #3B8FFF; + --darmasaba-blue-500: #237AE0; + --darmasaba-blue-600: #1C6BBF; + --darmasaba-blue-700: #155BA0; + --darmasaba-blue-800: #0E4980; + --darmasaba-blue-900: #073260; + --darmasaba-blue: #3B82F6; + + /* Darmasaba Success Colors */ + --darmasaba-success-50: #E3F9E7; + --darmasaba-success-100: #BFEEC7; + --darmasaba-success-200: #9BD8A7; + --darmasaba-success-300: #77C387; + --darmasaba-success-400: #5DB572; + --darmasaba-success-500: #499A5D; + --darmasaba-success-600: #3C7F4A; + --darmasaba-success-700: #2F6438; + --darmasaba-success-800: #234926; + --darmasaba-success-900: #17301B; + --darmasaba-success: #22C55E; + + /* Darmasaba Warning Colors */ + --darmasaba-warning-50: #FFF8E1; + --darmasaba-warning-100: #FEE7B3; + --darmasaba-warning-200: #FDD785; + --darmasaba-warning-300: #FDC757; + --darmasaba-warning-400: #FBBF3B; + --darmasaba-warning-500: #E1AC23; + --darmasaba-warning-600: #C2981D; + --darmasaba-warning-700: #A38418; + --darmasaba-warning-800: #856F12; + --darmasaba-warning-900: #675A0D; + --darmasaba-warning: #FACC15; + + /* Darmasaba Danger Colors */ + --darmasaba-danger-50: #FFE3E3; + --darmasaba-danger-100: #FFBABA; + --darmasaba-danger-200: #FF9191; + --darmasaba-danger-300: #FF6868; + --darmasaba-danger-400: #FA4B4B; + --darmasaba-danger-500: #E03333; + --darmasaba-danger-600: #C22A2A; + --darmasaba-danger-700: #A32020; + --darmasaba-danger-800: #851616; + --darmasaba-danger-900: #670C0C; + --darmasaba-danger: #EF4444; + + /* Darmasaba Background */ + --darmasaba-background: #F5F8FB; + + /* Standard colors for dark mode */ + --slate-900: #0F172A; + --slate-800: #1E293B; + --slate-700: #334155; + --slate-600: #475569; + --gray-50: #F9FAFB; + --gray-100: #F3F4F6; + --gray-200: #E5E7EB; + --gray-600: #4B5563; + --gray-700: #1F2937; + --gray-400: #9CA3AF; + --gray-500: #6B7280; + --gray-800: #1F2937; + --blue-50: #EFF6FF; + --blue-100: #DBEAFE; + --blue-900: #1E3A5F; + --red-500: #EF4444; + --green-500: #22C55E; +} + +/* Dark mode support */ +[data-mantine-color-scheme="dark"] { + color-scheme: dark; +} + +[data-mantine-color-scheme="light"] { + color-scheme: light; +} diff --git a/tailwind.config.js b/tailwind.config.js index 3623a49..4a9d1b2 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,11 +1,15 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], + content: [ + "./src/index.html", + "./public/**/*.html", + "./src/**/*.{js,ts,jsx,tsx}", + ], theme: { extend: { colors: { "darmasaba-navy": { - DEFAULT: "#1E3A5F", // Primary navy color + DEFAULT: "#1E3A5F", 50: "#E1E4F2", 100: "#B9C2DD", 200: "#91A0C9", @@ -18,7 +22,7 @@ module.exports = { 900: "#071833", }, "darmasaba-blue": { - DEFAULT: "#3B82F6", // Primary blue color + DEFAULT: "#3B82F6", 50: "#E3F0FF", 100: "#B6D9FF", 200: "#89C2FF",