forked from Transparency/kgroad-frontend2
308 lines
9.0 KiB
TypeScript
308 lines
9.0 KiB
TypeScript
"use client";
|
||
|
||
import "./ReportForm.scss";
|
||
import "leaflet/dist/leaflet.css";
|
||
import Image from "next/image";
|
||
import {
|
||
MapContainer,
|
||
Marker,
|
||
Popup,
|
||
TileLayer,
|
||
useMapEvents,
|
||
} from "react-leaflet";
|
||
import clip from "./icons/clip.svg";
|
||
import arrow_right from "./icons/arrow-right.svg";
|
||
import pin_image from "./icons/pin-image.svg";
|
||
import { ChangeEventHandler, useState } from "react";
|
||
import { Icon } from "leaflet";
|
||
import pin_icon from "./icons/pin_icon.svg";
|
||
import axios, { AxiosError } from "axios";
|
||
import { apiInstance } from "@/shared/config/apiConfig";
|
||
import { useSession } from "next-auth/react";
|
||
import { IDisplayMap } from "@/shared/types/map-type";
|
||
import Loader from "@/shared/ui/components/Loader/Loader";
|
||
import ReportMessage from "@/features/ReportMessage/ReportMessage";
|
||
|
||
interface ILatLng {
|
||
lat: number;
|
||
lng: number;
|
||
}
|
||
|
||
const ReportForm = () => {
|
||
const session = useSession();
|
||
const [latLng, setLatLng] = useState<ILatLng[]>([]);
|
||
const [displayLatLng, setDisplayLatLng] = useState<string[]>([]);
|
||
const [images, setImages] = useState<File[]>([]);
|
||
const [showMessage, setShowMessage] = useState<boolean>(false);
|
||
const position = {
|
||
lat: 42.8746,
|
||
lng: 74.606,
|
||
};
|
||
const [loader, setLoader] = useState<boolean>(false);
|
||
const [locationWarning, setLocationWarning] = useState<string>("");
|
||
const [descriptionWarning, setDescriptionWarning] =
|
||
useState<string>("");
|
||
const [imageWarning, setImageWarning] = useState<string>("");
|
||
const [error, setError] = useState<string>("");
|
||
|
||
const customIcon = new Icon({
|
||
iconUrl: pin_icon.src,
|
||
iconSize: [32, 32],
|
||
});
|
||
|
||
const handleImages: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||
if (e.target.files) {
|
||
const getArrayFromObject = Array.from(e.target.files);
|
||
setImages(getArrayFromObject);
|
||
}
|
||
};
|
||
|
||
const deleteImage = (name: string) => {
|
||
setImages((prev) => prev.filter((img) => img.name !== name));
|
||
};
|
||
|
||
const createReport: React.MouseEventHandler<
|
||
HTMLFormElement
|
||
> = async (e) => {
|
||
e.preventDefault();
|
||
const formData = new FormData(e.currentTarget);
|
||
|
||
if (displayLatLng.length === 0 || latLng.length === 0) {
|
||
setDescriptionWarning("");
|
||
setImageWarning("");
|
||
setError("");
|
||
setLocationWarning("Выберите хотя бы одну точку!");
|
||
return;
|
||
}
|
||
|
||
if (!formData.get("description")) {
|
||
setLocationWarning("");
|
||
setImageWarning("");
|
||
setError("");
|
||
|
||
setDescriptionWarning("Пожалуйста опишите свое обращение");
|
||
return;
|
||
}
|
||
|
||
if (images.length === 0) {
|
||
setLocationWarning("");
|
||
setDescriptionWarning("");
|
||
setError("");
|
||
|
||
setImageWarning("Загрузите по меньшей мере одну фотографию");
|
||
return;
|
||
}
|
||
|
||
setLocationWarning("");
|
||
setDescriptionWarning("");
|
||
setError("");
|
||
setImageWarning("");
|
||
|
||
const Authorization = `Bearer ${session.data?.access_token}`;
|
||
const config = {
|
||
headers: {
|
||
Authorization,
|
||
},
|
||
};
|
||
|
||
images.forEach((image) => {
|
||
formData.append("image", image);
|
||
});
|
||
formData.append("latitude1", latLng[0].lat.toString());
|
||
formData.append("longitude1", latLng[0].lng.toString());
|
||
if (latLng.length === 2) {
|
||
formData.append("latitude2", latLng[1].lat.toString());
|
||
formData.append("longitude2", latLng[1].lng.toString());
|
||
}
|
||
|
||
try {
|
||
setLoader(true);
|
||
const res = await apiInstance.post(
|
||
"/report/create/",
|
||
formData,
|
||
config
|
||
);
|
||
|
||
if ([200, 201].includes(res.status)) {
|
||
setShowMessage(true);
|
||
}
|
||
} catch (error: unknown) {
|
||
if (error instanceof AxiosError) {
|
||
console.log(error);
|
||
setError(error.message);
|
||
} else {
|
||
setError("Произошла непредвиденная ошибка");
|
||
}
|
||
} finally {
|
||
setLoader(false);
|
||
}
|
||
};
|
||
|
||
const MapPins = (e: any) => {
|
||
useMapEvents({
|
||
click(e) {
|
||
if (latLng.length < 2) {
|
||
setLatLng([...latLng, e.latlng]);
|
||
|
||
axios
|
||
.get(
|
||
`https://nominatim.openstreetmap.org/reverse?lat=${e.latlng.lat}&lon=${e.latlng.lng}&format=json`
|
||
)
|
||
.then((res) => {
|
||
console.log(res.data);
|
||
if (res.data.address.country_code !== "kg") {
|
||
setLocationWarning("Выберите точку в Кыргызстане");
|
||
return;
|
||
} else if (res.data.address.road === undefined) {
|
||
setLocationWarning("Выберите точку на дороге");
|
||
return;
|
||
}
|
||
setDisplayLatLng([
|
||
...displayLatLng,
|
||
res.data.display_name,
|
||
]);
|
||
})
|
||
.catch((error) => console.log(error));
|
||
}
|
||
},
|
||
});
|
||
|
||
const removeMarker = (latLng: { lat: number; lng: number }) => {
|
||
setLatLng((prev) => prev.filter((l) => l !== latLng));
|
||
|
||
axios
|
||
.get<IDisplayMap>(
|
||
`https://nominatim.openstreetmap.org/reverse?lat=${latLng.lat}&lon=${latLng.lng}&format=json`
|
||
)
|
||
.then((res) =>
|
||
setDisplayLatLng((prev) =>
|
||
prev.filter((loc) => loc !== res.data.display_name)
|
||
)
|
||
)
|
||
.catch((error) => console.log(error));
|
||
};
|
||
|
||
return latLng.map((l) => (
|
||
<Marker
|
||
eventHandlers={{
|
||
click: () => {
|
||
removeMarker(l);
|
||
},
|
||
}}
|
||
key={l.lat}
|
||
icon={customIcon}
|
||
position={l}
|
||
/>
|
||
));
|
||
};
|
||
|
||
return (
|
||
<form onSubmit={createReport} className="report-form">
|
||
<div className="report-form__input">
|
||
<label>Адрес</label>
|
||
<input
|
||
value={displayLatLng.join("; ")}
|
||
disabled
|
||
placeholder="Отметьте точки на карте"
|
||
type="text"
|
||
/>
|
||
{locationWarning ? <p>{locationWarning}</p> : null}
|
||
</div>
|
||
|
||
<div className="report-form__map">
|
||
<MapContainer
|
||
center={position}
|
||
zoom={14}
|
||
scrollWheelZoom={false}
|
||
>
|
||
<TileLayer
|
||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||
/>
|
||
<MapPins />
|
||
</MapContainer>
|
||
|
||
<div className="report-form__info">
|
||
<h4>Как отметить участок дороги? </h4>
|
||
<Image src={pin_image} alt="Pin Image" />
|
||
<p>
|
||
Поставьте булавку и начните рисовать участок дороги{" "}
|
||
<span>
|
||
(он может состоять из любого количества ломаных линий)
|
||
</span>
|
||
</p>
|
||
<p>Чтобы удалить отрезок нажмите на точки повторно.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="report-form__input">
|
||
<label>Добавьте описание проблемы</label>
|
||
<textarea name="description" placeholder="Введите описание" />
|
||
{descriptionWarning ? <p>{descriptionWarning}</p> : null}
|
||
</div>
|
||
|
||
<div className="report-form__add-images">
|
||
<label>Добавьте фотографии</label>
|
||
<p>
|
||
Загрузите до 5 фотографии, связанные с дорогой, которую Вы
|
||
хотите отметить. Фотографии помогут лучше понять проблему.
|
||
</p>
|
||
<label
|
||
className="report-form__upload"
|
||
htmlFor="report-form-upload"
|
||
>
|
||
<Image src={clip} alt="Paper Clip Icon" />
|
||
Прикрепить файл
|
||
<span>(до 5 МБ)</span>
|
||
</label>
|
||
{imageWarning ? (
|
||
<p className="report-form__add-images-error">
|
||
{imageWarning}
|
||
</p>
|
||
) : null}
|
||
|
||
<input
|
||
onChange={handleImages}
|
||
multiple
|
||
type="file"
|
||
id="report-form-upload"
|
||
/>
|
||
{images.length ? (
|
||
<div className="report-form__images">
|
||
{images.map((image) => (
|
||
<div key={image.name}>
|
||
<img
|
||
src={URL.createObjectURL(image)}
|
||
alt="Report Image"
|
||
/>
|
||
<button onClick={() => deleteImage(image.name)}>
|
||
удалить
|
||
</button>
|
||
</div>
|
||
))}
|
||
</div>
|
||
) : null}
|
||
</div>
|
||
<button disabled={loader} type="submit">
|
||
{loader ? (
|
||
<Loader />
|
||
) : (
|
||
<>
|
||
Отправить на модерацию
|
||
<Image src={arrow_right} alt="Arrow Right Icon" />
|
||
</>
|
||
)}
|
||
</button>
|
||
{error ? <p className="report-form__error">{error}</p> : null}
|
||
{showMessage ? (
|
||
<ReportMessage
|
||
setShowMessage={setShowMessage}
|
||
showMessage={showMessage}
|
||
/>
|
||
) : null}
|
||
</form>
|
||
);
|
||
};
|
||
|
||
export default ReportForm;
|