2026-04-03 06:29:03 +00:00

134 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;