Compare commits

...

54 Commits

Author SHA1 Message Date
kris ebc960abb5 Update .gitlab-ci.yml file 2025-11-11 04:32:51 +00:00
kris af4926b1d7 Update .gitlab-ci.yml file 2025-11-11 04:26:32 +00:00
kris 3d134d7b8e Update .gitlab-ci.yml file 2025-11-11 04:20:32 +00:00
kris ea5ab83795 Update .gitlab-ci.yml file 2025-11-11 04:08:07 +00:00
kris 132ce52f23 Update .gitlab-ci.yml file 2025-11-11 03:59:20 +00:00
kris b1482fb586 Update .gitlab-ci.yml file 2025-11-10 09:48:36 +00:00
Mitra Berlian Unggas 56b75af69f Update .gitlab-ci.yml file 2025-11-10 09:31:50 +00:00
kris e05db3c0c4 Update .gitlab-ci.yml file 2025-11-10 09:05:07 +00:00
kris 695b7d64ec Update .gitlab-ci.yml file 2025-11-10 08:57:15 +00:00
kris f2c581fcc2 Update .gitlab-ci.yml file 2025-11-10 08:52:09 +00:00
kris f761a12137 Update .gitlab-ci.yml file 2025-11-10 08:44:55 +00:00
kris fef1b59138 Update .gitlab-ci.yml file 2025-11-10 08:34:58 +00:00
kris 472ff1d3da Update .gitlab-ci.yml file 2025-11-10 08:26:05 +00:00
kris 90de8f4e4d Update .gitlab-ci.yml file 2025-11-10 08:22:49 +00:00
kris 8912a82dba Update .gitlab-ci.yml file 2025-11-10 08:06:34 +00:00
kris 3ae5a0f9b7 Update .gitlab-ci.yml file 2025-11-10 08:05:40 +00:00
kris 2aaaf9a442 Update .gitlab-ci.yml file 2025-11-10 08:04:37 +00:00
kris caf406a383 Update .gitlab-ci.yml file 2025-11-10 06:59:47 +00:00
kris 10ed17b0ed Update .gitlab-ci.yml file 2025-11-10 06:44:05 +00:00
kris fd47a3b407 Update .gitlab-ci.yml file 2025-11-10 06:36:25 +00:00
kris 5cab1a072d Update .gitlab-ci.yml file 2025-11-10 06:32:29 +00:00
kris 288c675de7 Update .gitlab-ci.yml file 2025-11-10 06:24:44 +00:00
kris d8f16558a3 Update .gitlab-ci.yml file 2025-11-10 06:20:57 +00:00
kris 13d57c206b Update .gitlab-ci.yml file 2025-11-09 10:21:06 +00:00
kris 773aa2dbb1 Update .gitlab-ci.yml file 2025-11-09 10:10:19 +00:00
kris f14adc46d3 Update .gitlab-ci.yml file 2025-11-09 09:50:29 +00:00
kris e7592eb221 Update .gitlab-ci.yml file 2025-11-09 09:48:13 +00:00
kris 32f202d814 Update .gitlab-ci.yml file 2025-11-09 09:23:32 +00:00
kris 942b19375e Merge branch 'chore/build-cicd' into 'development'
update Dockerfile

See merge request mbugroup/lti-web-client!47
2025-11-09 09:09:14 +00:00
GitLab Deploy Bot b62427c5f4 update Dockerfile 2025-11-09 16:08:22 +07:00
kris f126e976fd Update .gitlab-ci.yml file 2025-11-09 08:34:51 +00:00
kris 0a2373572f Merge branch 'chore/build-cicd' into 'development'
edit Dockerfile

See merge request mbugroup/lti-web-client!46
2025-11-09 08:22:15 +00:00
GitLab Deploy Bot 73d2de6dfb edit Dockerfile 2025-11-09 15:21:15 +07:00
kris 49e648689a Merge branch 'chore/build-cicd' into 'development'
edit Dockerfile

See merge request mbugroup/lti-web-client!45
2025-11-09 08:16:08 +00:00
GitLab Deploy Bot d3cc38aed5 edit Dockerfile 2025-11-09 15:15:26 +07:00
kris a9620246c0 Update .gitlab-ci.yml file 2025-11-09 08:05:11 +00:00
kris 2d649eb0ff Merge branch 'chore/build-cicd' into 'development'
edit .gitlab-ci

See merge request mbugroup/lti-web-client!44
2025-11-09 08:02:31 +00:00
GitLab Deploy Bot 66b6579f27 edit .gitlab-ci 2025-11-09 15:01:10 +07:00
kris 4f9695aabe Merge branch 'chore/build-cicd' into 'development'
edit .gitlab-ci

See merge request mbugroup/lti-web-client!43
2025-11-09 07:54:31 +00:00
GitLab Deploy Bot 29ff1bb50a edit .gitlab-ci 2025-11-09 14:53:49 +07:00
kris fefb665485 Merge branch 'chore/build-cicd' into 'development'
build docker via gitlab

See merge request mbugroup/lti-web-client!42
2025-11-09 07:49:21 +00:00
GitLab Deploy Bot 52e8fb4a3b build with tag docker 2025-11-09 14:44:58 +07:00
GitLab Deploy Bot 8a11c176aa build docker via gitlab 2025-11-09 14:21:58 +07:00
Adnan Zahir d679c9f278 Merge branch 'fix/ISSUE-236/table-dropdown-issue' into 'development'
Fix: LTI Issue #236

