fix(web): update dashboard pages, layouts, and listing forms

Update 12 page/layout files across auth, dashboard, listings, and search
routes to improve type safety, fix component imports, and align with
latest API changes.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-11 01:39:59 +07:00
parent a59bf8eda2
commit 759052a71f
12 changed files with 234 additions and 30 deletions

View File

@@ -6,6 +6,7 @@ import * as React from 'react';
import { FilterBar, type SearchFilters } from '@/components/search/filter-bar';
import { SearchResults } from '@/components/search/search-results';
import { Button } from '@/components/ui/button';
import { useCreateSavedSearch } from '@/lib/hooks/use-saved-searches';
import { listingsApi, type ListingDetail, type PaginatedResult } from '@/lib/listings-api';
const ListingMap = dynamic(
@@ -58,6 +59,12 @@ function SearchContent() {
const [viewMode, setViewMode] = React.useState<ViewMode>('list');
const [showMobileFilters, setShowMobileFilters] = React.useState(false);
const [selectedListingId, setSelectedListingId] = React.useState<string | undefined>();
const [showSaveDialog, setShowSaveDialog] = React.useState(false);
const [saveName, setSaveName] = React.useState('');
const [saveAlertEnabled, setSaveAlertEnabled] = React.useState(true);
const [saveSuccess, setSaveSuccess] = React.useState(false);
const createSavedSearch = useCreateSavedSearch();
const handleMarkerClick = (listing: ListingDetail) => {
setSelectedListingId(listing.id);
@@ -120,14 +127,116 @@ function SearchContent() {
([key, value]) => value && key !== 'sort',
).length;
const handleSaveSearch = () => {
if (!saveName.trim()) return;
const filterData: Record<string, string> = {};
Object.entries(filters).forEach(([key, value]) => {
if (value && key !== 'sort') filterData[key] = value;
});
createSavedSearch.mutate(
{ name: saveName.trim(), filters: filterData, alertEnabled: saveAlertEnabled },
{
onSuccess: () => {
setSaveSuccess(true);
setSaveName('');
setTimeout(() => {
setShowSaveDialog(false);
setSaveSuccess(false);
}, 1500);
},
},
);
};
return (
<div className="mx-auto max-w-7xl px-4 py-6">
{/* Header */}
<div className="mb-6">
<h1 className="text-2xl font-bold md:text-3xl">Tìm kiếm bất đng sản</h1>
<p className="mt-1 text-muted-foreground">
Tìm bất đng sản phù hợp với nhu cầu của bạn
</p>
<div className="mb-6 flex items-start justify-between">
<div>
<h1 className="text-2xl font-bold md:text-3xl">Tìm kiếm bất đng sản</h1>
<p className="mt-1 text-muted-foreground">
Tìm bất đng sản phù hợp với nhu cầu của bạn
</p>
</div>
{activeFilterCount > 0 && (
<div className="relative">
<Button
variant="outline"
size="sm"
onClick={() => setShowSaveDialog(!showSaveDialog)}
>
<svg className="mr-1.5 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z" />
</svg>
Lưu tìm kiếm
</Button>
{/* Save search dialog */}
{showSaveDialog && (
<div className="absolute right-0 top-full z-50 mt-2 w-80 rounded-lg border bg-card p-4 shadow-lg">
{saveSuccess ? (
<div className="flex items-center gap-2 text-green-600">
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
<span className="text-sm font-medium">Đã lưu tìm kiếm!</span>
</div>
) : (
<>
<h3 className="mb-3 font-medium" id="save-search-heading">Lưu bộ lọc tìm kiếm</h3>
<label htmlFor="save-search-name" className="sr-only">Tên tìm kiếm</label>
<input
id="save-search-name"
type="text"
value={saveName}
onChange={(e) => setSaveName(e.target.value)}
placeholder="Tên tìm kiếm (VD: Chung cư Q7 dưới 3 tỷ)"
className="mb-3 w-full rounded-md border bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-primary"
maxLength={100}
onKeyDown={(e) => e.key === 'Enter' && handleSaveSearch()}
aria-describedby="save-search-heading"
/>
<div className="mb-3 flex items-center gap-2 text-sm">
<input
id="save-alert-enabled"
type="checkbox"
checked={saveAlertEnabled}
onChange={(e) => setSaveAlertEnabled(e.target.checked)}
className="rounded"
/>
<label htmlFor="save-alert-enabled">
Nhận thông báo khi kết quả mới
</label>
</div>
<div className="flex items-center justify-end gap-2">
<Button
size="sm"
variant="ghost"
onClick={() => setShowSaveDialog(false)}
>
Hủy
</Button>
<Button
size="sm"
onClick={handleSaveSearch}
disabled={!saveName.trim() || createSavedSearch.isPending}
>
{createSavedSearch.isPending ? 'Đang lưu...' : 'Lưu'}
</Button>
</div>
{createSavedSearch.isError && (
<p className="mt-2 text-xs text-destructive">
Không thể lưu tìm kiếm. Vui lòng thử lại.
</p>
)}
</>
)}
</div>
)}
</div>
)}
</div>
{/* View Mode Toggle + Mobile Filter Button */}