Skip to content

Commit b0d6c55

Browse files
Ripwordsclaude
andcommitted
feat(dashboard): source facet in the inbox sidebar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent fe4fde3 commit b0d6c55

2 files changed

Lines changed: 59 additions & 2 deletions

File tree

apps/dashboard/app/components/inbox/facet-sidebar.vue

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@ interface Props {
2727
priorityCounts: Record<ReportPriority, number>
2828
assignees: Assignee[]
2929
tags: Array<{ name: string; count: number }>
30+
sourceCounts: { web: number; expo: number; ios: number; android: number }
3031
selectedPriority: ReportPriority[]
3132
selectedAssignee: string[]
3233
selectedTags: string[]
34+
selectedSource: string[]
3335
sessionUserId: string
3436
}
3537
@@ -38,6 +40,7 @@ const emit = defineEmits<{
3840
priority: [ReportPriority[]]
3941
assignee: [string[]]
4042
tag: [string[]]
43+
source: [string[]]
4144
}>()
4245
4346
const PRIORITIES: ReportPriority[] = ["urgent", "high", "normal", "low"]
@@ -60,6 +63,13 @@ function toggleTag(name: string) {
6063
const has = props.selectedTags.includes(name)
6164
emit("tag", has ? props.selectedTags.filter((x) => x !== name) : [...props.selectedTags, name])
6265
}
66+
function toggleSource(token: string) {
67+
const has = props.selectedSource.includes(token)
68+
emit(
69+
"source",
70+
has ? props.selectedSource.filter((x) => x !== token) : [...props.selectedSource, token],
71+
)
72+
}
6373
6474
function isAssigneeSelected(a: Assignee): boolean {
6575
if (a.id === null) return props.selectedAssignee.includes("unassigned")
@@ -81,6 +91,12 @@ function priorityLabel(p: ReportPriority): string {
8191
return p.charAt(0).toUpperCase() + p.slice(1)
8292
}
8393
94+
const sourceItems = computed(() => [
95+
{ token: "web", label: "Web", count: props.sourceCounts.web },
96+
{ token: "ios", label: "iOS", count: props.sourceCounts.ios },
97+
{ token: "android", label: "Android", count: props.sourceCounts.android },
98+
])
99+
84100
// Priority rows get a colored leading dot (urgent=red, high=orange,
85101
// normal=teal, low=muted). That single dot carries more signal than a
86102
// pill badge and pairs visually with the priority column in the table.
@@ -195,5 +211,41 @@ const priorityDot: Record<ReportPriority, string> = {
195211
</li>
196212
</ul>
197213
</section>
214+
215+
<section>
216+
<h3 class="px-2 mb-2 text-xs font-semibold uppercase tracking-[0.14em] text-muted">Source</h3>
217+
<ul>
218+
<li v-for="item in sourceItems" :key="item.token">
219+
<button
220+
type="button"
221+
:aria-pressed="selectedSource.includes(item.token)"
222+
class="group w-full flex items-center gap-2 rounded-lg px-2.5 py-1.5 transition-colors"
223+
:class="
224+
selectedSource.includes(item.token)
225+
? 'bg-elevated text-default font-semibold'
226+
: 'text-muted hover:text-default hover:bg-elevated/60'
227+
"
228+
@click="toggleSource(item.token)"
229+
>
230+
<span
231+
class="size-1.5 rounded-full shrink-0"
232+
:class="
233+
selectedSource.includes(item.token)
234+
? 'bg-primary'
235+
: 'bg-transparent group-hover:bg-muted/60'
236+
"
237+
aria-hidden="true"
238+
/>
239+
<span class="flex-1 text-left font-medium">{{ item.label }}</span>
240+
<span
241+
class="text-xs font-medium tabular-nums"
242+
:class="selectedSource.includes(item.token) ? 'text-primary' : 'text-muted'"
243+
>
244+
{{ item.count }}
245+
</span>
246+
</button>
247+
</li>
248+
</ul>
249+
</section>
198250
</aside>
199251
</template>

apps/dashboard/app/pages/projects/[id]/reports/index.vue

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const { data, pending, refresh } = useApi<{
4242
count: number
4343
}>
4444
tags: Array<{ name: string; count: number }>
45+
source: { web: number; expo: number; ios: number; android: number }
4546
}
4647
}>(listUrl, { watch: [listUrl] })
4748
@@ -145,11 +146,12 @@ const hasActiveFilters = computed(
145146
query.value.status.length > 0 ||
146147
query.value.priority.length > 0 ||
147148
query.value.assignee.length > 0 ||
148-
query.value.tag.length > 0,
149+
query.value.tag.length > 0 ||
150+
query.value.source.length > 0,
149151
)
150152
151153
function clearFilters() {
152-
update({ q: "", status: [], priority: [], assignee: [], tag: [] })
154+
update({ q: "", status: [], priority: [], assignee: [], tag: [], source: [] })
153155
}
154156
155157
// ---- Keyboard navigation ----
@@ -324,13 +326,16 @@ function onRowSelect(_e: Event, row: TableRowLike) {
324326
:priority-counts="data?.facets.priority ?? ({} as Record<ReportPriority, number>)"
325327
:assignees="data?.facets.assignees ?? []"
326328
:tags="data?.facets.tags ?? []"
329+
:source-counts="data?.facets.source ?? { web: 0, expo: 0, ios: 0, android: 0 }"
327330
:selected-priority="query.priority as ReportPriority[]"
328331
:selected-assignee="query.assignee"
329332
:selected-tags="query.tag"
333+
:selected-source="query.source"
330334
:session-user-id="sessionUserId"
331335
@priority="update({ priority: $event })"
332336
@assignee="update({ assignee: $event })"
333337
@tag="update({ tag: $event })"
338+
@source="update({ source: $event, offset: 0 })"
334339
/>
335340

336341
<div class="flex-1 min-w-0 flex flex-col">

0 commit comments

Comments
 (0)