See merge request mbugroup/lti-web-client!41
2025-11-03 09:08:21 +07:00
ValdiANS 0ae4fe0831 chore: format code using prettier 2025-11-01 15:58:47 +07:00
ValdiANS f01dae5f97 chore: add format script 2025-11-01 15:58:03 +07:00
ValdiANS 42b4206e66 chore: install prettier 2025-11-01 15:53:37 +07:00
ValdiANS 46572fd992 chore: update add button styling 2025-11-01 15:36:21 +07:00
ValdiANS b2540f1d43 chore: use RowOptionsMenuWrapper 2025-11-01 15:36:11 +07:00
ValdiANS ad10ffbba3 chore: set min width for RowCollapseOptions 2025-11-01 15:35:49 +07:00
ValdiANS 8a3c7d35ec chore: update add button styling and copywriting 2025-11-01 15:35:34 +07:00
ValdiANS d853b43e17 fix: use RowOptionsMenuWrapper component for RowOptionsMenu 2025-11-01 15:31:11 +07:00
ValdiANS e6187555ce chore: create RowOptionsMenuWrapper component 2025-11-01 15:26:25 +07:00
ValdiANS bba8fb15e5 chore: change a element to button 2025-11-01 15:24:52 +07:00
93 changed files with 1819 additions and 1578 deletions
+135 -62
View File
@@ -1,76 +1,149 @@
stages: [notify] stages:
- build
- deploy
# --- Notify when MR is opened/updated --- # ====== TEMPLATE: BUILD STATIC NEXT.JS ======
notify_discord_mr: .build_template: &build_template
stage: notify stage: build
image: alpine:3.20 image: node:20-alpine
rules: cache:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"' key: npm-cache
paths:
- node_modules/
variables: variables:
WEBHOOK_URL: $DISCORD_WEBHOOK_URL NPM_CONFIG_PRODUCTION: "false"
before_script: NODE_ENV: ""
- apk add --no-cache curl jq script:
script: | - echo "Installing dependencies..."
MR_URL="${CI_PROJECT_URL}/-/merge_requests/${CI_MERGE_REQUEST_IID}" - 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
.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"
# 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}"
# Tentukan nama environment
if [ "$CI_COMMIT_BRANCH" = "devops-s3" ]; 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 \ jq -n \
--arg title "$TITLE" \
--arg desc "$DESC" \
--arg color "$COLOR" \
--arg repo "$CI_PROJECT_PATH" \ --arg repo "$CI_PROJECT_PATH" \
--arg mr "#${CI_MERGE_REQUEST_IID}" \ --arg actor "$GITLAB_USER_LOGIN" \
--arg url "$MR_URL" \ --arg commit "$CI_COMMIT_SHA" \
--arg requestor "${GITLAB_USER_LOGIN:-$GITLAB_USER_NAME}" \ --arg run_url "$RUN_URL" \
--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", username: "CI Bot - LTI WEB",
embeds: [{ embeds: [{
title: "📣 [LTI WEB CLIENT] Merge Request Opened/Updated", title: $title,
description: ($mr + " in " + $repo), description: $desc,
url: $url, color: ($color|tonumber),
color: 3447003,
fields: [ fields: [
{name: "Author", value: $requestor, inline: true}, {name: "Repository", value: $repo, inline: true},
{name: "Source → Target", value: ($source + " → " + $target), inline: true}, {name: "Actor", value: $actor, inline: true},
{name: "Title", value: $title} {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 @- "$WEBHOOK_URL"
# --- Notify when MR is merged --- curl -sS -H "Content-Type: application/json" -d @payload.json "$DISCORD_WEBHOOK_URL"
notify_discord_merge:
stage: notify # ====== DEVELOPMENT (Branch devops-s3) ======
image: alpine:3.20 build:dev:
<<: *build_template
rules: rules:
# Only run for merge request pipelines that are in merged state - if: '$CI_COMMIT_BRANCH == "devops-s3"'
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_STATE == "merged"' environment:
name: devops-s3
variables: variables:
WEBHOOK_URL: $DISCORD_WEBHOOK_URL NEXT_PUBLIC_API_BASE_URL: "https://dev-api-lti.mbugroup.id"
before_script: NEXT_PUBLIC_SSO_LOGIN_URL: "https://dev-api-sso.mbugroup.id"
- apk add --no-cache curl jq
script: | deploy:dev:
MR_URL="${CI_PROJECT_URL}/-/merge_requests/${CI_MERGE_REQUEST_IID}" <<: *deploy_template
needs: ["build:dev"]
rules:
- if: '$CI_COMMIT_BRANCH == "devops-s3"'
variables:
S3_BUCKET: "dev-lti-erp.mbugroup.id"
AWS_REGION: "ap-southeast-3"
CLOUDFRONT_DISTRIBUTION_ID: "E1Z8XTA8XF1GIV"
environment:
name: devops-s3
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"
+25
View File
@@ -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"]
+39
View File
@@ -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
+9 -9
View File
@@ -1,6 +1,6 @@
import { dirname } from "path"; import { dirname } from 'path';
import { fileURLToPath } from "url"; import { fileURLToPath } from 'url';
import { FlatCompat } from "@eslint/eslintrc"; import { FlatCompat } from '@eslint/eslintrc';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename); const __dirname = dirname(__filename);
@@ -10,14 +10,14 @@ const compat = new FlatCompat({
}); });
const eslintConfig = [ const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"), ...compat.extends('next/core-web-vitals', 'next/typescript'),
{ {
ignores: [ ignores: [
"node_modules/**", 'node_modules/**',
".next/**", '.next/**',
"out/**", 'out/**',
"build/**", 'build/**',
"next-env.d.ts", 'next-env.d.ts',
], ],
}, },
]; ];
+17
View File
@@ -39,6 +39,7 @@
"eslint": "^9", "eslint": "^9",
"eslint-config-next": "15.5.3", "eslint-config-next": "15.5.3",
"husky": "^9.1.7", "husky": "^9.1.7",
"prettier": "^3.6.2",
"tailwindcss": "^4", "tailwindcss": "^4",
"typescript": "^5" "typescript": "^5"
} }
@@ -5669,6 +5670,22 @@
"node": ">= 0.8.0" "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": { "node_modules/prop-types": {
"version": "15.8.1", "version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+3 -1
View File
@@ -7,7 +7,8 @@
"build": "next build --turbopack", "build": "next build --turbopack",
"start": "next start", "start": "next start",
"lint": "eslint", "lint": "eslint",
"prepare": "husky" "prepare": "husky",
"format": "prettier --write ."
}, },
"dependencies": { "dependencies": {
"@tanstack/match-sorter-utils": "^8.19.4", "@tanstack/match-sorter-utils": "^8.19.4",
@@ -41,6 +42,7 @@
"eslint": "^9", "eslint": "^9",
"eslint-config-next": "15.5.3", "eslint-config-next": "15.5.3",
"husky": "^9.1.7", "husky": "^9.1.7",
"prettier": "^3.6.2",
"tailwindcss": "^4", "tailwindcss": "^4",
"typescript": "^5" "typescript": "^5"
} }
+1 -1
View File
@@ -1,5 +1,5 @@
const config = { const config = {
plugins: ["@tailwindcss/postcss"], plugins: ['@tailwindcss/postcss'],
}; };
export default config; export default config;
+2 -4
View File
@@ -3,10 +3,10 @@
@import '../styles/daisyui.css'; @import '../styles/daisyui.css';
@plugin "daisyui/theme" { @plugin "daisyui/theme" {
name: "lti"; name: 'lti';
default: false; default: false;
prefersdark: false; prefersdark: false;
color-scheme: "light"; color-scheme: 'light';
--color-base-100: oklch(98% 0.001 106.423); --color-base-100: oklch(98% 0.001 106.423);
--color-base-200: oklch(97% 0.001 106.424); --color-base-200: oklch(97% 0.001 106.424);
--color-base-300: oklch(92% 0.003 48.717); --color-base-300: oklch(92% 0.003 48.717);
@@ -37,8 +37,6 @@
--noise: 0; --noise: 0;
} }
:root { :root {
--color-primary: #1f74bf; --color-primary: #1f74bf;
} }
+3 -3
View File
@@ -1,11 +1,11 @@
import InventoryAdjustmentForm from "@/components/pages/inventory/adjustment/form/InventoryAdjustmentForm"; import InventoryAdjustmentForm from '@/components/pages/inventory/adjustment/form/InventoryAdjustmentForm';
const CreateInventoryAdjustment = () => { const CreateInventoryAdjustment = () => {
return ( return (
<section className="w-full p-4 flex flex-row justify-center"> <section className='w-full p-4 flex flex-row justify-center'>
<InventoryAdjustmentForm /> <InventoryAdjustmentForm />
</section> </section>
); );
} };
export default CreateInventoryAdjustment; export default CreateInventoryAdjustment;
@@ -1,11 +1,11 @@
import SuspenseHelper from "@/components/helper/SuspenseHelper" import SuspenseHelper from '@/components/helper/SuspenseHelper';
const Layout = ({ const Layout = ({
children children,
}: Readonly<{ }: Readonly<{
children: React.ReactNode children: React.ReactNode;
}>) => { }>) => {
return <SuspenseHelper>{children}</SuspenseHelper> return <SuspenseHelper>{children}</SuspenseHelper>;
} };
export default Layout; export default Layout;
+7 -6
View File
@@ -7,11 +7,12 @@ import type { InventoryAdjustment } from '@/types/api/inventory/adjustment';
const DetailInventoryAdjustment = () => { const DetailInventoryAdjustment = () => {
const router = useRouter(); const router = useRouter();
const [inventoryAdjustment, setInventoryAdjustment] = useState<InventoryAdjustment | null>(null); const [inventoryAdjustment, setInventoryAdjustment] =
useState<InventoryAdjustment | null>(null);
// Ambil data dari router state // Ambil data dari router state
useEffect(() => { useEffect(() => {
console.log("Router State"); console.log('Router State');
console.log(window.history.state); console.log(window.history.state);
const state = window.history.state?.usr as const state = window.history.state?.usr as
| { inventoryAdjustment?: InventoryAdjustment } | { inventoryAdjustment?: InventoryAdjustment }
@@ -25,19 +26,19 @@ const DetailInventoryAdjustment = () => {
const finalData = inventoryAdjustment; const finalData = inventoryAdjustment;
console.log("Final Data"); console.log('Final Data');
console.log(finalData); console.log(finalData);
if (!finalData) { if (!finalData) {
return ( return (
<div className="w-full flex flex-row justify-center items-center p-4"> <div className='w-full flex flex-row justify-center items-center p-4'>
<span className="loading loading-spinner loading-xl" /> <span className='loading loading-spinner loading-xl' />
</div> </div>
); );
} }
return ( return (
<section className="w-full p-4 flex flex-row justify-center"> <section className='w-full p-4 flex flex-row justify-center'>
<InventoryAdjustmentForm initialValues={finalData} /> <InventoryAdjustmentForm initialValues={finalData} />
</section> </section>
); );
+3 -3
View File
@@ -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 = () => { const AddCustomer = () => {
return ( return (
<section className="w-full p-4 flex flex-row justify-center"> <section className='w-full p-4 flex flex-row justify-center'>
<CustomerForm /> <CustomerForm />
</section> </section>
); );
} };
export default AddCustomer; export default AddCustomer;
+15 -13
View File
@@ -1,16 +1,16 @@
'use client' 'use client';
import { useRouter, useSearchParams } from "next/navigation"; import { useRouter, useSearchParams } from 'next/navigation';
import useSWR from "swr"; import useSWR from 'swr';
import { CustomerApi } from '@/services/api/master-data'; import { CustomerApi } from '@/services/api/master-data';
import { isResponseError, isResponseSuccess } from "@/lib/api-helper"; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import CustomerForm from "@/components/pages/master-data/customer/form/CustomerForm"; import CustomerForm from '@/components/pages/master-data/customer/form/CustomerForm';
const CustomerDetail = () => { const CustomerDetail = () => {
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const costumerId = searchParams.get("customerId"); const costumerId = searchParams.get('customerId');
const { data: costumer, isLoading: isLoadingCostumer } = useSWR( const { data: costumer, isLoading: isLoadingCostumer } = useSWR(
costumerId, costumerId,
@@ -21,25 +21,27 @@ const CustomerDetail = () => {
router.back(); router.back();
return ( return (
<div className="w-full flex flex-row justify-center items-center p-4"> <div className='w-full flex flex-row justify-center items-center p-4'>
<span className="loading loading-spinner loading-xl" /> <span className='loading loading-spinner loading-xl' />
</div> </div>
); );
} }
if (!isLoadingCostumer && (!costumer || isResponseError(costumer))) { if (!isLoadingCostumer && (!costumer || isResponseError(costumer))) {
router.replace("/404"); router.replace('/404');
return; return;
} }
return ( return (
<div className="w-full p-4 flex flex-row justify-center"> <div className='w-full p-4 flex flex-row justify-center'>
{isLoadingCostumer && <span className="loading loading-spinner loading-xl" />} {isLoadingCostumer && (
<span className='loading loading-spinner loading-xl' />
)}
{!isLoadingCostumer && isResponseSuccess(costumer) && ( {!isLoadingCostumer && isResponseSuccess(costumer) && (
<CustomerForm formType="detail" initialValues={costumer.data} /> <CustomerForm formType='detail' initialValues={costumer.data} />
)} )}
</div> </div>
) );
}; };
export default CustomerDetail; export default CustomerDetail;
+3 -3
View File
@@ -1,11 +1,11 @@
import CustomersTable from "@/components/pages/master-data/customer/CustomersTable"; import CustomersTable from '@/components/pages/master-data/customer/CustomersTable';
const Customer = () => { const Customer = () => {
return ( return (
<section className="w-full p-4"> <section className='w-full p-4'>
<CustomersTable /> <CustomersTable />
</section> </section>
) );
}; };
export default Customer; export default Customer;
+3 -3
View File
@@ -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 = () => { const AddFlock = () => {
return ( return (
<section className="w-full p-4 flex flex-row justify-center"> <section className='w-full p-4 flex flex-row justify-center'>
<FlockForm /> <FlockForm />
</section> </section>
); );
} };
export default AddFlock; export default AddFlock;
@@ -1,10 +1,10 @@
'use client' 'use client';
import FlockForm from "@/components/pages/master-data/flock/form/FlockForm"; import FlockForm from '@/components/pages/master-data/flock/form/FlockForm';
import { isResponseError, isResponseSuccess } from "@/lib/api-helper"; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { FlockApi } from "@/services/api/master-data"; import { FlockApi } from '@/services/api/master-data';
import { useRouter, useSearchParams } from "next/navigation"; import { useRouter, useSearchParams } from 'next/navigation';
import useSWR from "swr"; import useSWR from 'swr';
const FlockEdit = () => { const FlockEdit = () => {
const router = useRouter(); const router = useRouter();
@@ -44,6 +44,6 @@ const FlockEdit = () => {
)} )}
</div> </div>
); );
} };
export default FlockEdit; export default FlockEdit;
+5 -5
View File
@@ -1,11 +1,11 @@
import SuspenseHelper from "@/components/helper/SuspenseHelper" import SuspenseHelper from '@/components/helper/SuspenseHelper';
const Layout = ({ const Layout = ({
children children,
}: Readonly<{ }: Readonly<{
children: React.ReactNode children: React.ReactNode;
}>) => { }>) => {
return <SuspenseHelper>{children}</SuspenseHelper> return <SuspenseHelper>{children}</SuspenseHelper>;
} };
export default Layout; export default Layout;
+16 -13
View File
@@ -1,10 +1,10 @@
'use client' 'use client';
import FlockForm from "@/components/pages/master-data/flock/form/FlockForm"; import FlockForm from '@/components/pages/master-data/flock/form/FlockForm';
import { isResponseError, isResponseSuccess } from "@/lib/api-helper"; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { FlockApi } from "@/services/api/master-data"; import { FlockApi } from '@/services/api/master-data';
import { useRouter, useSearchParams } from "next/navigation"; import { useRouter, useSearchParams } from 'next/navigation';
import useSWR from "swr"; import useSWR from 'swr';
const FlockDetail = () => { const FlockDetail = () => {
const router = useRouter(); const router = useRouter();
@@ -14,14 +14,17 @@ const FlockDetail = () => {
const flockId = searchParams.get('flockId'); const flockId = searchParams.get('flockId');
// Fetch Data // 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(); router.back();
return ( return (
<div className="w-full flex flex-row justify-center items-center p-4"> <div className='w-full flex flex-row justify-center items-center p-4'>
<span className="loading loading-spinner loading-xl" /> <span className='loading loading-spinner loading-xl' />
</div> </div>
); );
} }
@@ -32,15 +35,15 @@ const FlockDetail = () => {
} }
return ( return (
<div className="w-full p-4 flex flex-row justify-center"> <div className='w-full p-4 flex flex-row justify-center'>
{isLoadingFlock && ( {isLoadingFlock && (
<span className="loading loading-spinner loading-xl" /> <span className='loading loading-spinner loading-xl' />
)} )}
{!isLoadingFlock && isResponseSuccess(flock) && ( {!isLoadingFlock && isResponseSuccess(flock) && (
<FlockForm formType="detail" initialValues={flock.data} /> <FlockForm formType='detail' initialValues={flock.data} />
)} )}
</div> </div>
); );
} };
export default FlockDetail; export default FlockDetail;
+3 -3
View File
@@ -1,11 +1,11 @@
import FlockTable from "@/components/pages/master-data/flock/FlocksTable"; import FlockTable from '@/components/pages/master-data/flock/FlocksTable';
const Flock = () => { const Flock = () => {
return ( return (
<section className="w-full p-4"> <section className='w-full p-4'>
<FlockTable /> <FlockTable />
</section> </section>
); );
} };
export default Flock; export default Flock;
@@ -1,8 +1,8 @@
import ProductCategoryForm from "@/components/pages/master-data/product-category/form/ProductCategoryForm"; import ProductCategoryForm from '@/components/pages/master-data/product-category/form/ProductCategoryForm';
const AddProductCategory = () => { const AddProductCategory = () => {
return ( return (
<div className="w-full p-4 flex flex-row justify-center"> <div className='w-full p-4 flex flex-row justify-center'>
<ProductCategoryForm /> <ProductCategoryForm />
</div> </div>
); );
@@ -29,19 +29,24 @@ const ProductCategoryEdit = () => {
); );
} }
if (!isLoadingProductCategory && (!productCategory || isResponseError(productCategory))) { if (
!isLoadingProductCategory &&
(!productCategory || isResponseError(productCategory))
) {
router.replace('/404'); router.replace('/404');
return; return;
} }
return ( return (
<div className='w-full p-4 flex flex-row justify-center'> <div className='w-full p-4 flex flex-row justify-center'>
{isLoadingProductCategory && <span className='loading loading-spinner loading-xl' />} {isLoadingProductCategory && (
<span className='loading loading-spinner loading-xl' />
)}
{!isLoadingProductCategory && isResponseSuccess(productCategory) && ( {!isLoadingProductCategory && isResponseSuccess(productCategory) && (
<ProductCategoryForm type='edit' initialValues={productCategory.data} /> <ProductCategoryForm type='edit' initialValues={productCategory.data} />
)} )}
</div> </div>
); );
} };
export default ProductCategoryEdit; export default ProductCategoryEdit;
@@ -29,16 +29,24 @@ const ProductCategoryDetail = () => {
); );
} }
if (!isLoadingProductCategory && (!productCategory || isResponseError(productCategory))) { if (
!isLoadingProductCategory &&
(!productCategory || isResponseError(productCategory))
) {
router.replace('/404'); router.replace('/404');
return; return;
} }
return ( return (
<div className='w-full p-4 flex flex-row justify-center'> <div className='w-full p-4 flex flex-row justify-center'>
{isLoadingProductCategory && <span className='loading loading-spinner loading-xl' />} {isLoadingProductCategory && (
<span className='loading loading-spinner loading-xl' />
)}
{!isLoadingProductCategory && isResponseSuccess(productCategory) && ( {!isLoadingProductCategory && isResponseSuccess(productCategory) && (
<ProductCategoryForm type='detail' initialValues={productCategory.data} /> <ProductCategoryForm
type='detail'
initialValues={productCategory.data}
/>
)} )}
</div> </div>
); );
@@ -1,8 +1,8 @@
import ProductCategoryTable from "@/components/pages/master-data/product-category/ProductCategoryTable"; import ProductCategoryTable from '@/components/pages/master-data/product-category/ProductCategoryTable';
const ProductCategory = () => { const ProductCategory = () => {
return ( return (
<section className="w-full p-4"> <section className='w-full p-4'>
<ProductCategoryTable /> <ProductCategoryTable />
</section> </section>
); );
+1 -1
View File
@@ -2,7 +2,7 @@ import ProductForm from '@/components/pages/master-data/product/form/ProductForm
const AddProduct = () => { const AddProduct = () => {
return ( return (
<div className="w-full p-4 flex flex-row justify-center"> <div className='w-full p-4 flex flex-row justify-center'>
<ProductForm /> <ProductForm />
</div> </div>
); );
@@ -13,9 +13,8 @@ const ProductEdit = () => {
const productId = searchParams.get('productId'); const productId = searchParams.get('productId');
const { data: product, isLoading } = useSWR( const { data: product, isLoading } = useSWR(productId, (id: number) =>
productId, ProductApi.getSingle(id)
(id: number) => ProductApi.getSingle(id)
); );
if (!productId) { if (!productId) {
+2 -3
View File
@@ -13,9 +13,8 @@ const ProductDetail = () => {
const productId = searchParams.get('productId'); const productId = searchParams.get('productId');
const { data: product, isLoading } = useSWR( const { data: product, isLoading } = useSWR(productId, (id: number) =>
productId, ProductApi.getSingle(id)
(id: number) => ProductApi.getSingle(id)
); );
if (!productId) { if (!productId) {
+2 -2
View File
@@ -1,8 +1,8 @@
import ProductsTable from "@/components/pages/master-data/product/ProductTable"; import ProductsTable from '@/components/pages/master-data/product/ProductTable';
const Product = () => { const Product = () => {
return ( return (
<section className="w-full p-4"> <section className='w-full p-4'>
<ProductsTable /> <ProductsTable />
</section> </section>
); );
+1 -1
View File
@@ -1,4 +1,4 @@
import SuppliersTable from "@/components/pages/master-data/supplier/SupplierTable"; import SuppliersTable from '@/components/pages/master-data/supplier/SupplierTable';
const Supplier = () => { const Supplier = () => {
return ( return (
+5 -5
View File
@@ -1,11 +1,11 @@
import SuspenseHelper from "@/components/helper/SuspenseHelper" import SuspenseHelper from '@/components/helper/SuspenseHelper';
const Layout = ({ const Layout = ({
children children,
}: Readonly<{ }: Readonly<{
children: React.ReactNode children: React.ReactNode;
}>) => { }>) => {
return <SuspenseHelper>{children}</SuspenseHelper> return <SuspenseHelper>{children}</SuspenseHelper>;
} };
export default Layout; export default Layout;
+5 -5
View File
@@ -1,11 +1,11 @@
import SuspenseHelper from "@/components/helper/SuspenseHelper" import SuspenseHelper from '@/components/helper/SuspenseHelper';
const Layout = ({ const Layout = ({
children children,
}: Readonly<{ }: Readonly<{
children: React.ReactNode children: React.ReactNode;
}>) => { }>) => {
return <SuspenseHelper>{children}</SuspenseHelper> return <SuspenseHelper>{children}</SuspenseHelper>;
} };
export default Layout; export default Layout;
+4 -4
View File
@@ -43,9 +43,8 @@ const DetailChickin = () => {
// chickin.data?.approval.step_number == 1 ? false : true // chickin.data?.approval.step_number == 1 ? false : true
true true
); );
const [isRejectedDisabled, setIsRejectedDisabled] = useState( const [isRejectedDisabled, setIsRejectedDisabled] =
!isApprovedDisabled useState(!isApprovedDisabled);
);
const [approvalAction, setApprovalAction] = useState<'APPROVED' | 'REJECTED'>( const [approvalAction, setApprovalAction] = useState<'APPROVED' | 'REJECTED'>(
!isApprovedDisabled ? 'APPROVED' : 'REJECTED' !isApprovedDisabled ? 'APPROVED' : 'REJECTED'
); );
@@ -264,7 +263,8 @@ const DetailChickin = () => {
<Icon icon='mdi:times' width={24} height={24} /> <Icon icon='mdi:times' width={24} height={24} />
Delete Delete
</Button> </Button>
<Button color='warning' <Button
color='warning'
onClick={() => { onClick={() => {
chickinModal.openModal(); chickinModal.openModal();
}} }}
+3 -3
View File
@@ -1,10 +1,10 @@
import ChickinTable from "@/components/pages/production/chickin/ChickinTable"; import ChickinTable from '@/components/pages/production/chickin/ChickinTable';
const Chickin = () => { const Chickin = () => {
return ( return (
<section className="w-full p-4"> <section className='w-full p-4'>
<ChickinTable /> <ChickinTable />
</section> </section>
); );
} };
export default Chickin; export default Chickin;
@@ -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 = () => { const AddProjectFlock = () => {
return ( return (
<section className="w-full p-4 flex flex-row justify-center"> <section className='w-full p-4 flex flex-row justify-center'>
<ProjectFlockForm formType="add"/> <ProjectFlockForm formType='add' />
</section> </section>
); );
} };
export default AddProjectFlock; export default AddProjectFlock;
@@ -1,17 +1,16 @@
'use client' 'use client';
import ProjectFlockForm from '@/components/pages/production/project-flock/form/ProjectFlockForm';
import ProjectFlockForm from "@/components/pages/production/project-flock/form/ProjectFlockForm"; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { isResponseError, isResponseSuccess } from "@/lib/api-helper"; import { ProjectFlockApi } from '@/services/api/production';
import { ProjectFlockApi } from "@/services/api/production"; import { useRouter, useSearchParams } from 'next/navigation';
import { useRouter, useSearchParams } from "next/navigation"; import useSWR from 'swr';
import useSWR from "swr";
const ProjectFlockEdit = () => { const ProjectFlockEdit = () => {
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const projectFlockId = searchParams.get("projectFlockId"); const projectFlockId = searchParams.get('projectFlockId');
const { data: projectFlock, isLoading: isLoadingCostumer } = useSWR( const { data: projectFlock, isLoading: isLoadingCostumer } = useSWR(
projectFlockId, projectFlockId,
@@ -22,25 +21,27 @@ const ProjectFlockEdit = () => {
router.back(); router.back();
return ( return (
<div className="w-full flex flex-row justify-center items-center p-4"> <div className='w-full flex flex-row justify-center items-center p-4'>
<span className="loading loading-spinner loading-xl" /> <span className='loading loading-spinner loading-xl' />
</div> </div>
); );
} }
if (!isLoadingCostumer && (!projectFlock || isResponseError(projectFlock))) { if (!isLoadingCostumer && (!projectFlock || isResponseError(projectFlock))) {
router.replace("/404"); router.replace('/404');
return; return;
} }
return ( return (
<div className="w-full p-4 flex flex-row justify-center"> <div className='w-full p-4 flex flex-row justify-center'>
{isLoadingCostumer && <span className="loading loading-spinner loading-xl" />} {isLoadingCostumer && (
<span className='loading loading-spinner loading-xl' />
)}
{!isLoadingCostumer && isResponseSuccess(projectFlock) && ( {!isLoadingCostumer && isResponseSuccess(projectFlock) && (
<ProjectFlockForm formType="edit" initialValues={projectFlock.data} /> <ProjectFlockForm formType='edit' initialValues={projectFlock.data} />
)} )}
</div> </div>
) );
} };
export default ProjectFlockEdit; export default ProjectFlockEdit;
@@ -1,11 +1,11 @@
import SuspenseHelper from "@/components/helper/SuspenseHelper" import SuspenseHelper from '@/components/helper/SuspenseHelper';
const Layout = ({ const Layout = ({
children children,
}: Readonly<{ }: Readonly<{
children: React.ReactNode children: React.ReactNode;
}>) => { }>) => {
return <SuspenseHelper>{children}</SuspenseHelper> return <SuspenseHelper>{children}</SuspenseHelper>;
} };
export default Layout; export default Layout;
@@ -42,7 +42,11 @@ const ProjectFlockDetail = () => {
<span className='loading loading-spinner loading-xl' /> <span className='loading loading-spinner loading-xl' />
)} )}
{!isLoadingProjectFlock && isResponseSuccess(projectFlock) && ( {!isLoadingProjectFlock && isResponseSuccess(projectFlock) && (
<ProjectFlockForm formType='detail' initialValues={projectFlock.data} refreshProjectFlocks={refreshProjectFlock} /> <ProjectFlockForm
formType='detail'
initialValues={projectFlock.data}
refreshProjectFlocks={refreshProjectFlock}
/>
)} )}
</div> </div>
); );
+3 -3
View File
@@ -1,11 +1,11 @@
import ProjectFlockTable from "@/components/pages/production/project-flock/ProjectFlockTable"; import ProjectFlockTable from '@/components/pages/production/project-flock/ProjectFlockTable';
const ProjectFlock = () => { const ProjectFlock = () => {
return ( return (
<section className="w-full p-4"> <section className='w-full p-4'>
<ProjectFlockTable /> <ProjectFlockTable />
</section> </section>
); );
} };
export default ProjectFlock; export default ProjectFlock;
+13 -15
View File
@@ -1,13 +1,11 @@
'use client'; 'use client';
import { import { HTMLAttributes, ReactNode } from 'react';
HTMLAttributes,
ReactNode,
} from 'react';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
export interface CardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'className'> { export interface CardProps
extends Omit<HTMLAttributes<HTMLDivElement>, 'className'> {
title?: string; title?: string;
subtitle?: string; subtitle?: string;
image?: string; image?: string;
@@ -44,17 +42,17 @@ const Card = ({
const baseClasses = 'card bg-base-100'; const baseClasses = 'card bg-base-100';
const variantClasses = { const variantClasses = {
'default': '', default: '',
'compact': 'card-compact', compact: 'card-compact',
'bordered': 'border border-base-300', bordered: 'border border-base-300',
'shadow': 'shadow-xl', shadow: 'shadow-xl',
'image-full': 'card-side card-compact shadow-xl', 'image-full': 'card-side card-compact shadow-xl',
}; };
const sizeClasses = { const sizeClasses = {
'sm': 'w-64', sm: 'w-64',
'md': 'w-96', md: 'w-96',
'lg': 'w-[28rem]', lg: 'w-[28rem]',
}; };
return cn( return cn(
@@ -84,9 +82,9 @@ const Card = ({
const getTitleClasses = () => { const getTitleClasses = () => {
const sizeClasses = { const sizeClasses = {
'sm': 'text-lg', sm: 'text-lg',
'md': 'text-xl', md: 'text-xl',
'lg': 'text-2xl', lg: 'text-2xl',
}; };
return cn('card-title font-bold', sizeClasses[size], className?.title); return cn('card-title font-bold', sizeClasses[size], className?.title);
+2 -9
View File
@@ -1,10 +1,6 @@
'use client'; 'use client';
import { import { ChangeEventHandler, FocusEventHandler, ReactNode } from 'react';
ChangeEventHandler,
FocusEventHandler,
ReactNode,
} from 'react';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
@@ -109,10 +105,7 @@ const DateInput = ({
min={min} min={min}
max={max} max={max}
disabled={disabled} disabled={disabled}
className={cn( className={cn('grow bg-transparent cursor-pointer', className?.input)}
'grow bg-transparent cursor-pointer',
className?.input
)}
readOnly={readOnly} readOnly={readOnly}
/> />
+1 -4
View File
@@ -69,10 +69,7 @@ const FileInput = ({
onChange={onChange} onChange={onChange}
onBlur={onBlur} onBlur={onBlur}
disabled={disabled} disabled={disabled}
className={cn( className={cn('grow file-input w-full h-12 rounded', className?.input)}
'grow file-input w-full h-12 rounded',
className?.input
)}
readOnly={readOnly} readOnly={readOnly}
/> />
+8 -8
View File
@@ -1,10 +1,10 @@
"use client"; 'use client';
import { ChangeEvent, ReactNode } from "react"; import { ChangeEvent, ReactNode } from 'react';
import { NumericFormat, OnValueChange } from "react-number-format"; import { NumericFormat, OnValueChange } from 'react-number-format';
import TextInput, { TextInputProps } from "@/components/input/TextInput"; import TextInput, { TextInputProps } from '@/components/input/TextInput';
interface NumberInputProps extends Omit<TextInputProps, "type"> { interface NumberInputProps extends Omit<TextInputProps, 'type'> {
thousandSeparator?: string; thousandSeparator?: string;
decimalSeparator?: string; decimalSeparator?: string;
decimalScale?: number; decimalScale?: number;
@@ -17,8 +17,8 @@ interface NumberInputProps extends Omit<TextInputProps, "type"> {
} }
const NumberInput = ({ const NumberInput = ({
thousandSeparator = ",", thousandSeparator = ',',
decimalSeparator = ".", decimalSeparator = '.',
decimalScale = 5, decimalScale = 5,
allowNegative = true, allowNegative = true,
onChange, onChange,
@@ -28,7 +28,7 @@ const NumberInput = ({
}: NumberInputProps) => { }: NumberInputProps) => {
const valueChangeHandler: OnValueChange = ( const valueChangeHandler: OnValueChange = (
numberFormatValues, numberFormatValues,
sourceInfo, sourceInfo
) => { ) => {
const newChangeEvent = sourceInfo.event as const newChangeEvent = sourceInfo.event as
| ChangeEvent<HTMLInputElement> | ChangeEvent<HTMLInputElement>
+6 -2
View File
@@ -49,14 +49,18 @@ const MenuItem = ({
); );
return ( return (
<li onClick={onClick}> <li>
{href && ( {href && (
<Link href={href} className={menuItemBaseClassName}> <Link href={href} className={menuItemBaseClassName}>
{menuItemContent} {menuItemContent}
</Link> </Link>
)} )}
{!href && <a className={menuItemBaseClassName}>{menuItemContent}</a>} {!href && (
<button className={menuItemBaseClassName} onClick={onClick}>
{menuItemContent}
</button>
)}
</li> </li>
); );
}; };
@@ -10,11 +10,7 @@ import { inventoryAdjustmentApi } from '@/services/api/inventory';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { InventoryAdjustment } from '@/types/api/inventory/adjustment'; import { InventoryAdjustment } from '@/types/api/inventory/adjustment';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import { import { ColumnDef, ColumnSort, SortingState } from '@tanstack/react-table';
ColumnDef,
ColumnSort,
SortingState,
} from '@tanstack/react-table';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import useSWR from 'swr'; import useSWR from 'swr';
@@ -44,10 +40,7 @@ const InventoryAdjustmentTable = () => {
}); });
// Fetch Data // Fetch Data
const { const { data: inventoryAdjustments, isLoading } = useSWR(
data: inventoryAdjustments,
isLoading,
} = useSWR(
`${inventoryAdjustmentApi.basePath}${getTableFilterQueryString()}`, `${inventoryAdjustmentApi.basePath}${getTableFilterQueryString()}`,
inventoryAdjustmentApi.getAllFetcher inventoryAdjustmentApi.getAllFetcher
); );
@@ -187,8 +180,13 @@ const InventoryAdjustmentTable = () => {
<div className='w-full p-0 sm:p-4'> <div className='w-full p-0 sm:p-4'>
<div className='flex flex-col gap-2 mb-4'> <div className='flex flex-col gap-2 mb-4'>
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'> <div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
<div className='flex flex-row'> <div className='w-full flex flex-row'>
<Button href='/inventory/adjustment/add' color='primary'> <Button
href='/inventory/adjustment/add'
variant='outline'
color='primary'
className='w-full sm:w-fit'
>
<Icon icon='ic:round-plus' width={24} height={24} /> <Icon icon='ic:round-plus' width={24} height={24} />
Tambah Tambah
</Button> </Button>
@@ -211,7 +209,7 @@ const InventoryAdjustmentTable = () => {
value: tableFilterState.pageSize, value: tableFilterState.pageSize,
}} }}
onChange={pageSizeChangeHandler} onChange={pageSizeChangeHandler}
className={{ wrapper: 'max-w-28' }} className={{ wrapper: 'min-w-28' }}
/> />
</div> </div>
</div> </div>
@@ -4,24 +4,21 @@ export const InventoryAdjustmentFormSchema = Yup.object({
product_category: Yup.object({ product_category: Yup.object({
value: Yup.number().required('ID Kategori Produk wajib diisi!'), value: Yup.number().required('ID Kategori Produk wajib diisi!'),
label: Yup.string().required('Nama Kategori Produk wajib diisi!'), label: Yup.string().required('Nama Kategori Produk wajib diisi!'),
}) }).nullable(),
.nullable(),
product_category_id: Yup.number().nullable(), product_category_id: Yup.number().nullable(),
product: Yup.object({ product: Yup.object({
value: Yup.number().required('ID Produk wajib diisi!'), value: Yup.number().required('ID Produk wajib diisi!'),
label: Yup.string().required('Nama Produk wajib diisi!'), label: Yup.string().required('Nama Produk wajib diisi!'),
}) }).nullable(),
.nullable(),
product_id: Yup.number().nullable(), product_id: Yup.number().nullable(),
warehouse: Yup.object({ warehouse: Yup.object({
value: Yup.number().required('ID Gudang wajib diisi!'), value: Yup.number().required('ID Gudang wajib diisi!'),
label: Yup.string().required('Nama Gudang wajib diisi!'), label: Yup.string().required('Nama Gudang wajib diisi!'),
}) }).nullable(),
.nullable(),
warehouse_id: Yup.number().nullable(), warehouse_id: Yup.number().nullable(),
@@ -51,9 +51,8 @@ const InventoryAdjustmentForm = ({
// Submit Handler // Submit Handler
const createInventoryAdjustmentHandler = useCallback( const createInventoryAdjustmentHandler = useCallback(
async (payload: CreateInventoryAdjustmentPayload) => { async (payload: CreateInventoryAdjustmentPayload) => {
const createInventoryAdjustmentRes = await inventoryAdjustmentApi.create( const createInventoryAdjustmentRes =
payload await inventoryAdjustmentApi.create(payload);
);
if (isResponseError(createInventoryAdjustmentRes)) { if (isResponseError(createInventoryAdjustmentRes)) {
setInventoryAdjustmentFormErrorMessage( setInventoryAdjustmentFormErrorMessage(
@@ -68,7 +67,9 @@ const InventoryAdjustmentForm = ({
[router] [router]
); );
const formikInitialValues = useMemo<Partial<InventoryAdjustmentFormValues>>(() => { const formikInitialValues = useMemo<
Partial<InventoryAdjustmentFormValues>
>(() => {
return { return {
product_category_id: initialValues?.product_category?.id ?? 0, product_category_id: initialValues?.product_category?.id ?? 0,
product_id: initialValues?.product?.id ?? 0, product_id: initialValues?.product?.id ?? 0,
@@ -185,7 +186,6 @@ const InventoryAdjustmentForm = ({
warehouseChangeHandler(null); warehouseChangeHandler(null);
}; };
const { setValues: formikSetValues } = formik; const { setValues: formikSetValues } = formik;
// Effect // Effect
@@ -225,7 +225,13 @@ const InventoryAdjustmentForm = ({
const type = initialValues.transaction_type.toLowerCase(); const type = initialValues.transaction_type.toLowerCase();
setQuantityLabel(type === 'increase' ? 'Tambah Stok' : 'Kurangi Stok'); setQuantityLabel(type === 'increase' ? 'Tambah Stok' : 'Kurangi Stok');
} }
}, [formik, initialValues, setQuantityLabel, setDisabledProduct, setSelectedProductCategories]); }, [
formik,
initialValues,
setQuantityLabel,
setDisabledProduct,
setSelectedProductCategories,
]);
useEffect(() => { useEffect(() => {
formikSetValues(formikInitialValues as InventoryAdjustmentFormValues); formikSetValues(formikInitialValues as InventoryAdjustmentFormValues);
}, [formikSetValues, formikInitialValues]); }, [formikSetValues, formikInitialValues]);
@@ -364,7 +370,11 @@ const InventoryAdjustmentForm = ({
errorMessage={formik.errors.transaction_type as string} errorMessage={formik.errors.transaction_type as string}
variant='radio-primary' variant='radio-primary'
required required
bottomLabel={formik.values.transaction_type == undefined ? 'Pilih salah satu tipe transaksi' : undefined} bottomLabel={
formik.values.transaction_type == undefined
? 'Pilih salah satu tipe transaksi'
: undefined
}
disabled={type === 'detail'} disabled={type === 'detail'}
/> />
@@ -395,8 +405,6 @@ const InventoryAdjustmentForm = ({
readOnly={type === 'detail'} readOnly={type === 'detail'}
/> />
{/* Text Area Input Reason */} {/* Text Area Input Reason */}
<TextArea <TextArea
required required
@@ -413,14 +421,23 @@ const InventoryAdjustmentForm = ({
<div className='flex flex-row justify-between gap-2 flex-wrap'> <div className='flex flex-row justify-between gap-2 flex-wrap'>
{type !== 'detail' && ( {type !== 'detail' && (
<div className='flex flex-row justify-end gap-2'> <div className='flex flex-row justify-end gap-2'>
<Button type='button' color='warning' className='px-4' onClick={resetHandler}> <Button
type='button'
color='warning'
className='px-4'
onClick={resetHandler}
>
Reset Reset
</Button> </Button>
<Button <Button
type='submit' type='submit'
color='primary' color='primary'
isLoading={formik.isSubmitting} isLoading={formik.isSubmitting}
disabled={!formik.isValid || formik.isSubmitting || formik.values.product == undefined} disabled={
!formik.isValid ||
formik.isSubmitting ||
formik.values.product == undefined
}
className='px-4' className='px-4'
> >
Submit Submit
@@ -77,7 +77,7 @@ const MovementTable = () => {
<TableToolbar <TableToolbar
addButton={{ addButton={{
href: '/inventory/movement/add', href: '/inventory/movement/add',
label: 'Tambah Movement', label: 'Tambah',
}} }}
search={{ search={{
value: tableFilterState.search, value: tableFilterState.search,
@@ -14,6 +14,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import SelectInput, { OptionType } from '@/components/input/SelectInput'; import SelectInput, { OptionType } from '@/components/input/SelectInput';
import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { Area } from '@/types/api/master-data/area'; import { Area } from '@/types/api/master-data/area';
import { AreaApi } from '@/services/api/master-data'; import { AreaApi } from '@/services/api/master-data';
@@ -32,16 +33,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void; deleteClickHandler: () => void;
}) => { }) => {
return ( return (
<div <RowOptionsMenuWrapper type={type}>
tabIndex={type === 'dropdown' ? 0 : undefined}
className={cn(
{
'dropdown-content': type === 'dropdown',
'mt-2': type === 'collapse',
},
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
)}
>
<Button <Button
href={`/master-data/area/detail/?areaId=${props.row.original.id}`} href={`/master-data/area/detail/?areaId=${props.row.original.id}`}
variant='ghost' variant='ghost'
@@ -76,7 +68,7 @@ const RowOptionsMenu = ({
/> />
Delete Delete
</Button> </Button>
</div> </RowOptionsMenuWrapper>
); );
}; };
@@ -150,7 +142,7 @@ const AreasTable = () => {
{currentPageSize <= 2 && ( {currentPageSize <= 2 && (
<RowCollapseOptions> <RowCollapseOptions>
<RowOptionsMenu <RowOptionsMenu
type='dropdown' type='collapse'
props={props} props={props}
deleteClickHandler={deleteClickHandler} deleteClickHandler={deleteClickHandler}
/> />
@@ -199,10 +191,15 @@ const AreasTable = () => {
<div className='w-full p-0 sm:p-4'> <div className='w-full p-0 sm:p-4'>
<div className='flex flex-col gap-2 mb-4'> <div className='flex flex-col gap-2 mb-4'>
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'> <div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
<div className='flex flex-row'> <div className='w-full flex flex-row'>
<Button href='/master-data/area/add' color='primary'> <Button
href='/master-data/area/add'
variant='outline'
color='primary'
className='w-full sm:w-fit'
>
<Icon icon='ic:round-plus' width={24} height={24} /> <Icon icon='ic:round-plus' width={24} height={24} />
Tambah Area Tambah
</Button> </Button>
</div> </div>
@@ -14,6 +14,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import SelectInput, { OptionType } from '@/components/input/SelectInput'; import SelectInput, { OptionType } from '@/components/input/SelectInput';
import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { Bank } from '@/types/api/master-data/bank'; import { Bank } from '@/types/api/master-data/bank';
import { BankApi } from '@/services/api/master-data'; import { BankApi } from '@/services/api/master-data';
@@ -32,16 +33,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void; deleteClickHandler: () => void;
}) => { }) => {
return ( return (
<div <RowOptionsMenuWrapper type={type}>
tabIndex={type === 'dropdown' ? 0 : undefined}
className={cn(
{
'dropdown-content': type === 'dropdown',
'mt-2': type === 'collapse',
},
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
)}
>
<Button <Button
href={`/master-data/bank/detail/?bankId=${props.row.original.id}`} href={`/master-data/bank/detail/?bankId=${props.row.original.id}`}
variant='ghost' variant='ghost'
@@ -66,7 +58,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler} onClick={deleteClickHandler}
variant='ghost' variant='ghost'
color='error' color='error'
className='text-error hover:text-inherit' className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
> >
<Icon <Icon
icon='material-symbols:delete-outline-rounded' icon='material-symbols:delete-outline-rounded'
@@ -76,7 +68,7 @@ const RowOptionsMenu = ({
/> />
Delete Delete
</Button> </Button>
</div> </RowOptionsMenuWrapper>
); );
}; };
@@ -163,7 +155,7 @@ const BanksTable = () => {
{currentPageSize <= 2 && ( {currentPageSize <= 2 && (
<RowCollapseOptions> <RowCollapseOptions>
<RowOptionsMenu <RowOptionsMenu
type='dropdown' type='collapse'
props={props} props={props}
deleteClickHandler={deleteClickHandler} deleteClickHandler={deleteClickHandler}
/> />
@@ -212,10 +204,15 @@ const BanksTable = () => {
<div className='w-full p-0 sm:p-4'> <div className='w-full p-0 sm:p-4'>
<div className='flex flex-col gap-2 mb-4'> <div className='flex flex-col gap-2 mb-4'>
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'> <div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
<div className='flex flex-row'> <div className='w-full flex flex-row'>
<Button href='/master-data/bank/add' color='primary'> <Button
href='/master-data/bank/add'
variant='outline'
color='primary'
className='w-full sm:w-fit'
>
<Icon icon='ic:round-plus' width={24} height={24} /> <Icon icon='ic:round-plus' width={24} height={24} />
Tambah Bank Tambah
</Button> </Button>
</div> </div>
@@ -8,6 +8,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import Table from '@/components/Table'; import Table from '@/components/Table';
import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { ROWS_OPTIONS } from '@/config/constant'; import { ROWS_OPTIONS } from '@/config/constant';
import { isResponseSuccess } from '@/lib/api-helper'; import { isResponseSuccess } from '@/lib/api-helper';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
@@ -15,10 +16,7 @@ import { CustomerApi } from '@/services/api/master-data';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { Customer } from '@/types/api/master-data/customer'; import { Customer } from '@/types/api/master-data/customer';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import { import { CellContext, ColumnDef } from '@tanstack/react-table';
CellContext,
ColumnDef,
} from '@tanstack/react-table';
import { useState } from 'react'; import { useState } from 'react';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import useSWR from 'swr'; import useSWR from 'swr';
@@ -33,16 +31,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void; deleteClickHandler: () => void;
}) => { }) => {
return ( return (
<div <RowOptionsMenuWrapper type={type}>
tabIndex={type == 'dropdown' ? 0 : undefined}
className={cn(
{
'dropdown-content': type === 'dropdown',
'mt-2': type === 'collapse',
},
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
)}
>
<Button <Button
href={`/master-data/customer/detail/?customerId=${props.row.original.id}`} href={`/master-data/customer/detail/?customerId=${props.row.original.id}`}
variant='ghost' variant='ghost'
@@ -53,10 +42,10 @@ const RowOptionsMenu = ({
Detail Detail
</Button> </Button>
<Button <Button
className='justify-start text-sm'
href={`/master-data/customer/detail/edit/?customerId=${props.row.original.id}`} href={`/master-data/customer/detail/edit/?customerId=${props.row.original.id}`}
variant='ghost' variant='ghost'
color='warning' color='warning'
className='justify-start text-sm'
> >
<Icon icon='material-symbols:edit-outline' width={16} height={16} /> <Icon icon='material-symbols:edit-outline' width={16} height={16} />
Edit Edit
@@ -65,7 +54,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler} onClick={deleteClickHandler}
variant='ghost' variant='ghost'
color='error' color='error'
className='text-error hover:text-inherit' className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
> >
<Icon <Icon
icon='material-symbols:delete-outline-rounded' icon='material-symbols:delete-outline-rounded'
@@ -75,7 +64,7 @@ const RowOptionsMenu = ({
/> />
Delete Delete
</Button> </Button>
</div> </RowOptionsMenuWrapper>
); );
}; };
@@ -174,7 +163,7 @@ const CustomersTable = () => {
{currentPageSize <= 2 && ( {currentPageSize <= 2 && (
<RowCollapseOptions> <RowCollapseOptions>
<RowOptionsMenu <RowOptionsMenu
type='dropdown' type='collapse'
props={props} props={props}
deleteClickHandler={deleteClickHandler} deleteClickHandler={deleteClickHandler}
/> />
@@ -210,10 +199,15 @@ const CustomersTable = () => {
<div className='w-full p-0 sm:p-4'> <div className='w-full p-0 sm:p-4'>
<div className='flex flex-col gap-2 mb-4'> <div className='flex flex-col gap-2 mb-4'>
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'> <div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
<div className='flex flex-row'> <div className='w-full flex flex-row'>
<Button href='/master-data/customer/add' color='primary'> <Button
href='/master-data/customer/add'
variant='outline'
color='primary'
className='w-full sm:w-fit'
>
<Icon icon='ic:round-plus' width={24} height={24} /> <Icon icon='ic:round-plus' width={24} height={24} />
Tambah Customer Tambah
</Button> </Button>
</div> </div>
@@ -11,7 +11,11 @@ import {
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { CustomerFormSchema, CustomerFormValues, UpdateCustomerFormSchema } from '@/components/pages/master-data/customer/form/CustomerForm.schema'; import {
CustomerFormSchema,
CustomerFormValues,
UpdateCustomerFormSchema,
} from '@/components/pages/master-data/customer/form/CustomerForm.schema';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import Button from '@/components/Button'; import Button from '@/components/Button';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
@@ -150,7 +154,8 @@ const CustomerForm = ({
const formik = useFormik<CustomerFormValues>({ const formik = useFormik<CustomerFormValues>({
initialValues: formikInitialValues, initialValues: formikInitialValues,
enableReinitialize: true, enableReinitialize: true,
validationSchema: formType === 'edit' ? UpdateCustomerFormSchema : CustomerFormSchema, validationSchema:
formType === 'edit' ? UpdateCustomerFormSchema : CustomerFormSchema,
onSubmit: async (values) => { onSubmit: async (values) => {
// reset error message // reset error message
setCustomerFormErrorMessage(''); setCustomerFormErrorMessage('');
@@ -14,6 +14,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import SelectInput, { OptionType } from '@/components/input/SelectInput'; import SelectInput, { OptionType } from '@/components/input/SelectInput';
import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { Fcr } from '@/types/api/master-data/fcr'; import { Fcr } from '@/types/api/master-data/fcr';
import { FcrApi } from '@/services/api/master-data'; import { FcrApi } from '@/services/api/master-data';
@@ -32,16 +33,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void; deleteClickHandler: () => void;
}) => { }) => {
return ( return (
<div <RowOptionsMenuWrapper type={type}>
tabIndex={type === 'dropdown' ? 0 : undefined}
className={cn(
{
'dropdown-content': type === 'dropdown',
'mt-2': type === 'collapse',
},
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
)}
>
<Button <Button
href={`/master-data/fcr/detail/?fcrId=${props.row.original.id}`} href={`/master-data/fcr/detail/?fcrId=${props.row.original.id}`}
variant='ghost' variant='ghost'
@@ -66,7 +58,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler} onClick={deleteClickHandler}
variant='ghost' variant='ghost'
color='error' color='error'
className='text-error hover:text-inherit' className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
> >
<Icon <Icon
icon='material-symbols:delete-outline-rounded' icon='material-symbols:delete-outline-rounded'
@@ -76,7 +68,7 @@ const RowOptionsMenu = ({
/> />
Delete Delete
</Button> </Button>
</div> </RowOptionsMenuWrapper>
); );
}; };
@@ -150,7 +142,7 @@ const FcrsTable = () => {
{currentPageSize <= 2 && ( {currentPageSize <= 2 && (
<RowCollapseOptions> <RowCollapseOptions>
<RowOptionsMenu <RowOptionsMenu
type='dropdown' type='collapse'
props={props} props={props}
deleteClickHandler={deleteClickHandler} deleteClickHandler={deleteClickHandler}
/> />
@@ -199,10 +191,15 @@ const FcrsTable = () => {
<div className='w-full p-0 sm:p-4'> <div className='w-full p-0 sm:p-4'>
<div className='flex flex-col gap-2 mb-4'> <div className='flex flex-col gap-2 mb-4'>
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'> <div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
<div className='flex flex-row'> <div className='w-full flex flex-row'>
<Button href='/master-data/fcr/add' color='primary'> <Button
href='/master-data/fcr/add'
variant='outline'
color='primary'
className='w-full sm:w-fit'
>
<Icon icon='ic:round-plus' width={24} height={24} /> <Icon icon='ic:round-plus' width={24} height={24} />
Tambah FCR Tambah
</Button> </Button>
</div> </div>
@@ -12,6 +12,7 @@ import { FlockApi } from '@/services/api/master-data';
import { useModal } from '@/components/Modal'; import { useModal } from '@/components/Modal';
import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import DebouncedTextInput from '@/components/input/DebouncedTextInput'; import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import SelectInput, { OptionType } from '@/components/input/SelectInput'; import SelectInput, { OptionType } from '@/components/input/SelectInput';
@@ -30,16 +31,7 @@ const RowsOptions = ({
deleteClickHandler: () => void; deleteClickHandler: () => void;
}) => { }) => {
return ( return (
<div <RowOptionsMenuWrapper type={type}>
tabIndex={type == 'dropdown' ? 0 : undefined}
className={cn(
{
'dropdown-content': type === 'dropdown',
'mt-2': type === 'collapse',
},
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
)}
>
<Button <Button
href={`/master-data/flock/detail/edit/?flockId=${props.row.original.id}`} href={`/master-data/flock/detail/edit/?flockId=${props.row.original.id}`}
variant='ghost' variant='ghost'
@@ -72,7 +64,7 @@ const RowsOptions = ({
onClick={deleteClickHandler} onClick={deleteClickHandler}
variant='ghost' variant='ghost'
color='error' color='error'
className='text-error hover:text-inherit' className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
> >
<Icon <Icon
icon='material-symbols:delete-outline-rounded' icon='material-symbols:delete-outline-rounded'
@@ -82,7 +74,7 @@ const RowsOptions = ({
/> />
Delete Delete
</Button> </Button>
</div> </RowOptionsMenuWrapper>
); );
}; };
@@ -203,9 +195,15 @@ const FlockTable = () => {
<div className='w-full p-0 sm:p-4'> <div className='w-full p-0 sm:p-4'>
<div className='flex flex-col gap-2 mb-4'> <div className='flex flex-col gap-2 mb-4'>
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'> <div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
<div className='flex flex-row'> <div className='w-full flex flex-row'>
<Button href='/master-data/flock/add' color='primary'> <Button
Tambah Flock href='/master-data/flock/add'
variant='outline'
color='primary'
className='w-full sm:w-fit'
>
<Icon icon='ic:round-plus' width={24} height={24} />
Tambah
</Button> </Button>
</div> </div>
@@ -3,10 +3,7 @@ import * as Yup from 'yup';
export const FlockFormSchema = Yup.object({ export const FlockFormSchema = Yup.object({
name: Yup.string() name: Yup.string()
.required('Nama wajib diisi!') .required('Nama wajib diisi!')
.matches( .matches(/^[\p{L}\p{N}\s]+$/u, 'Nama tidak boleh mengandung simbol'),
/^[\p{L}\p{N}\s]+$/u,
'Nama tidak boleh mengandung simbol'
),
}); });
export const UpdateFlockFormSchema = FlockFormSchema; export const UpdateFlockFormSchema = FlockFormSchema;
@@ -1,11 +1,15 @@
'use client' 'use client';
import { useModal } from '@/components/Modal'; import { useModal } from '@/components/Modal';
import { FlockApi } from '@/services/api/master-data'; import { FlockApi } from '@/services/api/master-data';
import { Flock } from '@/types/api/master-data/flock'; import { Flock } from '@/types/api/master-data/flock';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { FlockFormSchema, FlockFormValues, UpdateFlockFormSchema } from '@/components/pages/master-data/flock/form/FlockForm.schema'; import {
FlockFormSchema,
FlockFormValues,
UpdateFlockFormSchema,
} from '@/components/pages/master-data/flock/form/FlockForm.schema';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import Button from '@/components/Button'; import Button from '@/components/Button';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
@@ -48,7 +52,8 @@ const FlockForm = ({ formType = 'add', initialValues }: FlockCustomProps) => {
const formik = useFormik<FlockFormValues>({ const formik = useFormik<FlockFormValues>({
initialValues: formikInitialValue, initialValues: formikInitialValue,
enableReinitialize: true, enableReinitialize: true,
validationSchema: formType === 'edit' ? UpdateFlockFormSchema : FlockFormSchema, validationSchema:
formType === 'edit' ? UpdateFlockFormSchema : FlockFormSchema,
onSubmit: async (values) => { onSubmit: async (values) => {
// reset error message // reset error message
setFlockFormErrorMessage(''); setFlockFormErrorMessage('');
@@ -19,6 +19,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import SelectInput, { OptionType } from '@/components/input/SelectInput'; import SelectInput, { OptionType } from '@/components/input/SelectInput';
import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { Kandang } from '@/types/api/master-data/kandang'; import { Kandang } from '@/types/api/master-data/kandang';
import { KandangApi } from '@/services/api/master-data'; import { KandangApi } from '@/services/api/master-data';
@@ -37,16 +38,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void; deleteClickHandler: () => void;
}) => { }) => {
return ( return (
<div <RowOptionsMenuWrapper type={type}>
tabIndex={type === 'dropdown' ? 0 : undefined}
className={cn(
{
'dropdown-content': type === 'dropdown',
'mt-2': type === 'collapse',
},
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
)}
>
<Button <Button
href={`/master-data/kandang/detail/?kandangId=${props.row.original.id}`} href={`/master-data/kandang/detail/?kandangId=${props.row.original.id}`}
variant='ghost' variant='ghost'
@@ -71,7 +63,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler} onClick={deleteClickHandler}
variant='ghost' variant='ghost'
color='error' color='error'
className='text-error hover:text-inherit' className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
> >
<Icon <Icon
icon='material-symbols:delete-outline-rounded' icon='material-symbols:delete-outline-rounded'
@@ -81,7 +73,7 @@ const RowOptionsMenu = ({
/> />
Delete Delete
</Button> </Button>
</div> </RowOptionsMenuWrapper>
); );
}; };
@@ -173,7 +165,7 @@ const KandangsTable = () => {
{currentPageSize <= 2 && ( {currentPageSize <= 2 && (
<RowCollapseOptions> <RowCollapseOptions>
<RowOptionsMenu <RowOptionsMenu
type='dropdown' type='collapse'
props={props} props={props}
deleteClickHandler={deleteClickHandler} deleteClickHandler={deleteClickHandler}
/> />
@@ -238,10 +230,15 @@ const KandangsTable = () => {
<div className='w-full p-0 sm:p-4'> <div className='w-full p-0 sm:p-4'>
<div className='flex flex-col gap-2 mb-4'> <div className='flex flex-col gap-2 mb-4'>
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'> <div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
<div className='flex flex-row'> <div className='w-full flex flex-row'>
<Button href='/master-data/kandang/add' color='primary'> <Button
href='/master-data/kandang/add'
variant='outline'
color='primary'
className='w-full sm:w-fit'
>
<Icon icon='ic:round-plus' width={24} height={24} /> <Icon icon='ic:round-plus' width={24} height={24} />
Tambah Kandang Tambah
</Button> </Button>
</div> </div>
@@ -19,6 +19,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import SelectInput, { OptionType } from '@/components/input/SelectInput'; import SelectInput, { OptionType } from '@/components/input/SelectInput';
import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { Location } from '@/types/api/master-data/location'; import { Location } from '@/types/api/master-data/location';
import { LocationApi } from '@/services/api/master-data'; import { LocationApi } from '@/services/api/master-data';
@@ -37,16 +38,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void; deleteClickHandler: () => void;
}) => { }) => {
return ( return (
<div <RowOptionsMenuWrapper type={type}>
tabIndex={type === 'dropdown' ? 0 : undefined}
className={cn(
{
'dropdown-content': type === 'dropdown',
'mt-2': type === 'collapse',
},
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
)}
>
<Button <Button
href={`/master-data/location/detail/?locationId=${props.row.original.id}`} href={`/master-data/location/detail/?locationId=${props.row.original.id}`}
variant='ghost' variant='ghost'
@@ -71,7 +63,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler} onClick={deleteClickHandler}
variant='ghost' variant='ghost'
color='error' color='error'
className='text-error hover:text-inherit' className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
> >
<Icon <Icon
icon='material-symbols:delete-outline-rounded' icon='material-symbols:delete-outline-rounded'
@@ -81,7 +73,7 @@ const RowOptionsMenu = ({
/> />
Delete Delete
</Button> </Button>
</div> </RowOptionsMenuWrapper>
); );
}; };
@@ -172,7 +164,7 @@ const LocationsTable = () => {
{currentPageSize <= 2 && ( {currentPageSize <= 2 && (
<RowCollapseOptions> <RowCollapseOptions>
<RowOptionsMenu <RowOptionsMenu
type='dropdown' type='collapse'
props={props} props={props}
deleteClickHandler={deleteClickHandler} deleteClickHandler={deleteClickHandler}
/> />
@@ -237,10 +229,15 @@ const LocationsTable = () => {
<div className='w-full p-0 sm:p-4'> <div className='w-full p-0 sm:p-4'>
<div className='flex flex-col gap-2 mb-4'> <div className='flex flex-col gap-2 mb-4'>
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'> <div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
<div className='flex flex-row'> <div className='w-full flex flex-row'>
<Button href='/master-data/location/add' color='primary'> <Button
href='/master-data/location/add'
variant='outline'
color='primary'
className='w-full sm:w-fit'
>
<Icon icon='ic:round-plus' width={24} height={24} /> <Icon icon='ic:round-plus' width={24} height={24} />
Tambah Location Tambah
</Button> </Button>
</div> </div>
@@ -19,6 +19,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import SelectInput, { OptionType } from '@/components/input/SelectInput'; import SelectInput, { OptionType } from '@/components/input/SelectInput';
import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { Nonstock } from '@/types/api/master-data/nonstock'; import { Nonstock } from '@/types/api/master-data/nonstock';
import { NonstockApi } from '@/services/api/master-data'; import { NonstockApi } from '@/services/api/master-data';
@@ -37,16 +38,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void; deleteClickHandler: () => void;
}) => { }) => {
return ( return (
<div <RowOptionsMenuWrapper type={type}>
tabIndex={type === 'dropdown' ? 0 : undefined}
className={cn(
{
'dropdown-content': type === 'dropdown',
'mt-2': type === 'collapse',
},
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
)}
>
<Button <Button
href={`/master-data/nonstock/detail/?nonstockId=${props.row.original.id}`} href={`/master-data/nonstock/detail/?nonstockId=${props.row.original.id}`}
variant='ghost' variant='ghost'
@@ -71,7 +63,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler} onClick={deleteClickHandler}
variant='ghost' variant='ghost'
color='error' color='error'
className='text-error hover:text-inherit' className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
> >
<Icon <Icon
icon='material-symbols:delete-outline-rounded' icon='material-symbols:delete-outline-rounded'
@@ -81,7 +73,7 @@ const RowOptionsMenu = ({
/> />
Delete Delete
</Button> </Button>
</div> </RowOptionsMenuWrapper>
); );
}; };
@@ -184,7 +176,7 @@ const NonstocksTable = () => {
{currentPageSize <= 2 && ( {currentPageSize <= 2 && (
<RowCollapseOptions> <RowCollapseOptions>
<RowOptionsMenu <RowOptionsMenu
type='dropdown' type='collapse'
props={props} props={props}
deleteClickHandler={deleteClickHandler} deleteClickHandler={deleteClickHandler}
/> />
@@ -249,10 +241,15 @@ const NonstocksTable = () => {
<div className='w-full p-0 sm:p-4'> <div className='w-full p-0 sm:p-4'>
<div className='flex flex-col gap-2 mb-4'> <div className='flex flex-col gap-2 mb-4'>
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'> <div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
<div className='flex flex-row'> <div className='w-full flex flex-row'>
<Button href='/master-data/nonstock/add' color='primary'> <Button
href='/master-data/nonstock/add'
variant='outline'
color='primary'
className='w-full sm:w-fit'
>
<Icon icon='ic:round-plus' width={24} height={24} /> <Icon icon='ic:round-plus' width={24} height={24} />
Tambah Nonstock Tambah
</Button> </Button>
</div> </div>
@@ -14,6 +14,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import SelectInput, { OptionType } from '@/components/input/SelectInput'; import SelectInput, { OptionType } from '@/components/input/SelectInput';
import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { ProductCategory } from '@/types/api/master-data/product-category'; import { ProductCategory } from '@/types/api/master-data/product-category';
import { ProductCategoryApi } from '@/services/api/master-data'; import { ProductCategoryApi } from '@/services/api/master-data';
@@ -32,16 +33,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void; deleteClickHandler: () => void;
}) => { }) => {
return ( return (
<div <RowOptionsMenuWrapper type={type}>
tabIndex={type === 'dropdown' ? 0 : undefined}
className={cn(
{
'dropdown-content': type === 'dropdown',
'mt-2': type === 'collapse',
},
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
)}
>
<Button <Button
href={`/master-data/product-category/detail/?productCategoryId=${props.row.original.id}`} href={`/master-data/product-category/detail/?productCategoryId=${props.row.original.id}`}
variant='ghost' variant='ghost'
@@ -64,7 +56,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler} onClick={deleteClickHandler}
variant='ghost' variant='ghost'
color='error' color='error'
className='text-error hover:text-inherit' className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
> >
<Icon <Icon
icon='mdi:delete-outline' icon='mdi:delete-outline'
@@ -74,7 +66,7 @@ const RowOptionsMenu = ({
/> />
Delete Delete
</Button> </Button>
</div> </RowOptionsMenuWrapper>
); );
}; };
@@ -154,7 +146,7 @@ const ProductCategoryTable = () => {
{currentPageSize <= 2 && ( {currentPageSize <= 2 && (
<RowCollapseOptions> <RowCollapseOptions>
<RowOptionsMenu <RowOptionsMenu
type='dropdown' type='collapse'
props={props} props={props}
deleteClickHandler={deleteClickHandler} deleteClickHandler={deleteClickHandler}
/> />
@@ -200,10 +192,15 @@ const ProductCategoryTable = () => {
<div className='w-full p-0 sm:p-4'> <div className='w-full p-0 sm:p-4'>
<div className='flex flex-col gap-2 mb-4'> <div className='flex flex-col gap-2 mb-4'>
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'> <div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
<div className='flex flex-row'> <div className='w-full flex flex-row'>
<Button href='/master-data/product-category/add' color='primary'> <Button
href='/master-data/product-category/add'
variant='outline'
color='primary'
className='w-full sm:w-fit'
>
<Icon icon='ic:round-plus' width={24} height={24} /> <Icon icon='ic:round-plus' width={24} height={24} />
Tambah Product Category Tambah
</Button> </Button>
</div> </div>
<DebouncedTextInput <DebouncedTextInput
@@ -1,10 +1,14 @@
import * as Yup from 'yup'; import * as Yup from 'yup';
export const ProductCategoryFormSchema = Yup.object({ export const ProductCategoryFormSchema = Yup.object({
code: Yup.string().required('Kode wajib diisi!').max(3, 'Kode kategori produk melebihi 3 karakter!'), code: Yup.string()
.required('Kode wajib diisi!')
.max(3, 'Kode kategori produk melebihi 3 karakter!'),
name: Yup.string().required('Nama wajib diisi!'), name: Yup.string().required('Nama wajib diisi!'),
}); });
export const UpdateProductCategoryFormSchema = ProductCategoryFormSchema; export const UpdateProductCategoryFormSchema = ProductCategoryFormSchema;
export type ProductCategoryFormValues = Yup.InferType<typeof ProductCategoryFormSchema>; export type ProductCategoryFormValues = Yup.InferType<
typeof ProductCategoryFormSchema
>;
@@ -30,7 +30,10 @@ interface ProductCategoryFormProps {
initialValues?: ProductCategory; initialValues?: ProductCategory;
} }
const ProductCategoryForm = ({ type = 'add', initialValues }: ProductCategoryFormProps) => { const ProductCategoryForm = ({
type = 'add',
initialValues,
}: ProductCategoryFormProps) => {
const router = useRouter(); const router = useRouter();
const deleteModal = useModal(); const deleteModal = useModal();
@@ -77,7 +80,10 @@ const ProductCategoryForm = ({ type = 'add', initialValues }: ProductCategoryFor
const formik = useFormik<ProductCategoryFormValues>({ const formik = useFormik<ProductCategoryFormValues>({
initialValues: formikInitialValues, initialValues: formikInitialValues,
validationSchema: type === 'edit' ? UpdateProductCategoryFormSchema : ProductCategoryFormSchema, validationSchema:
type === 'edit'
? UpdateProductCategoryFormSchema
: ProductCategoryFormSchema,
onSubmit: async (values) => { onSubmit: async (values) => {
setFormErrorMessage(''); setFormErrorMessage('');
@@ -91,7 +97,10 @@ const ProductCategoryForm = ({ type = 'add', initialValues }: ProductCategoryFor
await createProductCategoryHandler(payload); await createProductCategoryHandler(payload);
break; break;
case 'edit': case 'edit':
await updateProductCategoryHandler(initialValues?.id as number, payload); await updateProductCategoryHandler(
initialValues?.id as number,
payload
);
break; break;
} }
}, },
@@ -19,6 +19,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import SelectInput, { OptionType } from '@/components/input/SelectInput'; import SelectInput, { OptionType } from '@/components/input/SelectInput';
import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { Product } from '@/types/api/master-data/product'; import { Product } from '@/types/api/master-data/product';
import { ProductApi } from '@/services/api/master-data'; import { ProductApi } from '@/services/api/master-data';
@@ -36,16 +37,7 @@ const RowOptionsMenu = ({
props: CellContext<Product, unknown>; props: CellContext<Product, unknown>;
deleteClickHandler: () => void; deleteClickHandler: () => void;
}) => ( }) => (
<div <RowOptionsMenuWrapper type={type}>
tabIndex={type === 'dropdown' ? 0 : undefined}
className={cn(
{
'dropdown-content': type === 'dropdown',
'mt-2': type === 'collapse',
},
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
)}
>
<Button <Button
href={`/master-data/product/detail/?productId=${props.row.original.id}`} href={`/master-data/product/detail/?productId=${props.row.original.id}`}
variant='ghost' variant='ghost'
@@ -68,7 +60,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler} onClick={deleteClickHandler}
variant='ghost' variant='ghost'
color='error' color='error'
className='text-error hover:text-inherit' className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
> >
<Icon <Icon
icon='material-symbols:delete-outline-rounded' icon='material-symbols:delete-outline-rounded'
@@ -78,7 +70,7 @@ const RowOptionsMenu = ({
/> />
Delete Delete
</Button> </Button>
</div> </RowOptionsMenuWrapper>
); );
const ProductsTable = () => { const ProductsTable = () => {
@@ -217,7 +209,7 @@ const ProductsTable = () => {
{currentPageSize <= 2 && ( {currentPageSize <= 2 && (
<RowCollapseOptions> <RowCollapseOptions>
<RowOptionsMenu <RowOptionsMenu
type='dropdown' type='collapse'
props={props} props={props}
deleteClickHandler={deleteClickHandler} deleteClickHandler={deleteClickHandler}
/> />
@@ -280,10 +272,15 @@ const ProductsTable = () => {
<div className='w-full p-0 sm:p-4'> <div className='w-full p-0 sm:p-4'>
<div className='flex flex-col gap-2 mb-4'> <div className='flex flex-col gap-2 mb-4'>
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'> <div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
<div className='flex flex-row'> <div className='w-full flex flex-row'>
<Button href='/master-data/product/add' color='primary'> <Button
href='/master-data/product/add'
variant='outline'
className='w-full sm:w-fit'
color='primary'
>
<Icon icon='ic:round-plus' width={24} height={24} /> <Icon icon='ic:round-plus' width={24} height={24} />
Tambah Produk Tambah
</Button> </Button>
</div> </div>
<DebouncedTextInput <DebouncedTextInput
@@ -8,7 +8,9 @@ export const ProductFormSchema = Yup.object({
value: Yup.number().min(1).required(), value: Yup.number().min(1).required(),
label: Yup.string().required(), label: Yup.string().required(),
}).nullable(), }).nullable(),
uom_id: Yup.number().required('Satuan wajib diisi!').typeError('Satuan wajib diisi!'), uom_id: Yup.number()
.required('Satuan wajib diisi!')
.typeError('Satuan wajib diisi!'),
product_category: Yup.object({ product_category: Yup.object({
value: Yup.number().min(1).required(), value: Yup.number().min(1).required(),
label: Yup.string().required(), label: Yup.string().required(),
@@ -50,4 +52,3 @@ export const ProductFormSchema = Yup.object({
export const UpdateProductFormSchema = ProductFormSchema; export const UpdateProductFormSchema = ProductFormSchema;
export type ProductFormValues = Yup.InferType<typeof ProductFormSchema>; export type ProductFormValues = Yup.InferType<typeof ProductFormSchema>;
@@ -24,7 +24,12 @@ import {
CreateProductPayload, CreateProductPayload,
UpdateProductPayload, UpdateProductPayload,
} from '@/types/api/master-data/product'; } from '@/types/api/master-data/product';
import { UomApi, ProductCategoryApi, SupplierApi, ProductApi } from '@/services/api/master-data'; import {
UomApi,
ProductCategoryApi,
SupplierApi,
ProductApi,
} from '@/services/api/master-data';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
import { PRODUCT_FLAG_OPTIONS } from '@/config/constant'; import { PRODUCT_FLAG_OPTIONS } from '@/config/constant';
@@ -67,7 +72,8 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
[router] [router]
); );
const formikInitialValues = useMemo<ProductFormValues>(() => ({ const formikInitialValues = useMemo<ProductFormValues>(
() => ({
name: initialValues?.name ?? '', name: initialValues?.name ?? '',
brand: initialValues?.brand ?? '', brand: initialValues?.brand ?? '',
sku: initialValues?.sku ?? '', sku: initialValues?.sku ?? '',
@@ -76,7 +82,10 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
: null, : null,
uom_id: initialValues?.uom?.id ?? 0, uom_id: initialValues?.uom?.id ?? 0,
product_category: initialValues?.product_category product_category: initialValues?.product_category
? { value: initialValues.product_category.id, label: initialValues.product_category.name } ? {
value: initialValues.product_category.id,
label: initialValues.product_category.name,
}
: null, : null,
product_category_id: initialValues?.product_category?.id ?? 0, product_category_id: initialValues?.product_category?.id ?? 0,
product_price: initialValues?.product_price ?? 0, product_price: initialValues?.product_price ?? 0,
@@ -84,13 +93,16 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
tax: initialValues?.tax ?? 0, tax: initialValues?.tax ?? 0,
expiry_period: initialValues?.expiry_period ?? 0, expiry_period: initialValues?.expiry_period ?? 0,
supplier: null, // not used for payload, just for UI supplier: null, // not used for payload, just for UI
supplier_ids: initialValues?.suppliers?.map(s => s.id) ?? [], supplier_ids: initialValues?.suppliers?.map((s) => s.id) ?? [],
flags: initialValues?.flags ?? [], flags: initialValues?.flags ?? [],
}), [initialValues]); }),
[initialValues]
);
const formik = useFormik<ProductFormValues>({ const formik = useFormik<ProductFormValues>({
initialValues: formikInitialValues, initialValues: formikInitialValues,
validationSchema: type === 'edit' ? UpdateProductFormSchema : ProductFormSchema, validationSchema:
type === 'edit' ? UpdateProductFormSchema : ProductFormSchema,
onSubmit: async (values) => { onSubmit: async (values) => {
setProductFormErrorMessage(''); setProductFormErrorMessage('');
const payload: CreateProductPayload = { const payload: CreateProductPayload = {
@@ -103,8 +115,12 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
selling_price: values.selling_price, selling_price: values.selling_price,
tax: values.tax, tax: values.tax,
expiry_period: values.expiry_period, expiry_period: values.expiry_period,
supplier_ids: (values.supplier_ids ?? []).filter((id): id is number => typeof id === 'number'), supplier_ids: (values.supplier_ids ?? []).filter(
flags: (values.flags ?? []).filter((f): f is string => typeof f === 'string'), (id): id is number => typeof id === 'number'
),
flags: (values.flags ?? []).filter(
(f): f is string => typeof f === 'string'
),
}; };
switch (type) { switch (type) {
case 'add': case 'add':
@@ -122,7 +138,10 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
// UOM // UOM
const [uomSelectInputValue, setUomSelectInputValue] = useState(''); const [uomSelectInputValue, setUomSelectInputValue] = useState('');
const uomsUrl = `${UomApi.basePath}?${new URLSearchParams({ search: uomSelectInputValue ?? '' }).toString()}`; const uomsUrl = `${UomApi.basePath}?${new URLSearchParams({ search: uomSelectInputValue ?? '' }).toString()}`;
const { data: uoms, isLoading: isLoadingUoms } = useSWR(uomsUrl, UomApi.getAllFetcher); const { data: uoms, isLoading: isLoadingUoms } = useSWR(
uomsUrl,
UomApi.getAllFetcher
);
const uomOptions = isResponseSuccess(uoms) const uomOptions = isResponseSuccess(uoms)
? uoms?.data.map((uom) => ({ value: uom.id, label: uom.name })) ? uoms?.data.map((uom) => ({ value: uom.id, label: uom.name }))
: []; : [];
@@ -136,7 +155,10 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
// Product Category // Product Category
const [categorySelectInputValue, setCategorySelectInputValue] = useState(''); const [categorySelectInputValue, setCategorySelectInputValue] = useState('');
const categoriesUrl = `${ProductCategoryApi.basePath}?${new URLSearchParams({ search: categorySelectInputValue ?? '' }).toString()}`; const categoriesUrl = `${ProductCategoryApi.basePath}?${new URLSearchParams({ search: categorySelectInputValue ?? '' }).toString()}`;
const { data: categories, isLoading: isLoadingCategories } = useSWR(categoriesUrl, ProductCategoryApi.getAllFetcher); const { data: categories, isLoading: isLoadingCategories } = useSWR(
categoriesUrl,
ProductCategoryApi.getAllFetcher
);
const categoryOptions = isResponseSuccess(categories) const categoryOptions = isResponseSuccess(categories)
? categories?.data.map((cat) => ({ value: cat.id, label: cat.name })) ? categories?.data.map((cat) => ({ value: cat.id, label: cat.name }))
: []; : [];
@@ -150,7 +172,10 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
// Supplier (multi select) // Supplier (multi select)
const [supplierSelectInputValue, setSupplierSelectInputValue] = useState(''); const [supplierSelectInputValue, setSupplierSelectInputValue] = useState('');
const suppliersUrl = `${SupplierApi.basePath}?${new URLSearchParams({ search: supplierSelectInputValue ?? '' }).toString()}`; const suppliersUrl = `${SupplierApi.basePath}?${new URLSearchParams({ search: supplierSelectInputValue ?? '' }).toString()}`;
const { data: suppliers, isLoading: isLoadingSuppliers } = useSWR(suppliersUrl, SupplierApi.getAllFetcher); const { data: suppliers, isLoading: isLoadingSuppliers } = useSWR(
suppliersUrl,
SupplierApi.getAllFetcher
);
const supplierOptions = isResponseSuccess(suppliers) const supplierOptions = isResponseSuccess(suppliers)
? suppliers?.data ? suppliers?.data
.filter((sup) => sup.category === 'SAPRONAK') .filter((sup) => sup.category === 'SAPRONAK')
@@ -159,7 +184,10 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
const supplierChangeHandler = (val: OptionType | OptionType[] | null) => { const supplierChangeHandler = (val: OptionType | OptionType[] | null) => {
const arr = Array.isArray(val) ? val : val ? [val] : []; const arr = Array.isArray(val) ? val : val ? [val] : [];
formik.setFieldTouched('supplier_ids', true); formik.setFieldTouched('supplier_ids', true);
formik.setFieldValue('supplier_ids', arr.map((v) => (v as OptionType).value)); formik.setFieldValue(
'supplier_ids',
arr.map((v) => (v as OptionType).value)
);
}; };
const deleteProductClickHandler = () => { const deleteProductClickHandler = () => {
@@ -260,7 +288,10 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
options={categoryOptions} options={categoryOptions}
onInputChange={setCategorySelectInputValue} onInputChange={setCategorySelectInputValue}
isLoading={isLoadingCategories} isLoading={isLoadingCategories}
isError={formik.touched.product_category_id && Boolean(formik.errors.product_category_id)} isError={
formik.touched.product_category_id &&
Boolean(formik.errors.product_category_id)
}
errorMessage={formik.errors.product_category_id as string} errorMessage={formik.errors.product_category_id as string}
isDisabled={type === 'detail'} isDisabled={type === 'detail'}
isClearable isClearable
@@ -274,7 +305,10 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
value={formik.values.product_price} value={formik.values.product_price}
onChange={formik.handleChange} onChange={formik.handleChange}
onBlur={formik.handleBlur} onBlur={formik.handleBlur}
isError={formik.touched.product_price && Boolean(formik.errors.product_price)} isError={
formik.touched.product_price &&
Boolean(formik.errors.product_price)
}
errorMessage={formik.errors.product_price as string} errorMessage={formik.errors.product_price as string}
readOnly={type === 'detail'} readOnly={type === 'detail'}
/> />
@@ -287,7 +321,10 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
value={formik.values.selling_price} value={formik.values.selling_price}
onChange={formik.handleChange} onChange={formik.handleChange}
onBlur={formik.handleBlur} onBlur={formik.handleBlur}
isError={formik.touched.selling_price && Boolean(formik.errors.selling_price)} isError={
formik.touched.selling_price &&
Boolean(formik.errors.selling_price)
}
errorMessage={formik.errors.selling_price as string} errorMessage={formik.errors.selling_price as string}
readOnly={type === 'detail'} readOnly={type === 'detail'}
/> />
@@ -313,7 +350,10 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
value={formik.values.expiry_period} value={formik.values.expiry_period}
onChange={formik.handleChange} onChange={formik.handleChange}
onBlur={formik.handleBlur} onBlur={formik.handleBlur}
isError={formik.touched.expiry_period && Boolean(formik.errors.expiry_period)} isError={
formik.touched.expiry_period &&
Boolean(formik.errors.expiry_period)
}
errorMessage={formik.errors.expiry_period as string} errorMessage={formik.errors.expiry_period as string}
readOnly={type === 'detail'} readOnly={type === 'detail'}
/> />
@@ -321,12 +361,17 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
required required
label='Supplier' label='Supplier'
isMulti isMulti
value={supplierOptions.filter(opt => formik.values.supplier_ids.includes(opt.value))} value={supplierOptions.filter((opt) =>
formik.values.supplier_ids.includes(opt.value)
)}
onChange={supplierChangeHandler} onChange={supplierChangeHandler}
options={supplierOptions} options={supplierOptions}
onInputChange={setSupplierSelectInputValue} onInputChange={setSupplierSelectInputValue}
isLoading={isLoadingSuppliers} isLoading={isLoadingSuppliers}
isError={formik.touched.supplier_ids && Boolean(formik.errors.supplier_ids)} isError={
formik.touched.supplier_ids &&
Boolean(formik.errors.supplier_ids)
}
errorMessage={formik.errors.supplier_ids as string} errorMessage={formik.errors.supplier_ids as string}
isDisabled={type === 'detail'} isDisabled={type === 'detail'}
isClearable isClearable
@@ -335,10 +380,15 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
required required
label='Flags' label='Flags'
isMulti isMulti
value={PRODUCT_FLAG_OPTIONS.filter(opt => formik.values.flags.includes(opt.value))} value={PRODUCT_FLAG_OPTIONS.filter((opt) =>
onChange={val => { formik.values.flags.includes(opt.value)
)}
onChange={(val) => {
const arr = Array.isArray(val) ? val : val ? [val] : []; const arr = Array.isArray(val) ? val : val ? [val] : [];
formik.setFieldValue('flags', arr.map((v) => (v as OptionType).value)); formik.setFieldValue(
'flags',
arr.map((v) => (v as OptionType).value)
);
}} }}
options={PRODUCT_FLAG_OPTIONS} options={PRODUCT_FLAG_OPTIONS}
isError={formik.touched.flags && Boolean(formik.errors.flags)} isError={formik.touched.flags && Boolean(formik.errors.flags)}
@@ -8,6 +8,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import Table from '@/components/Table'; import Table from '@/components/Table';
import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { ROWS_OPTIONS } from '@/config/constant'; import { ROWS_OPTIONS } from '@/config/constant';
import { isResponseSuccess } from '@/lib/api-helper'; import { isResponseSuccess } from '@/lib/api-helper';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
@@ -30,16 +31,7 @@ const RowOptions = ({
deleteClickHandler: () => void; deleteClickHandler: () => void;
}) => { }) => {
return ( return (
<div <RowOptionsMenuWrapper type={type}>
tabIndex={type == 'dropdown' ? 0 : undefined}
className={cn(
{
'dropdown-content': type === 'dropdown',
'mt-2': type === 'collapse',
},
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
)}
>
<Button <Button
href={`/master-data/supplier/detail/?supplierId=${props.row.original.id}`} href={`/master-data/supplier/detail/?supplierId=${props.row.original.id}`}
variant='ghost' variant='ghost'
@@ -72,7 +64,7 @@ const RowOptions = ({
onClick={deleteClickHandler} onClick={deleteClickHandler}
variant='ghost' variant='ghost'
color='error' color='error'
className='text-error hover:text-inherit' className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
> >
<Icon <Icon
icon='material-symbols:delete-outline-rounded' icon='material-symbols:delete-outline-rounded'
@@ -82,7 +74,7 @@ const RowOptions = ({
/> />
Delete Delete
</Button> </Button>
</div> </RowOptionsMenuWrapper>
); );
}; };
@@ -226,10 +218,15 @@ const SuppliersTable = () => {
<div className='w-full p-0 sm:p-4'> <div className='w-full p-0 sm:p-4'>
<div className='flex flex-col gap-2 mb-4'> <div className='flex flex-col gap-2 mb-4'>
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'> <div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
<div className='flex flex-row'> <div className='w-full flex flex-row'>
<Button href='/master-data/supplier/add' color='primary'> <Button
href='/master-data/supplier/add'
variant='outline'
color='primary'
className='w-full sm:w-fit'
>
<Icon icon='ic:round-plus' width={24} height={24} /> <Icon icon='ic:round-plus' width={24} height={24} />
Tambah Supplier Tambah
</Button> </Button>
</div> </div>
@@ -3,20 +3,21 @@ import * as Yup from 'yup';
export const SupplierFormSchema = Yup.object({ export const SupplierFormSchema = Yup.object({
name: Yup.string().required('Nama wajib diisi!'), name: Yup.string().required('Nama wajib diisi!'),
alias: Yup.string() alias: Yup.string()
.matches(/^[A-Za-z0-9]+$/, 'Alias hanya boleh berisi huruf dan angka tanpa spasi atau simbol!') .matches(
/^[A-Za-z0-9]+$/,
'Alias hanya boleh berisi huruf dan angka tanpa spasi atau simbol!'
)
.max(5, 'Alias maksimal 5 karakter!') .max(5, 'Alias maksimal 5 karakter!')
.required('Alias wajib diisi!'), .required('Alias wajib diisi!'),
pic: Yup.string().required('PIC wajib diisi!'), pic: Yup.string().required('PIC wajib diisi!'),
type: Yup.object({ type: Yup.object({
value: Yup.string().required(), value: Yup.string().required(),
label: Yup.string().required(), label: Yup.string().required(),
}) }).required('Tipe wajib diisi!'),
.required('Tipe wajib diisi!'),
category: Yup.object({ category: Yup.object({
value: Yup.string().required(), value: Yup.string().required(),
label: Yup.string().required(), label: Yup.string().required(),
}) }).required('Tipe wajib diisi!'),
.required('Tipe wajib diisi!'),
hatchery: Yup.string().required('Hatchery wajib diisi!'), hatchery: Yup.string().required('Hatchery wajib diisi!'),
phone: Yup.string() phone: Yup.string()
.matches(/^[0-9]+$/, 'Nomor telepon hanya boleh berisi angka!') .matches(/^[0-9]+$/, 'Nomor telepon hanya boleh berisi angka!')
@@ -33,7 +34,9 @@ export const SupplierFormSchema = Yup.object({
account_number: Yup.string() account_number: Yup.string()
.matches(/^[0-9]+$/, 'Nomor rekening hanya boleh berisi angka!') .matches(/^[0-9]+$/, 'Nomor rekening hanya boleh berisi angka!')
.required('Nomor rekening wajib diisi!'), .required('Nomor rekening wajib diisi!'),
due_date: Yup.number().min(1, 'Tanggal jatuh tempo wajib diisi!').required('Tanggal jatuh tempo wajib diisi!'), due_date: Yup.number()
.min(1, 'Tanggal jatuh tempo wajib diisi!')
.required('Tanggal jatuh tempo wajib diisi!'),
}); });
export const UpdateSupplierFormSchema = SupplierFormSchema; export const UpdateSupplierFormSchema = SupplierFormSchema;
@@ -41,7 +41,9 @@ const SupplierForm = ({
// Setup State // Setup State
const [supplierFormErrorMessage, setSupplierFormErrorMessage] = useState(''); const [supplierFormErrorMessage, setSupplierFormErrorMessage] = useState('');
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [hatcheryOptionsValues, setHatcheryOptionValues] = useState<OptionType[]>([]); const [hatcheryOptionsValues, setHatcheryOptionValues] = useState<
OptionType[]
>([]);
// -- Options data mapping // -- Options data mapping
const typeOptions = TYPE_OPTIONS; const typeOptions = TYPE_OPTIONS;
@@ -177,11 +179,13 @@ const SupplierForm = ({
} }
}, [formikSetValues, formikInitialValues, setHatcheryOptionValues]); }, [formikSetValues, formikInitialValues, setHatcheryOptionValues]);
useEffect(() => { useEffect(() => {
const commaSeparatedValues = hatcheryOptionsValues.map((item) => item.value).join(','); const commaSeparatedValues = hatcheryOptionsValues
.map((item) => item.value)
.join(',');
formikSetValues({ formikSetValues({
...formik.values, ...formik.values,
hatchery: commaSeparatedValues, hatchery: commaSeparatedValues,
}) });
}, [hatcheryOptionsValues, formikSetValues]); }, [hatcheryOptionsValues, formikSetValues]);
// Option Handler // Option Handler
@@ -305,7 +309,9 @@ const SupplierForm = ({
console.log(val); // pastikan val = array of { value, label } console.log(val); // pastikan val = array of { value, label }
setHatcheryOptionValues(val as OptionType[]); setHatcheryOptionValues(val as OptionType[]);
}} }}
isError={formik.touched.hatchery && Boolean(formik.errors.hatchery)} isError={
formik.touched.hatchery && Boolean(formik.errors.hatchery)
}
errorMessage={formik.errors.hatchery as string} errorMessage={formik.errors.hatchery as string}
isDisabled={formType === 'detail'} isDisabled={formType === 'detail'}
isClearable isClearable
@@ -14,6 +14,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import SelectInput, { OptionType } from '@/components/input/SelectInput'; import SelectInput, { OptionType } from '@/components/input/SelectInput';
import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { Uom } from '@/types/api/master-data/uom'; import { Uom } from '@/types/api/master-data/uom';
import { UomApi } from '@/services/api/master-data'; import { UomApi } from '@/services/api/master-data';
@@ -32,16 +33,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void; deleteClickHandler: () => void;
}) => { }) => {
return ( return (
<div <RowOptionsMenuWrapper type={type}>
tabIndex={type === 'dropdown' ? 0 : undefined}
className={cn(
{
'dropdown-content': type === 'dropdown',
'mt-2': type === 'collapse',
},
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
)}
>
<Button <Button
href={`/master-data/uom/detail/?uomId=${props.row.original.id}`} href={`/master-data/uom/detail/?uomId=${props.row.original.id}`}
variant='ghost' variant='ghost'
@@ -66,7 +58,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler} onClick={deleteClickHandler}
variant='ghost' variant='ghost'
color='error' color='error'
className='text-error hover:text-inherit' className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
> >
<Icon <Icon
icon='material-symbols:delete-outline-rounded' icon='material-symbols:delete-outline-rounded'
@@ -76,7 +68,7 @@ const RowOptionsMenu = ({
/> />
Delete Delete
</Button> </Button>
</div> </RowOptionsMenuWrapper>
); );
}; };
@@ -150,7 +142,7 @@ const UomsTable = () => {
{currentPageSize <= 2 && ( {currentPageSize <= 2 && (
<RowCollapseOptions> <RowCollapseOptions>
<RowOptionsMenu <RowOptionsMenu
type='dropdown' type='collapse'
props={props} props={props}
deleteClickHandler={deleteClickHandler} deleteClickHandler={deleteClickHandler}
/> />
@@ -199,10 +191,15 @@ const UomsTable = () => {
<div className='w-full p-0 sm:p-4'> <div className='w-full p-0 sm:p-4'>
<div className='flex flex-col gap-2 mb-4'> <div className='flex flex-col gap-2 mb-4'>
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'> <div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
<div className='flex flex-row'> <div className='w-full flex flex-row'>
<Button href='/master-data/uom/add' color='primary'> <Button
href='/master-data/uom/add'
variant='outline'
color='primary'
className='w-full sm:w-fit'
>
<Icon icon='ic:round-plus' width={24} height={24} /> <Icon icon='ic:round-plus' width={24} height={24} />
Tambah UOM Tambah
</Button> </Button>
</div> </div>
@@ -19,6 +19,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import SelectInput, { OptionType } from '@/components/input/SelectInput'; import SelectInput, { OptionType } from '@/components/input/SelectInput';
import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { Warehouse } from '@/types/api/master-data/warehouse'; import { Warehouse } from '@/types/api/master-data/warehouse';
import { WarehouseApi } from '@/services/api/master-data'; import { WarehouseApi } from '@/services/api/master-data';
@@ -37,16 +38,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void; deleteClickHandler: () => void;
}) => { }) => {
return ( return (
<div <RowOptionsMenuWrapper type={type}>
tabIndex={type === 'dropdown' ? 0 : undefined}
className={cn(
{
'dropdown-content': type === 'dropdown',
'mt-2': type === 'collapse',
},
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
)}
>
<Button <Button
href={`/master-data/warehouse/detail/?warehouseId=${props.row.original.id}`} href={`/master-data/warehouse/detail/?warehouseId=${props.row.original.id}`}
variant='ghost' variant='ghost'
@@ -81,7 +73,7 @@ const RowOptionsMenu = ({
/> />
Delete Delete
</Button> </Button>
</div> </RowOptionsMenuWrapper>
); );
}; };
@@ -206,7 +198,7 @@ const WarehousesTable = () => {
{currentPageSize <= 2 && ( {currentPageSize <= 2 && (
<RowCollapseOptions> <RowCollapseOptions>
<RowOptionsMenu <RowOptionsMenu
type='dropdown' type='collapse'
props={props} props={props}
deleteClickHandler={deleteClickHandler} deleteClickHandler={deleteClickHandler}
/> />
@@ -277,10 +269,15 @@ const WarehousesTable = () => {
<div className='w-full p-0 sm:p-4'> <div className='w-full p-0 sm:p-4'>
<div className='flex flex-col gap-2 mb-4'> <div className='flex flex-col gap-2 mb-4'>
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'> <div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
<div className='flex flex-row'> <div className='w-full flex flex-row'>
<Button href='/master-data/warehouse/add' color='primary'> <Button
href='/master-data/warehouse/add'
variant='outline'
color='primary'
className='w-full sm:w-fit'
>
<Icon icon='ic:round-plus' width={24} height={24} /> <Icon icon='ic:round-plus' width={24} height={24} />
Tambah Warehouse Tambah
</Button> </Button>
</div> </div>
@@ -8,6 +8,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import Table from '@/components/Table'; import Table from '@/components/Table';
import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { TableRowSizeSelector } from '@/components/table/TableRowSizeSelector'; import { TableRowSizeSelector } from '@/components/table/TableRowSizeSelector';
import { ROWS_OPTIONS } from '@/config/constant'; import { ROWS_OPTIONS } from '@/config/constant';
import { isResponseSuccess } from '@/lib/api-helper'; import { isResponseSuccess } from '@/lib/api-helper';
@@ -87,7 +88,9 @@ const ChickinTable = () => {
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'> <div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
<Button <Button
href='/production/chickin/add?projectFlockId=1' href='/production/chickin/add?projectFlockId=1'
variant='outline'
color='primary' color='primary'
className='w-full sm:w-fit'
> >
<Icon icon='uil:plus' width={24} height={24} /> <Icon icon='uil:plus' width={24} height={24} />
Tambah Tambah
@@ -130,20 +133,20 @@ const ChickinTable = () => {
} else { } else {
return '-'; return '-';
} }
} },
}, },
{ {
accessorFn: (row) => row.chick_in_date, accessorFn: (row) => row.chick_in_date,
header: 'Tanggal Chickin', header: 'Tanggal Chickin',
cell: (props) => { cell: (props) => {
if (props.row.original.chick_in_date) { if (props.row.original.chick_in_date) {
return new Date(props.row.original.chick_in_date).toLocaleDateString( return new Date(
'id-ID' props.row.original.chick_in_date
); ).toLocaleDateString('id-ID');
} else { } else {
return '-'; return '-';
} }
} },
}, },
{ {
accessorFn: (row) => row.note, accessorFn: (row) => row.note,
@@ -240,7 +243,9 @@ const ChickinTable = () => {
<Modal ref={chickinModal.ref}> <Modal ref={chickinModal.ref}>
<div className='flex flex-row justify-between items-center'> <div className='flex flex-row justify-between items-center'>
<h1 className='text-xl font-semibold text-center mb-6'> <h1 className='text-xl font-semibold text-center mb-6'>
Chickin Kandang - { selectedChickin?.project_flock_kandang && selectedChickin?.project_flock_kandang.kandang?.name} Chickin Kandang -{' '}
{selectedChickin?.project_flock_kandang &&
selectedChickin?.project_flock_kandang.kandang?.name}
</h1> </h1>
<Button <Button
color='error' color='error'
@@ -255,10 +260,14 @@ const ChickinTable = () => {
/> />
</Button> </Button>
</div> </div>
<ChickinForm initialValues={selectedChickin} formType='edit' afterSubmit={() => { <ChickinForm
refreshChickins() initialValues={selectedChickin}
chickinModal.closeModal() formType='edit'
}}/> afterSubmit={() => {
refreshChickins();
chickinModal.closeModal();
}}
/>
</Modal> </Modal>
</> </>
); );
@@ -276,16 +285,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void; deleteClickHandler: () => void;
}) => { }) => {
return ( return (
<div <RowOptionsMenuWrapper type={type}>
tabIndex={type == 'dropdown' ? 0 : undefined}
className={cn(
{
'dropdown-content': type === 'dropdown',
'mt-2': type === 'collapse',
},
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
)}
>
<Button <Button
href={`/production/chickin/detail?chickinId=${props.row.original.id}`} href={`/production/chickin/detail?chickinId=${props.row.original.id}`}
variant='ghost' variant='ghost'
@@ -308,7 +308,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler} onClick={deleteClickHandler}
variant='ghost' variant='ghost'
color='error' color='error'
className='text-error hover:text-inherit' className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
> >
<Icon <Icon
icon='material-symbols:delete-outline-rounded' icon='material-symbols:delete-outline-rounded'
@@ -318,7 +318,7 @@ const RowOptionsMenu = ({
/> />
Delete Delete
</Button> </Button>
</div> </RowOptionsMenuWrapper>
); );
}; };
@@ -3,8 +3,10 @@ import * as Yup from 'yup';
export const ChickinFormSchema = Yup.object({ export const ChickinFormSchema = Yup.object({
chick_in_date: Yup.string().required('Tanggal masuk wajib diisi!'), chick_in_date: Yup.string().required('Tanggal masuk wajib diisi!'),
note: Yup.string().required('Catatan wajib diisi!'), note: Yup.string().required('Catatan wajib diisi!'),
quantity: Yup.number().min(1, 'Jumlah wajib diisi!').required('Jumlah wajib diisi!'), quantity: Yup.number()
}) .min(1, 'Jumlah wajib diisi!')
.required('Jumlah wajib diisi!'),
});
export type ChickinFormValues = Yup.InferType<typeof ChickinFormSchema>; export type ChickinFormValues = Yup.InferType<typeof ChickinFormSchema>;
@@ -9,6 +9,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import Table from '@/components/Table'; import Table from '@/components/Table';
import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { ROWS_OPTIONS } from '@/config/constant'; import { ROWS_OPTIONS } from '@/config/constant';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
@@ -37,16 +38,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void; deleteClickHandler: () => void;
}) => { }) => {
return ( return (
<div <RowOptionsMenuWrapper type={type}>
tabIndex={type == 'dropdown' ? 0 : undefined}
className={cn(
{
'dropdown-content': type === 'dropdown',
'mt-2': type === 'collapse',
},
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
)}
>
<Button <Button
href={`/production/project-flock/detail?projectFlockId=${props.row.original.id}`} href={`/production/project-flock/detail?projectFlockId=${props.row.original.id}`}
variant='ghost' variant='ghost'
@@ -82,7 +74,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler} onClick={deleteClickHandler}
variant='ghost' variant='ghost'
color='error' color='error'
className='text-error hover:text-inherit' className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
> >
<Icon <Icon
icon='material-symbols:delete-outline-rounded' icon='material-symbols:delete-outline-rounded'
@@ -92,7 +84,7 @@ const RowOptionsMenu = ({
/> />
Delete Delete
</Button> </Button>
</div> </RowOptionsMenuWrapper>
); );
}; };
@@ -259,6 +251,7 @@ const ProjectFlockTable = () => {
<div className='flex flex-col sm:flex-row gap-3 w-full'> <div className='flex flex-col sm:flex-row gap-3 w-full'>
<Button <Button
href='/production/project-flock/add' href='/production/project-flock/add'
variant='outline'
color='primary' color='primary'
className='w-full sm:w-fit' className='w-full sm:w-fit'
> >
@@ -372,9 +365,9 @@ const ProjectFlockTable = () => {
(row) => row.original?.approval?.step_number == 1 (row) => row.original?.approval?.step_number == 1
); );
const allSelected = selectableRows.every((row) => const allSelected =
row.getIsSelected() selectableRows.every((row) => row.getIsSelected()) &&
) && selectableRows.length != 0; selectableRows.length != 0;
const someSelected = const someSelected =
selectableRows.some((row) => row.getIsSelected()) && selectableRows.some((row) => row.getIsSelected()) &&
@@ -508,7 +501,7 @@ const ProjectFlockTable = () => {
{currentPageSize <= 2 && ( {currentPageSize <= 2 && (
<RowCollapseOptions> <RowCollapseOptions>
<RowOptionsMenu <RowOptionsMenu
type='dropdown' type='collapse'
props={props} props={props}
deleteClickHandler={deleteClickHandler} deleteClickHandler={deleteClickHandler}
/> />
@@ -24,7 +24,8 @@ export const ProjectFlockFormSchema = Yup.object({
value: Yup.string().required('Nilai Kategori wajib diisi!'), value: Yup.string().required('Nilai Kategori wajib diisi!'),
label: Yup.string().required('Label Kategori wajib diisi!'), label: Yup.string().required('Label Kategori wajib diisi!'),
}).nullable(), }).nullable(),
category: Yup.string().oneOf(['GROWING', 'LAYING'], 'Kategori wajib diisi!') category: Yup.string()
.oneOf(['GROWING', 'LAYING'], 'Kategori wajib diisi!')
.required('Kategori wajib diisi!'), .required('Kategori wajib diisi!'),
// FCR // FCR
@@ -79,9 +79,8 @@ const ProjectFlockForm = ({
const [isApprovedDisabled, setIsApprovedDisabled] = useState( const [isApprovedDisabled, setIsApprovedDisabled] = useState(
initialValues?.approval.step_name == 'Pengajuan' ? false : true initialValues?.approval.step_name == 'Pengajuan' ? false : true
); );
const [isRejectedDisabled, setIsRejectedDisabled] = useState( const [isRejectedDisabled, setIsRejectedDisabled] =
!isApprovedDisabled useState(!isApprovedDisabled);
);
const [approvalAction, setApprovalAction] = useState<'APPROVED' | 'REJECTED'>( const [approvalAction, setApprovalAction] = useState<'APPROVED' | 'REJECTED'>(
!isApprovedDisabled ? 'APPROVED' : 'REJECTED' !isApprovedDisabled ? 'APPROVED' : 'REJECTED'
); );
@@ -143,10 +142,11 @@ const ProjectFlockForm = ({
search: '', search: '',
location_id: selectedLocation == '' ? '0' : selectedLocation, location_id: selectedLocation == '' ? '0' : selectedLocation,
}).toString()}`; }).toString()}`;
const { data: kandang, isLoading: isLoadingKandang, mutate: refreshKandang} = useSWR( const {
kandangUrl, data: kandang,
KandangApi.getAllFetcher isLoading: isLoadingKandang,
); mutate: refreshKandang,
} = useSWR(kandangUrl, KandangApi.getAllFetcher);
const getPeriodFlocksUrl = `flocks/${selectedFlock}/periods`; const getPeriodFlocksUrl = `flocks/${selectedFlock}/periods`;
@@ -207,10 +207,7 @@ const ProjectFlockForm = ({
setOpenSelectKandangs(true); setOpenSelectKandangs(true);
const newRowSelection = Object.fromEntries( const newRowSelection = Object.fromEntries(
initialValues.kandangs.map((k: Kandang) => [ initialValues.kandangs.map((k: Kandang) => [k.id.toString(), true])
k.id.toString(),
true,
])
); );
setRowSelection(newRowSelection); setRowSelection(newRowSelection);
} }
@@ -38,10 +38,13 @@ const ProjectFlockKandangTable = ({
const allSelected = const allSelected =
selectableRows.every((row) => row.getIsSelected()) && selectableRows.every((row) => row.getIsSelected()) &&
selectableRows.length != 0 && formType != 'detail'; selectableRows.length != 0 &&
formType != 'detail';
const someSelected = const someSelected =
selectableRows.some((row) => row.getIsSelected()) && !allSelected && formType != 'detail'; selectableRows.some((row) => row.getIsSelected()) &&
!allSelected &&
formType != 'detail';
const toggleSelectableRows = () => { const toggleSelectableRows = () => {
const shouldSelect = !allSelected; const shouldSelect = !allSelected;
@@ -14,6 +14,7 @@ import { TableRowSizeSelector } from '@/components/table/TableRowSizeSelector';
import Table from '@/components/Table'; import Table from '@/components/Table';
import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { type CellContext } from '@tanstack/react-table'; import { type CellContext } from '@tanstack/react-table';
import { type Recording } from '@/types/api/production/recording'; import { type Recording } from '@/types/api/production/recording';
@@ -126,16 +127,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void; deleteClickHandler: () => void;
}) => { }) => {
return ( return (
<div <RowOptionsMenuWrapper type={type}>
tabIndex={type === 'dropdown' ? 0 : undefined}
className={cn(
{
'dropdown-content': type === 'dropdown',
'mt-2': type === 'collapse',
},
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
)}
>
<Button <Button
href={`recording/detail/?recordingId=${props.row.original.id}`} href={`recording/detail/?recordingId=${props.row.original.id}`}
variant='ghost' variant='ghost'
@@ -158,7 +150,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler} onClick={deleteClickHandler}
variant='ghost' variant='ghost'
color='error' color='error'
className='text-error hover:text-inherit' className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
> >
<Icon <Icon
icon='mdi:delete-outline' icon='mdi:delete-outline'
@@ -168,7 +160,7 @@ const RowOptionsMenu = ({
/> />
Delete Delete
</Button> </Button>
</div> </RowOptionsMenuWrapper>
); );
}; };
@@ -255,7 +247,7 @@ const RecordingTable = () => {
<TableToolbar <TableToolbar
addButton={{ addButton={{
href: 'recording/add', href: 'recording/add',
label: 'Tambah Recording', label: 'Tambah',
}} }}
search={{ search={{
value: search, value: search,
@@ -487,7 +487,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
// Create wrapper handlers that match NumberInput's onChange signature // Create wrapper handlers that match NumberInput's onChange signature
const handleChickenWeightChangeWrapper = useCallback( const handleChickenWeightChangeWrapper = useCallback(
(idx: number) => (e: React.ChangeEvent<HTMLInputElement>) => { (idx: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
const value = parseFloat(e.target.value.replace(/[^\d,.-]/g, '').replace(/,/g, '')) || 0; const value =
parseFloat(e.target.value.replace(/[^\d,.-]/g, '').replace(/,/g, '')) ||
0;
handleChickenWeightChange(idx, value); handleChickenWeightChange(idx, value);
}, },
[handleChickenWeightChange] [handleChickenWeightChange]
@@ -495,7 +497,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const handleChickenCountChangeWrapper = useCallback( const handleChickenCountChangeWrapper = useCallback(
(idx: number) => (e: React.ChangeEvent<HTMLInputElement>) => { (idx: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
const value = parseFloat(e.target.value.replace(/[^\d,.-]/g, '').replace(/,/g, '')) || 0; const value =
parseFloat(e.target.value.replace(/[^\d,.-]/g, '').replace(/,/g, '')) ||
0;
handleChickenCountChange(idx, value); handleChickenCountChange(idx, value);
}, },
[handleChickenCountChange] [handleChickenCountChange]
@@ -503,7 +507,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const handleAverageWeightChangeWrapper = useCallback( const handleAverageWeightChangeWrapper = useCallback(
(idx: number) => (e: React.ChangeEvent<HTMLInputElement>) => { (idx: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
const value = parseFloat(e.target.value.replace(/[^\d,.-]/g, '').replace(/,/g, '')) || 0; const value =
parseFloat(e.target.value.replace(/[^\d,.-]/g, '').replace(/,/g, '')) ||
0;
handleAverageWeightChange(idx, value); handleAverageWeightChange(idx, value);
}, },
[handleAverageWeightChange] [handleAverageWeightChange]
@@ -836,18 +842,18 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
{formik.values.feed_data?.map((feed, idx) => ( {formik.values.feed_data?.map((feed, idx) => (
<tr key={`feed-${idx}`}> <tr key={`feed-${idx}`}>
{type !== 'detail' && ( {type !== 'detail' && (
<td className="!align-middle"> <td className='!align-middle'>
<CheckboxInput <CheckboxInput
name={`feed-${idx}`} name={`feed-${idx}`}
checked={selectedFeed.includes(idx)} checked={selectedFeed.includes(idx)}
onChange={( onChange={(
e: React.ChangeEvent<HTMLInputElement>, e: React.ChangeEvent<HTMLInputElement>
) => { ) => {
if (e.target.checked) { if (e.target.checked) {
setSelectedFeed([...selectedFeed, idx]); setSelectedFeed([...selectedFeed, idx]);
} else { } else {
setSelectedFeed( setSelectedFeed(
selectedFeed.filter((i) => i !== idx), selectedFeed.filter((i) => i !== idx)
); );
} }
}} }}
@@ -1047,7 +1053,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
} }
}} }}
classNames={{ classNames={{
wrapper: 'flex justify-center items-center h-full', wrapper:
'flex justify-center items-center h-full',
checkbox: 'checkbox checkbox-sm', checkbox: 'checkbox checkbox-sm',
}} }}
/> />
@@ -1287,7 +1294,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
} }
}} }}
classNames={{ classNames={{
wrapper: 'flex justify-center items-center h-full', wrapper:
'flex justify-center items-center h-full',
checkbox: 'checkbox checkbox-sm', checkbox: 'checkbox checkbox-sm',
}} }}
/> />
@@ -1320,18 +1328,18 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
{formik.values.vaccination?.map((vaccine, idx) => ( {formik.values.vaccination?.map((vaccine, idx) => (
<tr key={`vaccine-${idx}`}> <tr key={`vaccine-${idx}`}>
{type !== 'detail' && ( {type !== 'detail' && (
<td className="!align-middle"> <td className='!align-middle'>
<CheckboxInput <CheckboxInput
name={`vaccine-${idx}`} name={`vaccine-${idx}`}
checked={selectedVaccine.includes(idx)} checked={selectedVaccine.includes(idx)}
onChange={( onChange={(
e: React.ChangeEvent<HTMLInputElement>, e: React.ChangeEvent<HTMLInputElement>
) => { ) => {
if (e.target.checked) { if (e.target.checked) {
setSelectedVaccine([...selectedVaccine, idx]); setSelectedVaccine([...selectedVaccine, idx]);
} else { } else {
setSelectedVaccine( setSelectedVaccine(
selectedVaccine.filter((i) => i !== idx), selectedVaccine.filter((i) => i !== idx)
); );
} }
}} }}
@@ -1418,7 +1426,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
vaccine.total_stock === '' || vaccine.total_stock === '' ||
vaccine.total_stock === undefined vaccine.total_stock === undefined
? '' ? ''
: Number(vaccine.total_stock).toLocaleString('en-US') : Number(vaccine.total_stock).toLocaleString(
'en-US'
)
} }
onChange={formik.handleChange} onChange={formik.handleChange}
onBlur={formik.handleBlur} onBlur={formik.handleBlur}
@@ -1547,7 +1557,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
} }
}} }}
classNames={{ classNames={{
wrapper: 'flex justify-center items-center h-full', wrapper:
'flex justify-center items-center h-full',
checkbox: 'checkbox checkbox-sm', checkbox: 'checkbox checkbox-sm',
}} }}
/> />
@@ -1579,12 +1590,12 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
{formik.values.mortality?.map((mortality, idx) => ( {formik.values.mortality?.map((mortality, idx) => (
<tr key={`mortality-${idx}`}> <tr key={`mortality-${idx}`}>
{type !== 'detail' && ( {type !== 'detail' && (
<td className="!align-middle"> <td className='!align-middle'>
<CheckboxInput <CheckboxInput
name={`mortality-${idx}`} name={`mortality-${idx}`}
checked={selectedMortality.includes(idx)} checked={selectedMortality.includes(idx)}
onChange={( onChange={(
e: React.ChangeEvent<HTMLInputElement>, e: React.ChangeEvent<HTMLInputElement>
) => { ) => {
if (e.target.checked) { if (e.target.checked) {
setSelectedMortality([ setSelectedMortality([
@@ -1593,7 +1604,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
]); ]);
} else { } else {
setSelectedMortality( setSelectedMortality(
selectedMortality.filter((i) => i !== idx), selectedMortality.filter((i) => i !== idx)
); );
} }
}} }}
@@ -19,6 +19,7 @@ import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import TextInput from '@/components/input/TextInput'; import TextInput from '@/components/input/TextInput';
import CheckboxInput from '@/components/input/CheckboxInput'; import CheckboxInput from '@/components/input/CheckboxInput';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { TransferToLaying } from '@/types/api/production/transfer-to-laying'; import { TransferToLaying } from '@/types/api/production/transfer-to-laying';
import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying'; import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying';
@@ -43,16 +44,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void; deleteClickHandler: () => void;
}) => { }) => {
return ( return (
<div <RowOptionsMenuWrapper type={type}>
tabIndex={type === 'dropdown' ? 0 : undefined}
className={cn(
{
'dropdown-content': type === 'dropdown',
'mt-2': type === 'collapse',
},
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
)}
>
<Button <Button
href={`/production/transfer-to-laying/detail/?transferToLayingId=${props.row.original.id}`} href={`/production/transfer-to-laying/detail/?transferToLayingId=${props.row.original.id}`}
variant='ghost' variant='ghost'
@@ -97,7 +89,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler} onClick={deleteClickHandler}
variant='ghost' variant='ghost'
color='error' color='error'
className='text-error hover:text-inherit' className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
> >
<Icon <Icon
icon='material-symbols:delete-outline-rounded' icon='material-symbols:delete-outline-rounded'
@@ -107,7 +99,7 @@ const RowOptionsMenu = ({
/> />
Delete Delete
</Button> </Button>
</div> </RowOptionsMenuWrapper>
); );
}; };
@@ -291,7 +283,7 @@ const TransferToLayingsTable = () => {
{currentPageSize <= 2 && ( {currentPageSize <= 2 && (
<RowCollapseOptions> <RowCollapseOptions>
<RowOptionsMenu <RowOptionsMenu
type='dropdown' type='collapse'
props={props} props={props}
approveClickHandler={approveClickHandler} approveClickHandler={approveClickHandler}
rejectClickHandler={rejectClickHandler} rejectClickHandler={rejectClickHandler}
@@ -328,9 +320,8 @@ const TransferToLayingsTable = () => {
const confirmationModalApproveClickHandler = async () => { const confirmationModalApproveClickHandler = async () => {
setIsApproveLoading(true); setIsApproveLoading(true);
const bulkApproveResponse = await TransferToLayingApi.bulkApprove( const bulkApproveResponse =
selectedRowIds await TransferToLayingApi.bulkApprove(selectedRowIds);
);
if (isResponseSuccess(bulkApproveResponse)) { if (isResponseSuccess(bulkApproveResponse)) {
refreshTransferToLayings(); refreshTransferToLayings();
@@ -358,9 +349,8 @@ const TransferToLayingsTable = () => {
const confirmationModalRejectClickHandler = async () => { const confirmationModalRejectClickHandler = async () => {
setIsRejectLoading(true); setIsRejectLoading(true);
const bulkRejectResponse = await TransferToLayingApi.bulkReject( const bulkRejectResponse =
selectedRowIds await TransferToLayingApi.bulkReject(selectedRowIds);
);
if (isResponseSuccess(bulkRejectResponse)) { if (isResponseSuccess(bulkRejectResponse)) {
refreshTransferToLayings(); refreshTransferToLayings();
@@ -437,11 +427,12 @@ const TransferToLayingsTable = () => {
<div className='w-full sm:w-fit flex flex-col sm:flex-row self-start gap-2'> <div className='w-full sm:w-fit flex flex-col sm:flex-row self-start gap-2'>
<Button <Button
href='/production/transfer-to-laying/add' href='/production/transfer-to-laying/add'
variant='outline'
color='primary' color='primary'
className='w-full sm:w-fit' className='w-full sm:w-fit'
> >
<Icon icon='ic:round-plus' width={24} height={24} /> <Icon icon='ic:round-plus' width={24} height={24} />
Tambah Transfer ke Laying Tambah
</Button> </Button>
{selectedRowIds.length > 0 && ( {selectedRowIds.length > 0 && (
@@ -484,7 +475,9 @@ const TransferToLayingsTable = () => {
placeholder='Cari TransferToLaying' placeholder='Cari TransferToLaying'
value={tableFilterState.search} value={tableFilterState.search}
onChange={searchChangeHandler} onChange={searchChangeHandler}
className={{ wrapper: 'sm:max-w-3xs' }} className={{
wrapper: 'sm:max-w-3xs',
}}
/> />
</div> </div>
@@ -497,7 +490,9 @@ const TransferToLayingsTable = () => {
placeholder='Masukkan tanggal transfer' placeholder='Masukkan tanggal transfer'
value={tableFilterState.transferDate} value={tableFilterState.transferDate}
onChange={transferDateChangeHandler} onChange={transferDateChangeHandler}
className={{ wrapper: 'col-span-12 sm:col-span-3' }} className={{
wrapper: 'col-span-12 sm:col-span-3',
}}
/> />
<SelectInput <SelectInput
+1 -1
View File
@@ -16,7 +16,7 @@ const RowCollapseOptions = ({ children }: RowCollapseOptionsProps) => {
<Icon icon='material-symbols:more-vert' width={16} height={16} /> <Icon icon='material-symbols:more-vert' width={16} height={16} />
</Button> </Button>
} }
className='w-fit' className='w-fit min-w-36'
titleClassName='p-0! justify-self-end' titleClassName='p-0! justify-self-end'
> >
{children} {children}
@@ -0,0 +1,29 @@
import { ReactNode } from 'react';
import { cn } from '@/lib/helper';
interface RowOptionsMenuWrapperProps {
children?: ReactNode;
type: 'dropdown' | 'collapse';
}
const RowOptionsMenuWrapper = ({
children,
type,
}: RowOptionsMenuWrapperProps) => {
return (
<div
tabIndex={type === 'dropdown' ? 0 : undefined}
className={cn(
{
'dropdown-content mr-2': type === 'dropdown',
'w-fit ml-auto mt-2': type === 'collapse',
},
'p-2.5 bg-base-100 rounded-box z-10 border border-black/10 shadow'
)}
>
<div className='flex flex-col gap-1'>{children}</div>
</div>
);
};
export default RowOptionsMenuWrapper;
+4 -13
View File
@@ -1,6 +1,6 @@
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import Button from '../Button'; import Button from '../Button';
import { cn } from '@/lib/helper'; import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
interface TableRowOptionsProps { interface TableRowOptionsProps {
type?: 'dropdown' | 'collapse'; type?: 'dropdown' | 'collapse';
@@ -21,16 +21,7 @@ export const TableRowOptions = ({
showEdit = true, showEdit = true,
showDelete = true, showDelete = true,
}: TableRowOptionsProps) => ( }: TableRowOptionsProps) => (
<div <RowOptionsMenuWrapper type={type}>
tabIndex={type === 'dropdown' ? 0 : undefined}
className={cn(
{
'dropdown-content': type === 'dropdown',
'mt-2': type === 'collapse',
},
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
)}
>
<Button <Button
href={`${basePath}/detail/?${queryParam}=${recordId}`} href={`${basePath}/detail/?${queryParam}=${recordId}`}
variant='ghost' variant='ghost'
@@ -56,7 +47,7 @@ export const TableRowOptions = ({
onClick={onDelete} onClick={onDelete}
variant='ghost' variant='ghost'
color='error' color='error'
className='text-error hover:text-inherit justify-start text-sm' className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
> >
<Icon <Icon
icon='mdi:delete-outline' icon='mdi:delete-outline'
@@ -67,5 +58,5 @@ export const TableRowOptions = ({
Delete Delete
</Button> </Button>
)} )}
</div> </RowOptionsMenuWrapper>
); );
+7 -2
View File
@@ -18,8 +18,13 @@ export const TableToolbar = ({ addButton, search }: TableToolbarProps) => {
return ( return (
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'> <div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
{addButton && ( {addButton && (
<div className='flex flex-row'> <div className='w-full flex flex-row'>
<Button href={addButton.href} color='primary'> <Button
href={addButton.href}
variant='outline'
color='primary'
className='w-full sm:w-fit'
>
<Icon icon='ic:round-plus' width={24} height={24} /> <Icon icon='ic:round-plus' width={24} height={24} />
{addButton.label} {addButton.label}
</Button> </Button>
+5 -2
View File
@@ -105,10 +105,13 @@ export class BaseApiService<T, CreatePayloadGeneric, UpdatePayloadGeneric> {
const url = options?.params const url = options?.params
? `${urlBase}?${new URLSearchParams( ? `${urlBase}?${new URLSearchParams(
Object.entries(options.params).reduce((acc, [key, value]) => { Object.entries(options.params).reduce(
(acc, [key, value]) => {
if (value !== undefined) acc[key] = String(value); if (value !== undefined) acc[key] = String(value);
return acc; return acc;
}, {} as Record<string, string>) },
{} as Record<string, string>
)
)}` )}`
: urlBase; : urlBase;
+3 -3
View File
@@ -1,4 +1,4 @@
import { BaseMetadata, CreatedUser } from "@/types/api/api-general"; import { BaseMetadata, CreatedUser } from '@/types/api/api-general';
export type BaseCustomer = { export type BaseCustomer = {
id: number; id: number;
@@ -10,7 +10,7 @@ export type BaseCustomer = {
phone: string; phone: string;
email: string; email: string;
account_number: string; account_number: string;
} };
export type Customer = BaseMetadata & BaseCustomer; export type Customer = BaseMetadata & BaseCustomer;
@@ -22,6 +22,6 @@ export type CreateCustomerPayload = {
phone: string; phone: string;
email: string; email: string;
account_number: string; account_number: string;
} };
export type UpdateCustomerPayload = CreateCustomerPayload; export type UpdateCustomerPayload = CreateCustomerPayload;
+2 -2
View File
@@ -15,7 +15,7 @@ export type BaseSupplier = {
account_number: string; account_number: string;
due_date: number; due_date: number;
balance?: number; balance?: number;
} };
export type Supplier = BaseMetadata & BaseSupplier; export type Supplier = BaseMetadata & BaseSupplier;
@@ -33,6 +33,6 @@ export type CreateSupplierPayload = {
account_number: string; account_number: string;
due_date: number; due_date: number;
balance?: number; balance?: number;
} };
export type UpdateSupplierPayload = CreateSupplierPayload; export type UpdateSupplierPayload = CreateSupplierPayload;
+4 -4
View File
@@ -1,5 +1,5 @@
import { BaseApproval, BaseMetadata } from "@/types/api/api-general"; import { BaseApproval, BaseMetadata } from '@/types/api/api-general';
import { ProjectFlockKandang } from "@/types/api/production/project-flock-kandang"; import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
export type BaseChickin = { export type BaseChickin = {
id?: number; id?: number;
@@ -8,7 +8,7 @@ export type BaseChickin = {
note?: string; note?: string;
project_flock_kandang?: ProjectFlockKandang; project_flock_kandang?: ProjectFlockKandang;
approval: BaseApproval; approval: BaseApproval;
} };
export type Chickin = BaseMetadata & BaseChickin; export type Chickin = BaseMetadata & BaseChickin;
@@ -17,7 +17,7 @@ export type CreateChickinPayload = {
chick_in_date: string; chick_in_date: string;
note: string; note: string;
quantity?: number; quantity?: number;
} };
export type UpdateChickinPayload = CreateChickinPayload & { export type UpdateChickinPayload = CreateChickinPayload & {
id: number; id: number;
+4 -4
View File
@@ -1,5 +1,5 @@
import { Kandang } from "@/type/master-data/kandang"; import { Kandang } from '@/type/master-data/kandang';
import { ProjectFlock } from "@/types/api/production/project-flock"; import { ProjectFlock } from '@/types/api/production/project-flock';
export type BaseProjectFlockKandang = { export type BaseProjectFlockKandang = {
id: number; id: number;
@@ -8,11 +8,11 @@ export type BaseProjectFlockKandang = {
kandang: Kandang; kandang: Kandang;
project_flock: ProjectFlock; project_flock: ProjectFlock;
available_quantity?: number; available_quantity?: number;
} };
export type ProjectFlockKandang = BaseProjectFlockKandang; export type ProjectFlockKandang = BaseProjectFlockKandang;
export type LookupProjectFlockKandangPayload = { export type LookupProjectFlockKandangPayload = {
project_flock_id: number; project_flock_id: number;
kandang_id: number; kandang_id: number;
} };