Files
Investidor-V2/resources/js/components/opportunity-parcel-geometry.tsx
T
nala 01405cbf8a
tests / ci (push) Canceled after 0s
oportunidades 5
2026-10-10 14:15:28 +02:00

225 lines
10 KiB
TypeScript

import InteractiveInvestmentMap from "./interactive-investment-map";
import { control } from "@/pages/management/shared";
import type { MultiPolygon } from "geojson";
type Point = [number | null, number | null];
export default function OpportunityParcelGeometry({
value,
onChange,
}: {
value: unknown;
onChange: (v: unknown) => void;
}) {
const data =
value && typeof value === "object"
? (value as { coordinates?: Point[][][] })
: {};
const polygons = Array.isArray(data.coordinates) ? data.coordinates : [];
function replace(p: number, r: number, points: Point[]) {
onChange({
type: "MultiPolygon",
coordinates: polygons.map((poly, i) =>
i === p
? poly.map((ring, j) => (j === r ? points : ring))
: poly,
),
});
}
const valid =
polygons.length > 0 &&
polygons.every(
(poly) =>
poly.length > 0 &&
poly.every(
(ring) =>
ring.length >= 4 &&
ring.every(
(point) =>
point.length === 2 &&
point.every(
(c) =>
typeof c === "number" &&
Number.isFinite(c),
),
) &&
ring[0][0] === ring.at(-1)?.[0] &&
ring[0][1] === ring.at(-1)?.[1],
),
);
return (
<section className="space-y-2">
<p>
Indique longitude e latitude do perímetro declarado. O limite
administrativo serve de contexto; não define a parcela.
</p>
{polygons.map((poly, p) => (
<fieldset key={p} className="rounded border p-2">
<legend>Polígono {p + 1}</legend>
{poly.map((ring, r) => (
<section key={r}>
<h4>
{r === 0
? "Anel exterior"
: `Anel interior ${r}`}
</h4>
{ring.map((point, i) => (
<div key={i} className="flex flex-wrap gap-2">
{[0, 1].map((axis) => (
<label key={axis}>
{axis === 0
? "Longitude"
: "Latitude"}
<input
className={control}
type="number"
step="any"
min={axis === 0 ? -180 : -90}
max={axis === 0 ? 180 : 90}
value={point[axis] ?? ""}
onChange={(e) =>
replace(
p,
r,
ring.map((v, j) =>
j === i
? (v.map(
(c, k) =>
k ===
axis
? e
.target
.value ===
""
? null
: Number(
e
.target
.value,
)
: c,
) as Point)
: v,
),
)
}
/>
</label>
))}
<button
type="button"
onClick={() =>
replace(
p,
r,
ring.filter((_, j) => j !== i),
)
}
>
Retirar vértice {i + 1}
</button>
</div>
))}
<button
type="button"
className="underline mr-4"
onClick={() =>
replace(p, r, [...ring, [null, null]])
}
>
Adicionar vértice
</button>
<button
type="button"
className="underline mr-4"
disabled={
!ring.length ||
ring[0].some((c) => c === null)
}
onClick={() =>
replace(p, r, [
...ring,
[...ring[0]] as Point,
])
}
>
Fechar anel
</button>
<button
type="button"
onClick={() =>
onChange({
type: "MultiPolygon",
coordinates: polygons.map((v, i) =>
i === p
? v.filter((_, j) => j !== r)
: v,
),
})
}
>
Retirar anel
</button>
</section>
))}
<button
type="button"
className="underline mr-4"
onClick={() =>
onChange({
type: "MultiPolygon",
coordinates: polygons.map((v, i) =>
i === p ? [...v, []] : v,
),
})
}
>
Adicionar anel interior
</button>
<button
type="button"
onClick={() =>
onChange({
type: "MultiPolygon",
coordinates: polygons.filter((_, i) => i !== p),
})
}
>
Retirar polígono
</button>
</fieldset>
))}
<button
type="button"
className="underline"
onClick={() =>
onChange({
type: "MultiPolygon",
coordinates: [...polygons, [[]]],
})
}
>
Adicionar polígono
</button>
{valid && (
<InteractiveInvestmentMap
points={[]}
contextOnly
areas={[
{
purpose: "declared_parcel",
title: "Perímetro declarado pelo Promotor",
source_reference:
"Declaração do Promotor; revisão cadastral pendente",
url: "#",
geometry: {
type: "MultiPolygon",
coordinates: polygons as [number, number][][][],
} as MultiPolygon,
},
]}
/>
)}
</section>
);
}