From 42b95bb2129794aa2758879f67ca18ab5091ed75 Mon Sep 17 00:00:00 2001 From: efrilm Date: Thu, 11 Sep 2025 13:37:52 +0700 Subject: [PATCH] Fixed Asset Table --- .../(private)/apps/fixed-assets/page.tsx | 7 + .../layout/vertical/VerticalMenu.tsx | 8 + src/data/dictionaries/en.json | 3 +- src/data/dictionaries/id.json | 3 +- src/types/apps/fixedAssetTypes.ts | 8 + .../apps/fixed-assets/FixedAssetCard.tsx | 63 +++ .../apps/fixed-assets/FixedAssetTable.tsx | 512 ++++++++++++++++++ src/views/apps/fixed-assets/index.tsx | 18 + 8 files changed, 620 insertions(+), 2 deletions(-) create mode 100644 src/app/[lang]/(dashboard)/(private)/apps/fixed-assets/page.tsx create mode 100644 src/types/apps/fixedAssetTypes.ts create mode 100644 src/views/apps/fixed-assets/FixedAssetCard.tsx create mode 100644 src/views/apps/fixed-assets/FixedAssetTable.tsx create mode 100644 src/views/apps/fixed-assets/index.tsx diff --git a/src/app/[lang]/(dashboard)/(private)/apps/fixed-assets/page.tsx b/src/app/[lang]/(dashboard)/(private)/apps/fixed-assets/page.tsx new file mode 100644 index 0000000..fe82eda --- /dev/null +++ b/src/app/[lang]/(dashboard)/(private)/apps/fixed-assets/page.tsx @@ -0,0 +1,7 @@ +import FixedAssetList from '@/views/apps/fixed-assets' + +const FixedAssetPage = () => { + return +} + +export default FixedAssetPage diff --git a/src/components/layout/vertical/VerticalMenu.tsx b/src/components/layout/vertical/VerticalMenu.tsx index 880cd61..3c71659 100644 --- a/src/components/layout/vertical/VerticalMenu.tsx +++ b/src/components/layout/vertical/VerticalMenu.tsx @@ -137,6 +137,14 @@ const VerticalMenu = ({ dictionary, scrollMenu }: Props) => { > {dictionary['navigation'].account} + } + exactMatch={false} + activeUrl='/apps/fixed-assets' + > + {dictionary['navigation'].fixed_assets} + }> {dictionary['navigation'].list} diff --git a/src/data/dictionaries/en.json b/src/data/dictionaries/en.json index f5948c4..2194f85 100644 --- a/src/data/dictionaries/en.json +++ b/src/data/dictionaries/en.json @@ -125,6 +125,7 @@ "quotes": "Quotes", "expenses": "Expenses", "cash_and_bank": "Cash & Bank", - "account": "Account" + "account": "Account", + "fixed_assets": "Fixed Assets" } } diff --git a/src/data/dictionaries/id.json b/src/data/dictionaries/id.json index d080b8c..054b387 100644 --- a/src/data/dictionaries/id.json +++ b/src/data/dictionaries/id.json @@ -125,6 +125,7 @@ "quotes": "Penawaran", "expenses": "Biaya", "cash_and_bank": "Kas & Bank", - "account": "Akun" + "account": "Akun", + "fixed_assets": "Aset Tetap" } } diff --git a/src/types/apps/fixedAssetTypes.ts b/src/types/apps/fixedAssetTypes.ts new file mode 100644 index 0000000..8416aff --- /dev/null +++ b/src/types/apps/fixedAssetTypes.ts @@ -0,0 +1,8 @@ +export type FixedAssetType = { + id: number + assetName: string + puchaseBill: string // Code Purchase + reference: string + date: string + price: number +} diff --git a/src/views/apps/fixed-assets/FixedAssetCard.tsx b/src/views/apps/fixed-assets/FixedAssetCard.tsx new file mode 100644 index 0000000..1a15e48 --- /dev/null +++ b/src/views/apps/fixed-assets/FixedAssetCard.tsx @@ -0,0 +1,63 @@ +// MUI Imports +import Grid from '@mui/material/Grid2' + +// Type Imports +import type { UserDataType } from '@components/card-statistics/HorizontalWithSubtitle' + +// Component Imports +import HorizontalWithSubtitle from '@components/card-statistics/HorizontalWithSubtitle' + +// Vars +const data: UserDataType[] = [ + // Fixed Assets Data (from the image) + { + title: 'Nilai Aset', + stats: 'Rp 17.900.000', + avatarIcon: 'tabler-building-store', + avatarColor: 'success', + trend: 'positive', + trendNumber: '100%', + subtitle: 'Hari ini vs 365 hari lalu' + }, + { + title: 'Depresiasi Aset', + stats: 'Rp 0', + avatarIcon: 'tabler-trending-down', + avatarColor: 'secondary', + trend: 'neutral', + trendNumber: '0%', + subtitle: 'Tahun ini vs tanggal sama tahun lalu' + }, + { + title: 'Laba/Rugi Pelepasan Aset', + stats: 'Rp 0', + avatarIcon: 'tabler-exchange', + avatarColor: 'secondary', + trend: 'neutral', + trendNumber: '0%', + subtitle: 'Tahun ini vs tanggal sama tahun lalu' + }, + { + title: 'Aset Baru', + stats: 'Rp 17.900.000', + avatarIcon: 'tabler-plus-circle', + avatarColor: 'success', + trend: 'positive', + trendNumber: '100%', + subtitle: 'Tahun ini vs tanggal sama tahun lalu' + } +] + +const FixedAssetCards = () => { + return ( + + {data.map((item, i) => ( + + + + ))} + + ) +} + +export default FixedAssetCards diff --git a/src/views/apps/fixed-assets/FixedAssetTable.tsx b/src/views/apps/fixed-assets/FixedAssetTable.tsx new file mode 100644 index 0000000..f8226ba --- /dev/null +++ b/src/views/apps/fixed-assets/FixedAssetTable.tsx @@ -0,0 +1,512 @@ +'use client' + +// React Imports +import { useCallback, useEffect, useMemo, useState } from 'react' + +// Next Imports +import Link from 'next/link' +import { useParams } from 'next/navigation' + +// MUI Imports +import Button from '@mui/material/Button' +import Card from '@mui/material/Card' +import CardHeader from '@mui/material/CardHeader' +import Checkbox from '@mui/material/Checkbox' +import Chip from '@mui/material/Chip' +import IconButton from '@mui/material/IconButton' +import MenuItem from '@mui/material/MenuItem' +import { styled } from '@mui/material/styles' +import type { TextFieldProps } from '@mui/material/TextField' +import Typography from '@mui/material/Typography' + +// Third-party Imports +import type { RankingInfo } from '@tanstack/match-sorter-utils' +import { rankItem } from '@tanstack/match-sorter-utils' +import type { ColumnDef, FilterFn } from '@tanstack/react-table' +import { createColumnHelper, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table' +import classnames from 'classnames' + +// Type Imports +import type { Locale } from '@configs/i18n' + +// Component Imports +import CustomTextField from '@core/components/mui/TextField' +import OptionMenu from '@core/components/option-menu' + +// Style Imports +import tableStyles from '@core/styles/table.module.css' +import { Box, CircularProgress, TablePagination } from '@mui/material' +import { useDispatch } from 'react-redux' +import TablePaginationComponent from '@/components/TablePaginationComponent' +import Loading from '@/components/layout/shared/Loading' +import { getLocalizedUrl } from '@/utils/i18n' +import StatusFilterTabs from '@/components/StatusFilterTab' + +// Fixed Asset Type +export type FixedAssetType = { + id: number + assetName: string + puchaseBill: string // Code Purchase + reference: string + date: string + price: number +} + +declare module '@tanstack/table-core' { + interface FilterFns { + fuzzy: FilterFn + } + interface FilterMeta { + itemRank: RankingInfo + } +} + +type FixedAssetTypeWithAction = FixedAssetType & { + actions?: string +} + +// Dummy data for fixed assets +const fixedAssetData: FixedAssetType[] = [ + { + id: 1, + assetName: 'Laptop Dell XPS 13', + puchaseBill: 'PB-2024-001', + reference: 'REF-001', + date: '2024-01-15', + price: 15000000 + }, + { + id: 2, + assetName: 'Office Furniture Set', + puchaseBill: 'PB-2024-002', + reference: 'REF-002', + date: '2024-02-10', + price: 8500000 + }, + { + id: 3, + assetName: 'Printer Canon ImageClass', + puchaseBill: 'PB-2024-003', + reference: 'REF-003', + date: '2024-02-20', + price: 3200000 + }, + { + id: 4, + assetName: 'Air Conditioning Unit', + puchaseBill: 'PB-2024-004', + reference: 'REF-004', + date: '2024-03-05', + price: 12000000 + }, + { + id: 5, + assetName: 'Conference Room TV', + puchaseBill: 'PB-2024-005', + reference: 'REF-005', + date: '2024-03-15', + price: 7500000 + }, + { + id: 6, + assetName: 'MacBook Pro 16"', + puchaseBill: 'PB-2024-006', + reference: 'REF-006', + date: '2024-04-01', + price: 28000000 + }, + { + id: 7, + assetName: 'Standing Desk Electric', + puchaseBill: 'PB-2024-007', + reference: 'REF-007', + date: '2024-04-15', + price: 4500000 + }, + { + id: 8, + assetName: 'Server HP ProLiant', + puchaseBill: 'PB-2024-008', + reference: 'REF-008', + date: '2024-05-01', + price: 45000000 + } +] + +// Styled Components +const Icon = styled('i')({}) + +const fuzzyFilter: FilterFn = (row, columnId, value, addMeta) => { + // Rank the item + const itemRank = rankItem(row.getValue(columnId), value) + + // Store the itemRank info + addMeta({ + itemRank + }) + + // Return if the item should be filtered in/out + return itemRank.passed +} + +const DebouncedInput = ({ + value: initialValue, + onChange, + debounce = 500, + ...props +}: { + value: string | number + onChange: (value: string | number) => void + debounce?: number +} & Omit) => { + // States + const [value, setValue] = useState(initialValue) + + useEffect(() => { + setValue(initialValue) + }, [initialValue]) + + useEffect(() => { + const timeout = setTimeout(() => { + onChange(value) + }, debounce) + + return () => clearTimeout(timeout) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [value]) + + return setValue(e.target.value)} /> +} + +// Format currency +const formatCurrency = (amount: number) => { + return new Intl.NumberFormat('id-ID', { + style: 'currency', + currency: 'IDR', + minimumFractionDigits: 0 + }).format(amount) +} + +// Format date +const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString('id-ID', { + day: '2-digit', + month: '2-digit', + year: 'numeric' + }) +} + +// Column Definitions +const columnHelper = createColumnHelper() + +const FixedAssetTable = () => { + const dispatch = useDispatch() + + // States + const [addAssetOpen, setAddAssetOpen] = useState(false) + const [rowSelection, setRowSelection] = useState({}) + const [currentPage, setCurrentPage] = useState(0) + const [pageSize, setPageSize] = useState(10) + const [openConfirm, setOpenConfirm] = useState(false) + const [assetId, setAssetId] = useState('') + const [search, setSearch] = useState('') + const [filteredData, setFilteredData] = useState([]) + const [statusFilter, setStatusFilter] = useState('Draft') + + // Hooks + const { lang: locale } = useParams() + + // Initialize data on component mount + useEffect(() => { + console.log('Initial fixedAssetData:', fixedAssetData) + setFilteredData(fixedAssetData) + }, []) + + // Filter data based on search + useEffect(() => { + let filtered = [...fixedAssetData] + + // Filter by search + if (search) { + filtered = filtered.filter( + asset => + asset.assetName.toLowerCase().includes(search.toLowerCase()) || + asset.puchaseBill.toLowerCase().includes(search.toLowerCase()) || + asset.reference.toLowerCase().includes(search.toLowerCase()) + ) + } + + console.log('Filtered data:', filtered) // Debug log + setFilteredData(filtered) + setCurrentPage(0) + }, [search]) + + const totalCount = filteredData.length + const paginatedData = useMemo(() => { + const startIndex = currentPage * pageSize + return filteredData.slice(startIndex, startIndex + pageSize) + }, [filteredData, currentPage, pageSize]) + + // Calculate total value from filtered data + const totalValue = useMemo(() => { + return filteredData.reduce((sum, asset) => sum + asset.price, 0) + }, [filteredData]) + + const handlePageChange = useCallback((event: unknown, newPage: number) => { + setCurrentPage(newPage) + }, []) + + const handlePageSizeChange = useCallback((event: React.ChangeEvent) => { + const newPageSize = parseInt(event.target.value, 10) + setPageSize(newPageSize) + setCurrentPage(0) + }, []) + + const handleDelete = () => { + setOpenConfirm(false) + } + + const handleAssetClick = (assetId: string) => { + console.log('Navigasi ke detail Asset:', assetId) + } + + const handleStatusFilter = (status: string) => { + setStatusFilter(status) + } + + const columns = useMemo[]>( + () => [ + { + id: 'select', + header: ({ table }) => ( + + ), + cell: ({ row }) => ( + + ) + }, + columnHelper.accessor('puchaseBill', { + header: 'Kode Pembelian', + cell: ({ row }) => ( + + ) + }), + columnHelper.accessor('assetName', { + header: 'Nama Aset', + cell: ({ row }) => ( + + {row.original.assetName} + + ) + }), + columnHelper.accessor('reference', { + header: 'Referensi', + cell: ({ row }) => {row.original.reference || '-'} + }), + columnHelper.accessor('date', { + header: 'Tanggal Pembelian', + cell: ({ row }) => {formatDate(row.original.date)} + }), + columnHelper.accessor('price', { + header: 'Harga', + cell: ({ row }) => ( + {formatCurrency(row.original.price)} + ) + }) + ], + [locale] + ) + + const table = useReactTable({ + data: paginatedData as FixedAssetType[], + columns, + filterFns: { + fuzzy: fuzzyFilter + }, + state: { + rowSelection, + pagination: { + pageIndex: currentPage, + pageSize + } + }, + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + getCoreRowModel: getCoreRowModel(), + manualPagination: true, + pageCount: Math.ceil(totalCount / pageSize) + }) + + return ( + <> + + {/* Header */} +
+ +
+ +
+ setSearch(value as string)} + placeholder='Cari Aset Tetap' + className='max-sm:is-full' + /> +
+ + 10 + 25 + 50 + + + +
+
+ +
+ + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(header => ( + + ))} + + ))} + + {filteredData.length === 0 ? ( + + + + + + ) : ( + + {table.getRowModel().rows.map(row => { + return ( + + {row.getVisibleCells().map(cell => ( + + ))} + + ) + })} + + {/* Total Row */} + + + + + + + + + + + )} +
+ {header.isPlaceholder ? null : ( + <> +
+ {flexRender(header.column.columnDef.header, header.getContext())} + {{ + asc: , + desc: + }[header.column.getIsSorted() as 'asc' | 'desc'] ?? null} +
+ + )} +
+ Tidak ada data tersedia +
{flexRender(cell.column.columnDef.cell, cell.getContext())}
+ + Total Nilai Aset + + + + {formatCurrency(totalValue)} + +
+
+ + ( + + )} + count={totalCount} + rowsPerPage={pageSize} + page={currentPage} + onPageChange={handlePageChange} + onRowsPerPageChange={handlePageSizeChange} + rowsPerPageOptions={[10, 25, 50]} + /> +
+ + ) +} + +export default FixedAssetTable diff --git a/src/views/apps/fixed-assets/index.tsx b/src/views/apps/fixed-assets/index.tsx new file mode 100644 index 0000000..2de61a9 --- /dev/null +++ b/src/views/apps/fixed-assets/index.tsx @@ -0,0 +1,18 @@ +import Grid from '@mui/material/Grid2' +import FixedAssetCards from './FixedAssetCard' +import FixedAssetTable from './FixedAssetTable' + +const FixedAssetList = () => { + return ( + + + + + + + + + ) +} + +export default FixedAssetList