Skip to content

Commit 97d4856

Browse files
authored
refactor: decompose ReviewView into focused components (FE-16) (#923)
* refactor: decompose ReviewView into focused components (FE-16) Extract the monolithic ReviewView.vue (1,659 lines) into focused components and composables, each under 400 lines: Components (in frontend/taskdeck-web/src/components/review/): - ReviewHeader.vue (199 lines) - hero, board selector, action buttons - ReviewSummaryCards.vue (74 lines) - statistics cards grid - ReviewEmptyState.vue (47 lines) - empty state with guided actions - ReviewProposalCard.vue (342 lines) - individual proposal card - ReviewProposalActions.vue (229 lines) - approve/reject/apply/dismiss - ReviewProposalDetails.vue (346 lines) - collapsible details sections Composables (in frontend/taskdeck-web/src/composables/): - useReviewProposals.ts (346 lines) - state, filtering, data loading - useReviewActions.ts (159 lines) - proposal actions and diff toggle ReviewView.vue is now 148 lines (orchestration + layout only). All 45 existing ReviewView tests pass without modification. Typecheck, build, and lint all pass with no new warnings. Closes #856 * fix: address review findings in decomposed ReviewView components - Make fullCorrelationId a computed() in ReviewProposalDetails for reactivity when the proposal prop changes (gemini-code-assist finding) - Add null safety to shortCorrelationId in ReviewProposalCard for defensive API data handling (gemini-code-assist finding) - Narrow onBoardFilterInput type in ReviewHeader from string|number|boolean to string to match InputAssistField emit type
1 parent 7676f46 commit 97d4856

9 files changed

Lines changed: 1826 additions & 1595 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<script setup lang="ts">
2+
defineEmits<{
3+
(e: 'open-inbox'): void
4+
(e: 'navigate', path: string): void
5+
}>()
6+
</script>
7+
8+
<template>
9+
<section class="td-panel td-review-empty">
10+
<h2 class="td-section-title">No proposals need review yet</h2>
11+
<p class="td-section-desc">
12+
Start from Inbox when you want Taskdeck to propose a change, or open Boards if you want to continue directly
13+
with the work that already landed.
14+
</p>
15+
<div class="td-review-empty__actions">
16+
<button class="td-btn td-btn--primary" @click="$emit('open-inbox')">Go to Inbox</button>
17+
<button class="td-btn td-btn--secondary" @click="$emit('navigate', '/workspace/boards')">Open Boards</button>
18+
<button class="td-btn td-btn--secondary" @click="$emit('navigate', '/workspace/home')">Back to Home</button>
19+
</div>
20+
</section>
21+
</template>
22+
23+
<style scoped>
24+
.td-review-empty {
25+
display: flex;
26+
flex-direction: column;
27+
gap: var(--td-space-3);
28+
}
29+
30+
.td-review-empty__actions {
31+
display: flex;
32+
flex-wrap: wrap;
33+
gap: var(--td-space-2);
34+
}
35+
36+
@media (max-width: 640px) {
37+
.td-review-empty__actions {
38+
flex-direction: column;
39+
}
40+
41+
.td-review-empty__actions .td-btn {
42+
width: 100%;
43+
min-height: 44px;
44+
justify-content: center;
45+
}
46+
}
47+
</style>
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
<script setup lang="ts">
2+
import InputAssistField from '../common/InputAssistField.vue'
3+
import type { InputAssistOption } from '../../utils/inputAssist'
4+
5+
defineProps<{
6+
activeBoardFilter: string
7+
activeBoardName: string
8+
boardFilterInput: string
9+
boardOptions: InputAssistOption[]
10+
loadingBoards: boolean
11+
showCompleted: boolean
12+
proposalsLoading: boolean
13+
dismissableCount: number
14+
}>()
15+
16+
const emit = defineEmits<{
17+
(e: 'update:boardFilterInput', value: string): void
18+
(e: 'update:showCompleted', value: boolean): void
19+
(e: 'select-board', option: InputAssistOption): void
20+
(e: 'clear-board-filter'): void
21+
(e: 'dismiss-applied'): void
22+
(e: 'refresh'): void
23+
(e: 'open-inbox'): void
24+
(e: 'navigate', path: string): void
25+
}>()
26+
27+
function onBoardFilterInput(value: string) {
28+
emit('update:boardFilterInput', value)
29+
}
30+
</script>
31+
32+
<template>
33+
<header class="td-panel td-review__hero">
34+
<div class="td-review__hero-copy">
35+
<span class="td-review__eyebrow" aria-hidden="true">Review</span>
36+
<h1 class="td-page-title">Review</h1>
37+
<p class="td-review__subtitle">
38+
Nothing changes on a board until you approve it here.
39+
</p>
40+
<p v-if="activeBoardFilter" class="td-review__board-filter">
41+
Showing proposals for <strong>{{ activeBoardName }}</strong>.
42+
<button class="td-btn td-btn--link td-btn--sm" @click="$emit('clear-board-filter')">Show all boards</button>
43+
</p>
44+
</div>
45+
46+
<div class="td-review__board-selector">
47+
<InputAssistField
48+
:model-value="boardFilterInput"
49+
:options="boardOptions"
50+
aria-label="Filter by board"
51+
placeholder="Filter proposals by board..."
52+
no-results-text="No matching boards."
53+
:disabled="loadingBoards"
54+
@update:model-value="onBoardFilterInput"
55+
@select="(option: InputAssistOption) => $emit('select-board', option)"
56+
/>
57+
</div>
58+
59+
<div class="td-review__hero-actions">
60+
<label class="td-review__toggle">
61+
<input
62+
:checked="showCompleted"
63+
type="checkbox"
64+
class="td-review__toggle-input"
65+
@change="$emit('update:showCompleted', ($event.target as HTMLInputElement).checked)"
66+
/>
67+
<span class="td-review__toggle-label">Show completed</span>
68+
</label>
69+
<button
70+
class="td-btn td-btn--secondary"
71+
:disabled="dismissableCount === 0"
72+
@click="$emit('dismiss-applied')"
73+
>
74+
Clear completed ({{ dismissableCount }})
75+
</button>
76+
<button class="td-btn td-btn--primary" :disabled="proposalsLoading" @click="$emit('refresh')">
77+
{{ proposalsLoading ? 'Refreshing...' : 'Refresh Review' }}
78+
</button>
79+
<button class="td-btn td-btn--secondary" @click="$emit('open-inbox')">Open Inbox</button>
80+
<button class="td-btn td-btn--secondary" @click="$emit('navigate', '/workspace/automations/queue')">
81+
Open Queue (Advanced)
82+
</button>
83+
<button class="td-btn td-btn--secondary" @click="$emit('navigate', '/workspace/automations/chat')">
84+
Open Chat (Advanced)
85+
</button>
86+
</div>
87+
</header>
88+
</template>
89+
90+
<style scoped>
91+
.td-review__hero {
92+
display: flex;
93+
justify-content: space-between;
94+
gap: var(--td-space-6);
95+
align-items: flex-start;
96+
}
97+
98+
.td-review__hero-copy {
99+
display: flex;
100+
flex-direction: column;
101+
gap: var(--td-space-2);
102+
max-width: 720px;
103+
}
104+
105+
.td-review__eyebrow {
106+
font-size: var(--td-font-xs);
107+
font-weight: 700;
108+
letter-spacing: 0.08em;
109+
text-transform: uppercase;
110+
color: var(--td-color-primary);
111+
}
112+
113+
.td-review__subtitle {
114+
color: var(--td-text-secondary);
115+
line-height: 1.6;
116+
}
117+
118+
.td-review__board-filter {
119+
margin: 0;
120+
color: var(--td-color-primary);
121+
font-size: var(--td-font-sm);
122+
font-weight: 600;
123+
display: flex;
124+
align-items: center;
125+
gap: var(--td-space-2);
126+
}
127+
128+
.td-review__board-selector {
129+
max-width: 320px;
130+
}
131+
132+
.td-review__hero-actions {
133+
display: flex;
134+
flex-wrap: wrap;
135+
gap: var(--td-space-2);
136+
justify-content: flex-end;
137+
align-items: center;
138+
}
139+
140+
.td-review__toggle {
141+
display: flex;
142+
align-items: center;
143+
gap: var(--td-space-2);
144+
cursor: pointer;
145+
user-select: none;
146+
}
147+
148+
.td-review__toggle-input {
149+
accent-color: var(--td-color-primary);
150+
width: 16px;
151+
height: 16px;
152+
cursor: pointer;
153+
}
154+
155+
.td-review__toggle-label {
156+
font-size: var(--td-font-sm);
157+
font-weight: 600;
158+
color: var(--td-text-secondary);
159+
white-space: nowrap;
160+
}
161+
162+
@media (max-width: 900px) {
163+
.td-review__hero {
164+
flex-direction: column;
165+
}
166+
167+
.td-review__hero-actions {
168+
justify-content: flex-start;
169+
}
170+
}
171+
172+
@media (max-width: 640px) {
173+
.td-review__hero {
174+
gap: var(--td-space-4);
175+
padding: var(--td-space-4);
176+
}
177+
178+
.td-review__board-selector {
179+
max-width: 100%;
180+
width: 100%;
181+
}
182+
183+
.td-review__hero-actions {
184+
flex-direction: column;
185+
width: 100%;
186+
}
187+
188+
.td-review__hero-actions .td-btn {
189+
width: 100%;
190+
min-height: 44px;
191+
justify-content: center;
192+
}
193+
194+
.td-review__board-filter {
195+
flex-direction: column;
196+
gap: var(--td-space-1);
197+
}
198+
}
199+
</style>

0 commit comments

Comments
 (0)