diff --git a/.gitignore b/.gitignore
index 82965e2d..d86875dd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,8 +40,5 @@ yarn-error.log*
*.tsbuildinfo
next-env.d.ts
-# prettier
-.prettierrc
-
# idea
.idea
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index efda72f0..951e5472 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,76 +1,146 @@
-stages: [notify]
+stages:
+ - build
+ - deploy
-# --- Notify when MR is opened/updated ---
-notify_discord_mr:
- stage: notify
- image: alpine:3.20
- rules:
- - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
+.build_template: &build_template
+ stage: build
+ image: node:20-alpine
+ cache:
+ key: npm-cache
+ paths:
+ - node_modules/
variables:
- WEBHOOK_URL: $DISCORD_WEBHOOK_URL
- before_script:
- - apk add --no-cache curl jq
- script: |
- MR_URL="${CI_PROJECT_URL}/-/merge_requests/${CI_MERGE_REQUEST_IID}"
+ NPM_CONFIG_PRODUCTION: 'false'
+ NODE_ENV: ''
+ script:
+ - echo "Installing dependencies..."
+ - npm ci --no-audit --no-fund
+ - echo "Building Next.js static export..."
+ - npx next build
+ artifacts:
+ name: 'out-$CI_COMMIT_SHORT_SHA'
+ paths:
+ - out/
+ expire_in: 1 week
- jq -n \
- --arg repo "$CI_PROJECT_PATH" \
- --arg mr "#${CI_MERGE_REQUEST_IID}" \
- --arg url "$MR_URL" \
- --arg requestor "${GITLAB_USER_LOGIN:-$GITLAB_USER_NAME}" \
- --arg source "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" \
- --arg target "$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" \
- --arg title "$CI_MERGE_REQUEST_TITLE" \
- '{
- username: "CI Bot - FE",
- embeds: [{
- title: "📣 [LTI WEB CLIENT] Merge Request Opened/Updated",
- description: ($mr + " in " + $repo),
- url: $url,
- color: 3447003,
- fields: [
- {name: "Author", value: $requestor, inline: true},
- {name: "Source → Target", value: ($source + " → " + $target), inline: true},
- {name: "Title", value: $title}
- ]
- }]
- }' \
- | curl -sS -H "Content-Type: application/json" -d @- "$WEBHOOK_URL"
+.deploy_template: &deploy_template
+ stage: deploy
+ image:
+ name: amazon/aws-cli:latest
+ entrypoint: ['/bin/sh', '-c']
+ script:
+ - set -e
+ - aws --version
+ - echo "Cleaning up newline characters in AWS credentials..."
+ - export AWS_ACCESS_KEY_ID=$(echo $AWS_ACCESS_KEY_ID | tr -d '\r\n')
+ - export AWS_SECRET_ACCESS_KEY=$(echo $AWS_SECRET_ACCESS_KEY | tr -d '\r\n')
+ - echo "Deploying to s3://$S3_BUCKET in region $AWS_REGION"
+ - aws s3api head-bucket --bucket "$S3_BUCKET" --region "$AWS_REGION" || aws s3api create-bucket --bucket "$S3_BUCKET" --region "$AWS_REGION" --create-bucket-configuration LocationConstraint="$AWS_REGION"
+ - aws s3 sync ./out "s3://$S3_BUCKET" --delete --region "$AWS_REGION" --endpoint-url "https://s3.ap-southeast-3.amazonaws.com"
-# --- Notify when MR is merged ---
-notify_discord_merge:
- stage: notify
- image: alpine:3.20
+ # CloudFront invalidation
+ - |
+ STATUS="success"
+ if [ -n "$CLOUDFRONT_DISTRIBUTION_ID" ]; then
+ echo "Invalidating CloudFront cache..."
+ if ! aws cloudfront create-invalidation --distribution-id "$CLOUDFRONT_DISTRIBUTION_ID" --paths "/*"; then
+ echo "CloudFront invalidation failed."
+ STATUS="failed"
+ fi
+ else
+ echo "No CloudFront distribution specified — skipping invalidation"
+ fi
+
+ # Notifikasi Discord
+ - |
+ RUN_URL="${CI_PROJECT_URL}/-/pipelines/${CI_PIPELINE_ID}"
+
+ if [ "$CI_COMMIT_BRANCH" = "development" ]; then
+ ENVIRONMENT_NAME="WEB-LTI-DEV"
+ elif [ "$CI_COMMIT_BRANCH" = "master" ]; then
+ ENVIRONMENT_NAME="WEB-LTI-PROD"
+ else
+ ENVIRONMENT_NAME="UNKNOWN"
+ fi
+
+ if [ "$STATUS" = "success" ]; then
+ COLOR=3066993
+ TITLE="✅ Deployment ${ENVIRONMENT_NAME} Succeeded"
+ DESC="Deployment job on branch \`${CI_COMMIT_REF_NAME}\` completed successfully."
+ else
+ COLOR=15158332
+ TITLE="❌ Deployment ${ENVIRONMENT_NAME} Failed"
+ DESC="Deployment job on branch \`${CI_COMMIT_REF_NAME}\` encountered issues."
+ fi
+
+ jq -n \
+ --arg title "$TITLE" \
+ --arg desc "$DESC" \
+ --arg color "$COLOR" \
+ --arg repo "$CI_PROJECT_PATH" \
+ --arg actor "$GITLAB_USER_LOGIN" \
+ --arg commit "$CI_COMMIT_SHA" \
+ --arg run_url "$RUN_URL" \
+ '{
+ username: "CI Bot - LTI WEB",
+ embeds: [{
+ title: $title,
+ description: $desc,
+ color: ($color|tonumber),
+ fields: [
+ {name: "Repository", value: $repo, inline: true},
+ {name: "Actor", value: $actor, inline: true},
+ {name: "Commit", value: $commit, inline: false},
+ {name: "Pipeline", value: ("[Open run](" + $run_url + ")"), inline: false}
+ ]
+ }]
+ }' > payload.json
+
+ curl -sS -H "Content-Type: application/json" -d @payload.json "$DISCORD_WEBHOOK_URL"
+
+# ====== DEVELOPMENT (Branch development) ======
+build:dev:
+ <<: *build_template
rules:
- # Only run for merge request pipelines that are in merged state
- - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_STATE == "merged"'
+ - if: '$CI_COMMIT_BRANCH == "development"'
+ environment:
+ name: development
variables:
- WEBHOOK_URL: $DISCORD_WEBHOOK_URL
- before_script:
- - apk add --no-cache curl jq
- script: |
- MR_URL="${CI_PROJECT_URL}/-/merge_requests/${CI_MERGE_REQUEST_IID}"
+ NEXT_PUBLIC_API_BASE_URL: 'https://dev-api-lti.mbugroup.id'
+ NEXT_PUBLIC_SSO_LOGIN_URL: 'https://dev-api-sso.mbugroup.id'
+
+deploy:dev:
+ <<: *deploy_template
+ needs: ['build:dev']
+ rules:
+ - if: '$CI_COMMIT_BRANCH == "development"'
+ variables:
+ S3_BUCKET: 'dev-lti-erp.mbugroup.id'
+ AWS_REGION: 'ap-southeast-3'
+ CLOUDFRONT_DISTRIBUTION_ID: 'E1Z8XTA8XF1GIV'
+ environment:
+ name: development
+ url: https://dev-lti-erp.mbugroup.id
+# ====== PRODUCTION ======
+# build:production:
+# <<: *build_template
+# rules:
+# # pilih salah satu: pakai branch master ATAU pakai tags rilis
+# - if: '$CI_COMMIT_BRANCH == "master"'
+# # - if: '$CI_COMMIT_TAG' # kalau mau rilis via tag, uncomment ini dan hapus baris di atas
+# environment:
+# name: production
+
+# deploy:production:
+# <<: *deploy_template
+# needs: ["build:production"]
+# rules:
+# - if: '$CI_COMMIT_BRANCH == "master"'
+# # - if: '$CI_COMMIT_TAG' # selaras dengan rule di build:production
+# variables:
+# S3_BUCKET: "lti-erp.mbugroup.id"
+# CLOUDFRONT_DISTRIBUTION_ID: "ddfd"
+# environment:
+# name: production
+# url: https://royalgoldcapital.com
- jq -n \
- --arg repo "$CI_PROJECT_PATH" \
- --arg mr "#${CI_MERGE_REQUEST_IID}" \
- --arg url "$MR_URL" \
- --arg requestor "${CI_MERGE_REQUEST_AUTHOR}" \
- --arg source "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" \
- --arg target "$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" \
- --arg title "$CI_MERGE_REQUEST_TITLE" \
- '{
- username: "CI Bot - FE",
- embeds: [{
- title: "✅ [LTI WEB CLIENT] Merge Request Merged",
- description: ($mr + " has been merged into " + $repo),
- url: $url,
- color: 3066993,
- fields: [
- {name: "Author", value: $requestor, inline: true},
- {name: "Source → Target", value: ($source + " → " + $target), inline: true},
- {name: "Title", value: $title}
- ]
- }]
- }' \
- | curl -sS -H "Content-Type: application/json" -d @- "$WEBHOOK_URL"
diff --git a/.husky/pre-commit b/.husky/pre-commit
index 66ff6a67..e7bb3165 100644
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1,2 +1,3 @@
+npm run format
npm run lint
npm run build
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..a3a2e197
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,25 @@
+FROM node:20-alpine
+
+RUN apk add --no-cache git bash build-base curl
+
+WORKDIR /app
+
+COPY package*.json ./
+RUN npm ci
+
+COPY . .
+
+# Buat config agar Next tahu output: export
+RUN echo "const config = { output: 'export', images: { unoptimized: true } }; export default config;" > next.config.mjs
+
+# Build project (Next.js 15 otomatis static export)
+RUN NEXT_DISABLE_TURBOPACK=1 npx next build
+
+# Copy static assets dan hasil build agar bisa diakses
+RUN mkdir -p .next/server/app/_next && \
+ cp -r .next/static .next/server/app/_next/static && \
+ cp -r public/* .next/server/app/
+
+EXPOSE 3000
+
+CMD ["npx", "serve", ".next/server/app", "-l", "3000"]
\ No newline at end of file
diff --git a/docker-compose.yaml b/docker-compose.yaml
new file mode 100644
index 00000000..b89f441b
--- /dev/null
+++ b/docker-compose.yaml
@@ -0,0 +1,39 @@
+version: '3.9'
+
+services:
+ dev-web-lti:
+ container_name: dev-web-lti
+ build:
+ context: .
+ dockerfile: Dockerfile
+ ports:
+ - '3002:3000'
+ env_file:
+ - .env
+ environment:
+ NODE_ENV: production
+ APP_ENV: production
+ networks:
+ - dev-lti-network
+ restart: always
+ deploy:
+ resources:
+ limits:
+ cpus: '3.0'
+ memory: 3G
+ reservations:
+ cpus: '1.0'
+ memory: 512M
+ extra_hosts:
+ - 'host.docker.internal:host-gateway'
+ # Optional: aktifkan healthcheck jika punya endpoint
+ # healthcheck:
+ # test: ["CMD-SHELL", "curl -fsS http://localhost:3000/api/healthz || exit 1"]
+ # interval: 10s
+ # timeout: 3s
+ # retries: 10
+ # start_period: 15s
+
+networks:
+ dev-lti-network:
+ external: true
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 719cea2b..fa167c8d 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -1,6 +1,6 @@
-import { dirname } from "path";
-import { fileURLToPath } from "url";
-import { FlatCompat } from "@eslint/eslintrc";
+import { dirname } from 'path';
+import { fileURLToPath } from 'url';
+import { FlatCompat } from '@eslint/eslintrc';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
@@ -10,14 +10,14 @@ const compat = new FlatCompat({
});
const eslintConfig = [
- ...compat.extends("next/core-web-vitals", "next/typescript"),
+ ...compat.extends('next/core-web-vitals', 'next/typescript'),
{
ignores: [
- "node_modules/**",
- ".next/**",
- "out/**",
- "build/**",
- "next-env.d.ts",
+ 'node_modules/**',
+ '.next/**',
+ 'out/**',
+ 'build/**',
+ 'next-env.d.ts',
],
},
];
diff --git a/package-lock.json b/package-lock.json
index e1f28d3e..fc5a5ebf 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,11 +13,12 @@
"axios": "^1.12.2",
"clsx": "^2.1.1",
"formik": "^2.4.6",
- "inputmask": "^5.0.9",
"moment": "^2.30.1",
"next": "15.5.3",
"react": "19.1.0",
+ "react-day-picker": "^9.11.1",
"react-dom": "19.1.0",
+ "react-dropzone": "^14.3.8",
"react-hot-toast": "^2.6.0",
"react-number-format": "^5.4.4",
"react-select": "^5.10.2",
@@ -31,7 +32,6 @@
"@eslint/eslintrc": "^3",
"@iconify/react": "^6.0.2",
"@tailwindcss/postcss": "^4",
- "@types/inputmask": "^5.0.7",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
@@ -39,6 +39,7 @@
"eslint": "^9",
"eslint-config-next": "15.5.3",
"husky": "^9.1.7",
+ "prettier": "^3.6.2",
"tailwindcss": "^4",
"typescript": "^5"
}
@@ -195,6 +196,12 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@date-fns/tz": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.4.1.tgz",
+ "integrity": "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==",
+ "license": "MIT"
+ },
"node_modules/@emnapi/core": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.6.0.tgz",
@@ -1638,13 +1645,6 @@
"@types/react": "*"
}
},
- "node_modules/@types/inputmask": {
- "version": "5.0.7",
- "resolved": "https://registry.npmjs.org/@types/inputmask/-/inputmask-5.0.7.tgz",
- "integrity": "sha512-uojbVPWzBQ/n/0jc/d16fLqmGasFIptbrLD2WrCPWArlk+5PgblOqH4EDkI3AoobXLAlOK5yF01V8jMmvMG5qg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -1680,6 +1680,7 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -1749,6 +1750,7 @@
"integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.46.2",
"@typescript-eslint/types": "8.46.2",
@@ -2266,6 +2268,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2516,6 +2519,15 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
+ "node_modules/attr-accept": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz",
+ "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@@ -2799,7 +2811,8 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/daisyui": {
"version": "5.3.10",
@@ -2872,6 +2885,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/date-fns": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
+ "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
+ },
+ "node_modules/date-fns-jalali": {
+ "version": "4.1.0-0",
+ "resolved": "https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz",
+ "integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==",
+ "license": "MIT"
+ },
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -3227,6 +3256,7 @@
"integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -3400,6 +3430,7 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -3720,6 +3751,18 @@
"node": ">=16.0.0"
}
},
+ "node_modules/file-selector": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz",
+ "integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.7.0"
+ },
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -4202,12 +4245,6 @@
"node": ">=0.8.19"
}
},
- "node_modules/inputmask": {
- "version": "5.0.9",
- "resolved": "https://registry.npmjs.org/inputmask/-/inputmask-5.0.9.tgz",
- "integrity": "sha512-s0lUfqcEbel+EQXtehXqwCJGShutgieOaIImFKC/r4reYNvX3foyrChl6LOEvaEgxEbesePIrw1Zi2jhZaDZbQ==",
- "license": "MIT"
- },
"node_modules/internal-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
@@ -4679,9 +4716,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5669,6 +5706,22 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/prettier": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -5728,15 +5781,38 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=0.10.0"
}
},
+ "node_modules/react-day-picker": {
+ "version": "9.11.1",
+ "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.11.1.tgz",
+ "integrity": "sha512-l3ub6o8NlchqIjPKrRFUCkTUEq6KwemQlfv3XZzzwpUeGwmDJ+0u0Upmt38hJyd7D/vn2dQoOoLV/qAp0o3uUw==",
+ "license": "MIT",
+ "dependencies": {
+ "@date-fns/tz": "^1.4.1",
+ "date-fns": "^4.1.0",
+ "date-fns-jalali": "^4.1.0-0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/gpbl"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
"node_modules/react-dom": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"scheduler": "^0.26.0"
},
@@ -5744,6 +5820,23 @@
"react": "^19.1.0"
}
},
+ "node_modules/react-dropzone": {
+ "version": "14.3.8",
+ "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.8.tgz",
+ "integrity": "sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==",
+ "license": "MIT",
+ "dependencies": {
+ "attr-accept": "^2.2.4",
+ "file-selector": "^2.1.0",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">= 10.13"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8 || 18.0.0"
+ }
+ },
"node_modules/react-fast-compare": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
@@ -6535,6 +6628,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -6702,6 +6796,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
diff --git a/package.json b/package.json
index b371e4e7..a6372994 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,8 @@
"build": "next build --turbopack",
"start": "next start",
"lint": "eslint",
- "prepare": "husky"
+ "prepare": "husky",
+ "format": "prettier --write ."
},
"dependencies": {
"@tanstack/match-sorter-utils": "^8.19.4",
@@ -15,11 +16,12 @@
"axios": "^1.12.2",
"clsx": "^2.1.1",
"formik": "^2.4.6",
- "inputmask": "^5.0.9",
"moment": "^2.30.1",
"next": "15.5.3",
"react": "19.1.0",
+ "react-day-picker": "^9.11.1",
"react-dom": "19.1.0",
+ "react-dropzone": "^14.3.8",
"react-hot-toast": "^2.6.0",
"react-number-format": "^5.4.4",
"react-select": "^5.10.2",
@@ -33,7 +35,6 @@
"@eslint/eslintrc": "^3",
"@iconify/react": "^6.0.2",
"@tailwindcss/postcss": "^4",
- "@types/inputmask": "^5.0.7",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
@@ -41,6 +42,7 @@
"eslint": "^9",
"eslint-config-next": "15.5.3",
"husky": "^9.1.7",
+ "prettier": "^3.6.2",
"tailwindcss": "^4",
"typescript": "^5"
}
diff --git a/postcss.config.mjs b/postcss.config.mjs
index c7bcb4b1..ba720fe5 100644
--- a/postcss.config.mjs
+++ b/postcss.config.mjs
@@ -1,5 +1,5 @@
const config = {
- plugins: ["@tailwindcss/postcss"],
+ plugins: ['@tailwindcss/postcss'],
};
export default config;
diff --git a/src/app/expense/add/page.tsx b/src/app/expense/add/page.tsx
new file mode 100644
index 00000000..afa40f48
--- /dev/null
+++ b/src/app/expense/add/page.tsx
@@ -0,0 +1,11 @@
+import ExpenseRequestForm from '@/components/pages/expense/form/ExpenseRequestForm';
+
+const AddExpense = () => {
+ return (
+
+
+
+ );
+};
+
+export default AddExpense;
diff --git a/src/app/expense/detail/edit/page.tsx b/src/app/expense/detail/edit/page.tsx
new file mode 100644
index 00000000..b37fdb8f
--- /dev/null
+++ b/src/app/expense/detail/edit/page.tsx
@@ -0,0 +1,61 @@
+'use client';
+
+import { useRouter, useSearchParams } from 'next/navigation';
+import useSWR from 'swr';
+
+import ExpenseRequestForm from '@/components/pages/expense/form/ExpenseRequestForm';
+
+import { ExpenseApi } from '@/services/api/expense';
+import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
+
+const ExpenseEditPage = () => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ const expenseId = searchParams.get('expenseId');
+
+ const { data: expense, isLoading: isLoadingExpense } = useSWR(
+ expenseId,
+ (id: number) => ExpenseApi.getSingle(id)
+ );
+
+ if (!expenseId) {
+ router.back();
+
+ return (
+
+
+
+ );
+ }
+
+ if (!isLoadingExpense && (!expense || isResponseError(expense))) {
+ router.replace('/404');
+ return;
+ }
+
+ const isExpenseRejectedOrApproved =
+ !isLoadingExpense &&
+ isResponseSuccess(expense) &&
+ (expense.data.approval.action === 'REJECTED' ||
+ expense.data.approval.step_number === 5);
+
+ if (isExpenseRejectedOrApproved) {
+ router.back();
+ return;
+ }
+
+ return (
+
+ {isLoadingExpense && (
+
+ )}
+
+ {!isLoadingExpense && isResponseSuccess(expense) && (
+
+ )}
+
+ );
+};
+
+export default ExpenseEditPage;
diff --git a/src/app/expense/detail/layout.tsx b/src/app/expense/detail/layout.tsx
new file mode 100644
index 00000000..7220dfa1
--- /dev/null
+++ b/src/app/expense/detail/layout.tsx
@@ -0,0 +1,11 @@
+import SuspenseHelper from '@/components/helper/SuspenseHelper';
+
+const Layout = ({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) => {
+ return {children};
+};
+
+export default Layout;
diff --git a/src/app/expense/detail/page.tsx b/src/app/expense/detail/page.tsx
new file mode 100644
index 00000000..a0d90f70
--- /dev/null
+++ b/src/app/expense/detail/page.tsx
@@ -0,0 +1,50 @@
+'use client';
+
+import { useRouter, useSearchParams } from 'next/navigation';
+import useSWR from 'swr';
+
+import ExpenseDetail from '@/components/pages/expense/ExpenseDetail';
+
+import { ExpenseApi } from '@/services/api/expense';
+import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
+
+const ExpenseDetailPage = () => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ const expenseId = searchParams.get('expenseId');
+
+ const { data: expense, isLoading: isLoadingExpense } = useSWR(
+ expenseId,
+ (id: number) => ExpenseApi.getSingle(id)
+ );
+
+ if (!expenseId) {
+ router.back();
+
+ return (
+
+
+
+ );
+ }
+
+ if (!isLoadingExpense && (!expense || isResponseError(expense))) {
+ router.replace('/404');
+ return;
+ }
+
+ return (
+
+ {isLoadingExpense && (
+
+ )}
+
+ {!isLoadingExpense && isResponseSuccess(expense) && (
+
+ )}
+
+ );
+};
+
+export default ExpenseDetailPage;
diff --git a/src/app/expense/page.tsx b/src/app/expense/page.tsx
new file mode 100644
index 00000000..d6b00286
--- /dev/null
+++ b/src/app/expense/page.tsx
@@ -0,0 +1,11 @@
+import ExpensesTable from '@/components/pages/expense/ExpensesTable';
+
+const Expense = () => {
+ return (
+
+ );
+};
+
+export default Expense;
diff --git a/src/app/globals.css b/src/app/globals.css
index 97be6978..e50e020d 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -3,10 +3,10 @@
@import '../styles/daisyui.css';
@plugin "daisyui/theme" {
- name: "lti";
+ name: 'lti';
default: false;
prefersdark: false;
- color-scheme: "light";
+ color-scheme: 'light';
--color-base-100: oklch(98% 0.001 106.423);
--color-base-200: oklch(97% 0.001 106.424);
--color-base-300: oklch(92% 0.003 48.717);
@@ -37,8 +37,6 @@
--noise: 0;
}
-
-
:root {
--color-primary: #1f74bf;
}
@@ -50,3 +48,8 @@
html {
scrollbar-gutter: initial;
}
+
+.react-select__menu-portal {
+ position: relative;
+ z-index: 99999 !important;
+}
diff --git a/src/app/inventory/adjustment/add/page.tsx b/src/app/inventory/adjustment/add/page.tsx
index 3bd64573..e20eedfc 100644
--- a/src/app/inventory/adjustment/add/page.tsx
+++ b/src/app/inventory/adjustment/add/page.tsx
@@ -1,11 +1,11 @@
-import InventoryAdjustmentForm from "@/components/pages/inventory/adjustment/form/InventoryAdjustmentForm";
+import InventoryAdjustmentForm from '@/components/pages/inventory/adjustment/form/InventoryAdjustmentForm';
const CreateInventoryAdjustment = () => {
return (
-
-
+
);
-}
+};
-export default CreateInventoryAdjustment;
\ No newline at end of file
+export default CreateInventoryAdjustment;
diff --git a/src/app/inventory/adjustment/detail/layout.tsx b/src/app/inventory/adjustment/detail/layout.tsx
index b41c70f9..7220dfa1 100644
--- a/src/app/inventory/adjustment/detail/layout.tsx
+++ b/src/app/inventory/adjustment/detail/layout.tsx
@@ -1,11 +1,11 @@
-import SuspenseHelper from "@/components/helper/SuspenseHelper"
+import SuspenseHelper from '@/components/helper/SuspenseHelper';
const Layout = ({
- children
+ children,
}: Readonly<{
- children: React.ReactNode
+ children: React.ReactNode;
}>) => {
- return {children}
-}
+ return {children};
+};
-export default Layout;
\ No newline at end of file
+export default Layout;
diff --git a/src/app/inventory/adjustment/detail/page.tsx b/src/app/inventory/adjustment/detail/page.tsx
index 5e96c86a..acb9f8db 100644
--- a/src/app/inventory/adjustment/detail/page.tsx
+++ b/src/app/inventory/adjustment/detail/page.tsx
@@ -7,11 +7,12 @@ import type { InventoryAdjustment } from '@/types/api/inventory/adjustment';
const DetailInventoryAdjustment = () => {
const router = useRouter();
- const [inventoryAdjustment, setInventoryAdjustment] = useState(null);
+ const [inventoryAdjustment, setInventoryAdjustment] =
+ useState(null);
// Ambil data dari router state
useEffect(() => {
- console.log("Router State");
+ console.log('Router State');
console.log(window.history.state);
const state = window.history.state?.usr as
| { inventoryAdjustment?: InventoryAdjustment }
@@ -24,20 +25,20 @@ const DetailInventoryAdjustment = () => {
}, [router]);
const finalData = inventoryAdjustment;
-
- console.log("Final Data");
+
+ console.log('Final Data');
console.log(finalData);
if (!finalData) {
return (
-
-
+
+
);
}
return (
-
+
);
diff --git a/src/app/marketing/sales-orders/add/page.tsx b/src/app/marketing/sales-orders/add/page.tsx
new file mode 100644
index 00000000..e60085ef
--- /dev/null
+++ b/src/app/marketing/sales-orders/add/page.tsx
@@ -0,0 +1,11 @@
+import SalesForm from '@/components/pages/marketing/sales-orders/form/SalesForm';
+
+const AddSalesOrder = () => {
+ return (
+
+
+
+ );
+};
+
+export default AddSalesOrder;
diff --git a/src/app/marketing/sales-orders/detail/edit/page.tsx b/src/app/marketing/sales-orders/detail/edit/page.tsx
new file mode 100644
index 00000000..86cafcb6
--- /dev/null
+++ b/src/app/marketing/sales-orders/detail/edit/page.tsx
@@ -0,0 +1,42 @@
+'use client';
+
+import SalesForm from '@/components/pages/marketing/sales-orders/form/SalesForm';
+import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
+import { MarketingApi } from '@/services/api/marketing/marketing';
+import { useRouter, useSearchParams } from 'next/navigation';
+import useSWR from 'swr';
+
+const EditSalesOrder = () => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ const soId = searchParams.get('salesOrderId');
+
+ const { data: marketing, isLoading: isLoading } = useSWR(soId, (id: number) =>
+ MarketingApi.getSingle(id)
+ );
+
+ if (!soId) {
+ router.back();
+
+ return (
+
+
+
+ );
+ }
+
+ if (!isLoading && (!marketing || isResponseError(marketing))) {
+ router.replace('/404');
+ return;
+ }
+ return (
+
+ {isLoading && }
+ {!isLoading && isResponseSuccess(marketing) && (
+
+ )}
+
+ );
+};
+export default EditSalesOrder;
diff --git a/src/app/marketing/sales-orders/detail/layout.tsx b/src/app/marketing/sales-orders/detail/layout.tsx
new file mode 100644
index 00000000..7220dfa1
--- /dev/null
+++ b/src/app/marketing/sales-orders/detail/layout.tsx
@@ -0,0 +1,11 @@
+import SuspenseHelper from '@/components/helper/SuspenseHelper';
+
+const Layout = ({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) => {
+ return {children};
+};
+
+export default Layout;
diff --git a/src/app/marketing/sales-orders/detail/page.tsx b/src/app/marketing/sales-orders/detail/page.tsx
new file mode 100644
index 00000000..22d2651c
--- /dev/null
+++ b/src/app/marketing/sales-orders/detail/page.tsx
@@ -0,0 +1,44 @@
+'use client';
+
+import SalesOrderDetail from '@/components/pages/marketing/sales-orders/detail/SalesOrderDetail';
+import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
+import { MarketingApi } from '@/services/api/marketing/marketing';
+import { useRouter, useSearchParams } from 'next/navigation';
+import useSWR from 'swr';
+
+const DetailSalesOrder = () => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ const soId = searchParams.get('salesOrderId');
+
+ const { data: marketing, isLoading: isLoading } = useSWR(soId, (id: number) =>
+ MarketingApi.getSingle(id)
+ );
+
+ if (!soId) {
+ router.back();
+
+ return (
+
+
+
+ );
+ }
+
+ if (!isLoading && (!marketing || isResponseError(marketing))) {
+ router.replace('/404');
+ return;
+ }
+
+ return (
+
+ {isLoading && }
+ {!isLoading && isResponseSuccess(marketing) && (
+
+ )}
+
+ );
+};
+
+export default DetailSalesOrder;
diff --git a/src/app/marketing/sales-orders/page.tsx b/src/app/marketing/sales-orders/page.tsx
new file mode 100644
index 00000000..3494b6a1
--- /dev/null
+++ b/src/app/marketing/sales-orders/page.tsx
@@ -0,0 +1,10 @@
+import SalesOrderTable from '@/components/pages/marketing/sales-orders/SalesOrderTable';
+
+const SalesOrder = () => {
+ return (
+
+
+
+ );
+};
+export default SalesOrder;
diff --git a/src/app/master-data/customer/add/page.tsx b/src/app/master-data/customer/add/page.tsx
index a1096f02..dd75c679 100644
--- a/src/app/master-data/customer/add/page.tsx
+++ b/src/app/master-data/customer/add/page.tsx
@@ -1,11 +1,11 @@
-import CustomerForm from "@/components/pages/master-data/customer/form/CustomerForm";
+import CustomerForm from '@/components/pages/master-data/customer/form/CustomerForm';
const AddCustomer = () => {
return (
-
-
+
);
-}
+};
-export default AddCustomer;
\ No newline at end of file
+export default AddCustomer;
diff --git a/src/app/master-data/customer/detail/page.tsx b/src/app/master-data/customer/detail/page.tsx
index 263458c2..d778f83b 100644
--- a/src/app/master-data/customer/detail/page.tsx
+++ b/src/app/master-data/customer/detail/page.tsx
@@ -1,45 +1,47 @@
-'use client'
+'use client';
-import { useRouter, useSearchParams } from "next/navigation";
-import useSWR from "swr";
+import { useRouter, useSearchParams } from 'next/navigation';
+import useSWR from 'swr';
import { CustomerApi } from '@/services/api/master-data';
-import { isResponseError, isResponseSuccess } from "@/lib/api-helper";
-import CustomerForm from "@/components/pages/master-data/customer/form/CustomerForm";
+import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
+import CustomerForm from '@/components/pages/master-data/customer/form/CustomerForm';
const CustomerDetail = () => {
const router = useRouter();
const searchParams = useSearchParams();
- const costumerId = searchParams.get("customerId");
+ const costumerId = searchParams.get('customerId');
const { data: costumer, isLoading: isLoadingCostumer } = useSWR(
costumerId,
(id: number) => CustomerApi.getSingle(id)
);
- if(!costumerId){
+ if (!costumerId) {
router.back();
return (
-
-
+
+
);
}
- if(!isLoadingCostumer && (!costumer || isResponseError(costumer))){
- router.replace("/404");
+ if (!isLoadingCostumer && (!costumer || isResponseError(costumer))) {
+ router.replace('/404');
return;
}
return (
-
- {isLoadingCostumer &&
}
+
+ {isLoadingCostumer && (
+
+ )}
{!isLoadingCostumer && isResponseSuccess(costumer) && (
-
+
)}
- )
+ );
};
export default CustomerDetail;
diff --git a/src/app/master-data/customer/page.tsx b/src/app/master-data/customer/page.tsx
index b80401f1..8aec1088 100644
--- a/src/app/master-data/customer/page.tsx
+++ b/src/app/master-data/customer/page.tsx
@@ -1,11 +1,11 @@
-import CustomersTable from "@/components/pages/master-data/customer/CustomersTable";
+import CustomersTable from '@/components/pages/master-data/customer/CustomersTable';
const Customer = () => {
return (
-
+
- )
+ );
};
-export default Customer;
\ No newline at end of file
+export default Customer;
diff --git a/src/app/master-data/flock/add/page.tsx b/src/app/master-data/flock/add/page.tsx
index 5ee3958e..d038d414 100644
--- a/src/app/master-data/flock/add/page.tsx
+++ b/src/app/master-data/flock/add/page.tsx
@@ -1,11 +1,11 @@
-import FlockForm from "@/components/pages/master-data/flock/form/FlockForm";
+import FlockForm from '@/components/pages/master-data/flock/form/FlockForm';
const AddFlock = () => {
return (
-
+
);
-}
+};
export default AddFlock;
diff --git a/src/app/master-data/flock/detail/edit/page.tsx b/src/app/master-data/flock/detail/edit/page.tsx
index c9651727..babc6653 100644
--- a/src/app/master-data/flock/detail/edit/page.tsx
+++ b/src/app/master-data/flock/detail/edit/page.tsx
@@ -1,10 +1,10 @@
-'use client'
+'use client';
-import FlockForm from "@/components/pages/master-data/flock/form/FlockForm";
-import { isResponseError, isResponseSuccess } from "@/lib/api-helper";
-import { FlockApi } from "@/services/api/master-data";
-import { useRouter, useSearchParams } from "next/navigation";
-import useSWR from "swr";
+import FlockForm from '@/components/pages/master-data/flock/form/FlockForm';
+import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
+import { FlockApi } from '@/services/api/master-data';
+import { useRouter, useSearchParams } from 'next/navigation';
+import useSWR from 'swr';
const FlockEdit = () => {
const router = useRouter();
@@ -44,6 +44,6 @@ const FlockEdit = () => {
)}
);
-}
+};
-export default FlockEdit;
\ No newline at end of file
+export default FlockEdit;
diff --git a/src/app/master-data/flock/detail/layout.tsx b/src/app/master-data/flock/detail/layout.tsx
index b41c70f9..7220dfa1 100644
--- a/src/app/master-data/flock/detail/layout.tsx
+++ b/src/app/master-data/flock/detail/layout.tsx
@@ -1,11 +1,11 @@
-import SuspenseHelper from "@/components/helper/SuspenseHelper"
+import SuspenseHelper from '@/components/helper/SuspenseHelper';
const Layout = ({
- children
+ children,
}: Readonly<{
- children: React.ReactNode
+ children: React.ReactNode;
}>) => {
- return
{children}
-}
+ return
{children};
+};
-export default Layout;
\ No newline at end of file
+export default Layout;
diff --git a/src/app/master-data/flock/detail/page.tsx b/src/app/master-data/flock/detail/page.tsx
index 8a805911..e9620d33 100644
--- a/src/app/master-data/flock/detail/page.tsx
+++ b/src/app/master-data/flock/detail/page.tsx
@@ -1,10 +1,10 @@
-'use client'
+'use client';
-import FlockForm from "@/components/pages/master-data/flock/form/FlockForm";
-import { isResponseError, isResponseSuccess } from "@/lib/api-helper";
-import { FlockApi } from "@/services/api/master-data";
-import { useRouter, useSearchParams } from "next/navigation";
-import useSWR from "swr";
+import FlockForm from '@/components/pages/master-data/flock/form/FlockForm';
+import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
+import { FlockApi } from '@/services/api/master-data';
+import { useRouter, useSearchParams } from 'next/navigation';
+import useSWR from 'swr';
const FlockDetail = () => {
const router = useRouter();
@@ -14,33 +14,36 @@ const FlockDetail = () => {
const flockId = searchParams.get('flockId');
// Fetch Data
- const { data: flock, isLoading: isLoadingFlock } = useSWR(flockId, (id: number) => FlockApi.getSingle(id));
+ const { data: flock, isLoading: isLoadingFlock } = useSWR(
+ flockId,
+ (id: number) => FlockApi.getSingle(id)
+ );
- if(!flockId){
+ if (!flockId) {
router.back();
return (
-
-
+
+
);
}
- if(!isLoadingFlock && (!flock || isResponseError(flock))){
+ if (!isLoadingFlock && (!flock || isResponseError(flock))) {
router.replace('/404');
return;
}
return (
-
+
{isLoadingFlock && (
-
+
)}
{!isLoadingFlock && isResponseSuccess(flock) && (
-
+
)}
);
-}
+};
-export default FlockDetail;
\ No newline at end of file
+export default FlockDetail;
diff --git a/src/app/master-data/flock/page.tsx b/src/app/master-data/flock/page.tsx
index b317091a..76cc32c1 100644
--- a/src/app/master-data/flock/page.tsx
+++ b/src/app/master-data/flock/page.tsx
@@ -1,11 +1,11 @@
-import FlockTable from "@/components/pages/master-data/flock/FlocksTable";
+import FlockTable from '@/components/pages/master-data/flock/FlocksTable';
const Flock = () => {
return (
-
-
+
- );
-}
+ );
+};
export default Flock;
diff --git a/src/app/master-data/product-category/add/page.tsx b/src/app/master-data/product-category/add/page.tsx
index 0993ba7a..2331159e 100644
--- a/src/app/master-data/product-category/add/page.tsx
+++ b/src/app/master-data/product-category/add/page.tsx
@@ -1,11 +1,11 @@
-import ProductCategoryForm from "@/components/pages/master-data/product-category/form/ProductCategoryForm";
+import ProductCategoryForm from '@/components/pages/master-data/product-category/form/ProductCategoryForm';
const AddProductCategory = () => {
return (
-
+
);
};
-export default AddProductCategory;
\ No newline at end of file
+export default AddProductCategory;
diff --git a/src/app/master-data/product-category/detail/edit/page.tsx b/src/app/master-data/product-category/detail/edit/page.tsx
index 6bc10644..4cb7eb5a 100644
--- a/src/app/master-data/product-category/detail/edit/page.tsx
+++ b/src/app/master-data/product-category/detail/edit/page.tsx
@@ -9,39 +9,44 @@ import { ProductCategoryApi } from '@/services/api/master-data';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
const ProductCategoryEdit = () => {
- const router = useRouter();
- const searchParams = useSearchParams();
+ const router = useRouter();
+ const searchParams = useSearchParams();
- const productCategoryId = searchParams.get('productCategoryId');
+ const productCategoryId = searchParams.get('productCategoryId');
- const { data: productCategory, isLoading: isLoadingProductCategory } = useSWR(
- productCategoryId,
- (id: number) => ProductCategoryApi.getSingle(id)
- );
+ const { data: productCategory, isLoading: isLoadingProductCategory } = useSWR(
+ productCategoryId,
+ (id: number) => ProductCategoryApi.getSingle(id)
+ );
- if (!productCategoryId) {
- router.back();
-
- return (
-
-
-
- );
- }
-
- if (!isLoadingProductCategory && (!productCategory || isResponseError(productCategory))) {
- router.replace('/404');
- return;
- }
+ if (!productCategoryId) {
+ router.back();
return (
-
- {isLoadingProductCategory &&
}
- {!isLoadingProductCategory && isResponseSuccess(productCategory) && (
-
- )}
-
+
+
+
);
-}
+ }
-export default ProductCategoryEdit;
\ No newline at end of file
+ if (
+ !isLoadingProductCategory &&
+ (!productCategory || isResponseError(productCategory))
+ ) {
+ router.replace('/404');
+ return;
+ }
+
+ return (
+
+ {isLoadingProductCategory && (
+
+ )}
+ {!isLoadingProductCategory && isResponseSuccess(productCategory) && (
+
+ )}
+
+ );
+};
+
+export default ProductCategoryEdit;
diff --git a/src/app/master-data/product-category/detail/page.tsx b/src/app/master-data/product-category/detail/page.tsx
index cba06fdb..c1a21aaf 100644
--- a/src/app/master-data/product-category/detail/page.tsx
+++ b/src/app/master-data/product-category/detail/page.tsx
@@ -29,16 +29,24 @@ const ProductCategoryDetail = () => {
);
}
- if (!isLoadingProductCategory && (!productCategory || isResponseError(productCategory))) {
+ if (
+ !isLoadingProductCategory &&
+ (!productCategory || isResponseError(productCategory))
+ ) {
router.replace('/404');
return;
}
return (
- {isLoadingProductCategory &&
}
+ {isLoadingProductCategory && (
+
+ )}
{!isLoadingProductCategory && isResponseSuccess(productCategory) && (
-
+
)}
);
diff --git a/src/app/master-data/product-category/page.tsx b/src/app/master-data/product-category/page.tsx
index 5ec6d555..78a4fda3 100644
--- a/src/app/master-data/product-category/page.tsx
+++ b/src/app/master-data/product-category/page.tsx
@@ -1,11 +1,11 @@
-import ProductCategoryTable from "@/components/pages/master-data/product-category/ProductCategoryTable";
+import ProductCategoryTable from '@/components/pages/master-data/product-category/ProductCategoryTable';
const ProductCategory = () => {
return (
-
+
);
};
-export default ProductCategory;
\ No newline at end of file
+export default ProductCategory;
diff --git a/src/app/master-data/product/add/page.tsx b/src/app/master-data/product/add/page.tsx
index 7cc995b6..37f42691 100644
--- a/src/app/master-data/product/add/page.tsx
+++ b/src/app/master-data/product/add/page.tsx
@@ -2,10 +2,10 @@ import ProductForm from '@/components/pages/master-data/product/form/ProductForm
const AddProduct = () => {
return (
-
+
);
};
-export default AddProduct;
\ No newline at end of file
+export default AddProduct;
diff --git a/src/app/master-data/product/detail/edit/page.tsx b/src/app/master-data/product/detail/edit/page.tsx
index 96cfdc42..8916a98e 100644
--- a/src/app/master-data/product/detail/edit/page.tsx
+++ b/src/app/master-data/product/detail/edit/page.tsx
@@ -13,9 +13,8 @@ const ProductEdit = () => {
const productId = searchParams.get('productId');
- const { data: product, isLoading } = useSWR(
- productId,
- (id: number) => ProductApi.getSingle(id)
+ const { data: product, isLoading } = useSWR(productId, (id: number) =>
+ ProductApi.getSingle(id)
);
if (!productId) {
@@ -42,4 +41,4 @@ const ProductEdit = () => {
);
};
-export default ProductEdit;
\ No newline at end of file
+export default ProductEdit;
diff --git a/src/app/master-data/product/detail/page.tsx b/src/app/master-data/product/detail/page.tsx
index 916a44d0..34743e1f 100644
--- a/src/app/master-data/product/detail/page.tsx
+++ b/src/app/master-data/product/detail/page.tsx
@@ -13,9 +13,8 @@ const ProductDetail = () => {
const productId = searchParams.get('productId');
- const { data: product, isLoading } = useSWR(
- productId,
- (id: number) => ProductApi.getSingle(id)
+ const { data: product, isLoading } = useSWR(productId, (id: number) =>
+ ProductApi.getSingle(id)
);
if (!productId) {
@@ -42,4 +41,4 @@ const ProductDetail = () => {
);
};
-export default ProductDetail;
\ No newline at end of file
+export default ProductDetail;
diff --git a/src/app/master-data/product/page.tsx b/src/app/master-data/product/page.tsx
index 6014aeb9..a385d411 100644
--- a/src/app/master-data/product/page.tsx
+++ b/src/app/master-data/product/page.tsx
@@ -1,11 +1,11 @@
-import ProductsTable from "@/components/pages/master-data/product/ProductTable";
+import ProductsTable from '@/components/pages/master-data/product/ProductTable';
const Product = () => {
return (
-
-
+
);
};
-export default Product;
\ No newline at end of file
+export default Product;
diff --git a/src/app/master-data/supplier/add/page.tsx b/src/app/master-data/supplier/add/page.tsx
index 8a95c3c6..37df33b0 100644
--- a/src/app/master-data/supplier/add/page.tsx
+++ b/src/app/master-data/supplier/add/page.tsx
@@ -8,4 +8,4 @@ const AddSupplier = () => {
);
};
-export default AddSupplier;
\ No newline at end of file
+export default AddSupplier;
diff --git a/src/app/master-data/supplier/detail/page.tsx b/src/app/master-data/supplier/detail/page.tsx
index 433fa043..a34ad72e 100644
--- a/src/app/master-data/supplier/detail/page.tsx
+++ b/src/app/master-data/supplier/detail/page.tsx
@@ -46,4 +46,4 @@ const SupplierDetail = () => {
);
};
-export default SupplierDetail;
\ No newline at end of file
+export default SupplierDetail;
diff --git a/src/app/master-data/supplier/page.tsx b/src/app/master-data/supplier/page.tsx
index 1f54bd0d..8000be0a 100644
--- a/src/app/master-data/supplier/page.tsx
+++ b/src/app/master-data/supplier/page.tsx
@@ -1,4 +1,4 @@
-import SuppliersTable from "@/components/pages/master-data/supplier/SupplierTable";
+import SuppliersTable from '@/components/pages/master-data/supplier/SupplierTable';
const Supplier = () => {
return (
diff --git a/src/app/production/chickin/add/layout.tsx b/src/app/production/chickin/add/layout.tsx
deleted file mode 100644
index b41c70f9..00000000
--- a/src/app/production/chickin/add/layout.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import SuspenseHelper from "@/components/helper/SuspenseHelper"
-
-const Layout = ({
- children
-}: Readonly<{
- children: React.ReactNode
-}>) => {
- return {children}
-}
-
-export default Layout;
\ No newline at end of file
diff --git a/src/app/production/chickin/add/page.tsx b/src/app/production/chickin/add/page.tsx
deleted file mode 100644
index 3ef73396..00000000
--- a/src/app/production/chickin/add/page.tsx
+++ /dev/null
@@ -1,270 +0,0 @@
-'use client';
-
-import Button from '@/components/Button';
-import SelectInput, { OptionType } from '@/components/input/SelectInput';
-import Modal, { useModal } from '@/components/Modal';
-import ConfirmationModal from '@/components/modal/ConfirmationModal';
-import ChickinForm from '@/components/pages/production/chickin/form/ChickinForm';
-import Table from '@/components/Table';
-import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
-import { cn } from '@/lib/helper';
-import { ProjectFlockApi } from '@/services/api/production';
-import { useTableFilter } from '@/services/hooks/useTableFilter';
-import { BaseApiResponse } from '@/types/api/api-general';
-import { Kandang } from '@/types/api/master-data/kandang';
-import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
-import { Icon } from '@iconify/react';
-import { useRouter, useSearchParams } from 'next/navigation';
-import { useState } from 'react';
-
-import useSWR from 'swr';
-
-const AddChickin = () => {
- const router = useRouter();
- const searchParams = useSearchParams();
- const projectFlockId = searchParams.get('projectFlockId');
-
- // Tables Props
- const { state: tableFilterState } = useTableFilter({
- initial: { search: '' },
- paramMap: { page: 'page', pageSize: 'limit' },
- });
-
- // States
- const [selectedKandang, setSelectedKandang] = useState(
- undefined
- );
- const [projectFlockKandang, setProjectFlockKandang] =
- useState>();
- const [isLoadingProjectFlockKandang, setIsLoadingProjectFlockKandang] =
- useState(false);
- const [searchProjectFlock, setSearchProjectFlock] = useState('');
-
- // Fetch Data
- const { data: projectFlock, isLoading: isLoadingProjectFlock } = useSWR(
- projectFlockId,
- (id: number) => ProjectFlockApi.getSingle(id)
- );
- const { data: listProjectFlock, isLoading: isLoadingListProjectFlock } =
- useSWR(
- `${ProjectFlockApi.basePath}?${new URLSearchParams({
- search: searchProjectFlock,
- }).toString()}`,
- ProjectFlockApi.getAllFetcher
- );
-
- const getProjectFlockKandangUrl = `/kandangs/lookup`;
- // Mapping Options
- const options = isResponseSuccess(listProjectFlock)
- ? listProjectFlock?.data.map((projectFlock) => {
- return {
- value: projectFlock.id,
- label: `${projectFlock?.flock?.name} - ${projectFlock?.category} - Periode ${projectFlock.period}`,
- };
- })
- : [];
-
- const chickinModal = useModal();
- const alertModal = useModal();
-
- if (!projectFlockId) {
- router.back();
-
- return (
-
-
-
- );
- }
-
- if (
- !isLoadingProjectFlock &&
- (!projectFlock || isResponseError(projectFlock))
- ) {
- router.replace('/404');
- return;
- }
-
- // Handle Function
- const handleChickinClick = async (kandang: Kandang) => {
- setIsLoadingProjectFlockKandang(true);
- setSelectedKandang(kandang);
- const ProjectFlockKandangRes = await ProjectFlockApi.customRequest<
- BaseApiResponse,
- 'GET'
- >(getProjectFlockKandangUrl, {
- method: 'GET',
- params: {
- project_flock_id: projectFlockId ?? 0,
- kandang_id: kandang.id,
- },
- });
- if (isResponseSuccess(ProjectFlockKandangRes)) {
- setProjectFlockKandang(ProjectFlockKandangRes);
- setIsLoadingProjectFlockKandang(false);
- if (
- ProjectFlockKandangRes.data.available_quantity &&
- ProjectFlockKandangRes.data.available_quantity > 0
- ) {
- chickinModal.openModal();
- } else {
- alertModal.openModal();
- }
- }
- };
- const handleAfterSubmit = () => {
- chickinModal.closeModal();
- router.push('/production/chickin');
- };
-
- return (
- <>
- {isResponseSuccess(projectFlock) && (
- <>
-
-
-
- data={projectFlock.data?.kandangs}
- columns={[
- {
- header: '#',
- cell: (props) =>
- tableFilterState.pageSize * (tableFilterState.page - 1) +
- props.row.index +
- 1,
- },
- {
- accessorKey: 'name',
- header: 'Nama Kandang',
- },
- {
- header: 'Aksi',
- cell: (props) => {
- return (
- <>
-
- >
- );
- },
- },
- ]}
- page={undefined}
- className={{
- containerClassName: cn({
- 'mb-20':
- isResponseSuccess(projectFlock) &&
- projectFlock.data?.kandangs?.length === 0,
- }),
- tableWrapperClassName: 'overflow-x-auto min-h-full!',
- tableClassName: 'font-inter w-full table-auto min-h-full!',
- headerRowClassName: 'border-b border-b-gray-200',
- headerColumnClassName:
- 'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
- bodyRowClassName: 'border-b border-b-gray-200',
- bodyColumnClassName:
- 'px-6 py-3 last:flex last:flex-row last:justify-end',
- paginationClassName: 'hidden',
- }}
- />
-
-
-
-
- Chickin Kandang - {selectedKandang?.name}
-
-
-
- {isResponseSuccess(projectFlockKandang) &&
- !isLoadingProjectFlockKandang && (
-
- )}
-
- {
- alertModal.closeModal();
- },
- }}
- />
- >
- )}
- >
- );
-};
-
-export default AddChickin;
diff --git a/src/app/production/chickin/detail/layout.tsx b/src/app/production/chickin/detail/layout.tsx
deleted file mode 100644
index b41c70f9..00000000
--- a/src/app/production/chickin/detail/layout.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import SuspenseHelper from "@/components/helper/SuspenseHelper"
-
-const Layout = ({
- children
-}: Readonly<{
- children: React.ReactNode
-}>) => {
- return {children}
-}
-
-export default Layout;
\ No newline at end of file
diff --git a/src/app/production/chickin/page.tsx b/src/app/production/chickin/page.tsx
deleted file mode 100644
index ad662f65..00000000
--- a/src/app/production/chickin/page.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import ChickinTable from "@/components/pages/production/chickin/ChickinTable";
-
-const Chickin = () => {
- return (
-
- );
-}
-export default Chickin;
\ No newline at end of file
diff --git a/src/app/production/project-flock/add/page.tsx b/src/app/production/project-flock/add/page.tsx
index 60141d80..b323b5f3 100644
--- a/src/app/production/project-flock/add/page.tsx
+++ b/src/app/production/project-flock/add/page.tsx
@@ -1,13 +1,13 @@
-'use client'
+'use client';
-import ProjectFlockForm from "@/components/pages/production/project-flock/form/ProjectFlockForm";
+import ProjectFlockForm from '@/components/pages/production/project-flock/form/ProjectFlockForm';
const AddProjectFlock = () => {
return (
-
-
+
);
-}
+};
-export default AddProjectFlock;
\ No newline at end of file
+export default AddProjectFlock;
diff --git a/src/app/production/project-flock/chickin/add/kandang/layout.tsx b/src/app/production/project-flock/chickin/add/kandang/layout.tsx
new file mode 100644
index 00000000..7220dfa1
--- /dev/null
+++ b/src/app/production/project-flock/chickin/add/kandang/layout.tsx
@@ -0,0 +1,11 @@
+import SuspenseHelper from '@/components/helper/SuspenseHelper';
+
+const Layout = ({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) => {
+ return {children};
+};
+
+export default Layout;
diff --git a/src/app/production/project-flock/chickin/add/kandang/page.tsx b/src/app/production/project-flock/chickin/add/kandang/page.tsx
new file mode 100644
index 00000000..a22039d1
--- /dev/null
+++ b/src/app/production/project-flock/chickin/add/kandang/page.tsx
@@ -0,0 +1,60 @@
+'use client';
+
+import ChickinForm from '@/components/pages/production/chickin/form/ChickinForm';
+import { isResponseSuccess } from '@/lib/api-helper';
+import { ProjectFlockKandangApi } from '@/services/api/production';
+import { useRouter, useSearchParams } from 'next/navigation';
+import useSWR from 'swr';
+
+export default function AddChickinKandang() {
+ const searchParams = useSearchParams();
+ const projectFlockKandangId = searchParams.get('projectFlockKandangId');
+ const projectFlockId = searchParams.get('projectFlockId');
+ const router = useRouter();
+
+ const {
+ data: projectFlockKandang,
+ isLoading: isLoading,
+ mutate: refreshProjectFlockKandang,
+ } = useSWR(
+ `get-single-project-flock-kandang/${projectFlockKandangId}`,
+ async () =>
+ ProjectFlockKandangApi.getSingle(
+ parseInt(projectFlockKandangId as string)
+ )
+ );
+
+ if (!projectFlockKandangId) {
+ router.push(`/production/chickin/add?projectFlockId=${projectFlockId}`);
+ return (
+
+
+
+ );
+ }
+
+ if (!isLoading && !projectFlockKandang) {
+ router.replace('/404');
+ return;
+ }
+
+ const handleAfterSubmit = () => {
+ refreshProjectFlockKandang();
+ };
+
+ return (
+ <>
+
+ {isLoading && }
+ {!isLoading &&
+ isResponseSuccess(projectFlockKandang) &&
+ projectFlockId && (
+
+ )}
+
+ >
+ );
+}
diff --git a/src/app/production/project-flock/chickin/add/layout.tsx b/src/app/production/project-flock/chickin/add/layout.tsx
new file mode 100644
index 00000000..7220dfa1
--- /dev/null
+++ b/src/app/production/project-flock/chickin/add/layout.tsx
@@ -0,0 +1,11 @@
+import SuspenseHelper from '@/components/helper/SuspenseHelper';
+
+const Layout = ({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) => {
+ return {children};
+};
+
+export default Layout;
diff --git a/src/app/production/project-flock/chickin/add/page.tsx b/src/app/production/project-flock/chickin/add/page.tsx
new file mode 100644
index 00000000..3ca09c89
--- /dev/null
+++ b/src/app/production/project-flock/chickin/add/page.tsx
@@ -0,0 +1,24 @@
+'use client';
+
+import { FormHeader } from '@/components/helper/form/FormHeader';
+import ProjectFlockChickinDetail from '@/components/pages/production/project-flock/chickin/ProjectFlockChickinDetail';
+import { useSearchParams } from 'next/navigation';
+
+const AddChickin = () => {
+ const searchParams = useSearchParams();
+ const projectFlockId = searchParams.get('projectFlockId');
+
+ return (
+ <>
+
+ >
+ );
+};
+
+export default AddChickin;
diff --git a/src/app/production/project-flock/chickin/detail/layout.tsx b/src/app/production/project-flock/chickin/detail/layout.tsx
new file mode 100644
index 00000000..7220dfa1
--- /dev/null
+++ b/src/app/production/project-flock/chickin/detail/layout.tsx
@@ -0,0 +1,11 @@
+import SuspenseHelper from '@/components/helper/SuspenseHelper';
+
+const Layout = ({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) => {
+ return {children};
+};
+
+export default Layout;
diff --git a/src/app/production/chickin/detail/page.tsx b/src/app/production/project-flock/chickin/detail/page.tsx
similarity index 92%
rename from src/app/production/chickin/detail/page.tsx
rename to src/app/production/project-flock/chickin/detail/page.tsx
index 96647c55..daea0f0a 100644
--- a/src/app/production/chickin/detail/page.tsx
+++ b/src/app/production/project-flock/chickin/detail/page.tsx
@@ -6,7 +6,7 @@ import Modal, { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
import ChickinForm from '@/components/pages/production/chickin/form/ChickinForm';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
-import { ChickinApi } from '@/services/api/production';
+import { ChickinApi } from '@/services/api/production/chickin';
import { BaseApiResponse } from '@/types/api/api-general';
import {
Chickin,
@@ -20,7 +20,7 @@ import useSWR from 'swr';
/**
* TODO: Refactor code - pindahin detail ke reuseable component
- * setelah implement approval and reject
+ * setelah implement approval and reject
*/
const DetailChickin = () => {
@@ -43,9 +43,8 @@ const DetailChickin = () => {
// chickin.data?.approval.step_number == 1 ? false : true
true
);
- const [isRejectedDisabled, setIsRejectedDisabled] = useState(
- !isApprovedDisabled
- );
+ const [isRejectedDisabled, setIsRejectedDisabled] =
+ useState(!isApprovedDisabled);
const [approvalAction, setApprovalAction] = useState<'APPROVED' | 'REJECTED'>(
!isApprovedDisabled ? 'APPROVED' : 'REJECTED'
);
@@ -171,8 +170,8 @@ const DetailChickin = () => {
Flock
{
- chickin.data.project_flock_kandang?.project_flock.flock
- .name
+ chickin?.data?.project_flock_kandang?.project_flock?.flock
+ ?.name
}
@@ -226,8 +225,8 @@ const DetailChickin = () => {
Flock Kandang
{
- chickin.data.project_flock_kandang?.project_flock.flock
- .name
+ chickin?.data?.project_flock_kandang?.project_flock?.flock
+ ?.name
}{' '}
- {chickin.data.project_flock_kandang?.kandang.name}
@@ -264,7 +263,8 @@ const DetailChickin = () => {
Delete
-
- {
- refreshChickin();
- chickinModal.closeModal();
- }}
- />
{
text={`Apakah anda yakin ingin ${
approvalAction == 'APPROVED' ? 'approve' : 'reject'
} chickin berikut? (${
- chickin?.data.project_flock_kandang?.project_flock.flock.name
+ chickin?.data?.project_flock_kandang?.project_flock?.flock?.name
} - ${chickin?.data.project_flock_kandang?.kandang.name})?`}
secondaryButton={{
text: 'Tidak',
diff --git a/src/app/production/project-flock/chickin/page.tsx b/src/app/production/project-flock/chickin/page.tsx
new file mode 100644
index 00000000..5d105aab
--- /dev/null
+++ b/src/app/production/project-flock/chickin/page.tsx
@@ -0,0 +1,10 @@
+import ChickinTable from '@/components/pages/production/chickin/ChickinTable';
+
+const Chickin = () => {
+ return (
+
+ );
+};
+export default Chickin;
diff --git a/src/app/production/project-flock/detail/edit/page.tsx b/src/app/production/project-flock/detail/edit/page.tsx
index 858d0ca8..f55ce601 100644
--- a/src/app/production/project-flock/detail/edit/page.tsx
+++ b/src/app/production/project-flock/detail/edit/page.tsx
@@ -1,46 +1,51 @@
-'use client'
+'use client';
-
-import ProjectFlockForm from "@/components/pages/production/project-flock/form/ProjectFlockForm";
-import { isResponseError, isResponseSuccess } from "@/lib/api-helper";
-import { ProjectFlockApi } from "@/services/api/production";
-import { useRouter, useSearchParams } from "next/navigation";
-import useSWR from "swr";
+import ProjectFlockForm from '@/components/pages/production/project-flock/form/ProjectFlockForm';
+import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
+import { ProjectFlockApi } from '@/services/api/production/project-flock';
+import { useRouter, useSearchParams } from 'next/navigation';
+import useSWR from 'swr';
const ProjectFlockEdit = () => {
const router = useRouter();
const searchParams = useSearchParams();
- const projectFlockId = searchParams.get("projectFlockId");
+ const projectFlockId = searchParams.get('projectFlockId');
- const { data: projectFlock, isLoading: isLoadingCostumer } = useSWR(
- projectFlockId,
- (id: number) => ProjectFlockApi.getSingle(id)
- );
+ const {
+ data: projectFlock,
+ isLoading: isLoadingProjectFlock,
+ mutate: refreshProjectFlocks,
+ } = useSWR(projectFlockId, (id: number) => ProjectFlockApi.getSingle(id));
- if(!projectFlockId){
+ if (!projectFlockId) {
router.back();
return (
-
-
+
+
);
}
- if(!isLoadingCostumer && (!projectFlock || isResponseError(projectFlock))){
- router.replace("/404");
+ if (
+ !isLoadingProjectFlock &&
+ (!projectFlock || isResponseError(projectFlock))
+ ) {
+ router.replace('/404');
return;
}
return (
-
- {isLoadingCostumer &&
}
- {!isLoadingCostumer && isResponseSuccess(projectFlock) && (
-
+
+ {isLoadingProjectFlock && (
+
+ )}
+ {!isLoadingProjectFlock && isResponseSuccess(projectFlock) && (
+
)}
- )
-}
+ );
+};
-export default ProjectFlockEdit;
\ No newline at end of file
+export default ProjectFlockEdit;
diff --git a/src/app/production/project-flock/detail/layout.tsx b/src/app/production/project-flock/detail/layout.tsx
index b41c70f9..7220dfa1 100644
--- a/src/app/production/project-flock/detail/layout.tsx
+++ b/src/app/production/project-flock/detail/layout.tsx
@@ -1,11 +1,11 @@
-import SuspenseHelper from "@/components/helper/SuspenseHelper"
+import SuspenseHelper from '@/components/helper/SuspenseHelper';
const Layout = ({
- children
+ children,
}: Readonly<{
- children: React.ReactNode
+ children: React.ReactNode;
}>) => {
- return
{children}
-}
+ return
{children};
+};
-export default Layout;
\ No newline at end of file
+export default Layout;
diff --git a/src/app/production/project-flock/detail/page.tsx b/src/app/production/project-flock/detail/page.tsx
index bea96b84..91d4dfd5 100644
--- a/src/app/production/project-flock/detail/page.tsx
+++ b/src/app/production/project-flock/detail/page.tsx
@@ -2,7 +2,7 @@
import ProjectFlockForm from '@/components/pages/production/project-flock/form/ProjectFlockForm';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
-import { ProjectFlockApi } from '@/services/api/production';
+import { ProjectFlockApi } from '@/services/api/production/project-flock';
import { useRouter, useSearchParams } from 'next/navigation';
import useSWR from 'swr';
@@ -37,12 +37,16 @@ const ProjectFlockDetail = () => {
}
return (
-
+
{isLoadingProjectFlock && (
)}
- {!isLoadingProjectFlock && isResponseSuccess(projectFlock) && (
-
+ {isResponseSuccess(projectFlock) && (
+
)}
);
diff --git a/src/app/production/project-flock/page.tsx b/src/app/production/project-flock/page.tsx
index d264d9e4..79feb41f 100644
--- a/src/app/production/project-flock/page.tsx
+++ b/src/app/production/project-flock/page.tsx
@@ -1,11 +1,11 @@
-import ProjectFlockTable from "@/components/pages/production/project-flock/ProjectFlockTable";
+import ProjectFlockTable from '@/components/pages/production/project-flock/ProjectFlockTable';
const ProjectFlock = () => {
return (
-
-
+
);
-}
+};
export default ProjectFlock;
diff --git a/src/app/production/recording/add/layout.tsx b/src/app/production/recording/add/layout.tsx
new file mode 100644
index 00000000..7220dfa1
--- /dev/null
+++ b/src/app/production/recording/add/layout.tsx
@@ -0,0 +1,11 @@
+import SuspenseHelper from '@/components/helper/SuspenseHelper';
+
+const Layout = ({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) => {
+ return {children};
+};
+
+export default Layout;
diff --git a/src/app/production/recording/grading/add/page.tsx b/src/app/production/recording/grading/add/page.tsx
new file mode 100644
index 00000000..9b918d98
--- /dev/null
+++ b/src/app/production/recording/grading/add/page.tsx
@@ -0,0 +1,49 @@
+'use client';
+
+import { useRouter, useSearchParams } from 'next/navigation';
+import useSWR from 'swr';
+import GradingForm from '@/components/pages/production/recording/grading/form/GradingForm';
+import { RecordingApi } from '@/services/api/production';
+import { isResponseSuccess } from '@/lib/api-helper';
+
+const AddGrading = () => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ const recordingId = searchParams.get('recording_id');
+
+ const { data: recording, isLoading: isLoadingRecording } = useSWR(
+ recordingId && recordingId !== 'new' ? [recordingId] : null,
+ ([id]) => RecordingApi.getSingle(parseInt(id))
+ );
+
+ if (
+ recordingId &&
+ recordingId !== 'new' &&
+ !isLoadingRecording &&
+ (!recording || !isResponseSuccess(recording))
+ ) {
+ router.replace('/404');
+ return;
+ }
+
+ return (
+
+ {recordingId && recordingId !== 'new' && isLoadingRecording && (
+
+ )}
+ {(!recordingId ||
+ recordingId === 'new' ||
+ (!isLoadingRecording && recording && isResponseSuccess(recording))) && (
+
+ )}
+
+ );
+};
+
+export default AddGrading;
diff --git a/src/app/production/recording/grading/detail/edit/page.tsx b/src/app/production/recording/grading/detail/edit/page.tsx
new file mode 100644
index 00000000..0a65f528
--- /dev/null
+++ b/src/app/production/recording/grading/detail/edit/page.tsx
@@ -0,0 +1,53 @@
+'use client';
+
+import { useRouter, useSearchParams } from 'next/navigation';
+import useSWR from 'swr';
+import GradingForm from '@/components/pages/production/recording/grading/form/GradingForm';
+import { RecordingApi } from '@/services/api/production';
+import { isResponseSuccess } from '@/lib/api-helper';
+
+const EditGrading = () => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ const recordingId = searchParams.get('recordingId');
+ const gradingId = searchParams.get('gradingId');
+
+ const { data: recording, isLoading: isLoadingRecording } = useSWR(
+ recordingId ? [recordingId] : null,
+ ([id]) => RecordingApi.getSingle(parseInt(id))
+ );
+
+ if (!recordingId) {
+ router.back();
+
+ return (
+
+
+
+ );
+ }
+
+ if (!isLoadingRecording && (!recording || !isResponseSuccess(recording))) {
+ router.replace('/404');
+ return;
+ }
+
+ return (
+
+ {isLoadingRecording && (
+
+ )}
+ {!isLoadingRecording && recording && isResponseSuccess(recording) && (
+ egg.id === parseInt(gradingId || '0')
+ )}
+ />
+ )}
+
+ );
+};
+
+export default EditGrading;
diff --git a/src/app/production/recording/grading/detail/page.tsx b/src/app/production/recording/grading/detail/page.tsx
new file mode 100644
index 00000000..6a5fbcba
--- /dev/null
+++ b/src/app/production/recording/grading/detail/page.tsx
@@ -0,0 +1,52 @@
+'use client';
+
+import { useRouter, useSearchParams } from 'next/navigation';
+import useSWR from 'swr';
+import GradingForm from '@/components/pages/production/recording/grading/form/GradingForm';
+import { RecordingApi } from '@/services/api/production';
+import { isResponseSuccess } from '@/lib/api-helper';
+
+const DetailGrading = () => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ const gradingId = searchParams.get('gradingId');
+
+ const { data: grading, isLoading: isLoadingGrading } = useSWR(
+ gradingId ? [gradingId] : null,
+ ([id]) => RecordingApi.getSingle(parseInt(id))
+ );
+
+ if (!gradingId) {
+ router.back();
+
+ return (
+
+
+
+ );
+ }
+
+ if (!isLoadingGrading && (!grading || !isResponseSuccess(grading))) {
+ router.replace('/404');
+ return;
+ }
+
+ return (
+
+ {isLoadingGrading && (
+
+ )}
+ {!isLoadingGrading && grading && isResponseSuccess(grading) && (
+ egg.id === parseInt(gradingId)
+ )}
+ />
+ )}
+
+ );
+};
+
+export default DetailGrading;
diff --git a/src/app/production/recording/grading/layout.tsx b/src/app/production/recording/grading/layout.tsx
new file mode 100644
index 00000000..7220dfa1
--- /dev/null
+++ b/src/app/production/recording/grading/layout.tsx
@@ -0,0 +1,11 @@
+import SuspenseHelper from '@/components/helper/SuspenseHelper';
+
+const Layout = ({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) => {
+ return {children};
+};
+
+export default Layout;
diff --git a/src/app/production/transfer-to-laying/detail/edit/page.tsx b/src/app/production/transfer-to-laying/detail/edit/page.tsx
index 9003dbba..d5498e08 100644
--- a/src/app/production/transfer-to-laying/detail/edit/page.tsx
+++ b/src/app/production/transfer-to-laying/detail/edit/page.tsx
@@ -8,91 +8,6 @@ import TransferToLayingForm from '@/components/pages/production/transfer-to-layi
import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
-import { TransferToLaying } from '@/types/api/production/transfer-to-laying';
-
-// TODO: delete dummy data
-const DUMMY_TRANSFER_TO_LAYING_EDIT: TransferToLaying = {
- id: 1,
- transfer_date: '2025-10-14',
- flock_source: {
- id: 1,
- name: 'Flock asal test',
- },
- flock_destination: {
- id: 2,
- name: 'Flock tujuan destination',
- },
- quantity: 10,
- kandangs: [
- {
- kandang: {
- id: 1,
- name: 'Kandang test',
- status: 'ACTIVE',
- location: {
- id: 1,
- name: 'test location',
- address: 'test address 1',
- area: { id: 1, name: 'test area 1' },
- },
- pic: {
- id: 1,
- id_user: 2,
- email: 'test@gmail.com',
- name: 'test',
- },
- created_user: {
- id: 1,
- id_user: 2,
- email: 'test@gmail.com',
- name: 'test',
- },
- created_at: '14-10-2025',
- updated_at: '14-10-2025',
- },
- quantity: 8,
- },
- {
- kandang: {
- id: 1,
- name: 'Kandang test 2',
- status: 'ACTIVE',
- location: {
- id: 1,
- name: 'test location',
- address: 'test address 1',
- area: { id: 1, name: 'test area 1' },
- },
- pic: {
- id: 1,
- id_user: 2,
- email: 'test@gmail.com',
- name: 'test',
- },
- created_user: {
- id: 1,
- id_user: 2,
- email: 'test@gmail.com',
- name: 'test',
- },
- created_at: '14-10-2025',
- updated_at: '14-10-2025',
- },
- quantity: 2,
- },
- ],
- reason: 'Test alasan',
-
- created_user: {
- id: 1,
- id_user: 2,
- email: 'test@gmail.com',
- name: 'test',
- },
- created_at: '14-10-2025',
- updated_at: '14-10-2025',
-};
-
const TransferToLayingEdit = () => {
const router = useRouter();
const searchParams = useSearchParams();
@@ -114,33 +29,33 @@ const TransferToLayingEdit = () => {
);
}
- // TODO: remove dummy data and integrate with real API
if (
!isLoadingTransferToLaying &&
- (!transferToLaying ||
- (isResponseError(transferToLaying) && !DUMMY_TRANSFER_TO_LAYING_EDIT))
+ (!transferToLaying || isResponseError(transferToLaying))
) {
router.replace('/404');
return;
}
+ if (
+ isResponseSuccess(transferToLaying) &&
+ transferToLaying.data.approval.step_number === 2
+ ) {
+ router.replace('/production/transfer-to-laying');
+ return;
+ }
+
return (
{isLoadingTransferToLaying && (
)}
- {/* {!isLoadingTransferToLaying && isResponseSuccess(transferToLaying) && (
+ {!isLoadingTransferToLaying && isResponseSuccess(transferToLaying) && (
- )} */}
-
- {/* TODO: remove this dummy data and integrate to real API */}
-
+ )}
);
};
diff --git a/src/app/production/transfer-to-laying/detail/page.tsx b/src/app/production/transfer-to-laying/detail/page.tsx
index de5426c8..9ff6ed5e 100644
--- a/src/app/production/transfer-to-laying/detail/page.tsx
+++ b/src/app/production/transfer-to-laying/detail/page.tsx
@@ -8,91 +8,6 @@ import TransferToLayingForm from '@/components/pages/production/transfer-to-layi
import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
-import { TransferToLaying } from '@/types/api/production/transfer-to-laying';
-
-// TODO: delete dummy data
-const DUMMY_TRANSFER_TO_LAYING_DETAIL: TransferToLaying = {
- id: 1,
- transfer_date: '2025-10-14',
- flock_source: {
- id: 1,
- name: 'Flock asal test',
- },
- flock_destination: {
- id: 2,
- name: 'Flock tujuan destination',
- },
- quantity: 10,
- kandangs: [
- {
- kandang: {
- id: 1,
- name: 'Kandang test',
- status: 'ACTIVE',
- location: {
- id: 1,
- name: 'test location',
- address: 'test address 1',
- area: { id: 1, name: 'test area 1' },
- },
- pic: {
- id: 1,
- id_user: 2,
- email: 'test@gmail.com',
- name: 'test',
- },
- created_user: {
- id: 1,
- id_user: 2,
- email: 'test@gmail.com',
- name: 'test',
- },
- created_at: '14-10-2025',
- updated_at: '14-10-2025',
- },
- quantity: 8,
- },
- {
- kandang: {
- id: 1,
- name: 'Kandang test 2',
- status: 'ACTIVE',
- location: {
- id: 1,
- name: 'test location',
- address: 'test address 1',
- area: { id: 1, name: 'test area 1' },
- },
- pic: {
- id: 1,
- id_user: 2,
- email: 'test@gmail.com',
- name: 'test',
- },
- created_user: {
- id: 1,
- id_user: 2,
- email: 'test@gmail.com',
- name: 'test',
- },
- created_at: '14-10-2025',
- updated_at: '14-10-2025',
- },
- quantity: 2,
- },
- ],
- reason: 'Test alasan',
-
- created_user: {
- id: 1,
- id_user: 2,
- email: 'test@gmail.com',
- name: 'test',
- },
- created_at: '14-10-2025',
- updated_at: '14-10-2025',
-};
-
const TransferToLayingDetail = () => {
const router = useRouter();
const searchParams = useSearchParams();
@@ -114,11 +29,9 @@ const TransferToLayingDetail = () => {
);
}
- // TODO: remove dummy data and integrate with real API
if (
!isLoadingTransferToLaying &&
- (!transferToLaying ||
- (isResponseError(transferToLaying) && !DUMMY_TRANSFER_TO_LAYING_DETAIL))
+ (!transferToLaying || isResponseError(transferToLaying))
) {
router.replace('/404');
return;
@@ -129,18 +42,13 @@ const TransferToLayingDetail = () => {
{isLoadingTransferToLaying && (
)}
- {/* {!isLoadingTransferToLaying && isResponseSuccess(transferToLaying) && (
+
+ {!isLoadingTransferToLaying && isResponseSuccess(transferToLaying) && (
- )} */}
-
- {/* TODO: remove this dummy data and integrate to real API */}
-
+ )}
);
};
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
index 7cad5b58..2f209ece 100644
--- a/src/components/Button.tsx
+++ b/src/components/Button.tsx
@@ -3,7 +3,7 @@ import Link from 'next/link';
import { cn } from '@/lib/helper';
import { Color } from '@/types/theme';
-interface ButtonProps extends react.ComponentProps<'button'> {
+export interface ButtonProps extends react.ComponentProps<'button'> {
variant?: 'soft' | 'outline' | 'dash' | 'ghost' | 'link' | 'active';
color?: Color;
href?: string;
diff --git a/src/components/Card.tsx b/src/components/Card.tsx
index 06438390..7b022971 100644
--- a/src/components/Card.tsx
+++ b/src/components/Card.tsx
@@ -1,13 +1,12 @@
'use client';
-import {
- HTMLAttributes,
- ReactNode,
-} from 'react';
+import { HTMLAttributes, ReactNode } from 'react';
import { cn } from '@/lib/helper';
+import Image from 'next/image';
-export interface CardProps extends Omit
, 'className'> {
+export interface CardProps
+ extends Omit, 'className'> {
title?: string;
subtitle?: string;
image?: string;
@@ -44,17 +43,17 @@ const Card = ({
const baseClasses = 'card bg-base-100';
const variantClasses = {
- 'default': '',
- 'compact': 'card-compact',
- 'bordered': 'border border-base-300',
- 'shadow': 'shadow-xl',
+ default: '',
+ compact: 'card-compact',
+ bordered: 'border border-base-300',
+ shadow: 'shadow-xl',
'image-full': 'card-side card-compact shadow-xl',
};
const sizeClasses = {
- 'sm': 'w-64',
- 'md': 'w-96',
- 'lg': 'w-[28rem]',
+ sm: 'w-64',
+ md: 'w-96',
+ lg: 'w-[28rem]',
};
return cn(
@@ -84,9 +83,9 @@ const Card = ({
const getTitleClasses = () => {
const sizeClasses = {
- 'sm': 'text-lg',
- 'md': 'text-xl',
- 'lg': 'text-2xl',
+ sm: 'text-lg',
+ md: 'text-xl',
+ lg: 'text-2xl',
};
return cn('card-title font-bold', sizeClasses[size], className?.title);
@@ -108,7 +107,7 @@ const Card = ({
return (
-
{image && (
-
{
@@ -8,31 +15,34 @@ export const useModal = () => {
const [open, setOpen] = useState(false);
const openModal = useCallback(() => {
+ if (!ref.current) return;
+ ref.current.show();
setOpen(true);
-
- ref.current?.showModal();
}, []);
const closeModal = useCallback(() => {
+ if (!ref.current) return;
+ ref.current.close();
setOpen(false);
- ref.current?.close();
}, []);
const toggle = useCallback(() => {
- if (open) {
- closeModal();
- } else {
- openModal();
- }
+ open ? closeModal() : openModal();
}, [open, closeModal, openModal]);
- if (ref.current) {
- ref.current.addEventListener('close', () => {
- closeModal();
- });
- }
+ useEffect(() => {
+ const dialog = ref.current;
+ if (!dialog) return;
- return { ref, open, setOpen, openModal, closeModal, toggle } as const;
+ const handleClose = () => setOpen(false);
+ dialog.addEventListener('close', handleClose);
+
+ return () => {
+ dialog.removeEventListener('close', handleClose);
+ };
+ }, []);
+
+ return { ref, open, openModal, closeModal, toggle } as const;
};
interface ModalProps {
@@ -46,15 +56,19 @@ interface ModalProps {
}
const Modal = ({ ref, children, closeOnBackdrop, className }: ModalProps) => {
- return (
-