mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 05:22:02 +00:00
Merge branch 'feat/server-side-sorting' into 'development'
[FEAT/FE] Server Side Sorting See merge request mbugroup/lti-web-client!482
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
# Project-local RTK filters — commit this file with your repo.
|
||||||
|
# Filters here override user-global and built-in filters.
|
||||||
|
# Docs: https://github.com/rtk-ai/rtk#custom-filters
|
||||||
|
schema_version = 1
|
||||||
|
|
||||||
|
# Example: suppress build noise from a custom tool
|
||||||
|
# [filters.my-tool]
|
||||||
|
# description = "Compact my-tool output"
|
||||||
|
# match_command = "^my-tool\\s+build"
|
||||||
|
# strip_ansi = true
|
||||||
|
# strip_lines_matching = ["^\\s*$", "^Downloading", "^Installing"]
|
||||||
|
# max_lines = 30
|
||||||
|
# on_empty = "my-tool: ok"
|
||||||
@@ -260,3 +260,155 @@ const handleExportExcel = useCallback(async () => {
|
|||||||
- Do **not** import `xlsx`, `@react-pdf/renderer`, `jspdf`, `exceljs` in page/tab components.
|
- Do **not** import `xlsx`, `@react-pdf/renderer`, `jspdf`, `exceljs` in page/tab components.
|
||||||
|
|
||||||
**Reference implementation:** `MarketingReportApiService.exportDailyMarketingToExcel` / `exportDailyMarketingToPDF` in [src/services/api/report/marketing-report.ts](src/services/api/report/marketing-report.ts), consumed by [src/components/pages/report/marketing/tab/DailyMarketingTab.tsx](src/components/pages/report/marketing/tab/DailyMarketingTab.tsx).
|
**Reference implementation:** `MarketingReportApiService.exportDailyMarketingToExcel` / `exportDailyMarketingToPDF` in [src/services/api/report/marketing-report.ts](src/services/api/report/marketing-report.ts), consumed by [src/components/pages/report/marketing/tab/DailyMarketingTab.tsx](src/components/pages/report/marketing/tab/DailyMarketingTab.tsx).
|
||||||
|
|
||||||
|
<!-- rtk-instructions v2 -->
|
||||||
|
|
||||||
|
# RTK (Rust Token Killer) - Token-Optimized Commands
|
||||||
|
|
||||||
|
## Golden Rule
|
||||||
|
|
||||||
|
**Always prefix commands with `rtk`**. If RTK has a dedicated filter, it uses it. If not, it passes through unchanged. This means RTK is always safe to use.
|
||||||
|
|
||||||
|
**Important**: Even in command chains with `&&`, use `rtk`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ❌ Wrong
|
||||||
|
git add . && git commit -m "msg" && git push
|
||||||
|
|
||||||
|
# ✅ Correct
|
||||||
|
rtk git add . && rtk git commit -m "msg" && rtk git push
|
||||||
|
```
|
||||||
|
|
||||||
|
## RTK Commands by Workflow
|
||||||
|
|
||||||
|
### Build & Compile (80-90% savings)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rtk cargo build # Cargo build output
|
||||||
|
rtk cargo check # Cargo check output
|
||||||
|
rtk cargo clippy # Clippy warnings grouped by file (80%)
|
||||||
|
rtk tsc # TypeScript errors grouped by file/code (83%)
|
||||||
|
rtk lint # ESLint/Biome violations grouped (84%)
|
||||||
|
rtk prettier --check # Files needing format only (70%)
|
||||||
|
rtk next build # Next.js build with route metrics (87%)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test (60-99% savings)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rtk cargo test # Cargo test failures only (90%)
|
||||||
|
rtk go test # Go test failures only (90%)
|
||||||
|
rtk jest # Jest failures only (99.5%)
|
||||||
|
rtk vitest # Vitest failures only (99.5%)
|
||||||
|
rtk playwright test # Playwright failures only (94%)
|
||||||
|
rtk pytest # Python test failures only (90%)
|
||||||
|
rtk rake test # Ruby test failures only (90%)
|
||||||
|
rtk rspec # RSpec test failures only (60%)
|
||||||
|
rtk test <cmd> # Generic test wrapper - failures only
|
||||||
|
```
|
||||||
|
|
||||||
|
### Git (59-80% savings)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rtk git status # Compact status
|
||||||
|
rtk git log # Compact log (works with all git flags)
|
||||||
|
rtk git diff # Compact diff (80%)
|
||||||
|
rtk git show # Compact show (80%)
|
||||||
|
rtk git add # Ultra-compact confirmations (59%)
|
||||||
|
rtk git commit # Ultra-compact confirmations (59%)
|
||||||
|
rtk git push # Ultra-compact confirmations
|
||||||
|
rtk git pull # Ultra-compact confirmations
|
||||||
|
rtk git branch # Compact branch list
|
||||||
|
rtk git fetch # Compact fetch
|
||||||
|
rtk git stash # Compact stash
|
||||||
|
rtk git worktree # Compact worktree
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: Git passthrough works for ALL subcommands, even those not explicitly listed.
|
||||||
|
|
||||||
|
### GitHub (26-87% savings)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rtk gh pr view <num> # Compact PR view (87%)
|
||||||
|
rtk gh pr checks # Compact PR checks (79%)
|
||||||
|
rtk gh run list # Compact workflow runs (82%)
|
||||||
|
rtk gh issue list # Compact issue list (80%)
|
||||||
|
rtk gh api # Compact API responses (26%)
|
||||||
|
```
|
||||||
|
|
||||||
|
### JavaScript/TypeScript Tooling (70-90% savings)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rtk pnpm list # Compact dependency tree (70%)
|
||||||
|
rtk pnpm outdated # Compact outdated packages (80%)
|
||||||
|
rtk pnpm install # Compact install output (90%)
|
||||||
|
rtk npm run <script> # Compact npm script output
|
||||||
|
rtk npx <cmd> # Compact npx command output
|
||||||
|
rtk prisma # Prisma without ASCII art (88%)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Files & Search (60-75% savings)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rtk ls <path> # Tree format, compact (65%)
|
||||||
|
rtk read <file> # Code reading with filtering (60%)
|
||||||
|
rtk grep <pattern> # Search grouped by file (75%)
|
||||||
|
rtk find <pattern> # Find grouped by directory (70%)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Analysis & Debug (70-90% savings)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rtk err <cmd> # Filter errors only from any command
|
||||||
|
rtk log <file> # Deduplicated logs with counts
|
||||||
|
rtk json <file> # JSON structure without values
|
||||||
|
rtk deps # Dependency overview
|
||||||
|
rtk env # Environment variables compact
|
||||||
|
rtk summary <cmd> # Smart summary of command output
|
||||||
|
rtk diff # Ultra-compact diffs
|
||||||
|
```
|
||||||
|
|
||||||
|
### Infrastructure (85% savings)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rtk docker ps # Compact container list
|
||||||
|
rtk docker images # Compact image list
|
||||||
|
rtk docker logs <c> # Deduplicated logs
|
||||||
|
rtk kubectl get # Compact resource list
|
||||||
|
rtk kubectl logs # Deduplicated pod logs
|
||||||
|
```
|
||||||
|
|
||||||
|
### Network (65-70% savings)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rtk curl <url> # Compact HTTP responses (70%)
|
||||||
|
rtk wget <url> # Compact download output (65%)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Meta Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rtk gain # View token savings statistics
|
||||||
|
rtk gain --history # View command history with savings
|
||||||
|
rtk discover # Analyze Claude Code sessions for missed RTK usage
|
||||||
|
rtk proxy <cmd> # Run command without filtering (for debugging)
|
||||||
|
rtk init # Add RTK instructions to CLAUDE.md
|
||||||
|
rtk init --global # Add RTK to ~/.claude/CLAUDE.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Token Savings Overview
|
||||||
|
|
||||||
|
| Category | Commands | Typical Savings |
|
||||||
|
| ---------------- | ------------------------------ | --------------- |
|
||||||
|
| Tests | vitest, playwright, cargo test | 90-99% |
|
||||||
|
| Build | next, tsc, lint, prettier | 70-87% |
|
||||||
|
| Git | status, log, diff, add, commit | 59-80% |
|
||||||
|
| GitHub | gh pr, gh run, gh issue | 26-87% |
|
||||||
|
| Package Managers | pnpm, npm, npx | 70-90% |
|
||||||
|
| Files | ls, read, grep, find | 60-75% |
|
||||||
|
| Infrastructure | docker, kubectl | 85% |
|
||||||
|
| Network | curl, wget | 65-70% |
|
||||||
|
|
||||||
|
Overall average: **60-90% token reduction** on common development operations.
|
||||||
|
|
||||||
|
<!-- /rtk-instructions -->
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import {
|
|||||||
} from '@/services/api/master-data';
|
} from '@/services/api/master-data';
|
||||||
import { Supplier } from '@/types/api/master-data/supplier';
|
import { Supplier } from '@/types/api/master-data/supplier';
|
||||||
import { Nonstock } from '@/types/api/master-data/nonstock';
|
import { Nonstock } from '@/types/api/master-data/nonstock';
|
||||||
import { ColumnDef } from '@tanstack/react-table';
|
import { ColumnDef, SortingState, Updater } from '@tanstack/react-table';
|
||||||
import { httpClient } from '@/services/http/client';
|
import { httpClient } from '@/services/http/client';
|
||||||
import { BaseApiResponse } from '@/types/api/api-general';
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
import ButtonFilter from '@/components/helper/ButtonFilter';
|
import ButtonFilter from '@/components/helper/ButtonFilter';
|
||||||
@@ -73,6 +73,25 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
|||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [pageSize, setPageSize] = useState(10);
|
const [pageSize, setPageSize] = useState(10);
|
||||||
|
|
||||||
|
// ===== SORTING STATE =====
|
||||||
|
const [sortBy, setSortBy] = useState('');
|
||||||
|
const [orderBy, setOrderBy] = useState('');
|
||||||
|
|
||||||
|
const sorting: SortingState = sortBy
|
||||||
|
? [{ id: sortBy, desc: orderBy === 'desc' }]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const handleSortingChange = (updater: Updater<SortingState>) => {
|
||||||
|
const next = typeof updater === 'function' ? updater(sorting) : updater;
|
||||||
|
if (next.length > 0) {
|
||||||
|
setSortBy(next[0].id);
|
||||||
|
setOrderBy(next[0].desc ? 'desc' : 'asc');
|
||||||
|
} else {
|
||||||
|
setSortBy('');
|
||||||
|
setOrderBy('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleFilterModalOpenRef = useRef(() => {});
|
const handleFilterModalOpenRef = useRef(() => {});
|
||||||
|
|
||||||
const filterModal = useModal();
|
const filterModal = useModal();
|
||||||
@@ -252,6 +271,8 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
|||||||
if (filterParams.category) {
|
if (filterParams.category) {
|
||||||
params.append('category', filterParams.category);
|
params.append('category', filterParams.category);
|
||||||
}
|
}
|
||||||
|
if (sortBy) params.append('sort_by', sortBy);
|
||||||
|
if (orderBy) params.append('sort_order', orderBy);
|
||||||
|
|
||||||
Object.entries(extraParams ?? {}).forEach(([key, value]) => {
|
Object.entries(extraParams ?? {}).forEach(([key, value]) => {
|
||||||
params.set(key, value);
|
params.set(key, value);
|
||||||
@@ -259,7 +280,7 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
|||||||
|
|
||||||
return params.toString();
|
return params.toString();
|
||||||
},
|
},
|
||||||
[filterParams]
|
[filterParams, sortBy, orderBy]
|
||||||
);
|
);
|
||||||
|
|
||||||
// ===== DATA FETCHING =====
|
// ===== DATA FETCHING =====
|
||||||
@@ -443,19 +464,23 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
header: 'No',
|
header: 'No',
|
||||||
|
enableSorting: false,
|
||||||
cell: (props) => (page - 1) * pageSize + props.row.index + 1,
|
cell: (props) => (page - 1) * pageSize + props.row.index + 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'No. PO',
|
header: 'No. PO',
|
||||||
accessorKey: 'po_number',
|
accessorKey: 'po_number',
|
||||||
|
enableSorting: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'No. Referensi',
|
header: 'No. Referensi',
|
||||||
accessorKey: 'reference_number',
|
accessorKey: 'reference_number',
|
||||||
|
enableSorting: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Tanggal Realisasi',
|
header: 'Tanggal Realisasi',
|
||||||
accessorKey: 'realization_date',
|
accessorKey: 'realization_date',
|
||||||
|
enableSorting: true,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return formatDate(row.original?.realization_date, 'DD MMM, YYYY');
|
return formatDate(row.original?.realization_date, 'DD MMM, YYYY');
|
||||||
},
|
},
|
||||||
@@ -463,6 +488,7 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
|||||||
{
|
{
|
||||||
header: 'Tanggal Transaksi',
|
header: 'Tanggal Transaksi',
|
||||||
accessorKey: 'transaction_date',
|
accessorKey: 'transaction_date',
|
||||||
|
enableSorting: true,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return formatDate(row.original?.transaction_date, 'DD MMM, YYYY');
|
return formatDate(row.original?.transaction_date, 'DD MMM, YYYY');
|
||||||
},
|
},
|
||||||
@@ -470,21 +496,30 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
|||||||
{
|
{
|
||||||
header: 'Kategori',
|
header: 'Kategori',
|
||||||
accessorKey: 'category',
|
accessorKey: 'category',
|
||||||
|
enableSorting: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Produk',
|
header: 'Produk',
|
||||||
|
accessorKey: 'product',
|
||||||
|
enableSorting: true,
|
||||||
accessorFn: (row) => row.pengajuan?.nonstock?.name,
|
accessorFn: (row) => row.pengajuan?.nonstock?.name,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Supplier',
|
header: 'Supplier',
|
||||||
|
accessorKey: 'supplier',
|
||||||
|
enableSorting: true,
|
||||||
accessorFn: (row) => row.supplier?.name,
|
accessorFn: (row) => row.supplier?.name,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Lokasi',
|
header: 'Lokasi',
|
||||||
|
accessorKey: 'location',
|
||||||
|
enableSorting: true,
|
||||||
accessorFn: (row) => row.kandang?.location?.name,
|
accessorFn: (row) => row.kandang?.location?.name,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Kandang',
|
header: 'Kandang',
|
||||||
|
accessorKey: 'kandang',
|
||||||
|
enableSorting: true,
|
||||||
accessorFn: (row) => row.kandang?.name,
|
accessorFn: (row) => row.kandang?.name,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -492,23 +527,19 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
|||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
header: 'Qty',
|
header: 'Qty',
|
||||||
id: 'qty_pengajuan',
|
accessorKey: 'qty_pengajuan',
|
||||||
accessorFn: (row) => row.pengajuan?.qty,
|
|
||||||
cell: ({ row }) =>
|
cell: ({ row }) =>
|
||||||
row.original.pengajuan?.qty?.toLocaleString('id-ID') || '0',
|
row.original.pengajuan?.qty?.toLocaleString('id-ID') || '0',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Harga',
|
header: 'Harga',
|
||||||
id: 'harga_pengajuan',
|
accessorKey: 'price_pengajuan',
|
||||||
accessorFn: (row) => row.pengajuan?.price,
|
|
||||||
cell: ({ row }) =>
|
cell: ({ row }) =>
|
||||||
formatCurrency(row.original.pengajuan?.price || 0),
|
formatCurrency(row.original.pengajuan?.price || 0),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Total',
|
header: 'Total',
|
||||||
id: 'total_pengajuan',
|
accessorKey: 'total_pengajuan',
|
||||||
accessorFn: (row) =>
|
|
||||||
(row.pengajuan?.qty || 0) * (row.pengajuan?.price || 0),
|
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const total =
|
const total =
|
||||||
(row.original.pengajuan?.qty || 0) *
|
(row.original.pengajuan?.qty || 0) *
|
||||||
@@ -523,23 +554,19 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
|||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
header: 'Qty',
|
header: 'Qty',
|
||||||
id: 'qty_realisasi',
|
accessorKey: 'qty_realisasi',
|
||||||
accessorFn: (row) => row.realisasi?.qty,
|
|
||||||
cell: ({ row }) =>
|
cell: ({ row }) =>
|
||||||
row.original.realisasi?.qty?.toLocaleString('id-ID') || '0',
|
row.original.realisasi?.qty?.toLocaleString('id-ID') || '0',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Harga',
|
header: 'Harga',
|
||||||
id: 'harga_realisasi',
|
accessorKey: 'price_realisasi',
|
||||||
accessorFn: (row) => row.realisasi?.price,
|
|
||||||
cell: ({ row }) =>
|
cell: ({ row }) =>
|
||||||
formatCurrency(row.original.realisasi?.price || 0),
|
formatCurrency(row.original.realisasi?.price || 0),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Total',
|
header: 'Total',
|
||||||
id: 'total_realisasi',
|
accessorKey: 'total_realisasi',
|
||||||
accessorFn: (row) =>
|
|
||||||
(row.realisasi?.qty || 0) * (row.realisasi?.price || 0),
|
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const total =
|
const total =
|
||||||
(row.original.realisasi?.qty || 0) *
|
(row.original.realisasi?.qty || 0) *
|
||||||
@@ -550,6 +577,7 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 'realization_status',
|
||||||
header: 'Status Pencairan',
|
header: 'Status Pencairan',
|
||||||
cell: (props) => (
|
cell: (props) => (
|
||||||
<RealizationStatusBadge
|
<RealizationStatusBadge
|
||||||
@@ -558,6 +586,7 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 'bop_status',
|
||||||
header: 'Status BOP',
|
header: 'Status BOP',
|
||||||
cell: (props) => (
|
cell: (props) => (
|
||||||
<ExpenseStatusBadge approval={props.row.original?.latest_approval} />
|
<ExpenseStatusBadge approval={props.row.original?.latest_approval} />
|
||||||
@@ -602,6 +631,9 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
|||||||
totalItems={meta?.total_results || 0}
|
totalItems={meta?.total_results || 0}
|
||||||
onPageChange={setPage}
|
onPageChange={setPage}
|
||||||
onPageSizeChange={setPageSize}
|
onPageSizeChange={setPageSize}
|
||||||
|
sorting={sorting}
|
||||||
|
setSorting={handleSortingChange}
|
||||||
|
manualSorting
|
||||||
className={{
|
className={{
|
||||||
containerClassName: 'w-full mb-0!',
|
containerClassName: 'w-full mb-0!',
|
||||||
tableWrapperClassName: 'overflow-x-auto',
|
tableWrapperClassName: 'overflow-x-auto',
|
||||||
|
|||||||
Reference in New Issue
Block a user