ComplaintList
The complaints inbox sidebar: a tab bar over the rows, a loader while fetching and two empty states, all in one column. Rows render through ComplaintListItem; the parent owns filtering — the component renders rows exactly as handed over.
vue
<script setup lang="ts">
import { ref } from "vue"
import RyderComplaintList from "@opeolluwa/ryder/components/complaints/ComplaintList.vue"
import type { ComplaintsInboxTab } from "@opeolluwa/ryder/types"
const activeTab = ref<ComplaintsInboxTab>("all")
const selectedId = ref<string | null>(null)
</script>
<template>
<RyderComplaintList
:rows="rows"
:loading="loading"
:inbox-empty="inboxEmpty"
:active-tab="activeTab"
:tab-count="tabCount"
:reply-counts="replyCounts"
:selected-id="selectedId"
@update:active-tab="activeTab = $event"
@select="selectedId = $event"
/>
</template>Examples
vue
<script setup lang="ts">
import { computed, ref } from "vue";
import ComplaintList from "../../../src/components/complaints/ComplaintList.vue";
import { complaintMatchesTab } from "../../../src/utils/complaintStatus";
import { replyCounts, rows } from "./fixtures";
import type { ComplaintsInboxTab } from "../../../src/types/complaints";
const activeTab = ref<ComplaintsInboxTab>("all");
const selectedId = ref<string | null>(rows[0].complaint.identifier);
// The component renders `rows` verbatim — filtering to the active tab is the
// parent's job, here with the shared tab matcher.
const visibleRows = computed(() =>
rows.filter((item) =>
complaintMatchesTab(activeTab.value, item.complaint.status),
),
);
const tabCount = computed(() => ({
all: rows.length,
open: rows.filter((item) =>
complaintMatchesTab("open", item.complaint.status),
).length,
resolved: rows.filter((item) =>
complaintMatchesTab("resolved", item.complaint.status),
).length,
}));
</script>
<template>
<div>
<div class="h-[28rem]">
<ComplaintList
:rows="visibleRows"
:loading="false"
:inbox-empty="false"
:active-tab="activeTab"
:tab-count="tabCount"
:reply-counts="replyCounts"
:selected-id="selectedId"
@update:active-tab="activeTab = $event"
@select="selectedId = $event"
/>
</div>
<p class="mt-3 text-xs text-gray-500 dark:text-white/40">
Active tab: {{ activeTab }} — selected: {{ selectedId ?? "none" }}
</p>
</div>
</template>Props
| Prop | Type | Default | Description |
|---|---|---|---|
rows | ComplaintRow[] | — | Rows to render, already filtered by the parent. |
loading | boolean | — | Shows PageLoader and disables every tab. |
inboxEmpty | boolean | — | The account has no complaints at all: hides the tab bar and shows the "No complaints yet" empty state. |
activeTab | "all" | "open" | "resolved" | — | Which tab is on; controlled through update:activeTab. |
tabCount | Record<"all" | "open" | "resolved", number> | — | Per-tab counts. Declared required but never rendered — see Notes. |
replyCounts | Record<string, number> | — | Reply badge per complaint identifier; a missing key renders 0 (no badge). |
selectedId | string | null | — | Identifier of the highlighted row. |
tabs | ComplaintsInboxTab[] | [...COMPLAINTS_INBOX_TABS] | Which tabs exist — by default all, open, resolved. Pass [] to drop the tab bar (e.g. a customer's short history). |
showCustomer | boolean | true | Forwarded to each row: headline by customer vs by subject. |
Emits
| Event | Payload | Description |
|---|---|---|
update:activeTab | [value: ComplaintsInboxTab] | A tab was picked; write it back into activeTab. |
select | [identifier: string] | A row was clicked. |
Slots
None.
Notes
tabCountis declared required but never rendered. No template reference reads it (its only use would be labelling the tabs). Keep passing it — callers usually derive it from the samerows— but know it currently has no effect.- Tab bar visibility: hidden when
inboxEmptyis true ortabsis[]; every tab is disabled whileloading. - Two distinct empty states:
inboxEmpty→ "No complaints yet" (the account is empty);rows.length === 0withinboxEmptyfalse → "No complaints in this tab" (filtering emptied the view). - Filtering is the caller's job — the demo filters with the shared
complaintMatchesTabhelper fromsrc/utils/complaintStatus.ts. - Layout: full width below
lg, then a fixedw-80sidebar with a scrolling row area (lg:h-full lg:overflow-y-auto), so give it a sized parent.