diff --git a/src/app/production/transfer-to-laying/add/page.tsx b/src/app/production/transfer-to-laying/add/page.tsx new file mode 100644 index 00000000..6f29085f --- /dev/null +++ b/src/app/production/transfer-to-laying/add/page.tsx @@ -0,0 +1,11 @@ +import TransferToLayingForm from '@/components/pages/production/transfer-to-laying/form/TransferToLayingForm'; + +const AddTransferToLaying = () => { + return ( +
+ +
+ ); +}; + +export default AddTransferToLaying; diff --git a/src/app/production/transfer-to-laying/detail/layout.tsx b/src/app/production/transfer-to-laying/detail/layout.tsx new file mode 100644 index 00000000..7220dfa1 --- /dev/null +++ b/src/app/production/transfer-to-laying/detail/layout.tsx @@ -0,0 +1,11 @@ +import SuspenseHelper from '@/components/helper/SuspenseHelper'; + +const Layout = ({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) => { + return {children}; +}; + +export default Layout; diff --git a/src/app/production/transfer-to-laying/detail/page.tsx b/src/app/production/transfer-to-laying/detail/page.tsx new file mode 100644 index 00000000..4d603098 --- /dev/null +++ b/src/app/production/transfer-to-laying/detail/page.tsx @@ -0,0 +1,140 @@ +'use client'; + +import { useRouter, useSearchParams } from 'next/navigation'; +import useSWR from 'swr'; + +import TransferToLayingForm from '@/components/pages/production/transfer-to-laying/form/TransferToLayingForm'; + +import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; + +import { TransferToLaying } from '@/types/api/production/transfer-to-laying'; + +// TODO: delete dummy data +const DUMMY_TRANSFER_TO_LAYING_DETAIL: TransferToLaying = { + id: 1, + transfer_date: '14-10-2025', + flock_source: { + id: 1, + name: 'Flock asal test', + }, + flock_destination: { + id: 2, + name: 'Flock tujuan destination', + }, + quantity: 10, + kandangs: [ + { + kandang: { + id: 1, + name: 'Kandang test', + status: 'ACTIVE', + location: { + id: 1, + name: 'test location', + address: 'test address 1', + area: { id: 1, name: 'test area 1' }, + }, + pic: { + id: 1, + id_user: 2, + email: 'test@gmail.com', + name: 'test', + }, + created_user: { + id: 1, + id_user: 2, + email: 'test@gmail.com', + name: 'test', + }, + created_at: '14-10-2025', + updated_at: '14-10-2025', + }, + quantity: 8, + }, + { + kandang: { + id: 1, + name: 'Kandang test 2', + status: 'ACTIVE', + location: { + id: 1, + name: 'test location', + address: 'test address 1', + area: { id: 1, name: 'test area 1' }, + }, + pic: { + id: 1, + id_user: 2, + email: 'test@gmail.com', + name: 'test', + }, + created_user: { + id: 1, + id_user: 2, + email: 'test@gmail.com', + name: 'test', + }, + created_at: '14-10-2025', + updated_at: '14-10-2025', + }, + quantity: 2, + }, + ], + reason: 'Test alasan', + + created_user: { + id: 1, + id_user: 2, + email: 'test@gmail.com', + name: 'test', + }, + created_at: '14-10-2025', + updated_at: '14-10-2025', +}; + +const TransferToLayingDetail = () => { + const router = useRouter(); + const searchParams = useSearchParams(); + + const transferToLayingId = searchParams.get('transferToLayingId'); + + const { data: transferToLaying, isLoading: isLoadingTransferToLaying } = + useSWR(transferToLayingId, (id: number) => + TransferToLayingApi.getSingle(id) + ); + + if (!transferToLayingId) { + router.back(); + + return ( +
+ +
+ ); + } + + if ( + !isLoadingTransferToLaying && + (!transferToLaying || isResponseError(transferToLaying)) + ) { + router.replace('/404'); + return; + } + + return ( +
+ {isLoadingTransferToLaying && ( + + )} + {!isLoadingTransferToLaying && isResponseSuccess(transferToLaying) && ( + + )} +
+ ); +}; + +export default TransferToLayingDetail; diff --git a/src/app/production/transfer-to-laying/page.tsx b/src/app/production/transfer-to-laying/page.tsx new file mode 100644 index 00000000..40829c20 --- /dev/null +++ b/src/app/production/transfer-to-laying/page.tsx @@ -0,0 +1,16 @@ +import { Icon } from '@iconify/react'; +import Button from '@/components/Button'; + +const TransferToLaying = () => { + return ( +
+

Transfer to Laying

+ +
+ ); +}; + +export default TransferToLaying; diff --git a/src/components/helper/RequireAuth.tsx b/src/components/helper/RequireAuth.tsx index 1d9d86b4..dbd4b6bc 100644 --- a/src/components/helper/RequireAuth.tsx +++ b/src/components/helper/RequireAuth.tsx @@ -158,7 +158,7 @@ const RequireAuth = ({ children }: RequireAuthProps) => { const { data: userResponse, isLoading: isLoadingUserResponse } = useSWRImmutable( - '/auth/get-me', + '/auth/sso/userinfo', httpClientFetcher, { shouldRetryOnError: false, @@ -194,4 +194,4 @@ const RequireAuth = ({ children }: RequireAuthProps) => { return <>{children}; }; -export default RequireAuth; \ No newline at end of file +export default RequireAuth; diff --git a/src/components/input/SelectInput.tsx b/src/components/input/SelectInput.tsx index b35ad7dd..b491077f 100644 --- a/src/components/input/SelectInput.tsx +++ b/src/components/input/SelectInput.tsx @@ -1,6 +1,8 @@ 'use client'; import { ComponentType, ReactNode, useEffect, useMemo, useState } from 'react'; +import useSWR from 'swr'; + import Select, { OptionProps, GroupBase, @@ -11,7 +13,10 @@ import Select, { import CreatableSelect from 'react-select/creatable'; import makeAnimated from 'react-select/animated'; import { useDebounce } from 'use-debounce'; -import { cn } from '@/lib/helper'; +import { cn, getByPath } from '@/lib/helper'; +import { httpClientFetcher } from '@/services/http/client'; +import { isResponseSuccess } from '@/lib/api-helper'; +import { BaseApiResponse } from '@/types/api/api-general'; export interface OptionType { value: string | number; @@ -160,7 +165,7 @@ const SelectInput = (props: SelectInputProps) => { classNames={{ control: ({ isFocused, isDisabled }) => cn( - 'w-full min-h-12! rounded-lg! border bg-white transition-shadow cursor-pointer!', + 'w-full min-h-12! rounded! border bg-base-100 transition-shadow cursor-pointer!', { 'border-red-500! ring-2 ring-red-200': isError, 'border-indigo-500 ring-2 ring-indigo-200': isFocused, @@ -176,24 +181,24 @@ const SelectInput = (props: SelectInputProps) => { input: () => cn('text-gray-900'), indicatorsContainer: () => cn('flex items-center gap-1 pr-2'), dropdownIndicator: ({ isFocused }) => - cn('p-1 rounded-md hover:bg-gray-100', { + cn('p-1 rounded hover:bg-gray-100', { 'text-gray-900': isFocused, 'text-gray-500': !isFocused, 'text-error!': isError, }), menu: () => - cn('border border-gray-200 rounded-lg bg-white shadow-lg!'), + cn('border border-gray-200 rounded! bg-base-100 shadow-lg!'), menuList: () => cn('p-2! max-h-60 overflow-auto'), option: ({ isFocused, isSelected }) => - cn('mt-1 px-3 py-2 rounded-md cursor-pointer!', { - 'bg-indigo-600 text-white': isFocused, + cn('mt-1 px-3 py-2 rounded cursor-pointer!', { + 'bg-indigo-600 text-base-100': isFocused, 'bg-blue-500!': isSelected, 'text-gray-700': !isFocused && !isSelected, }), multiValue: ({ getValue, index }) => { const selectedValues = getValue() as T[]; return cn( - 'bg-indigo-50 rounded-md py-0.5 pl-2 pr-1 flex items-center gap-1!', + 'bg-indigo-50 rounded py-0.5 pl-2 pr-1 flex items-center gap-1!', selectedValues[index]?.className ); }, @@ -222,4 +227,45 @@ const SelectInput = (props: SelectInputProps) => { ); }; +const useSelect = ( + basePath: string, + valueKey: keyof T, + labelKey: keyof T, + searchKey: string = 'search', + params?: { [key: string]: string } +) => { + const [inputValue, setInputValue] = useState(''); + + const optionsUrlParams = useMemo(() => { + return new URLSearchParams({ + [searchKey]: inputValue ?? '', + ...params, + }).toString(); + }, [inputValue, searchKey]); + + const optionsUrl = `${basePath}?${optionsUrlParams}`; + + const { data, isLoading } = useSWR(optionsUrl, async (url) => { + return await httpClientFetcher>(url); + }); + + const options = isResponseSuccess(data) + ? data.data.map((item) => { + return { + value: getByPath(item, valueKey as string), + label: getByPath(item, labelKey as string), + }; + }) + : []; + + return { + inputValue, + setInputValue, + options, + isLoadingOptions: isLoading, + rawData: data, + }; +}; + +export { useSelect }; export default SelectInput; diff --git a/src/components/input/TextArea.tsx b/src/components/input/TextArea.tsx index e9517277..fbb1637a 100644 --- a/src/components/input/TextArea.tsx +++ b/src/components/input/TextArea.tsx @@ -1,10 +1,6 @@ 'use client'; -import { - ChangeEventHandler, - FocusEventHandler, - ReactNode, -} from 'react'; +import { ChangeEventHandler, FocusEventHandler, ReactNode } from 'react'; import { cn } from '@/lib/helper'; @@ -52,7 +48,7 @@ const TextArea = ({ onBlur, readOnly = false, isLoading = false, - rows = 3 + rows = 3, }: TextAreaProps) => { return (
)} - {startAdornment && startAdornment} + {startAdornment && startAdornment} -