feat: add StatusBadge component with configurable status display

This commit is contained in:
Firman Ramdhani
2026-07-16 11:36:17 +07:00
parent d004836615
commit 853769b1f8
2 changed files with 3 additions and 3 deletions
@@ -0,0 +1,231 @@
import React from 'react';
import { Badge, BadgeProps } from '@mantine/core';
import {
CheckCircle2,
XCircle,
Clock,
Edit2,
Loader2,
Package,
AlertCircle,
RotateCcw,
Check,
CreditCard,
Send,
Users,
} from 'lucide-react';
export enum STATUS_DATA {
DRAFT = 'draft',
ACTIVE = 'active',
ACTIVATED = 'activated',
INACTIVE = 'inactive',
DEACTIVATED = 'deactivated',
REQUEST = 'request',
REQUESTING = 'requesting',
REQUESTED = 'requested',
APPROVAL = 'approval',
APPROVED = 'approved',
OPEN = 'open',
OPENED = 'opened',
PENDING = 'pending',
WAIT = 'wait',
WAITING = 'waiting',
WAITING_LIST = 'waiting-list',
PROCESS = 'process',
PROCESSING = 'processing',
IN_PROCESS = 'in-process',
PROCESSED = 'processed',
CLOSE = 'close',
CLOSED = 'closed',
CANCEL = 'cancel',
CANCELLED = 'cancelled',
COMPLETE = 'complete',
COMPLETED = 'completed',
TODO = 'todo',
DONE = 'done',
PARTIAL_DONE = 'partial-done',
ON_HOLD = 'on-hold',
SHIPMENT = 'shipment',
SHIPPING = 'shipping',
SHIPPED = 'shipped',
EXPIRED = 'expired',
REFUND = 'refund',
REFUNDED = 'refunded',
PARTIAL_REFUND = 'partial-refund',
PARTIALLY_REFUNDED = 'partially-refunded',
REFUND_PROCESSING = 'refund-processing',
CANCEL_REFUNDED = 'cancel-refunded',
REFUND_PROCESS = 'refund-processing',
TRANSFERRED = 'transferred',
SETTLEMENT = 'settlement',
SETTLED = 'settled',
SETTLED_FREE = 'settled-free',
REJECTION = 'rejection',
REJECTED = 'rejected',
PAYMENT = 'payment',
PAID = 'paid',
ISSUE = 'issue',
PROBLEM = 'problem',
RESOLVED = 'resolved',
CHECKOUT = 'checkout',
INVOICE = 'invoice',
INVOICED = 'invoiced',
JOINED = 'joined',
INVITED = 'invited',
PRESENT = 'present',
}
export type StatusBadgeConfigCallback = (status: string) => Partial<BadgeProps>;
export interface StatusBadgeProps extends Omit<BadgeProps, 'color' | 'leftSection'> {
/** The status string or STATUS_DATA enum value */
status: STATUS_DATA | string;
/** Optional custom label, overrides default status string */
label?: string;
/** Callback to provide custom badge props (color, icon) for a specific status */
getCustomConfig?: StatusBadgeConfigCallback;
/** Overrides default and custom color */
color?: string;
/** Overrides default and custom leftSection icon */
leftSection?: React.ReactNode;
}
const ICON_SIZE = 12;
const STROKE_WIDTH = 2.5;
const getIcon = (IconCmp: any) => <IconCmp size={ICON_SIZE} strokeWidth={STROKE_WIDTH} />;
export const DEFAULT_STATUS_MAP: Record<string, BadgeProps> = {
// #B6B6B6
[STATUS_DATA.DRAFT]: { color: '#B6B6B6', leftSection: getIcon(Edit2) },
[STATUS_DATA.CHECKOUT]: { color: '#B6B6B6', leftSection: getIcon(CreditCard) },
// #66BB6A
[STATUS_DATA.ACTIVE]: { color: '#66BB6A', leftSection: getIcon(CheckCircle2) },
[STATUS_DATA.ACTIVATED]: { color: '#66BB6A', leftSection: getIcon(CheckCircle2) },
[STATUS_DATA.APPROVAL]: { color: '#66BB6A', leftSection: getIcon(CheckCircle2) },
[STATUS_DATA.APPROVED]: { color: '#66BB6A', leftSection: getIcon(CheckCircle2) },
[STATUS_DATA.COMPLETE]: { color: '#66BB6A', leftSection: getIcon(CheckCircle2) },
[STATUS_DATA.COMPLETED]: { color: '#66BB6A', leftSection: getIcon(CheckCircle2) },
[STATUS_DATA.DONE]: { color: '#66BB6A', leftSection: getIcon(CheckCircle2) },
[STATUS_DATA.PARTIAL_DONE]: { color: '#66BB6A', leftSection: getIcon(Clock) },
[STATUS_DATA.JOINED]: { color: '#66BB6A', leftSection: getIcon(Users) },
[STATUS_DATA.RESOLVED]: { color: '#66BB6A', leftSection: getIcon(CheckCircle2) },
[STATUS_DATA.PAID]: { color: '#66BB6A', leftSection: getIcon(CreditCard) },
// #EF5350
[STATUS_DATA.INACTIVE]: { color: '#EF5350', leftSection: getIcon(XCircle) },
[STATUS_DATA.DEACTIVATED]: { color: '#EF5350', leftSection: getIcon(XCircle) },
[STATUS_DATA.CLOSE]: { color: '#EF5350', leftSection: getIcon(XCircle) },
[STATUS_DATA.CLOSED]: { color: '#EF5350', leftSection: getIcon(XCircle) },
[STATUS_DATA.CANCELLED]: { color: '#EF5350', leftSection: getIcon(XCircle) },
[STATUS_DATA.CANCEL_REFUNDED]: { color: '#EF5350', leftSection: getIcon(XCircle) },
[STATUS_DATA.REJECTION]: { color: '#EF5350', leftSection: getIcon(XCircle) },
[STATUS_DATA.REJECTED]: { color: '#EF5350', leftSection: getIcon(XCircle) },
[STATUS_DATA.ISSUE]: { color: '#EF5350', leftSection: getIcon(AlertCircle) },
[STATUS_DATA.PROBLEM]: { color: '#EF5350', leftSection: getIcon(AlertCircle) },
// #42A5F5
[STATUS_DATA.REQUEST]: { color: '#42A5F5', leftSection: getIcon(Send) },
[STATUS_DATA.REQUESTING]: { color: '#42A5F5', leftSection: getIcon(Send) },
[STATUS_DATA.REQUESTED]: { color: '#42A5F5', leftSection: getIcon(Send) },
[STATUS_DATA.OPEN]: { color: '#42A5F5', leftSection: getIcon(CheckCircle2) },
[STATUS_DATA.OPENED]: { color: '#42A5F5', leftSection: getIcon(CheckCircle2) },
[STATUS_DATA.PROCESS]: { color: '#42A5F5', leftSection: getIcon(Loader2) },
[STATUS_DATA.PROCESSING]: { color: '#42A5F5', leftSection: getIcon(Loader2) },
[STATUS_DATA.IN_PROCESS]: { color: '#42A5F5', leftSection: getIcon(Loader2) },
[STATUS_DATA.PROCESSED]: { color: '#42A5F5', leftSection: getIcon(Check) },
[STATUS_DATA.SHIPMENT]: { color: '#42A5F5', leftSection: getIcon(Package) },
[STATUS_DATA.SHIPPING]: { color: '#42A5F5', leftSection: getIcon(Package) },
[STATUS_DATA.SHIPPED]: { color: '#42A5F5', leftSection: getIcon(Check) },
[STATUS_DATA.PAYMENT]: { color: '#42A5F5', leftSection: getIcon(CreditCard) },
[STATUS_DATA.INVOICE]: { color: '#42A5F5', leftSection: getIcon(CreditCard) },
[STATUS_DATA.INVOICED]: { color: '#42A5F5', leftSection: getIcon(CreditCard) },
// #DFAB45
[STATUS_DATA.PENDING]: { color: '#DFAB45', leftSection: getIcon(Clock) },
// #6C5DD0
[STATUS_DATA.WAIT]: { color: '#6C5DD0', leftSection: getIcon(Clock) },
[STATUS_DATA.WAITING]: { color: '#6C5DD0', leftSection: getIcon(Clock) },
[STATUS_DATA.WAITING_LIST]: { color: '#6C5DD0', leftSection: getIcon(Clock) },
// #D26DA9
[STATUS_DATA.CANCEL]: { color: '#D26DA9', leftSection: getIcon(XCircle) },
[STATUS_DATA.TRANSFERRED]: { color: '#D26DA9', leftSection: getIcon(Send) },
// #9575CD
[STATUS_DATA.TODO]: { color: '#9575CD', leftSection: getIcon(Clock) },
// #FFB74D
[STATUS_DATA.ON_HOLD]: { color: '#FFB74D', leftSection: getIcon(Clock) },
// #787878
[STATUS_DATA.EXPIRED]: { color: '#787878', leftSection: getIcon(AlertCircle) },
// #FE725E
[STATUS_DATA.REFUND]: { color: '#FE725E', leftSection: getIcon(RotateCcw) },
[STATUS_DATA.REFUNDED]: { color: '#FE725E', leftSection: getIcon(RotateCcw) },
[STATUS_DATA.PARTIAL_REFUND]: { color: '#FE725E', leftSection: getIcon(RotateCcw) },
[STATUS_DATA.PARTIALLY_REFUNDED]: { color: '#FE725E', leftSection: getIcon(RotateCcw) },
[STATUS_DATA.INVITED]: { color: '#FE725E', leftSection: getIcon(Users) },
// #D7A29A
[STATUS_DATA.REFUND_PROCESSING]: { color: '#D7A29A', leftSection: getIcon(Loader2) },
// #5DADD0
[STATUS_DATA.SETTLEMENT]: { color: '#5DADD0', leftSection: getIcon(CheckCircle2) },
[STATUS_DATA.SETTLED]: { color: '#5DADD0', leftSection: getIcon(CheckCircle2) },
[STATUS_DATA.SETTLED_FREE]: { color: '#5DADD0', leftSection: getIcon(CheckCircle2) },
// #E2B43E
[STATUS_DATA.PRESENT]: { color: '#E2B43E', leftSection: getIcon(Users) },
};
export function StatusBadge({ status, label, getCustomConfig, color, leftSection, ...rest }: StatusBadgeProps) {
if (!status) return null;
const normalizedStatus = status?.toLowerCase() || '';
const defaultConfig = DEFAULT_STATUS_MAP[normalizedStatus] || { color: 'gray' };
const customConfig = getCustomConfig ? getCustomConfig(normalizedStatus) : {};
return (
<Badge
size="sm"
radius="sm"
fw={600}
style={{ textTransform: 'capitalize' }}
variant={customConfig.variant ? customConfig.variant : 'light'}
color={color || customConfig.color || defaultConfig.color}
leftSection={leftSection || customConfig.leftSection || defaultConfig.leftSection}
{...rest}
>
{label || (status ? status.replace(/-/g, ' ') : 'Unknown')}
</Badge>
);
}