Skip to content

Commit cf06ff3

Browse files
Copilothotlong
andcommitted
feat: complete remaining issue requirements — status type mapping, Overdue Xd format, is_completed green indicator
- getCellRenderer(): add 'status' type → SelectCellRenderer mapping - formatRelativeDate(): overdue dates now show "Overdue Xd" format (e.g. "Overdue 3d") - BooleanCellRenderer: is_completed/done fields render as green circle indicator - Update airtable-style test for new completion indicator - Add 5 new tests for status type, completion indicator variants Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 9566734 commit cf06ff3

3 files changed

Lines changed: 93 additions & 10 deletions

File tree

packages/fields/src/__tests__/cell-renderers.test.tsx

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,19 @@ describe('getCellRenderer', () => {
3434
expect(container.querySelector('[class*="bg-green"]')).toBeInTheDocument();
3535
});
3636

37+
it('should return SelectCellRenderer for status type', () => {
38+
const renderer = getCellRenderer('status');
39+
expect(renderer).toBeDefined();
40+
// Verify it renders a badge (same as select)
41+
const { container } = render(
42+
React.createElement(renderer, {
43+
value: 'Active',
44+
field: { name: 'status', type: 'status', options: [{ value: 'Active', label: 'Active', color: 'green' }] } as any,
45+
})
46+
);
47+
expect(container.querySelector('[class*="bg-green"]')).toBeInTheDocument();
48+
});
49+
3750
it('should return DateCellRenderer for date type', () => {
3851
const renderer = getCellRenderer('date');
3952
expect(renderer).toBeDefined();
@@ -204,14 +217,14 @@ describe('DateCellRenderer', () => {
204217
expect(screen.getByText('Yesterday')).toBeInTheDocument();
205218
});
206219

207-
it('should render recent past date as "X days ago"', () => {
220+
it('should render recent past date as "Overdue Xd"', () => {
208221
render(
209222
<DateCellRenderer
210223
value="2026-02-20T08:00:00Z"
211224
field={{ name: 'due_date', type: 'date' } as any}
212225
/>
213226
);
214-
expect(screen.getByText('4 days ago')).toBeInTheDocument();
227+
expect(screen.getByText('Overdue 4d')).toBeInTheDocument();
215228
});
216229

217230
it('should render overdue date with red text styling', () => {
@@ -313,6 +326,53 @@ describe('BooleanCellRenderer', () => {
313326
);
314327
expect(screen.getByText('—')).toBeInTheDocument();
315328
});
329+
330+
it('should render green circle indicator for is_completed=true', () => {
331+
const { container } = render(
332+
<BooleanCellRenderer
333+
value={true}
334+
field={{ name: 'is_completed', type: 'boolean' } as any}
335+
/>
336+
);
337+
const indicator = container.querySelector('[data-testid="completion-indicator"]');
338+
expect(indicator).toBeInTheDocument();
339+
expect(indicator).toHaveClass('bg-green-500');
340+
});
341+
342+
it('should render empty circle indicator for is_completed=false', () => {
343+
const { container } = render(
344+
<BooleanCellRenderer
345+
value={false}
346+
field={{ name: 'is_completed', type: 'boolean' } as any}
347+
/>
348+
);
349+
const indicator = container.querySelector('[data-testid="completion-indicator"]');
350+
expect(indicator).toBeInTheDocument();
351+
expect(indicator).toHaveClass('border-2');
352+
});
353+
354+
it('should render green circle indicator for completed=true', () => {
355+
const { container } = render(
356+
<BooleanCellRenderer
357+
value={true}
358+
field={{ name: 'completed', type: 'boolean' } as any}
359+
/>
360+
);
361+
const indicator = container.querySelector('[data-testid="completion-indicator"]');
362+
expect(indicator).toBeInTheDocument();
363+
expect(indicator).toHaveClass('bg-green-500');
364+
});
365+
366+
it('should render standard checkbox for non-completion boolean fields', () => {
367+
render(
368+
<BooleanCellRenderer
369+
value={true}
370+
field={{ name: 'active', type: 'boolean' } as any}
371+
/>
372+
);
373+
const checkbox = screen.getByRole('checkbox');
374+
expect(checkbox).toHaveAttribute('data-state', 'checked');
375+
});
316376
});
317377

318378
// =========================================================================
@@ -353,8 +413,8 @@ describe('formatRelativeDate', () => {
353413
expect(formatRelativeDate('2026-02-25T08:00:00Z')).toBe('Tomorrow');
354414
});
355415

356-
it('should return "X days ago" for 2-7 days ago', () => {
357-
expect(formatRelativeDate('2026-02-21T08:00:00Z')).toBe('3 days ago');
416+
it('should return "Overdue Xd" for 2-7 days ago', () => {
417+
expect(formatRelativeDate('2026-02-21T08:00:00Z')).toBe('Overdue 3d');
358418
});
359419

360420
it('should return formatted date for >7 days ago', () => {

packages/fields/src/index.tsx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ export function formatRelativeDate(value: string | Date): string {
123123
if (diffDays === -1) return 'Yesterday';
124124
if (diffDays < -1) {
125125
const absDays = Math.abs(diffDays);
126-
if (absDays <= 7) return `${absDays} days ago`;
126+
if (absDays <= 7) return `Overdue ${absDays}d`;
127127
return formatDate(date);
128128
}
129129
if (diffDays > 1 && diffDays <= 7) return `In ${diffDays} days`;
@@ -245,11 +245,33 @@ export function PercentCellRenderer({ value, field }: CellRendererProps): React.
245245

246246
/**
247247
* Boolean field cell renderer (Airtable-style checkbox)
248+
* Supports semantic rendering for completion fields (green indicator).
248249
*/
249-
export function BooleanCellRenderer({ value }: CellRendererProps): React.ReactElement {
250+
export function BooleanCellRenderer({ value, field }: CellRendererProps): React.ReactElement {
250251
if (value == null) {
251252
return <span className="text-muted-foreground/50 text-xs italic flex items-center justify-center"></span>;
252253
}
254+
255+
// Semantic rendering for completion fields (green circle indicator)
256+
const fieldName = field?.name?.toLowerCase() || '';
257+
const isCompletionField = ['completed', 'is_completed', 'done', 'is_done'].some(
258+
f => fieldName === f || fieldName.endsWith(`_${f}`)
259+
);
260+
261+
if (isCompletionField) {
262+
return (
263+
<div className="flex items-center justify-center">
264+
{value ? (
265+
<div className="size-5 rounded-full bg-green-500 flex items-center justify-center" data-testid="completion-indicator">
266+
<Check className="size-3 text-white" />
267+
</div>
268+
) : (
269+
<div className="size-5 rounded-full border-2 border-muted-foreground/30" data-testid="completion-indicator" />
270+
)}
271+
</div>
272+
);
273+
}
274+
253275
return (
254276
<div className="flex items-center justify-center">
255277
<Checkbox checked={!!value} disabled className="pointer-events-none" />
@@ -640,6 +662,7 @@ export function getCellRenderer(fieldType: string): React.FC<CellRendererProps>
640662
datetime: DateTimeCellRenderer,
641663
time: TextCellRenderer,
642664
select: SelectCellRenderer,
665+
status: SelectCellRenderer,
643666
lookup: SelectCellRenderer, // Default fallback
644667
master_detail: SelectCellRenderer, // Default fallback
645668
email: EmailCellRenderer,

packages/plugin-grid/src/__tests__/airtable-style.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ describe('Auto-type inference: Select/Badge fields', () => {
162162
// 3. Auto-type inference: Boolean/Checkbox fields
163163
// =========================================================================
164164
describe('Auto-type inference: Boolean/Checkbox fields', () => {
165-
it('should render is_completed as checkbox', async () => {
165+
it('should render is_completed as semantic completion indicator', async () => {
166166
renderGrid([
167167
{ field: 'subject', label: 'Subject' },
168168
{ field: 'is_completed', label: 'Is Completed' },
@@ -172,9 +172,9 @@ describe('Auto-type inference: Boolean/Checkbox fields', () => {
172172
expect(screen.getByText('Subject')).toBeInTheDocument();
173173
});
174174

175-
// Should render checkboxes (role="checkbox")
176-
const checkboxes = screen.getAllByRole('checkbox');
177-
expect(checkboxes.length).toBeGreaterThan(0);
175+
// Should render green circle completion indicators (not checkboxes)
176+
const indicators = screen.getAllByTestId('completion-indicator');
177+
expect(indicators.length).toBeGreaterThan(0);
178178
});
179179
});
180180

0 commit comments

Comments
 (0)