fix(portal): mobile responsive — card view for RGI, visible map controls
Mobile (< 640px): - RGI: card-based layout instead of table (shows nr cerere, status, solicitant, termen, rezolutie, UAT in compact card) - Header: compact "Portal" title, smaller tab buttons - Map: selection toolbar centered at bottom (always visible) - UAT info card: smaller text, truncated, doesn't overlap basemap switcher - Feature panel: narrower (w-56 vs w-64) - Filter buttons: smaller text Desktop (>= 640px): - Same table view as before (hidden on mobile) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -767,11 +767,11 @@ function RgiContent() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4 max-w-[1400px] mx-auto p-4">
|
||||
<div className="space-y-3 sm:space-y-4 max-w-[1400px] mx-auto p-3 sm:p-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-bold">Documente Eliberate eTerra</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Lucrari depuse cu documente eliberate — descarca direct din eTerra RGI
|
||||
<h2 className="text-base sm:text-lg font-bold">Documente Eliberate eTerra</h2>
|
||||
<p className="text-xs sm:text-sm text-muted-foreground">
|
||||
Lucrari depuse cu documente eliberate
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -855,7 +855,7 @@ function RgiContent() {
|
||||
key={opt.id}
|
||||
onClick={() => setFilterMode(opt.id)}
|
||||
className={cn(
|
||||
"px-3 py-1 text-xs rounded font-medium transition-colors",
|
||||
"px-2 sm:px-3 py-1 text-[11px] sm:text-xs rounded font-medium transition-colors",
|
||||
filterMode === opt.id
|
||||
? "bg-background shadow text-foreground"
|
||||
: "text-muted-foreground hover:text-foreground",
|
||||
@@ -906,8 +906,65 @@ function RgiContent() {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Mobile card view */}
|
||||
{!loading && processed.length > 0 && (
|
||||
<Card>
|
||||
<div className="space-y-2 sm:hidden">
|
||||
{processed.map((app) => {
|
||||
const pk = app.applicationPk;
|
||||
const isExpanded = expandedPk === pk;
|
||||
const solved = app.hasSolution === 1;
|
||||
return (
|
||||
<Card key={pk} className={cn(isExpanded && "ring-1 ring-foreground/10")}>
|
||||
<CardContent className="p-3">
|
||||
<div className="flex items-start gap-2" onClick={() => setExpandedPk(isExpanded ? null : pk)}>
|
||||
<button
|
||||
type="button"
|
||||
className="mt-0.5 shrink-0"
|
||||
onClick={(e) => { e.stopPropagation(); void downloadAllForApp(app); }}
|
||||
disabled={downloadingAppPk === pk}
|
||||
>
|
||||
{downloadingAppPk === pk ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin text-emerald-500" />
|
||||
) : solved ? (
|
||||
<Download className="h-4 w-4 text-emerald-500" />
|
||||
) : (
|
||||
<Clock className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
</button>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 justify-between">
|
||||
<span className="font-mono font-bold text-sm">{app.appNo}</span>
|
||||
<Badge
|
||||
variant={solved ? "default" : "secondary"}
|
||||
className={cn("text-[10px]", solved && "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-400")}
|
||||
>
|
||||
{app.statusName || app.stateCode || "-"}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground truncate">{app.requester || app.deponent || "-"}</p>
|
||||
<div className="flex items-center gap-3 text-[11px] text-muted-foreground mt-1">
|
||||
<span>Termen: {fmtTs(app.dueDate)}</span>
|
||||
<Badge variant="outline" className="text-[9px]">{app.resolutionName || "-"}</Badge>
|
||||
{app.uat && <span>{app.uat}</span>}
|
||||
</div>
|
||||
</div>
|
||||
{isExpanded ? <ChevronUp className="h-4 w-4 text-muted-foreground shrink-0" /> : <ChevronDown className="h-4 w-4 text-muted-foreground shrink-0" />}
|
||||
</div>
|
||||
{isExpanded && (
|
||||
<div className="mt-2 pt-2 border-t">
|
||||
<IssuedDocsPanel applicationPk={pk} workspaceId={app.workspaceId} appNo={app.appNo} />
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Desktop table view */}
|
||||
{!loading && processed.length > 0 && (
|
||||
<Card className="hidden sm:block">
|
||||
<CardContent className="p-0">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
@@ -1450,15 +1507,15 @@ function HartaContent() {
|
||||
layerVisibility={layerVisibility}
|
||||
/>
|
||||
|
||||
{/* Top-left: UAT info + change button */}
|
||||
<div className="absolute top-3 left-3 z-10 flex flex-col gap-2">
|
||||
<div className="bg-background/95 backdrop-blur-sm border rounded-lg shadow-lg px-3 py-2 flex items-center gap-2">
|
||||
{/* Top-left: UAT info + change button (responsive) */}
|
||||
<div className="absolute top-2 left-2 right-[140px] sm:right-auto z-10 flex flex-col gap-2">
|
||||
<div className="bg-background/95 backdrop-blur-sm border rounded-lg shadow-lg px-2 sm:px-3 py-1.5 sm:py-2 flex items-center gap-2">
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-semibold truncate">{selectedUat?.name}</p>
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
<p className="text-xs sm:text-sm font-semibold truncate">{selectedUat?.name}</p>
|
||||
<p className="text-[10px] sm:text-[11px] text-muted-foreground truncate">
|
||||
{selectedUat?.county && `${selectedUat.county} - `}SIRUTA: {selectedSiruta}
|
||||
{selectedUat && selectedUat.localFeatures > 0 && (
|
||||
<span className="ml-2">{selectedUat.localFeatures.toLocaleString()} parcele</span>
|
||||
<span className="ml-1 sm:ml-2">{selectedUat.localFeatures.toLocaleString()} parcele</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -1480,10 +1537,10 @@ function HartaContent() {
|
||||
</div>
|
||||
|
||||
{/* Top-right: basemap switcher + simple feature info */}
|
||||
<div className="absolute top-3 right-3 z-10 flex flex-col items-end gap-2">
|
||||
<div className="absolute top-2 right-2 z-10 flex flex-col items-end gap-2">
|
||||
<PortalBasemapSwitcher value={basemap} onChange={setBasemap} />
|
||||
{clickedFeature && selectionMode === "off" && (
|
||||
<div className="bg-background/95 backdrop-blur-sm border rounded-lg shadow-lg w-64 overflow-hidden">
|
||||
<div className="bg-background/95 backdrop-blur-sm border rounded-lg shadow-lg w-56 sm:w-64 overflow-hidden">
|
||||
<div className="flex items-center justify-between px-3 py-2 border-b">
|
||||
<h3 className="text-sm font-semibold truncate">
|
||||
{String(clickedFeature.properties.cadastral_ref ?? clickedFeature.properties.object_id ?? "Parcela")}
|
||||
@@ -1526,8 +1583,8 @@ function HartaContent() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Bottom-left: selection toolbar */}
|
||||
<div className="absolute bottom-8 left-3 z-10">
|
||||
{/* Bottom: selection toolbar — centered on mobile */}
|
||||
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 sm:left-3 sm:translate-x-0 z-10">
|
||||
<SelectionToolbar
|
||||
selectedFeatures={selectedFeatures}
|
||||
selectionMode={selectionMode}
|
||||
@@ -1555,13 +1612,13 @@ export default function PortalPage() {
|
||||
<div className="fixed inset-0 z-[100] bg-background overflow-auto flex flex-col">
|
||||
{/* Header */}
|
||||
<Tabs defaultValue="rgi" className="flex-1 flex flex-col gap-0">
|
||||
<header className="border-b bg-background/95 backdrop-blur-sm px-4 py-2 flex items-center gap-4 shrink-0">
|
||||
<h1 className="text-lg font-bold whitespace-nowrap">Portal Cadastral</h1>
|
||||
<header className="border-b bg-background/95 backdrop-blur-sm px-3 sm:px-4 py-2 flex items-center gap-2 sm:gap-4 shrink-0">
|
||||
<h1 className="text-sm sm:text-lg font-bold whitespace-nowrap">Portal</h1>
|
||||
<TabsList className="h-8">
|
||||
<TabsTrigger value="rgi" className="text-xs px-3 h-7">
|
||||
Documente RGI
|
||||
<TabsTrigger value="rgi" className="text-xs px-2 sm:px-3 h-7">
|
||||
RGI
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="harta" className="text-xs px-3 h-7">
|
||||
<TabsTrigger value="harta" className="text-xs px-2 sm:px-3 h-7">
|
||||
Harta
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
Reference in New Issue
Block a user