feat: ilan listesine harita görünümü eklendi (split layout)
- Liste / Harita toggle butonu header'da
- Harita modunda sol panel: kart listesi (fotoğraf, fiyat, oda/m²)
+ sağ panel: MapLibre harita tüm koordinatlı ilanlar
- İlan renkleri duruma göre: aktif=mavi, pasif=gri, satıldı=turuncu, kiralandı=mor
- Pini tıkla → kart listesinde o ilanın kartına scroll
- Kartı tıkla → haritada o ilanın pinine flyTo + popup açılır
- Popup içinde başlık, fiyat, özellikler, 'Detay →' linki
- Koordinatsız ilanlar listede görünür ama haritada pin yok (📍 Konum yok)
- PropertiesMapView: dynamic next/dynamic wrapper (ssr: false)
This commit is contained in:
@@ -9,7 +9,7 @@ export default async function PropertiesPage() {
|
|||||||
const properties = await listProperties(ctx.tenantId);
|
const properties = await listProperties(ctx.tenantId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-1 flex-col gap-4 p-4 md:p-6">
|
<div className="flex flex-1 flex-col gap-4 p-4 md:p-6 overflow-hidden">
|
||||||
<PropertiesClient initialProperties={properties} />
|
<PropertiesClient initialProperties={properties} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
import maplibregl from "maplibre-gl";
|
||||||
|
import "maplibre-gl/dist/maplibre-gl.css";
|
||||||
|
import type { Property } from "@/lib/appwrite/schema";
|
||||||
|
import {
|
||||||
|
PROPERTY_TYPE_LABELS,
|
||||||
|
LISTING_TYPE_LABELS,
|
||||||
|
} from "@/lib/appwrite/schema";
|
||||||
|
|
||||||
|
const STYLE_URL = "https://tiles.openfreemap.org/styles/bright";
|
||||||
|
const TURKEY_CENTER: [number, number] = [35.0, 39.0];
|
||||||
|
|
||||||
|
const STATUS_COLORS: Record<string, string> = {
|
||||||
|
aktif: "#2563eb",
|
||||||
|
pasif: "#9ca3af",
|
||||||
|
satildi: "#f97316",
|
||||||
|
kiralandit: "#8b5cf6",
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
properties: Property[];
|
||||||
|
selectedId?: string | null;
|
||||||
|
onSelect: (id: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PropertiesMapViewInner({ properties, selectedId, onSelect }: Props) {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const mapRef = useRef<maplibregl.Map | null>(null);
|
||||||
|
const markersRef = useRef<Map<string, maplibregl.Marker>>(new Map());
|
||||||
|
const popupsRef = useRef<Map<string, maplibregl.Popup>>(new Map());
|
||||||
|
const onSelectRef = useRef(onSelect);
|
||||||
|
onSelectRef.current = onSelect;
|
||||||
|
|
||||||
|
const mapped = properties.filter((p) => p.mapLat != null && p.mapLng != null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!containerRef.current) return;
|
||||||
|
|
||||||
|
let center: [number, number] = TURKEY_CENTER;
|
||||||
|
let zoom = 5.5;
|
||||||
|
if (mapped.length === 1) {
|
||||||
|
center = [mapped[0].mapLng!, mapped[0].mapLat!];
|
||||||
|
zoom = 14;
|
||||||
|
} else if (mapped.length > 1) {
|
||||||
|
const avgLng = mapped.reduce((s, p) => s + p.mapLng!, 0) / mapped.length;
|
||||||
|
const avgLat = mapped.reduce((s, p) => s + p.mapLat!, 0) / mapped.length;
|
||||||
|
center = [avgLng, avgLat];
|
||||||
|
zoom = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
const map = new maplibregl.Map({
|
||||||
|
container: containerRef.current,
|
||||||
|
style: STYLE_URL,
|
||||||
|
center,
|
||||||
|
zoom,
|
||||||
|
});
|
||||||
|
|
||||||
|
map.addControl(new maplibregl.NavigationControl(), "top-right");
|
||||||
|
mapRef.current = map;
|
||||||
|
|
||||||
|
map.on("load", () => {
|
||||||
|
for (const p of mapped) {
|
||||||
|
const color = STATUS_COLORS[p.status] ?? "#2563eb";
|
||||||
|
|
||||||
|
const popup = new maplibregl.Popup({
|
||||||
|
offset: 28,
|
||||||
|
closeButton: true,
|
||||||
|
maxWidth: "260px",
|
||||||
|
className: "property-popup",
|
||||||
|
}).setHTML(`
|
||||||
|
<div style="font-family:system-ui,sans-serif;padding:2px 0">
|
||||||
|
<p style="font-weight:600;font-size:13px;margin:0 0 3px;line-height:1.3">${p.title}</p>
|
||||||
|
<p style="font-size:11px;color:#6b7280;margin:0 0 6px">${[p.neighborhood, p.district, p.city].filter(Boolean).join(", ")}</p>
|
||||||
|
<p style="font-size:18px;font-weight:700;color:#111;margin:0 0 6px">${p.price.toLocaleString("tr-TR")} <span style="font-size:12px;font-weight:400;color:#6b7280">${p.currency ?? "TRY"}</span></p>
|
||||||
|
<div style="display:flex;gap:5px;flex-wrap:wrap;margin-bottom:10px">
|
||||||
|
<span style="background:#f3f4f6;border-radius:999px;padding:2px 8px;font-size:11px;color:#374151">${PROPERTY_TYPE_LABELS[p.propertyType] ?? p.propertyType}</span>
|
||||||
|
<span style="background:#f3f4f6;border-radius:999px;padding:2px 8px;font-size:11px;color:#374151">${LISTING_TYPE_LABELS[p.listingType] ?? p.listingType}</span>
|
||||||
|
${p.roomCount ? `<span style="background:#f3f4f6;border-radius:999px;padding:2px 8px;font-size:11px;color:#374151">${p.roomCount}</span>` : ""}
|
||||||
|
${p.netM2 ? `<span style="background:#f3f4f6;border-radius:999px;padding:2px 8px;font-size:11px;color:#374151">${p.netM2} m²</span>` : ""}
|
||||||
|
</div>
|
||||||
|
<a href="/properties/${p.$id}" style="display:inline-flex;align-items:center;background:#2563eb;color:white;border-radius:6px;padding:5px 14px;font-size:12px;text-decoration:none;font-weight:500">Detay →</a>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
const marker = new maplibregl.Marker({ color })
|
||||||
|
.setLngLat([p.mapLng!, p.mapLat!])
|
||||||
|
.setPopup(popup)
|
||||||
|
.addTo(map);
|
||||||
|
|
||||||
|
marker.getElement().addEventListener("click", () => {
|
||||||
|
onSelectRef.current(p.$id);
|
||||||
|
});
|
||||||
|
|
||||||
|
markersRef.current.set(p.$id, marker);
|
||||||
|
popupsRef.current.set(p.$id, popup);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
map.remove();
|
||||||
|
mapRef.current = null;
|
||||||
|
markersRef.current.clear();
|
||||||
|
popupsRef.current.clear();
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Fly to + open popup when selection changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (!selectedId || !mapRef.current) return;
|
||||||
|
const prop = mapped.find((p) => p.$id === selectedId);
|
||||||
|
if (!prop) return;
|
||||||
|
|
||||||
|
mapRef.current.flyTo({
|
||||||
|
center: [prop.mapLng!, prop.mapLat!],
|
||||||
|
zoom: Math.max(mapRef.current.getZoom(), 14),
|
||||||
|
duration: 700,
|
||||||
|
});
|
||||||
|
|
||||||
|
const marker = markersRef.current.get(selectedId);
|
||||||
|
if (marker && !marker.getPopup().isOpen()) {
|
||||||
|
marker.togglePopup();
|
||||||
|
}
|
||||||
|
}, [selectedId]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
if (mapped.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="flex h-full items-center justify-center bg-muted/30 rounded-lg border text-sm text-muted-foreground">
|
||||||
|
Koordinatlı ilan yok — ilan formundan konum ekleyin.
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div ref={containerRef} className="h-full w-full rounded-lg overflow-hidden border" />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
import { MapPin } from "lucide-react";
|
||||||
|
import type { Property } from "@/lib/appwrite/schema";
|
||||||
|
|
||||||
|
const Inner = dynamic(
|
||||||
|
() =>
|
||||||
|
import("./properties-map-view-inner").then((m) => m.PropertiesMapViewInner),
|
||||||
|
{
|
||||||
|
ssr: false,
|
||||||
|
loading: () => (
|
||||||
|
<div className="flex h-full items-center justify-center rounded-lg border bg-muted/30">
|
||||||
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
|
<MapPin className="size-4 animate-pulse" />
|
||||||
|
Harita yükleniyor...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
properties: Property[];
|
||||||
|
selectedId?: string | null;
|
||||||
|
onSelect: (id: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PropertiesMapView(props: Props) {
|
||||||
|
return <Inner {...props} />;
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState, useRef } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { MoreHorizontal, Plus, Pencil, Trash2, ExternalLink } from "lucide-react";
|
import { MoreHorizontal, Plus, Pencil, Trash2, ExternalLink, List, Map } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -15,6 +15,8 @@ import {
|
|||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { deletePropertyAction } from "@/lib/appwrite/property-actions";
|
import { deletePropertyAction } from "@/lib/appwrite/property-actions";
|
||||||
import { PropertyFormSheet } from "./property-form-sheet";
|
import { PropertyFormSheet } from "./property-form-sheet";
|
||||||
|
import { PropertiesMapView } from "@/components/map/properties-map-view";
|
||||||
|
import { getPropertyImagePreviewUrl, parseImageIds } from "@/lib/appwrite/storage-utils";
|
||||||
import type { Property } from "@/lib/appwrite/schema";
|
import type { Property } from "@/lib/appwrite/schema";
|
||||||
import {
|
import {
|
||||||
PROPERTY_STATUS_LABELS,
|
PROPERTY_STATUS_LABELS,
|
||||||
@@ -26,20 +28,19 @@ interface PropertiesClientProps {
|
|||||||
initialProperties: Property[];
|
initialProperties: Property[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ViewMode = "list" | "map";
|
||||||
|
|
||||||
export function PropertiesClient({ initialProperties }: PropertiesClientProps) {
|
export function PropertiesClient({ initialProperties }: PropertiesClientProps) {
|
||||||
const [properties, setProperties] = useState(initialProperties);
|
const [properties, setProperties] = useState(initialProperties);
|
||||||
const [sheetOpen, setSheetOpen] = useState(false);
|
const [sheetOpen, setSheetOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<Property | null>(null);
|
const [editing, setEditing] = useState<Property | null>(null);
|
||||||
|
const [viewMode, setViewMode] = useState<ViewMode>("list");
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
|
const rowRefs = useRef<Record<string, HTMLTableRowElement>>({});
|
||||||
|
const cardRefs = useRef<Record<string, HTMLDivElement>>({});
|
||||||
|
|
||||||
function openCreate() {
|
function openCreate() { setEditing(null); setSheetOpen(true); }
|
||||||
setEditing(null);
|
function openEdit(p: Property) { setEditing(p); setSheetOpen(true); }
|
||||||
setSheetOpen(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function openEdit(p: Property) {
|
|
||||||
setEditing(p);
|
|
||||||
setSheetOpen(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleDelete(p: Property) {
|
async function handleDelete(p: Property) {
|
||||||
if (!confirm(`"${p.title}" silinsin mi?`)) return;
|
if (!confirm(`"${p.title}" silinsin mi?`)) return;
|
||||||
@@ -52,22 +53,68 @@ export function PropertiesClient({ initialProperties }: PropertiesClientProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSuccess() {
|
function handleMapSelect(id: string) {
|
||||||
// Revalidation handles the data refresh; re-fetching in client is not
|
setSelectedId(id);
|
||||||
// necessary since the page will reload on next navigation.
|
// Scroll the card into view in the list panel
|
||||||
// For immediate local update we just remove the item or close the sheet.
|
const card = cardRefs.current[id];
|
||||||
|
card?.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleCardClick(id: string) {
|
||||||
|
setSelectedId(id === selectedId ? null : id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mappedCount = properties.filter((p) => p.mapLat != null && p.mapLng != null).length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4 flex-1">
|
||||||
<div className="flex items-center justify-between">
|
{/* Header */}
|
||||||
|
<div className="flex items-center justify-between gap-3 flex-wrap">
|
||||||
|
<div>
|
||||||
<h1 className="text-2xl font-bold">İlanlar</h1>
|
<h1 className="text-2xl font-bold">İlanlar</h1>
|
||||||
|
{viewMode === "map" && mappedCount < properties.length && (
|
||||||
|
<p className="text-xs text-muted-foreground mt-0.5">
|
||||||
|
{mappedCount} / {properties.length} ilanın koordinatı var
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{/* View toggle */}
|
||||||
|
<div className="flex rounded-md border overflow-hidden">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setViewMode("list")}
|
||||||
|
className={`flex items-center gap-1.5 px-3 py-1.5 text-sm transition-colors ${
|
||||||
|
viewMode === "list"
|
||||||
|
? "bg-primary text-primary-foreground"
|
||||||
|
: "bg-background text-muted-foreground hover:text-foreground"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<List className="size-3.5" />
|
||||||
|
Liste
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setViewMode("map")}
|
||||||
|
className={`flex items-center gap-1.5 px-3 py-1.5 text-sm transition-colors border-l ${
|
||||||
|
viewMode === "map"
|
||||||
|
? "bg-primary text-primary-foreground"
|
||||||
|
: "bg-background text-muted-foreground hover:text-foreground"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Map className="size-3.5" />
|
||||||
|
Harita
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<Button onClick={openCreate} size="sm">
|
<Button onClick={openCreate} size="sm">
|
||||||
<Plus className="mr-1.5 size-4" />
|
<Plus className="mr-1.5 size-4" />
|
||||||
Yeni İlan
|
Yeni İlan
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* List view */}
|
||||||
|
{viewMode === "list" && (
|
||||||
<div className="rounded-md border">
|
<div className="rounded-md border">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
@@ -90,7 +137,10 @@ export function PropertiesClient({ initialProperties }: PropertiesClientProps) {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
)}
|
)}
|
||||||
{properties.map((p) => (
|
{properties.map((p) => (
|
||||||
<TableRow key={p.$id}>
|
<TableRow
|
||||||
|
key={p.$id}
|
||||||
|
ref={(el) => { if (el) rowRefs.current[p.$id] = el; }}
|
||||||
|
>
|
||||||
<TableCell className="font-medium max-w-[200px] truncate">{p.title}</TableCell>
|
<TableCell className="font-medium max-w-[200px] truncate">{p.title}</TableCell>
|
||||||
<TableCell>{PROPERTY_TYPE_LABELS[p.propertyType] ?? p.propertyType}</TableCell>
|
<TableCell>{PROPERTY_TYPE_LABELS[p.propertyType] ?? p.propertyType}</TableCell>
|
||||||
<TableCell>{LISTING_TYPE_LABELS[p.listingType] ?? p.listingType}</TableCell>
|
<TableCell>{LISTING_TYPE_LABELS[p.listingType] ?? p.listingType}</TableCell>
|
||||||
@@ -134,12 +184,100 @@ export function PropertiesClient({ initialProperties }: PropertiesClientProps) {
|
|||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Map view — split layout */}
|
||||||
|
{viewMode === "map" && (
|
||||||
|
<div className="flex gap-4 flex-1 min-h-0" style={{ height: "calc(100vh - 14rem)" }}>
|
||||||
|
{/* Left: scrollable property cards */}
|
||||||
|
<div className="w-80 shrink-0 overflow-y-auto space-y-2 pr-1">
|
||||||
|
{properties.length === 0 && (
|
||||||
|
<p className="text-muted-foreground text-sm text-center py-10">Henüz ilan yok.</p>
|
||||||
|
)}
|
||||||
|
{properties.map((p) => {
|
||||||
|
const coverImageId = parseImageIds(p.imageIds)[0];
|
||||||
|
const hasCoords = p.mapLat != null && p.mapLng != null;
|
||||||
|
const isSelected = selectedId === p.$id;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={p.$id}
|
||||||
|
ref={(el) => { if (el) cardRefs.current[p.$id] = el; }}
|
||||||
|
onClick={() => hasCoords && handleCardClick(p.$id)}
|
||||||
|
className={`rounded-lg border bg-card overflow-hidden transition-all ${
|
||||||
|
hasCoords ? "cursor-pointer hover:shadow-md" : "opacity-60"
|
||||||
|
} ${isSelected ? "ring-2 ring-primary shadow-md" : ""}`}
|
||||||
|
>
|
||||||
|
{/* Cover image */}
|
||||||
|
{coverImageId && (
|
||||||
|
<div className="h-28 overflow-hidden bg-muted">
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
<img
|
||||||
|
src={getPropertyImagePreviewUrl(coverImageId, 320, 180)}
|
||||||
|
alt={p.title}
|
||||||
|
className="h-full w-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="p-3 space-y-1">
|
||||||
|
<div className="flex items-start justify-between gap-2">
|
||||||
|
<p className="text-sm font-semibold leading-snug line-clamp-2">{p.title}</p>
|
||||||
|
<StatusBadge status={p.status} />
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground line-clamp-1">
|
||||||
|
{[p.neighborhood, p.district, p.city].filter(Boolean).join(", ")}
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<p className="text-sm font-bold">
|
||||||
|
{p.price.toLocaleString("tr-TR")}
|
||||||
|
<span className="text-xs font-normal text-muted-foreground ml-1">{p.currency ?? "TRY"}</span>
|
||||||
|
</p>
|
||||||
|
<div className="flex gap-1">
|
||||||
|
{p.roomCount && (
|
||||||
|
<span className="text-xs bg-muted text-muted-foreground px-1.5 py-0.5 rounded">
|
||||||
|
{p.roomCount}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{p.netM2 && (
|
||||||
|
<span className="text-xs bg-muted text-muted-foreground px-1.5 py-0.5 rounded">
|
||||||
|
{p.netM2}m²
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between pt-0.5">
|
||||||
|
{!hasCoords && (
|
||||||
|
<span className="text-xs text-muted-foreground">📍 Konum yok</span>
|
||||||
|
)}
|
||||||
|
<Link
|
||||||
|
href={`/properties/${p.$id}`}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
className="ml-auto text-xs text-primary hover:underline"
|
||||||
|
>
|
||||||
|
Detay →
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right: map */}
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<PropertiesMapView
|
||||||
|
properties={properties}
|
||||||
|
selectedId={selectedId}
|
||||||
|
onSelect={handleMapSelect}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<PropertyFormSheet
|
<PropertyFormSheet
|
||||||
open={sheetOpen}
|
open={sheetOpen}
|
||||||
onOpenChange={setSheetOpen}
|
onOpenChange={setSheetOpen}
|
||||||
property={editing}
|
property={editing}
|
||||||
onSuccess={handleSuccess}
|
onSuccess={() => {}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -153,7 +291,7 @@ function StatusBadge({ status }: { status: string }) {
|
|||||||
kiralandit: "outline",
|
kiralandit: "outline",
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Badge variant={map[status] ?? "secondary"}>
|
<Badge variant={map[status] ?? "secondary"} className="shrink-0 text-xs">
|
||||||
{PROPERTY_STATUS_LABELS[status as keyof typeof PROPERTY_STATUS_LABELS] ?? status}
|
{PROPERTY_STATUS_LABELS[status as keyof typeof PROPERTY_STATUS_LABELS] ?? status}
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user