Split dashboard InboxPanel card renderer
This commit is contained in:
@@ -77,101 +77,37 @@ function inboxTargetHref(item: InboxItem): string | null {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function InboxPanel({
|
interface InboxCardProps {
|
||||||
overview,
|
inboxActionKey: InboxActionKey | null;
|
||||||
tasks,
|
item: InboxItem;
|
||||||
|
locale: Locale;
|
||||||
|
onInboxAction: (item: InboxItem, action: DashboardInboxAction) => void;
|
||||||
|
onTaskAction: (task: DashboardTask, action: DashboardTaskAction) => void;
|
||||||
|
taskActionKey: string | null;
|
||||||
|
tasks: DashboardTask[];
|
||||||
|
t: Messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
function InboxCard({
|
||||||
|
inboxActionKey,
|
||||||
|
item,
|
||||||
locale,
|
locale,
|
||||||
onInboxAction,
|
onInboxAction,
|
||||||
onTaskAction,
|
onTaskAction,
|
||||||
inboxActionKey,
|
|
||||||
taskActionKey,
|
taskActionKey,
|
||||||
|
tasks,
|
||||||
t,
|
t,
|
||||||
}: InboxPanelProps) {
|
}: InboxCardProps) {
|
||||||
const [filter, setFilter] = useState<InboxFilter>('all');
|
|
||||||
const items = overview.inbox ?? [];
|
|
||||||
const counts = useMemo(() => {
|
|
||||||
const next: Record<InboxFilter, number> = {
|
|
||||||
all: items.length,
|
|
||||||
'pending-room': 0,
|
|
||||||
'reviewer-request': 0,
|
|
||||||
approval: 0,
|
|
||||||
'arbiter-request': 0,
|
|
||||||
'ci-failure': 0,
|
|
||||||
mention: 0,
|
|
||||||
};
|
|
||||||
for (const item of items) next[item.kind] += 1;
|
|
||||||
return next;
|
|
||||||
}, [items]);
|
|
||||||
const filteredItems =
|
|
||||||
filter === 'all' ? items : items.filter((item) => item.kind === filter);
|
|
||||||
const severityCounts = items.reduce(
|
|
||||||
(acc, item) => {
|
|
||||||
acc[item.severity] += 1;
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{ error: 0, warn: 0, info: 0 },
|
|
||||||
);
|
|
||||||
|
|
||||||
if (items.length === 0) {
|
|
||||||
return <EmptyState>{t.inbox.empty}</EmptyState>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="inbox-board">
|
|
||||||
<section className="inbox-summary" aria-label={t.inbox.summary}>
|
|
||||||
<div>
|
|
||||||
<span>{t.inbox.total}</span>
|
|
||||||
<strong>{items.length}</strong>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span>{t.inbox.severity.error}</span>
|
|
||||||
<strong>{severityCounts.error}</strong>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span>{t.inbox.severity.warn}</span>
|
|
||||||
<strong>{severityCounts.warn}</strong>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span>{t.inbox.severity.info}</span>
|
|
||||||
<strong>{severityCounts.info}</strong>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div className="inbox-filters" aria-label={t.inbox.filters}>
|
|
||||||
{INBOX_FILTERS.map((item) => {
|
|
||||||
if (item !== 'all' && counts[item] === 0) return null;
|
|
||||||
const label = item === 'all' ? t.inbox.all : t.inbox.kinds[item];
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
aria-pressed={filter === item}
|
|
||||||
className={filter === item ? 'is-active' : undefined}
|
|
||||||
key={item}
|
|
||||||
onClick={() => setFilter(item)}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
<span>{counts[item]}</span>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="inbox-list" aria-label={t.inbox.cardsAria}>
|
|
||||||
{filteredItems.map((item) => {
|
|
||||||
const href = inboxTargetHref(item);
|
const href = inboxTargetHref(item);
|
||||||
const linkedTask =
|
const linkedTask =
|
||||||
item.source === 'scheduled-task' && item.taskId
|
item.source === 'scheduled-task' && item.taskId
|
||||||
? tasks.find((task) => task.id === item.taskId)
|
? tasks.find((task) => task.id === item.taskId)
|
||||||
: undefined;
|
: undefined;
|
||||||
const linkedTaskActions = linkedTask
|
const linkedTaskActions = linkedTask ? taskActionsFor(linkedTask) : [];
|
||||||
? taskActionsFor(linkedTask)
|
|
||||||
: [];
|
|
||||||
const inboxActions = inboxActionsFor(item);
|
const inboxActions = inboxActionsFor(item);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article
|
<article className={`inbox-card inbox-${item.severity}`}>
|
||||||
className={`inbox-card inbox-${item.severity}`}
|
|
||||||
key={item.id}
|
|
||||||
>
|
|
||||||
<div className="inbox-card-head">
|
<div className="inbox-card-head">
|
||||||
<div>
|
<div>
|
||||||
<span className="eyebrow">{t.inbox.kinds[item.kind]}</span>
|
<span className="eyebrow">{t.inbox.kinds[item.kind]}</span>
|
||||||
@@ -256,8 +192,102 @@ export function InboxPanel({
|
|||||||
) : null}
|
) : null}
|
||||||
</article>
|
</article>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function InboxPanel({
|
||||||
|
overview,
|
||||||
|
tasks,
|
||||||
|
locale,
|
||||||
|
onInboxAction,
|
||||||
|
onTaskAction,
|
||||||
|
inboxActionKey,
|
||||||
|
taskActionKey,
|
||||||
|
t,
|
||||||
|
}: InboxPanelProps) {
|
||||||
|
const [filter, setFilter] = useState<InboxFilter>('all');
|
||||||
|
const items = overview.inbox ?? [];
|
||||||
|
const counts = useMemo(() => {
|
||||||
|
const next: Record<InboxFilter, number> = {
|
||||||
|
all: items.length,
|
||||||
|
'pending-room': 0,
|
||||||
|
'reviewer-request': 0,
|
||||||
|
approval: 0,
|
||||||
|
'arbiter-request': 0,
|
||||||
|
'ci-failure': 0,
|
||||||
|
mention: 0,
|
||||||
|
};
|
||||||
|
for (const item of items) next[item.kind] += 1;
|
||||||
|
return next;
|
||||||
|
}, [items]);
|
||||||
|
const filteredItems =
|
||||||
|
filter === 'all' ? items : items.filter((item) => item.kind === filter);
|
||||||
|
const severityCounts = items.reduce(
|
||||||
|
(acc, item) => {
|
||||||
|
acc[item.severity] += 1;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ error: 0, warn: 0, info: 0 },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (items.length === 0) {
|
||||||
|
return <EmptyState>{t.inbox.empty}</EmptyState>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="inbox-board">
|
||||||
|
<section className="inbox-summary" aria-label={t.inbox.summary}>
|
||||||
|
<div>
|
||||||
|
<span>{t.inbox.total}</span>
|
||||||
|
<strong>{items.length}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{t.inbox.severity.error}</span>
|
||||||
|
<strong>{severityCounts.error}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{t.inbox.severity.warn}</span>
|
||||||
|
<strong>{severityCounts.warn}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{t.inbox.severity.info}</span>
|
||||||
|
<strong>{severityCounts.info}</strong>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div className="inbox-filters" aria-label={t.inbox.filters}>
|
||||||
|
{INBOX_FILTERS.map((item) => {
|
||||||
|
if (item !== 'all' && counts[item] === 0) return null;
|
||||||
|
const label = item === 'all' ? t.inbox.all : t.inbox.kinds[item];
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
aria-pressed={filter === item}
|
||||||
|
className={filter === item ? 'is-active' : undefined}
|
||||||
|
key={item}
|
||||||
|
onClick={() => setFilter(item)}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
<span>{counts[item]}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="inbox-list" aria-label={t.inbox.cardsAria}>
|
||||||
|
{filteredItems.map((item) => (
|
||||||
|
<InboxCard
|
||||||
|
inboxActionKey={inboxActionKey}
|
||||||
|
item={item}
|
||||||
|
key={item.id}
|
||||||
|
locale={locale}
|
||||||
|
onInboxAction={onInboxAction}
|
||||||
|
onTaskAction={onTaskAction}
|
||||||
|
taskActionKey={taskActionKey}
|
||||||
|
tasks={tasks}
|
||||||
|
t={t}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user