134 lines
4.2 KiB
TypeScript
134 lines
4.2 KiB
TypeScript
import React from 'react';
|
||
import Link from 'next/link';
|
||
import moment from 'moment';
|
||
import { DragSourceMonitor, useDrag } from 'react-dnd';
|
||
import ListActionsPopover from '../ListActionsPopover';
|
||
|
||
type Props = {
|
||
item: any;
|
||
column: { id: string; label: string };
|
||
entityName: string;
|
||
showFieldName: string;
|
||
setItemIdToDelete: (id: string) => void;
|
||
};
|
||
|
||
const humanize = (value?: string | null) => {
|
||
if (!value) return '';
|
||
return value
|
||
.replace(/_/g, ' ')
|
||
.replace(/\b\w/g, (char) => char.toUpperCase());
|
||
};
|
||
|
||
const formatDateRange = (start?: string, end?: string) => {
|
||
if (!start && !end) return '';
|
||
if (start && end) {
|
||
return `${moment(start).format('MMM D')} – ${moment(end).format('MMM D')}`;
|
||
}
|
||
|
||
return moment(start || end).format('MMM D');
|
||
};
|
||
|
||
const KanbanCard = ({
|
||
item,
|
||
entityName,
|
||
showFieldName,
|
||
setItemIdToDelete,
|
||
column,
|
||
}: Props) => {
|
||
const [{ isDragging }, drag] = useDrag(
|
||
() => ({
|
||
type: 'box',
|
||
item: { item, column },
|
||
collect: (monitor: DragSourceMonitor) => ({
|
||
isDragging: monitor.isDragging(),
|
||
}),
|
||
}),
|
||
[item, column],
|
||
);
|
||
|
||
const title = item?.[showFieldName] ?? 'Untitled';
|
||
const dateRange = formatDateRange(item?.check_in_at || item?.start_at, item?.check_out_at || item?.end_at);
|
||
const locationLabel =
|
||
item?.preferred_property?.name ||
|
||
item?.property?.name ||
|
||
item?.unit?.unit_number ||
|
||
item?.unit_type?.name ||
|
||
'';
|
||
const supportingLabel =
|
||
item?.requested_by?.email ||
|
||
item?.requested_by?.firstName ||
|
||
item?.organization?.name ||
|
||
item?.organizations?.name ||
|
||
'';
|
||
const updatedAt = item?.updatedAt || item?.createdAt;
|
||
const stats = [
|
||
item?.guest_count
|
||
? { label: 'Guests', value: String(item.guest_count) }
|
||
: null,
|
||
item?.priority
|
||
? { label: 'Priority', value: humanize(item.priority) }
|
||
: null,
|
||
dateRange
|
||
? { label: entityName === 'booking_requests' ? 'Stay' : 'Dates', value: dateRange }
|
||
: null,
|
||
locationLabel ? { label: 'Location', value: locationLabel } : null,
|
||
].filter(Boolean) as Array<{ label: string; value: string }>;
|
||
|
||
return (
|
||
<div
|
||
ref={drag}
|
||
className={`rounded-2xl border border-gray-200/80 bg-white p-4 shadow-sm transition ${
|
||
isDragging ? 'cursor-grabbing opacity-70' : 'cursor-grab hover:-translate-y-0.5'
|
||
} dark:border-dark-700 dark:bg-dark-900`}
|
||
>
|
||
<div className='flex items-start justify-between gap-3'>
|
||
<div className='min-w-0'>
|
||
<Link
|
||
href={`/${entityName}/${entityName}-view/?id=${item.id}`}
|
||
className='block truncate text-sm font-semibold text-gray-900 dark:text-white'
|
||
>
|
||
{title}
|
||
</Link>
|
||
{supportingLabel && (
|
||
<p className='mt-1 truncate text-xs text-gray-500 dark:text-gray-400'>
|
||
{supportingLabel}
|
||
</p>
|
||
)}
|
||
</div>
|
||
<ListActionsPopover
|
||
itemId={item.id}
|
||
pathEdit={`/${entityName}/${entityName}-edit/?id=${item.id}`}
|
||
pathView={`/${entityName}/${entityName}-view/?id=${item.id}`}
|
||
onDelete={(id) => setItemIdToDelete(id)}
|
||
hasUpdatePermission={true}
|
||
className='h-5 w-5 text-gray-400 dark:text-gray-500'
|
||
iconClassName='w-5 text-gray-400 dark:text-gray-500'
|
||
/>
|
||
</div>
|
||
|
||
{stats.length > 0 && (
|
||
<div className='mt-3 grid gap-2'>
|
||
{stats.slice(0, 3).map((stat) => (
|
||
<div
|
||
key={`${item.id}-${stat.label}`}
|
||
className='flex items-center justify-between rounded-xl bg-gray-50 px-3 py-2 text-xs dark:bg-dark-800/80'
|
||
>
|
||
<span className='text-gray-500 dark:text-gray-400'>{stat.label}</span>
|
||
<span className='ml-3 truncate font-medium text-gray-700 dark:text-gray-200'>
|
||
{stat.value}
|
||
</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
<div className='mt-4 flex items-center justify-between text-[11px] uppercase tracking-wide text-gray-400 dark:text-gray-500'>
|
||
<span>{humanize(column.label)}</span>
|
||
<span>{updatedAt ? moment(updatedAt).format('MMM D') : '—'}</span>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default KanbanCard;
|