Merge branch 'fix/ISSUE-236/table-dropdown-issue' into 'development'

Fix: LTI Issue #236

See merge request mbugroup/lti-web-client!41
This commit is contained in:
Adnan Zahir
2025-11-03 09:08:21 +07:00
90 changed files with 1613 additions and 1509 deletions
+9 -9
View File
@@ -1,6 +1,6 @@
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";
import { dirname } from 'path';
import { fileURLToPath } from 'url';
import { FlatCompat } from '@eslint/eslintrc';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
@@ -10,14 +10,14 @@ const compat = new FlatCompat({
});
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
...compat.extends('next/core-web-vitals', 'next/typescript'),
{
ignores: [
"node_modules/**",
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
'node_modules/**',
'.next/**',
'out/**',
'build/**',
'next-env.d.ts',
],
},
];
+17
View File
@@ -39,6 +39,7 @@
"eslint": "^9",
"eslint-config-next": "15.5.3",
"husky": "^9.1.7",
"prettier": "^3.6.2",
"tailwindcss": "^4",
"typescript": "^5"
}
@@ -5669,6 +5670,22 @@
"node": ">= 0.8.0"
}
},
"node_modules/prettier": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+3 -1
View File
@@ -7,7 +7,8 @@
"build": "next build --turbopack",
"start": "next start",
"lint": "eslint",
"prepare": "husky"
"prepare": "husky",
"format": "prettier --write ."
},
"dependencies": {
"@tanstack/match-sorter-utils": "^8.19.4",
@@ -41,6 +42,7 @@
"eslint": "^9",
"eslint-config-next": "15.5.3",
"husky": "^9.1.7",
"prettier": "^3.6.2",
"tailwindcss": "^4",
"typescript": "^5"
}
+1 -1
View File
@@ -1,5 +1,5 @@
const config = {
plugins: ["@tailwindcss/postcss"],
plugins: ['@tailwindcss/postcss'],
};
export default config;
+2 -4
View File
@@ -3,10 +3,10 @@
@import '../styles/daisyui.css';
@plugin "daisyui/theme" {
name: "lti";
name: 'lti';
default: false;
prefersdark: false;
color-scheme: "light";
color-scheme: 'light';
--color-base-100: oklch(98% 0.001 106.423);
--color-base-200: oklch(97% 0.001 106.424);
--color-base-300: oklch(92% 0.003 48.717);
@@ -37,8 +37,6 @@
--noise: 0;
}
:root {
--color-primary: #1f74bf;
}
+4 -4
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 = () => {
return (
<section className="w-full p-4 flex flex-row justify-center">
<InventoryAdjustmentForm/>
<section className='w-full p-4 flex flex-row justify-center'>
<InventoryAdjustmentForm />
</section>
);
}
};
export default CreateInventoryAdjustment;
@@ -1,11 +1,11 @@
import SuspenseHelper from "@/components/helper/SuspenseHelper"
import SuspenseHelper from '@/components/helper/SuspenseHelper';
const Layout = ({
children
children,
}: Readonly<{
children: React.ReactNode
children: React.ReactNode;
}>) => {
return <SuspenseHelper>{children}</SuspenseHelper>
}
return <SuspenseHelper>{children}</SuspenseHelper>;
};
export default Layout;
+7 -6
View File
@@ -7,11 +7,12 @@ import type { InventoryAdjustment } from '@/types/api/inventory/adjustment';
const DetailInventoryAdjustment = () => {
const router = useRouter();
const [inventoryAdjustment, setInventoryAdjustment] = useState<InventoryAdjustment | null>(null);
const [inventoryAdjustment, setInventoryAdjustment] =
useState<InventoryAdjustment | null>(null);
// Ambil data dari router state
useEffect(() => {
console.log("Router State");
console.log('Router State');
console.log(window.history.state);
const state = window.history.state?.usr as
| { inventoryAdjustment?: InventoryAdjustment }
@@ -25,19 +26,19 @@ const DetailInventoryAdjustment = () => {
const finalData = inventoryAdjustment;
console.log("Final Data");
console.log('Final Data');
console.log(finalData);
if (!finalData) {
return (
<div className="w-full flex flex-row justify-center items-center p-4">
<span className="loading loading-spinner loading-xl" />
<div className='w-full flex flex-row justify-center items-center p-4'>
<span className='loading loading-spinner loading-xl' />
</div>
);
}
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} />
</section>
);
+4 -4
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 = () => {
return (
<section className="w-full p-4 flex flex-row justify-center">
<CustomerForm/>
<section className='w-full p-4 flex flex-row justify-center'>
<CustomerForm />
</section>
);
}
};
export default AddCustomer;
+17 -15
View File
@@ -1,45 +1,47 @@
'use client'
'use client';
import { useRouter, useSearchParams } from "next/navigation";
import useSWR from "swr";
import { useRouter, useSearchParams } from 'next/navigation';
import useSWR from 'swr';
import { CustomerApi } from '@/services/api/master-data';
import { isResponseError, isResponseSuccess } from "@/lib/api-helper";
import CustomerForm from "@/components/pages/master-data/customer/form/CustomerForm";
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import CustomerForm from '@/components/pages/master-data/customer/form/CustomerForm';
const CustomerDetail = () => {
const router = useRouter();
const searchParams = useSearchParams();
const costumerId = searchParams.get("customerId");
const costumerId = searchParams.get('customerId');
const { data: costumer, isLoading: isLoadingCostumer } = useSWR(
costumerId,
(id: number) => CustomerApi.getSingle(id)
);
if(!costumerId){
if (!costumerId) {
router.back();
return (
<div className="w-full flex flex-row justify-center items-center p-4">
<span className="loading loading-spinner loading-xl" />
<div className='w-full flex flex-row justify-center items-center p-4'>
<span className='loading loading-spinner loading-xl' />
</div>
);
}
if(!isLoadingCostumer && (!costumer || isResponseError(costumer))){
router.replace("/404");
if (!isLoadingCostumer && (!costumer || isResponseError(costumer))) {
router.replace('/404');
return;
}
return (
<div className="w-full p-4 flex flex-row justify-center">
{isLoadingCostumer && <span className="loading loading-spinner loading-xl" />}
<div className='w-full p-4 flex flex-row justify-center'>
{isLoadingCostumer && (
<span className='loading loading-spinner loading-xl' />
)}
{!isLoadingCostumer && isResponseSuccess(costumer) && (
<CustomerForm formType="detail" initialValues={costumer.data} />
<CustomerForm formType='detail' initialValues={costumer.data} />
)}
</div>
)
);
};
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 = () => {
return (
<section className="w-full p-4">
<section className='w-full p-4'>
<CustomersTable />
</section>
)
);
};
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 = () => {
return (
<section className="w-full p-4 flex flex-row justify-center">
<section className='w-full p-4 flex flex-row justify-center'>
<FlockForm />
</section>
);
}
};
export default AddFlock;
@@ -1,10 +1,10 @@
'use client'
'use client';
import FlockForm from "@/components/pages/master-data/flock/form/FlockForm";
import { isResponseError, isResponseSuccess } from "@/lib/api-helper";
import { FlockApi } from "@/services/api/master-data";
import { useRouter, useSearchParams } from "next/navigation";
import useSWR from "swr";
import FlockForm from '@/components/pages/master-data/flock/form/FlockForm';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { FlockApi } from '@/services/api/master-data';
import { useRouter, useSearchParams } from 'next/navigation';
import useSWR from 'swr';
const FlockEdit = () => {
const router = useRouter();
@@ -44,6 +44,6 @@ const FlockEdit = () => {
)}
</div>
);
}
};
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 = ({
children
children,
}: Readonly<{
children: React.ReactNode
children: React.ReactNode;
}>) => {
return <SuspenseHelper>{children}</SuspenseHelper>
}
return <SuspenseHelper>{children}</SuspenseHelper>;
};
export default Layout;
+18 -15
View File
@@ -1,10 +1,10 @@
'use client'
'use client';
import FlockForm from "@/components/pages/master-data/flock/form/FlockForm";
import { isResponseError, isResponseSuccess } from "@/lib/api-helper";
import { FlockApi } from "@/services/api/master-data";
import { useRouter, useSearchParams } from "next/navigation";
import useSWR from "swr";
import FlockForm from '@/components/pages/master-data/flock/form/FlockForm';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { FlockApi } from '@/services/api/master-data';
import { useRouter, useSearchParams } from 'next/navigation';
import useSWR from 'swr';
const FlockDetail = () => {
const router = useRouter();
@@ -14,33 +14,36 @@ const FlockDetail = () => {
const flockId = searchParams.get('flockId');
// Fetch Data
const { data: flock, isLoading: isLoadingFlock } = useSWR(flockId, (id: number) => FlockApi.getSingle(id));
const { data: flock, isLoading: isLoadingFlock } = useSWR(
flockId,
(id: number) => FlockApi.getSingle(id)
);
if(!flockId){
if (!flockId) {
router.back();
return (
<div className="w-full flex flex-row justify-center items-center p-4">
<span className="loading loading-spinner loading-xl" />
<div className='w-full flex flex-row justify-center items-center p-4'>
<span className='loading loading-spinner loading-xl' />
</div>
);
}
if(!isLoadingFlock && (!flock || isResponseError(flock))){
if (!isLoadingFlock && (!flock || isResponseError(flock))) {
router.replace('/404');
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 && (
<span className="loading loading-spinner loading-xl" />
<span className='loading loading-spinner loading-xl' />
)}
{!isLoadingFlock && isResponseSuccess(flock) && (
<FlockForm formType="detail" initialValues={flock.data} />
<FlockForm formType='detail' initialValues={flock.data} />
)}
</div>
);
}
};
export default FlockDetail;
+4 -4
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 = () => {
return (
<section className="w-full p-4">
<FlockTable/>
<section className='w-full p-4'>
<FlockTable />
</section>
);
}
};
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 = () => {
return (
<div className="w-full p-4 flex flex-row justify-center">
<div className='w-full p-4 flex flex-row justify-center'>
<ProductCategoryForm />
</div>
);
@@ -9,39 +9,44 @@ import { ProductCategoryApi } from '@/services/api/master-data';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
const ProductCategoryEdit = () => {
const router = useRouter();
const searchParams = useSearchParams();
const router = useRouter();
const searchParams = useSearchParams();
const productCategoryId = searchParams.get('productCategoryId');
const productCategoryId = searchParams.get('productCategoryId');
const { data: productCategory, isLoading: isLoadingProductCategory } = useSWR(
productCategoryId,
(id: number) => ProductCategoryApi.getSingle(id)
);
const { data: productCategory, isLoading: isLoadingProductCategory } = useSWR(
productCategoryId,
(id: number) => ProductCategoryApi.getSingle(id)
);
if (!productCategoryId) {
router.back();
return (
<div className='w-full flex flex-row justify-center items-center p-4'>
<span className='loading loading-spinner loading-xl' />
</div>
);
}
if (!isLoadingProductCategory && (!productCategory || isResponseError(productCategory))) {
router.replace('/404');
return;
}
if (!productCategoryId) {
router.back();
return (
<div className='w-full p-4 flex flex-row justify-center'>
{isLoadingProductCategory && <span className='loading loading-spinner loading-xl' />}
{!isLoadingProductCategory && isResponseSuccess(productCategory) && (
<ProductCategoryForm type='edit' initialValues={productCategory.data} />
)}
</div>
<div className='w-full flex flex-row justify-center items-center p-4'>
<span className='loading loading-spinner loading-xl' />
</div>
);
}
}
if (
!isLoadingProductCategory &&
(!productCategory || isResponseError(productCategory))
) {
router.replace('/404');
return;
}
return (
<div className='w-full p-4 flex flex-row justify-center'>
{isLoadingProductCategory && (
<span className='loading loading-spinner loading-xl' />
)}
{!isLoadingProductCategory && isResponseSuccess(productCategory) && (
<ProductCategoryForm type='edit' initialValues={productCategory.data} />
)}
</div>
);
};
export default ProductCategoryEdit;
@@ -29,16 +29,24 @@ const ProductCategoryDetail = () => {
);
}
if (!isLoadingProductCategory && (!productCategory || isResponseError(productCategory))) {
if (
!isLoadingProductCategory &&
(!productCategory || isResponseError(productCategory))
) {
router.replace('/404');
return;
}
return (
<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) && (
<ProductCategoryForm type='detail' initialValues={productCategory.data} />
<ProductCategoryForm
type='detail'
initialValues={productCategory.data}
/>
)}
</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 = () => {
return (
<section className="w-full p-4">
<section className='w-full p-4'>
<ProductCategoryTable />
</section>
);
+1 -1
View File
@@ -2,7 +2,7 @@ import ProductForm from '@/components/pages/master-data/product/form/ProductForm
const AddProduct = () => {
return (
<div className="w-full p-4 flex flex-row justify-center">
<div className='w-full p-4 flex flex-row justify-center'>
<ProductForm />
</div>
);
@@ -13,9 +13,8 @@ const ProductEdit = () => {
const productId = searchParams.get('productId');
const { data: product, isLoading } = useSWR(
productId,
(id: number) => ProductApi.getSingle(id)
const { data: product, isLoading } = useSWR(productId, (id: number) =>
ProductApi.getSingle(id)
);
if (!productId) {
+2 -3
View File
@@ -13,9 +13,8 @@ const ProductDetail = () => {
const productId = searchParams.get('productId');
const { data: product, isLoading } = useSWR(
productId,
(id: number) => ProductApi.getSingle(id)
const { data: product, isLoading } = useSWR(productId, (id: number) =>
ProductApi.getSingle(id)
);
if (!productId) {
+3 -3
View File
@@ -1,9 +1,9 @@
import ProductsTable from "@/components/pages/master-data/product/ProductTable";
import ProductsTable from '@/components/pages/master-data/product/ProductTable';
const Product = () => {
return (
<section className="w-full p-4">
<ProductsTable />
<section className='w-full p-4'>
<ProductsTable />
</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 = () => {
return (
+5 -5
View File
@@ -1,11 +1,11 @@
import SuspenseHelper from "@/components/helper/SuspenseHelper"
import SuspenseHelper from '@/components/helper/SuspenseHelper';
const Layout = ({
children
children,
}: Readonly<{
children: React.ReactNode
children: React.ReactNode;
}>) => {
return <SuspenseHelper>{children}</SuspenseHelper>
}
return <SuspenseHelper>{children}</SuspenseHelper>;
};
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 = ({
children
children,
}: Readonly<{
children: React.ReactNode
children: React.ReactNode;
}>) => {
return <SuspenseHelper>{children}</SuspenseHelper>
}
return <SuspenseHelper>{children}</SuspenseHelper>;
};
export default Layout;
+4 -4
View File
@@ -43,9 +43,8 @@ const DetailChickin = () => {
// chickin.data?.approval.step_number == 1 ? false : true
true
);
const [isRejectedDisabled, setIsRejectedDisabled] = useState(
!isApprovedDisabled
);
const [isRejectedDisabled, setIsRejectedDisabled] =
useState(!isApprovedDisabled);
const [approvalAction, setApprovalAction] = useState<'APPROVED' | 'REJECTED'>(
!isApprovedDisabled ? 'APPROVED' : 'REJECTED'
);
@@ -264,7 +263,8 @@ const DetailChickin = () => {
<Icon icon='mdi:times' width={24} height={24} />
Delete
</Button>
<Button color='warning'
<Button
color='warning'
onClick={() => {
chickinModal.openModal();
}}
+4 -4
View File
@@ -1,10 +1,10 @@
import ChickinTable from "@/components/pages/production/chickin/ChickinTable";
import ChickinTable from '@/components/pages/production/chickin/ChickinTable';
const Chickin = () => {
return (
<section className="w-full p-4">
<ChickinTable/>
<section className='w-full p-4'>
<ChickinTable />
</section>
);
}
};
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 = () => {
return (
<section className="w-full p-4 flex flex-row justify-center">
<ProjectFlockForm formType="add"/>
<section className='w-full p-4 flex flex-row justify-center'>
<ProjectFlockForm formType='add' />
</section>
);
}
};
export default AddProjectFlock;
@@ -1,46 +1,47 @@
'use client'
'use client';
import ProjectFlockForm from "@/components/pages/production/project-flock/form/ProjectFlockForm";
import { isResponseError, isResponseSuccess } from "@/lib/api-helper";
import { ProjectFlockApi } from "@/services/api/production";
import { useRouter, useSearchParams } from "next/navigation";
import useSWR from "swr";
import ProjectFlockForm from '@/components/pages/production/project-flock/form/ProjectFlockForm';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { ProjectFlockApi } from '@/services/api/production';
import { useRouter, useSearchParams } from 'next/navigation';
import useSWR from 'swr';
const ProjectFlockEdit = () => {
const router = useRouter();
const searchParams = useSearchParams();
const projectFlockId = searchParams.get("projectFlockId");
const projectFlockId = searchParams.get('projectFlockId');
const { data: projectFlock, isLoading: isLoadingCostumer } = useSWR(
projectFlockId,
(id: number) => ProjectFlockApi.getSingle(id)
);
if(!projectFlockId){
if (!projectFlockId) {
router.back();
return (
<div className="w-full flex flex-row justify-center items-center p-4">
<span className="loading loading-spinner loading-xl" />
<div className='w-full flex flex-row justify-center items-center p-4'>
<span className='loading loading-spinner loading-xl' />
</div>
);
}
if(!isLoadingCostumer && (!projectFlock || isResponseError(projectFlock))){
router.replace("/404");
if (!isLoadingCostumer && (!projectFlock || isResponseError(projectFlock))) {
router.replace('/404');
return;
}
return (
<div className="w-full p-4 flex flex-row justify-center">
{isLoadingCostumer && <span className="loading loading-spinner loading-xl" />}
<div className='w-full p-4 flex flex-row justify-center'>
{isLoadingCostumer && (
<span className='loading loading-spinner loading-xl' />
)}
{!isLoadingCostumer && isResponseSuccess(projectFlock) && (
<ProjectFlockForm formType="edit" initialValues={projectFlock.data} />
<ProjectFlockForm formType='edit' initialValues={projectFlock.data} />
)}
</div>
)
}
);
};
export default ProjectFlockEdit;
@@ -1,11 +1,11 @@
import SuspenseHelper from "@/components/helper/SuspenseHelper"
import SuspenseHelper from '@/components/helper/SuspenseHelper';
const Layout = ({
children
children,
}: Readonly<{
children: React.ReactNode
children: React.ReactNode;
}>) => {
return <SuspenseHelper>{children}</SuspenseHelper>
}
return <SuspenseHelper>{children}</SuspenseHelper>;
};
export default Layout;
@@ -42,7 +42,11 @@ const ProjectFlockDetail = () => {
<span className='loading loading-spinner loading-xl' />
)}
{!isLoadingProjectFlock && isResponseSuccess(projectFlock) && (
<ProjectFlockForm formType='detail' initialValues={projectFlock.data} refreshProjectFlocks={refreshProjectFlock} />
<ProjectFlockForm
formType='detail'
initialValues={projectFlock.data}
refreshProjectFlocks={refreshProjectFlock}
/>
)}
</div>
);
+4 -4
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 = () => {
return (
<section className="w-full p-4">
<ProjectFlockTable/>
<section className='w-full p-4'>
<ProjectFlockTable />
</section>
);
}
};
export default ProjectFlock;
+13 -15
View File
@@ -1,13 +1,11 @@
'use client';
import {
HTMLAttributes,
ReactNode,
} from 'react';
import { HTMLAttributes, ReactNode } from 'react';
import { cn } from '@/lib/helper';
export interface CardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'className'> {
export interface CardProps
extends Omit<HTMLAttributes<HTMLDivElement>, 'className'> {
title?: string;
subtitle?: string;
image?: string;
@@ -44,17 +42,17 @@ const Card = ({
const baseClasses = 'card bg-base-100';
const variantClasses = {
'default': '',
'compact': 'card-compact',
'bordered': 'border border-base-300',
'shadow': 'shadow-xl',
default: '',
compact: 'card-compact',
bordered: 'border border-base-300',
shadow: 'shadow-xl',
'image-full': 'card-side card-compact shadow-xl',
};
const sizeClasses = {
'sm': 'w-64',
'md': 'w-96',
'lg': 'w-[28rem]',
sm: 'w-64',
md: 'w-96',
lg: 'w-[28rem]',
};
return cn(
@@ -84,9 +82,9 @@ const Card = ({
const getTitleClasses = () => {
const sizeClasses = {
'sm': 'text-lg',
'md': 'text-xl',
'lg': 'text-2xl',
sm: 'text-lg',
md: 'text-xl',
lg: 'text-2xl',
};
return cn('card-title font-bold', sizeClasses[size], className?.title);
+10 -10
View File
@@ -185,17 +185,17 @@ const Pagination = ({
currentPage <= 2
? currentPage + 2
: currentPage === totalPages - 2
? 3
: currentPage >= totalPages - 1
? 4
: 1
? 3
: currentPage >= totalPages - 1
? 4
: 1
}
endPage={
currentPage <= 2 || currentPage >= totalPages - 1
? totalPages - 3
: currentPage === totalPages - 2
? totalPages - 4
: 2
? totalPages - 4
: 2
}
onPageItemClick={pageChangeHandler}
/>
@@ -242,15 +242,15 @@ const Pagination = ({
currentPage <= 3
? currentPage + 2
: currentPage >= 4
? currentPage + 2
: 1
? currentPage + 2
: 1
}
endPage={
currentPage <= 3
? totalPages - 2
: currentPage >= 4
? totalPages - 1
: 0
? totalPages - 1
: 0
}
onPageItemClick={pageChangeHandler}
/>
+2 -9
View File
@@ -1,10 +1,6 @@
'use client';
import {
ChangeEventHandler,
FocusEventHandler,
ReactNode,
} from 'react';
import { ChangeEventHandler, FocusEventHandler, ReactNode } from 'react';
import { cn } from '@/lib/helper';
@@ -109,10 +105,7 @@ const DateInput = ({
min={min}
max={max}
disabled={disabled}
className={cn(
'grow bg-transparent cursor-pointer',
className?.input
)}
className={cn('grow bg-transparent cursor-pointer', className?.input)}
readOnly={readOnly}
/>
+1 -4
View File
@@ -69,10 +69,7 @@ const FileInput = ({
onChange={onChange}
onBlur={onBlur}
disabled={disabled}
className={cn(
'grow file-input w-full h-12 rounded',
className?.input
)}
className={cn('grow file-input w-full h-12 rounded', className?.input)}
readOnly={readOnly}
/>
+8 -8
View File
@@ -1,10 +1,10 @@
"use client";
'use client';
import { ChangeEvent, ReactNode } from "react";
import { NumericFormat, OnValueChange } from "react-number-format";
import TextInput, { TextInputProps } from "@/components/input/TextInput";
import { ChangeEvent, ReactNode } from 'react';
import { NumericFormat, OnValueChange } from 'react-number-format';
import TextInput, { TextInputProps } from '@/components/input/TextInput';
interface NumberInputProps extends Omit<TextInputProps, "type"> {
interface NumberInputProps extends Omit<TextInputProps, 'type'> {
thousandSeparator?: string;
decimalSeparator?: string;
decimalScale?: number;
@@ -17,8 +17,8 @@ interface NumberInputProps extends Omit<TextInputProps, "type"> {
}
const NumberInput = ({
thousandSeparator = ",",
decimalSeparator = ".",
thousandSeparator = ',',
decimalSeparator = '.',
decimalScale = 5,
allowNegative = true,
onChange,
@@ -28,7 +28,7 @@ const NumberInput = ({
}: NumberInputProps) => {
const valueChangeHandler: OnValueChange = (
numberFormatValues,
sourceInfo,
sourceInfo
) => {
const newChangeEvent = sourceInfo.event as
| ChangeEvent<HTMLInputElement>
+6 -2
View File
@@ -49,14 +49,18 @@ const MenuItem = ({
);
return (
<li onClick={onClick}>
<li>
{href && (
<Link href={href} className={menuItemBaseClassName}>
{menuItemContent}
</Link>
)}
{!href && <a className={menuItemBaseClassName}>{menuItemContent}</a>}
{!href && (
<button className={menuItemBaseClassName} onClick={onClick}>
{menuItemContent}
</button>
)}
</li>
);
};
+10 -10
View File
@@ -31,21 +31,21 @@ const ApprovalSteps = ({ approvals }: ApprovalStepsProps) => {
approval.status === 'APPROVED'
? 'success'
: approval.status === 'REJECTED'
? 'error'
: approval.status === 'WAITING'
? 'warning'
: undefined;
? 'error'
: approval.status === 'WAITING'
? 'warning'
: undefined;
const stepItemIcon =
approval.status === 'APPROVED'
? 'material-symbols:check-rounded'
: approval.status === 'REJECTED'
? 'material-symbols:close-rounded'
: approval.status === 'WAITING'
? 'pajamas:dash-circle'
: approval.logs && approval.logs.length > 0
? 'material-symbols:info-outline-rounded'
: 'bxs:hourglass';
? 'material-symbols:close-rounded'
: approval.status === 'WAITING'
? 'pajamas:dash-circle'
: approval.logs && approval.logs.length > 0
? 'material-symbols:info-outline-rounded'
: 'bxs:hourglass';
return (
<StepItem
@@ -10,11 +10,7 @@ import { inventoryAdjustmentApi } from '@/services/api/inventory';
import { useTableFilter } from '@/services/hooks/useTableFilter';
import { InventoryAdjustment } from '@/types/api/inventory/adjustment';
import { Icon } from '@iconify/react';
import {
ColumnDef,
ColumnSort,
SortingState,
} from '@tanstack/react-table';
import { ColumnDef, ColumnSort, SortingState } from '@tanstack/react-table';
import { useCallback, useEffect, useState } from 'react';
import useSWR from 'swr';
@@ -44,10 +40,7 @@ const InventoryAdjustmentTable = () => {
});
// Fetch Data
const {
data: inventoryAdjustments,
isLoading,
} = useSWR(
const { data: inventoryAdjustments, isLoading } = useSWR(
`${inventoryAdjustmentApi.basePath}${getTableFilterQueryString()}`,
inventoryAdjustmentApi.getAllFetcher
);
@@ -113,8 +106,8 @@ const InventoryAdjustmentTable = () => {
type === 'INCREASE'
? 'Peningkatan'
: type === 'DECREASE'
? 'Penurunan'
: '-';
? 'Penurunan'
: '-';
return (
<div
@@ -187,8 +180,13 @@ const InventoryAdjustmentTable = () => {
<div className='w-full p-0 sm:p-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='flex flex-row'>
<Button href='/inventory/adjustment/add' color='primary'>
<div className='w-full flex flex-row'>
<Button
href='/inventory/adjustment/add'
variant='outline'
color='primary'
className='w-full sm:w-fit'
>
<Icon icon='ic:round-plus' width={24} height={24} />
Tambah
</Button>
@@ -211,7 +209,7 @@ const InventoryAdjustmentTable = () => {
value: tableFilterState.pageSize,
}}
onChange={pageSizeChangeHandler}
className={{ wrapper: 'max-w-28' }}
className={{ wrapper: 'min-w-28' }}
/>
</div>
</div>
@@ -4,24 +4,21 @@ export const InventoryAdjustmentFormSchema = Yup.object({
product_category: Yup.object({
value: Yup.number().required('ID Kategori Produk wajib diisi!'),
label: Yup.string().required('Nama Kategori Produk wajib diisi!'),
})
.nullable(),
}).nullable(),
product_category_id: Yup.number().nullable(),
product: Yup.object({
value: Yup.number().required('ID Produk wajib diisi!'),
label: Yup.string().required('Nama Produk wajib diisi!'),
})
.nullable(),
}).nullable(),
product_id: Yup.number().nullable(),
warehouse: Yup.object({
value: Yup.number().required('ID Gudang wajib diisi!'),
label: Yup.string().required('Nama Gudang wajib diisi!'),
})
.nullable(),
}).nullable(),
warehouse_id: Yup.number().nullable(),
@@ -51,9 +51,8 @@ const InventoryAdjustmentForm = ({
// Submit Handler
const createInventoryAdjustmentHandler = useCallback(
async (payload: CreateInventoryAdjustmentPayload) => {
const createInventoryAdjustmentRes = await inventoryAdjustmentApi.create(
payload
);
const createInventoryAdjustmentRes =
await inventoryAdjustmentApi.create(payload);
if (isResponseError(createInventoryAdjustmentRes)) {
setInventoryAdjustmentFormErrorMessage(
@@ -68,7 +67,9 @@ const InventoryAdjustmentForm = ({
[router]
);
const formikInitialValues = useMemo<Partial<InventoryAdjustmentFormValues>>(() => {
const formikInitialValues = useMemo<
Partial<InventoryAdjustmentFormValues>
>(() => {
return {
product_category_id: initialValues?.product_category?.id ?? 0,
product_id: initialValues?.product?.id ?? 0,
@@ -185,7 +186,6 @@ const InventoryAdjustmentForm = ({
warehouseChangeHandler(null);
};
const { setValues: formikSetValues } = formik;
// Effect
@@ -225,7 +225,13 @@ const InventoryAdjustmentForm = ({
const type = initialValues.transaction_type.toLowerCase();
setQuantityLabel(type === 'increase' ? 'Tambah Stok' : 'Kurangi Stok');
}
}, [formik, initialValues, setQuantityLabel, setDisabledProduct, setSelectedProductCategories]);
}, [
formik,
initialValues,
setQuantityLabel,
setDisabledProduct,
setSelectedProductCategories,
]);
useEffect(() => {
formikSetValues(formikInitialValues as InventoryAdjustmentFormValues);
}, [formikSetValues, formikInitialValues]);
@@ -364,15 +370,19 @@ const InventoryAdjustmentForm = ({
errorMessage={formik.errors.transaction_type as string}
variant='radio-primary'
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'}
/>
{/* Number Input Stock */}
<TextInput
className={{
wrapper: `${formik.values.transaction_type != undefined ? '' : 'hidden'}`,
}}
className={{
wrapper: `${formik.values.transaction_type != undefined ? '' : 'hidden'}`,
}}
required
label={quantityLabel}
name='quantity'
@@ -395,8 +405,6 @@ const InventoryAdjustmentForm = ({
readOnly={type === 'detail'}
/>
{/* Text Area Input Reason */}
<TextArea
required
@@ -413,14 +421,23 @@ const InventoryAdjustmentForm = ({
<div className='flex flex-row justify-between gap-2 flex-wrap'>
{type !== 'detail' && (
<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
</Button>
<Button
type='submit'
color='primary'
isLoading={formik.isSubmitting}
disabled={!formik.isValid || formik.isSubmitting || formik.values.product == undefined}
disabled={
!formik.isValid ||
formik.isSubmitting ||
formik.values.product == undefined
}
className='px-4'
>
Submit
@@ -77,7 +77,7 @@ const MovementTable = () => {
<TableToolbar
addButton={{
href: '/inventory/movement/add',
label: 'Tambah Movement',
label: 'Tambah',
}}
search={{
value: tableFilterState.search,
@@ -14,6 +14,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import SelectInput, { OptionType } from '@/components/input/SelectInput';
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { Area } from '@/types/api/master-data/area';
import { AreaApi } from '@/services/api/master-data';
@@ -32,16 +33,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void;
}) => {
return (
<div
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'
)}
>
<RowOptionsMenuWrapper type={type}>
<Button
href={`/master-data/area/detail/?areaId=${props.row.original.id}`}
variant='ghost'
@@ -76,7 +68,7 @@ const RowOptionsMenu = ({
/>
Delete
</Button>
</div>
</RowOptionsMenuWrapper>
);
};
@@ -150,7 +142,7 @@ const AreasTable = () => {
{currentPageSize <= 2 && (
<RowCollapseOptions>
<RowOptionsMenu
type='dropdown'
type='collapse'
props={props}
deleteClickHandler={deleteClickHandler}
/>
@@ -199,10 +191,15 @@ const AreasTable = () => {
<div className='w-full p-0 sm:p-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='flex flex-row'>
<Button href='/master-data/area/add' color='primary'>
<div className='w-full flex flex-row'>
<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} />
Tambah Area
Tambah
</Button>
</div>
@@ -14,6 +14,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import SelectInput, { OptionType } from '@/components/input/SelectInput';
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { Bank } from '@/types/api/master-data/bank';
import { BankApi } from '@/services/api/master-data';
@@ -32,16 +33,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void;
}) => {
return (
<div
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'
)}
>
<RowOptionsMenuWrapper type={type}>
<Button
href={`/master-data/bank/detail/?bankId=${props.row.original.id}`}
variant='ghost'
@@ -66,7 +58,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler}
variant='ghost'
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='material-symbols:delete-outline-rounded'
@@ -76,7 +68,7 @@ const RowOptionsMenu = ({
/>
Delete
</Button>
</div>
</RowOptionsMenuWrapper>
);
};
@@ -163,7 +155,7 @@ const BanksTable = () => {
{currentPageSize <= 2 && (
<RowCollapseOptions>
<RowOptionsMenu
type='dropdown'
type='collapse'
props={props}
deleteClickHandler={deleteClickHandler}
/>
@@ -212,10 +204,15 @@ const BanksTable = () => {
<div className='w-full p-0 sm:p-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='flex flex-row'>
<Button href='/master-data/bank/add' color='primary'>
<div className='w-full flex flex-row'>
<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} />
Tambah Bank
Tambah
</Button>
</div>
@@ -8,6 +8,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import Table from '@/components/Table';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { ROWS_OPTIONS } from '@/config/constant';
import { isResponseSuccess } from '@/lib/api-helper';
import { cn } from '@/lib/helper';
@@ -15,10 +16,7 @@ import { CustomerApi } from '@/services/api/master-data';
import { useTableFilter } from '@/services/hooks/useTableFilter';
import { Customer } from '@/types/api/master-data/customer';
import { Icon } from '@iconify/react';
import {
CellContext,
ColumnDef,
} from '@tanstack/react-table';
import { CellContext, ColumnDef } from '@tanstack/react-table';
import { useState } from 'react';
import toast from 'react-hot-toast';
import useSWR from 'swr';
@@ -33,16 +31,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void;
}) => {
return (
<div
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'
)}
>
<RowOptionsMenuWrapper type={type}>
<Button
href={`/master-data/customer/detail/?customerId=${props.row.original.id}`}
variant='ghost'
@@ -53,10 +42,10 @@ const RowOptionsMenu = ({
Detail
</Button>
<Button
className='justify-start text-sm'
href={`/master-data/customer/detail/edit/?customerId=${props.row.original.id}`}
variant='ghost'
color='warning'
className='justify-start text-sm'
>
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
Edit
@@ -65,7 +54,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler}
variant='ghost'
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='material-symbols:delete-outline-rounded'
@@ -75,7 +64,7 @@ const RowOptionsMenu = ({
/>
Delete
</Button>
</div>
</RowOptionsMenuWrapper>
);
};
@@ -174,7 +163,7 @@ const CustomersTable = () => {
{currentPageSize <= 2 && (
<RowCollapseOptions>
<RowOptionsMenu
type='dropdown'
type='collapse'
props={props}
deleteClickHandler={deleteClickHandler}
/>
@@ -210,10 +199,15 @@ const CustomersTable = () => {
<div className='w-full p-0 sm:p-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='flex flex-row'>
<Button href='/master-data/customer/add' color='primary'>
<div className='w-full flex flex-row'>
<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} />
Tambah Customer
Tambah
</Button>
</div>
@@ -11,7 +11,11 @@ import {
import { useRouter } from 'next/navigation';
import { useCallback, useEffect, useMemo, useState } from 'react';
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 Button from '@/components/Button';
import { Icon } from '@iconify/react';
@@ -150,7 +154,8 @@ const CustomerForm = ({
const formik = useFormik<CustomerFormValues>({
initialValues: formikInitialValues,
enableReinitialize: true,
validationSchema: formType === 'edit' ? UpdateCustomerFormSchema : CustomerFormSchema,
validationSchema:
formType === 'edit' ? UpdateCustomerFormSchema : CustomerFormSchema,
onSubmit: async (values) => {
// reset error message
setCustomerFormErrorMessage('');
@@ -14,6 +14,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import SelectInput, { OptionType } from '@/components/input/SelectInput';
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { Fcr } from '@/types/api/master-data/fcr';
import { FcrApi } from '@/services/api/master-data';
@@ -32,16 +33,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void;
}) => {
return (
<div
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'
)}
>
<RowOptionsMenuWrapper type={type}>
<Button
href={`/master-data/fcr/detail/?fcrId=${props.row.original.id}`}
variant='ghost'
@@ -66,7 +58,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler}
variant='ghost'
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='material-symbols:delete-outline-rounded'
@@ -76,7 +68,7 @@ const RowOptionsMenu = ({
/>
Delete
</Button>
</div>
</RowOptionsMenuWrapper>
);
};
@@ -150,7 +142,7 @@ const FcrsTable = () => {
{currentPageSize <= 2 && (
<RowCollapseOptions>
<RowOptionsMenu
type='dropdown'
type='collapse'
props={props}
deleteClickHandler={deleteClickHandler}
/>
@@ -199,10 +191,15 @@ const FcrsTable = () => {
<div className='w-full p-0 sm:p-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='flex flex-row'>
<Button href='/master-data/fcr/add' color='primary'>
<div className='w-full flex flex-row'>
<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} />
Tambah FCR
Tambah
</Button>
</div>
@@ -12,6 +12,7 @@ import { FlockApi } from '@/services/api/master-data';
import { useModal } from '@/components/Modal';
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import toast from 'react-hot-toast';
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import SelectInput, { OptionType } from '@/components/input/SelectInput';
@@ -30,16 +31,7 @@ const RowsOptions = ({
deleteClickHandler: () => void;
}) => {
return (
<div
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'
)}
>
<RowOptionsMenuWrapper type={type}>
<Button
href={`/master-data/flock/detail/edit/?flockId=${props.row.original.id}`}
variant='ghost'
@@ -54,7 +46,7 @@ const RowsOptions = ({
/>
Edit
</Button>
<Button
<Button
href={`/master-data/flock/detail/?flockId=${props.row.original.id}`}
variant='ghost'
color='primary'
@@ -72,7 +64,7 @@ const RowsOptions = ({
onClick={deleteClickHandler}
variant='ghost'
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='material-symbols:delete-outline-rounded'
@@ -82,7 +74,7 @@ const RowsOptions = ({
/>
Delete
</Button>
</div>
</RowOptionsMenuWrapper>
);
};
@@ -203,9 +195,15 @@ const FlockTable = () => {
<div className='w-full p-0 sm:p-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='flex flex-row'>
<Button href='/master-data/flock/add' color='primary'>
Tambah Flock
<div className='w-full flex flex-row'>
<Button
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>
</div>
@@ -3,10 +3,7 @@ import * as Yup from 'yup';
export const FlockFormSchema = Yup.object({
name: Yup.string()
.required('Nama wajib diisi!')
.matches(
/^[\p{L}\p{N}\s]+$/u,
'Nama tidak boleh mengandung simbol'
),
.matches(/^[\p{L}\p{N}\s]+$/u, 'Nama tidak boleh mengandung simbol'),
});
export const UpdateFlockFormSchema = FlockFormSchema;
@@ -1,11 +1,15 @@
'use client'
'use client';
import { useModal } from '@/components/Modal';
import { FlockApi } from '@/services/api/master-data';
import { Flock } from '@/types/api/master-data/flock';
import { useRouter } from 'next/navigation';
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 Button from '@/components/Button';
import { Icon } from '@iconify/react';
@@ -48,7 +52,8 @@ const FlockForm = ({ formType = 'add', initialValues }: FlockCustomProps) => {
const formik = useFormik<FlockFormValues>({
initialValues: formikInitialValue,
enableReinitialize: true,
validationSchema: formType === 'edit' ? UpdateFlockFormSchema : FlockFormSchema,
validationSchema:
formType === 'edit' ? UpdateFlockFormSchema : FlockFormSchema,
onSubmit: async (values) => {
// reset error message
setFlockFormErrorMessage('');
@@ -19,6 +19,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import SelectInput, { OptionType } from '@/components/input/SelectInput';
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { Kandang } from '@/types/api/master-data/kandang';
import { KandangApi } from '@/services/api/master-data';
@@ -37,16 +38,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void;
}) => {
return (
<div
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'
)}
>
<RowOptionsMenuWrapper type={type}>
<Button
href={`/master-data/kandang/detail/?kandangId=${props.row.original.id}`}
variant='ghost'
@@ -71,7 +63,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler}
variant='ghost'
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='material-symbols:delete-outline-rounded'
@@ -81,7 +73,7 @@ const RowOptionsMenu = ({
/>
Delete
</Button>
</div>
</RowOptionsMenuWrapper>
);
};
@@ -173,7 +165,7 @@ const KandangsTable = () => {
{currentPageSize <= 2 && (
<RowCollapseOptions>
<RowOptionsMenu
type='dropdown'
type='collapse'
props={props}
deleteClickHandler={deleteClickHandler}
/>
@@ -238,10 +230,15 @@ const KandangsTable = () => {
<div className='w-full p-0 sm:p-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='flex flex-row'>
<Button href='/master-data/kandang/add' color='primary'>
<div className='w-full flex flex-row'>
<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} />
Tambah Kandang
Tambah
</Button>
</div>
@@ -19,6 +19,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import SelectInput, { OptionType } from '@/components/input/SelectInput';
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { Location } from '@/types/api/master-data/location';
import { LocationApi } from '@/services/api/master-data';
@@ -37,16 +38,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void;
}) => {
return (
<div
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'
)}
>
<RowOptionsMenuWrapper type={type}>
<Button
href={`/master-data/location/detail/?locationId=${props.row.original.id}`}
variant='ghost'
@@ -71,7 +63,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler}
variant='ghost'
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='material-symbols:delete-outline-rounded'
@@ -81,7 +73,7 @@ const RowOptionsMenu = ({
/>
Delete
</Button>
</div>
</RowOptionsMenuWrapper>
);
};
@@ -172,7 +164,7 @@ const LocationsTable = () => {
{currentPageSize <= 2 && (
<RowCollapseOptions>
<RowOptionsMenu
type='dropdown'
type='collapse'
props={props}
deleteClickHandler={deleteClickHandler}
/>
@@ -237,10 +229,15 @@ const LocationsTable = () => {
<div className='w-full p-0 sm:p-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='flex flex-row'>
<Button href='/master-data/location/add' color='primary'>
<div className='w-full flex flex-row'>
<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} />
Tambah Location
Tambah
</Button>
</div>
@@ -19,6 +19,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import SelectInput, { OptionType } from '@/components/input/SelectInput';
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { Nonstock } from '@/types/api/master-data/nonstock';
import { NonstockApi } from '@/services/api/master-data';
@@ -37,16 +38,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void;
}) => {
return (
<div
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'
)}
>
<RowOptionsMenuWrapper type={type}>
<Button
href={`/master-data/nonstock/detail/?nonstockId=${props.row.original.id}`}
variant='ghost'
@@ -71,7 +63,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler}
variant='ghost'
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='material-symbols:delete-outline-rounded'
@@ -81,7 +73,7 @@ const RowOptionsMenu = ({
/>
Delete
</Button>
</div>
</RowOptionsMenuWrapper>
);
};
@@ -184,7 +176,7 @@ const NonstocksTable = () => {
{currentPageSize <= 2 && (
<RowCollapseOptions>
<RowOptionsMenu
type='dropdown'
type='collapse'
props={props}
deleteClickHandler={deleteClickHandler}
/>
@@ -249,10 +241,15 @@ const NonstocksTable = () => {
<div className='w-full p-0 sm:p-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='flex flex-row'>
<Button href='/master-data/nonstock/add' color='primary'>
<div className='w-full flex flex-row'>
<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} />
Tambah Nonstock
Tambah
</Button>
</div>
@@ -14,6 +14,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import SelectInput, { OptionType } from '@/components/input/SelectInput';
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { ProductCategory } from '@/types/api/master-data/product-category';
import { ProductCategoryApi } from '@/services/api/master-data';
@@ -32,16 +33,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void;
}) => {
return (
<div
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'
)}
>
<RowOptionsMenuWrapper type={type}>
<Button
href={`/master-data/product-category/detail/?productCategoryId=${props.row.original.id}`}
variant='ghost'
@@ -64,7 +56,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler}
variant='ghost'
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='mdi:delete-outline'
@@ -74,7 +66,7 @@ const RowOptionsMenu = ({
/>
Delete
</Button>
</div>
</RowOptionsMenuWrapper>
);
};
@@ -154,7 +146,7 @@ const ProductCategoryTable = () => {
{currentPageSize <= 2 && (
<RowCollapseOptions>
<RowOptionsMenu
type='dropdown'
type='collapse'
props={props}
deleteClickHandler={deleteClickHandler}
/>
@@ -200,10 +192,15 @@ const ProductCategoryTable = () => {
<div className='w-full p-0 sm:p-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='flex flex-row'>
<Button href='/master-data/product-category/add' color='primary'>
<div className='w-full flex flex-row'>
<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} />
Tambah Product Category
Tambah
</Button>
</div>
<DebouncedTextInput
@@ -1,10 +1,14 @@
import * as Yup from 'yup';
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!'),
});
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;
}
const ProductCategoryForm = ({ type = 'add', initialValues }: ProductCategoryFormProps) => {
const ProductCategoryForm = ({
type = 'add',
initialValues,
}: ProductCategoryFormProps) => {
const router = useRouter();
const deleteModal = useModal();
@@ -77,7 +80,10 @@ const ProductCategoryForm = ({ type = 'add', initialValues }: ProductCategoryFor
const formik = useFormik<ProductCategoryFormValues>({
initialValues: formikInitialValues,
validationSchema: type === 'edit' ? UpdateProductCategoryFormSchema : ProductCategoryFormSchema,
validationSchema:
type === 'edit'
? UpdateProductCategoryFormSchema
: ProductCategoryFormSchema,
onSubmit: async (values) => {
setFormErrorMessage('');
@@ -91,7 +97,10 @@ const ProductCategoryForm = ({ type = 'add', initialValues }: ProductCategoryFor
await createProductCategoryHandler(payload);
break;
case 'edit':
await updateProductCategoryHandler(initialValues?.id as number, payload);
await updateProductCategoryHandler(
initialValues?.id as number,
payload
);
break;
}
},
@@ -19,6 +19,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import SelectInput, { OptionType } from '@/components/input/SelectInput';
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { Product } from '@/types/api/master-data/product';
import { ProductApi } from '@/services/api/master-data';
@@ -36,16 +37,7 @@ const RowOptionsMenu = ({
props: CellContext<Product, unknown>;
deleteClickHandler: () => void;
}) => (
<div
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'
)}
>
<RowOptionsMenuWrapper type={type}>
<Button
href={`/master-data/product/detail/?productId=${props.row.original.id}`}
variant='ghost'
@@ -68,7 +60,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler}
variant='ghost'
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='material-symbols:delete-outline-rounded'
@@ -78,7 +70,7 @@ const RowOptionsMenu = ({
/>
Delete
</Button>
</div>
</RowOptionsMenuWrapper>
);
const ProductsTable = () => {
@@ -217,7 +209,7 @@ const ProductsTable = () => {
{currentPageSize <= 2 && (
<RowCollapseOptions>
<RowOptionsMenu
type='dropdown'
type='collapse'
props={props}
deleteClickHandler={deleteClickHandler}
/>
@@ -280,10 +272,15 @@ const ProductsTable = () => {
<div className='w-full p-0 sm:p-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='flex flex-row'>
<Button href='/master-data/product/add' color='primary'>
<div className='w-full flex flex-row'>
<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} />
Tambah Produk
Tambah
</Button>
</div>
<DebouncedTextInput
@@ -5,49 +5,50 @@ export const ProductFormSchema = Yup.object({
brand: Yup.string().required('Merek wajib diisi!'),
sku: Yup.string().required('SKU wajib diisi!'),
uom: Yup.object({
value: Yup.number().min(1).required(),
label: Yup.string().required(),
}).nullable(),
uom_id: Yup.number().required('Satuan wajib diisi!').typeError('Satuan wajib diisi!'),
value: Yup.number().min(1).required(),
label: Yup.string().required(),
}).nullable(),
uom_id: Yup.number()
.required('Satuan wajib diisi!')
.typeError('Satuan wajib diisi!'),
product_category: Yup.object({
value: Yup.number().min(1).required(),
label: Yup.string().required(),
}).nullable(),
value: Yup.number().min(1).required(),
label: Yup.string().required(),
}).nullable(),
product_category_id: Yup.number()
.required('Kategori produk wajib diisi!')
.typeError('Kategori produk wajib diisi!'),
.required('Kategori produk wajib diisi!')
.typeError('Kategori produk wajib diisi!'),
product_price: Yup.number()
.required('Harga produk wajib diisi!')
.typeError('Harga produk wajib diisi!')
.min(0, 'Harga produk tidak boleh kurang dari 0!'),
.required('Harga produk wajib diisi!')
.typeError('Harga produk wajib diisi!')
.min(0, 'Harga produk tidak boleh kurang dari 0!'),
selling_price: Yup.number()
.required('Harga jual wajib diisi!')
.typeError('Harga jual wajib diisi!')
.min(0, 'Harga jual tidak boleh kurang dari 0!'),
.required('Harga jual wajib diisi!')
.typeError('Harga jual wajib diisi!')
.min(0, 'Harga jual tidak boleh kurang dari 0!'),
tax: Yup.number()
.required('Pajak wajib diisi!')
.typeError('Pajak wajib diisi!')
.min(0, 'Pajak tidak boleh kurang dari 0!')
.max(100, 'Pajak tidak boleh lebih dari 100%!'),
.required('Pajak wajib diisi!')
.typeError('Pajak wajib diisi!')
.min(0, 'Pajak tidak boleh kurang dari 0!')
.max(100, 'Pajak tidak boleh lebih dari 100%!'),
expiry_period: Yup.number()
.required('Periode kadaluarsa wajib diisi!')
.typeError('Periode kadaluarsa wajib diisi!')
.min(0, 'Periode kadaluarsa tidak boleh kurang dari 0!'),
.required('Periode kadaluarsa wajib diisi!')
.typeError('Periode kadaluarsa wajib diisi!')
.min(0, 'Periode kadaluarsa tidak boleh kurang dari 0!'),
supplier: Yup.object({
value: Yup.number().min(1).required(),
label: Yup.string().required(),
}).nullable(),
value: Yup.number().min(1).required(),
label: Yup.string().required(),
}).nullable(),
supplier_ids: Yup.array()
.of(Yup.number().typeError('Supplier tidak valid!'))
.min(1, 'Minimal harus ada 1 supplier!')
.required('Supplier wajib diisi!'),
.of(Yup.number().typeError('Supplier tidak valid!'))
.min(1, 'Minimal harus ada 1 supplier!')
.required('Supplier wajib diisi!'),
flags: Yup.array()
.of(Yup.string())
.min(1, 'Minimal harus ada 1 flag!')
.required('Flag wajib diisi!'),
.of(Yup.string())
.min(1, 'Minimal harus ada 1 flag!')
.required('Flag wajib diisi!'),
});
export const UpdateProductFormSchema = ProductFormSchema;
export type ProductFormValues = Yup.InferType<typeof ProductFormSchema>;
@@ -24,7 +24,12 @@ import {
CreateProductPayload,
UpdateProductPayload,
} 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 { PRODUCT_FLAG_OPTIONS } from '@/config/constant';
@@ -67,30 +72,37 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
[router]
);
const formikInitialValues = useMemo<ProductFormValues>(() => ({
name: initialValues?.name ?? '',
brand: initialValues?.brand ?? '',
sku: initialValues?.sku ?? '',
uom: initialValues?.uom
? { value: initialValues.uom.id, label: initialValues.uom.name }
: null,
uom_id: initialValues?.uom?.id ?? 0,
product_category: initialValues?.product_category
? { value: initialValues.product_category.id, label: initialValues.product_category.name }
: null,
product_category_id: initialValues?.product_category?.id ?? 0,
product_price: initialValues?.product_price ?? 0,
selling_price: initialValues?.selling_price ?? 0,
tax: initialValues?.tax ?? 0,
expiry_period: initialValues?.expiry_period ?? 0,
supplier: null, // not used for payload, just for UI
supplier_ids: initialValues?.suppliers?.map(s => s.id) ?? [],
flags: initialValues?.flags ?? [],
}), [initialValues]);
const formikInitialValues = useMemo<ProductFormValues>(
() => ({
name: initialValues?.name ?? '',
brand: initialValues?.brand ?? '',
sku: initialValues?.sku ?? '',
uom: initialValues?.uom
? { value: initialValues.uom.id, label: initialValues.uom.name }
: null,
uom_id: initialValues?.uom?.id ?? 0,
product_category: initialValues?.product_category
? {
value: initialValues.product_category.id,
label: initialValues.product_category.name,
}
: null,
product_category_id: initialValues?.product_category?.id ?? 0,
product_price: initialValues?.product_price ?? 0,
selling_price: initialValues?.selling_price ?? 0,
tax: initialValues?.tax ?? 0,
expiry_period: initialValues?.expiry_period ?? 0,
supplier: null, // not used for payload, just for UI
supplier_ids: initialValues?.suppliers?.map((s) => s.id) ?? [],
flags: initialValues?.flags ?? [],
}),
[initialValues]
);
const formik = useFormik<ProductFormValues>({
initialValues: formikInitialValues,
validationSchema: type === 'edit' ? UpdateProductFormSchema : ProductFormSchema,
validationSchema:
type === 'edit' ? UpdateProductFormSchema : ProductFormSchema,
onSubmit: async (values) => {
setProductFormErrorMessage('');
const payload: CreateProductPayload = {
@@ -103,8 +115,12 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
selling_price: values.selling_price,
tax: values.tax,
expiry_period: values.expiry_period,
supplier_ids: (values.supplier_ids ?? []).filter((id): id is number => typeof id === 'number'),
flags: (values.flags ?? []).filter((f): f is string => typeof f === 'string'),
supplier_ids: (values.supplier_ids ?? []).filter(
(id): id is number => typeof id === 'number'
),
flags: (values.flags ?? []).filter(
(f): f is string => typeof f === 'string'
),
};
switch (type) {
case 'add':
@@ -122,7 +138,10 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
// UOM
const [uomSelectInputValue, setUomSelectInputValue] = useState('');
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)
? uoms?.data.map((uom) => ({ value: uom.id, label: uom.name }))
: [];
@@ -136,7 +155,10 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
// Product Category
const [categorySelectInputValue, setCategorySelectInputValue] = useState('');
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)
? categories?.data.map((cat) => ({ value: cat.id, label: cat.name }))
: [];
@@ -150,16 +172,22 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
// Supplier (multi select)
const [supplierSelectInputValue, setSupplierSelectInputValue] = useState('');
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)
? suppliers?.data
.filter((sup) => sup.category === 'SAPRONAK')
.map((sup) => ({ value: sup.id, label: sup.name }))
: [];
? suppliers?.data
.filter((sup) => sup.category === 'SAPRONAK')
.map((sup) => ({ value: sup.id, label: sup.name }))
: [];
const supplierChangeHandler = (val: OptionType | OptionType[] | null) => {
const arr = Array.isArray(val) ? val : val ? [val] : [];
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 = () => {
@@ -260,7 +288,10 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
options={categoryOptions}
onInputChange={setCategorySelectInputValue}
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}
isDisabled={type === 'detail'}
isClearable
@@ -274,7 +305,10 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
value={formik.values.product_price}
onChange={formik.handleChange}
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}
readOnly={type === 'detail'}
/>
@@ -287,7 +321,10 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
value={formik.values.selling_price}
onChange={formik.handleChange}
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}
readOnly={type === 'detail'}
/>
@@ -313,7 +350,10 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
value={formik.values.expiry_period}
onChange={formik.handleChange}
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}
readOnly={type === 'detail'}
/>
@@ -321,12 +361,17 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
required
label='Supplier'
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}
options={supplierOptions}
onInputChange={setSupplierSelectInputValue}
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}
isDisabled={type === 'detail'}
isClearable
@@ -335,10 +380,15 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
required
label='Flags'
isMulti
value={PRODUCT_FLAG_OPTIONS.filter(opt => formik.values.flags.includes(opt.value))}
onChange={val => {
value={PRODUCT_FLAG_OPTIONS.filter((opt) =>
formik.values.flags.includes(opt.value)
)}
onChange={(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}
isError={formik.touched.flags && Boolean(formik.errors.flags)}
@@ -8,6 +8,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import Table from '@/components/Table';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { ROWS_OPTIONS } from '@/config/constant';
import { isResponseSuccess } from '@/lib/api-helper';
import { cn } from '@/lib/helper';
@@ -30,16 +31,7 @@ const RowOptions = ({
deleteClickHandler: () => void;
}) => {
return (
<div
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'
)}
>
<RowOptionsMenuWrapper type={type}>
<Button
href={`/master-data/supplier/detail/?supplierId=${props.row.original.id}`}
variant='ghost'
@@ -72,7 +64,7 @@ const RowOptions = ({
onClick={deleteClickHandler}
variant='ghost'
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='material-symbols:delete-outline-rounded'
@@ -82,7 +74,7 @@ const RowOptions = ({
/>
Delete
</Button>
</div>
</RowOptionsMenuWrapper>
);
};
@@ -226,10 +218,15 @@ const SuppliersTable = () => {
<div className='w-full p-0 sm:p-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='flex flex-row'>
<Button href='/master-data/supplier/add' color='primary'>
<div className='w-full flex flex-row'>
<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} />
Tambah Supplier
Tambah
</Button>
</div>
@@ -1,39 +1,42 @@
import * as Yup from 'yup';
export const SupplierFormSchema = Yup.object({
name: Yup.string().required('Nama wajib diisi!'),
alias: Yup.string()
.matches(/^[A-Za-z0-9]+$/, 'Alias hanya boleh berisi huruf dan angka tanpa spasi atau simbol!')
name: Yup.string().required('Nama wajib diisi!'),
alias: Yup.string()
.matches(
/^[A-Za-z0-9]+$/,
'Alias hanya boleh berisi huruf dan angka tanpa spasi atau simbol!'
)
.max(5, 'Alias maksimal 5 karakter!')
.required('Alias wajib diisi!'),
pic: Yup.string().required('PIC wajib diisi!'),
type: Yup.object({
value: Yup.string().required(),
label: Yup.string().required(),
})
.required('Tipe wajib diisi!'),
category: Yup.object({
value: Yup.string().required(),
label: Yup.string().required(),
})
.required('Tipe wajib diisi!'),
hatchery: Yup.string().required('Hatchery wajib diisi!'),
phone: Yup.string()
.matches(/^[0-9]+$/, 'Nomor telepon hanya boleh berisi angka!')
.min(10, 'Nomor telepon minimal 10 digit!')
.max(12, 'Nomor telepon maksimal 12 digit!')
.required('Nomor telepon wajib diisi!'),
email: Yup.string()
.email('Format email tidak valid!')
.required('Email wajib diisi!'),
address: Yup.string().required('Alamat wajib diisi!'),
npwp: Yup.string()
.matches(/^[0-9]+$/, 'Nomor NPWP hanya boleh berisi angka!')
.required('Nomor NPWP wajib diisi!'),
account_number: Yup.string()
.matches(/^[0-9]+$/, 'Nomor rekening hanya boleh berisi angka!')
.required('Nomor rekening wajib diisi!'),
due_date: Yup.number().min(1, 'Tanggal jatuh tempo wajib diisi!').required('Tanggal jatuh tempo wajib diisi!'),
pic: Yup.string().required('PIC wajib diisi!'),
type: Yup.object({
value: Yup.string().required(),
label: Yup.string().required(),
}).required('Tipe wajib diisi!'),
category: Yup.object({
value: Yup.string().required(),
label: Yup.string().required(),
}).required('Tipe wajib diisi!'),
hatchery: Yup.string().required('Hatchery wajib diisi!'),
phone: Yup.string()
.matches(/^[0-9]+$/, 'Nomor telepon hanya boleh berisi angka!')
.min(10, 'Nomor telepon minimal 10 digit!')
.max(12, 'Nomor telepon maksimal 12 digit!')
.required('Nomor telepon wajib diisi!'),
email: Yup.string()
.email('Format email tidak valid!')
.required('Email wajib diisi!'),
address: Yup.string().required('Alamat wajib diisi!'),
npwp: Yup.string()
.matches(/^[0-9]+$/, 'Nomor NPWP hanya boleh berisi angka!')
.required('Nomor NPWP wajib diisi!'),
account_number: Yup.string()
.matches(/^[0-9]+$/, 'Nomor rekening hanya boleh berisi angka!')
.required('Nomor rekening wajib diisi!'),
due_date: Yup.number()
.min(1, 'Tanggal jatuh tempo wajib diisi!')
.required('Tanggal jatuh tempo wajib diisi!'),
});
export const UpdateSupplierFormSchema = SupplierFormSchema;
@@ -41,7 +41,9 @@ const SupplierForm = ({
// Setup State
const [supplierFormErrorMessage, setSupplierFormErrorMessage] = useState('');
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [hatcheryOptionsValues, setHatcheryOptionValues] = useState<OptionType[]>([]);
const [hatcheryOptionsValues, setHatcheryOptionValues] = useState<
OptionType[]
>([]);
// -- Options data mapping
const typeOptions = TYPE_OPTIONS;
@@ -167,7 +169,7 @@ const SupplierForm = ({
// Initialize Formik
useEffect(() => {
formikSetValues(formikInitialValues);
if(formType != 'add'){
if (formType != 'add') {
const hatcheryArrays = formikInitialValues.hatchery.split(',');
const hatcheryCreatedOptions = hatcheryArrays.map((item) => ({
value: item,
@@ -177,11 +179,13 @@ const SupplierForm = ({
}
}, [formikSetValues, formikInitialValues, setHatcheryOptionValues]);
useEffect(() => {
const commaSeparatedValues = hatcheryOptionsValues.map((item) => item.value).join(',');
const commaSeparatedValues = hatcheryOptionsValues
.map((item) => item.value)
.join(',');
formikSetValues({
...formik.values,
hatchery: commaSeparatedValues,
})
});
}, [hatcheryOptionsValues, formikSetValues]);
// Option Handler
@@ -305,7 +309,9 @@ const SupplierForm = ({
console.log(val); // pastikan val = array of { value, label }
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}
isDisabled={formType === 'detail'}
isClearable
@@ -14,6 +14,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import SelectInput, { OptionType } from '@/components/input/SelectInput';
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { Uom } from '@/types/api/master-data/uom';
import { UomApi } from '@/services/api/master-data';
@@ -32,16 +33,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void;
}) => {
return (
<div
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'
)}
>
<RowOptionsMenuWrapper type={type}>
<Button
href={`/master-data/uom/detail/?uomId=${props.row.original.id}`}
variant='ghost'
@@ -66,7 +58,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler}
variant='ghost'
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='material-symbols:delete-outline-rounded'
@@ -76,7 +68,7 @@ const RowOptionsMenu = ({
/>
Delete
</Button>
</div>
</RowOptionsMenuWrapper>
);
};
@@ -150,7 +142,7 @@ const UomsTable = () => {
{currentPageSize <= 2 && (
<RowCollapseOptions>
<RowOptionsMenu
type='dropdown'
type='collapse'
props={props}
deleteClickHandler={deleteClickHandler}
/>
@@ -199,10 +191,15 @@ const UomsTable = () => {
<div className='w-full p-0 sm:p-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='flex flex-row'>
<Button href='/master-data/uom/add' color='primary'>
<div className='w-full flex flex-row'>
<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} />
Tambah UOM
Tambah
</Button>
</div>
@@ -19,6 +19,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import SelectInput, { OptionType } from '@/components/input/SelectInput';
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { Warehouse } from '@/types/api/master-data/warehouse';
import { WarehouseApi } from '@/services/api/master-data';
@@ -37,16 +38,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void;
}) => {
return (
<div
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'
)}
>
<RowOptionsMenuWrapper type={type}>
<Button
href={`/master-data/warehouse/detail/?warehouseId=${props.row.original.id}`}
variant='ghost'
@@ -81,7 +73,7 @@ const RowOptionsMenu = ({
/>
Delete
</Button>
</div>
</RowOptionsMenuWrapper>
);
};
@@ -206,7 +198,7 @@ const WarehousesTable = () => {
{currentPageSize <= 2 && (
<RowCollapseOptions>
<RowOptionsMenu
type='dropdown'
type='collapse'
props={props}
deleteClickHandler={deleteClickHandler}
/>
@@ -277,10 +269,15 @@ const WarehousesTable = () => {
<div className='w-full p-0 sm:p-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='flex flex-row'>
<Button href='/master-data/warehouse/add' color='primary'>
<div className='w-full flex flex-row'>
<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} />
Tambah Warehouse
Tambah
</Button>
</div>
@@ -8,6 +8,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import Table from '@/components/Table';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { TableRowSizeSelector } from '@/components/table/TableRowSizeSelector';
import { ROWS_OPTIONS } from '@/config/constant';
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'>
<Button
href='/production/chickin/add?projectFlockId=1'
variant='outline'
color='primary'
className='w-full sm:w-fit'
>
<Icon icon='uil:plus' width={24} height={24} />
Tambah
@@ -130,20 +133,20 @@ const ChickinTable = () => {
} else {
return '-';
}
}
},
},
{
accessorFn: (row) => row.chick_in_date,
header: 'Tanggal Chickin',
cell: (props) => {
if (props.row.original.chick_in_date) {
return new Date(props.row.original.chick_in_date).toLocaleDateString(
'id-ID'
);
return new Date(
props.row.original.chick_in_date
).toLocaleDateString('id-ID');
} else {
return '-';
}
}
},
},
{
accessorFn: (row) => row.note,
@@ -166,7 +169,7 @@ const ChickinTable = () => {
deleteModal.openModal();
};
const editClickHandler = () => {
const editClickHandler = () => {
setSelectedChickin(props.row.original);
chickinModal.openModal();
};
@@ -240,7 +243,9 @@ const ChickinTable = () => {
<Modal ref={chickinModal.ref}>
<div className='flex flex-row justify-between items-center'>
<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>
<Button
color='error'
@@ -255,10 +260,14 @@ const ChickinTable = () => {
/>
</Button>
</div>
<ChickinForm initialValues={selectedChickin} formType='edit' afterSubmit={() => {
refreshChickins()
chickinModal.closeModal()
}}/>
<ChickinForm
initialValues={selectedChickin}
formType='edit'
afterSubmit={() => {
refreshChickins();
chickinModal.closeModal();
}}
/>
</Modal>
</>
);
@@ -276,16 +285,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void;
}) => {
return (
<div
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'
)}
>
<RowOptionsMenuWrapper type={type}>
<Button
href={`/production/chickin/detail?chickinId=${props.row.original.id}`}
variant='ghost'
@@ -308,7 +308,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler}
variant='ghost'
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='material-symbols:delete-outline-rounded'
@@ -318,7 +318,7 @@ const RowOptionsMenu = ({
/>
Delete
</Button>
</div>
</RowOptionsMenuWrapper>
);
};
@@ -3,8 +3,10 @@ import * as Yup from 'yup';
export const ChickinFormSchema = Yup.object({
chick_in_date: Yup.string().required('Tanggal masuk 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>;
@@ -9,6 +9,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import Table from '@/components/Table';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { ROWS_OPTIONS } from '@/config/constant';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { cn } from '@/lib/helper';
@@ -37,16 +38,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void;
}) => {
return (
<div
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'
)}
>
<RowOptionsMenuWrapper type={type}>
<Button
href={`/production/project-flock/detail?projectFlockId=${props.row.original.id}`}
variant='ghost'
@@ -82,7 +74,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler}
variant='ghost'
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='material-symbols:delete-outline-rounded'
@@ -92,7 +84,7 @@ const RowOptionsMenu = ({
/>
Delete
</Button>
</div>
</RowOptionsMenuWrapper>
);
};
@@ -259,6 +251,7 @@ const ProjectFlockTable = () => {
<div className='flex flex-col sm:flex-row gap-3 w-full'>
<Button
href='/production/project-flock/add'
variant='outline'
color='primary'
className='w-full sm:w-fit'
>
@@ -372,9 +365,9 @@ const ProjectFlockTable = () => {
(row) => row.original?.approval?.step_number == 1
);
const allSelected = selectableRows.every((row) =>
row.getIsSelected()
) && selectableRows.length != 0;
const allSelected =
selectableRows.every((row) => row.getIsSelected()) &&
selectableRows.length != 0;
const someSelected =
selectableRows.some((row) => row.getIsSelected()) &&
@@ -508,7 +501,7 @@ const ProjectFlockTable = () => {
{currentPageSize <= 2 && (
<RowCollapseOptions>
<RowOptionsMenu
type='dropdown'
type='collapse'
props={props}
deleteClickHandler={deleteClickHandler}
/>
@@ -24,7 +24,8 @@ export const ProjectFlockFormSchema = Yup.object({
value: Yup.string().required('Nilai Kategori wajib diisi!'),
label: Yup.string().required('Label Kategori wajib diisi!'),
}).nullable(),
category: Yup.string().oneOf(['GROWING', 'LAYING'], 'Kategori wajib diisi!')
category: Yup.string()
.oneOf(['GROWING', 'LAYING'], 'Kategori wajib diisi!')
.required('Kategori wajib diisi!'),
// FCR
@@ -79,9 +79,8 @@ const ProjectFlockForm = ({
const [isApprovedDisabled, setIsApprovedDisabled] = useState(
initialValues?.approval.step_name == 'Pengajuan' ? false : true
);
const [isRejectedDisabled, setIsRejectedDisabled] = useState(
!isApprovedDisabled
);
const [isRejectedDisabled, setIsRejectedDisabled] =
useState(!isApprovedDisabled);
const [approvalAction, setApprovalAction] = useState<'APPROVED' | 'REJECTED'>(
!isApprovedDisabled ? 'APPROVED' : 'REJECTED'
);
@@ -143,10 +142,11 @@ const ProjectFlockForm = ({
search: '',
location_id: selectedLocation == '' ? '0' : selectedLocation,
}).toString()}`;
const { data: kandang, isLoading: isLoadingKandang, mutate: refreshKandang} = useSWR(
kandangUrl,
KandangApi.getAllFetcher
);
const {
data: kandang,
isLoading: isLoadingKandang,
mutate: refreshKandang,
} = useSWR(kandangUrl, KandangApi.getAllFetcher);
const getPeriodFlocksUrl = `flocks/${selectedFlock}/periods`;
@@ -207,10 +207,7 @@ const ProjectFlockForm = ({
setOpenSelectKandangs(true);
const newRowSelection = Object.fromEntries(
initialValues.kandangs.map((k: Kandang) => [
k.id.toString(),
true,
])
initialValues.kandangs.map((k: Kandang) => [k.id.toString(), true])
);
setRowSelection(newRowSelection);
}
@@ -38,10 +38,13 @@ const ProjectFlockKandangTable = ({
const allSelected =
selectableRows.every((row) => row.getIsSelected()) &&
selectableRows.length != 0 && formType != 'detail';
selectableRows.length != 0 &&
formType != 'detail';
const someSelected =
selectableRows.some((row) => row.getIsSelected()) && !allSelected && formType != 'detail';
selectableRows.some((row) => row.getIsSelected()) &&
!allSelected &&
formType != 'detail';
const toggleSelectableRows = () => {
const shouldSelect = !allSelected;
@@ -14,6 +14,7 @@ import { TableRowSizeSelector } from '@/components/table/TableRowSizeSelector';
import Table from '@/components/Table';
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { type CellContext } from '@tanstack/react-table';
import { type Recording } from '@/types/api/production/recording';
@@ -126,16 +127,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void;
}) => {
return (
<div
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'
)}
>
<RowOptionsMenuWrapper type={type}>
<Button
href={`recording/detail/?recordingId=${props.row.original.id}`}
variant='ghost'
@@ -158,7 +150,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler}
variant='ghost'
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='mdi:delete-outline'
@@ -168,7 +160,7 @@ const RowOptionsMenu = ({
/>
Delete
</Button>
</div>
</RowOptionsMenuWrapper>
);
};
@@ -255,7 +247,7 @@ const RecordingTable = () => {
<TableToolbar
addButton={{
href: 'recording/add',
label: 'Tambah Recording',
label: 'Tambah',
}}
search={{
value: search,
File diff suppressed because it is too large Load Diff
@@ -19,6 +19,7 @@ import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import TextInput from '@/components/input/TextInput';
import CheckboxInput from '@/components/input/CheckboxInput';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { TransferToLaying } from '@/types/api/production/transfer-to-laying';
import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying';
@@ -43,16 +44,7 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void;
}) => {
return (
<div
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'
)}
>
<RowOptionsMenuWrapper type={type}>
<Button
href={`/production/transfer-to-laying/detail/?transferToLayingId=${props.row.original.id}`}
variant='ghost'
@@ -97,7 +89,7 @@ const RowOptionsMenu = ({
onClick={deleteClickHandler}
variant='ghost'
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='material-symbols:delete-outline-rounded'
@@ -107,7 +99,7 @@ const RowOptionsMenu = ({
/>
Delete
</Button>
</div>
</RowOptionsMenuWrapper>
);
};
@@ -291,7 +283,7 @@ const TransferToLayingsTable = () => {
{currentPageSize <= 2 && (
<RowCollapseOptions>
<RowOptionsMenu
type='dropdown'
type='collapse'
props={props}
approveClickHandler={approveClickHandler}
rejectClickHandler={rejectClickHandler}
@@ -328,9 +320,8 @@ const TransferToLayingsTable = () => {
const confirmationModalApproveClickHandler = async () => {
setIsApproveLoading(true);
const bulkApproveResponse = await TransferToLayingApi.bulkApprove(
selectedRowIds
);
const bulkApproveResponse =
await TransferToLayingApi.bulkApprove(selectedRowIds);
if (isResponseSuccess(bulkApproveResponse)) {
refreshTransferToLayings();
@@ -358,9 +349,8 @@ const TransferToLayingsTable = () => {
const confirmationModalRejectClickHandler = async () => {
setIsRejectLoading(true);
const bulkRejectResponse = await TransferToLayingApi.bulkReject(
selectedRowIds
);
const bulkRejectResponse =
await TransferToLayingApi.bulkReject(selectedRowIds);
if (isResponseSuccess(bulkRejectResponse)) {
refreshTransferToLayings();
@@ -437,11 +427,12 @@ const TransferToLayingsTable = () => {
<div className='w-full sm:w-fit flex flex-col sm:flex-row self-start gap-2'>
<Button
href='/production/transfer-to-laying/add'
variant='outline'
color='primary'
className='w-full sm:w-fit'
>
<Icon icon='ic:round-plus' width={24} height={24} />
Tambah Transfer ke Laying
Tambah
</Button>
{selectedRowIds.length > 0 && (
@@ -484,7 +475,9 @@ const TransferToLayingsTable = () => {
placeholder='Cari TransferToLaying'
value={tableFilterState.search}
onChange={searchChangeHandler}
className={{ wrapper: 'sm:max-w-3xs' }}
className={{
wrapper: 'sm:max-w-3xs',
}}
/>
</div>
@@ -497,7 +490,9 @@ const TransferToLayingsTable = () => {
placeholder='Masukkan tanggal transfer'
value={tableFilterState.transferDate}
onChange={transferDateChangeHandler}
className={{ wrapper: 'col-span-12 sm:col-span-3' }}
className={{
wrapper: 'col-span-12 sm:col-span-3',
}}
/>
<SelectInput
+1 -1
View File
@@ -16,7 +16,7 @@ const RowCollapseOptions = ({ children }: RowCollapseOptionsProps) => {
<Icon icon='material-symbols:more-vert' width={16} height={16} />
</Button>
}
className='w-fit'
className='w-fit min-w-36'
titleClassName='p-0! justify-self-end'
>
{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 Button from '../Button';
import { cn } from '@/lib/helper';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
interface TableRowOptionsProps {
type?: 'dropdown' | 'collapse';
@@ -21,16 +21,7 @@ export const TableRowOptions = ({
showEdit = true,
showDelete = true,
}: TableRowOptionsProps) => (
<div
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'
)}
>
<RowOptionsMenuWrapper type={type}>
<Button
href={`${basePath}/detail/?${queryParam}=${recordId}`}
variant='ghost'
@@ -56,7 +47,7 @@ export const TableRowOptions = ({
onClick={onDelete}
variant='ghost'
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='mdi:delete-outline'
@@ -67,5 +58,5 @@ export const TableRowOptions = ({
Delete
</Button>
)}
</div>
</RowOptionsMenuWrapper>
);
+7 -2
View File
@@ -18,8 +18,13 @@ export const TableToolbar = ({ addButton, search }: TableToolbarProps) => {
return (
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
{addButton && (
<div className='flex flex-row'>
<Button href={addButton.href} color='primary'>
<div className='w-full flex flex-row'>
<Button
href={addButton.href}
variant='outline'
color='primary'
className='w-full sm:w-fit'
>
<Icon icon='ic:round-plus' width={24} height={24} />
{addButton.label}
</Button>
+7 -4
View File
@@ -105,10 +105,13 @@ export class BaseApiService<T, CreatePayloadGeneric, UpdatePayloadGeneric> {
const url = options?.params
? `${urlBase}?${new URLSearchParams(
Object.entries(options.params).reduce((acc, [key, value]) => {
if (value !== undefined) acc[key] = String(value);
return acc;
}, {} as Record<string, string>)
Object.entries(options.params).reduce(
(acc, [key, value]) => {
if (value !== undefined) acc[key] = String(value);
return acc;
},
{} as Record<string, string>
)
)}`
: urlBase;
+19 -19
View File
@@ -1,27 +1,27 @@
import { BaseMetadata, CreatedUser } from "@/types/api/api-general";
import { BaseMetadata, CreatedUser } from '@/types/api/api-general';
export type BaseCustomer = {
id: number;
name: string;
pic_id: number;
pic: CreatedUser;
type: string;
address: string;
phone: string;
email: string;
account_number: string;
}
id: number;
name: string;
pic_id: number;
pic: CreatedUser;
type: string;
address: string;
phone: string;
email: string;
account_number: string;
};
export type Customer = BaseMetadata & BaseCustomer;
export type CreateCustomerPayload = {
name: string;
pic_id: number;
type: string;
address: string;
phone: string;
email: string;
account_number: string;
}
name: string;
pic_id: number;
type: string;
address: string;
phone: string;
email: string;
account_number: string;
};
export type UpdateCustomerPayload = CreateCustomerPayload;
+29 -29
View File
@@ -1,38 +1,38 @@
import { BaseMetadata } from '@/types/api/api-general';
export type BaseSupplier = {
id: number;
name: string;
alias: string;
pic: string;
type: string;
category: string;
hatchery: string;
phone: string;
email: string;
address: string;
npwp: string;
account_number: string;
due_date: number;
balance?: number;
}
id: number;
name: string;
alias: string;
pic: string;
type: string;
category: string;
hatchery: string;
phone: string;
email: string;
address: string;
npwp: string;
account_number: string;
due_date: number;
balance?: number;
};
export type Supplier = BaseMetadata & BaseSupplier;
export type CreateSupplierPayload = {
name: string;
alias: string;
pic: string;
type: string;
category: string;
hatchery: string;
phone: string;
email: string;
address: string;
npwp: string;
account_number: string;
due_date: number;
balance?: number;
}
name: string;
alias: string;
pic: string;
type: string;
category: string;
hatchery: string;
phone: string;
email: string;
address: string;
npwp: string;
account_number: string;
due_date: number;
balance?: number;
};
export type UpdateSupplierPayload = CreateSupplierPayload;
+4 -4
View File
@@ -1,5 +1,5 @@
import { BaseApproval, BaseMetadata } from "@/types/api/api-general";
import { ProjectFlockKandang } from "@/types/api/production/project-flock-kandang";
import { BaseApproval, BaseMetadata } from '@/types/api/api-general';
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
export type BaseChickin = {
id?: number;
@@ -8,7 +8,7 @@ export type BaseChickin = {
note?: string;
project_flock_kandang?: ProjectFlockKandang;
approval: BaseApproval;
}
};
export type Chickin = BaseMetadata & BaseChickin;
@@ -17,7 +17,7 @@ export type CreateChickinPayload = {
chick_in_date: string;
note: string;
quantity?: number;
}
};
export type UpdateChickinPayload = CreateChickinPayload & {
id: number;
+4 -4
View File
@@ -1,5 +1,5 @@
import { Kandang } from "@/type/master-data/kandang";
import { ProjectFlock } from "@/types/api/production/project-flock";
import { Kandang } from '@/type/master-data/kandang';
import { ProjectFlock } from '@/types/api/production/project-flock';
export type BaseProjectFlockKandang = {
id: number;
@@ -8,11 +8,11 @@ export type BaseProjectFlockKandang = {
kandang: Kandang;
project_flock: ProjectFlock;
available_quantity?: number;
}
};
export type ProjectFlockKandang = BaseProjectFlockKandang;
export type LookupProjectFlockKandangPayload = {
project_flock_id: number;
kandang_id: number;
}
};