after finishing the OTC module, they said: "hey, can you also build the Opex Capex reporting?" cool cool cool. 😎
What is Opex Capex?
OPEX = Operational Expenditure (day-to-day expenses) CAPEX = Capital Expenditure (long-term investments)
Mining companies have TONS of both. tracking, reporting, comparing them? that's what this module does.
The Features
1. Interactive Expandable Tables
Users drill down from summary → category → subcategory → individual items.
interface OpexRow {
id: string;
category: string;
subcategory?: string;
amount: number;
children?: OpexRow[];
_expanded?: boolean;
}
const ExpandableTable = ({ data }: { data: OpexRow[] }) => (
<Table dataSource={data}>
<Column
dataIndex="category"
render={(text, record) => (
<Space>
{record.children && (
<button onClick={() => toggleExpand(record.id)}>
{record._expanded ? '▼' : '▶'}
</button>
)}
<span>{text}</span>
</Space>
)}
/>
<Column dataIndex="amount" render={formatCurrency} />
</Table>
);
2. Dynamic Opex Summary Table
18+ columns. users can:
- Show/hide any column
- Drag to reorder
- Pin important columns left
- Sort by any column
- Filter by period, category, cost center
// Column management state
const [columns, setColumns] = useState<ColumnConfig[]>(DEFAULT_COLUMNS);
const [pinnedColumns, setPinnedColumns] = useState(['category', 'total']);
const ColumnManager = () => (
<Dropdown
overlay={
<Menu>
{columns.map(col => (
<Menu.Item key={col.key}>
<Checkbox
checked={columns.includes(col)}
onChange={() => toggleColumn(col.key)}
/>
{col.title}
</Menu.Item>
))}
</Menu>
}
>
<Button icon={<SettingOutlined />}>Manage Columns</Button>
</Dropdown>
);
3. Budget vs Actual Report
interface BudgetActualRow {
category: string;
budget: number;
actual: number;
variance: number; // actual - budget
variancePercent: number;
status: 'on_track' | 'over_budget' | 'under_budget';
}
// Color-coded variance
const getVarianceColor = (status: string) => {
switch (status) {
case 'over_budget': return 'red';
case 'under_budget': return 'green';
default: return 'default';
}
};
The hard part? Budget and Actual data came from DIFFERENT systems. had to:
- Fetch from both APIs
- Normalize formats
- Match by category codes (which were different format in each system 💀)
- Calculate variances
- Handle missing data gracefully
4. Capex Summary with Table Merging
Some Capex items span multiple periods. needed to merge rows intelligently:
const mergeCapexRows = (data: CapexItem[]): MergedRow[] => {
const grouped = groupBy(data, 'capexProject');
return Object.entries(grouped).map(([project, items]) => {
const totalAmount = items.reduce((sum, item) => sum + item.amount, 0);
const startDate = min(items.map(i => i.startDate));
const endDate = max(items.map(i => i.endDate));
return {
project,
items: items.length,
totalAmount,
duration: differenceInDays(endDate, startDate),
rows: items // keep for drill-down
};
});
};
Financial Data Challenges
Data validation is EVERYTHING
- Negative amounts in wrong places
- Currency mismatches
- Missing period data
- Duplicate entries
Used Zod for runtime validation:
const OpexSchema = z.object({
category: z.string().min(1),
subcategory: z.string().optional(),
amount: z.number().positive(),
currency: z.enum(['IDR', 'USD']),
period: z.string().regex(/^\d{4}-\d{2}$/),
costCenter: z.string().regex(/^CC\d{4}$/),
});
const validateOpexData = (data: unknown): OpexData[] => {
if (Array.isArray(data)) {
return data.map(item => OpexSchema.parse(item));
}
throw new Error('Data must be an array');
};
Results
- ✅ Users can drill down from summary to individual transactions
- ✅ 18+ column flexibility without overwhelming users
- ✅ Real-time Budget vs Actual comparisons
- ✅ Capex projects properly aggregated across periods
- ✅ Export to Excel for offline analysis
Performance Optimization
With 10,000+ rows of data, we needed:
- Virtual scrolling (react-window)
- Lazy loading for drill-down
- Pagination on backend
- Memoization of calculations
What I Learned
- Financial data is messy - expect inconsistencies
- Table UX is hard - users want power + simplicity
- Data transformation - 70% of the code was reshaping data
- Business logic - understanding OPEX vs CAPEX was essential
building financial tools sounds boring until you realize: people make million-dollar decisions based on these reports. accuracy matters.
Related Articles
PAMAFix SAP - Transforming Monolith to Microservices
Revamping a monolithic ERP into microservices architecture - OTC module with dynamic forms, error handling, and .NET integration.
Kalla Property Management - Full-Stack Real Estate System
Built a comprehensive property management system for Kalla Group's real estate portfolio.
Music Distribution Platform - Licensing & Digital Contracts
Built a music distribution platform with payment gateway, digital signatures, and 20GB file uploads.