Compare commits

...

34 Commits

Author SHA1 Message Date
270875a95c upd: api version 2026-03-16 10:39:23 +08:00
09bd75d5e5 Merge pull request 'upd: api noc' (#25) from amalia/12-mar-26 into join
Reviewed-on: #25
2026-03-12 16:11:03 +08:00
339b1e25cc upd: api noc 2026-03-12 16:08:42 +08:00
d9c6f486a9 Merge pull request 'upd: api noc' (#24) from amalia/11-mar-26 into join
Reviewed-on: #24
2026-03-11 16:41:40 +08:00
1a20697f4c upd: api noc 2026-03-11 16:40:42 +08:00
3927a6b756 Merge pull request 'amalia/09-mar-26' (#23) from amalia/09-mar-26 into join
Reviewed-on: #23
2026-03-10 16:45:28 +08:00
079395654d update version dan data seeder 2026-03-09 11:34:13 +08:00
93e7f33f7c update version 2026-03-09 10:36:31 +08:00
aba7a4c8fc update workflow 2026-03-09 10:35:37 +08:00
f55b171987 Merge pull request 'amalia/06-mar-26' (#22) from amalia/06-mar-26 into join
Reviewed-on: #22
2026-03-06 16:37:01 +08:00
bipproduction
d401ebb208 fix: add custom Pages Router _error page for 404/500 prerendering
Override default Next.js _error page that imports <Html> from
next/document, which fails during Docker build prerendering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:35:48 +08:00
bipproduction
5230a31942 fix: add custom 404 and global error pages for Docker build
Create not-found.tsx and global-error.tsx to prevent Next.js from
falling back to legacy Pages Router _error pages during static
generation, which fail with useContext null in Docker.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:28:37 +08:00
bipproduction
5e7eb20c26 fix: force dynamic rendering to skip static prerendering
Add `export const dynamic = 'force-dynamic'` to root layout so all
pages are rendered at request time instead of build time. Fixes
useContext null error during Docker build prerendering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:19:36 +08:00
bipproduction
b7063d3658 fix: update Dockerfile bun version and remove --ignore-scripts
Upgrade bun 1.3.1 to 1.3.6 and remove --ignore-scripts flag to fix
prerender error during Docker build (null useContext dispatcher).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:12:24 +08:00
4abaa97cc0 upd bun 2026-03-06 10:59:14 +08:00
069174cba1 update env example dummy 2026-03-06 10:42:45 +08:00
a04e0186a2 update env example 2026-03-06 10:33:22 +08:00
2af22b4bc7 build 2026-03-06 10:21:40 +08:00
0f90302f11 Merge pull request 'upd: unregistered token log logot' (#20) from amalia/04-mar-26 into join
Reviewed-on: #20
2026-03-04 16:38:22 +08:00
1b1a6b1b51 upd: unregistered token log logot 2026-03-04 16:33:47 +08:00
3a116ce212 Merge pull request 'upd: token, login dan version' (#19) from amalia/03-mar-26 into join
Reviewed-on: #19
2026-03-03 16:50:26 +08:00
60e88f5c9b upd: token, login dan version 2026-03-03 16:41:39 +08:00
2cd931dcfd Merge pull request 'upd: next config:' (#18) from amalia/25-feb-26 into join
Reviewed-on: #18
2026-02-25 12:44:21 +08:00
64fbc486f0 upd: next config: 2026-02-25 12:43:06 +08:00
02c9decbd8 Merge pull request 'upd: seeder dan version' (#17) from amalia/24-feb-26 into join
Reviewed-on: #17
2026-02-24 15:43:34 +08:00
c13340d254 upd: seeder dan version 2026-02-24 15:42:40 +08:00
757595e6af Merge pull request 'upd: seeder' (#16) from amalia/24-feb-26 into join
Reviewed-on: #16
2026-02-24 15:33:21 +08:00
5b3b39c19d upd: seeder 2026-02-24 15:32:36 +08:00
6b14427a2e Merge pull request 'upd: fix error dan seeder setting' (#15) from amalia/24-feb-26 into join
Reviewed-on: #15
2026-02-24 15:08:54 +08:00
4d73e4c875 upd: fix error dan seeder setting 2026-02-24 15:07:27 +08:00
519adeb376 Merge pull request 'upd: api mobile revisi' (#14) from amalia/23-feb-26 into join
Reviewed-on: #14
2026-02-24 13:38:27 +08:00
0ed01d287f upd: api mobile revisi 2026-02-23 14:37:26 +08:00
e62909b070 Merge pull request 'amalia/05-feb-26' (#12) from amalia/05-feb-26 into join
Reviewed-on: http://wibugit.wibudev.com/wibu/sistem-desa-mandiri/pulls/12
2026-02-05 17:27:41 +08:00
191e567e12 Merge pull request 'upd: update seeder data desa dummy' (#11) from amalia/05-feb-26 into join
Reviewed-on: http://wibugit.wibudev.com/wibu/sistem-desa-mandiri/pulls/11
2026-02-05 14:05:57 +08:00
34 changed files with 2231 additions and 73 deletions

52
.env.example Normal file
View File

@@ -0,0 +1,52 @@
# ===========================================
# SISTEM DESA MANDIRI - ENVIRONMENT VARIABLES
# ===========================================
# Copy this file to .env and fill in the appropriate values
# ===========================================
# DATABASE CONFIGURATION
# ===========================================
# PostgreSQL, MySQL, or SQLite connection string
# Example (PostgreSQL): postgresql://user:password@localhost:5432/dbname
# Example (MySQL): mysql://user:password@localhost:3306/dbname
# Example (SQLite): file:./dev.db
DATABASE_URL="your-database-url-here"
# ===========================================
# FIREBASE ADMIN SDK (For FCM Push Notifications)
# ===========================================
# Google Cloud project ID
GOOGLE_PROJECT_ID="your-google-project-id"
# Google service account client email
GOOGLE_CLIENT_EMAIL="your-service-account-email@your-project.iam.gserviceaccount.com"
# Google service account private key (include the full key with newlines)
GOOGLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nYOUR_PRIVATE_KEY_HERE\n-----END PRIVATE KEY-----"
# Google service account private key ID (optional but recommended)
GOOGLE_PRIVATE_KEY_ID="your-private-key-id"
# ===========================================
# WEB PUSH NOTIFICATIONS (VAPID Keys)
# ===========================================
# VAPID public key (exposed to client-side, must start with NEXT_PUBLIC_)
NEXT_PUBLIC_VAPID_PUBLIC_KEY="BJlglqrIZCbPCZyUs8UIzEP1Wi18hzvGaC3-KPLkQuoCV_EOKdyGJNbu7fs5jYaO571ipVAMko8YiwIMa1VjQEg"
# VAPID private key (keep secret, server-side only)
VAPID_PRIVATE_KEY="UHDY8M3-0beVIA2kt2zL3ZeMStJ0j6zVkVd2Cfqpgrc"
# ===========================================
# FILE STORAGE / WEBSOCKET API
# ===========================================
# API key for file operations (upload, delete, copy, view directory)
WS_APIKEY="your-websocket-api-key"
# ===========================================
# APPLICATION SETTINGS
# ===========================================
# Next.js node environment (development, production, test)
NODE_ENV="development"
# Application URL (optional, for reference)
NEXT_PUBLIC_APP_URL="http://localhost:3000"

76
.github/workflows/publish.yml vendored Normal file
View File

@@ -0,0 +1,76 @@
name: Publish Docker to GHCR
on:
workflow_dispatch:
inputs:
stack_env:
description: "stack env"
required: true
type: choice
default: "dev"
options:
- dev
- prod
- stg
tag:
description: "Image tag (e.g. 1.0.0)"
required: true
default: "1.0.0"
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
publish:
name: Build & Push to GHCR ${{ github.repository }}:${{ github.event.inputs.stack_env }}-${{ github.event.inputs.tag }}
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Free disk space
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf /usr/local/lib/android
sudo rm -rf /opt/ghc
sudo rm -rf /opt/hostedtoolcache/CodeQL
sudo docker image prune --all --force
df -h
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Generate image metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=${{ github.event.inputs.stack_env }}-${{ github.event.inputs.tag }}
type=raw,value=${{ github.event.inputs.stack_env }}-latest
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
platforms: linux/amd64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
no-cache: true

37
.github/workflows/re-pull.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Re-Pull Docker
on:
workflow_dispatch:
inputs:
stack_name:
description: "stack name"
required: true
type: string
stack_env:
description: "stack env"
required: true
type: choice
default: "dev"
options:
- dev
- stg
- prod
jobs:
publish:
name: Re-Pull Docker ${{ github.event.inputs.stack_name }}
runs-on: ubuntu-latest
environment: ${{ vars.PORTAINER_ENV || 'portainer' }}
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Deploy ke Portainer
run: bash ./.github/workflows/script/re-pull.sh
env:
PORTAINER_USERNAME: ${{ secrets.PORTAINER_USERNAME }}
PORTAINER_PASSWORD: ${{ secrets.PORTAINER_PASSWORD }}
PORTAINER_URL: ${{ secrets.PORTAINER_URL }}
STACK_NAME: ${{ github.event.inputs.stack_name }}-${{ github.event.inputs.stack_env }}

93
.github/workflows/script/re-pull.sh vendored Normal file
View File

@@ -0,0 +1,93 @@
#!/bin/bash
: "${PORTAINER_URL:?PORTAINER_URL tidak di-set}"
: "${PORTAINER_USERNAME:?PORTAINER_USERNAME tidak di-set}"
: "${PORTAINER_PASSWORD:?PORTAINER_PASSWORD tidak di-set}"
: "${STACK_NAME:?STACK_NAME tidak di-set}"
echo "🔐 Autentikasi ke Portainer..."
TOKEN=$(curl -s -X POST https://${PORTAINER_URL}/api/auth \
-H "Content-Type: application/json" \
-d "{\"username\": \"${PORTAINER_USERNAME}\", \"password\": \"${PORTAINER_PASSWORD}\"}" \
| jq -r .jwt)
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
echo "❌ Autentikasi gagal! Cek PORTAINER_URL, USERNAME, dan PASSWORD."
exit 1
fi
echo "🔍 Mencari stack: $STACK_NAME..."
STACK=$(curl -s -X GET https://${PORTAINER_URL}/api/stacks \
-H "Authorization: Bearer ${TOKEN}" \
| jq ".[] | select(.Name == \"$STACK_NAME\")")
if [ -z "$STACK" ]; then
echo "❌ Stack '$STACK_NAME' tidak ditemukan di Portainer!"
echo " Pastikan nama stack sudah benar."
exit 1
fi
STACK_ID=$(echo "$STACK" | jq -r .Id)
ENDPOINT_ID=$(echo "$STACK" | jq -r .EndpointId)
ENV=$(echo "$STACK" | jq '.Env // []')
echo "📄 Mengambil compose file..."
STACK_FILE=$(curl -s -X GET "https://${PORTAINER_URL}/api/stacks/${STACK_ID}/file" \
-H "Authorization: Bearer ${TOKEN}" \
| jq -r .StackFileContent)
PAYLOAD=$(jq -n \
--arg content "$STACK_FILE" \
--argjson env "$ENV" \
'{stackFileContent: $content, env: $env, pullImage: true}')
echo "🚀 Redeploying $STACK_NAME (pull latest image)..."
HTTP_STATUS=$(curl -s -o /tmp/portainer_response.json -w "%{http_code}" \
-X PUT "https://${PORTAINER_URL}/api/stacks/${STACK_ID}?endpointId=${ENDPOINT_ID}" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d "$PAYLOAD")
if [ "$HTTP_STATUS" != "200" ]; then
echo "❌ Redeploy gagal! HTTP Status: $HTTP_STATUS"
cat /tmp/portainer_response.json | jq .
exit 1
fi
echo "⏳ Menunggu container running..."
MAX_RETRY=15
COUNT=0
while [ $COUNT -lt $MAX_RETRY ]; do
sleep 5
COUNT=$((COUNT + 1))
CONTAINERS=$(curl -s -X GET \
"https://${PORTAINER_URL}/api/endpoints/${ENDPOINT_ID}/docker/containers/json?all=true&filters=%7B%22label%22%3A%5B%22com.docker.compose.project%3D${STACK_NAME}%22%5D%7D" \
-H "Authorization: Bearer ${TOKEN}")
TOTAL=$(echo "$CONTAINERS" | jq 'length')
RUNNING=$(echo "$CONTAINERS" | jq '[.[] | select(.State == "running")] | length')
FAILED=$(echo "$CONTAINERS" | jq '[.[] | select(.State == "exited" and (.Status | test("Exited \\(0\\)") | not))] | length')
echo "🔄 [${COUNT}/${MAX_RETRY}] Running: ${RUNNING} | Failed: ${FAILED} | Total: ${TOTAL}"
echo "$CONTAINERS" | jq -r '.[] | " → \(.Names[0]) | \(.State) | \(.Status)"'
if [ "$FAILED" -gt "0" ]; then
echo ""
echo "❌ Ada container yang crash!"
echo "$CONTAINERS" | jq -r '.[] | select(.State == "exited" and (.Status | test("Exited \\(0\\)") | not)) | " → \(.Names[0]) | \(.Status)"'
exit 1
fi
if [ "$RUNNING" -gt "0" ]; then
echo ""
echo "✅ Stack $STACK_NAME berhasil di-redeploy dan running!"
exit 0
fi
done
echo ""
echo "❌ Timeout! Stack tidak kunjung running setelah $((MAX_RETRY * 5)) detik."
exit 1

83
Dockerfile Normal file
View File

@@ -0,0 +1,83 @@
# ==============================
# Stage 1: Builder (Bun)
# ==============================
FROM oven/bun:1.3.6-debian AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
libc6 \
git \
openssl \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
COPY package.json bun.lockb* ./
COPY prisma ./prisma
ENV ONNXRUNTIME_NODE_INSTALL_CUDA=0
ENV SHARP_IGNORE_GLOBAL_LIBVIPS=1
ENV NEXT_TELEMETRY_DISABLED=1
RUN bun install
COPY . .
# Gunakan .env jika ada, fallback ke .env.example.
# Untuk build dengan .env custom, hapus .env dari .dockerignore
# atau berikan via: docker build --secret id=env,src=.env (BuildKit)
RUN if [ -f .env ]; then \
echo "INFO: Menggunakan .env"; \
elif [ -f .env.example ]; then \
cp .env.example .env; \
echo "WARNING: .env tidak ditemukan, menggunakan .env.example (isi dengan nilai yang benar)"; \
else \
echo "WARNING: Tidak ada .env atau .env.example"; \
fi
# Generate prisma client
RUN ./node_modules/.bin/prisma generate
# Build Next.js
RUN bun run build
# ==============================
# Stage 2: Runner (Bun)
# ==============================
FROM oven/bun:1.3.6-debian AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN apt-get update && apt-get install -y --no-install-recommends \
openssl \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
RUN groupadd --system --gid 1001 nodejs \
&& useradd --system --uid 1001 --gid nodejs nextjs
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/tsconfig.json ./tsconfig.json
COPY --from=builder /app/prisma ./prisma
COPY --from=builder /app/src ./src
# Env vars runtime dikelola oleh Portainer (stack env / container env).
# Tidak perlu copy .env ke runner — image tetap bersih tanpa secrets.
RUN chown -R nextjs:nodejs /app
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["bun", "run", "start"]

View File

@@ -3,6 +3,12 @@ const nextConfig = {
devIndicators: {
buildActivityPosition: 'bottom-right',
},
typescript: {
ignoreBuildErrors: true, // ini yang fix TypeScript error
},
eslint: {
ignoreDuringBuilds: true,
},
};
export default nextConfig;

View File

@@ -0,0 +1,879 @@
-- CreateTable
CREATE TABLE "AdminRole" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "AdminRole_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Admin" (
"id" TEXT NOT NULL,
"idAdminRole" TEXT NOT NULL,
"name" TEXT NOT NULL,
"phone" TEXT NOT NULL,
"email" TEXT,
"gender" TEXT NOT NULL DEFAULT 'M',
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Admin_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "UserRole" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"desc" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "UserRole_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Village" (
"id" TEXT NOT NULL,
"idTheme" TEXT,
"name" TEXT NOT NULL,
"desc" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Village_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Group" (
"id" TEXT NOT NULL,
"idVillage" TEXT NOT NULL,
"name" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Group_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Position" (
"id" TEXT NOT NULL,
"idGroup" TEXT NOT NULL,
"name" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Position_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"idUserRole" TEXT NOT NULL,
"idVillage" TEXT NOT NULL,
"idGroup" TEXT NOT NULL,
"idPosition" TEXT,
"nik" TEXT NOT NULL,
"name" TEXT NOT NULL,
"phone" TEXT NOT NULL,
"email" TEXT,
"gender" TEXT NOT NULL DEFAULT 'M',
"img" TEXT,
"isFirstLogin" BOOLEAN NOT NULL DEFAULT true,
"isWithoutOTP" BOOLEAN NOT NULL DEFAULT false,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TokenDeviceUser" (
"id" TEXT NOT NULL,
"idUser" TEXT NOT NULL,
"token" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "TokenDeviceUser_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "UserLog" (
"id" TEXT NOT NULL,
"idUser" TEXT NOT NULL,
"action" TEXT NOT NULL,
"desc" TEXT NOT NULL,
"idContent" TEXT NOT NULL,
"tbContent" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "UserLog_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Announcement" (
"id" TEXT NOT NULL,
"idVillage" TEXT NOT NULL,
"title" TEXT NOT NULL,
"desc" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdBy" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Announcement_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AnnouncementMember" (
"id" TEXT NOT NULL,
"idAnnouncement" TEXT NOT NULL,
"idGroup" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "AnnouncementMember_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AnnouncementFile" (
"id" TEXT NOT NULL,
"idAnnouncement" TEXT NOT NULL,
"name" TEXT NOT NULL,
"extension" TEXT NOT NULL,
"idStorage" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "AnnouncementFile_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Project" (
"id" TEXT NOT NULL,
"idVillage" TEXT NOT NULL,
"idGroup" TEXT NOT NULL,
"title" TEXT NOT NULL,
"status" INTEGER NOT NULL DEFAULT 0,
"desc" TEXT,
"reason" TEXT,
"report" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdBy" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Project_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProjectMember" (
"id" TEXT NOT NULL,
"idProject" TEXT NOT NULL,
"idUser" TEXT NOT NULL,
"isLeader" BOOLEAN NOT NULL DEFAULT false,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ProjectMember_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProjectFile" (
"id" TEXT NOT NULL,
"idProject" TEXT NOT NULL,
"name" TEXT NOT NULL,
"extension" TEXT NOT NULL,
"idStorage" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ProjectFile_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProjectLink" (
"id" TEXT NOT NULL,
"idProject" TEXT NOT NULL,
"link" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ProjectLink_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProjectTask" (
"id" TEXT NOT NULL,
"idProject" TEXT NOT NULL,
"title" TEXT NOT NULL,
"desc" TEXT,
"status" INTEGER NOT NULL DEFAULT 0,
"notifikasi" BOOLEAN NOT NULL DEFAULT false,
"dateStart" DATE NOT NULL,
"dateEnd" DATE NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ProjectTask_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProjectTaskDetail" (
"id" TEXT NOT NULL,
"idTask" TEXT NOT NULL,
"date" DATE NOT NULL,
"timeStart" TIME,
"timeEnd" TIME,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ProjectTaskDetail_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Division" (
"id" TEXT NOT NULL,
"idVillage" TEXT NOT NULL,
"idGroup" TEXT NOT NULL,
"name" TEXT NOT NULL,
"desc" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdBy" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Division_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionMember" (
"id" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"idUser" TEXT NOT NULL,
"isAdmin" BOOLEAN NOT NULL DEFAULT false,
"isLeader" BOOLEAN NOT NULL DEFAULT false,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionMember_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionProject" (
"id" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"title" TEXT NOT NULL,
"desc" TEXT,
"reason" TEXT,
"report" TEXT,
"status" INTEGER NOT NULL DEFAULT 0,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionProject_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionProjectLink" (
"id" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"idProject" TEXT NOT NULL,
"link" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionProjectLink_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionProjectTask" (
"id" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"idProject" TEXT NOT NULL,
"title" TEXT NOT NULL,
"desc" TEXT,
"status" INTEGER NOT NULL DEFAULT 0,
"notifikasi" BOOLEAN NOT NULL DEFAULT false,
"dateStart" DATE NOT NULL,
"dateEnd" DATE NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionProjectTask_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionProjectTaskDetail" (
"id" TEXT NOT NULL,
"idTask" TEXT NOT NULL,
"date" DATE NOT NULL,
"timeStart" TIME,
"timeEnd" TIME,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionProjectTaskDetail_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionProjectMember" (
"id" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"idProject" TEXT NOT NULL,
"idUser" TEXT NOT NULL,
"isLeader" BOOLEAN NOT NULL DEFAULT false,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionProjectMember_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionProjectFile" (
"id" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"idProject" TEXT NOT NULL,
"idFile" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdBy" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionProjectFile_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionDisscussion" (
"id" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"title" TEXT,
"desc" TEXT NOT NULL,
"status" INTEGER NOT NULL DEFAULT 1,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdBy" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionDisscussion_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionDisscussionComment" (
"id" TEXT NOT NULL,
"idDisscussion" TEXT NOT NULL,
"comment" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdBy" TEXT NOT NULL,
"isEdited" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionDisscussionComment_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionDiscussionFile" (
"id" TEXT NOT NULL,
"idDiscussion" TEXT NOT NULL,
"name" TEXT NOT NULL,
"extension" TEXT NOT NULL,
"idStorage" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionDiscussionFile_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionDocumentFolderFile" (
"id" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"idStorage" TEXT,
"category" TEXT NOT NULL DEFAULT 'FOLDER',
"name" TEXT NOT NULL,
"extension" TEXT NOT NULL,
"path" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdBy" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionDocumentFolderFile_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionDocumentShare" (
"id" TEXT NOT NULL,
"idDocument" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionDocumentShare_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionCalendar" (
"id" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"title" TEXT NOT NULL,
"desc" TEXT NOT NULL,
"linkMeet" TEXT,
"dateStart" DATE NOT NULL,
"dateEnd" DATE,
"timeStart" TIME NOT NULL,
"timeEnd" TIME NOT NULL,
"repeatEventTyper" TEXT NOT NULL,
"repeatValue" INTEGER NOT NULL DEFAULT 1,
"reminderInterval" TEXT,
"status" INTEGER NOT NULL DEFAULT 0,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdBy" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "DivisionCalendar_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionCalendarReminder" (
"id" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"idCalendar" TEXT NOT NULL,
"dateStart" DATE NOT NULL,
"dateEnd" DATE,
"timeStart" TIME NOT NULL,
"timeEnd" TIME NOT NULL,
"status" INTEGER NOT NULL DEFAULT 0,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionCalendarReminder_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionCalendarMember" (
"id" TEXT NOT NULL,
"idCalendar" TEXT NOT NULL,
"idUser" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionCalendarMember_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ContainerImage" (
"id" TEXT NOT NULL,
"category" TEXT NOT NULL,
"idCategory" TEXT NOT NULL,
"extension" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ContainerImage_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ContainerFileDivision" (
"id" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"idStorage" TEXT,
"name" TEXT NOT NULL,
"extension" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ContainerFileDivision_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ColorTheme" (
"id" TEXT NOT NULL,
"idVillage" TEXT,
"name" TEXT NOT NULL,
"utama" TEXT NOT NULL,
"bgUtama" TEXT NOT NULL,
"bgIcon" TEXT NOT NULL,
"bgFiturHome" TEXT NOT NULL,
"bgFiturDivision" TEXT NOT NULL,
"bgTotalKegiatan" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ColorTheme_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "BannerImage" (
"id" TEXT NOT NULL,
"idVillage" TEXT,
"title" TEXT NOT NULL,
"extension" TEXT NOT NULL,
"image" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "BannerImage_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Notifications" (
"id" TEXT NOT NULL,
"idUserTo" TEXT NOT NULL,
"idUserFrom" TEXT NOT NULL,
"category" TEXT NOT NULL,
"idContent" TEXT NOT NULL,
"title" TEXT NOT NULL,
"desc" TEXT NOT NULL,
"isRead" BOOLEAN NOT NULL DEFAULT false,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Notifications_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Subscribe" (
"id" TEXT NOT NULL,
"idUser" TEXT NOT NULL,
"subscription" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3),
CONSTRAINT "Subscribe_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Discussion" (
"id" TEXT NOT NULL,
"idVillage" TEXT NOT NULL,
"idGroup" TEXT NOT NULL,
"title" TEXT,
"desc" TEXT NOT NULL,
"status" INTEGER NOT NULL DEFAULT 1,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdBy" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Discussion_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DiscussionMember" (
"id" TEXT NOT NULL,
"idDiscussion" TEXT NOT NULL,
"idUser" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DiscussionMember_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DiscussionComment" (
"id" TEXT NOT NULL,
"idDiscussion" TEXT NOT NULL,
"idUser" TEXT NOT NULL,
"comment" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"isEdited" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DiscussionComment_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DiscussionFile" (
"id" TEXT NOT NULL,
"idDiscussion" TEXT NOT NULL,
"name" TEXT NOT NULL,
"extension" TEXT NOT NULL,
"idStorage" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DiscussionFile_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Setting" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"value" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Setting_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Admin_phone_key" ON "Admin"("phone");
-- CreateIndex
CREATE UNIQUE INDEX "Admin_email_key" ON "Admin"("email");
-- CreateIndex
CREATE UNIQUE INDEX "User_nik_key" ON "User"("nik");
-- CreateIndex
CREATE UNIQUE INDEX "User_phone_key" ON "User"("phone");
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "Subscribe_idUser_key" ON "Subscribe"("idUser");
-- AddForeignKey
ALTER TABLE "Admin" ADD CONSTRAINT "Admin_idAdminRole_fkey" FOREIGN KEY ("idAdminRole") REFERENCES "AdminRole"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Group" ADD CONSTRAINT "Group_idVillage_fkey" FOREIGN KEY ("idVillage") REFERENCES "Village"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Position" ADD CONSTRAINT "Position_idGroup_fkey" FOREIGN KEY ("idGroup") REFERENCES "Group"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "User" ADD CONSTRAINT "User_idUserRole_fkey" FOREIGN KEY ("idUserRole") REFERENCES "UserRole"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "User" ADD CONSTRAINT "User_idVillage_fkey" FOREIGN KEY ("idVillage") REFERENCES "Village"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "User" ADD CONSTRAINT "User_idGroup_fkey" FOREIGN KEY ("idGroup") REFERENCES "Group"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "User" ADD CONSTRAINT "User_idPosition_fkey" FOREIGN KEY ("idPosition") REFERENCES "Position"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TokenDeviceUser" ADD CONSTRAINT "TokenDeviceUser_idUser_fkey" FOREIGN KEY ("idUser") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "UserLog" ADD CONSTRAINT "UserLog_idUser_fkey" FOREIGN KEY ("idUser") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Announcement" ADD CONSTRAINT "Announcement_idVillage_fkey" FOREIGN KEY ("idVillage") REFERENCES "Village"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Announcement" ADD CONSTRAINT "Announcement_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AnnouncementMember" ADD CONSTRAINT "AnnouncementMember_idAnnouncement_fkey" FOREIGN KEY ("idAnnouncement") REFERENCES "Announcement"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AnnouncementMember" ADD CONSTRAINT "AnnouncementMember_idGroup_fkey" FOREIGN KEY ("idGroup") REFERENCES "Group"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AnnouncementMember" ADD CONSTRAINT "AnnouncementMember_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AnnouncementFile" ADD CONSTRAINT "AnnouncementFile_idAnnouncement_fkey" FOREIGN KEY ("idAnnouncement") REFERENCES "Announcement"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Project" ADD CONSTRAINT "Project_idVillage_fkey" FOREIGN KEY ("idVillage") REFERENCES "Village"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Project" ADD CONSTRAINT "Project_idGroup_fkey" FOREIGN KEY ("idGroup") REFERENCES "Group"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Project" ADD CONSTRAINT "Project_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProjectMember" ADD CONSTRAINT "ProjectMember_idProject_fkey" FOREIGN KEY ("idProject") REFERENCES "Project"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProjectMember" ADD CONSTRAINT "ProjectMember_idUser_fkey" FOREIGN KEY ("idUser") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProjectFile" ADD CONSTRAINT "ProjectFile_idProject_fkey" FOREIGN KEY ("idProject") REFERENCES "Project"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProjectLink" ADD CONSTRAINT "ProjectLink_idProject_fkey" FOREIGN KEY ("idProject") REFERENCES "Project"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProjectTask" ADD CONSTRAINT "ProjectTask_idProject_fkey" FOREIGN KEY ("idProject") REFERENCES "Project"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProjectTaskDetail" ADD CONSTRAINT "ProjectTaskDetail_idTask_fkey" FOREIGN KEY ("idTask") REFERENCES "ProjectTask"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Division" ADD CONSTRAINT "Division_idVillage_fkey" FOREIGN KEY ("idVillage") REFERENCES "Village"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Division" ADD CONSTRAINT "Division_idGroup_fkey" FOREIGN KEY ("idGroup") REFERENCES "Group"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Division" ADD CONSTRAINT "Division_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionMember" ADD CONSTRAINT "DivisionMember_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionMember" ADD CONSTRAINT "DivisionMember_idUser_fkey" FOREIGN KEY ("idUser") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProject" ADD CONSTRAINT "DivisionProject_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProjectLink" ADD CONSTRAINT "DivisionProjectLink_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProjectLink" ADD CONSTRAINT "DivisionProjectLink_idProject_fkey" FOREIGN KEY ("idProject") REFERENCES "DivisionProject"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProjectTask" ADD CONSTRAINT "DivisionProjectTask_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProjectTask" ADD CONSTRAINT "DivisionProjectTask_idProject_fkey" FOREIGN KEY ("idProject") REFERENCES "DivisionProject"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProjectTaskDetail" ADD CONSTRAINT "DivisionProjectTaskDetail_idTask_fkey" FOREIGN KEY ("idTask") REFERENCES "DivisionProjectTask"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProjectMember" ADD CONSTRAINT "DivisionProjectMember_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProjectMember" ADD CONSTRAINT "DivisionProjectMember_idProject_fkey" FOREIGN KEY ("idProject") REFERENCES "DivisionProject"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProjectMember" ADD CONSTRAINT "DivisionProjectMember_idUser_fkey" FOREIGN KEY ("idUser") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProjectFile" ADD CONSTRAINT "DivisionProjectFile_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProjectFile" ADD CONSTRAINT "DivisionProjectFile_idProject_fkey" FOREIGN KEY ("idProject") REFERENCES "DivisionProject"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProjectFile" ADD CONSTRAINT "DivisionProjectFile_idFile_fkey" FOREIGN KEY ("idFile") REFERENCES "ContainerFileDivision"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProjectFile" ADD CONSTRAINT "DivisionProjectFile_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionDisscussion" ADD CONSTRAINT "DivisionDisscussion_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionDisscussion" ADD CONSTRAINT "DivisionDisscussion_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionDisscussionComment" ADD CONSTRAINT "DivisionDisscussionComment_idDisscussion_fkey" FOREIGN KEY ("idDisscussion") REFERENCES "DivisionDisscussion"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionDisscussionComment" ADD CONSTRAINT "DivisionDisscussionComment_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionDiscussionFile" ADD CONSTRAINT "DivisionDiscussionFile_idDiscussion_fkey" FOREIGN KEY ("idDiscussion") REFERENCES "DivisionDisscussion"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionDocumentFolderFile" ADD CONSTRAINT "DivisionDocumentFolderFile_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionDocumentFolderFile" ADD CONSTRAINT "DivisionDocumentFolderFile_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionDocumentShare" ADD CONSTRAINT "DivisionDocumentShare_idDocument_fkey" FOREIGN KEY ("idDocument") REFERENCES "DivisionDocumentFolderFile"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionDocumentShare" ADD CONSTRAINT "DivisionDocumentShare_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionCalendar" ADD CONSTRAINT "DivisionCalendar_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionCalendar" ADD CONSTRAINT "DivisionCalendar_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionCalendarReminder" ADD CONSTRAINT "DivisionCalendarReminder_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionCalendarReminder" ADD CONSTRAINT "DivisionCalendarReminder_idCalendar_fkey" FOREIGN KEY ("idCalendar") REFERENCES "DivisionCalendar"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionCalendarMember" ADD CONSTRAINT "DivisionCalendarMember_idCalendar_fkey" FOREIGN KEY ("idCalendar") REFERENCES "DivisionCalendar"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionCalendarMember" ADD CONSTRAINT "DivisionCalendarMember_idUser_fkey" FOREIGN KEY ("idUser") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ContainerFileDivision" ADD CONSTRAINT "ContainerFileDivision_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ColorTheme" ADD CONSTRAINT "ColorTheme_idVillage_fkey" FOREIGN KEY ("idVillage") REFERENCES "Village"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "BannerImage" ADD CONSTRAINT "BannerImage_idVillage_fkey" FOREIGN KEY ("idVillage") REFERENCES "Village"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Notifications" ADD CONSTRAINT "UserToUserMap" FOREIGN KEY ("idUserTo") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Notifications" ADD CONSTRAINT "UserFromUserMap" FOREIGN KEY ("idUserFrom") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Subscribe" ADD CONSTRAINT "Subscribe_idUser_fkey" FOREIGN KEY ("idUser") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Discussion" ADD CONSTRAINT "Discussion_idVillage_fkey" FOREIGN KEY ("idVillage") REFERENCES "Village"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Discussion" ADD CONSTRAINT "Discussion_idGroup_fkey" FOREIGN KEY ("idGroup") REFERENCES "Group"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Discussion" ADD CONSTRAINT "Discussion_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DiscussionMember" ADD CONSTRAINT "DiscussionMember_idDiscussion_fkey" FOREIGN KEY ("idDiscussion") REFERENCES "Discussion"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DiscussionMember" ADD CONSTRAINT "DiscussionMember_idUser_fkey" FOREIGN KEY ("idUser") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DiscussionComment" ADD CONSTRAINT "DiscussionComment_idDiscussion_fkey" FOREIGN KEY ("idDiscussion") REFERENCES "Discussion"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DiscussionComment" ADD CONSTRAINT "DiscussionComment_idUser_fkey" FOREIGN KEY ("idUser") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DiscussionFile" ADD CONSTRAINT "DiscussionFile_idDiscussion_fkey" FOREIGN KEY ("idDiscussion") REFERENCES "Discussion"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"

View File

@@ -658,3 +658,12 @@ model DiscussionFile {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Setting{
id String @id @default(cuid())
name String
value String
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

View File

@@ -1,9 +1,9 @@
import { seederAdmin, seederAdminRole, seederAnnouncement, seederAnnouncementMember, seederDesa, seederDiscussion, seederDiscussionMember, seederDivision, seederDivisionMember, seederGroup, seederPosition, seederProject, seederProjectMember, seederProjectTask, seederTheme, seederUser, seederUserRole } from '@/module/seeder';
import { seederAdmin, seederAdminRole, seederAnnouncement, seederAnnouncementMember, seederDesa, seederDiscussion, seederDiscussionMember, seederDivision, seederDivisionMember, seederGroup, seederPosition, seederProject, seederProjectMember, seederProjectTask, seederSetting, seederTheme, seederUser, seederUserRole } from '@/module/seeder';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient()
// DATA YG DI SEEDER MERUPAKAN DATA REAL(DARMASABA) & DATA DUMMY (MANDALA)
// DATA JSON GABUNGAN (REAL & DUMMY) ADALAH adminRole, admin, theme, desa, group, position, user, userRole, user
// DATA JSON GABUNGAN (REAL & DUMMY) ADALAH adminRole, admin, theme, desa, group, position, user, userRole, user, dan setting
// Selain table yg disebutkan, data lainnya merupakan data dummy
async function main() {
@@ -159,10 +159,10 @@ async function main() {
idGroup: data.idGroup,
idPosition: data.idPosition,
idUserRole: data.idUserRole,
nik: data.nik,
// nik: data.nik,
name: data.name,
// phone: data.phone,
email: data.email,
// email: data.email,
gender: data.gender
},
create: {
@@ -385,6 +385,23 @@ async function main() {
}
})
}
// SETTING
for (let data of seederSetting) {
await prisma.setting.upsert({
where: {
id: data.id
},
update: {
name: data.name,
},
create: {
id: data.id,
name: data.name,
value: data.value
}
})
}
}
main().then(async () => {

View File

@@ -167,12 +167,19 @@ export async function DELETE(request: Request, context: { params: { id: string }
// EDIT PENGUMUMAN
export async function PUT(request: Request, context: { params: { id: string } }) {
try {
const body = await request.formData()
const dataBody = body.get("data")
const cekFile = body.has("file0")
const contentType = request.headers.get("content-type");
let title, desc, groups, user, oldFile: any[] = [], cekFile, body: FormData | undefined
if (contentType?.includes("multipart/form-data")) {
body = await request.formData()
const dataBody = body.get("data")
cekFile = body.has("file0");
({ title, desc, groups, user, oldFile } = JSON.parse(dataBody as string))
} else {
({ title, desc, groups, user } = await request.json());
}
// const { title, desc, groups, user } = (await request.json());
const { title, desc, groups, user, oldFile } = JSON.parse(dataBody as string)
const { id } = context.params;
const userMobile = await funGetUserById({ id: String(user) })
@@ -245,7 +252,7 @@ export async function PUT(request: Request, context: { params: { id: string } })
}
}
if (cekFile) {
if (cekFile && body) {
body.delete("data")
for (var pair of body.entries()) {
if (String(pair[0]).substring(0, 4) == "file") {

View File

@@ -113,12 +113,19 @@ export async function GET(request: Request) {
// CREATE PENGUMUMAN
export async function POST(request: Request) {
try {
const body = await request.formData()
const dataBody = body.get("data")
const cekFile = body.has("file0")
const contentType = request.headers.get("content-type");
let title, desc, groups, user, cekFile, body: FormData | undefined
if (contentType?.includes("multipart/form-data")) {
body = await request.formData()
const dataBody = body.get("data")
cekFile = body.has("file0");
({ title, desc, groups, user } = JSON.parse(dataBody as string))
} else {
({ title, desc, groups, user } = await request.json());
}
// const { title, desc, groups, user } = (await request.json());
const { title, desc, groups, user } = JSON.parse(dataBody as string)
const userMobile = await funGetUserById({ id: String(user) })
if (userMobile.id == "null" || userMobile.id == undefined || userMobile.id == "") {
@@ -144,7 +151,6 @@ export async function POST(request: Request) {
let memberDivision = []
for (var i = 0, l = groups.length; i < l; i++) {
2
var obj = groups[i].Division;
for (let index = 0; index < obj.length; index++) {
const element = obj[index];
@@ -158,7 +164,7 @@ export async function POST(request: Request) {
}
if (cekFile) {
if (cekFile && body) {
body.delete("data")
for (var pair of body.entries()) {
if (String(pair[0]).substring(0, 4) == "file") {
@@ -247,7 +253,7 @@ export async function POST(request: Request) {
where: {
isActive: true,
idUserRole: "supadmin",
idVillage: user.idVillage
idVillage: String(villaId)
},
select: {
id: true,

View File

@@ -0,0 +1,32 @@
import { prisma } from "@/module/_global";
import { funGetUserById } from "@/module/auth";
import { NextResponse } from "next/server";
export async function POST(request: Request) {
try {
const { token, user } = (await request.json());
const userMobile = await funGetUserById({ id: user })
if (userMobile.id == "null" || userMobile.id == undefined || userMobile.id == "") {
return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 200 });
}
const cek = await prisma.tokenDeviceUser.count({
where: {
idUser: userMobile.id,
token
}
})
if (cek > 0) {
return NextResponse.json({ success: true, message: "Token terdaftar", data: true }, { status: 200 });
} else {
return NextResponse.json({ success: false, message: "Token tidak terdaftar", data: false }, { status: 200 })
}
} catch (error) {
console.error(error);
return NextResponse.json({ success: false, message: "Gagal mengecek token, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 });
}
};

View File

@@ -5,7 +5,7 @@ import { NextResponse } from "next/server";
export async function POST(request: Request) {
try {
const { token, user } = (await request.json());
const { token, user, category } = (await request.json());
const userMobile = await funGetUserById({ id: user })
if (userMobile.id == "null" || userMobile.id == undefined || userMobile.id == "") {
@@ -19,8 +19,10 @@ export async function POST(request: Request) {
}
})
// create log user
const log = await createLogUserMobile({ act: 'LOGIN', desc: 'User login', table: 'user', data: '', user: userMobile.id })
if (category != "register") {
// create log user
const log = await createLogUserMobile({ act: 'LOGIN', desc: 'User login', table: 'user', data: '', user: userMobile.id })
}
if (cek == 0 && token != "" && token != undefined && token != null) {
const data = await prisma.tokenDeviceUser.create({
@@ -43,7 +45,7 @@ export async function POST(request: Request) {
export async function PUT(request: Request) {
try {
const { token, user } = (await request.json());
const { token, user, category } = (await request.json());
const userMobile = await funGetUserById({ id: user })
if (userMobile.id == "null" || userMobile.id == undefined || userMobile.id == "") {
@@ -60,8 +62,10 @@ export async function PUT(request: Request) {
}
// create log user
const log = await createLogUserMobile({ act: 'LOGOUT', desc: 'User logout', table: 'user', data: '', user: userMobile.id })
if (category != "unregister") {
// create log user
const log = await createLogUserMobile({ act: 'LOGOUT', desc: 'User logout', table: 'user', data: '', user: userMobile.id })
}
return NextResponse.json({ success: true, message: "Berhasil menghapus token", }, { status: 200 });
} catch (error) {

View File

@@ -242,10 +242,10 @@ export async function DELETE(request: Request, context: { params: { id: string }
// create log user
if (active) {
const log = await createLogUserMobile({ act: 'DELETE', desc: 'User mengaktifkan data diskusi umum', table: 'disscussion', data: id, user: userMobile.id })
return NextResponse.json({ success: true, message: "Berhasil mengaktifkan diskusi umum", user: user.id }, { status: 200 });
return NextResponse.json({ success: true, message: "Berhasil mengaktifkan diskusi umum" }, { status: 200 });
} else {
const log = await createLogUserMobile({ act: 'DELETE', desc: 'User mengarsipkan data diskusi umum', table: 'disscussion', data: id, user: userMobile.id })
return NextResponse.json({ success: true, message: "Berhasil mengarsipkan diskusi umum", user: user.id }, { status: 200 });
return NextResponse.json({ success: true, message: "Berhasil mengarsipkan diskusi umum" }, { status: 200 });
}
@@ -260,12 +260,19 @@ export async function DELETE(request: Request, context: { params: { id: string }
export async function PUT(request: Request, context: { params: { id: string } }) {
try {
const { id } = context.params
const body = await request.formData()
const dataBody = body.get("data")
const cekFile = body.has("file0")
const contentType = request.headers.get("content-type");
let title, desc, user, oldFile: any[] = [], cekFile, body: FormData | undefined
if (contentType?.includes("multipart/form-data")) {
body = await request.formData()
const dataBody = body.get("data")
cekFile = body.has("file0");
({ title, desc, user, oldFile } = JSON.parse(dataBody as string))
} else {
({ title, desc, user } = await request.json());
}
// const { title, desc, user } = (await request.json());
const { title, desc, user, oldFile } = JSON.parse(dataBody as string)
const userMobile = await funGetUserById({ id: String(user) })
@@ -306,7 +313,7 @@ export async function PUT(request: Request, context: { params: { id: string } })
}
}
if (cekFile) {
if (cekFile && body) {
body.delete("data")
for (var pair of body.entries()) {
if (String(pair[0]).substring(0, 4) == "file") {

View File

@@ -109,16 +109,20 @@ export async function GET(request: Request) {
// CREATE DISCUSSION GENERALE
// CREATE DISCUSSION GENERAL
export async function POST(request: Request) {
try {
const contentType = request.headers.get("content-type");
let idGroup, user, title, desc, member, cekFile, body: FormData | undefined
if (contentType?.includes("multipart/form-data")) {
body = await request.formData()
const dataBody = body.get("data")
cekFile = body.has("file0");
({ idGroup, user, title, desc, member } = JSON.parse(dataBody as string))
} else {
({ idGroup, user, title, desc, member } = await request.json());
}
const body = await request.formData()
const dataBody = body.get("data")
const cekFile = body.has("file0")
// const { idGroup, user, title, desc, member } = await request.json();
const { idGroup, user, title, desc, member } = JSON.parse(dataBody as string)
const userMobile = await funGetUserById({ id: user })
@@ -153,7 +157,7 @@ export async function POST(request: Request) {
})
if (cekFile) {
if (cekFile && body) {
body.delete("data")
for (var pair of body.entries()) {
if (String(pair[0]).substring(0, 4) == "file") {
@@ -213,7 +217,7 @@ export async function POST(request: Request) {
where: {
isActive: true,
idUserRole: "supadmin",
idVillage: user.idVillage
idVillage: String(userMobile.idVillage)
},
select: {
id: true,

View File

@@ -227,12 +227,18 @@ export async function PUT(request: Request, context: { params: { id: string } })
export async function POST(request: Request, context: { params: { id: string } }) {
try {
const { id } = context.params
const body = await request.formData()
const dataBody = body.get("data")
const cekFile = body.has("file0")
const contentType = request.headers.get("content-type");
// const { title, desc, user } = (await request.json())
const { title, desc, user, oldFile } = JSON.parse(dataBody as string)
let title, desc, user, oldFile: any[] = [], cekFile, body: FormData | undefined
if (contentType?.includes("multipart/form-data")) {
body = await request.formData()
const dataBody = body.get("data")
cekFile = body.has("file0");
({ title, desc, user, oldFile } = JSON.parse(dataBody as string))
} else {
({ title, desc, user } = await request.json());
}
const userMobile = await funGetUserById({ id: String(user) })
@@ -273,7 +279,7 @@ export async function POST(request: Request, context: { params: { id: string } }
}
}
if (cekFile) {
if (cekFile && body) {
body.delete("data")
for (var pair of body.entries()) {
if (String(pair[0]).substring(0, 4) == "file") {

View File

@@ -102,12 +102,17 @@ export async function GET(request: Request) {
// CREATE DISCUSSION
export async function POST(request: Request) {
try {
const body = await request.formData()
const dataBody = body.get("data")
const cekFile = body.has("file0")
const contentType = request.headers.get("content-type");
// const { idDivision, desc, user } = (await request.json());
const { idDivision, desc, user } = JSON.parse(String(dataBody));
let idDivision, desc, user, cekFile, body: FormData | undefined
if (contentType?.includes("multipart/form-data")) {
body = await request.formData()
const dataBody = body.get("data")
cekFile = body.has("file0");
({ idDivision, desc, user } = JSON.parse(String(dataBody)));
} else {
({ idDivision, desc, user } = await request.json());
}
const userMobile = await funGetUserById({ id: String(user) })
@@ -143,7 +148,7 @@ export async function POST(request: Request) {
});
if (cekFile) {
if (cekFile && body) {
body.delete("data")
for (var pair of body.entries()) {
if (String(pair[0]).substring(0, 4) == "file") {

View File

@@ -217,7 +217,7 @@ export async function PUT(request: Request, context: { params: { id: string } })
const resize = await sharp(imageBuffer).resize(300).toBuffer();
// Convert buffer ke Blob
const blob = new Blob([resize], { type: file.type });
const blob = new Blob([resize as any], { type: file.type });
// Convert Blob ke File
const resizedFile = new File([blob], fileName, {

View File

@@ -133,7 +133,7 @@ export async function PUT(request: Request) {
const resize = await sharp(imageBuffer).resize(300).toBuffer();
// Convert buffer ke Blob
const blob = new Blob([resize], { type: file.type });
const blob = new Blob([resize as any], { type: file.type });
// Convert Blob ke File
const resizedFile = new File([blob], fileName, {

View File

@@ -210,7 +210,7 @@ export async function POST(request: Request) {
const resize = await sharp(imageBuffer).resize(300).toBuffer();
// Convert buffer ke Blob
const blob = new Blob([resize], { type: file.type });
const blob = new Blob([resize as any], { type: file.type });
// Convert Blob ke File
const resizedFile = new File([blob], fileName, {

View File

@@ -0,0 +1,25 @@
import { prisma } from "@/module/_global";
import { NextResponse } from "next/server";
export async function GET(request: Request) {
try {
const data = await prisma.setting.findMany({
where: {
isActive: true,
id: {
contains: "mobile_"
}
},
select: {
id: true,
name: true,
value: true
}
})
return NextResponse.json({ success: true, data }, { status: 200 });
} catch (error) {
console.error(error);
return NextResponse.json({ success: false, reason: (error as Error).message, }, { status: 500 });
}
}

View File

@@ -0,0 +1,687 @@
import { prisma } from "@/module/_global";
import cors from "@elysiajs/cors";
import { swagger } from "@elysiajs/swagger";
import Elysia, { t } from "elysia";
import _ from "lodash";
import moment from "moment";
import "moment/locale/id";
// Gabungkan semua ke dalam satu instance server yang dipasang di /api/noc
const NocServer = new Elysia({ prefix: "/api/noc" })
.use(cors({
origin: "*",
methods: ["GET", "POST", "OPTIONS"],
}))
.use(swagger({
path: "/docs", // Karena prefix instance adalah /api/noc, maka ini akan diakses di /api/noc/docs
documentation: {
info: {
title: "Sistem Desa Mandiri - NOC API",
version: "1.0.0",
description: "API Khusus untuk kebutuhan NOC (Network Operation Center) dan Monitoring Desa",
},
tags: [
{ name: "NOC", description: "Endpoint khusus monitoring" }
]
}
}))
// ── GET /api/noc/active-divisions ──────────────────────────────────────────
.get(
"/active-divisions",
async ({ query, set }) => {
const { idDesa, limit } = query;
if (!idDesa) {
set.status = 400;
return {
success: false,
message: "Parameter idDesa wajib diisi",
data: null,
};
}
const maxResults = Number(limit ?? 5);
try {
// Cek apakah desa ada
const village = await prisma.village.findUnique({
where: { id: idDesa },
select: { id: true, name: true },
});
if (!village) {
set.status = 404;
return {
success: false,
message: "Desa tidak ditemukan",
data: null,
};
}
// Ambil semua divisi milik desa ini
const divisions = await prisma.division.findMany({
where: {
idVillage: idDesa,
isActive: true,
},
select: {
id: true,
name: true,
idGroup: true,
Group: {
select: {
name: true,
},
},
_count: {
select: {
DivisionProject: true,
},
},
},
});
// Hitung total kegiatan per divisi & urutkan descending, ambil top sesuai limit
const ranked = divisions
.map((d) => ({
id: d.id,
division: d.name,
group: d.Group.name,
totalKegiatan: d._count.DivisionProject
}))
.sort((a, b) => b.totalKegiatan - a.totalKegiatan)
.slice(0, maxResults);
return {
success: true,
message: "Berhasil mendapatkan divisi teraktif",
data: {
idDesa: village.id,
namaDesa: village.name,
divisi: ranked,
},
};
} catch (error) {
console.error("[NOC] active-divisions error:", error);
set.status = 500;
return {
success: false,
message: "Terjadi kesalahan pada server",
data: null,
};
}
},
{
query: t.Object({
idDesa: t.String({ description: "ID Desa yang ingin dicari" }),
limit: t.Optional(t.String({ description: "Jumlah maksimal data (default: 5)" })),
}),
detail: {
summary: "Divisi Teraktif",
description: "Mendapatkan daftar divisi teraktif berdasarkan jumlah proyek pada desa tertentu.",
tags: ["NOC"],
},
}
)
// ── GET /api/noc/latest-projects ──────────────────────────────────────────
.get(
"/latest-projects",
async ({ query, set }) => {
const { idDesa, limit } = query;
if (!idDesa) {
set.status = 400;
return {
success: false,
message: "Parameter idDesa wajib diisi",
data: null,
};
}
const maxResults = Math.min(Number(limit ?? 5), 50);
try {
// Cek apakah desa ada
const village = await prisma.village.findUnique({
where: { id: idDesa },
select: { id: true, name: true },
});
if (!village) {
set.status = 404;
return {
success: false,
message: "Desa tidak ditemukan",
data: null,
};
}
// Ambil proyek umum terbaru dari desa ini
const projects = await prisma.project.findMany({
where: {
idVillage: idDesa,
isActive: true,
},
select: {
id: true,
title: true,
status: true,
desc: true,
updatedAt: true,
Group: {
select: {
name: true,
},
},
User: {
select: {
name: true,
},
},
},
orderBy: {
updatedAt: "desc",
},
take: maxResults,
});
const mapped = projects.map((p) => ({
id: p.id,
title: p.title,
status: p.status,
desc: p.desc,
group: p.Group.name,
createdBy: p.User.name,
updatedAt: p.updatedAt,
}));
return {
success: true,
message: "Berhasil mendapatkan proyek terbaru",
data: {
idDesa: village.id,
namaDesa: village.name,
total: mapped.length,
projects: mapped,
},
};
} catch (error) {
console.error("[NOC] latest-projects error:", error);
set.status = 500;
return {
success: false,
message: "Terjadi kesalahan pada server",
data: null,
};
}
},
{
query: t.Object({
idDesa: t.String({ description: "ID Desa yang ingin dicari" }),
limit: t.Optional(
t.String({ description: "Jumlah maksimal proyek (default: 5, maks: 50)" })
),
}),
detail: {
summary: "Latest Projects General",
description: "Mendapatkan daftar proyek umum terbaru dari berbagai grup pada desa tertentu.",
tags: ["NOC"],
},
}
)
// ── GET /api/noc/upcoming-events ───────────────────────────────────────────
.get(
"/upcoming-events",
async ({ query, set }) => {
const { idDesa, limit, filter } = query;
if (!idDesa) {
set.status = 400;
return {
success: false,
message: "Parameter idDesa wajib diisi",
data: null,
};
}
const maxResults = Math.min(Number(limit ?? 10), 50);
const today = moment().startOf("day").toDate();
try {
const village = await prisma.village.findUnique({
where: { id: idDesa },
select: { id: true, name: true },
});
if (!village) {
set.status = 404;
return {
success: false,
message: "Desa tidak ditemukan",
data: null,
};
}
const events = await prisma.divisionCalendarReminder.findMany({
where: {
isActive: true,
dateStart: {
gte: today,
},
Division: {
idVillage: idDesa,
isActive: true,
},
DivisionCalendar: {
isActive: true,
},
},
select: {
id: true,
idCalendar: true,
dateStart: true,
dateEnd: true,
timeStart: true,
timeEnd: true,
status: true,
Division: {
select: {
id: true,
name: true,
},
},
DivisionCalendar: {
select: {
title: true,
desc: true,
linkMeet: true,
repeatEventTyper: true,
User: {
select: {
name: true,
},
},
},
},
},
orderBy: [
{ dateStart: "asc" },
{ timeStart: "asc" },
],
take: maxResults,
});
const todayMoment = moment().startOf("day");
const mapper = (e: any) => ({
id: e.id,
idCalendar: e.idCalendar,
title: e.DivisionCalendar.title,
desc: e.DivisionCalendar.desc,
linkMeet: e.DivisionCalendar.linkMeet ?? null,
repeatEventTyper: e.DivisionCalendar.repeatEventTyper,
dateStart: moment(e.dateStart).format("YYYY-MM-DD"),
dateEnd: e.dateEnd
? moment(e.dateEnd).format("YYYY-MM-DD")
: null,
timeStart: moment.utc(e.timeStart).format("HH:mm"),
timeEnd: moment.utc(e.timeEnd).format("HH:mm"),
status: e.status,
createdBy: e.DivisionCalendar.User.name,
divisi: {
id: e.Division.id,
name: e.Division.name,
},
});
const todayEvents = events.filter(e => moment(e.dateStart).isSame(todayMoment, 'day')).map(mapper);
const upcomingEvents = events.filter(e => moment(e.dateStart).isAfter(todayMoment, 'day')).map(mapper);
let data: any = {
idDesa: village.id,
namaDesa: village.name,
};
if (filter === "today") {
data.events = todayEvents;
} else if (filter === "upcoming") {
data.events = upcomingEvents;
} else {
data.today = todayEvents;
data.upcoming = upcomingEvents;
}
return {
success: true,
message: "Berhasil mendapatkan events",
data: data,
};
} catch (error) {
console.error("[NOC] upcoming-events error:", error);
set.status = 500;
return {
success: false,
message: "Terjadi kesalahan pada server",
data: null,
};
}
},
{
query: t.Object({
idDesa: t.String({ description: "ID Desa yang ingin dicari" }),
limit: t.Optional(
t.String({ description: "Jumlah maksimal event (default: 10, maks: 50)" })
),
filter: t.Optional(
t.String({ description: "Filter event: 'today' atau 'upcoming'" })
),
}),
detail: {
summary: "Events (Today & Upcoming)",
description: "Mendapatkan daftar event pada hari ini dan yang akan datang untuk semua divisi pada desa tertentu.",
tags: ["NOC"],
},
}
)
// ── GET /api/noc/diagram-jumlah-document ───────────────────────────────────────────────
.get(
"/diagram-jumlah-document",
async ({ query, set }) => {
const { idDesa } = query;
if (!idDesa) {
set.status = 400;
return {
success: false,
message: "Parameter idDesa wajib diisi",
data: null,
};
}
try {
const village = await prisma.village.findUnique({
where: { id: idDesa },
select: { id: true, name: true },
});
if (!village) {
set.status = 404;
return {
success: false,
message: "Desa tidak ditemukan",
data: null,
};
}
const documents = await prisma.divisionDocumentFolderFile.findMany({
where: {
isActive: true,
category: 'FILE',
Division: {
isActive: true,
idVillage: idDesa,
Group: {
isActive: true,
}
}
}
})
const groupData = _.map(_.groupBy(documents, "extension"), (v: any) => ({
file: v[0].extension,
jumlah: v.length,
}))
const image = ['jpg', 'jpeg', 'png', 'heic']
let hasilImage = {
label: 'Gambar',
value: 0,
color: '#fac858'
}
let hasilFile = {
label: 'Dokumen',
value: 0,
color: '#92cc76'
}
groupData.map((v: any) => {
if (image.some((i: any) => i == v.file)) {
hasilImage = {
label: 'Gambar',
value: hasilImage.value + v.jumlah,
color: '#fac858'
}
} else {
hasilFile = {
label: 'Dokumen',
value: hasilFile.value + v.jumlah,
color: '#92cc76'
}
}
})
const allData = [hasilImage, hasilFile]
return {
success: true,
message: "Berhasil mendapatkan jumlah document",
data: allData
};
} catch (error) {
console.error("[NOC] jumlah-document error:", error);
set.status = 500;
return {
success: false,
message: "Terjadi kesalahan pada server",
data: null,
};
}
},
{
query: t.Object({
idDesa: t.String({ description: "ID Desa yang ingin dicari" }),
}),
detail: {
summary: "Diagram Jumlah Document",
description: "Mendapatkan diagram jumlah document pada desa tertentu.",
tags: ["NOC"],
},
}
)
// -- GET /api/noc/diagram-progres-kegiatan
.get(
"/diagram-progres-kegiatan",
async ({ query, set }) => {
const { idDesa } = query;
if (!idDesa) {
set.status = 400;
return {
success: false,
message: "Parameter idDesa wajib diisi",
data: null,
};
}
try {
const village = await prisma.village.findUnique({
where: { id: idDesa },
select: { id: true, name: true },
});
if (!village) {
set.status = 404;
return {
success: false,
message: "Desa tidak ditemukan",
data: null,
};
}
const data = await prisma.project.groupBy({
where: {
isActive: true,
idVillage: idDesa,
Group: {
isActive: true,
}
},
by: ["status"],
_count: true
})
const dataStatus = [{ name: 'Segera dikerjakan', status: 0, color: '#177AD5' }, { name: 'Dikerjakan', status: 1, color: '#fac858' }, { name: 'Selesai dikerjakan', status: 2, color: '#92cc76' }, { name: 'Dibatalkan', status: 3, color: '#ED6665' }]
const hasil: any[] = []
let input
for (let index = 0; index < dataStatus.length; index++) {
const cek = data.some((i: any) => i.status == dataStatus[index].status)
if (cek) {
const find = ((Number(data.find((i: any) => i.status == dataStatus[index].status)?._count) * 100) / data.reduce((n, { _count }) => n + _count, 0)).toFixed(2)
const fix = find != "100.00" ? find.substr(-2, 2) == "00" ? find.substr(0, 2) : find : "100"
input = {
text: fix + '%',
value: fix,
color: dataStatus[index].color
}
} else {
input = {
text: '0%',
value: 0,
color: dataStatus[index].color
}
}
hasil.push(input)
}
return {
success: true,
message: "Berhasil mendapatkan progres kegiatan",
data: hasil
};
} catch (error) {
console.error("[NOC] progres-kegiatan error:", error);
set.status = 500;
return {
success: false,
message: "Terjadi kesalahan pada server",
data: null,
};
}
},
{
query: t.Object({
idDesa: t.String({ description: "ID Desa yang ingin dicari" }),
}),
detail: {
summary: "Diagram Progres Kegiatan",
description: "Mendapatkan diagram progres kegiatan pada desa tertentu.",
tags: ["NOC"],
},
}
)
// -- GET /api/noc/latest-discussion
.get(
"/latest-discussion",
async ({ query, set }) => {
const { idDesa, limit } = query;
const maxResults = Math.min(Number(limit ?? 5), 50);
if (!idDesa) {
set.status = 400;
return {
success: false,
message: "Parameter idDesa wajib diisi",
data: null,
};
}
try {
const village = await prisma.village.findUnique({
where: { id: idDesa },
select: { id: true, name: true },
});
if (!village) {
set.status = 404;
return {
success: false,
message: "Desa tidak ditemukan",
data: null,
};
}
const data = await prisma.discussion.findMany({
take: maxResults,
where: {
idVillage: idDesa,
isActive: true,
status: 1,
},
select: {
id: true,
title: true,
desc: true,
createdAt: true,
User: {
select: {
name: true
}
},
Group: {
select: {
name: true
}
}
},
orderBy: {
createdAt: "desc"
}
})
const allData = data.map((v: any) => ({
..._.omit(v, ["createdAt", "User", "Group"]),
date: moment(v.createdAt).format("ll"),
user: v.User.name,
group: v.Group.name
}))
return {
success: true,
message: "Berhasil mendapatkan latest discussion",
data: allData
};
} catch (error) {
console.error("[NOC] latest-discussion error:", error);
set.status = 500;
return {
success: false,
message: "Terjadi kesalahan pada server",
data: null,
};
}
},
{
query: t.Object({
idDesa: t.String({ description: "ID Desa yang ingin dicari" }),
limit: t.Optional(t.String({ description: "Limit data" })),
}),
detail: {
summary: "Latest Discussion",
description: "Mendapatkan latest discussion pada desa tertentu.",
tags: ["NOC"],
},
}
);
export const GET = NocServer.handle;
export const POST = NocServer.handle;

View File

@@ -215,7 +215,7 @@ export async function PUT(request: Request, context: { params: { id: string } })
const resize = await sharp(imageBuffer).resize(300).toBuffer();
// Convert buffer ke Blob
const blob = new Blob([resize], { type: file.type });
const blob = new Blob([resize as any], { type: file.type });
// Convert Blob ke File
const resizedFile = new File([blob], fileName, {

View File

@@ -133,7 +133,7 @@ export async function PUT(request: Request) {
const resize = await sharp(imageBuffer).resize(300).toBuffer();
// Convert buffer ke Blob
const blob = new Blob([resize], { type: file.type });
const blob = new Blob([resize as any], { type: file.type });
// Convert Blob ke File
const resizedFile = new File([blob], fileName, {

View File

@@ -207,7 +207,7 @@ export async function POST(request: Request) {
const resize = await sharp(imageBuffer).resize(300).toBuffer();
// Convert buffer ke Blob
const blob = new Blob([resize], { type: file.type });
const blob = new Blob([resize as any], { type: file.type });
// Convert Blob ke File
const resizedFile = new File([blob], fileName, {

View File

@@ -2,7 +2,7 @@ import { NextResponse } from "next/server";
export async function GET(request: Request) {
try {
return NextResponse.json({ success: true, version: "2.1.0", tahap: "beta", update: "-api mobile; -login tanpa otp (mobile app); -tambah laporan pada project dan tugas divisi; -tambah upload link pada project dan tugas divisi; -tambah detail tanggal dan jam pada project dan tugas divisi; -api jenna ai; -privacy policy" }, { status: 200 });
return NextResponse.json({ success: true, version: "2.1.7", tahap: "beta", update: "-api untuk dashboard noc" }, { status: 200 });
} catch (error) {
console.error(error);
return NextResponse.json({ success: false, version: "Gagal mendapatkan version, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 });

41
src/app/global-error.tsx Normal file
View File

@@ -0,0 +1,41 @@
"use client";
export default function GlobalError({
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<html lang="en">
<body
style={{
backgroundColor: "#252A2F",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
minHeight: "100vh",
gap: 16,
fontFamily: "Lato, sans-serif",
color: "white",
}}
>
<h2>Terjadi Kesalahan</h2>
<button
onClick={() => reset()}
style={{
padding: "8px 16px",
borderRadius: 4,
border: "1px solid #ccc",
background: "transparent",
color: "white",
cursor: "pointer",
}}
>
Coba Lagi
</button>
</body>
</html>
);
}

View File

@@ -9,6 +9,8 @@ import '@mantine/notifications/styles.css';
import { Lato } from "next/font/google";
import { Toaster } from 'react-hot-toast';
export const dynamic = 'force-dynamic';
export const metadata = {
title: "SISTEM DESA MANDIRI",
description: "I have followed setup instructions carefully",

24
src/app/not-found.tsx Normal file
View File

@@ -0,0 +1,24 @@
import { Box, Text, Button } from "@mantine/core";
import Link from "next/link";
export default function NotFound() {
return (
<Box
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
minHeight: "100vh",
gap: 16,
}}
>
<Text size="xl" fw={700} c="white">
404 - Halaman Tidak Ditemukan
</Text>
<Button component={Link} href="/" variant="light">
Kembali ke Beranda
</Button>
</Box>
);
}

View File

@@ -0,0 +1,22 @@
[
{
"id": "mobile_latest_version",
"name": "latest version",
"value": "2.0.5"
},
{
"id": "mobile_minimum_version",
"name": "minimum version",
"value": "2.0.5"
},
{
"id": "mobile_maintenance",
"name": "maintenance",
"value": "false"
},
{
"id": "mobile_message_update",
"name": "message update",
"value": "Kami telah meningkatkan performa aplikasi"
}
]

View File

@@ -19,8 +19,8 @@
"idPosition": "pos_ketua_rt01",
"nik": "3201010101010001",
"name": "Juli Ningrum",
"phone": "081234567890",
"email": "juli@gmail.com",
"phone": "6281234567890",
"email": "juliningrum@gmail.com",
"gender": "F"
},
{
@@ -31,8 +31,8 @@
"idPosition": "pos_sekretaris_rt01",
"nik": "3201010101010002",
"name": "Salwa Kusmawati",
"phone": "081234567891",
"email": "salwa@gmail.com",
"phone": "6281234567891",
"email": "salwakusmawati@gmail.com",
"gender": "F"
},
{
@@ -43,8 +43,8 @@
"idPosition": "pos_staff_rt01",
"nik": "3201010101010005",
"name": "Bakidin Wibowo",
"phone": "6",
"email": "bakidin@gmail.com",
"phone": "6281234567894",
"email": "bakidinwibowo@gmail.com",
"gender": "M"
},
{
@@ -55,8 +55,8 @@
"idPosition": "pos_staff_rt01",
"nik": "3201010101010006",
"name": "Jais Kurniawan",
"phone": "081234567895",
"email": "jais@gmail.com",
"phone": "6281234567895",
"email": "jaiskurniawan@gmail.com",
"gender": "M"
},
{
@@ -67,8 +67,8 @@
"idPosition": "pos_staff_rt01",
"nik": "3201010101010007",
"name": "Safira Oktaviani S.I.Kom",
"phone": "081234567896",
"email": "safira@gmail.com",
"phone": "6281234567896",
"email": "safiraoktaviani@gmail.com",
"gender": "F"
},
{
@@ -79,8 +79,8 @@
"idPosition": "pos_staff_rt01",
"nik": "3201010101010008",
"name": "Agus Setiawan",
"phone": "081234567897",
"email": "agus@gmail.com",
"phone": "6281234567897",
"email": "agussetiawannn@gmail.com",
"gender": "M"
}
]

View File

@@ -12,9 +12,10 @@ import seederPosition from "./data/position.json";
import seederProject from "./data/project.json";
import seederProjectMember from "./data/project_member.json";
import seederProjectTask from "./data/project_task.json";
import seederSetting from "./data/setting.json";
import seederTheme from "./data/theme.json";
import seederUser from "./data/user.json";
import seederUserRole from "./data/user_role.json";
export { seederAdmin, seederAdminRole, seederAnnouncement, seederAnnouncementMember, seederDesa, seederDiscussion, seederDiscussionMember, seederDivision, seederDivisionMember, seederGroup, seederPosition, seederProject, seederProjectMember, seederProjectTask, seederTheme, seederUser, seederUserRole };
export { seederAdmin, seederAdminRole, seederAnnouncement, seederAnnouncementMember, seederDesa, seederDiscussion, seederDiscussionMember, seederDivision, seederDivisionMember, seederGroup, seederPosition, seederProject, seederProjectMember, seederProjectTask, seederSetting, seederTheme, seederUser, seederUserRole };

30
src/pages/_error.tsx Normal file
View File

@@ -0,0 +1,30 @@
import { NextPageContext } from "next";
function ErrorPage({ statusCode }: { statusCode?: number }) {
return (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
minHeight: "100vh",
backgroundColor: "#252A2F",
color: "white",
fontFamily: "Lato, sans-serif",
}}
>
<h1 style={{ fontSize: 24, fontWeight: 700 }}>
{statusCode === 404
? "404 - Halaman Tidak Ditemukan"
: "Terjadi Kesalahan"}
</h1>
</div>
);
}
ErrorPage.getInitialProps = ({ res, err }: NextPageContext) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
return { statusCode };
};
export default ErrorPage;