57 lines
2.2 KiB
TypeScript
57 lines
2.2 KiB
TypeScript
import { ExternalIcon } from '../../icons';
|
|
import { parseDate, timeAgo, initials } from '../../../utils/format.utils';
|
|
import type { Ticket } from '../../../types/ticket.types';
|
|
import styles from './BoardCard.module.scss';
|
|
|
|
function daysSince(v: string | null): number | null {
|
|
const d = parseDate(v);
|
|
if (!d) return null;
|
|
return Math.floor((Date.now() - d.getTime()) / 86_400_000);
|
|
}
|
|
|
|
interface BoardCardProps {
|
|
ticket: Ticket;
|
|
color: string;
|
|
brandColor?: string;
|
|
onSelect: (t: Ticket) => void;
|
|
}
|
|
|
|
export default function BoardCard({ ticket, color, brandColor, onSelect }: BoardCardProps) {
|
|
const lifetime = daysSince(ticket.openedAt);
|
|
const inState = daysSince(ticket.stateChangedAt);
|
|
const bm = [ticket.brand, ticket.market].filter(Boolean).join(' · ');
|
|
|
|
return (
|
|
<div className={styles.card} style={{ '--col': color } as React.CSSProperties} onClick={() => onSelect(ticket)}>
|
|
<div className={styles.top}>
|
|
{ticket.link ? (
|
|
<a href={ticket.link} target="_blank" rel="noopener noreferrer" className={styles.number}
|
|
onClick={e => e.stopPropagation()} title="Open in ServiceNow">
|
|
{ticket.number}<ExternalIcon size={11} />
|
|
</a>
|
|
) : (
|
|
<span className={styles.number}>{ticket.number}</span>
|
|
)}
|
|
{ticket.jira?.status && <span className={styles.jira}>{ticket.jira.status}</span>}
|
|
</div>
|
|
|
|
<div className={styles.title} title={ticket.shortDesc}>{ticket.shortDesc || '—'}</div>
|
|
|
|
{bm && <span className={styles.bm} style={brandColor ? { background: `${brandColor}1a`, color: brandColor, borderColor: `${brandColor}55` } : undefined}>{bm}</span>}
|
|
|
|
<div className={styles.foot}>
|
|
<span className={styles.assignee}>
|
|
<span className={styles.avatar}>{initials(ticket.assignedTo)}</span>
|
|
<span className={styles.aName}>{ticket.assignedTo ?? 'Unassigned'}</span>
|
|
</span>
|
|
{inState != null && <span className={styles.pill} title="days in current state">{inState}d</span>}
|
|
</div>
|
|
|
|
<div className={styles.meta}>
|
|
{lifetime != null && <span title="lifetime">⏱ {lifetime}d</span>}
|
|
<span title="last activity">✎ {timeAgo(ticket.lastActivityAt)}</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|