225 lines
10 KiB
TypeScript
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>
|
|
);
|
|
}
|