Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
import { useState, useMemo } from 'react';
|
|
|
|
|
import {
|
|
|
|
|
Typography,
|
|
|
|
|
Button,
|
|
|
|
|
Card,
|
|
|
|
|
Table,
|
|
|
|
|
Space,
|
|
|
|
|
Tag,
|
|
|
|
|
Modal,
|
|
|
|
|
Form,
|
|
|
|
|
Input,
|
|
|
|
|
Select,
|
|
|
|
|
Spin,
|
|
|
|
|
Alert,
|
|
|
|
|
Drawer,
|
|
|
|
|
Descriptions,
|
|
|
|
|
Row,
|
|
|
|
|
Col,
|
|
|
|
|
Statistic,
|
|
|
|
|
DatePicker,
|
|
|
|
|
Divider,
|
|
|
|
|
List,
|
|
|
|
|
Checkbox,
|
|
|
|
|
Radio,
|
|
|
|
|
Tooltip,
|
|
|
|
|
} from 'antd';
|
|
|
|
|
import { showSuccess, showError, showWarning } from '@/lib/errorHandling';
|
|
|
|
|
import {
|
|
|
|
|
PlusOutlined,
|
|
|
|
|
SearchOutlined,
|
|
|
|
|
EyeOutlined,
|
|
|
|
|
CheckOutlined,
|
|
|
|
|
StopOutlined,
|
|
|
|
|
FileTextOutlined,
|
|
|
|
|
ShoppingCartOutlined,
|
|
|
|
|
FileDoneOutlined,
|
|
|
|
|
BarcodeOutlined,
|
|
|
|
|
} from '@ant-design/icons';
|
|
|
|
|
import dayjs from 'dayjs';
|
|
|
|
|
import { useCompany } from '@/hooks/useCompany';
|
|
|
|
|
import { useCurrentFiscalYear } from '@/stores/periodStore';
|
|
|
|
|
import { useOrders } from '@/api/queries/orderQueries';
|
|
|
|
|
import { useActiveCustomers, type Customer } from '@/api/queries/customerQueries';
|
|
|
|
|
import { useActiveProducts, type Product } from '@/api/queries/productQueries';
|
|
|
|
|
import {
|
|
|
|
|
useCreateOrder,
|
|
|
|
|
useAddOrderLine,
|
|
|
|
|
useConfirmOrder,
|
|
|
|
|
useCancelOrder,
|
|
|
|
|
useConvertOrderToInvoice,
|
|
|
|
|
type CreateOrderInput,
|
|
|
|
|
type AddOrderLineInput,
|
|
|
|
|
type CancelOrderInput,
|
|
|
|
|
type InvoiceOrderLinesInput,
|
|
|
|
|
} from '@/api/mutations/orderMutations';
|
|
|
|
|
import { formatCurrency, formatDate } from '@/lib/formatters';
|
|
|
|
|
import { spacing } from '@/styles/designTokens';
|
|
|
|
|
import { accountingColors } from '@/styles/theme';
|
|
|
|
|
import { AmountText } from '@/components/shared/AmountText';
|
|
|
|
|
import { EmptyState } from '@/components/shared/EmptyState';
|
2026-02-05 21:35:26 +01:00
|
|
|
import { PageHeader } from '@/components/shared/PageHeader';
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
import type { ColumnsType } from 'antd/es/table';
|
|
|
|
|
import type { Order, OrderLine, OrderStatus } from '@/types/order';
|
|
|
|
|
import { ORDER_STATUS_LABELS, ORDER_STATUS_COLORS } from '@/types/order';
|
|
|
|
|
|
|
|
|
|
const { Title, Text } = Typography;
|
|
|
|
|
|
|
|
|
|
export default function Ordrer() {
|
|
|
|
|
const { company } = useCompany();
|
|
|
|
|
const currentFiscalYear = useCurrentFiscalYear();
|
|
|
|
|
|
|
|
|
|
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
|
|
|
|
const [isAddLineModalOpen, setIsAddLineModalOpen] = useState(false);
|
|
|
|
|
const [isCancelModalOpen, setIsCancelModalOpen] = useState(false);
|
|
|
|
|
const [isConvertModalOpen, setIsConvertModalOpen] = useState(false);
|
|
|
|
|
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
|
|
|
|
const [selectedOrder, setSelectedOrder] = useState<Order | null>(null);
|
|
|
|
|
const [selectedLinesToInvoice, setSelectedLinesToInvoice] = useState<number[]>([]);
|
|
|
|
|
const [searchText, setSearchText] = useState('');
|
|
|
|
|
const [statusFilter, setStatusFilter] = useState<OrderStatus | 'all'>('all');
|
|
|
|
|
const [addLineMode, setAddLineMode] = useState<'product' | 'freetext'>('product');
|
|
|
|
|
const [selectedProductId, setSelectedProductId] = useState<string | null>(null);
|
|
|
|
|
const [createForm] = Form.useForm();
|
|
|
|
|
const [addLineForm] = Form.useForm();
|
|
|
|
|
const [cancelForm] = Form.useForm();
|
|
|
|
|
|
|
|
|
|
// Fetch orders
|
|
|
|
|
const {
|
|
|
|
|
data: orders = [],
|
|
|
|
|
isLoading: loading,
|
|
|
|
|
error,
|
|
|
|
|
refetch,
|
|
|
|
|
} = useOrders(company?.id);
|
|
|
|
|
|
|
|
|
|
// Fetch customers for dropdown
|
|
|
|
|
const { data: customers = [] } = useActiveCustomers(company?.id);
|
|
|
|
|
|
|
|
|
|
// Fetch products for dropdown
|
|
|
|
|
const { data: products = [] } = useActiveProducts(company?.id);
|
|
|
|
|
|
|
|
|
|
// Mutations
|
|
|
|
|
const createOrderMutation = useCreateOrder();
|
|
|
|
|
const addOrderLineMutation = useAddOrderLine();
|
|
|
|
|
const confirmOrderMutation = useConfirmOrder();
|
|
|
|
|
const cancelOrderMutation = useCancelOrder();
|
|
|
|
|
const convertToInvoiceMutation = useConvertOrderToInvoice();
|
|
|
|
|
|
|
|
|
|
// Filter orders
|
|
|
|
|
const filteredOrders = useMemo(() => {
|
|
|
|
|
return orders.filter((order) => {
|
|
|
|
|
const matchesSearch =
|
|
|
|
|
searchText === '' ||
|
|
|
|
|
order.orderNumber.toLowerCase().includes(searchText.toLowerCase()) ||
|
|
|
|
|
order.customerName.toLowerCase().includes(searchText.toLowerCase());
|
|
|
|
|
|
|
|
|
|
const matchesStatus = statusFilter === 'all' || order.status === statusFilter;
|
|
|
|
|
|
|
|
|
|
return matchesSearch && matchesStatus;
|
|
|
|
|
});
|
|
|
|
|
}, [orders, searchText, statusFilter]);
|
|
|
|
|
|
|
|
|
|
// Statistics
|
|
|
|
|
const stats = useMemo(() => {
|
|
|
|
|
const total = orders.length;
|
|
|
|
|
const drafts = orders.filter((o) => o.status === 'draft').length;
|
|
|
|
|
const confirmed = orders.filter((o) => o.status === 'confirmed').length;
|
|
|
|
|
const totalValue = orders
|
|
|
|
|
.filter((o) => o.status !== 'cancelled')
|
|
|
|
|
.reduce((sum, o) => sum + o.amountTotal, 0);
|
|
|
|
|
const invoicedValue = orders.reduce((sum, o) => sum + (o.amountTotal - (o.uninvoicedAmount ?? 0)), 0);
|
|
|
|
|
return { total, drafts, confirmed, totalValue, invoicedValue };
|
|
|
|
|
}, [orders]);
|
|
|
|
|
|
|
|
|
|
const handleCreateOrder = () => {
|
|
|
|
|
createForm.resetFields();
|
|
|
|
|
createForm.setFieldsValue({
|
|
|
|
|
orderDate: dayjs(),
|
|
|
|
|
});
|
|
|
|
|
setIsCreateModalOpen(true);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleSubmitCreate = async () => {
|
|
|
|
|
if (!company || !currentFiscalYear) {
|
2026-02-05 21:35:26 +01:00
|
|
|
showError('Virksomhed eller regnskabsår ikke valgt');
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
const values = await createForm.validateFields();
|
|
|
|
|
const input: CreateOrderInput = {
|
|
|
|
|
companyId: company.id,
|
|
|
|
|
fiscalYearId: currentFiscalYear.id,
|
|
|
|
|
customerId: values.customerId,
|
|
|
|
|
orderDate: values.orderDate?.toISOString(),
|
|
|
|
|
expectedDeliveryDate: values.expectedDeliveryDate?.toISOString(),
|
|
|
|
|
notes: values.notes || undefined,
|
|
|
|
|
reference: values.reference || undefined,
|
|
|
|
|
};
|
|
|
|
|
const result = await createOrderMutation.mutateAsync(input);
|
|
|
|
|
showSuccess('Ordre oprettet');
|
|
|
|
|
setIsCreateModalOpen(false);
|
|
|
|
|
createForm.resetFields();
|
|
|
|
|
setSelectedOrder(result);
|
|
|
|
|
setIsDrawerOpen(true);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
if (err instanceof Error) {
|
|
|
|
|
showError(err);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleViewOrder = (order: Order) => {
|
|
|
|
|
setSelectedOrder(order);
|
|
|
|
|
setIsDrawerOpen(true);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleOpenAddLineModal = () => {
|
|
|
|
|
addLineForm.resetFields();
|
|
|
|
|
addLineForm.setFieldsValue({
|
|
|
|
|
quantity: 1,
|
|
|
|
|
vatCode: 'S25',
|
|
|
|
|
});
|
|
|
|
|
setAddLineMode('product');
|
|
|
|
|
setSelectedProductId(null);
|
|
|
|
|
setIsAddLineModalOpen(true);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleProductSelect = (productId: string) => {
|
|
|
|
|
setSelectedProductId(productId);
|
|
|
|
|
const product = products.find((p: Product) => p.id === productId);
|
|
|
|
|
if (product) {
|
|
|
|
|
addLineForm.setFieldsValue({
|
|
|
|
|
description: product.name,
|
|
|
|
|
unitPrice: product.unitPrice,
|
|
|
|
|
unit: product.unit || 'stk',
|
|
|
|
|
vatCode: product.vatCode || 'S25',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleSubmitAddLine = async () => {
|
|
|
|
|
if (!selectedOrder) return;
|
|
|
|
|
try {
|
|
|
|
|
const values = await addLineForm.validateFields();
|
|
|
|
|
const input: AddOrderLineInput = {
|
|
|
|
|
orderId: selectedOrder.id,
|
|
|
|
|
productId: addLineMode === 'product' && selectedProductId ? selectedProductId : undefined,
|
|
|
|
|
description: values.description,
|
|
|
|
|
quantity: Number(values.quantity),
|
|
|
|
|
unitPrice: Number(values.unitPrice),
|
|
|
|
|
unit: values.unit || undefined,
|
|
|
|
|
discountPercent: values.discountPercent ? Number(values.discountPercent) : undefined,
|
|
|
|
|
vatCode: values.vatCode,
|
|
|
|
|
};
|
|
|
|
|
const updatedOrder = await addOrderLineMutation.mutateAsync(input);
|
|
|
|
|
showSuccess('Linje tilføjet');
|
|
|
|
|
setIsAddLineModalOpen(false);
|
|
|
|
|
addLineForm.resetFields();
|
|
|
|
|
setSelectedProductId(null);
|
|
|
|
|
setSelectedOrder(updatedOrder);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
if (err instanceof Error) {
|
|
|
|
|
showError(err);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleConfirmOrder = async () => {
|
|
|
|
|
if (!selectedOrder) return;
|
|
|
|
|
if (selectedOrder.lines.length === 0) {
|
2026-02-05 21:35:26 +01:00
|
|
|
showWarning('Tilføj mindst en linje før bekræftelse');
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
await confirmOrderMutation.mutateAsync(selectedOrder.id);
|
2026-02-05 21:35:26 +01:00
|
|
|
showSuccess('Ordre bekræftet');
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
// Refresh would happen via query invalidation
|
|
|
|
|
} catch (err) {
|
|
|
|
|
if (err instanceof Error) {
|
|
|
|
|
showError(err);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleOpenCancelModal = () => {
|
|
|
|
|
cancelForm.resetFields();
|
|
|
|
|
setIsCancelModalOpen(true);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleSubmitCancel = async () => {
|
|
|
|
|
if (!selectedOrder) return;
|
|
|
|
|
try {
|
|
|
|
|
const values = await cancelForm.validateFields();
|
|
|
|
|
const input: CancelOrderInput = {
|
|
|
|
|
orderId: selectedOrder.id,
|
|
|
|
|
reason: values.reason,
|
|
|
|
|
};
|
|
|
|
|
await cancelOrderMutation.mutateAsync(input);
|
|
|
|
|
showSuccess('Ordre annulleret');
|
|
|
|
|
setIsCancelModalOpen(false);
|
|
|
|
|
cancelForm.resetFields();
|
|
|
|
|
} catch (err) {
|
|
|
|
|
if (err instanceof Error) {
|
|
|
|
|
showError(err);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleOpenConvertModal = () => {
|
|
|
|
|
if (!selectedOrder) return;
|
|
|
|
|
// Pre-select uninvoiced lines
|
|
|
|
|
const uninvoicedLines = selectedOrder.lines
|
|
|
|
|
.filter((line) => !line.isInvoiced)
|
|
|
|
|
.map((line) => line.lineNumber);
|
|
|
|
|
setSelectedLinesToInvoice(uninvoicedLines);
|
|
|
|
|
setIsConvertModalOpen(true);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleSubmitConvert = async () => {
|
|
|
|
|
if (!selectedOrder || selectedLinesToInvoice.length === 0) {
|
2026-02-05 21:35:26 +01:00
|
|
|
showWarning('Vælg mindst en linje at fakturere');
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
const input: InvoiceOrderLinesInput = {
|
|
|
|
|
orderId: selectedOrder.id,
|
|
|
|
|
lineNumbers: selectedLinesToInvoice,
|
|
|
|
|
};
|
|
|
|
|
const invoice = await convertToInvoiceMutation.mutateAsync(input);
|
|
|
|
|
showSuccess(`Faktura ${invoice.invoiceNumber} oprettet fra ordre`);
|
|
|
|
|
setIsConvertModalOpen(false);
|
|
|
|
|
setSelectedLinesToInvoice([]);
|
|
|
|
|
// Refresh the selected order to show updated invoice tracking
|
|
|
|
|
refetch();
|
|
|
|
|
} catch (err) {
|
|
|
|
|
if (err instanceof Error) {
|
|
|
|
|
showError(err);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Check if order can show the convert to invoice button
|
|
|
|
|
const canShowConvertToInvoice = (order: Order): boolean => {
|
|
|
|
|
if (order.status === 'cancelled' || order.status === 'fully_invoiced') {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// Show for draft (disabled) and confirmed/partially_invoiced (enabled)
|
|
|
|
|
return order.status === 'draft' || order.lines.some((line) => !line.isInvoiced);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Check if convert to invoice button should be disabled
|
|
|
|
|
const isConvertToInvoiceDisabled = (order: Order): boolean => {
|
|
|
|
|
if (order.status === 'draft') {
|
|
|
|
|
return true; // Must confirm order first
|
|
|
|
|
}
|
|
|
|
|
return !order.lines.some((line) => !line.isInvoiced);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const columns: ColumnsType<Order> = [
|
|
|
|
|
{
|
|
|
|
|
title: 'Ordrenr.',
|
|
|
|
|
dataIndex: 'orderNumber',
|
|
|
|
|
key: 'orderNumber',
|
|
|
|
|
width: 140,
|
|
|
|
|
sorter: (a, b) => a.orderNumber.localeCompare(b.orderNumber),
|
|
|
|
|
render: (value: string) => <Text code>{value}</Text>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: 'Kunde',
|
|
|
|
|
dataIndex: 'customerName',
|
|
|
|
|
key: 'customerName',
|
|
|
|
|
sorter: (a, b) => a.customerName.localeCompare(b.customerName),
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: 'Dato',
|
|
|
|
|
dataIndex: 'orderDate',
|
|
|
|
|
key: 'orderDate',
|
|
|
|
|
width: 100,
|
|
|
|
|
sorter: (a, b) => (a.orderDate || '').localeCompare(b.orderDate || ''),
|
|
|
|
|
render: (value: string | undefined) => (value ? formatDate(value) : '-'),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: 'Forventet levering',
|
|
|
|
|
dataIndex: 'expectedDeliveryDate',
|
|
|
|
|
key: 'expectedDeliveryDate',
|
|
|
|
|
width: 130,
|
|
|
|
|
render: (value: string | undefined) => (value ? formatDate(value) : '-'),
|
|
|
|
|
},
|
|
|
|
|
{
|
2026-02-05 21:35:26 +01:00
|
|
|
title: 'Beløb',
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
dataIndex: 'amountTotal',
|
|
|
|
|
key: 'amountTotal',
|
|
|
|
|
width: 120,
|
|
|
|
|
align: 'right',
|
|
|
|
|
sorter: (a, b) => a.amountTotal - b.amountTotal,
|
|
|
|
|
render: (value: number) => <AmountText amount={value} />,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: 'Faktureret',
|
|
|
|
|
dataIndex: 'amountInvoiced',
|
|
|
|
|
key: 'amountInvoiced',
|
|
|
|
|
width: 120,
|
|
|
|
|
align: 'right',
|
|
|
|
|
render: (value: number, record: Order) =>
|
|
|
|
|
record.status === 'cancelled' ? '-' : <AmountText amount={value} />,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: 'Status',
|
|
|
|
|
dataIndex: 'status',
|
|
|
|
|
key: 'status',
|
|
|
|
|
width: 140,
|
|
|
|
|
align: 'center',
|
|
|
|
|
filters: [
|
|
|
|
|
{ text: 'Kladde', value: 'draft' },
|
2026-02-05 21:35:26 +01:00
|
|
|
{ text: 'Bekræftet', value: 'confirmed' },
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
{ text: 'Delvist faktureret', value: 'partially_invoiced' },
|
|
|
|
|
{ text: 'Fuldt faktureret', value: 'fully_invoiced' },
|
|
|
|
|
{ text: 'Annulleret', value: 'cancelled' },
|
|
|
|
|
],
|
|
|
|
|
onFilter: (value, record) => record.status === value,
|
|
|
|
|
render: (value: OrderStatus) => (
|
|
|
|
|
<Tag color={ORDER_STATUS_COLORS[value]}>{ORDER_STATUS_LABELS[value]}</Tag>
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '',
|
|
|
|
|
key: 'actions',
|
|
|
|
|
width: 80,
|
|
|
|
|
align: 'center',
|
|
|
|
|
render: (_: unknown, record: Order) => (
|
|
|
|
|
<Button
|
|
|
|
|
type="text"
|
|
|
|
|
size="small"
|
|
|
|
|
icon={<EyeOutlined />}
|
|
|
|
|
onClick={() => handleViewOrder(record)}
|
|
|
|
|
/>
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div>
|
2026-02-05 21:35:26 +01:00
|
|
|
<PageHeader
|
|
|
|
|
title="Ordrer"
|
|
|
|
|
subtitle={company?.name}
|
|
|
|
|
breadcrumbs={[{ title: 'Fakturering', path: '/fakturaer' }, { title: 'Ordrer' }]}
|
|
|
|
|
extra={
|
|
|
|
|
<Button type="primary" icon={<PlusOutlined />} onClick={handleCreateOrder}>
|
|
|
|
|
Ny ordre
|
|
|
|
|
</Button>
|
|
|
|
|
}
|
|
|
|
|
/>
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
|
|
|
|
|
{/* Error State */}
|
|
|
|
|
{error && (
|
|
|
|
|
<Alert
|
2026-02-05 21:35:26 +01:00
|
|
|
message="Fejl ved indlæsning af ordrer"
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
description={error.message}
|
|
|
|
|
type="error"
|
|
|
|
|
showIcon
|
|
|
|
|
style={{ marginBottom: spacing.lg }}
|
|
|
|
|
action={
|
|
|
|
|
<Button size="small" onClick={() => refetch()}>
|
2026-02-05 21:35:26 +01:00
|
|
|
Prøv igen
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
</Button>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Statistics */}
|
|
|
|
|
<Row gutter={[spacing.lg, spacing.lg]} style={{ marginBottom: spacing.lg }}>
|
|
|
|
|
<Col xs={12} sm={6}>
|
|
|
|
|
<Card size="small">
|
|
|
|
|
<Statistic title="Ordrer i alt" value={stats.total} />
|
|
|
|
|
</Card>
|
|
|
|
|
</Col>
|
|
|
|
|
<Col xs={12} sm={6}>
|
|
|
|
|
<Card size="small">
|
|
|
|
|
<Statistic
|
|
|
|
|
title="Kladder"
|
|
|
|
|
value={stats.drafts}
|
|
|
|
|
valueStyle={{ color: '#8c8c8c' }}
|
|
|
|
|
/>
|
|
|
|
|
</Card>
|
|
|
|
|
</Col>
|
|
|
|
|
<Col xs={12} sm={6}>
|
|
|
|
|
<Card size="small">
|
|
|
|
|
<Statistic
|
2026-02-05 21:35:26 +01:00
|
|
|
title="Bekræftede"
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
value={stats.confirmed}
|
|
|
|
|
valueStyle={{ color: accountingColors.credit }}
|
|
|
|
|
/>
|
|
|
|
|
</Card>
|
|
|
|
|
</Col>
|
|
|
|
|
<Col xs={12} sm={6}>
|
|
|
|
|
<Card size="small">
|
|
|
|
|
<Statistic
|
2026-02-05 21:35:26 +01:00
|
|
|
title="Samlet værdi"
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
value={stats.totalValue}
|
|
|
|
|
precision={2}
|
|
|
|
|
valueStyle={{ color: accountingColors.credit }}
|
|
|
|
|
formatter={(value) => formatCurrency(value as number)}
|
|
|
|
|
/>
|
|
|
|
|
</Card>
|
|
|
|
|
</Col>
|
|
|
|
|
</Row>
|
|
|
|
|
|
|
|
|
|
{/* Filters */}
|
|
|
|
|
<Card size="small" style={{ marginBottom: spacing.lg }}>
|
|
|
|
|
<Space wrap>
|
|
|
|
|
<Input
|
2026-02-05 21:35:26 +01:00
|
|
|
placeholder="Søg ordre..."
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
prefix={<SearchOutlined />}
|
|
|
|
|
value={searchText}
|
|
|
|
|
onChange={(e) => setSearchText(e.target.value)}
|
|
|
|
|
style={{ width: 250 }}
|
|
|
|
|
allowClear
|
|
|
|
|
/>
|
|
|
|
|
<Select
|
|
|
|
|
value={statusFilter}
|
|
|
|
|
onChange={setStatusFilter}
|
|
|
|
|
style={{ width: 180 }}
|
|
|
|
|
options={[
|
|
|
|
|
{ value: 'all', label: 'Alle status' },
|
|
|
|
|
{ value: 'draft', label: 'Kladde' },
|
2026-02-05 21:35:26 +01:00
|
|
|
{ value: 'confirmed', label: 'Bekræftet' },
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
{ value: 'partially_invoiced', label: 'Delvist faktureret' },
|
|
|
|
|
{ value: 'fully_invoiced', label: 'Fuldt faktureret' },
|
|
|
|
|
{ value: 'cancelled', label: 'Annulleret' },
|
|
|
|
|
]}
|
|
|
|
|
/>
|
|
|
|
|
</Space>
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
{/* Order Table */}
|
|
|
|
|
<Card size="small">
|
|
|
|
|
{loading ? (
|
2026-02-05 21:35:26 +01:00
|
|
|
<Spin tip="Indlæser ordrer..." style={{ display: 'block', textAlign: 'center', padding: spacing.xl }}>
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
<div style={{ minHeight: 200 }} />
|
|
|
|
|
</Spin>
|
|
|
|
|
) : filteredOrders.length > 0 ? (
|
|
|
|
|
<Table
|
|
|
|
|
dataSource={filteredOrders}
|
|
|
|
|
columns={columns}
|
|
|
|
|
rowKey="id"
|
|
|
|
|
size="small"
|
|
|
|
|
pagination={{ pageSize: 20, showSizeChanger: true }}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<EmptyState
|
|
|
|
|
variant="default"
|
|
|
|
|
title="Ingen ordrer"
|
2026-02-05 21:35:26 +01:00
|
|
|
description={searchText ? 'Ingen ordrer matcher din søgning' : 'Opret din første ordre'}
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
primaryAction={
|
|
|
|
|
!searchText
|
|
|
|
|
? {
|
|
|
|
|
label: 'Opret ordre',
|
|
|
|
|
onClick: handleCreateOrder,
|
|
|
|
|
icon: <PlusOutlined />,
|
|
|
|
|
}
|
|
|
|
|
: undefined
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
{/* Create Order Modal */}
|
|
|
|
|
<Modal
|
|
|
|
|
title="Opret ordre"
|
|
|
|
|
open={isCreateModalOpen}
|
|
|
|
|
onCancel={() => setIsCreateModalOpen(false)}
|
|
|
|
|
onOk={handleSubmitCreate}
|
|
|
|
|
okText="Opret"
|
|
|
|
|
cancelText="Annuller"
|
|
|
|
|
confirmLoading={createOrderMutation.isPending}
|
|
|
|
|
>
|
|
|
|
|
<Form form={createForm} layout="vertical">
|
|
|
|
|
<Form.Item
|
|
|
|
|
name="customerId"
|
|
|
|
|
label="Kunde"
|
2026-02-05 21:35:26 +01:00
|
|
|
rules={[{ required: true, message: 'Vælg kunde' }]}
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
>
|
|
|
|
|
<Select
|
|
|
|
|
showSearch
|
2026-02-05 21:35:26 +01:00
|
|
|
placeholder="Vælg kunde"
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
optionFilterProp="children"
|
|
|
|
|
filterOption={(input, option) =>
|
|
|
|
|
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
|
|
|
|
}
|
|
|
|
|
options={customers.map((c: Customer) => ({
|
|
|
|
|
value: c.id,
|
|
|
|
|
label: `${c.customerNumber} - ${c.name}`,
|
|
|
|
|
}))}
|
|
|
|
|
/>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
<Row gutter={16}>
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
<Form.Item name="orderDate" label="Ordredato">
|
|
|
|
|
<DatePicker style={{ width: '100%' }} format="DD-MM-YYYY" />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</Col>
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
<Form.Item name="expectedDeliveryDate" label="Forventet levering">
|
|
|
|
|
<DatePicker style={{ width: '100%' }} format="DD-MM-YYYY" />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</Col>
|
|
|
|
|
</Row>
|
|
|
|
|
<Form.Item name="reference" label="Reference">
|
|
|
|
|
<Input placeholder="Projektnavn, tilbudsnr., etc." />
|
|
|
|
|
</Form.Item>
|
2026-02-05 21:35:26 +01:00
|
|
|
<Form.Item name="notes" label="Bemærkninger">
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
<Input.TextArea rows={2} />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</Form>
|
|
|
|
|
</Modal>
|
|
|
|
|
|
|
|
|
|
{/* Order Detail Drawer */}
|
|
|
|
|
<Drawer
|
|
|
|
|
title={
|
|
|
|
|
selectedOrder && (
|
|
|
|
|
<Space>
|
|
|
|
|
<ShoppingCartOutlined />
|
|
|
|
|
<span>Ordre {selectedOrder.orderNumber}</span>
|
|
|
|
|
<Tag color={ORDER_STATUS_COLORS[selectedOrder.status]}>
|
|
|
|
|
{ORDER_STATUS_LABELS[selectedOrder.status]}
|
|
|
|
|
</Tag>
|
|
|
|
|
</Space>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
placement="right"
|
|
|
|
|
width={700}
|
|
|
|
|
open={isDrawerOpen}
|
|
|
|
|
onClose={() => {
|
|
|
|
|
setIsDrawerOpen(false);
|
|
|
|
|
setSelectedOrder(null);
|
|
|
|
|
}}
|
|
|
|
|
extra={
|
|
|
|
|
selectedOrder && (
|
|
|
|
|
<Space>
|
|
|
|
|
{selectedOrder.status === 'draft' && (
|
|
|
|
|
<>
|
|
|
|
|
<Button
|
|
|
|
|
icon={<PlusOutlined />}
|
|
|
|
|
onClick={handleOpenAddLineModal}
|
|
|
|
|
loading={addOrderLineMutation.isPending}
|
|
|
|
|
>
|
2026-02-05 21:35:26 +01:00
|
|
|
Tilføj linje
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
type="primary"
|
|
|
|
|
icon={<CheckOutlined />}
|
|
|
|
|
onClick={handleConfirmOrder}
|
|
|
|
|
loading={confirmOrderMutation.isPending}
|
|
|
|
|
disabled={selectedOrder.lines.length === 0}
|
|
|
|
|
>
|
2026-02-05 21:35:26 +01:00
|
|
|
Bekræft
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
</Button>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
{canShowConvertToInvoice(selectedOrder) && (
|
|
|
|
|
<Tooltip
|
2026-02-05 21:35:26 +01:00
|
|
|
title={selectedOrder.status === 'draft' ? 'Bekræft ordren først' : undefined}
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
>
|
|
|
|
|
<Button
|
|
|
|
|
type="primary"
|
|
|
|
|
icon={<FileDoneOutlined />}
|
|
|
|
|
onClick={handleOpenConvertModal}
|
|
|
|
|
disabled={isConvertToInvoiceDisabled(selectedOrder)}
|
|
|
|
|
>
|
|
|
|
|
Opret faktura
|
|
|
|
|
</Button>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
)}
|
|
|
|
|
{selectedOrder.status !== 'cancelled' && selectedOrder.status !== 'fully_invoiced' && (
|
|
|
|
|
<Button danger icon={<StopOutlined />} onClick={handleOpenCancelModal}>
|
|
|
|
|
Annuller
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
</Space>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
{selectedOrder && (
|
|
|
|
|
<div>
|
|
|
|
|
<Descriptions column={2} size="small" bordered style={{ marginBottom: spacing.lg }}>
|
|
|
|
|
<Descriptions.Item label="Kunde" span={2}>
|
|
|
|
|
{selectedOrder.customerName}
|
|
|
|
|
</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="Ordredato">
|
|
|
|
|
{selectedOrder.orderDate ? formatDate(selectedOrder.orderDate) : '-'}
|
|
|
|
|
</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="Forventet levering">
|
|
|
|
|
{selectedOrder.expectedDeliveryDate ? formatDate(selectedOrder.expectedDeliveryDate) : '-'}
|
|
|
|
|
</Descriptions.Item>
|
|
|
|
|
{selectedOrder.reference && (
|
|
|
|
|
<Descriptions.Item label="Reference" span={2}>
|
|
|
|
|
{selectedOrder.reference}
|
|
|
|
|
</Descriptions.Item>
|
|
|
|
|
)}
|
|
|
|
|
</Descriptions>
|
|
|
|
|
|
|
|
|
|
<Title level={5}>Linjer</Title>
|
|
|
|
|
{selectedOrder.lines.length > 0 ? (
|
|
|
|
|
<List
|
|
|
|
|
size="small"
|
|
|
|
|
bordered
|
|
|
|
|
dataSource={selectedOrder.lines}
|
|
|
|
|
renderItem={(line: OrderLine) => {
|
|
|
|
|
const linkedProduct = line.productId
|
|
|
|
|
? products.find((p: Product) => p.id === line.productId)
|
|
|
|
|
: null;
|
|
|
|
|
return (
|
|
|
|
|
<List.Item>
|
|
|
|
|
<List.Item.Meta
|
|
|
|
|
title={
|
|
|
|
|
<Space>
|
|
|
|
|
<span>{line.description}</span>
|
|
|
|
|
{line.productId && (
|
|
|
|
|
<Tag color="purple" icon={<BarcodeOutlined />}>
|
|
|
|
|
{linkedProduct?.productNumber || 'Produkt'}
|
|
|
|
|
</Tag>
|
|
|
|
|
)}
|
|
|
|
|
{line.isInvoiced && (
|
|
|
|
|
<Tag color="green" icon={<FileTextOutlined />}>
|
|
|
|
|
Faktureret
|
|
|
|
|
</Tag>
|
|
|
|
|
)}
|
|
|
|
|
</Space>
|
|
|
|
|
}
|
|
|
|
|
description={
|
|
|
|
|
<Space>
|
|
|
|
|
<span>
|
|
|
|
|
{line.quantity} {line.unit || 'stk'} x {formatCurrency(line.unitPrice)}
|
|
|
|
|
</span>
|
|
|
|
|
{line.discountPercent > 0 && (
|
|
|
|
|
<Tag color="orange">-{line.discountPercent}%</Tag>
|
|
|
|
|
)}
|
|
|
|
|
<Tag>{line.vatCode}</Tag>
|
|
|
|
|
{line.isInvoiced && line.invoicedAt && (
|
|
|
|
|
<Tag color="blue">
|
|
|
|
|
Faktureret: {dayjs(line.invoicedAt).format('DD/MM/YYYY')}
|
|
|
|
|
</Tag>
|
|
|
|
|
)}
|
|
|
|
|
</Space>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
<AmountText amount={line.amountTotal} style={{ fontWeight: 'bold' }} />
|
|
|
|
|
</List.Item>
|
|
|
|
|
);
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<Alert
|
|
|
|
|
message="Ingen linjer endnu"
|
2026-02-05 21:35:26 +01:00
|
|
|
description="Tilføj linjer for at kunne bekræfte ordren."
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
type="info"
|
|
|
|
|
showIcon
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<Divider />
|
|
|
|
|
|
|
|
|
|
<Row gutter={16}>
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
{selectedOrder.notes && (
|
|
|
|
|
<>
|
2026-02-05 21:35:26 +01:00
|
|
|
<Text type="secondary">Bemærkninger:</Text>
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
<p>{selectedOrder.notes}</p>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
{selectedOrder.cancelledReason && (
|
|
|
|
|
<>
|
2026-02-05 21:35:26 +01:00
|
|
|
<Text type="secondary">Annulleringsårsag:</Text>
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
<p style={{ color: 'red' }}>{selectedOrder.cancelledReason}</p>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</Col>
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
<div style={{ textAlign: 'right' }}>
|
|
|
|
|
<div style={{ marginBottom: 4 }}>
|
2026-02-05 21:35:26 +01:00
|
|
|
<Text type="secondary">Beløb ex. moms: </Text>
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
<Text>{formatCurrency(selectedOrder.amountExVat)}</Text>
|
|
|
|
|
</div>
|
|
|
|
|
<div style={{ marginBottom: 4 }}>
|
|
|
|
|
<Text type="secondary">Moms: </Text>
|
|
|
|
|
<Text>{formatCurrency(selectedOrder.amountVat)}</Text>
|
|
|
|
|
</div>
|
|
|
|
|
<div style={{ marginBottom: 4 }}>
|
|
|
|
|
<Text strong>Total: </Text>
|
|
|
|
|
<Text strong style={{ fontSize: 16 }}>
|
|
|
|
|
{formatCurrency(selectedOrder.amountTotal)}
|
|
|
|
|
</Text>
|
|
|
|
|
</div>
|
|
|
|
|
{(selectedOrder.uninvoicedAmount ?? selectedOrder.amountTotal) < selectedOrder.amountTotal && (
|
|
|
|
|
<div style={{ marginBottom: 4 }}>
|
|
|
|
|
<Text type="secondary">Faktureret: </Text>
|
|
|
|
|
<Text type="success">{formatCurrency(selectedOrder.amountTotal - (selectedOrder.uninvoicedAmount ?? 0))}</Text>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{(selectedOrder.uninvoicedAmount ?? 0) > 0 &&
|
|
|
|
|
selectedOrder.status !== 'cancelled' && (
|
|
|
|
|
<div>
|
|
|
|
|
<Text type="secondary">Resterende: </Text>
|
|
|
|
|
<Text type="warning" strong>
|
|
|
|
|
{formatCurrency(selectedOrder.uninvoicedAmount ?? 0)}
|
|
|
|
|
</Text>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</Col>
|
|
|
|
|
</Row>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</Drawer>
|
|
|
|
|
|
|
|
|
|
{/* Cancel Order Modal */}
|
|
|
|
|
<Modal
|
|
|
|
|
title="Annuller ordre"
|
|
|
|
|
open={isCancelModalOpen}
|
|
|
|
|
onCancel={() => setIsCancelModalOpen(false)}
|
|
|
|
|
onOk={handleSubmitCancel}
|
|
|
|
|
okText="Annuller ordre"
|
|
|
|
|
okButtonProps={{ danger: true }}
|
|
|
|
|
cancelText="Fortryd"
|
|
|
|
|
confirmLoading={cancelOrderMutation.isPending}
|
|
|
|
|
>
|
|
|
|
|
<Alert
|
|
|
|
|
message="Advarsel"
|
2026-02-05 21:35:26 +01:00
|
|
|
description="At annullere ordren kan ikke fortrydes. Eventuelle delfaktureringer forbliver uændrede."
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
type="warning"
|
|
|
|
|
showIcon
|
|
|
|
|
style={{ marginBottom: spacing.lg }}
|
|
|
|
|
/>
|
|
|
|
|
<Form form={cancelForm} layout="vertical">
|
|
|
|
|
<Form.Item
|
|
|
|
|
name="reason"
|
2026-02-05 21:35:26 +01:00
|
|
|
label="Årsag til annullering"
|
|
|
|
|
rules={[{ required: true, message: 'Angiv årsag' }]}
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
>
|
|
|
|
|
<Input.TextArea rows={3} placeholder="Beskriv hvorfor ordren annulleres" />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</Form>
|
|
|
|
|
</Modal>
|
|
|
|
|
|
|
|
|
|
{/* Add Line Modal */}
|
|
|
|
|
<Modal
|
2026-02-05 21:35:26 +01:00
|
|
|
title="Tilføj linje"
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
open={isAddLineModalOpen}
|
|
|
|
|
onCancel={() => {
|
|
|
|
|
setIsAddLineModalOpen(false);
|
|
|
|
|
setSelectedProductId(null);
|
|
|
|
|
}}
|
|
|
|
|
onOk={handleSubmitAddLine}
|
2026-02-05 21:35:26 +01:00
|
|
|
okText="Tilføj"
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
cancelText="Annuller"
|
|
|
|
|
confirmLoading={addOrderLineMutation.isPending}
|
|
|
|
|
width={550}
|
|
|
|
|
>
|
|
|
|
|
<Form form={addLineForm} layout="vertical">
|
|
|
|
|
<Form.Item label="Linjetype" style={{ marginBottom: spacing.md }}>
|
|
|
|
|
<Radio.Group
|
|
|
|
|
value={addLineMode}
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
setAddLineMode(e.target.value);
|
|
|
|
|
setSelectedProductId(null);
|
|
|
|
|
addLineForm.resetFields();
|
|
|
|
|
addLineForm.setFieldsValue({
|
|
|
|
|
quantity: 1,
|
|
|
|
|
vatCode: 'S25',
|
|
|
|
|
});
|
|
|
|
|
}}
|
|
|
|
|
optionType="button"
|
|
|
|
|
buttonStyle="solid"
|
|
|
|
|
>
|
2026-02-05 21:35:26 +01:00
|
|
|
<Radio.Button value="product">Vælg produkt</Radio.Button>
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
<Radio.Button value="freetext">Fritekst</Radio.Button>
|
|
|
|
|
</Radio.Group>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
|
|
|
{addLineMode === 'product' && (
|
|
|
|
|
<Form.Item
|
|
|
|
|
label="Produkt"
|
|
|
|
|
required
|
|
|
|
|
validateStatus={addLineMode === 'product' && !selectedProductId ? 'error' : undefined}
|
2026-02-05 21:35:26 +01:00
|
|
|
help={addLineMode === 'product' && !selectedProductId ? 'Vælg et produkt' : undefined}
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
>
|
|
|
|
|
<Select
|
|
|
|
|
showSearch
|
2026-02-05 21:35:26 +01:00
|
|
|
placeholder="Søg efter produkt..."
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
optionFilterProp="children"
|
|
|
|
|
value={selectedProductId}
|
|
|
|
|
onChange={handleProductSelect}
|
|
|
|
|
filterOption={(input, option) =>
|
|
|
|
|
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
|
|
|
|
}
|
|
|
|
|
options={products.map((p: Product) => ({
|
|
|
|
|
value: p.id,
|
|
|
|
|
label: `${p.productNumber || ''} ${p.name}`.trim(),
|
|
|
|
|
}))}
|
|
|
|
|
/>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
name="description"
|
|
|
|
|
label="Beskrivelse"
|
|
|
|
|
rules={[{ required: true, message: 'Angiv beskrivelse' }]}
|
|
|
|
|
>
|
|
|
|
|
<Input
|
|
|
|
|
placeholder="Vare eller ydelse"
|
|
|
|
|
disabled={addLineMode === 'product' && !!selectedProductId}
|
|
|
|
|
/>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
<Row gutter={16}>
|
|
|
|
|
<Col span={8}>
|
|
|
|
|
<Form.Item
|
|
|
|
|
name="quantity"
|
|
|
|
|
label="Antal"
|
|
|
|
|
rules={[{ required: true, message: 'Angiv antal' }]}
|
|
|
|
|
>
|
|
|
|
|
<Input type="number" min={0} step={1} />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</Col>
|
|
|
|
|
<Col span={8}>
|
|
|
|
|
<Form.Item name="unit" label="Enhed">
|
|
|
|
|
<Input
|
|
|
|
|
placeholder="stk"
|
|
|
|
|
disabled={addLineMode === 'product' && !!selectedProductId}
|
|
|
|
|
/>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</Col>
|
|
|
|
|
<Col span={8}>
|
|
|
|
|
<Form.Item
|
|
|
|
|
name="unitPrice"
|
|
|
|
|
label="Enhedspris"
|
|
|
|
|
rules={[{ required: true, message: 'Angiv pris' }]}
|
|
|
|
|
>
|
|
|
|
|
<Input
|
|
|
|
|
type="number"
|
|
|
|
|
min={0}
|
|
|
|
|
step={0.01}
|
|
|
|
|
disabled={addLineMode === 'product' && !!selectedProductId}
|
|
|
|
|
/>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</Col>
|
|
|
|
|
</Row>
|
|
|
|
|
<Row gutter={16}>
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
<Form.Item name="discountPercent" label="Rabat (%)">
|
|
|
|
|
<Input type="number" min={0} max={100} step={1} />
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</Col>
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
<Form.Item
|
|
|
|
|
name="vatCode"
|
|
|
|
|
label="Momskode"
|
2026-02-05 21:35:26 +01:00
|
|
|
rules={[{ required: true, message: 'Vælg momskode' }]}
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
>
|
|
|
|
|
<Select
|
|
|
|
|
disabled={addLineMode === 'product' && !!selectedProductId}
|
|
|
|
|
options={[
|
|
|
|
|
{ value: 'S25', label: 'S25 - Salgsmoms 25%' },
|
|
|
|
|
{ value: 'S0', label: 'S0 - Momsfrit salg' },
|
|
|
|
|
]}
|
|
|
|
|
/>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</Col>
|
|
|
|
|
</Row>
|
|
|
|
|
</Form>
|
|
|
|
|
</Modal>
|
|
|
|
|
|
|
|
|
|
{/* Convert to Invoice Modal */}
|
|
|
|
|
<Modal
|
|
|
|
|
title="Opret faktura fra ordre"
|
|
|
|
|
open={isConvertModalOpen}
|
|
|
|
|
onCancel={() => {
|
|
|
|
|
setIsConvertModalOpen(false);
|
|
|
|
|
setSelectedLinesToInvoice([]);
|
|
|
|
|
}}
|
|
|
|
|
onOk={handleSubmitConvert}
|
|
|
|
|
okText="Opret faktura"
|
|
|
|
|
cancelText="Annuller"
|
|
|
|
|
confirmLoading={convertToInvoiceMutation.isPending}
|
|
|
|
|
width={600}
|
|
|
|
|
>
|
|
|
|
|
<Alert
|
2026-02-05 21:35:26 +01:00
|
|
|
message="Vælg linjer til fakturering"
|
|
|
|
|
description="Vælg hvilke ordrelinjer der skal inkluderes i fakturaen. Du kan fakturere delvist og oprette flere fakturaer senere."
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
type="info"
|
|
|
|
|
showIcon
|
|
|
|
|
style={{ marginBottom: spacing.lg }}
|
|
|
|
|
/>
|
|
|
|
|
{selectedOrder && (
|
|
|
|
|
<List
|
|
|
|
|
size="small"
|
|
|
|
|
bordered
|
|
|
|
|
dataSource={selectedOrder.lines.filter((line) => !line.isInvoiced)}
|
|
|
|
|
renderItem={(line: OrderLine) => (
|
|
|
|
|
<List.Item>
|
|
|
|
|
<Checkbox
|
|
|
|
|
checked={selectedLinesToInvoice.includes(line.lineNumber)}
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
if (e.target.checked) {
|
|
|
|
|
setSelectedLinesToInvoice([...selectedLinesToInvoice, line.lineNumber]);
|
|
|
|
|
} else {
|
|
|
|
|
setSelectedLinesToInvoice(
|
|
|
|
|
selectedLinesToInvoice.filter((n) => n !== line.lineNumber)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<Space direction="vertical" size={0}>
|
|
|
|
|
<Text>{line.description}</Text>
|
|
|
|
|
<Text type="secondary">
|
|
|
|
|
{line.quantity} {line.unit || 'stk'} x{' '}
|
|
|
|
|
{formatCurrency(line.unitPrice)} ={' '}
|
|
|
|
|
{formatCurrency(line.amountTotal)}
|
|
|
|
|
</Text>
|
|
|
|
|
</Space>
|
|
|
|
|
</Checkbox>
|
|
|
|
|
</List.Item>
|
|
|
|
|
)}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</Modal>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|