feat(web): listings page — ticker-style DataTable với toggle card view
Tạo mới trang /listings dạng bảng ticker-style theo spec TEC-3034. - DataTable compact (row 36px, sticky header, alternating rows) - Cột: #, Mã (GG-xxx), Quận, Loại, Giá, Δ30d, DT m², KL/Views - Sortable theo Giá, Δ30d, DT m², KL/Views - Filter inline: Loại giao dịch, Loại BĐS, Quận, Khoảng giá - Toggle view: Table (default) ↔ Card grid (legacy component cũ) - Pagination restyle compact, giữ nguyên API params - Click row → navigate to detail page - Dùng DataTable + PriceDelta từ @/components/design-system Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
49
apps/web/components/design-system/ticker-strip.tsx
Normal file
49
apps/web/components/design-system/ticker-strip.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import * as React from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { PriceDelta, type PriceDeltaDirection } from './price-delta';
|
||||
|
||||
export interface TickerItem {
|
||||
id: string;
|
||||
label: string;
|
||||
changePercent: number;
|
||||
direction?: PriceDeltaDirection;
|
||||
}
|
||||
|
||||
export interface TickerStripProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
items: TickerItem[];
|
||||
/** Tắt animation (cho unit test / reduced motion). */
|
||||
paused?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Thanh chạy ngang hiển thị biến động giá top quận.
|
||||
* Render 2 lần liên tiếp để tạo vòng lặp mượt với animation `-50%`.
|
||||
*/
|
||||
export function TickerStrip({ items, paused, className, ...rest }: TickerStripProps) {
|
||||
const duplicated = React.useMemo(() => [...items, ...items], [items]);
|
||||
return (
|
||||
<div className={cn('relative h-full overflow-hidden', className)} {...rest}>
|
||||
<div
|
||||
className={cn(
|
||||
'flex h-full w-max items-center gap-6 whitespace-nowrap px-4 font-mono text-ticker',
|
||||
!paused && 'animate-ticker',
|
||||
)}
|
||||
>
|
||||
{duplicated.map((item, idx) => (
|
||||
<span
|
||||
key={`${item.id}-${idx}`}
|
||||
className="inline-flex items-center gap-2 text-foreground-muted"
|
||||
>
|
||||
<span className="text-foreground">{item.label}</span>
|
||||
<PriceDelta
|
||||
value={item.changePercent}
|
||||
size="sm"
|
||||
hideIcon={false}
|
||||
direction={item.direction}
|
||||
/>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user