forked from Abdulbari/sgeUpdated
Add 'sge-frontend/' from commit '5fa787e054b25ac53edc7ff0275ea7960a709401'
git-subtree-dir: sge-frontend git-subtree-mainline:876c278ac4git-subtree-split:5fa787e054
This commit is contained in:
718
sge-frontend/src/views/Areas/Areas.js
Normal file
718
sge-frontend/src/views/Areas/Areas.js
Normal file
@@ -0,0 +1,718 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ChevronDown, Edit, MoreVertical, Plus, Trash } from "react-feather";
|
||||
import ReactPaginate from "react-paginate";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useSnackbar } from "notistack";
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Input,
|
||||
Label,
|
||||
Row,
|
||||
Col,
|
||||
Button,
|
||||
Modal,
|
||||
ModalHeader,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
UncontrolledDropdown,
|
||||
DropdownToggle,
|
||||
DropdownMenu,
|
||||
DropdownItem,
|
||||
} from "reactstrap";
|
||||
import { default as SweetAlert } from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import DataTable from "react-data-table-component";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Select from "react-select";
|
||||
import {
|
||||
getAreasWithPaginate,
|
||||
addArea,
|
||||
updateArea,
|
||||
deleteArea,
|
||||
} from "../../redux/actions/areas";
|
||||
import { getCities } from "../../redux/actions/cities";
|
||||
import { getCity } from "../../redux/actions/city";
|
||||
import { getDistrict } from "../../redux/actions/district";
|
||||
import { customFilterForSelect } from "../../utility/Utils";
|
||||
import { permissionCheck } from "../../components/permission-check";
|
||||
|
||||
const Areas = () => {
|
||||
const { t } = useTranslation();
|
||||
const initialColumns = [
|
||||
{
|
||||
name: t("Areas.areaName"),
|
||||
selector: (row) => row.tag,
|
||||
sortable: true,
|
||||
minWidth: "250px",
|
||||
},
|
||||
{
|
||||
name: t("Areas.countryName"),
|
||||
selector: (row) => row.countries,
|
||||
sortable: true,
|
||||
minWidth: "250px",
|
||||
cell: (row) => {
|
||||
// Collect all possible country names
|
||||
const countryNames = [
|
||||
row.neighborhoods?.[0]?.district?.city?.country?.name,
|
||||
row.districts?.[0]?.city?.country?.name,
|
||||
row.cities?.[0]?.country?.name,
|
||||
row.countries?.[0]?.name,
|
||||
].filter(Boolean);
|
||||
// Remove duplicates
|
||||
const uniqueCountryNames = countryNames.filter(
|
||||
(v, i, a) => a.indexOf(v) === i
|
||||
);
|
||||
return (
|
||||
<span style={{ display: "flex", flexDirection: "column" }}>
|
||||
{uniqueCountryNames.map((name, idx) => (
|
||||
<span key={idx}>{name}</span>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: t("Areas.areas"),
|
||||
selector: (row) => row.areas, // Bura boştu "".
|
||||
sortable: true,
|
||||
minWidth: "350px",
|
||||
cell: (row) => {
|
||||
// Collect all area strings
|
||||
const areaStrings = [
|
||||
...(row.neighborhoods?.map((neighborhood) =>
|
||||
neighborhood?.name +
|
||||
" - " +
|
||||
neighborhood?.district?.name +
|
||||
" - " +
|
||||
neighborhood?.district?.city?.name
|
||||
) || []),
|
||||
...(row.districts?.map((district) =>
|
||||
district?.name + " - " + district?.city?.name
|
||||
) || []),
|
||||
...(row.cities?.map((city) => city?.name) || [])
|
||||
].filter(Boolean);
|
||||
// Remove duplicates (case-insensitive, trimmed)
|
||||
const uniqueAreaStrings = areaStrings.filter((v, i, a) =>
|
||||
a.findIndex(x => x && x.trim().toLowerCase() === v.trim().toLowerCase()) === i
|
||||
);
|
||||
return (
|
||||
<span style={{ display: "flex", flexDirection: "column" }}>
|
||||
{uniqueAreaStrings.map((str, idx) => (
|
||||
<span key={idx}>{str}</span>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: t("Actions"),
|
||||
allowOverflow: false,
|
||||
maxWidth: "150px",
|
||||
cell: (row) => {
|
||||
return (
|
||||
<div className="d-flex">
|
||||
<UncontrolledDropdown>
|
||||
<DropdownToggle className="pl-1" tag="span">
|
||||
<MoreVertical size={15} />
|
||||
</DropdownToggle>
|
||||
<DropdownMenu container={"body"} end>
|
||||
{permissionCheck("area_update") && (
|
||||
<DropdownItem
|
||||
tag="a"
|
||||
className="w-100"
|
||||
onClick={() => handleEditArea(row)}
|
||||
>
|
||||
<Edit size={15} />
|
||||
<span className="align-middle ml-50">
|
||||
{t("Cruds.edit")}
|
||||
</span>
|
||||
</DropdownItem>
|
||||
)}
|
||||
{permissionCheck("area_delete") && (
|
||||
<DropdownItem
|
||||
tag="a"
|
||||
className="w-100"
|
||||
onClick={() => handleDeleteArea(row)}
|
||||
>
|
||||
<Trash size={15} />
|
||||
<span className="align-middle ml-50">
|
||||
{t("Cruds.delete")}
|
||||
</span>
|
||||
</DropdownItem>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
</UncontrolledDropdown>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (!(permissionCheck("area_update") || permissionCheck("area_delete"))) {
|
||||
const updatedColumns = initialColumns.filter(
|
||||
(column) => column.name !== ("Aksiyonlar" || "Actions")
|
||||
);
|
||||
setServerSideColumns(updatedColumns);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const Swal = withReactContent(SweetAlert);
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(5);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
|
||||
const [serverSideColumns, setServerSideColumns] = useState(initialColumns);
|
||||
|
||||
const citiesStore = useSelector((state) => state.cities);
|
||||
const [cities, setCities] = useState([]);
|
||||
const cityStore = useSelector((state) => state.city);
|
||||
const [selectedCity, setSelectedCity] = useState();
|
||||
|
||||
const [districts, setDistricts] = useState([]);
|
||||
const districtStore = useSelector((state) => state.district);
|
||||
const [selectedDistrict, setSelectedDistrict] = useState();
|
||||
|
||||
const [neighborhoods, setNeighborhoods] = useState([]);
|
||||
|
||||
const areasStore = useSelector((state) => state.areas);
|
||||
const [areas, setAreas] = useState([]);
|
||||
const [showAddAreaModal, setShowAddAreaModal] = useState(false);
|
||||
const [editingAreaData, setEditingAreaData] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getAreasWithPaginate({ currentPage, rowsPerPage }));
|
||||
}, [currentPage, rowsPerPage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (areasStore?.areasWithPaginate?.content) {
|
||||
setAreas(areasStore?.areasWithPaginate?.content);
|
||||
}
|
||||
}, [areasStore.areasWithPaginate?.content]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getCities());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedCity != undefined) {
|
||||
dispatch(getCity(selectedCity?.[selectedCity?.length - 1]?.value));
|
||||
}
|
||||
}, [selectedCity]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedDistrict != undefined && selectedDistrict.length != 0) {
|
||||
dispatch(
|
||||
getDistrict(selectedDistrict?.[selectedDistrict?.length - 1]?.value)
|
||||
);
|
||||
}
|
||||
}, [selectedDistrict]);
|
||||
|
||||
useEffect(() => {
|
||||
if (citiesStore) {
|
||||
setCities(
|
||||
citiesStore.cities?.map((city) => {
|
||||
return {
|
||||
value: city?.id,
|
||||
label: city?.name,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [citiesStore?.cities.length, citiesStore]);
|
||||
|
||||
useEffect(() => {
|
||||
if (cityStore?.city) {
|
||||
const list =
|
||||
cityStore.city.districts?.map((district) => ({
|
||||
value: district.id,
|
||||
label: `${district.name} - ${cityStore.city.name}`,
|
||||
})) || [];
|
||||
setDistricts((prevDistricts) => [...prevDistricts, ...list]);
|
||||
}
|
||||
}, [selectedCity, cityStore?.city]);
|
||||
|
||||
useEffect(() => {
|
||||
if (districtStore?.district) {
|
||||
const list2 =
|
||||
districtStore.district.neighborhoods?.map((neighborhood) => ({
|
||||
value: neighborhood.id,
|
||||
label: `${neighborhood.name} - ${districtStore.district.name} - ${districtStore.district.city.name}`,
|
||||
})) || [];
|
||||
setNeighborhoods((prevNeighborhoods) => [...prevNeighborhoods, ...list2]);
|
||||
}
|
||||
}, [selectedDistrict, districtStore?.district]);
|
||||
|
||||
const handleFilter = (e) => {
|
||||
setSearchValue(e.target.value);
|
||||
|
||||
if (e.target.value !== "") {
|
||||
setAreas(
|
||||
areasStore.areasWithPaginate?.content.filter((area) =>
|
||||
area.tag
|
||||
.toUpperCase()
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
.includes(
|
||||
e.target.value
|
||||
.toUpperCase()
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setAreas(areasStore.areasWithPaginate?.content);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePagination = (page) => {
|
||||
setCurrentPage(page.selected + 1);
|
||||
setAreas(areasStore.areasWithPaginate?.content);
|
||||
};
|
||||
|
||||
const handlePerPage = (e) => {
|
||||
setCurrentPage(1);
|
||||
setRowsPerPage(parseInt(e.target.value));
|
||||
setAreas(areasStore.areasWithPaginate?.content);
|
||||
};
|
||||
|
||||
const CustomPagination = () => {
|
||||
const count = Number(
|
||||
(
|
||||
areasStore?.areasWithPaginate?.pageInfo?.totalElements / rowsPerPage
|
||||
).toFixed(1)
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactPaginate
|
||||
previousLabel={""}
|
||||
nextLabel={""}
|
||||
breakLabel="..."
|
||||
pageCount={count || 1}
|
||||
marginPagesDisplayed={2}
|
||||
pageRangeDisplayed={2}
|
||||
activeClassName="active"
|
||||
forcePage={currentPage !== 0 ? currentPage - 1 : 0}
|
||||
onPageChange={(page) => handlePagination(page)}
|
||||
pageClassName={"page-item"}
|
||||
nextLinkClassName={"page-link"}
|
||||
nextClassName={"page-item next"}
|
||||
previousClassName={"page-item prev"}
|
||||
previousLinkClassName={"page-link"}
|
||||
pageLinkClassName={"page-link"}
|
||||
breakClassName="page-item"
|
||||
breakLinkClassName="page-link"
|
||||
containerClassName={
|
||||
"pagination react-paginate separated-pagination pagination-sm justify-content-end pr-1 mt-1"
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const onAddAreaButtonPressed = () => {
|
||||
setEditingAreaData({});
|
||||
setNeighborhoods([]);
|
||||
setDistricts([]);
|
||||
setSelectedCity();
|
||||
setSelectedDistrict();
|
||||
dispatch({ type: "GET_CITY", payload: { city: [] } });
|
||||
dispatch({ type: "GET_DISTRICT", payload: { district: [] } });
|
||||
setShowAddAreaModal(true);
|
||||
};
|
||||
const onAddAreaModalButtonPressed = () => {
|
||||
const arr = (
|
||||
editingAreaData?.neighborhoods ||
|
||||
editingAreaData?.districts ||
|
||||
editingAreaData?.cities ||
|
||||
[]
|
||||
).map((s) => s?.value || s.toString());
|
||||
const newAreaData = {
|
||||
id: editingAreaData?.id || null,
|
||||
tag: editingAreaData?.tag,
|
||||
cities:
|
||||
editingAreaData?.districts || !editingAreaData?.cities ? null : arr,
|
||||
districts:
|
||||
editingAreaData?.neighborhoods || !editingAreaData?.districts
|
||||
? null
|
||||
: arr,
|
||||
neighborhoods: editingAreaData?.neighborhoods ? arr : null,
|
||||
};
|
||||
|
||||
const check = JSON.parse(JSON.stringify(newAreaData), (key, value) =>
|
||||
value === null || value === "" ? undefined : value
|
||||
);
|
||||
|
||||
const deleteQuete = (list) => {
|
||||
return list?.map((a) => a?.value || a.toString());
|
||||
};
|
||||
|
||||
const updateAreaData = {
|
||||
id: editingAreaData?.id,
|
||||
tag: editingAreaData?.tag,
|
||||
cities:
|
||||
editingAreaData?.districts?.length != 0 ||
|
||||
editingAreaData?.neighborhoods?.length != 0
|
||||
? []
|
||||
: deleteQuete(editingAreaData?.cities),
|
||||
districts:
|
||||
editingAreaData?.neighborhoods?.length === 0 &&
|
||||
editingAreaData?.districts?.length != 0
|
||||
? deleteQuete(editingAreaData?.districts)
|
||||
: [],
|
||||
neighborhoods:
|
||||
editingAreaData?.neighborhoods?.length != 0
|
||||
? deleteQuete(editingAreaData?.neighborhoods)
|
||||
: [],
|
||||
};
|
||||
const checkUpdateData = JSON.parse(
|
||||
JSON.stringify(updateAreaData),
|
||||
(key, value) => (value === null || value === "" ? undefined : value)
|
||||
);
|
||||
|
||||
dispatch(newAreaData.id ? updateArea(checkUpdateData) : addArea(check))
|
||||
.then(() => {
|
||||
enqueueSnackbar(
|
||||
`${newAreaData.tag}, ${
|
||||
!newAreaData.id
|
||||
? t("Warnings.addedSuccessfully")
|
||||
: t("Warnings.updatedSuccessfully")
|
||||
}`,
|
||||
{
|
||||
variant: "success",
|
||||
}
|
||||
);
|
||||
setEditingAreaData(null);
|
||||
setShowAddAreaModal(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
enqueueSnackbar(
|
||||
`${newAreaData.tag} ${
|
||||
!newAreaData.id
|
||||
? t("Warnings.addedFail")
|
||||
: t("Warnings.updatedFail")
|
||||
}`,
|
||||
{
|
||||
variant: "error",
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
const renderAreaModal = () => {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={showAddAreaModal}
|
||||
toggle={() => {
|
||||
setShowAddAreaModal(!showAddAreaModal);
|
||||
//setEditingAreaData(null);
|
||||
}}
|
||||
className="modal-dialog-centered"
|
||||
>
|
||||
<ModalHeader
|
||||
toggle={() => {
|
||||
setShowAddAreaModal(!showAddAreaModal);
|
||||
//setEditingAreaData(null);
|
||||
}}
|
||||
>
|
||||
{editingAreaData?.id ? editingAreaData?.tag : t("Areas.addArea")}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" for="area-tag">
|
||||
{t("Areas.areaName")}:
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="area-tag"
|
||||
placeholder=""
|
||||
defaultValue={editingAreaData?.tag}
|
||||
onChange={(e) => {
|
||||
setEditingAreaData({
|
||||
...editingAreaData,
|
||||
tag: e.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" for="city-name">
|
||||
{t("Areas.city")}:
|
||||
</Label>
|
||||
<Select
|
||||
id="city-name"
|
||||
placeholder=""
|
||||
options={cities}
|
||||
className="react-select"
|
||||
isClearable={false}
|
||||
closeMenuOnSelect={false}
|
||||
isMulti
|
||||
filterOption={customFilterForSelect}
|
||||
defaultValue={editingAreaData?.cities}
|
||||
onChange={(value) => {
|
||||
setSelectedCity(value);
|
||||
setEditingAreaData({
|
||||
...editingAreaData,
|
||||
cities: value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" for="district-name">
|
||||
{t("Areas.district")}:
|
||||
</Label>
|
||||
<Select
|
||||
id="district-name"
|
||||
isClearable={false}
|
||||
closeMenuOnSelect={false}
|
||||
isMulti
|
||||
className="react-select"
|
||||
placeholder=""
|
||||
filterOption={customFilterForSelect}
|
||||
options={districts}
|
||||
defaultValue={editingAreaData?.districts}
|
||||
onChange={(value) => {
|
||||
setSelectedDistrict(value);
|
||||
setEditingAreaData({
|
||||
...editingAreaData,
|
||||
districts: value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" for="neighborhoods-name">
|
||||
{t("Areas.neighborhood")}:
|
||||
</Label>
|
||||
<Select
|
||||
id="neighborhoods-name"
|
||||
isClearable={false}
|
||||
closeMenuOnSelect={false}
|
||||
isMulti
|
||||
className="react-select"
|
||||
placeholder=""
|
||||
filterOption={customFilterForSelect}
|
||||
options={neighborhoods}
|
||||
defaultValue={editingAreaData?.neighborhoods}
|
||||
onChange={(value) => {
|
||||
setEditingAreaData({
|
||||
...editingAreaData,
|
||||
neighborhoods: value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={(e) => {
|
||||
onAddAreaModalButtonPressed();
|
||||
}}
|
||||
>
|
||||
{editingAreaData?.id ? t("Cruds.update") : t("Cruds.save")}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const handleDeleteArea = async (selectedArea) => {
|
||||
const result = await Swal.fire({
|
||||
title: ` ${t("Warnings.sureForDelete")}`,
|
||||
text: t("Warnings.notUndone"),
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonText: t("Cruds.delete"),
|
||||
cancelButtonText: t("Cruds.cancel"),
|
||||
customClass: {
|
||||
confirmButton: "btn btn-primary",
|
||||
cancelButton: "btn btn-danger ml-1",
|
||||
},
|
||||
buttonsStyling: false,
|
||||
});
|
||||
if (result.value !== null && result.value === true) {
|
||||
dispatch(deleteArea(selectedArea.id.trim()))
|
||||
.then(() => {
|
||||
enqueueSnackbar(
|
||||
`${selectedArea.tag} ${t("Warnings.deletedSuccessfully")}`,
|
||||
{
|
||||
variant: "success",
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch((e) => {
|
||||
enqueueSnackbar(`${selectedArea.tag} ${t("Warnings.deletedFail")}`, {
|
||||
variant: "error",
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditArea = (selectedArea) => {
|
||||
const selectedCities = (selectedArea.cities || []).map((city) => ({
|
||||
value: city?.id,
|
||||
label: city?.name,
|
||||
}));
|
||||
|
||||
const selectedDistricts = (selectedArea.districts || []).map(
|
||||
(district) => ({
|
||||
value: district?.id,
|
||||
label: district?.name,
|
||||
})
|
||||
);
|
||||
|
||||
const selectedCitiesOfDistricts = (selectedArea.districts || [])
|
||||
.map((district) => ({
|
||||
value: district?.city?.id,
|
||||
label: district?.city?.name,
|
||||
}))
|
||||
.filter(
|
||||
(tag, index, array) =>
|
||||
array.findIndex(
|
||||
(t) => t.value === tag.value && t.label === tag.label
|
||||
) === index
|
||||
);
|
||||
|
||||
const selectedNeighborhoods = (selectedArea.neighborhoods || []).map(
|
||||
(neighborhood) => ({
|
||||
value: neighborhood?.id,
|
||||
label:
|
||||
neighborhood?.name +
|
||||
"-" +
|
||||
neighborhood?.district?.name +
|
||||
"-" +
|
||||
neighborhood?.district?.city?.name,
|
||||
})
|
||||
);
|
||||
|
||||
const selectedCitiesOfNeighborhoods = (selectedArea.neighborhoods || [])
|
||||
.map((neighborhood) => ({
|
||||
value: neighborhood?.district?.city?.id,
|
||||
label: neighborhood?.district?.city?.name,
|
||||
}))
|
||||
.filter(
|
||||
(tag, index, array) =>
|
||||
array.findIndex(
|
||||
(t) => t.value === tag.value && t.label === tag.label
|
||||
) === index
|
||||
);
|
||||
|
||||
const selectedDistrictsOfNeighborhoods = (selectedArea.neighborhoods || [])
|
||||
.map((neighborhood) => ({
|
||||
value: neighborhood?.district?.id,
|
||||
label:
|
||||
neighborhood?.district?.name +
|
||||
"-" +
|
||||
neighborhood?.district?.city?.name,
|
||||
}))
|
||||
.filter(
|
||||
(tag, index, array) =>
|
||||
array.findIndex(
|
||||
(t) => t.value === tag.value && t.label === tag.label
|
||||
) === index
|
||||
);
|
||||
|
||||
setShowAddAreaModal(true);
|
||||
|
||||
setEditingAreaData({
|
||||
...selectedArea,
|
||||
cities:
|
||||
selectedCitiesOfNeighborhoods.length === 0
|
||||
? selectedCitiesOfDistricts.length === 0
|
||||
? selectedCities
|
||||
: selectedCitiesOfDistricts
|
||||
: selectedCitiesOfNeighborhoods,
|
||||
districts:
|
||||
selectedDistrictsOfNeighborhoods.length === 0
|
||||
? selectedDistricts
|
||||
: selectedDistrictsOfNeighborhoods,
|
||||
neighborhoods: selectedNeighborhoods,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "2%" }}>
|
||||
<Card>
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle tag="h4">{t("Areas.areas")}</CardTitle>
|
||||
{permissionCheck("area_create") && (
|
||||
<Button
|
||||
className="ml-2"
|
||||
color="primary"
|
||||
onClick={onAddAreaButtonPressed}
|
||||
>
|
||||
<Plus size={15} />
|
||||
<span className="align-middle ml-50">{t("Areas.addArea")}</span>
|
||||
</Button>
|
||||
)}
|
||||
</CardHeader>
|
||||
<Row className="mx-0 mt-1 mb-50">
|
||||
<Col sm="6" md="2">
|
||||
<div className="d-flex align-items-center">
|
||||
<Label for="sort-select">{t("Show")}</Label>
|
||||
<Input
|
||||
className="ml-1 dataTable-select"
|
||||
type="select"
|
||||
id="sort-select"
|
||||
value={rowsPerPage}
|
||||
onChange={(e) => handlePerPage(e)}
|
||||
>
|
||||
<option value={5}>5</option>
|
||||
<option value={10}>10</option>
|
||||
<option value={25}>25</option>
|
||||
<option value={50}>50</option>
|
||||
<option value={75}>75</option>
|
||||
<option value={100}>100</option>
|
||||
</Input>
|
||||
</div>
|
||||
</Col>
|
||||
<Col
|
||||
className="d-flex align-items-center justify-content-end mt-sm-0 mt-1 ml-md-auto"
|
||||
sm="6"
|
||||
md="3"
|
||||
>
|
||||
<Label className="mr-1" for="search-input">
|
||||
{t("Filter")}
|
||||
</Label>
|
||||
<Input
|
||||
className="dataTable-filter"
|
||||
type="text"
|
||||
bsSize="sm"
|
||||
id="search-input"
|
||||
value={searchValue}
|
||||
onChange={handleFilter}
|
||||
placeholder={t("ByName")}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<DataTable
|
||||
noHeader
|
||||
pagination
|
||||
paginationServer
|
||||
className="react-dataTable"
|
||||
columns={serverSideColumns}
|
||||
sortIcon={<ChevronDown size={10} />}
|
||||
//onSort={onSort}
|
||||
paginationComponent={CustomPagination}
|
||||
data={[...areas]}
|
||||
noDataComponent={
|
||||
<p className="p-2">
|
||||
{t("Areas.area")}
|
||||
{t("Warnings.notFound")}
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
{renderAreaModal()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Areas;
|
||||
192
sge-frontend/src/views/Areas/Cities.js
Normal file
192
sge-frontend/src/views/Areas/Cities.js
Normal file
@@ -0,0 +1,192 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ChevronDown } from "react-feather";
|
||||
import ReactPaginate from "react-paginate";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Input,
|
||||
Label,
|
||||
Row,
|
||||
Col,
|
||||
} from "reactstrap";
|
||||
import DataTable from "react-data-table-component";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { paginateCities } from "../../redux/actions/cities";
|
||||
|
||||
const Cities = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const serverSideColumns = [
|
||||
{
|
||||
name: t("Areas.cityName"),
|
||||
selector: (row) => row.name,
|
||||
sortable: true,
|
||||
minWidth: "350px",
|
||||
},
|
||||
{
|
||||
name: t("Areas.countryName"),
|
||||
selector: (row) => row.country,
|
||||
sortable: true,
|
||||
minWidth: "350px",
|
||||
cell: (row) => <span>{row.country.name}</span>,
|
||||
},
|
||||
];
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(5);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
|
||||
const citiesStore = useSelector((state) => state.cities);
|
||||
const [cities, setCities] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(paginateCities({ currentPage, rowsPerPage }));
|
||||
}, [currentPage, rowsPerPage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (citiesStore?.paginateCities?.content) {
|
||||
setCities(citiesStore?.paginateCities?.content);
|
||||
}
|
||||
}, [
|
||||
citiesStore?.paginateCities?.content?.length,
|
||||
citiesStore?.paginateCities,
|
||||
]);
|
||||
|
||||
// const handleFilter = (e) => {
|
||||
// setSearchValue(e.target.value);
|
||||
|
||||
// if (e.target.value !== "") {
|
||||
// setCities(
|
||||
// citiesStore.cities
|
||||
// .filter((city) =>
|
||||
// city.name.toLowerCase().includes(e.target.value.toLowerCase())
|
||||
// )
|
||||
// .slice(
|
||||
// currentPage * rowsPerPage - rowsPerPage,
|
||||
// currentPage * rowsPerPage
|
||||
// )
|
||||
// );
|
||||
// } else {
|
||||
// setCities(
|
||||
// citiesStore.cities.slice(
|
||||
// currentPage * rowsPerPage - rowsPerPage,
|
||||
// currentPage * rowsPerPage
|
||||
// )
|
||||
// );
|
||||
// }
|
||||
// };
|
||||
|
||||
const handlePagination = (page) => {
|
||||
setCurrentPage(page.selected + 1);
|
||||
setCities(citiesStore.paginateCities?.content);
|
||||
};
|
||||
|
||||
const handlePerPage = (e) => {
|
||||
setCurrentPage(1);
|
||||
setRowsPerPage(parseInt(e.target.value));
|
||||
setCities(citiesStore.paginateCities?.content);
|
||||
};
|
||||
|
||||
const CustomPagination = () => {
|
||||
const count = Number(
|
||||
(
|
||||
citiesStore.paginateCities?.pageInfo?.totalElements / rowsPerPage
|
||||
).toFixed(1)
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactPaginate
|
||||
previousLabel={""}
|
||||
nextLabel={""}
|
||||
breakLabel="..."
|
||||
pageCount={count || 1}
|
||||
marginPagesDisplayed={2}
|
||||
pageRangeDisplayed={2}
|
||||
activeClassName="active"
|
||||
forcePage={currentPage !== 0 ? currentPage - 1 : 0}
|
||||
onPageChange={(page) => handlePagination(page)}
|
||||
pageClassName={"page-item"}
|
||||
nextLinkClassName={"page-link"}
|
||||
nextClassName={"page-item next"}
|
||||
previousClassName={"page-item prev"}
|
||||
previousLinkClassName={"page-link"}
|
||||
pageLinkClassName={"page-link"}
|
||||
breakClassName="page-item"
|
||||
breakLinkClassName="page-link"
|
||||
containerClassName={
|
||||
"pagination react-paginate separated-pagination pagination-sm justify-content-end pr-1 mt-1"
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "2%" }}>
|
||||
<Card>
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle tag="h4">{t("Areas.cities")}</CardTitle>
|
||||
</CardHeader>
|
||||
<Row className="mx-0 mt-1 mb-50">
|
||||
<Col sm="6" md="2">
|
||||
<div className="d-flex align-items-center">
|
||||
<Label for="sort-select">{t("Show")}</Label>
|
||||
<Input
|
||||
className="ml-1 dataTable-select"
|
||||
type="select"
|
||||
id="sort-select"
|
||||
value={rowsPerPage}
|
||||
onChange={(e) => handlePerPage(e)}
|
||||
>
|
||||
<option value={5}>5</option>
|
||||
<option value={10}>10</option>
|
||||
<option value={25}>25</option>
|
||||
<option value={50}>50</option>
|
||||
<option value={75}>75</option>
|
||||
<option value={100}>100</option>
|
||||
</Input>
|
||||
</div>
|
||||
</Col>
|
||||
{/* <Col
|
||||
className="d-flex align-items-center justify-content-end mt-sm-0 mt-1 ml-md-auto"
|
||||
sm="6"
|
||||
md="3"
|
||||
>
|
||||
<Label className="mr-1" for="search-input">
|
||||
{t("Filter")}
|
||||
</Label>
|
||||
<Input
|
||||
className="dataTable-filter"
|
||||
type="text"
|
||||
bsSize="sm"
|
||||
id="search-input"
|
||||
value={searchValue}
|
||||
onChange={handleFilter}
|
||||
placeholder={t("ByName")}
|
||||
/>
|
||||
</Col> */}
|
||||
</Row>
|
||||
<DataTable
|
||||
noHeader
|
||||
pagination
|
||||
paginationServer
|
||||
className="react-dataTable"
|
||||
columns={serverSideColumns}
|
||||
sortIcon={<ChevronDown size={10} />}
|
||||
paginationComponent={CustomPagination}
|
||||
data={[...cities]}
|
||||
noDataComponent={
|
||||
<p className="p-2">
|
||||
{t("Areas.city")}
|
||||
{t("Warnings.notFound")}
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Cities;
|
||||
215
sge-frontend/src/views/Areas/Countries.js
Normal file
215
sge-frontend/src/views/Areas/Countries.js
Normal file
@@ -0,0 +1,215 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ChevronDown } from "react-feather";
|
||||
import ReactPaginate from "react-paginate";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Input,
|
||||
Label,
|
||||
Row,
|
||||
Col,
|
||||
} from "reactstrap";
|
||||
import DataTable from "react-data-table-component";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getCountries } from "../../redux/actions/country";
|
||||
|
||||
const Countries = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const serverSideColumns = [
|
||||
{
|
||||
name: t("Areas.countryName"),
|
||||
selector: (row) => row.name,
|
||||
sortable: true,
|
||||
minWidth: "350px",
|
||||
},
|
||||
{
|
||||
name: t("Areas.countryCode"),
|
||||
selector: (row) => row.countryCode,
|
||||
sortable: true,
|
||||
minWidth: "350px",
|
||||
},
|
||||
];
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(5);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
|
||||
const countriesStore = useSelector((state) => state.countries);
|
||||
const [countries, setCountries] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getCountries());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (countriesStore) {
|
||||
if (countriesStore.countries.length <= currentPage * rowsPerPage) {
|
||||
setCurrentPage(1);
|
||||
setCountries(countriesStore.countries.slice(0, rowsPerPage));
|
||||
} else {
|
||||
setCountries(
|
||||
countriesStore.countries.slice(
|
||||
currentPage * rowsPerPage - rowsPerPage,
|
||||
currentPage * rowsPerPage
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [countriesStore?.length, countriesStore]);
|
||||
|
||||
const handleFilter = (e) => {
|
||||
setSearchValue(e.target.value);
|
||||
|
||||
if (e.target.value !== "") {
|
||||
setCountries(
|
||||
countriesStore.countries
|
||||
.filter((country) =>
|
||||
country.name
|
||||
.toUpperCase()
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
.includes(
|
||||
e.target.value
|
||||
.toUpperCase()
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
)
|
||||
)
|
||||
.slice(
|
||||
currentPage * rowsPerPage - rowsPerPage,
|
||||
currentPage * rowsPerPage
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setCountries(
|
||||
countriesStore.countries.slice(
|
||||
currentPage * rowsPerPage - rowsPerPage,
|
||||
currentPage * rowsPerPage
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePagination = (page) => {
|
||||
setCurrentPage(page.selected + 1);
|
||||
setCountries(
|
||||
countriesStore.countries.slice(
|
||||
(page.selected + 1) * rowsPerPage - rowsPerPage,
|
||||
(page.selected + 1) * rowsPerPage
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const handlePerPage = (e) => {
|
||||
setRowsPerPage(parseInt(e.target.value));
|
||||
setCountries(
|
||||
countriesStore.countries.slice(
|
||||
currentPage * parseInt(e.target.value) - parseInt(e.target.value),
|
||||
currentPage * parseInt(e.target.value)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const CustomPagination = () => {
|
||||
const count = Number(
|
||||
(countriesStore.countries.length / rowsPerPage).toFixed(1)
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactPaginate
|
||||
previousLabel={""}
|
||||
nextLabel={""}
|
||||
breakLabel="..."
|
||||
pageCount={count || 1}
|
||||
marginPagesDisplayed={2}
|
||||
pageRangeDisplayed={2}
|
||||
activeClassName="active"
|
||||
forcePage={currentPage !== 0 ? currentPage - 1 : 0}
|
||||
onPageChange={(page) => handlePagination(page)}
|
||||
pageClassName={"page-item"}
|
||||
nextLinkClassName={"page-link"}
|
||||
nextClassName={"page-item next"}
|
||||
previousClassName={"page-item prev"}
|
||||
previousLinkClassName={"page-link"}
|
||||
pageLinkClassName={"page-link"}
|
||||
breakClassName="page-item"
|
||||
breakLinkClassName="page-link"
|
||||
containerClassName={
|
||||
"pagination react-paginate separated-pagination pagination-sm justify-content-end pr-1 mt-1"
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "2%" }}>
|
||||
<Card>
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle tag="h4">{t("Areas.countries")}</CardTitle>
|
||||
</CardHeader>
|
||||
<Row className="mx-0 mt-1 mb-50">
|
||||
<Col sm="6" md="2">
|
||||
<div className="d-flex align-items-center">
|
||||
<Label for="sort-select">{t("Show")}</Label>
|
||||
<Input
|
||||
className="ml-1 dataTable-select"
|
||||
type="select"
|
||||
id="sort-select"
|
||||
value={rowsPerPage}
|
||||
onChange={(e) => handlePerPage(e)}
|
||||
>
|
||||
<option value={5}>5</option>
|
||||
<option value={10}>10</option>
|
||||
<option value={25}>25</option>
|
||||
<option value={50}>50</option>
|
||||
<option value={75}>75</option>
|
||||
<option value={100}>100</option>
|
||||
</Input>
|
||||
</div>
|
||||
</Col>
|
||||
<Col
|
||||
className="d-flex align-items-center justify-content-end mt-sm-0 mt-1 ml-md-auto"
|
||||
sm="6"
|
||||
md="3"
|
||||
>
|
||||
<Label className="mr-1" for="search-input">
|
||||
{t("Filter")}
|
||||
</Label>
|
||||
<Input
|
||||
className="dataTable-filter"
|
||||
type="text"
|
||||
bsSize="sm"
|
||||
id="search-input"
|
||||
value={searchValue}
|
||||
onChange={handleFilter}
|
||||
placeholder={t("ByName")}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<DataTable
|
||||
noHeader
|
||||
pagination
|
||||
paginationServer
|
||||
className="react-dataTable"
|
||||
columns={serverSideColumns}
|
||||
sortIcon={<ChevronDown size={10} />}
|
||||
//onSort={onSort}
|
||||
paginationComponent={CustomPagination}
|
||||
data={[...countries]}
|
||||
noDataComponent={
|
||||
<p className="p-2">
|
||||
{t("Areas.country")}
|
||||
{t("Warnings.notFound")}
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Countries;
|
||||
198
sge-frontend/src/views/Areas/Districts.js
Normal file
198
sge-frontend/src/views/Areas/Districts.js
Normal file
@@ -0,0 +1,198 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ChevronDown } from "react-feather";
|
||||
import ReactPaginate from "react-paginate";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Input,
|
||||
Label,
|
||||
Row,
|
||||
Col,
|
||||
} from "reactstrap";
|
||||
import DataTable from "react-data-table-component";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getDistricts } from "../../redux/actions/districts";
|
||||
import { getDistrict } from "../../redux/actions/district";
|
||||
|
||||
const Districts = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const serverSideColumns = [
|
||||
{
|
||||
name: t("Areas.districtName"),
|
||||
selector: (row) => row.name,
|
||||
sortable: true,
|
||||
minWidth: "350px",
|
||||
},
|
||||
{
|
||||
name: t("Areas.cityName"),
|
||||
selector: (row) => row.city,
|
||||
minWidth: "350px",
|
||||
cell: (row) => <span>{row.city.name}</span>,
|
||||
},
|
||||
{
|
||||
name: t("Areas.countryName"),
|
||||
selector: (row) => row.city,
|
||||
minWidth: "350px",
|
||||
cell: (row) => <span>{row.city.country.name}</span>,
|
||||
},
|
||||
];
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(10);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
|
||||
const districtsStore = useSelector((state) => state.districts);
|
||||
const [districts, setDistricts] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getDistricts({ currentPage, rowsPerPage }));
|
||||
}, [currentPage, rowsPerPage]);
|
||||
useEffect(() => {
|
||||
if (districtsStore.districts?.content) {
|
||||
setDistricts(districtsStore.districts?.content);
|
||||
}
|
||||
}, [districtsStore?.districts?.content?.length, districtsStore?.districts]);
|
||||
|
||||
// const handleFilter = (e) => {
|
||||
// setSearchValue(e.target.value);
|
||||
|
||||
// if (e.target.value !== "") {
|
||||
// setCurrentPage(1);
|
||||
// setRowsPerPage(districtsStore.districts?.pageInfo?.totalElements);
|
||||
// setDistricts(
|
||||
// districtsStore.districts?.content
|
||||
// .filter((neighborhood) =>
|
||||
// neighborhood.name
|
||||
// .toLowerCase()
|
||||
// .includes(e.target.value.toLowerCase())
|
||||
// )
|
||||
// .slice(
|
||||
// currentPage * rowsPerPage - rowsPerPage,
|
||||
// currentPage * rowsPerPage
|
||||
// )
|
||||
// );
|
||||
// } else {
|
||||
// setCurrentPage(1);
|
||||
// setRowsPerPage(10);
|
||||
// setDistricts(districtsStore.districts?.content);
|
||||
// }
|
||||
// };
|
||||
|
||||
const handlePagination = (page) => {
|
||||
setCurrentPage(page.selected + 1);
|
||||
setDistricts(districtsStore.districts?.content);
|
||||
};
|
||||
|
||||
const handlePerPage = (e) => {
|
||||
setCurrentPage(1);
|
||||
setRowsPerPage(parseInt(e.target.value));
|
||||
setDistricts(districtsStore.districts?.content);
|
||||
};
|
||||
|
||||
const CustomPagination = () => {
|
||||
const count = Number(
|
||||
(districtsStore.districts?.pageInfo?.totalElements / rowsPerPage).toFixed(
|
||||
1
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactPaginate
|
||||
previousLabel={""}
|
||||
nextLabel={""}
|
||||
breakLabel="..."
|
||||
pageCount={count || 1}
|
||||
marginPagesDisplayed={2}
|
||||
pageRangeDisplayed={2}
|
||||
activeClassName="active"
|
||||
forcePage={currentPage !== 0 ? currentPage - 1 : 0}
|
||||
onPageChange={(page) => handlePagination(page)}
|
||||
pageClassName={"page-item"}
|
||||
nextLinkClassName={"page-link"}
|
||||
nextClassName={"page-item next"}
|
||||
previousClassName={"page-item prev"}
|
||||
previousLinkClassName={"page-link"}
|
||||
pageLinkClassName={"page-link"}
|
||||
breakClassName="page-item"
|
||||
breakLinkClassName="page-link"
|
||||
containerClassName={
|
||||
"pagination react-paginate separated-pagination pagination-sm justify-content-end pr-1 mt-1"
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
let listOfNumber = [10, 25, 50, 75, 100];
|
||||
return (
|
||||
<div style={{ marginTop: "2%" }}>
|
||||
<Card>
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle tag="h4">{t("Areas.districts")}</CardTitle>
|
||||
</CardHeader>
|
||||
<Row className="mx-0 mt-1 mb-50">
|
||||
<Col sm="6" md="2">
|
||||
<div className="d-flex align-items-center">
|
||||
<Label for="sort-select">{t("Show")}</Label>
|
||||
<Input
|
||||
className="ml-1 dataTable-select"
|
||||
type="select"
|
||||
id="sort-select"
|
||||
value={rowsPerPage}
|
||||
onChange={(e) => handlePerPage(e)}
|
||||
>
|
||||
<option value={10}>10</option>
|
||||
<option value={25}>25</option>
|
||||
<option value={50}>50</option>
|
||||
<option value={75}>75</option>
|
||||
<option value={100}>100</option>
|
||||
{!listOfNumber.includes(rowsPerPage) && (
|
||||
<option value={rowsPerPage}>{rowsPerPage}</option>
|
||||
)}
|
||||
</Input>
|
||||
</div>
|
||||
</Col>
|
||||
{/* <Col
|
||||
className="d-flex align-items-center justify-content-end mt-sm-0 mt-1 ml-md-auto"
|
||||
sm="6"
|
||||
md="3"
|
||||
>
|
||||
<Label className="mr-1" for="search-input">
|
||||
{t("Filter")}
|
||||
</Label>
|
||||
<Input
|
||||
className="dataTable-filter"
|
||||
type="text"
|
||||
bsSize="sm"
|
||||
id="search-input"
|
||||
value={searchValue}
|
||||
onChange={handleFilter}
|
||||
placeholder={t("ByName")}
|
||||
/>
|
||||
</Col> */}
|
||||
</Row>
|
||||
<DataTable
|
||||
noHeader
|
||||
pagination
|
||||
paginationServer
|
||||
className="react-dataTable"
|
||||
columns={serverSideColumns}
|
||||
sortIcon={<ChevronDown size={10} />}
|
||||
paginationComponent={CustomPagination}
|
||||
data={[...districts]}
|
||||
noDataComponent={
|
||||
<p className="p-2">
|
||||
{t("Areas.district")}
|
||||
{t("Warnings.notFound")}
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Districts;
|
||||
620
sge-frontend/src/views/Areas/Neighborhoods.js
Normal file
620
sge-frontend/src/views/Areas/Neighborhoods.js
Normal file
@@ -0,0 +1,620 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ChevronDown, Edit, MoreVertical, Plus, Trash } from "react-feather";
|
||||
import ReactPaginate from "react-paginate";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useSnackbar } from "notistack";
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Input,
|
||||
Label,
|
||||
Row,
|
||||
Col,
|
||||
Button,
|
||||
Modal,
|
||||
ModalHeader,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
UncontrolledDropdown,
|
||||
DropdownToggle,
|
||||
DropdownMenu,
|
||||
DropdownItem,
|
||||
} from "reactstrap";
|
||||
import { default as SweetAlert } from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import DataTable from "react-data-table-component";
|
||||
import MapComponent from "../../components/map";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Select from "react-select";
|
||||
import {
|
||||
getNeighborhoods,
|
||||
addNeighborhood,
|
||||
updateNeighborhood,
|
||||
deleteNeighborhood,
|
||||
} from "../../redux/actions/neighborhoods";
|
||||
import { getCities } from "../../redux/actions/cities";
|
||||
import { getCity } from "../../redux/actions/city";
|
||||
import { permissionCheck } from "../../components/permission-check";
|
||||
|
||||
const Neighborhoods = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const initialColumns = [
|
||||
{
|
||||
name: t("Areas.neighborhoodName"),
|
||||
selector: (row) => row.name,
|
||||
sortable: true,
|
||||
minWidth: "350px",
|
||||
},
|
||||
{
|
||||
name: t("Areas.districtName"),
|
||||
selector: (row) => row.district,
|
||||
sortable: true,
|
||||
minWidth: "350px",
|
||||
cell: (row) => <span>{row.district.name}</span>,
|
||||
},
|
||||
{
|
||||
name: t("Areas.cityName"),
|
||||
selector: (row) => row.district,
|
||||
sortable: true,
|
||||
minWidth: "350px",
|
||||
cell: (row) => <span>{row.district.city.name}</span>,
|
||||
},
|
||||
{
|
||||
name: t("Actions"),
|
||||
allowOverflow: false,
|
||||
maxWidth: "150px",
|
||||
cell: (row) => {
|
||||
return (
|
||||
<div className="d-flex">
|
||||
<UncontrolledDropdown>
|
||||
<DropdownToggle className="pl-1" tag="span">
|
||||
<MoreVertical size={15} />
|
||||
</DropdownToggle>
|
||||
<DropdownMenu container={"body"} end>
|
||||
{permissionCheck("neighborhood_update") && (
|
||||
<DropdownItem
|
||||
tag="a"
|
||||
className="w-100"
|
||||
onClick={() => handleEditNeighborhood(row)}
|
||||
>
|
||||
<Edit size={15} />
|
||||
<span className="align-middle ml-50">
|
||||
{t("Cruds.edit")}
|
||||
</span>
|
||||
</DropdownItem>
|
||||
)}
|
||||
{permissionCheck("neighborhood_delete") && (
|
||||
<DropdownItem
|
||||
tag="a"
|
||||
className="w-100"
|
||||
onClick={() => handleDeleteNeighborhood(row)}
|
||||
>
|
||||
<Trash size={15} />
|
||||
<span className="align-middle ml-50">
|
||||
{t("Cruds.delete")}
|
||||
</span>
|
||||
</DropdownItem>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
</UncontrolledDropdown>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!(
|
||||
permissionCheck("emission_source_delete") ||
|
||||
permissionCheck("emission_source_update")
|
||||
)
|
||||
) {
|
||||
const updatedColumns = initialColumns.filter(
|
||||
(column) => column.name !== ("Aksiyonlar" || "Actions")
|
||||
);
|
||||
setServerSideColumns(updatedColumns);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const Swal = withReactContent(SweetAlert);
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const dispatch = useDispatch();
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(10);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const [serverSideColumns, setServerSideColumns] = useState(initialColumns);
|
||||
|
||||
const citiesStore = useSelector((state) => state.cities.cities);
|
||||
const [cities, setCities] = useState([]);
|
||||
|
||||
const cityStore = useSelector((state) => state.city);
|
||||
const [selectedCity, setSelectedCity] = useState();
|
||||
const [districts, setDistricts] = useState([]);
|
||||
|
||||
const neighborhoodsStore = useSelector((state) => state.neighborhoods);
|
||||
const [neighborhoods, setNeighborhoods] = useState([]);
|
||||
const [showAddNeighborhoodModal, setShowAddNeighborhoodModal] =
|
||||
useState(false);
|
||||
const [editingNeighborhoodData, setEditingNeighborhoodData] = useState({
|
||||
lat: "",
|
||||
long: "",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getNeighborhoods({ currentPage, rowsPerPage }));
|
||||
}, [currentPage, rowsPerPage]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getCities());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedCity != undefined) {
|
||||
dispatch(getCity(selectedCity?.value));
|
||||
}
|
||||
}, [selectedCity]);
|
||||
|
||||
useEffect(() => {
|
||||
if (neighborhoodsStore.neighborhoods?.content) {
|
||||
setNeighborhoods(neighborhoodsStore.neighborhoods?.content);
|
||||
}
|
||||
}, [
|
||||
neighborhoodsStore.neighborhoods.content,
|
||||
neighborhoodsStore.neighborhoods,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (citiesStore) {
|
||||
setCities(
|
||||
citiesStore?.map((city) => {
|
||||
return {
|
||||
value: city?.id,
|
||||
label: city?.name,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [citiesStore]);
|
||||
|
||||
useEffect(() => {
|
||||
if (cityStore) {
|
||||
setDistricts(
|
||||
cityStore.city?.districts?.map((district) => {
|
||||
return {
|
||||
value: district?.id,
|
||||
label: district?.name,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [cityStore?.city?.districts, cityStore]);
|
||||
|
||||
// const handleFilter = (e) => {
|
||||
// setSearchValue(e.target.value);
|
||||
|
||||
// if (e.target.value !== "") {
|
||||
// setNeighborhoods(
|
||||
// neighborhoodsStore.neighborhoods
|
||||
// .filter((neighborhood) =>
|
||||
// neighborhood.name
|
||||
// .toLowerCase()
|
||||
// .includes(e.target.value.toLowerCase())
|
||||
// )
|
||||
// .slice(
|
||||
// currentPage * rowsPerPage - rowsPerPage,
|
||||
// currentPage * rowsPerPage
|
||||
// )
|
||||
// );
|
||||
// } else {
|
||||
// setNeighborhoods(
|
||||
// neighborhoodsStore.neighborhoods.slice(
|
||||
// currentPage * rowsPerPage - rowsPerPage,
|
||||
// currentPage * rowsPerPage
|
||||
// )
|
||||
// );
|
||||
// }
|
||||
// };
|
||||
|
||||
const handleFilter = (e) => {
|
||||
setSearchValue(e.target.value);
|
||||
if (e.target.value !== "") {
|
||||
setNeighborhoods(
|
||||
neighborhoodsStore.neighborhoods?.content?.filter((neighborhood) =>
|
||||
neighborhood.name
|
||||
.toUpperCase()
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
.includes(
|
||||
e.target.value
|
||||
.toUpperCase()
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setNeighborhoods(neighborhoodsStore.neighborhoods?.content);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePagination = (page) => {
|
||||
setCurrentPage(page.selected + 1);
|
||||
setNeighborhoods(neighborhoodsStore.neighborhoods.content);
|
||||
};
|
||||
|
||||
const handlePerPage = (e) => {
|
||||
setCurrentPage(1);
|
||||
setRowsPerPage(parseInt(e.target.value));
|
||||
setNeighborhoods(neighborhoodsStore.neighborhoods.content);
|
||||
};
|
||||
|
||||
const CustomPagination = () => {
|
||||
const count = Number(
|
||||
(
|
||||
neighborhoodsStore.neighborhoods?.pageInfo?.totalElements / rowsPerPage
|
||||
).toFixed(1)
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactPaginate
|
||||
previousLabel={""}
|
||||
nextLabel={""}
|
||||
breakLabel="..."
|
||||
pageCount={count || 1}
|
||||
marginPagesDisplayed={2}
|
||||
pageRangeDisplayed={2}
|
||||
activeClassName="active"
|
||||
forcePage={currentPage !== 0 ? currentPage - 1 : 0}
|
||||
onPageChange={(page) => handlePagination(page)}
|
||||
pageClassName={"page-item"}
|
||||
nextLinkClassName={"page-link"}
|
||||
nextClassName={"page-item next"}
|
||||
previousClassName={"page-item prev"}
|
||||
previousLinkClassName={"page-link"}
|
||||
pageLinkClassName={"page-link"}
|
||||
breakClassName="page-item"
|
||||
breakLinkClassName="page-link"
|
||||
containerClassName={
|
||||
"pagination react-paginate separated-pagination pagination-sm justify-content-end pr-1 mt-1"
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const onAddNeighborhoodButtonPressed = () => {
|
||||
setEditingNeighborhoodData({ lat: "", long: "" });
|
||||
setShowAddNeighborhoodModal(true);
|
||||
};
|
||||
|
||||
const onAddNeighborhoodModalButtonPressed = async () => {
|
||||
let newNeighborhoodData = {
|
||||
name: editingNeighborhoodData.name,
|
||||
district: editingNeighborhoodData.district?.value,
|
||||
city: editingNeighborhoodData.city?.value,
|
||||
lat: editingNeighborhoodData.lat,
|
||||
long: editingNeighborhoodData.long,
|
||||
id: editingNeighborhoodData.id,
|
||||
};
|
||||
if (!editingNeighborhoodData?.id) {
|
||||
dispatch(addNeighborhood(newNeighborhoodData))
|
||||
.then(() => {
|
||||
setEditingNeighborhoodData({ lat: "", long: "" });
|
||||
setSelectedCity();
|
||||
setShowAddNeighborhoodModal(false);
|
||||
enqueueSnackbar("Mahalle başarıyla eklendi.", {
|
||||
variant: "success",
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
enqueueSnackbar(
|
||||
"Mahalle eklenirken bir hata meydana geldi, lütfen daha sonra tekrar deneyiniz.",
|
||||
{
|
||||
variant: "error",
|
||||
}
|
||||
);
|
||||
});
|
||||
} else {
|
||||
setShowAddNeighborhoodModal(false);
|
||||
dispatch(updateNeighborhood(newNeighborhoodData))
|
||||
.then(() => {
|
||||
setEditingNeighborhoodData({ lat: "", long: "" });
|
||||
setSelectedCity();
|
||||
setShowAddNeighborhoodModal(false);
|
||||
enqueueSnackbar("Mahalle başarıyla güncellendi.", {
|
||||
variant: "success",
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
enqueueSnackbar(
|
||||
"Mahalle güncellenirken bir hata meydana geldi, lütfen daha sonra tekrar deneyiniz.",
|
||||
{
|
||||
variant: "error",
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const renderNeighorhoodModal = () => {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={showAddNeighborhoodModal}
|
||||
toggle={() => {
|
||||
setShowAddNeighborhoodModal(!showAddNeighborhoodModal);
|
||||
setEditingNeighborhoodData({ lat: "", long: "" });
|
||||
}}
|
||||
className="modal-dialog-centered"
|
||||
>
|
||||
<ModalHeader
|
||||
toggle={() => {
|
||||
setShowAddNeighborhoodModal(!showAddNeighborhoodModal);
|
||||
setEditingNeighborhoodData({ lat: "", long: "" });
|
||||
}}
|
||||
>
|
||||
{editingNeighborhoodData?.id
|
||||
? editingNeighborhoodData?.name
|
||||
: t("Areas.addNeighborhood")}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" for="city-name">
|
||||
{t("Areas.city")}:
|
||||
</Label>
|
||||
<Select
|
||||
id="city-name"
|
||||
placeholder=""
|
||||
options={cities}
|
||||
className="react-select"
|
||||
isClearable={false}
|
||||
closeMenuOnSelect={true}
|
||||
defaultValue={editingNeighborhoodData?.city}
|
||||
onChange={(value) => {
|
||||
setSelectedCity(value);
|
||||
setEditingNeighborhoodData({
|
||||
...editingNeighborhoodData,
|
||||
city: value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" for="district-name">
|
||||
{t("Areas.district")}:
|
||||
</Label>
|
||||
<Select
|
||||
id="district-name"
|
||||
placeholder=""
|
||||
options={districts}
|
||||
defaultValue={editingNeighborhoodData?.district}
|
||||
onChange={(value) => {
|
||||
setEditingNeighborhoodData({
|
||||
...editingNeighborhoodData,
|
||||
district: value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" for="neighborhood-name">
|
||||
{t("Areas.neighborhoodName")}:
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="neighborhood-name"
|
||||
placeholder=""
|
||||
defaultValue={editingNeighborhoodData?.name}
|
||||
onChange={(e) => {
|
||||
setEditingNeighborhoodData({
|
||||
...editingNeighborhoodData,
|
||||
name: e.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Row>
|
||||
<Col>
|
||||
<Label className="form-label" for="latitude">
|
||||
{t("Areas.latitude")}:
|
||||
</Label>
|
||||
<Input
|
||||
id="latitude"
|
||||
type="number"
|
||||
placeholder="Enlem"
|
||||
value={editingNeighborhoodData?.lat?.toString()}
|
||||
onChange={(e) => {
|
||||
setEditingNeighborhoodData({
|
||||
...editingNeighborhoodData,
|
||||
lat: Number(e.target.value),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col>
|
||||
<Label className="form-label" for="longitude">
|
||||
{t("Areas.longitude")}:
|
||||
</Label>
|
||||
<Input
|
||||
id="longitude"
|
||||
type="number"
|
||||
placeholder="Boylam"
|
||||
value={editingNeighborhoodData?.long?.toString()}
|
||||
onChange={(e) => {
|
||||
setEditingNeighborhoodData({
|
||||
...editingNeighborhoodData,
|
||||
long: Number(e.target.value),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{ marginLeft: "1%" }}>
|
||||
<div
|
||||
style={{
|
||||
marginTop: "2%",
|
||||
marginBottom: "%2",
|
||||
height: "0.000000001%",
|
||||
width: "90%",
|
||||
}}
|
||||
>
|
||||
<MapComponent
|
||||
editingNeighborhoodData={editingNeighborhoodData}
|
||||
setEditingNeighborhoodData={setEditingNeighborhoodData}
|
||||
/>
|
||||
</div>
|
||||
</Row>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={(e) => {
|
||||
onAddNeighborhoodModalButtonPressed();
|
||||
}}
|
||||
>
|
||||
{editingNeighborhoodData?.id ? t("Cruds.update") : t("Cruds.save")}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const handleDeleteNeighborhood = async (selectedNeighborhood) => {
|
||||
const result = await Swal.fire({
|
||||
title: ` ${t("Warnings.sureForDelete")}`,
|
||||
text: t("Warnings.notUndone"),
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonText: t("Cruds.delete"),
|
||||
cancelButtonText: t("Cruds.cancel"),
|
||||
customClass: {
|
||||
confirmButton: "btn btn-primary",
|
||||
cancelButton: "btn btn-danger ml-1",
|
||||
},
|
||||
buttonsStyling: false,
|
||||
});
|
||||
if (result.value !== null && result.value === true) {
|
||||
dispatch(deleteNeighborhood(selectedNeighborhood.id.trim()))
|
||||
.then(() => {
|
||||
enqueueSnackbar(
|
||||
`${selectedNeighborhood.name} ${t("Warnings.deletedSuccessfully")}`,
|
||||
{
|
||||
variant: "success",
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch((e) => {
|
||||
enqueueSnackbar(
|
||||
`${selectedNeighborhood.name} ${t("Warnings.deletedFail")}`,
|
||||
{
|
||||
variant: "error",
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditNeighborhood = (selectedNeighborhood) => {
|
||||
const district = {
|
||||
value: selectedNeighborhood.district.id,
|
||||
label: selectedNeighborhood.district.name,
|
||||
};
|
||||
|
||||
const city = {
|
||||
value: selectedNeighborhood.district.city.id,
|
||||
label: selectedNeighborhood.district.city.name,
|
||||
};
|
||||
|
||||
const lat = (selectedNeighborhood.maxLat + selectedNeighborhood.minLat) / 2;
|
||||
const long =
|
||||
(selectedNeighborhood.maxLong + selectedNeighborhood.minLong) / 2;
|
||||
|
||||
setShowAddNeighborhoodModal(true);
|
||||
setSelectedCity(city);
|
||||
setEditingNeighborhoodData({
|
||||
...selectedNeighborhood,
|
||||
district: district,
|
||||
city: city,
|
||||
lat: lat,
|
||||
long: long,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "2%" }}>
|
||||
<Card>
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle tag="h4">{t("Areas.neighborhoods")}</CardTitle>
|
||||
{permissionCheck("neighborhood_create") && (
|
||||
<Button
|
||||
className="ml-2"
|
||||
color="primary"
|
||||
onClick={onAddNeighborhoodButtonPressed}
|
||||
>
|
||||
<Plus size={15} />
|
||||
<span className="align-middle ml-50">
|
||||
{t("Areas.addNeighborhood")}
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
</CardHeader>
|
||||
<Row className="mx-0 mt-1 mb-50">
|
||||
<Col sm="6" md="2">
|
||||
<div className="d-flex align-items-center">
|
||||
<Label for="sort-select">{t("Show")}</Label>
|
||||
<Input
|
||||
className="ml-1 dataTable-select"
|
||||
type="select"
|
||||
id="sort-select"
|
||||
value={rowsPerPage}
|
||||
onChange={(e) => handlePerPage(e)}
|
||||
>
|
||||
<option value={5}>5</option>
|
||||
<option value={10}>10</option>
|
||||
<option value={25}>25</option>
|
||||
<option value={50}>50</option>
|
||||
<option value={75}>75</option>
|
||||
<option value={100}>100</option>
|
||||
</Input>
|
||||
</div>
|
||||
</Col>
|
||||
<Col
|
||||
className="d-flex align-items-center justify-content-end mt-sm-0 mt-1 ml-md-auto"
|
||||
sm="6"
|
||||
md="4"
|
||||
>
|
||||
<Input
|
||||
className="dataTable-filter"
|
||||
type="text"
|
||||
bsSize="ml"
|
||||
id="search-input"
|
||||
value={searchValue}
|
||||
onChange={handleFilter}
|
||||
placeholder={t("Areas.neighborhoodName") + " " + t("Filter")}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<DataTable
|
||||
noHeader
|
||||
pagination
|
||||
paginationServer
|
||||
className="react-dataTable"
|
||||
columns={serverSideColumns}
|
||||
sortIcon={<ChevronDown size={10} />}
|
||||
paginationComponent={CustomPagination}
|
||||
data={[...neighborhoods]}
|
||||
noDataComponent={
|
||||
<p className="p-2">
|
||||
{t("Areas.neighborhood")}
|
||||
{t("Warnings.notFound")}
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
{renderNeighorhoodModal()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Neighborhoods;
|
||||
218
sge-frontend/src/views/Communication.js
Normal file
218
sge-frontend/src/views/Communication.js
Normal file
@@ -0,0 +1,218 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Col,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalHeader,
|
||||
Row,
|
||||
UncontrolledTooltip, // Add UncontrolledTooltip import
|
||||
} from "reactstrap";
|
||||
import { Edit, Mail, Phone, MapPin } from "react-feather"; // Import Mail, Phone, MapPin
|
||||
|
||||
import { useSnackbar } from "notistack";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { permissionCheck } from "../components/permission-check";
|
||||
import {
|
||||
getMailSettings,
|
||||
clearMailSuccess,
|
||||
clearMailError,
|
||||
} from "../redux/actions/mailSettings";
|
||||
import MailSettings from "./MailSettings";
|
||||
import SpinnerComponent from "../@core/components/spinner/Fallback-spinner";
|
||||
|
||||
// Import custom styles for the communication page
|
||||
import "../assets/scss/pages/communication.scss";
|
||||
|
||||
function Communication() {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const { currentSettings, loading, success, error } = useSelector(
|
||||
// Add loading to useSelector
|
||||
(state) => state.mailSettings
|
||||
);
|
||||
const [showMailModal, setShowMailModal] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Load mail settings once when component mounts
|
||||
dispatch(getMailSettings());
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (success) {
|
||||
enqueueSnackbar(t("Warnings.updatedSuccessfully"), {
|
||||
variant: "success",
|
||||
});
|
||||
dispatch(clearMailSuccess());
|
||||
}
|
||||
}, [success, enqueueSnackbar, t, dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
const errorMessage =
|
||||
error?.graphQLErrors?.[0]?.message ||
|
||||
error?.message ||
|
||||
t("Warnings.genericUpdateFailed");
|
||||
|
||||
enqueueSnackbar(errorMessage, {
|
||||
variant: "error",
|
||||
});
|
||||
dispatch(clearMailError());
|
||||
}
|
||||
}, [error, enqueueSnackbar, t, dispatch]);
|
||||
|
||||
// Get email address from settings or use default from environment variable
|
||||
const defaultEmail = process.env.REACT_APP_DEFAULT_EMAIL || "";
|
||||
// Use default email if currentSettings is null or emailAddress is null
|
||||
const emailAddress = currentSettings?.emailAddress || defaultEmail;
|
||||
const handleEmailClick = () => {
|
||||
if (emailAddress) {
|
||||
window.location.href = `mailto:${emailAddress}`;
|
||||
}
|
||||
};
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setShowMailModal(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "2%" }}>
|
||||
{loading && !currentSettings ? (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
minHeight: 200,
|
||||
}}
|
||||
>
|
||||
<SpinnerComponent />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{permissionCheck("settings_access") && (
|
||||
<>
|
||||
<Modal
|
||||
isOpen={showMailModal}
|
||||
toggle={handleCloseModal}
|
||||
className="modal-dialog-centered"
|
||||
size="lg"
|
||||
>
|
||||
<ModalHeader toggle={handleCloseModal}>
|
||||
{t("MailSettings.editMailInfo")}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<MailSettings closeModal={handleCloseModal} />
|
||||
</ModalBody>
|
||||
</Modal>
|
||||
</>
|
||||
)}
|
||||
<Card className="border-bottom">
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle tag="h2" className="row ml-md-2 align-items-center">
|
||||
{t("Contact.contactInfo")}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<Row>
|
||||
<Col className="pl-md-5 pl-2 col-md-6 col-12">
|
||||
<Row className="mx-0 mt-1">
|
||||
<Col sm="6" md="5" className="mr-2">
|
||||
<div className="email-container d-flex align-items-center justify-content-between">
|
||||
<div>
|
||||
<h4>
|
||||
<Mail size={16} className="mr-1" />{" "}
|
||||
{t("Contact.contactEmail")}
|
||||
</h4>{" "}
|
||||
{/* Add Mail icon */}
|
||||
<p
|
||||
style={{
|
||||
color: emailAddress ? "blue" : "inherit",
|
||||
textDecoration: emailAddress ? "underline" : "none",
|
||||
cursor: emailAddress ? "pointer" : "default",
|
||||
}}
|
||||
onClick={handleEmailClick}
|
||||
>
|
||||
{emailAddress ||
|
||||
defaultEmail ||
|
||||
t("MailSettings.notConfigured")}
|
||||
</p>
|
||||
</div>
|
||||
{permissionCheck("settings_access") && (
|
||||
<>
|
||||
{" "}
|
||||
{/* Wrap button and tooltip in fragment */}
|
||||
<Button
|
||||
color="flat-primary"
|
||||
className="btn-icon"
|
||||
onClick={() => setShowMailModal(true)}
|
||||
id="edit-email-btn" // Add id for tooltip
|
||||
>
|
||||
<Edit size={18} />
|
||||
</Button>
|
||||
<UncontrolledTooltip
|
||||
placement="top"
|
||||
target="edit-email-btn" // Target the button id
|
||||
timeout={150} // Add timeout prop to fix the warning
|
||||
>
|
||||
{t("MailSettings.editMailInfo")}{" "}
|
||||
{/* Tooltip text */}
|
||||
</UncontrolledTooltip>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Col>
|
||||
<Col sm="6" md="5" className="mr-2">
|
||||
<div className="telephone-container">
|
||||
<h4>
|
||||
<Phone size={16} className="mr-1" />{" "}
|
||||
{t("Contact.contactPhoneNumber")}
|
||||
</h4>{" "}
|
||||
{/* Add Phone icon */}
|
||||
<p>+90 507 750 00 41</p>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mx-0 mt-4">
|
||||
<Col sm="6" md="5" className="mr-2">
|
||||
<div className="address-container">
|
||||
<h4>
|
||||
<MapPin size={16} className="mr-1" />{" "}
|
||||
{t("Contact.contactAddress")}
|
||||
</h4>{" "}
|
||||
{/* Add MapPin icon */}
|
||||
<address>
|
||||
Central Office: 4995 sokak no:3, Alacaatlı Mahallesi,
|
||||
Daire No: A2 06810 Çankaya/Ankara
|
||||
</address>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col className="pl-md-5 pl-2 col-md-6 col-12 pb-2 border-left">
|
||||
<Row className="mx-0 mt-1">
|
||||
<div className="address-map w-100">
|
||||
<div className="responsive-map-container">
|
||||
<iframe
|
||||
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3063.1752741150003!2d32.658217075858445!3d39.847904971536735!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x14d33eef6ee44755%3A0x77faea5f08f32c60!2zTUVBIEfDnFpFTEJBSMOHRU0!5e0!3m2!1sen!2str!4v1741165773414!5m2!1sen!2str"
|
||||
allowFullScreen=""
|
||||
loading="lazy"
|
||||
referrerPolicy="no-referrer-when-downgrade"
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Communication;
|
||||
265
sge-frontend/src/views/DataCenter.js
Normal file
265
sge-frontend/src/views/DataCenter.js
Normal file
@@ -0,0 +1,265 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { Card, CardHeader, CardTitle, Button } from "reactstrap";
|
||||
import DataTable from "react-data-table-component";
|
||||
import { ChevronDown, RefreshCw, Server, Monitor } from "react-feather";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { getDataCenters } from "../redux/actions/dataCenter";
|
||||
import SpinnerComponent from "../@core/components/spinner/Fallback-spinner";
|
||||
|
||||
const DataCenter = () => {
|
||||
const { t } = useTranslation();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// Redux state
|
||||
const dataCenterStore = useSelector((state) => state.dataCenter);
|
||||
const [refreshInterval, setRefreshInterval] = useState(null);
|
||||
|
||||
const getAllPhysicalMachines = (dataCenter) => {
|
||||
if (!dataCenter.projects) return [];
|
||||
return dataCenter.projects.flatMap(project =>
|
||||
project.physicalMachines || []
|
||||
);
|
||||
};
|
||||
|
||||
// Table columns following your pattern
|
||||
const initialColumns = [
|
||||
{
|
||||
name: "Number",
|
||||
selector: (row) => row.number,
|
||||
sortable: true,
|
||||
minWidth: "100px",
|
||||
},
|
||||
{
|
||||
name: "Data Centers",
|
||||
selector: (row) => row.dataCenter,
|
||||
sortable: true,
|
||||
minWidth: "200px",
|
||||
},
|
||||
// Projects
|
||||
{
|
||||
name: "Projects",
|
||||
selector: (row) =>
|
||||
(row.projects || []).map((p) => p.name).join(", "),
|
||||
sortable: false,
|
||||
minWidth: "200px",
|
||||
cell: (row) => (
|
||||
<span>
|
||||
{(row.projects || []).map((p) => p.name).join(", ") || "-"}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
// Physical Machines
|
||||
{
|
||||
name: "Physical Machines",
|
||||
selector: (row) => getAllPhysicalMachines(row),
|
||||
sortable: false,
|
||||
minWidth: "200px",
|
||||
cell: (row) => {
|
||||
const pms = getAllPhysicalMachines(row);
|
||||
return (
|
||||
<div className="d-flex align-items-center">
|
||||
<Server size={16} className="mr-1" />
|
||||
<span>{pms.length} machines</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Total Active VMs",
|
||||
selector: (row) => {
|
||||
const pms = getAllPhysicalMachines(row);
|
||||
return pms.reduce(
|
||||
(total, pm) => total + (pm.vms?.active?.length || 0),
|
||||
0
|
||||
);
|
||||
},
|
||||
sortable: true,
|
||||
minWidth: "150px",
|
||||
cell: (row) => {
|
||||
const pms = getAllPhysicalMachines(row);
|
||||
const totalVMs = pms.reduce(
|
||||
(total, pm) => total + (pm.vms?.active?.length || 0),
|
||||
0
|
||||
);
|
||||
return (
|
||||
<div className="d-flex align-items-center">
|
||||
<Monitor size={16} className="mr-1" />
|
||||
<span>{totalVMs}</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Fetch data on component mount
|
||||
useEffect(() => {
|
||||
dispatch(getDataCenters());
|
||||
|
||||
// Set up auto-refresh every 2 minutes
|
||||
const interval = setInterval(() => {
|
||||
dispatch(getDataCenters());
|
||||
}, 2 * 60 * 1000);
|
||||
|
||||
setRefreshInterval(interval);
|
||||
|
||||
return () => {
|
||||
if (interval) clearInterval(interval);
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
// Handle errors
|
||||
useEffect(() => {
|
||||
if (dataCenterStore.error) {
|
||||
enqueueSnackbar(dataCenterStore.error, {
|
||||
variant: "error",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
}
|
||||
}, [dataCenterStore.error, enqueueSnackbar]);
|
||||
|
||||
const handleRefresh = () => {
|
||||
dispatch(getDataCenters());
|
||||
};
|
||||
|
||||
// Expandable component for showing physical machines and VMs
|
||||
const ExpandableComponent = ({ data }) => {
|
||||
const physicalMachines = getAllPhysicalMachines(data);
|
||||
console.log("▶️ Expandable Data", physicalMachines);
|
||||
return (
|
||||
<div className="expandable-content p-3">
|
||||
<h6 className="mb-3">Physical Machines:</h6>
|
||||
{physicalMachines.length === 0 && (
|
||||
<p className="text-muted ml-3">No physical machines</p>
|
||||
)}
|
||||
{physicalMachines.map((pm) => (
|
||||
<div key={pm.id} className="mb-3 border rounded p-3">
|
||||
<h6 className="text-primary">{pm.name}</h6>
|
||||
|
||||
{/* Active VMs */}
|
||||
<p className="mb-2">
|
||||
<strong>Active VMs ({pm.vms?.active?.length || 0}):</strong>
|
||||
</p>
|
||||
{pm.vms?.active?.length > 0 ? (
|
||||
<div className="ml-3">
|
||||
{pm.vms.active.map((vm) => (
|
||||
<div
|
||||
key={vm.id}
|
||||
className="mb-2 p-2 border-left border-success"
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col-md-3">
|
||||
<strong>Name:</strong> {vm.name}
|
||||
</div>
|
||||
<div className="col-md-2">
|
||||
<strong>Status:</strong>
|
||||
<span className="badge badge-success ml-1">
|
||||
{vm.status}
|
||||
</span>
|
||||
</div>
|
||||
<div className="col-md-2">
|
||||
<strong>Power:</strong> {vm.power}
|
||||
</div>
|
||||
<div className="col-md-5">
|
||||
<strong>Config:</strong> CPU: {vm.config?.cpu}, RAM:{" "}
|
||||
{vm.config?.ram}, Disk: {vm.config?.disk}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-muted ml-3">No active VMs</p>
|
||||
)}
|
||||
|
||||
{/* Inactive VMs */}
|
||||
<p className="mb-2 mt-3">
|
||||
<strong>Inactive VMs ({pm.vms?.inactive?.length || 0}):</strong>
|
||||
</p>
|
||||
{pm.vms?.inactive?.length > 0 ? (
|
||||
<div className="ml-3">
|
||||
{pm.vms.inactive.map((vm) => (
|
||||
<div
|
||||
key={vm.id}
|
||||
className="mb-2 p-2 border-left border-warning"
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col-md-3">
|
||||
<strong>Name:</strong> {vm.name}
|
||||
</div>
|
||||
<div className="col-md-2">
|
||||
<strong>Status:</strong>
|
||||
<span className="badge badge-warning ml-1">
|
||||
{vm.status}
|
||||
</span>
|
||||
</div>
|
||||
<div className="col-md-2">
|
||||
<strong>Power:</strong> {vm.power}
|
||||
</div>
|
||||
<div className="col-md-5">
|
||||
<strong>Config:</strong> CPU: {vm.config?.cpu}, RAM:{" "}
|
||||
{vm.config?.ram}, Disk: {vm.config?.disk}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-muted ml-3">No inactive VMs</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "2%" }}>
|
||||
<Card>
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle tag="h4">Test</CardTitle>
|
||||
<Button
|
||||
className="ml-2"
|
||||
color="primary"
|
||||
onClick={handleRefresh}
|
||||
disabled={dataCenterStore.loading}
|
||||
>
|
||||
<RefreshCw size={15} />
|
||||
<span className="align-middle ml-50">
|
||||
{dataCenterStore.loading ? "Refreshing..." : "Refresh"}
|
||||
</span>
|
||||
</Button>
|
||||
</CardHeader>
|
||||
|
||||
<DataTable
|
||||
noHeader
|
||||
columns={initialColumns}
|
||||
data={dataCenterStore.dataCenters}
|
||||
sortIcon={<ChevronDown size={10} />}
|
||||
expandableRows
|
||||
expandableRowsComponent={ExpandableComponent}
|
||||
progressPending={dataCenterStore.loading}
|
||||
progressComponent={
|
||||
<div className="d-flex justify-content-center p-3">
|
||||
<SpinnerComponent />
|
||||
</div>
|
||||
}
|
||||
noDataComponent={
|
||||
<div className="d-flex flex-column align-items-center p-3">
|
||||
<Server size={48} className="text-muted mb-2" />
|
||||
|
||||
<p className="text-muted">No data centers found</p>
|
||||
</div>
|
||||
}
|
||||
className="react-dataTable"
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataCenter;
|
||||
922
sge-frontend/src/views/DataCenterManagement.js
Normal file
922
sge-frontend/src/views/DataCenterManagement.js
Normal file
@@ -0,0 +1,922 @@
|
||||
import React, { useState, useEffect, memo, useCallback } from "react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import ReactPaginate from "react-paginate";
|
||||
import { ChevronDown, MoreVertical, Plus, Trash } from "react-feather";
|
||||
import DataTable from "react-data-table-component";
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Input,
|
||||
Label,
|
||||
Row,
|
||||
Col,
|
||||
Button,
|
||||
Modal,
|
||||
ModalHeader,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
UncontrolledDropdown,
|
||||
DropdownToggle,
|
||||
DropdownMenu,
|
||||
DropdownItem,
|
||||
FormGroup,
|
||||
Form,
|
||||
} from "reactstrap";
|
||||
import Select from "react-select";
|
||||
import { Edit } from "@mui/icons-material";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { default as SweetAlert } from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { getDataCenters, createDataCenter, updateDataCenter, deleteDataCenter } from "../redux/actions/dataCenter";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getSectors, getSectorById, getSubSectorById, getConsuptionUnits } from "../redux/actions/datas";
|
||||
import { getAllEmissionSources } from "../redux/actions/emissionSources";
|
||||
import { permissionCheck } from "../components/permission-check";
|
||||
import { customFilterForSelect } from "../utility/Utils";
|
||||
import { MapContainer, TileLayer, Marker, useMapEvents } from 'react-leaflet';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import L from 'leaflet';
|
||||
import axios from 'axios';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
// Add Nominatim service configuration
|
||||
const NOMINATIM_BASE_URL = 'https://nominatim.openstreetmap.org';
|
||||
const nominatimAxios = axios.create({
|
||||
baseURL: NOMINATIM_BASE_URL,
|
||||
headers: {
|
||||
'User-Agent': 'SGE-DataCenter-Management' // Required by Nominatim's usage policy
|
||||
}
|
||||
});
|
||||
|
||||
const Swal = withReactContent(SweetAlert);
|
||||
|
||||
// Fix Leaflet marker icon issue
|
||||
delete L.Icon.Default.prototype._getIconUrl;
|
||||
L.Icon.Default.mergeOptions({
|
||||
iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
|
||||
iconUrl: require('leaflet/dist/images/marker-icon.png'),
|
||||
shadowUrl: require('leaflet/dist/images/marker-shadow.png')
|
||||
});
|
||||
|
||||
// Map marker component that handles clicks
|
||||
const MapMarker = ({ position, setPosition, setSelectedDataCenter }) => {
|
||||
useMapEvents({
|
||||
click(e) {
|
||||
setPosition([e.latlng.lat, e.latlng.lng]);
|
||||
// Use Nominatim reverse geocoding directly
|
||||
nominatimAxios.get(`/reverse?format=json&lat=${e.latlng.lat}&lon=${e.latlng.lng}`)
|
||||
.then(response => {
|
||||
const address = response.data.display_name;
|
||||
setSelectedDataCenter(prev => ({
|
||||
...prev,
|
||||
address,
|
||||
latitude: e.latlng.lat,
|
||||
longitude: e.latlng.lng
|
||||
}));
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error getting address:', error);
|
||||
// Still update coordinates even if address lookup fails
|
||||
setSelectedDataCenter(prev => ({
|
||||
...prev,
|
||||
latitude: e.latlng.lat,
|
||||
longitude: e.latlng.lng
|
||||
}));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Only render marker if position exists and has valid coordinates
|
||||
return position && position[0] && position[1] ? <Marker position={position} /> : null;
|
||||
};
|
||||
|
||||
const DataCenterManagement = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(10);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const [showAddModal, setShowAddModal] = useState(false);
|
||||
const [selectedDataCenter, setSelectedDataCenter] = useState({
|
||||
name: "",
|
||||
externalId: "",
|
||||
number: "",
|
||||
address: "",
|
||||
latitude: null,
|
||||
longitude: null,
|
||||
ayposURL: "",
|
||||
city: "",
|
||||
emissionScopeId: null,
|
||||
sectorId: null,
|
||||
subSectorId: null,
|
||||
emissionSourceId: null,
|
||||
consuptionUnitId: null,
|
||||
activitySubUnitId: null
|
||||
});
|
||||
|
||||
const [mapPosition, setMapPosition] = useState(null);
|
||||
|
||||
const dataCenterStore = useSelector((state) => state.dataCenter);
|
||||
const datasStore = useSelector((state) => state.datas);
|
||||
const emissionSourceStore = useSelector((state) => state.emissionSources);
|
||||
const [sectorsOptions, setSectorsOptions] = useState([]);
|
||||
const [subSectorsOptions, setSubSectorsOptions] = useState([]);
|
||||
const [emissionSourcesOptions, setEmissionSourcesOptions] = useState([]);
|
||||
const [consuptionUnitsOptions, setConsuptionUnitsOptions] = useState([]);
|
||||
const [activitySubUnitsOptions, setActivitySubUnitsOptions] = useState([]);
|
||||
const [emissionScopesOptions, setEmissionScopesOptions] = useState([]);
|
||||
|
||||
// Add state for selected sector and sub sector like in data input
|
||||
const [selectedSector, setSelectedSector] = useState(null);
|
||||
const [selectedSubSector, setSelectedSubSector] = useState(null);
|
||||
|
||||
const [editingDataCenter, setEditingDataCenter] = useState(null);
|
||||
|
||||
const initialColumns = [
|
||||
{
|
||||
name: t("DataCenter.id"),
|
||||
selector: (row) => row.externalId,
|
||||
sortable: true,
|
||||
minWidth: "100px",
|
||||
cell: (row) => <span>{row.externalId}</span>,
|
||||
},
|
||||
{
|
||||
name: t("DataCenter.name"),
|
||||
selector: (row) => row.dataCenter,
|
||||
sortable: true,
|
||||
minWidth: "200px",
|
||||
},
|
||||
{
|
||||
name: "Dashboard",
|
||||
selector: (row) => row.ayposURL,
|
||||
sortable: false,
|
||||
minWidth: "150px",
|
||||
cell: (row) => (
|
||||
<div className="d-flex justify-content-center w-100">
|
||||
{row.ayposURL ? (
|
||||
<Button
|
||||
color="primary"
|
||||
size="sm"
|
||||
onClick={() => window.open(row.ayposURL, '_blank')}
|
||||
>
|
||||
Dashboard
|
||||
</Button>
|
||||
) : (
|
||||
<span>-</span>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: t("Actions"),
|
||||
allowOverflow: false,
|
||||
maxWidth: "150px",
|
||||
cell: (row) => {
|
||||
return (
|
||||
<div className="d-flex">
|
||||
<UncontrolledDropdown>
|
||||
<DropdownToggle className="pl-1" tag="span">
|
||||
<MoreVertical size={15} />
|
||||
</DropdownToggle>
|
||||
<DropdownMenu container={"body"} end>
|
||||
{permissionCheck("datacenter_update") && (
|
||||
<DropdownItem
|
||||
tag="a"
|
||||
className="w-100"
|
||||
onClick={() => handleEditDataCenter(row)}
|
||||
>
|
||||
<Edit size={15} />
|
||||
<span className="align-middle ml-50">{t("Cruds.edit")}</span>
|
||||
</DropdownItem>
|
||||
)}
|
||||
{permissionCheck("datacenter_delete") && (
|
||||
<DropdownItem
|
||||
tag="a"
|
||||
className="w-100"
|
||||
onClick={() => handleDeleteDataCenter(row)}
|
||||
>
|
||||
<Trash size={15} />
|
||||
<span className="align-middle ml-50">{t("Cruds.delete")}</span>
|
||||
</DropdownItem>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
</UncontrolledDropdown>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const [serverSideColumns, setServerSideColumns] = useState(initialColumns);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getDataCenters());
|
||||
dispatch(getSectors());
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
setSectorsOptions(
|
||||
datasStore?.sectors?.map((sector) => ({
|
||||
value: sector?.id,
|
||||
label: sector?.tag,
|
||||
}))
|
||||
);
|
||||
}, [datasStore?.sectors]);
|
||||
|
||||
useEffect(() => {
|
||||
setSubSectorsOptions([]);
|
||||
setSubSectorsOptions(
|
||||
datasStore?.sector?.subSectors?.map((subSector) => ({
|
||||
value: subSector?.id,
|
||||
label: subSector?.tag,
|
||||
}))
|
||||
);
|
||||
}, [datasStore?.sector]);
|
||||
|
||||
useEffect(() => {
|
||||
setActivitySubUnitsOptions(
|
||||
datasStore?.subSector?.activitySubUnits?.map((activitySubUnit) => ({
|
||||
value: activitySubUnit?.id,
|
||||
label: activitySubUnit?.tag,
|
||||
}))
|
||||
);
|
||||
}, [datasStore?.subSector]);
|
||||
|
||||
useEffect(() => {
|
||||
setEmissionSourcesOptions(
|
||||
emissionSourceStore?.emissionSources
|
||||
?.filter((source) => source.convertUnitCheck != false)
|
||||
?.map((source) => ({
|
||||
value: source?.id,
|
||||
label: source?.tag,
|
||||
}))
|
||||
);
|
||||
}, [emissionSourceStore?.emissionSources]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedDataCenter?.emissionSourceId) {
|
||||
dispatch(
|
||||
getConsuptionUnits({
|
||||
id: selectedDataCenter?.emissionSourceId,
|
||||
sector: selectedDataCenter?.sectorId,
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [selectedDataCenter?.emissionSourceId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedSubSector != null) {
|
||||
dispatch(getAllEmissionSources(selectedSubSector));
|
||||
}
|
||||
}, [selectedSubSector]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedSector != null) {
|
||||
dispatch(getSectorById(selectedSector));
|
||||
}
|
||||
}, [selectedSector]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedSubSector != null) {
|
||||
dispatch(getSubSectorById(selectedSubSector));
|
||||
}
|
||||
}, [selectedSubSector]);
|
||||
|
||||
useEffect(() => {
|
||||
setConsuptionUnitsOptions(
|
||||
datasStore?.consuptionUnits?.map((consuptionUnit) => ({
|
||||
value: consuptionUnit?.unit?.id,
|
||||
label: consuptionUnit?.unit?.description,
|
||||
}))
|
||||
);
|
||||
}, [datasStore?.consuptionUnits]);
|
||||
|
||||
useEffect(() => {
|
||||
setEmissionScopesOptions([
|
||||
{
|
||||
label: "Şehir İçi",
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
label: "Şehir Dışı",
|
||||
value: true,
|
||||
},
|
||||
]);
|
||||
}, []);
|
||||
|
||||
const handleEditDataCenter = (row) => {
|
||||
setEditingDataCenter(row);
|
||||
setSelectedDataCenter({
|
||||
name: row.dataCenter,
|
||||
externalId: row.externalId,
|
||||
number: row.number,
|
||||
address: row.address,
|
||||
latitude: row.latitude,
|
||||
longitude: row.longitude,
|
||||
ayposURL: row.ayposURL,
|
||||
city: row.city,
|
||||
emissionScopeId: row.emissionScope?.id,
|
||||
sectorId: row.sector?.id,
|
||||
subSectorId: row.subSector?.id,
|
||||
emissionSourceId: row.emissionSource?.id,
|
||||
consuptionUnitId: row.consuptionUnit?.id,
|
||||
activitySubUnitId: row.activitySubUnit?.id
|
||||
});
|
||||
|
||||
// Set the selected sector and sub sector for cascading dropdowns
|
||||
setSelectedSector(row.sector?.id);
|
||||
setSelectedSubSector(row.subSector?.id);
|
||||
|
||||
// Only set map position if we have both address and valid coordinates
|
||||
setMapPosition(row.address && row.latitude && row.longitude ? [row.latitude, row.longitude] : null);
|
||||
setShowAddModal(true);
|
||||
};
|
||||
|
||||
const handleDeleteDataCenter = async (row) => {
|
||||
const result = await Swal.fire({
|
||||
title: t("Common.areYouSure"),
|
||||
text: t("Common.cantRevert"),
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonText: t("Common.yes"),
|
||||
cancelButtonText: t("Common.no"),
|
||||
customClass: {
|
||||
confirmButton: "btn btn-primary",
|
||||
cancelButton: "btn btn-outline-danger ms-1",
|
||||
},
|
||||
buttonsStyling: false,
|
||||
});
|
||||
|
||||
if (result.value) {
|
||||
try {
|
||||
await dispatch(deleteDataCenter(row.id));
|
||||
enqueueSnackbar(t("DataCenter.deleteSuccess"), { variant: "success" });
|
||||
} catch (error) {
|
||||
console.error("Delete error:", error);
|
||||
enqueueSnackbar(
|
||||
error?.message || t("DataCenter.deleteError"),
|
||||
{ variant: "error" }
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!selectedDataCenter.name || !selectedDataCenter.externalId) {
|
||||
enqueueSnackbar(t("Common.fillRequiredFields"), { variant: "error" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Ensure number is set for new data centers
|
||||
const dataToSubmit = {
|
||||
...selectedDataCenter,
|
||||
number: selectedDataCenter.number || 1, // Default to 1 if not set
|
||||
city: selectedDataCenter.city, // Add city to the payload
|
||||
emissionScopeId: selectedDataCenter.emissionScopeId,
|
||||
sectorId: selectedDataCenter.sectorId,
|
||||
subSectorId: selectedDataCenter.subSectorId,
|
||||
emissionSourceId: selectedDataCenter.emissionSourceId,
|
||||
consuptionUnitId: selectedDataCenter.consuptionUnitId,
|
||||
activitySubUnitId: selectedDataCenter.activitySubUnitId
|
||||
};
|
||||
|
||||
if (editingDataCenter) {
|
||||
// Update existing data center
|
||||
await dispatch(updateDataCenter(editingDataCenter.id, dataToSubmit));
|
||||
enqueueSnackbar(t("DataCenter.updateSuccess"), { variant: "success" });
|
||||
} else {
|
||||
// Create new data center
|
||||
await dispatch(createDataCenter(dataToSubmit));
|
||||
enqueueSnackbar(t("DataCenter.createSuccess"), { variant: "success" });
|
||||
}
|
||||
|
||||
handleCloseModal();
|
||||
} catch (error) {
|
||||
console.error("Submit error:", error);
|
||||
enqueueSnackbar(
|
||||
error?.message || t("DataCenter.submitError"),
|
||||
{ variant: "error" }
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setShowAddModal(false);
|
||||
setSelectedDataCenter({
|
||||
name: "",
|
||||
externalId: "",
|
||||
number: "",
|
||||
address: "",
|
||||
latitude: null,
|
||||
longitude: null,
|
||||
ayposURL: "",
|
||||
city: ""
|
||||
});
|
||||
setMapPosition(null);
|
||||
setEditingDataCenter(null);
|
||||
};
|
||||
|
||||
const handleFilter = (e) => {
|
||||
setSearchValue(e.target.value);
|
||||
};
|
||||
|
||||
const CustomPagination = () => (
|
||||
<ReactPaginate
|
||||
previousLabel=""
|
||||
nextLabel=""
|
||||
forcePage={currentPage - 1}
|
||||
onPageChange={(page) => setCurrentPage(page.selected + 1)}
|
||||
pageCount={Math.ceil(dataCenterStore.total / rowsPerPage)}
|
||||
breakLabel="..."
|
||||
pageRangeDisplayed={2}
|
||||
marginPagesDisplayed={2}
|
||||
activeClassName="active"
|
||||
pageClassName="page-item"
|
||||
breakClassName="page-item"
|
||||
nextLinkClassName="page-link"
|
||||
pageLinkClassName="page-link"
|
||||
breakLinkClassName="page-link"
|
||||
previousLinkClassName="page-link"
|
||||
nextClassName="page-item next-item"
|
||||
previousClassName="page-item prev-item"
|
||||
containerClassName="pagination react-paginate separated-pagination pagination-sm justify-content-end pe-1 mt-1"
|
||||
/>
|
||||
);
|
||||
|
||||
// Add geocoding function
|
||||
const geocodeAddress = useCallback(async (address) => {
|
||||
if (!address || !address.trim()) {
|
||||
setMapPosition(null);
|
||||
setSelectedDataCenter(prev => ({
|
||||
...prev,
|
||||
latitude: null,
|
||||
longitude: null
|
||||
}));
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await nominatimAxios.get(`/search?format=json&q=${encodeURIComponent(address)}`);
|
||||
|
||||
if (response.data && response.data[0]) {
|
||||
const { lat, lon } = response.data[0];
|
||||
const newPosition = [parseFloat(lat), parseFloat(lon)];
|
||||
setMapPosition(newPosition);
|
||||
setSelectedDataCenter(prev => ({
|
||||
...prev,
|
||||
latitude: parseFloat(lat),
|
||||
longitude: parseFloat(lon)
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
|
||||
// If no results found, clear the coordinates
|
||||
setMapPosition(null);
|
||||
setSelectedDataCenter(prev => ({
|
||||
...prev,
|
||||
latitude: null,
|
||||
longitude: null
|
||||
}));
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Error geocoding address:', error);
|
||||
// On error, clear the coordinates
|
||||
setMapPosition(null);
|
||||
setSelectedDataCenter(prev => ({
|
||||
...prev,
|
||||
latitude: null,
|
||||
longitude: null
|
||||
}));
|
||||
return false;
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Update the address input handler
|
||||
const handleAddressChange = (e) => {
|
||||
const newAddress = e.target.value;
|
||||
setSelectedDataCenter(prev => ({
|
||||
...prev,
|
||||
address: newAddress
|
||||
}));
|
||||
|
||||
// If address is empty, clear the coordinates
|
||||
if (!newAddress.trim()) {
|
||||
setMapPosition(null);
|
||||
setSelectedDataCenter(prev => ({
|
||||
...prev,
|
||||
latitude: null,
|
||||
longitude: null
|
||||
}));
|
||||
} else {
|
||||
// If address is not empty, try to geocode it after a delay
|
||||
debouncedGeocode(newAddress);
|
||||
}
|
||||
};
|
||||
|
||||
// Debounced version of geocoding
|
||||
const debouncedGeocode = useCallback(
|
||||
debounce((address) => geocodeAddress(address), 1000),
|
||||
[geocodeAddress]
|
||||
);
|
||||
|
||||
const renderModal = () => {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={showAddModal}
|
||||
toggle={handleCloseModal}
|
||||
className="modal-dialog-centered"
|
||||
size="lg"
|
||||
>
|
||||
<ModalHeader toggle={handleCloseModal}>
|
||||
{editingDataCenter
|
||||
? t("DataCenter.edit")
|
||||
: t("DataCenter.add")}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<Form>
|
||||
<Row>
|
||||
<Col sm="12">
|
||||
<FormGroup>
|
||||
<Label for="name">
|
||||
{t("DataCenter.name")} <span className="text-danger">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
name="name"
|
||||
id="name"
|
||||
placeholder={t("DataCenter.name")}
|
||||
value={selectedDataCenter.name}
|
||||
onChange={(e) =>
|
||||
setSelectedDataCenter({
|
||||
...selectedDataCenter,
|
||||
name: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
<Col sm="6">
|
||||
<FormGroup>
|
||||
<Label for="externalId">
|
||||
{t("DataCenter.externalId")} <span className="text-danger">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
name="externalId"
|
||||
id="externalId"
|
||||
placeholder={t("DataCenter.externalId")}
|
||||
value={selectedDataCenter.externalId}
|
||||
onChange={(e) =>
|
||||
setSelectedDataCenter({
|
||||
...selectedDataCenter,
|
||||
externalId: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
<Col sm="6">
|
||||
<FormGroup>
|
||||
<Label for="ayposURL">{t("DataCenter.ayposURL")}</Label>
|
||||
<Input
|
||||
type="text"
|
||||
name="ayposURL"
|
||||
id="ayposURL"
|
||||
placeholder={t("DataCenter.ayposURL")}
|
||||
value={selectedDataCenter.ayposURL}
|
||||
onChange={(e) =>
|
||||
setSelectedDataCenter({
|
||||
...selectedDataCenter,
|
||||
ayposURL: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
<Col sm="6">
|
||||
<FormGroup>
|
||||
<Label for="city">{t("DataCenter.city")}</Label>
|
||||
<Input
|
||||
type="text"
|
||||
name="city"
|
||||
id="city"
|
||||
placeholder={t("DataCenter.city")}
|
||||
value={selectedDataCenter.city}
|
||||
onChange={(e) =>
|
||||
setSelectedDataCenter({
|
||||
...selectedDataCenter,
|
||||
city: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
<Col sm="12">
|
||||
<FormGroup>
|
||||
<Label for="address">{t("DataCenter.address")}</Label>
|
||||
<div className="d-flex">
|
||||
<Input
|
||||
type="text"
|
||||
name="address"
|
||||
id="address"
|
||||
placeholder={t("DataCenter.address")}
|
||||
value={selectedDataCenter.address}
|
||||
onChange={handleAddressChange}
|
||||
/>
|
||||
<Button
|
||||
color="primary"
|
||||
className="ml-1"
|
||||
onClick={() => {
|
||||
if (selectedDataCenter.address) {
|
||||
geocodeAddress(selectedDataCenter.address);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t("DataCenter.findOnMap")}
|
||||
</Button>
|
||||
</div>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
|
||||
{/* Emission Scope Section */}
|
||||
<Col sm="12">
|
||||
<h5 className="mt-3 mb-2 text-primary">Emission Scope Configuration</h5>
|
||||
</Col>
|
||||
<Col sm="6">
|
||||
<FormGroup>
|
||||
<Label for="emissionScope">Emission Scope</Label>
|
||||
<Select
|
||||
id="emissionScope"
|
||||
name="emissionScope"
|
||||
placeholder="Select emission scope"
|
||||
options={emissionScopesOptions}
|
||||
value={emissionScopesOptions?.find(
|
||||
(option) => option.value === selectedDataCenter.emissionScopeId
|
||||
)}
|
||||
onChange={(option) =>
|
||||
setSelectedDataCenter({
|
||||
...selectedDataCenter,
|
||||
emissionScopeId: option?.value,
|
||||
})
|
||||
}
|
||||
isClearable
|
||||
filterOption={customFilterForSelect}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
<Col sm="6">
|
||||
<FormGroup>
|
||||
<Label for="sector">Sector</Label>
|
||||
<Select
|
||||
id="sector"
|
||||
name="sector"
|
||||
placeholder="Select sector"
|
||||
options={sectorsOptions}
|
||||
value={sectorsOptions?.find(
|
||||
(option) => option.value === selectedDataCenter.sectorId
|
||||
)}
|
||||
onChange={(option) => {
|
||||
setSelectedSector(option?.value);
|
||||
setSelectedDataCenter({
|
||||
...selectedDataCenter,
|
||||
sectorId: option?.value,
|
||||
subSectorId: null,
|
||||
emissionSourceId: null,
|
||||
consuptionUnitId: null,
|
||||
activitySubUnitId: null,
|
||||
});
|
||||
}}
|
||||
isClearable
|
||||
filterOption={customFilterForSelect}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
<Col sm="6">
|
||||
<FormGroup>
|
||||
<Label for="subSector">Sub Sector</Label>
|
||||
<Select
|
||||
id="subSector"
|
||||
name="subSector"
|
||||
placeholder="Select sub sector"
|
||||
options={subSectorsOptions}
|
||||
value={subSectorsOptions?.find(
|
||||
(option) => option.value === selectedDataCenter.subSectorId
|
||||
)}
|
||||
onChange={(option) => {
|
||||
setSelectedSubSector(option?.value);
|
||||
setSelectedDataCenter({
|
||||
...selectedDataCenter,
|
||||
subSectorId: option?.value,
|
||||
emissionSourceId: null,
|
||||
consuptionUnitId: null,
|
||||
activitySubUnitId: null,
|
||||
});
|
||||
}}
|
||||
isClearable
|
||||
filterOption={customFilterForSelect}
|
||||
isDisabled={!selectedDataCenter.sectorId}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
<Col sm="6">
|
||||
<FormGroup>
|
||||
<Label for="emissionSource">Emission Source</Label>
|
||||
<Select
|
||||
id="emissionSource"
|
||||
name="emissionSource"
|
||||
placeholder="Select emission source"
|
||||
options={emissionSourcesOptions}
|
||||
value={emissionSourcesOptions?.find(
|
||||
(option) => option.value === selectedDataCenter.emissionSourceId
|
||||
)}
|
||||
onChange={(option) => {
|
||||
setSelectedDataCenter({
|
||||
...selectedDataCenter,
|
||||
emissionSourceId: option?.value,
|
||||
consuptionUnitId: null,
|
||||
});
|
||||
}}
|
||||
isClearable
|
||||
filterOption={customFilterForSelect}
|
||||
isDisabled={!selectedDataCenter.subSectorId}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
<Col sm="6">
|
||||
<FormGroup>
|
||||
<Label for="consuptionUnit">Consumption Unit</Label>
|
||||
<Select
|
||||
id="consuptionUnit"
|
||||
name="consuptionUnit"
|
||||
placeholder="Select consumption unit"
|
||||
options={consuptionUnitsOptions}
|
||||
value={consuptionUnitsOptions?.find(
|
||||
(option) => option.value === selectedDataCenter.consuptionUnitId
|
||||
)}
|
||||
onChange={(option) => {
|
||||
setSelectedDataCenter({
|
||||
...selectedDataCenter,
|
||||
consuptionUnitId: option?.value,
|
||||
});
|
||||
}}
|
||||
isClearable
|
||||
filterOption={customFilterForSelect}
|
||||
isDisabled={!selectedDataCenter.emissionSourceId}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
<Col sm="12">
|
||||
<FormGroup>
|
||||
<Label for="activitySubUnit">Activity Sub Unit</Label>
|
||||
<Select
|
||||
id="activitySubUnit"
|
||||
name="activitySubUnit"
|
||||
placeholder="Select activity sub unit"
|
||||
options={activitySubUnitsOptions}
|
||||
value={activitySubUnitsOptions?.find(
|
||||
(option) => option.value === selectedDataCenter.activitySubUnitId
|
||||
)}
|
||||
onChange={(option) => {
|
||||
setSelectedDataCenter({
|
||||
...selectedDataCenter,
|
||||
activitySubUnitId: option?.value,
|
||||
});
|
||||
}}
|
||||
isClearable
|
||||
filterOption={customFilterForSelect}
|
||||
isDisabled={!selectedDataCenter.subSectorId}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
|
||||
<Col sm="12">
|
||||
<FormGroup>
|
||||
<Label>{t("DataCenter.location")}</Label>
|
||||
<div style={{ height: "400px", width: "100%" }}>
|
||||
<MapContainer
|
||||
center={mapPosition || [39.9334, 32.8597]} // Default to Ankara
|
||||
zoom={13}
|
||||
style={{ height: "100%", width: "100%" }}
|
||||
>
|
||||
<TileLayer
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
/>
|
||||
<MapMarker
|
||||
position={mapPosition}
|
||||
setPosition={(pos) => {
|
||||
setMapPosition(pos);
|
||||
setSelectedDataCenter({
|
||||
...selectedDataCenter,
|
||||
latitude: pos[0],
|
||||
longitude: pos[1]
|
||||
});
|
||||
}}
|
||||
setSelectedDataCenter={setSelectedDataCenter}
|
||||
/>
|
||||
</MapContainer>
|
||||
</div>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={handleSubmit}
|
||||
disabled={!selectedDataCenter.name || !selectedDataCenter.externalId}
|
||||
>
|
||||
{t("Common.save")}
|
||||
</Button>
|
||||
<Button
|
||||
color="secondary"
|
||||
onClick={handleCloseModal}
|
||||
>
|
||||
{t("Common.cancel")}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle tag="h4">{t("DataCenter.title")}</CardTitle>
|
||||
{permissionCheck("datacenter_create") && (
|
||||
<Button
|
||||
className="ml-2"
|
||||
color="primary"
|
||||
onClick={() => setShowAddModal(true)}
|
||||
>
|
||||
<Plus size={15} />
|
||||
<span className="align-middle ml-50">{t("DataCenter.create")}</span>
|
||||
</Button>
|
||||
)}
|
||||
</CardHeader>
|
||||
<Row className="mx-0 mt-1 mb-50">
|
||||
<Col sm="6">
|
||||
<div className="d-flex align-items-center">
|
||||
<Label for="sort-select">{t("Show")}</Label>
|
||||
<Input
|
||||
className="dataTable-select"
|
||||
type="select"
|
||||
id="sort-select"
|
||||
value={rowsPerPage}
|
||||
onChange={(e) => setRowsPerPage(Number(e.target.value))}
|
||||
>
|
||||
<option value={10}>10</option>
|
||||
<option value={25}>25</option>
|
||||
<option value={50}>50</option>
|
||||
<option value={75}>75</option>
|
||||
<option value={100}>100</option>
|
||||
</Input>
|
||||
</div>
|
||||
</Col>
|
||||
<Col
|
||||
className="d-flex align-items-center justify-content-sm-end mt-sm-0 mt-1"
|
||||
sm="6"
|
||||
>
|
||||
<Label className="me-1" for="search-input">
|
||||
{t("Filter")}
|
||||
</Label>
|
||||
<Input
|
||||
className="dataTable-filter"
|
||||
type="text"
|
||||
bsSize="sm"
|
||||
id="search-input"
|
||||
value={searchValue}
|
||||
onChange={handleFilter}
|
||||
placeholder={t("DataCenter.searchPlaceholder")}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<div className="react-dataTable">
|
||||
<DataTable
|
||||
noHeader
|
||||
pagination
|
||||
columns={serverSideColumns}
|
||||
paginationPerPage={rowsPerPage}
|
||||
className="react-dataTable"
|
||||
sortIcon={<ChevronDown size={10} />}
|
||||
paginationDefaultPage={currentPage}
|
||||
paginationComponent={CustomPagination}
|
||||
data={dataCenterStore.dataCenters}
|
||||
noDataComponent={
|
||||
<div className="p-2 text-center">
|
||||
{t("Common.noDataAvailable")}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{renderModal()}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(DataCenterManagement);
|
||||
389
sge-frontend/src/views/DataInput.js
Normal file
389
sge-frontend/src/views/DataInput.js
Normal file
@@ -0,0 +1,389 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
ModalBody,
|
||||
Label,
|
||||
Button,
|
||||
Input,
|
||||
Modal,
|
||||
ModalHeader,
|
||||
ModalFooter,
|
||||
} from "reactstrap";
|
||||
import { Plus } from "react-feather";
|
||||
import Select from "react-select";
|
||||
import DataInputGroup from "../components/data-input";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getDataCenters } from "../redux/actions/dataCenter";
|
||||
import { getSectors, getSubSectors } from "../redux/actions/datas";
|
||||
import { getAreas } from "../redux/actions/areas";
|
||||
import axios from "axios";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { customFilterForSelect } from "../utility/Utils";
|
||||
|
||||
const DataInput = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const dataCenterStore = useSelector((state) => state.dataCenter);
|
||||
const datasStore = useSelector((state) => state.datas);
|
||||
const [areasOptions, setAreasOptions] = useState([]);
|
||||
const [sectorOptions, setSectorOptions] = useState([]);
|
||||
const [subSectorOptions, setSubSectorOptions] = useState([]);
|
||||
const [showImportExcelModal, setShowImportExcelModal] = useState(false);
|
||||
const [inputData, setInputData] = useState([]);
|
||||
const [excelData, setExcelData] = useState([]);
|
||||
const [dataCenterOptions, setDataCenterOptions] = useState([]);
|
||||
const [selectedDataCenter, setSelectedDataCenter] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getDataCenters());
|
||||
dispatch(getSectors());
|
||||
dispatch(getSubSectors());
|
||||
dispatch(getAreas()); // Fetch areas for UUID mapping
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (dataCenterStore?.dataCenters?.length > 0) {
|
||||
const options = dataCenterStore.dataCenters.map((dc) => ({
|
||||
value: dc.id,
|
||||
label: dc.dataCenter, // or dc.name if that's the correct field
|
||||
}));
|
||||
setDataCenterOptions(options);
|
||||
// Optionally auto-select the first data center
|
||||
if (!selectedDataCenter && options.length > 0) {
|
||||
setSelectedDataCenter(options[0]);
|
||||
}
|
||||
}
|
||||
}, [dataCenterStore, selectedDataCenter]);
|
||||
|
||||
// Instead of fetching areas separately, derive area options from selected data center
|
||||
useEffect(() => {
|
||||
if (selectedDataCenter && dataCenterStore?.dataCenters?.length > 0) {
|
||||
const dc = dataCenterStore.dataCenters.find(
|
||||
(d) => d.id === selectedDataCenter.value
|
||||
);
|
||||
if (dc && dc.area) {
|
||||
// Area tag option (use tag as both value and label since we don't have area ID from DataCenter)
|
||||
const areaOptions = dc.area.tag
|
||||
? [{ value: dc.area.tag, label: dc.area.tag, type: "area" }]
|
||||
: [];
|
||||
// City options from cityNames array
|
||||
const cityOptions = (dc.area.cityNames || []).map((cityName) => ({
|
||||
value: cityName,
|
||||
label: cityName,
|
||||
type: "city",
|
||||
}));
|
||||
// District options from districtNames array
|
||||
const districtOptions = (dc.area.districtNames || []).map((districtName) => ({
|
||||
value: districtName,
|
||||
label: districtName,
|
||||
type: "district",
|
||||
}));
|
||||
setAreasOptions([...areaOptions, ...cityOptions, ...districtOptions]);
|
||||
} else {
|
||||
setAreasOptions([]);
|
||||
}
|
||||
} else {
|
||||
setAreasOptions([]);
|
||||
}
|
||||
}, [selectedDataCenter, dataCenterStore]);
|
||||
|
||||
// Reset sector and subsector when tag, district, or scope changes
|
||||
useEffect(() => {
|
||||
setInputData(prev => ({ ...prev, sector: null, subSector: null }));
|
||||
}, [inputData.tag, inputData.district, inputData.scope]);
|
||||
|
||||
// Update sector options when sectors are loaded from Redux
|
||||
useEffect(() => {
|
||||
if (datasStore.sectors && datasStore.sectors.length > 0) {
|
||||
const sectorOpts = datasStore.sectors.map(sector => ({
|
||||
value: sector.id,
|
||||
label: sector.description || sector.tag || `Sector ${sector.sectorNo}`,
|
||||
tag: sector.tag,
|
||||
type: "sector"
|
||||
}));
|
||||
console.log('Sector options from Redux:', sectorOpts);
|
||||
setSectorOptions(sectorOpts);
|
||||
}
|
||||
}, [datasStore.sectors]);
|
||||
|
||||
// Update subsector options when a sector is selected or subsectors are loaded from Redux
|
||||
useEffect(() => {
|
||||
if (inputData.sector?.tag && datasStore.subSectors && datasStore.subSectors.length > 0) {
|
||||
// Filter subsectors for the selected sector
|
||||
const filteredSubSectors = datasStore.subSectors.filter(subSector =>
|
||||
subSector.sector && subSector.sector.tag === inputData.sector.tag
|
||||
);
|
||||
|
||||
const subSectorOpts = filteredSubSectors.map(subSector => ({
|
||||
value: subSector.id,
|
||||
label: subSector.description || subSector.tag || `SubSector ${subSector.subSectorNo}`,
|
||||
type: "subSector"
|
||||
}));
|
||||
console.log('SubSector options from Redux for sector', inputData.sector.tag, ':', subSectorOpts);
|
||||
setSubSectorOptions(subSectorOpts);
|
||||
} else {
|
||||
setSubSectorOptions([]);
|
||||
setInputData(prev => ({ ...prev, subSector: null }));
|
||||
}
|
||||
}, [inputData.sector, datasStore.subSectors]);
|
||||
|
||||
const onImportExcelButtonPressed = () => {
|
||||
setShowImportExcelModal(true);
|
||||
};
|
||||
|
||||
const onImportExcelModalButtonPressed = () => {
|
||||
if (!excelData.file || !excelData.area || !selectedDataCenter?.value) {
|
||||
enqueueSnackbar(t("DataInput.missingRequiredFields"), {
|
||||
variant: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let formdata = new FormData();
|
||||
formdata.append("file", excelData.file);
|
||||
formdata.append(excelData.area.type, excelData.area.value);
|
||||
formdata.append("dataCenter", selectedDataCenter?.value);
|
||||
|
||||
// Use the base URL from environment variables
|
||||
const baseUrl = process.env.React_APP_API_BASE_URL;
|
||||
|
||||
axios
|
||||
.post(`${baseUrl}/upload`, formdata, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
Authorization: "Bearer " + localStorage.getItem("accessToken"),
|
||||
},
|
||||
})
|
||||
.then((a) => {
|
||||
enqueueSnackbar(t("Warnings.addedSuccessfully"), {
|
||||
variant: "success",
|
||||
});
|
||||
setShowImportExcelModal(false);
|
||||
setExcelData([]);
|
||||
})
|
||||
.catch(() => {
|
||||
enqueueSnackbar(t("DataInput.failUploadExcel"), {
|
||||
variant: "error",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const renderImportExcelModal = () => {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={showImportExcelModal}
|
||||
toggle={() => setShowImportExcelModal(!showImportExcelModal)}
|
||||
className="modal-dialog-centered"
|
||||
>
|
||||
<ModalHeader
|
||||
toggle={() => setShowImportExcelModal(!showImportExcelModal)}
|
||||
>
|
||||
Excel ile Veri Girişi
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" htmlFor="select-datacenter">
|
||||
{t("DataCenters.dataCenter")}: *
|
||||
</Label>
|
||||
<Select
|
||||
id="select-datacenter"
|
||||
isClearable={false}
|
||||
closeMenuOnSelect={true}
|
||||
maxMenuHeight={150}
|
||||
menuPlacement="auto"
|
||||
className="react-select"
|
||||
placeholder=""
|
||||
filterOption={customFilterForSelect}
|
||||
options={dataCenterOptions}
|
||||
value={selectedDataCenter}
|
||||
onChange={(value) => {
|
||||
setSelectedDataCenter(value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" htmlFor="select-area">
|
||||
{t("Areas.city")}/ {t("Areas.district")}/ {t("Areas.neighborhood")}: *
|
||||
</Label>
|
||||
<Select
|
||||
id="select-area"
|
||||
isClearable={false}
|
||||
closeMenuOnSelect={true}
|
||||
maxMenuHeight={150}
|
||||
menuPlacement="auto"
|
||||
className="react-select"
|
||||
placeholder=""
|
||||
filterOption={customFilterForSelect}
|
||||
options={areasOptions}
|
||||
value={excelData?.area || ""}
|
||||
onChange={(value) => {
|
||||
setExcelData({
|
||||
...excelData,
|
||||
area: value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<Input
|
||||
type="file"
|
||||
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
|
||||
onChange={(e) => {
|
||||
setExcelData({
|
||||
...excelData,
|
||||
file: e.target.files[0],
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={onImportExcelModalButtonPressed}
|
||||
disabled={!(excelData?.file && excelData?.area)}
|
||||
>
|
||||
{t("Cruds.save")}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "2%" }}>
|
||||
<Card>
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle tag="h4">{t("DataInput.dataInput")}</CardTitle>
|
||||
<Button
|
||||
className="ml-2"
|
||||
color="primary"
|
||||
onClick={onImportExcelButtonPressed}
|
||||
>
|
||||
<Plus size={15} />
|
||||
<span className="align-middle ml-50">Excel ile Veri Girişi</span>
|
||||
</Button>
|
||||
</CardHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div className="d-flex flex-md-row flex-column">
|
||||
<div className="mb-2 col-md-3">
|
||||
<Label className="form-label" htmlFor="select-datacenter">
|
||||
{t("DataCenters.dataCenter")}: *
|
||||
</Label>
|
||||
<Select
|
||||
id="select-datacenter"
|
||||
isClearable={false}
|
||||
closeMenuOnSelect={true}
|
||||
maxMenuHeight={150}
|
||||
menuPlacement="auto"
|
||||
className="react-select"
|
||||
placeholder=""
|
||||
filterOption={customFilterForSelect}
|
||||
options={dataCenterOptions}
|
||||
value={selectedDataCenter}
|
||||
onChange={(value) => {
|
||||
setSelectedDataCenter(value);
|
||||
setInputData({ ...inputData, tag: null, city: null, district: null, sector: null, subSector: null });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-2 col-md-3">
|
||||
<Label className="form-label" htmlFor="select-tag">
|
||||
{t("Areas.tag")}: *
|
||||
</Label>
|
||||
<Select
|
||||
id="select-tag"
|
||||
isClearable={false}
|
||||
closeMenuOnSelect={true}
|
||||
maxMenuHeight={150}
|
||||
menuPlacement="auto"
|
||||
className="react-select"
|
||||
placeholder=""
|
||||
filterOption={customFilterForSelect}
|
||||
options={areasOptions.filter(opt => opt.type === "area")}
|
||||
value={areasOptions.find(opt => opt.type === "area" && opt.value === inputData.tag) || null}
|
||||
onChange={(value) => {
|
||||
setInputData({ ...inputData, tag: value ? value.value : null, city: null, district: null, sector: null, subSector: null });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{inputData.tag && (
|
||||
<>
|
||||
<div className="mb-2 col-md-3">
|
||||
<Label className="form-label" htmlFor="select-city">
|
||||
{t("Areas.city")}
|
||||
</Label>
|
||||
<Select
|
||||
id="select-city"
|
||||
isClearable={false}
|
||||
closeMenuOnSelect={true}
|
||||
maxMenuHeight={150}
|
||||
menuPlacement="auto"
|
||||
className="react-select"
|
||||
placeholder=""
|
||||
filterOption={customFilterForSelect}
|
||||
options={areasOptions.filter(opt => opt.type === "city")}
|
||||
value={areasOptions.find(opt => opt.type === "city" && opt.value === inputData.city) || null}
|
||||
onChange={(value) => {
|
||||
setInputData({
|
||||
...inputData,
|
||||
city: value ? value.value : null,
|
||||
area: value, // Set area for DataInputGroup
|
||||
district: null,
|
||||
sector: null,
|
||||
subSector: null
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-2 col-md-3">
|
||||
<Label className="form-label" htmlFor="select-district">
|
||||
{t("Areas.district")}
|
||||
</Label>
|
||||
<Select
|
||||
id="select-district"
|
||||
isClearable={false}
|
||||
closeMenuOnSelect={true}
|
||||
maxMenuHeight={150}
|
||||
menuPlacement="auto"
|
||||
className="react-select"
|
||||
placeholder=""
|
||||
filterOption={customFilterForSelect}
|
||||
options={areasOptions.filter(opt => opt.type === "district")}
|
||||
value={areasOptions.find(opt => opt.type === "district" && opt.value === inputData.district) || null}
|
||||
onChange={(value) => {
|
||||
setInputData({
|
||||
...inputData,
|
||||
district: value ? value.value : null,
|
||||
area: value, // Set area for DataInputGroup
|
||||
sector: null,
|
||||
subSector: null
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<DataInputGroup
|
||||
inputData={inputData}
|
||||
setInputData={setInputData}
|
||||
selectedDataCenter={selectedDataCenter}
|
||||
hideSectorSelection={true}
|
||||
sectorOptions={sectorOptions}
|
||||
subSectorOptions={subSectorOptions}
|
||||
/>
|
||||
</ModalBody>
|
||||
</Card>
|
||||
{renderImportExcelModal()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataInput;
|
||||
224
sge-frontend/src/views/DataSet/ActivitySubUnit.js
Normal file
224
sge-frontend/src/views/DataSet/ActivitySubUnit.js
Normal file
@@ -0,0 +1,224 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ChevronDown } from "react-feather";
|
||||
import ReactPaginate from "react-paginate";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Label,
|
||||
Input,
|
||||
Col,
|
||||
Row,
|
||||
} from "reactstrap";
|
||||
import DataTable from "react-data-table-component";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getActivitySubUnits } from "../../redux/actions/datas";
|
||||
|
||||
const ActivitySubUnits = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const serverSideColumns = [
|
||||
{
|
||||
name: t("EmissionSources.subUnits"),
|
||||
selector: (row) => row.tag,
|
||||
sortable: true,
|
||||
minWidth: "250px",
|
||||
},
|
||||
{
|
||||
name: t("EmissionSources.subSector"),
|
||||
selector: (row) => row.subSector,
|
||||
sortable: false,
|
||||
minWidth: "250px",
|
||||
cell: (row) => <span> {row?.subSector?.tag}</span>,
|
||||
},
|
||||
{
|
||||
name: t("EmissionSources.sector"),
|
||||
selector: (row) => row.subSector,
|
||||
sortable: false,
|
||||
minWidth: "250px",
|
||||
cell: (row) => <span> {row?.subSector?.sector?.tag}</span>,
|
||||
},
|
||||
];
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(5);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
|
||||
const subSectorsStore = useSelector((state) => state.datas);
|
||||
const [activitySubUnits, setActivitySubUnits] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getActivitySubUnits());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (subSectorsStore) {
|
||||
if (
|
||||
subSectorsStore.activitySubUnits.length <=
|
||||
currentPage * rowsPerPage
|
||||
) {
|
||||
setCurrentPage(1);
|
||||
setActivitySubUnits(
|
||||
subSectorsStore.activitySubUnits.slice(0, rowsPerPage)
|
||||
);
|
||||
} else {
|
||||
setActivitySubUnits(
|
||||
subSectorsStore.activitySubUnits?.slice(
|
||||
currentPage * rowsPerPage - rowsPerPage,
|
||||
currentPage * rowsPerPage
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [subSectorsStore?.activitySubUnits?.length]);
|
||||
|
||||
const handleFilter = (e) => {
|
||||
setSearchValue(e.target.value);
|
||||
|
||||
if (e.target.value !== "") {
|
||||
setActivitySubUnits(
|
||||
subSectorsStore.activitySubUnits
|
||||
.filter((sector) =>
|
||||
sector?.tag
|
||||
.toUpperCase()
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
.includes(
|
||||
e.target.value
|
||||
.toUpperCase()
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
)
|
||||
)
|
||||
.slice(
|
||||
currentPage * rowsPerPage - rowsPerPage,
|
||||
currentPage * rowsPerPage
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setActivitySubUnits(
|
||||
subSectorsStore.activitySubUnits?.slice(
|
||||
currentPage * rowsPerPage - rowsPerPage,
|
||||
currentPage * rowsPerPage
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePagination = (page) => {
|
||||
setCurrentPage(page.selected + 1);
|
||||
setActivitySubUnits(
|
||||
subSectorsStore.activitySubUnits.slice(
|
||||
(page.selected + 1) * rowsPerPage - rowsPerPage,
|
||||
(page.selected + 1) * rowsPerPage
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const handlePerPage = (e) => {
|
||||
setRowsPerPage(parseInt(e.target.value));
|
||||
setActivitySubUnits(
|
||||
subSectorsStore.activitySubUnits.slice(
|
||||
currentPage * parseInt(e.target.value) - parseInt(e.target.value),
|
||||
currentPage * parseInt(e.target.value)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const CustomPagination = () => {
|
||||
const count = Number(
|
||||
(subSectorsStore.activitySubUnits.length / rowsPerPage).toFixed(1)
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactPaginate
|
||||
previousLabel={""}
|
||||
nextLabel={""}
|
||||
breakLabel="..."
|
||||
pageCount={count || 1}
|
||||
marginPagesDisplayed={2}
|
||||
pageRangeDisplayed={2}
|
||||
activeClassName="active"
|
||||
forcePage={currentPage !== 0 ? currentPage - 1 : 0}
|
||||
onPageChange={(page) => handlePagination(page)}
|
||||
pageClassName={"page-item"}
|
||||
nextLinkClassName={"page-link"}
|
||||
nextClassName={"page-item next"}
|
||||
previousClassName={"page-item prev"}
|
||||
previousLinkClassName={"page-link"}
|
||||
pageLinkClassName={"page-link"}
|
||||
breakClassName="page-item"
|
||||
breakLinkClassName="page-link"
|
||||
containerClassName={
|
||||
"pagination react-paginate separated-pagination pagination-sm justify-content-end pr-1 mt-1"
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "2%" }}>
|
||||
<Card>
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle tag="h4">{t("EmissionSources.subUnits")}</CardTitle>
|
||||
</CardHeader>
|
||||
<Row className="mx-0 mt-1 mb-50">
|
||||
<Col sm="6" md="2">
|
||||
<div className="d-flex align-items-center">
|
||||
<Label for="sort-select">{t("Show")}</Label>
|
||||
<Input
|
||||
className="ml-1 dataTable-select"
|
||||
type="select"
|
||||
id="sort-select"
|
||||
value={rowsPerPage}
|
||||
onChange={(e) => handlePerPage(e)}
|
||||
>
|
||||
<option value={5}>5</option>
|
||||
<option value={10}>10</option>
|
||||
<option value={25}>25</option>
|
||||
<option value={50}>50</option>
|
||||
<option value={75}>75</option>
|
||||
<option value={100}>100</option>
|
||||
</Input>
|
||||
</div>
|
||||
</Col>
|
||||
<Col
|
||||
className="d-flex align-items-center justify-content-end mt-sm-0 mt-1 ml-md-auto"
|
||||
sm="6"
|
||||
md="3"
|
||||
>
|
||||
<Input
|
||||
className="dataTable-filter"
|
||||
type="text"
|
||||
bsSize="sm"
|
||||
id="search-input"
|
||||
value={searchValue}
|
||||
onChange={handleFilter}
|
||||
placeholder={t("ByName") + " " + t("Filter")}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<DataTable
|
||||
noHeader
|
||||
pagination
|
||||
paginationServer
|
||||
className="react-dataTable"
|
||||
columns={serverSideColumns}
|
||||
sortIcon={<ChevronDown size={10} />}
|
||||
paginationComponent={CustomPagination}
|
||||
data={[...activitySubUnits]}
|
||||
noDataComponent={
|
||||
<p className="p-2">
|
||||
{t("EmissionSources.subUnit")}
|
||||
{t("Warnings.notFound")}
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActivitySubUnits;
|
||||
1245
sge-frontend/src/views/DataSet/EmissionSource.js
Normal file
1245
sge-frontend/src/views/DataSet/EmissionSource.js
Normal file
File diff suppressed because it is too large
Load Diff
149
sge-frontend/src/views/DataSet/MainDataTables.js
Normal file
149
sge-frontend/src/views/DataSet/MainDataTables.js
Normal file
@@ -0,0 +1,149 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { MaterialReactTable } from "material-react-table";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Card, CardHeader, CardTitle, Alert } from "reactstrap";
|
||||
import { getVMEmissionSummary } from "../../redux/actions/mainDataTables/index";
|
||||
import { editNumbers } from "../../components/edit-numbers";
|
||||
|
||||
function MainDataTables() {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const mainDataTablesStore = useSelector((state) => state.mainDataTables);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
// Fetch VM emission data
|
||||
dispatch(getVMEmissionSummary());
|
||||
} catch (err) {
|
||||
console.error('Error in MainDataTables:', err);
|
||||
setError(err.message);
|
||||
}
|
||||
}, [dispatch]);
|
||||
|
||||
// Debug log for store data
|
||||
useEffect(() => {
|
||||
console.log('Current store data:', mainDataTablesStore);
|
||||
}, [mainDataTablesStore]);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
header: "VM ID",
|
||||
accessorKey: "vmId",
|
||||
Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span>,
|
||||
},
|
||||
{
|
||||
header: "Data Center",
|
||||
accessorKey: "dataCenter",
|
||||
Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span>,
|
||||
},
|
||||
{
|
||||
header: "Project",
|
||||
accessorKey: "project",
|
||||
Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span>,
|
||||
},
|
||||
{
|
||||
header: "Physical Machine",
|
||||
accessorKey: "physicalMachine",
|
||||
Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span>,
|
||||
},
|
||||
{
|
||||
header: "Virtual Machine",
|
||||
accessorKey: "vmName",
|
||||
Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span>,
|
||||
},
|
||||
{
|
||||
header: "VM Power (W)",
|
||||
accessorKey: "vmPower",
|
||||
Cell: ({ cell }) => <span>{editNumbers(cell.getValue()) || "-"}</span>,
|
||||
},
|
||||
{
|
||||
header: "VM Status",
|
||||
accessorKey: "vmStatus",
|
||||
Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span>,
|
||||
},
|
||||
{
|
||||
header: "Total Emissions",
|
||||
accessorKey: "totalEmission",
|
||||
Cell: ({ cell }) => <span>{editNumbers(cell.getValue()) || "-"}</span>,
|
||||
},
|
||||
{
|
||||
header: "CO2",
|
||||
accessorKey: "co2",
|
||||
Cell: ({ cell }) => <span>{editNumbers(cell.getValue()) || "-"}</span>,
|
||||
},
|
||||
{
|
||||
header: "CH4",
|
||||
accessorKey: "ch4",
|
||||
Cell: ({ cell }) => <span>{editNumbers(cell.getValue()) || "-"}</span>,
|
||||
},
|
||||
{
|
||||
header: "N2O",
|
||||
accessorKey: "n2o",
|
||||
Cell: ({ cell }) => <span>{editNumbers(cell.getValue()) || "-"}</span>,
|
||||
},
|
||||
{
|
||||
header: "Created Date",
|
||||
accessorKey: "createdDate",
|
||||
Cell: ({ cell }) => (
|
||||
<span>
|
||||
{cell.getValue() ? new Date(cell.getValue()).toLocaleString() : "-"}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const tableData = mainDataTablesStore?.vmEmissionSummary || [];
|
||||
console.log('VM Emission data:', tableData);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Alert color="danger" className="m-2">
|
||||
Error loading data: {error}
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "2%" }}>
|
||||
<Card>
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle tag="h4">{t("Carbon Emission Data")}</CardTitle>
|
||||
</CardHeader>
|
||||
<MaterialReactTable
|
||||
columns={columns}
|
||||
data={tableData}
|
||||
enableColumnFilters={true}
|
||||
enableFilters={true}
|
||||
enableGlobalFilter={true}
|
||||
enablePagination={true}
|
||||
enableColumnResizing={true}
|
||||
enableStickyHeader={true}
|
||||
muiTableContainerProps={{ sx: { maxHeight: 'calc(100vh - 180px)' } }}
|
||||
muiTableProps={{
|
||||
sx: {
|
||||
tableLayout: 'auto',
|
||||
},
|
||||
}}
|
||||
initialState={{
|
||||
pagination: {
|
||||
pageSize: 100,
|
||||
pageIndex: 0
|
||||
},
|
||||
sorting: [
|
||||
{ id: 'dataCenter', desc: false }
|
||||
],
|
||||
density: 'compact'
|
||||
}}
|
||||
state={{
|
||||
isLoading: !mainDataTablesStore?.vmEmissionSummary
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { MainDataTables };
|
||||
export default MainDataTables;
|
||||
79
sge-frontend/src/views/DataSet/Sector.js
Normal file
79
sge-frontend/src/views/DataSet/Sector.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ChevronDown } from "react-feather";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { Card, CardHeader, CardTitle } from "reactstrap";
|
||||
import DataTable from "react-data-table-component";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getSectors } from "../../redux/actions/datas";
|
||||
|
||||
const Sectors = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const serverSideColumns = [
|
||||
{
|
||||
name: t("EmissionSources.sector"),
|
||||
selector: (row) => row.tag,
|
||||
sortable: true,
|
||||
minWidth: "250px",
|
||||
},
|
||||
{
|
||||
name: t("EmissionSources.subSector"),
|
||||
selector: (row) => row.subSectors,
|
||||
sortable: false,
|
||||
minWidth: "250px",
|
||||
cell: (row) => (
|
||||
<span>
|
||||
{row?.subSectors?.length > 0
|
||||
? row?.subSectors?.map((subSector) => subSector?.tag).join(", ")
|
||||
: "-"}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const sectorsStore = useSelector((state) => state.datas);
|
||||
const [sectors, setSectors] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getSectors());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (sectorsStore) {
|
||||
setSectors(sectorsStore.sectors);
|
||||
}
|
||||
}, [sectorsStore?.sectors?.length]);
|
||||
|
||||
const CustomPagination = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "2%" }}>
|
||||
<Card>
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle tag="h4">{t("EmissionSources.sectors")}</CardTitle>
|
||||
</CardHeader>
|
||||
<DataTable
|
||||
noHeader
|
||||
pagination
|
||||
paginationServer
|
||||
className="react-dataTable"
|
||||
columns={serverSideColumns}
|
||||
sortIcon={<ChevronDown size={10} />}
|
||||
paginationComponent={CustomPagination}
|
||||
data={[...sectors]}
|
||||
noDataComponent={
|
||||
<p className="p-2">
|
||||
{t("EmissionSources.sector")}
|
||||
{t("Warnings.notFound")}
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sectors;
|
||||
212
sge-frontend/src/views/DataSet/SubSector.js
Normal file
212
sge-frontend/src/views/DataSet/SubSector.js
Normal file
@@ -0,0 +1,212 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ChevronDown } from "react-feather";
|
||||
import ReactPaginate from "react-paginate";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Label,
|
||||
Input,
|
||||
Col,
|
||||
Row,
|
||||
} from "reactstrap";
|
||||
import DataTable from "react-data-table-component";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getSubSectors } from "../../redux/actions/datas";
|
||||
|
||||
const SubSectors = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const serverSideColumns = [
|
||||
{
|
||||
name: t("EmissionSources.subSector"),
|
||||
selector: (row) => row.tag,
|
||||
sortable: true,
|
||||
minWidth: "250px",
|
||||
},
|
||||
{
|
||||
name: t("EmissionSources.sector"),
|
||||
selector: (row) => row.sector,
|
||||
sortable: false,
|
||||
minWidth: "250px",
|
||||
cell: (row) => <span> {row?.sector?.tag}</span>,
|
||||
},
|
||||
];
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(5);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
|
||||
const subSectorsStore = useSelector((state) => state.datas);
|
||||
const [subSectors, setSubSectors] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getSubSectors());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (subSectorsStore) {
|
||||
if (subSectorsStore.subSectors.length <= currentPage * rowsPerPage) {
|
||||
setCurrentPage(1);
|
||||
setSubSectors(subSectorsStore.subSectors.slice(0, rowsPerPage));
|
||||
} else {
|
||||
setSubSectors(
|
||||
subSectorsStore.subSectors?.slice(
|
||||
currentPage * rowsPerPage - rowsPerPage,
|
||||
currentPage * rowsPerPage
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [subSectorsStore?.subSectors?.length]);
|
||||
|
||||
const handleFilter = (e) => {
|
||||
setSearchValue(e.target.value);
|
||||
|
||||
if (e.target.value !== "") {
|
||||
setSubSectors(
|
||||
subSectorsStore.subSectors
|
||||
.filter((sector) =>
|
||||
sector?.tag
|
||||
.toUpperCase()
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
.includes(
|
||||
e.target.value
|
||||
.toUpperCase()
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
)
|
||||
)
|
||||
.slice(
|
||||
currentPage * rowsPerPage - rowsPerPage,
|
||||
currentPage * rowsPerPage
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setSubSectors(
|
||||
subSectorsStore.subSectors?.slice(
|
||||
currentPage * rowsPerPage - rowsPerPage,
|
||||
currentPage * rowsPerPage
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePagination = (page) => {
|
||||
setCurrentPage(page.selected + 1);
|
||||
setSubSectors(
|
||||
subSectorsStore.subSectors.slice(
|
||||
(page.selected + 1) * rowsPerPage - rowsPerPage,
|
||||
(page.selected + 1) * rowsPerPage
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const handlePerPage = (e) => {
|
||||
setRowsPerPage(parseInt(e.target.value));
|
||||
setSubSectors(
|
||||
subSectorsStore.subSectors.slice(
|
||||
currentPage * parseInt(e.target.value) - parseInt(e.target.value),
|
||||
currentPage * parseInt(e.target.value)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const CustomPagination = () => {
|
||||
const count = Number(
|
||||
(subSectorsStore.subSectors.length / rowsPerPage).toFixed(1)
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactPaginate
|
||||
previousLabel={""}
|
||||
nextLabel={""}
|
||||
breakLabel="..."
|
||||
pageCount={count || 1}
|
||||
marginPagesDisplayed={2}
|
||||
pageRangeDisplayed={2}
|
||||
activeClassName="active"
|
||||
forcePage={currentPage !== 0 ? currentPage - 1 : 0}
|
||||
onPageChange={(page) => handlePagination(page)}
|
||||
pageClassName={"page-item"}
|
||||
nextLinkClassName={"page-link"}
|
||||
nextClassName={"page-item next"}
|
||||
previousClassName={"page-item prev"}
|
||||
previousLinkClassName={"page-link"}
|
||||
pageLinkClassName={"page-link"}
|
||||
breakClassName="page-item"
|
||||
breakLinkClassName="page-link"
|
||||
containerClassName={
|
||||
"pagination react-paginate separated-pagination pagination-sm justify-content-end pr-1 mt-1"
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "2%" }}>
|
||||
<Card>
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle tag="h4">{t("EmissionSources.subSectors")}</CardTitle>
|
||||
</CardHeader>
|
||||
<Row className="mx-0 mt-1 mb-50">
|
||||
<Col sm="6" md="2">
|
||||
<div className="d-flex align-items-center">
|
||||
<Label for="sort-select">{t("Show")}</Label>
|
||||
<Input
|
||||
className="ml-1 dataTable-select"
|
||||
type="select"
|
||||
id="sort-select"
|
||||
value={rowsPerPage}
|
||||
onChange={(e) => handlePerPage(e)}
|
||||
>
|
||||
<option value={5}>5</option>
|
||||
<option value={10}>10</option>
|
||||
<option value={25}>25</option>
|
||||
<option value={50}>50</option>
|
||||
<option value={75}>75</option>
|
||||
<option value={100}>100</option>
|
||||
</Input>
|
||||
</div>
|
||||
</Col>
|
||||
<Col
|
||||
className="d-flex align-items-center justify-content-end mt-sm-0 mt-1 ml-md-auto"
|
||||
sm="6"
|
||||
md="3"
|
||||
>
|
||||
<Input
|
||||
className="dataTable-filter"
|
||||
type="text"
|
||||
bsSize="sm"
|
||||
id="search-input"
|
||||
value={searchValue}
|
||||
onChange={handleFilter}
|
||||
placeholder={t("ByName") + " " + t("Filter")}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<DataTable
|
||||
noHeader
|
||||
pagination
|
||||
paginationServer
|
||||
className="react-dataTable"
|
||||
columns={serverSideColumns}
|
||||
sortIcon={<ChevronDown size={10} />}
|
||||
paginationComponent={CustomPagination}
|
||||
data={[...subSectors]}
|
||||
noDataComponent={
|
||||
<p className="p-2">
|
||||
{t("EmissionSources.subSector")}
|
||||
{t("Warnings.notFound")}
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SubSectors;
|
||||
30
sge-frontend/src/views/Error.js
Normal file
30
sge-frontend/src/views/Error.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Button } from "reactstrap";
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
import "../@core/scss/base/pages/page-misc.scss";
|
||||
|
||||
const Error = () => {
|
||||
const history = useHistory();
|
||||
return (
|
||||
<div className="misc-wrapper">
|
||||
<div className="misc-inner p-2 p-sm-3">
|
||||
<div className="w-100 text-center">
|
||||
<h2 className="mb-1">Aradığınız sayfa bulunamadı!</h2>
|
||||
<button
|
||||
style={{
|
||||
backgroundColor: "#028a4a",
|
||||
color: "white",
|
||||
border: "5px solid transparent",
|
||||
borderRadius: "5px",
|
||||
}}
|
||||
className="w-100 mb-2"
|
||||
onClick={() => history.replace("/")}
|
||||
>
|
||||
Anasayfa
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Error;
|
||||
236
sge-frontend/src/views/ForgotPassword.js
Normal file
236
sge-frontend/src/views/ForgotPassword.js
Normal file
@@ -0,0 +1,236 @@
|
||||
import React, { useState, useContext } from "react";
|
||||
import {
|
||||
Card,
|
||||
Box,
|
||||
Button,
|
||||
FormControl,
|
||||
Grid,
|
||||
InputLabel,
|
||||
OutlinedInput,
|
||||
Stack,
|
||||
Typography,
|
||||
FormHelperText,
|
||||
} from "@mui/material";
|
||||
import backgroundImage from "../assets/images/background_dot.png";
|
||||
import { ThemeColors } from "../utility/context/ThemeColors.js";
|
||||
import { ArrowLeft } from "react-feather";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSnackbar } from "notistack";
|
||||
import ApplicationService from "../services/ApplicationService.js";
|
||||
|
||||
const ForgotPassword = () => {
|
||||
const history = useHistory();
|
||||
const { t } = useTranslation();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const [email, setEmail] = useState("");
|
||||
const [error, setError] = useState({
|
||||
email: "",
|
||||
});
|
||||
const { colors } = useContext(ThemeColors);
|
||||
|
||||
const isEmail = (email) => /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]/i.test(email);
|
||||
|
||||
const onInputChange = (e) => {
|
||||
setEmail(e.target.value);
|
||||
validateInput(e);
|
||||
};
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
if (!isEmail(email) || email === "") {
|
||||
setError({
|
||||
email: t("PasswordLabel.enterEmail"),
|
||||
});
|
||||
} else {
|
||||
localStorage.setItem("userEmail", email);
|
||||
await ApplicationService.http()
|
||||
.post(
|
||||
"/graphql",
|
||||
{
|
||||
query: `
|
||||
mutation {
|
||||
doForgetPassword(email: "${email}")
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer " + localStorage.getItem("accessToken"),
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.data?.errors) {
|
||||
enqueueSnackbar("Email " + t("Warnings.notFound"), {
|
||||
variant: "error",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
} else {
|
||||
enqueueSnackbar(t("PasswordLabel.redirectToReset"), {
|
||||
variant: "success",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
history.push("/sifre-sifirlama");
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const validateInput = (e) => {
|
||||
let { name, value } = e.target;
|
||||
setError((prev) => {
|
||||
const stateObj = { ...prev, [name]: "" };
|
||||
|
||||
switch (name) {
|
||||
case "email":
|
||||
if (!value) {
|
||||
stateObj[name] = t("PasswordLabel.enterEmail");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return stateObj;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
direction="column"
|
||||
justifyContent="flex-end"
|
||||
sx={{ minHeight: "100vh" }}
|
||||
style={{
|
||||
backgroundImage: `url(${backgroundImage})`,
|
||||
backgroundRepeat: "repeat",
|
||||
}}
|
||||
>
|
||||
<Grid
|
||||
container
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
sx={{ minHeight: "calc(100vh - 68px)" }}
|
||||
>
|
||||
<Grid item sx={{ m: { xs: 1, sm: 3 }, mb: 0 }}>
|
||||
<Card
|
||||
sx={{
|
||||
maxWidth: { xs: 400, lg: 475 },
|
||||
margin: { xs: 2.5, md: 3 },
|
||||
"& > *": {
|
||||
flexGrow: 1,
|
||||
flexBasis: "50%",
|
||||
},
|
||||
}}
|
||||
content="false"
|
||||
>
|
||||
<Button
|
||||
disableElevation
|
||||
color="success"
|
||||
variant="contained"
|
||||
onClick={() => history.push("/login")}
|
||||
>
|
||||
<ArrowLeft />
|
||||
</Button>
|
||||
<Box sx={{ p: { xs: 2, sm: 3, xl: 5 } }}>
|
||||
<Grid
|
||||
container
|
||||
spacing={2}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Grid item xs={12}>
|
||||
<Grid
|
||||
container
|
||||
direction="column-reverse"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Grid item>
|
||||
<Stack
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
spacing={1}
|
||||
>
|
||||
<Typography
|
||||
color="#028a4a"
|
||||
gutterBottom
|
||||
variant="h3"
|
||||
style={{
|
||||
fontSize: "1.25rem",
|
||||
color: colors.primary.dark,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{t("PasswordLabel.forgotPassword")}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="caption"
|
||||
fontSize="15px"
|
||||
textAlign="center"
|
||||
style={{
|
||||
color: "rgba(158, 158, 158, 0.7)",
|
||||
fontWeight: 400,
|
||||
lineHeight: 1.66,
|
||||
maxWidth: 300,
|
||||
}}
|
||||
>
|
||||
{t("PasswordLabel.enterEmail")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<form noValidate onSubmit={handleSubmit} id="login-form">
|
||||
<FormControl
|
||||
fullWidth
|
||||
error={Boolean(error.email)}
|
||||
sx={{ ...colors.primary.light }}
|
||||
>
|
||||
<InputLabel htmlFor="outlined-adornment-email">
|
||||
Email
|
||||
</InputLabel>
|
||||
<OutlinedInput
|
||||
id="outlined-adornment-email"
|
||||
type="email"
|
||||
value={email}
|
||||
name="email"
|
||||
onBlur={validateInput}
|
||||
onChange={onInputChange}
|
||||
label="Email"
|
||||
/>
|
||||
</FormControl>
|
||||
{error?.email && (
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<FormHelperText error>{error.email}</FormHelperText>
|
||||
</Box>
|
||||
)}
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Button
|
||||
disableElevation
|
||||
fullWidth
|
||||
size="large"
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="success"
|
||||
id="login-form-submit-button"
|
||||
>
|
||||
{t("PasswordLabel.send")}
|
||||
</Button>
|
||||
</Box>
|
||||
</form>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default ForgotPassword;
|
||||
1025
sge-frontend/src/views/Graphics.js
Normal file
1025
sge-frontend/src/views/Graphics.js
Normal file
File diff suppressed because it is too large
Load Diff
311
sge-frontend/src/views/Login.js
Normal file
311
sge-frontend/src/views/Login.js
Normal file
@@ -0,0 +1,311 @@
|
||||
import React, { useState, useContext, useEffect } from "react";
|
||||
import {
|
||||
Card,
|
||||
Box,
|
||||
Button,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
Grid,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
InputLabel,
|
||||
OutlinedInput,
|
||||
Stack,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import Visibility from "@mui/icons-material/Visibility";
|
||||
import VisibilityOff from "@mui/icons-material/VisibilityOff";
|
||||
import { ThemeColors } from "../utility/context/ThemeColors";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { DefaultRoute } from "../router/routes";
|
||||
import { handleLogin } from "../redux/actions/auth";
|
||||
import backgroundImage from "../assets/images/background_dot.png";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Login = () => {
|
||||
const dispatch = useDispatch();
|
||||
const history = useHistory();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const { t } = useTranslation();
|
||||
const authState = useSelector((state) => state.auth);
|
||||
const { colors } = useContext(ThemeColors);
|
||||
const [emailTouched, setEmailTouched] = useState(false);
|
||||
const [passwordTouched, setPasswordTouched] = useState(false);
|
||||
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
|
||||
const [submitError, setSubmitError] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
const handleChange = (event) => {
|
||||
if (event.target.name === "email") {
|
||||
setEmail(event.target.value);
|
||||
setEmailTouched(true);
|
||||
} else if (event.target.name === "password") {
|
||||
setPassword(event.target.value);
|
||||
setPasswordTouched(true);
|
||||
}
|
||||
setSubmitError(null);
|
||||
};
|
||||
|
||||
const handleBlur = (event) => {
|
||||
if (event.target.name === "email") {
|
||||
setEmail(event.target.value);
|
||||
setEmailTouched(false);
|
||||
} else if (event.target.name === "password") {
|
||||
setPassword(event.target.value);
|
||||
setPasswordTouched(false);
|
||||
}
|
||||
setSubmitError(null);
|
||||
};
|
||||
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
if (email === "" || password === "") {
|
||||
setSubmitError(t("Warnings.required"));
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
dispatch(handleLogin(email, password))
|
||||
.then((data) => {
|
||||
setLoading(false);
|
||||
enqueueSnackbar(
|
||||
data.user.firstName +
|
||||
" " +
|
||||
data.user.lastName +
|
||||
" " +
|
||||
t("Auth.loginMessage"),
|
||||
{
|
||||
variant: "success",
|
||||
preventDuplicate: true,
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
setLoading(false);
|
||||
enqueueSnackbar(error.message, {
|
||||
variant: "error",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
setSubmitError(error.message);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (authState.isLoggedIn === true) {
|
||||
history.push("/");
|
||||
window.location.reload();
|
||||
}
|
||||
}, [authState.isLoggedIn]);
|
||||
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
direction="column"
|
||||
justifyContent="flex-end"
|
||||
sx={{ minHeight: "100vh" }}
|
||||
style={{
|
||||
backgroundImage: `url(${backgroundImage})`,
|
||||
backgroundRepeat: "repeat",
|
||||
}}
|
||||
>
|
||||
<Grid
|
||||
container
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
sx={{ minHeight: "calc(100vh - 68px)" }}
|
||||
>
|
||||
<Grid item sx={{ m: { xs: 1, sm: 3 }, mb: 0 }}>
|
||||
<Card
|
||||
sx={{
|
||||
maxWidth: { xs: 400, lg: 475 },
|
||||
margin: { xs: 2.5, md: 3 },
|
||||
"& > *": {
|
||||
flexGrow: 1,
|
||||
flexBasis: "50%",
|
||||
},
|
||||
}}
|
||||
content={"false"}
|
||||
>
|
||||
<Box sx={{ p: { xs: 2, sm: 3, xl: 5 } }}>
|
||||
<Grid
|
||||
container
|
||||
spacing={2}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
marginBottom={4}
|
||||
>
|
||||
<img src="./Secapsoft.png" width="150px" />
|
||||
</Grid>
|
||||
<Grid
|
||||
container
|
||||
spacing={2}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Grid item xs={12}>
|
||||
<Grid
|
||||
container
|
||||
direction="column-reverse"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Grid item>
|
||||
<Stack
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
spacing={1}
|
||||
>
|
||||
<Typography
|
||||
color={"#028a4a"}
|
||||
gutterBottom
|
||||
variant="h3"
|
||||
style={{
|
||||
fontSize: "1.25rem",
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{t("Auth.welcome")}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="caption"
|
||||
fontSize="15px"
|
||||
textAlign="center"
|
||||
style={{
|
||||
color: "rgba(158, 158, 158, 0.7)",
|
||||
fontWeight: 400,
|
||||
lineHeight: 1.66,
|
||||
maxWidth: 300,
|
||||
}}
|
||||
>
|
||||
{t("Auth.authorized")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<form noValidate onSubmit={handleSubmit} id="login-form">
|
||||
<FormControl
|
||||
fullWidth
|
||||
error={Boolean(emailTouched, submitError)}
|
||||
sx={{ ...colors.primary.light }}
|
||||
>
|
||||
<InputLabel htmlFor="outlined-adornment-email-login">
|
||||
Email
|
||||
</InputLabel>
|
||||
<OutlinedInput
|
||||
id="login-email-input"
|
||||
type="email"
|
||||
value={email}
|
||||
name="email"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
label="Email"
|
||||
inputProps={{}}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormControl
|
||||
fullWidth
|
||||
error={Boolean(passwordTouched, submitError)}
|
||||
sx={{ ...colors.primary.light }}
|
||||
style={{
|
||||
marginTop: "1rem",
|
||||
}}
|
||||
>
|
||||
<InputLabel htmlFor="outlined-adornment-password-login">
|
||||
{t("PasswordLabel.passwordLabel")}
|
||||
</InputLabel>
|
||||
<OutlinedInput
|
||||
id="outlined-adornment-password-login"
|
||||
type={showPassword ? "text" : "password"}
|
||||
value={password}
|
||||
name="password"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
endAdornment={
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label="toggle password visibility"
|
||||
onClick={() => setShowPassword(true)}
|
||||
onMouseDown={() => setShowPassword(false)}
|
||||
edge="end"
|
||||
size="large"
|
||||
>
|
||||
{showPassword ? (
|
||||
<Visibility />
|
||||
) : (
|
||||
<VisibilityOff />
|
||||
)}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
}
|
||||
label={t("PasswordLabel.passwordLabel")}
|
||||
inputProps={{}}
|
||||
/>
|
||||
</FormControl>
|
||||
{submitError && (
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<FormHelperText error>{submitError}</FormHelperText>
|
||||
</Box>
|
||||
)}
|
||||
<a>
|
||||
<Typography
|
||||
onClick={() => history.push("/sifremi-unuttum")}
|
||||
variant="subtitle1"
|
||||
sx={{ textDecoration: "none", cursor: "pointer" }}
|
||||
style={{
|
||||
fontSize: "0.875rem",
|
||||
color: "#028a4a",
|
||||
cursor: "pointer",
|
||||
textAlign: "right",
|
||||
paddingTop: 20,
|
||||
}}
|
||||
>
|
||||
{t("Auth.didUForgotPassword")}
|
||||
</Typography>
|
||||
</a>
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Button
|
||||
disableElevation
|
||||
disabled={loading}
|
||||
fullWidth
|
||||
size="large"
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="success"
|
||||
id="login-form-submit-button"
|
||||
>
|
||||
{t("Auth.login")}
|
||||
</Button>
|
||||
</Box>
|
||||
</form>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<div className="d-flex justify-content-center align-items-center">
|
||||
<img
|
||||
src="./enerji-istanbul-logo.png"
|
||||
alt="istanbul enerji"
|
||||
width="80px"
|
||||
height="30px"
|
||||
style={{ marginRight: "10px" }}
|
||||
/>
|
||||
<span className="float-md-right d-block">
|
||||
{`BLC © İletişim ve Güvenlik Sistemleri AŞ`}
|
||||
</span>
|
||||
</div>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
257
sge-frontend/src/views/MailSettings/index.js
Normal file
257
sge-frontend/src/views/MailSettings/index.js
Normal file
@@ -0,0 +1,257 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
Grid,
|
||||
TextField,
|
||||
Typography,
|
||||
FormControlLabel,
|
||||
Checkbox,
|
||||
Box,
|
||||
CircularProgress,
|
||||
Alert,
|
||||
} from "@mui/material";
|
||||
import {
|
||||
updateMailSettings,
|
||||
clearMailSuccess,
|
||||
clearMailError,
|
||||
} from "../../redux/actions/mailSettings";
|
||||
|
||||
const MailSettings = ({ closeModal }) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const { currentSettings, loading, error, success } = useSelector(
|
||||
(state) => state.mailSettings
|
||||
);
|
||||
|
||||
// State to track if we have multiple email configurations
|
||||
const [hasMultipleEmails, setHasMultipleEmails] = useState(false);
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
id: "",
|
||||
hostName: "",
|
||||
smtpPort: "",
|
||||
emailAddress: "",
|
||||
emailPassword: "",
|
||||
mainMail: true,
|
||||
});
|
||||
|
||||
// Clear success and error when modal is closed or component unmounts
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
dispatch(clearMailSuccess());
|
||||
dispatch(clearMailError());
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
// Check if there are multiple email configurations
|
||||
// Since we don't have a direct way to check this, we'll assume there's only one for now
|
||||
// TODO We would need to add a GraphQL query to get all mail configurations
|
||||
|
||||
useEffect(() => {
|
||||
if (currentSettings) {
|
||||
setHasMultipleEmails(false);
|
||||
setFormData({
|
||||
id: currentSettings.id,
|
||||
hostName: currentSettings.hostName,
|
||||
smtpPort: currentSettings.smtpPort,
|
||||
emailAddress: currentSettings.emailAddress,
|
||||
emailPassword: "",
|
||||
// If there's only one email, it must be the main one
|
||||
mainMail: hasMultipleEmails ? currentSettings.mainMail : true,
|
||||
});
|
||||
}
|
||||
}, [currentSettings]);
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value, type, checked } = e.target;
|
||||
|
||||
// If there's only one email and the field is mainMail, don't allow changes
|
||||
if (!hasMultipleEmails && name === "mainMail") {
|
||||
return;
|
||||
}
|
||||
|
||||
setFormData({
|
||||
...formData,
|
||||
[name]: type === "checkbox" ? checked : value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const submitData = {
|
||||
...formData,
|
||||
smtpPort: parseInt(formData.smtpPort),
|
||||
// If there's only one email, ensure mainMail is always true
|
||||
mainMail: hasMultipleEmails ? formData.mainMail : true,
|
||||
};
|
||||
if (!formData.emailPassword && formData.id) {
|
||||
delete submitData.emailPassword;
|
||||
}
|
||||
dispatch(updateMailSettings(submitData));
|
||||
};
|
||||
|
||||
// Only close modal on success, no notification
|
||||
useEffect(() => {
|
||||
if (success && closeModal) {
|
||||
closeModal(true);
|
||||
}
|
||||
}, [success, closeModal]);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{loading ? (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
minHeight: 200,
|
||||
}}
|
||||
>
|
||||
<CircularProgress color="success" />
|
||||
</Box>
|
||||
) : (
|
||||
<>
|
||||
{error && (
|
||||
<Alert severity="error" sx={{ mb: 2 }}>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
{!currentSettings ? (
|
||||
<Alert severity="warning" sx={{ mb: 2 }}>
|
||||
{t("MailSettings.noMailsRegistered")}
|
||||
</Alert>
|
||||
) : (
|
||||
<>
|
||||
<Typography variant="h5" sx={{ mb: 3 }}>
|
||||
{t("MailSettings.mailSettings")}
|
||||
</Typography>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Box component="form" onSubmit={handleSubmit}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label={t("MailSettings.hostName")}
|
||||
id="hostName"
|
||||
name="hostName"
|
||||
placeholder={t("MailSettings.hostName")}
|
||||
value={formData.hostName}
|
||||
onChange={handleChange}
|
||||
required
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
color="success"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label={t("MailSettings.smtpPort")}
|
||||
id="smtpPort"
|
||||
name="smtpPort"
|
||||
type="number"
|
||||
placeholder={t("MailSettings.smtpPort")}
|
||||
value={formData.smtpPort}
|
||||
onChange={handleChange}
|
||||
required
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
color="success"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label={t("MailSettings.emailAddress")}
|
||||
id="emailAddress"
|
||||
name="emailAddress"
|
||||
type="email"
|
||||
placeholder={
|
||||
formData.emailAddress
|
||||
? t("MailSettings.emailAddress")
|
||||
: t("MailSettings.noEmailFound")
|
||||
}
|
||||
value={formData.emailAddress || ""}
|
||||
onChange={handleChange}
|
||||
required
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
color="success"
|
||||
helperText={
|
||||
!formData.emailAddress
|
||||
? t("MailSettings.noEmailFound")
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label={`${t("MailSettings.emailPassword")} ${
|
||||
formData.id ? t("MailSettings.leaveBlankNote") : ""
|
||||
}`}
|
||||
id="emailPassword"
|
||||
name="emailPassword"
|
||||
type="password"
|
||||
placeholder={t("MailSettings.emailPassword")}
|
||||
value={formData.emailPassword}
|
||||
onChange={handleChange}
|
||||
required={!formData.id}
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
color="success"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{/* Only show the mainMail checkbox if there are multiple email configurations */}
|
||||
{hasMultipleEmails && (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
id="mainMail"
|
||||
name="mainMail"
|
||||
checked={formData.mainMail}
|
||||
onChange={handleChange}
|
||||
color="success"
|
||||
/>
|
||||
}
|
||||
label={t("MailSettings.mainMail")}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="success"
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
>
|
||||
{t("MailSettings.saveSettings")}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default MailSettings;
|
||||
729
sge-frontend/src/views/Map.js
Normal file
729
sge-frontend/src/views/Map.js
Normal file
@@ -0,0 +1,729 @@
|
||||
import React, { useEffect } from "react";
|
||||
import {
|
||||
MapContainer,
|
||||
Marker,
|
||||
Polygon,
|
||||
Popup,
|
||||
TileLayer,
|
||||
Tooltip,
|
||||
useMap,
|
||||
LayerGroup,
|
||||
} from "react-leaflet";
|
||||
import "../components/leaflet.css";
|
||||
import { useState, useRef } from "react";
|
||||
import {
|
||||
Button,
|
||||
Label,
|
||||
Modal,
|
||||
ModalHeader,
|
||||
ModalBody,
|
||||
Row,
|
||||
Col,
|
||||
Input,
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "reactstrap";
|
||||
import Select from "react-select";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import DataInputGroup from "../components/data-input/index.js";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { getCities } from "../redux/actions/cities";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { getCity } from "../redux/actions/city";
|
||||
import { getDistrict } from "../redux/actions/district";
|
||||
import {
|
||||
getOrganisations,
|
||||
getOrganisationById,
|
||||
} from "../redux/actions/organisations";
|
||||
import { getAreasWithCriteria } from "../redux/actions/areas";
|
||||
import { ChromePicker } from "react-color";
|
||||
import { customFilterForSelect } from "../utility/Utils";
|
||||
import { permissionCheck } from "../components/permission-check";
|
||||
import { getDataCenters } from "../redux/actions/dataCenter";
|
||||
|
||||
const ColorPicker = ({ selectedColors, setSelectedColors, index }) => {
|
||||
const [showColorPicker, setShowColorPicker] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const storedColors = localStorage.getItem("selectedMapColors");
|
||||
if (storedColors) {
|
||||
setSelectedColors(JSON.parse(storedColors));
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem("selectedMapColors", JSON.stringify(selectedColors));
|
||||
}, [selectedColors]);
|
||||
|
||||
const handleColorChange = (color) => {
|
||||
const rgbaColor = `rgba(${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b}, 0.3)`;
|
||||
const updatedColors = [...selectedColors];
|
||||
updatedColors[index] = rgbaColor;
|
||||
setSelectedColors(updatedColors);
|
||||
};
|
||||
|
||||
const handlePickerOutsideClick = (event) => {
|
||||
// Renk seçici dışına tıklama kontrolü
|
||||
if (
|
||||
event.target.closest(".color-picker-container") === null &&
|
||||
event.target.closest(".color-box") === null
|
||||
) {
|
||||
setShowColorPicker(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Tüm belgeye tıklama olayını dinle
|
||||
document.addEventListener("click", handlePickerOutsideClick);
|
||||
|
||||
return () => {
|
||||
// Bileşen kaldırıldığında tıklama olayının dinlemesini kaldır
|
||||
document.removeEventListener("click", handlePickerOutsideClick);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="color-picker-container">
|
||||
<div
|
||||
key={index}
|
||||
className="color-box"
|
||||
style={{
|
||||
backgroundColor: selectedColors[index],
|
||||
width: "30px",
|
||||
height: "15px",
|
||||
display: "inline-block",
|
||||
marginRight: "10px",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() => setShowColorPicker(true)}
|
||||
></div>
|
||||
{showColorPicker && (
|
||||
<div style={{ position: "absolute", zIndex: 1000 }}>
|
||||
<ChromePicker
|
||||
color={selectedColors[index]}
|
||||
onChange={handleColorChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Map = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const citiesStore = useSelector((state) => state.cities);
|
||||
const cityStore = useSelector((state) => state.city);
|
||||
const districtStore = useSelector((state) => state.district);
|
||||
const OrganisationsStore = useSelector((state) => state.organizations);
|
||||
const areasStore = useSelector((state) => state.areas);
|
||||
const dataCenterStore = useSelector((state) => state.dataCenter);
|
||||
|
||||
const [cities, setCities] = useState([]);
|
||||
const [districts, setDistricts] = useState([]);
|
||||
const [neighborhoods, setNeighborhoods] = useState([]);
|
||||
const [organizationOptions, setOrganizationOptions] = useState([]);
|
||||
const organizationId = localStorage.getItem("organizationId");
|
||||
const roleTag = localStorage.getItem("roleTag");
|
||||
const [selectedOrganization, setSelectedOrganization] = useState({
|
||||
label: localStorage.getItem("organizationName"),
|
||||
value: organizationId,
|
||||
});
|
||||
const [areasOptions, setAreasOptions] = useState([]);
|
||||
|
||||
const [done, setDone] = useState(false);
|
||||
const [selectedCity, setSelectedCity] = useState(null);
|
||||
const [selectedDistrict, setSelectedDistrict] = useState(null);
|
||||
const [districtView, setDistrictView] = useState(false);
|
||||
const [neighbourhoodView, setNeighbourhoodView] = useState(false);
|
||||
const cityRefs = useRef({});
|
||||
const districtRefs = useRef({});
|
||||
const neighbourhoodRefs = useRef({});
|
||||
const [zoom, setZoom] = useState(6.7);
|
||||
const [center, setCenter] = useState({ lat: 38.5, lng: 35.27 });
|
||||
|
||||
const [showDataInputModal, setShowDataInputModal] = useState(false);
|
||||
const [inputData, setInputData] = useState({
|
||||
organization: selectedOrganization,
|
||||
});
|
||||
const [referance, setReferance] = useState(
|
||||
Number(localStorage.getItem("referance")) || 1000
|
||||
);
|
||||
const [referance2, setReferance2] = useState(
|
||||
Number(localStorage.getItem("referance2")) || 2000
|
||||
);
|
||||
const initialColors = [
|
||||
"rgba(44,232,44,0.3)",
|
||||
"rgba(234,234,17,0.3)",
|
||||
"rgba(228,19,19,0.3)",
|
||||
];
|
||||
const [selectedColors, setSelectedColors] = useState(initialColors);
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
const [year, setYear] = useState({ value: currentYear, label: currentYear });
|
||||
const renderYearOptions = () => {
|
||||
const years = [];
|
||||
for (let year = currentYear; year >= 1990; year--) {
|
||||
years.push({ label: year, value: year });
|
||||
}
|
||||
return years;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getCities());
|
||||
dispatch(getDataCenters());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedOrganization?.value != "undefined") {
|
||||
dispatch(getAreasWithCriteria(selectedOrganization.value));
|
||||
}
|
||||
}, [selectedOrganization]);
|
||||
|
||||
useEffect(() => {
|
||||
setAreasOptions([]);
|
||||
|
||||
const citiesOptions =
|
||||
areasStore?.areasWithCriteria
|
||||
?.map((area) =>
|
||||
Array.isArray(area?.cities) // cities'in bir dizi olup olmadığını kontrol edin
|
||||
? area.cities.map((city) => ({
|
||||
value: city?.id,
|
||||
label: city?.name,
|
||||
type: "city",
|
||||
}))
|
||||
: []
|
||||
)
|
||||
.flat() || []; // Eğer map boş dönerse, `flat` sonrası boş bir dizi kullan
|
||||
|
||||
const districtsOptions = areasStore?.areasWithCriteria
|
||||
?.map((area) => {
|
||||
return area?.districts?.map((district) => {
|
||||
return {
|
||||
value: district?.id,
|
||||
label: district?.name + " (" + district?.city?.name + ")",
|
||||
type: "district",
|
||||
};
|
||||
});
|
||||
})
|
||||
.flat();
|
||||
|
||||
const neighborhoodsOptions = areasStore?.areasWithCriteria
|
||||
?.map((area) => {
|
||||
return area?.neighborhoods?.map((neighborhood) => {
|
||||
return {
|
||||
value: neighborhood?.id,
|
||||
label:
|
||||
neighborhood?.name +
|
||||
" (" +
|
||||
neighborhood?.district?.name +
|
||||
" / " +
|
||||
neighborhood?.district?.city?.name +
|
||||
")",
|
||||
type: "neighborhood",
|
||||
};
|
||||
});
|
||||
})
|
||||
.flat();
|
||||
|
||||
setAreasOptions([
|
||||
...(citiesOptions || []),
|
||||
...(districtsOptions || []),
|
||||
...(neighborhoodsOptions || []),
|
||||
]);
|
||||
}, [areasStore]);
|
||||
|
||||
useEffect(() => {
|
||||
if (roleTag === "SUPER_ADMIN") {
|
||||
dispatch(getOrganisations());
|
||||
} else {
|
||||
dispatch(getOrganisationById(organizationId));
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleDataInputButtonPressed = ({ area, type }) => {
|
||||
const areaName =
|
||||
type === "neighborhood"
|
||||
? `${area?.name} / ${area?.district?.name} / ${area?.district?.city?.name}`
|
||||
: type === "district"
|
||||
? `${area?.name} / ${area?.city?.name}`
|
||||
: area?.name;
|
||||
|
||||
const areaType =
|
||||
type === "district"
|
||||
? "district"
|
||||
: type === "city"
|
||||
? "city"
|
||||
: type === "neighborhood"
|
||||
? "neighborhood"
|
||||
: "";
|
||||
setShowDataInputModal(true);
|
||||
setInputData({
|
||||
...inputData,
|
||||
area: { value: area?.id, label: areaName, type: areaType },
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!citiesStore?.cities || !year?.value) {
|
||||
console.error("Cities or year is not defined.");
|
||||
return;
|
||||
}
|
||||
|
||||
setCities(() => {
|
||||
const updatedCities = citiesStore.cities.map((city) => {
|
||||
const mainDataTables = city.mainDataTables
|
||||
? city.mainDataTables.filter(
|
||||
(data) => data.year === year.value.toString()
|
||||
)
|
||||
: [];
|
||||
|
||||
const total = mainDataTables.reduce(
|
||||
(acc, data) => acc + (data?.totalEmission || 0),
|
||||
0
|
||||
);
|
||||
|
||||
return { ...city, mainDataTables, total };
|
||||
});
|
||||
|
||||
return updatedCities;
|
||||
});
|
||||
}, [citiesStore?.cities, year?.value]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedCity != undefined) {
|
||||
dispatch(getCity(selectedCity?.id));
|
||||
}
|
||||
}, [selectedCity]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedDistrict != undefined && selectedDistrict.length != 0) {
|
||||
dispatch(getDistrict(selectedDistrict?.id));
|
||||
}
|
||||
}, [selectedDistrict]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!cityStore?.city?.districts || !year.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDistricts(() => {
|
||||
const updatedDistricts = cityStore.city.districts?.map((district) => {
|
||||
const mainDataTables = district.mainDataTables.filter(
|
||||
(data) => data.year === year.value.toString()
|
||||
);
|
||||
const total = mainDataTables.reduce(
|
||||
(acc, data) => acc + data.totalEmission,
|
||||
0
|
||||
);
|
||||
return { ...district, mainDataTables, total };
|
||||
});
|
||||
|
||||
return updatedDistricts;
|
||||
});
|
||||
}, [selectedCity, cityStore?.city, year.value]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!districtStore?.district?.neighborhoods || !year.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
setNeighborhoods(() => {
|
||||
const updatedNeighborhoods = districtStore.district?.neighborhoods?.map(
|
||||
(neighborhood) => {
|
||||
const mainDataTables = neighborhood.mainDataTables.filter(
|
||||
(data) => data.year === year.value.toString()
|
||||
);
|
||||
const total = mainDataTables.reduce(
|
||||
(acc, data) => acc + data.totalEmission,
|
||||
0
|
||||
);
|
||||
return { ...neighborhood, mainDataTables, total };
|
||||
}
|
||||
);
|
||||
|
||||
return updatedNeighborhoods;
|
||||
});
|
||||
}, [selectedDistrict, districtStore?.district, year.value]);
|
||||
|
||||
useEffect(() => {
|
||||
let organizationOptions = [];
|
||||
|
||||
if (
|
||||
OrganisationsStore.organization &&
|
||||
OrganisationsStore.organization.length !== 0
|
||||
) {
|
||||
organizationOptions.push({
|
||||
value: OrganisationsStore.organization.id,
|
||||
label: OrganisationsStore.organization.tag,
|
||||
});
|
||||
|
||||
if (OrganisationsStore.organization.children) {
|
||||
organizationOptions = [
|
||||
...organizationOptions,
|
||||
...OrganisationsStore.organization.children.map((organization) => ({
|
||||
value: organization.child.id,
|
||||
label: organization.child.tag,
|
||||
})),
|
||||
];
|
||||
}
|
||||
} else {
|
||||
organizationOptions = OrganisationsStore.dataOrganization.map(
|
||||
(organization) => ({
|
||||
value: organization.id,
|
||||
label: organization.tag,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
setOrganizationOptions(organizationOptions);
|
||||
}, [OrganisationsStore]);
|
||||
|
||||
const renderDataInputModal = () => {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={showDataInputModal}
|
||||
toggle={() => setShowDataInputModal(!showDataInputModal)}
|
||||
className="modal-dialog-centered"
|
||||
size="lg"
|
||||
>
|
||||
<ModalHeader toggle={() => setShowDataInputModal(!showDataInputModal)}>
|
||||
{t("DataInput.dataInput")} - {inputData?.area?.label}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<DataInputGroup inputData={inputData} setInputData={setInputData} />
|
||||
</ModalBody>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const grayOptions = {
|
||||
color: "gray",
|
||||
fillColor: "transparent",
|
||||
opacity: 1,
|
||||
};
|
||||
|
||||
const greenOptions = {
|
||||
color: "gray",
|
||||
fillColor: selectedColors[0],
|
||||
opacity: 1,
|
||||
};
|
||||
|
||||
const yellowOptions = {
|
||||
color: "gray",
|
||||
fillColor: selectedColors[1],
|
||||
opacity: 1,
|
||||
};
|
||||
|
||||
const redOptions = {
|
||||
color: "gray",
|
||||
fillColor: selectedColors[2],
|
||||
opacity: 1,
|
||||
};
|
||||
|
||||
const pathOptions = (total) => {
|
||||
return total > referance2
|
||||
? redOptions
|
||||
: referance < total && total < referance2
|
||||
? yellowOptions
|
||||
: total != 0 && total < referance
|
||||
? greenOptions
|
||||
: grayOptions;
|
||||
};
|
||||
|
||||
const turkeyBounds = [
|
||||
[34.42, 44.83],
|
||||
[43.45, 25.62],
|
||||
];
|
||||
|
||||
const RenderDistrictView = () => {
|
||||
return districts?.map((district, index) => {
|
||||
return (
|
||||
<Polygon
|
||||
key={uuidv4()}
|
||||
ref={(c) => {
|
||||
districtRefs.current[index] = c;
|
||||
if (index === district.length - 1 && !done) {
|
||||
setDone(true);
|
||||
}
|
||||
}}
|
||||
fillOpacity={1}
|
||||
pathOptions={pathOptions(district.total)}
|
||||
positions={convertCoordinates(district.coordinates)}
|
||||
eventHandlers={{
|
||||
mouseover: () => {
|
||||
const district = districtRefs.current[index];
|
||||
district.openPopup();
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Tooltip permanent direction="center" className="tooltip">
|
||||
{district.name}
|
||||
</Tooltip>
|
||||
{
|
||||
<Popup closeButton={true} autoPan={false}>
|
||||
<h5 className="w-100 text-center">{district.name}</h5>
|
||||
<Button
|
||||
className="w-100 mb-1"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setSelectedDistrict(district);
|
||||
setNeighbourhoodView(true);
|
||||
setDistrictView(false);
|
||||
setSelectedCity(null);
|
||||
setZoom(11.0);
|
||||
let convertCordinates = convertCoordinates(
|
||||
district.coordinates
|
||||
);
|
||||
|
||||
let length = convertCordinates[0][0][0].length;
|
||||
let mlength = ((length + 1) / 2).toFixed(0);
|
||||
let lat1 = convertCordinates[0][0][0][0];
|
||||
let lng1 = convertCordinates[0][0][0][1];
|
||||
let lat2 = convertCordinates[0][0][mlength][0];
|
||||
let lng2 = convertCordinates[0][0][mlength][1];
|
||||
|
||||
setCenter({
|
||||
lat: ((lat1 + lat2) / 2).toFixed(2),
|
||||
lng: ((lng1 + lng2) / 2).toFixed(2),
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t("Areas.neighborhoods")}
|
||||
</Button>
|
||||
<br></br>
|
||||
{areaCheck({ areaId: district.id }) &&
|
||||
permissionCheck("dataset_create") && (
|
||||
<Button
|
||||
className="w-100 mb-1"
|
||||
color="primary"
|
||||
onClick={() =>
|
||||
handleDataInputButtonPressed({
|
||||
area: district,
|
||||
type: "district",
|
||||
})
|
||||
}
|
||||
>
|
||||
{t("DataInput.dataInput")}
|
||||
</Button>
|
||||
)}
|
||||
<br></br>
|
||||
<Button
|
||||
className="w-100"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setDistrictView(false);
|
||||
setCenter({ lat: 38, lng: 37 });
|
||||
setZoom(6.7);
|
||||
}}
|
||||
>
|
||||
{t("Map.goBack")}
|
||||
</Button>
|
||||
</Popup>
|
||||
}
|
||||
</Polygon>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const neighborhoodTooltipBackground = (total) => {
|
||||
return total > referance2
|
||||
? selectedColors[2]
|
||||
: referance < total && total < referance2
|
||||
? selectedColors[1]
|
||||
: total != 0 && total < referance
|
||||
? selectedColors[0]
|
||||
: "white";
|
||||
};
|
||||
|
||||
const renderNeighbourhoodView = () => {
|
||||
return neighborhoods?.map((neighbourhood, index) => {
|
||||
return (
|
||||
<Marker
|
||||
opacity={0}
|
||||
zoom={10.75}
|
||||
key={uuidv4()}
|
||||
ref={(c) => {
|
||||
// <--- add city refs to ref object here
|
||||
neighbourhoodRefs.current[index] = c;
|
||||
if (index === neighbourhood.length - 1 && !done) {
|
||||
setDone(true);
|
||||
}
|
||||
}}
|
||||
fillOpacity={1}
|
||||
pathOptions={pathOptions(neighbourhood.total)}
|
||||
position={[
|
||||
Number(neighbourhood?.minLat + neighbourhood?.maxLat) / 2,
|
||||
Number(neighbourhood?.minLong + neighbourhood?.maxLong) / 2,
|
||||
]}
|
||||
eventHandlers={{
|
||||
mouseover: () => {
|
||||
const neighbourhood = neighbourhoodRefs.current[index];
|
||||
neighbourhood.openPopup();
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Tooltip permanent direction="center" className="tooltip">
|
||||
{neighbourhood.name}
|
||||
</Tooltip>
|
||||
<Popup closeButton={true} autoPan={false}>
|
||||
{areaCheck({ areaId: neighbourhood.id }) &&
|
||||
permissionCheck("dataset_create") && (
|
||||
<Button
|
||||
className="w-100 mb-1"
|
||||
color="primary"
|
||||
onClick={() =>
|
||||
handleDataInputButtonPressed({
|
||||
area: neighbourhood,
|
||||
type: "neighborhood",
|
||||
})
|
||||
}
|
||||
>
|
||||
{t("DataInput.dataInput")}
|
||||
</Button>
|
||||
)}
|
||||
<br></br>
|
||||
<Button
|
||||
className="w-100"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setNeighbourhoodView(false);
|
||||
setCenter({ lat: 38, lng: 37 });
|
||||
setZoom(6.7);
|
||||
}}
|
||||
>
|
||||
{t("Map.goBack")}
|
||||
</Button>
|
||||
</Popup>
|
||||
</Marker>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
function ChangeZoom({ center, zoom }) {
|
||||
const map = useMap();
|
||||
map.setView(center, zoom);
|
||||
return null;
|
||||
}
|
||||
function convertCoordinates(coords) {
|
||||
const editedCoordinates = JSON.parse(coords);
|
||||
return editedCoordinates;
|
||||
}
|
||||
|
||||
function areaCheck({ areaId }) {
|
||||
return areasOptions?.some((area) => areaId === area.value);
|
||||
}
|
||||
|
||||
const renderDataCenterMarkers = () => {
|
||||
if (!dataCenterStore?.dataCenters) return null;
|
||||
|
||||
return (
|
||||
<LayerGroup>
|
||||
{dataCenterStore.dataCenters.map((dc) => {
|
||||
if (!dc.latitude || !dc.longitude) return null;
|
||||
|
||||
return (
|
||||
<Marker
|
||||
key={dc.id}
|
||||
position={[dc.latitude, dc.longitude]}
|
||||
>
|
||||
<Popup>
|
||||
<div className="data-center-popup">
|
||||
<h5 className="mb-2">{dc.dataCenter}</h5>
|
||||
<p className="mb-1"><strong>{t('DataCenter.number')}:</strong> {dc.number}</p>
|
||||
<p className="mb-1"><strong>{t('DataCenter.externalId')}:</strong> {dc.externalId}</p>
|
||||
<p className="mb-1"><strong>{t('DataCenter.city')}:</strong> {dc.area?.cityNames || "-"}</p>
|
||||
{dc.area && <p className="mb-1"><strong>{t('Area')}:</strong> {dc.area.tag}</p>}
|
||||
{dc.projects?.length > 0 && (
|
||||
<p className="mb-1">
|
||||
<strong>{t('Projects')}:</strong> {dc.projects.length}
|
||||
</p>
|
||||
)}
|
||||
{dc.ayposURL && (
|
||||
<Button
|
||||
className="w-100 mb-1"
|
||||
color="primary"
|
||||
onClick={() => window.open(dc.ayposURL, '_blank')}
|
||||
>
|
||||
Dashboard
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</Popup>
|
||||
<Tooltip>{dc.dataCenter}</Tooltip>
|
||||
</Marker>
|
||||
);
|
||||
})}
|
||||
</LayerGroup>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle tag="h4">{t("Map.title")}</CardTitle>
|
||||
</CardHeader>
|
||||
<div style={{ height: "700px", width: "100%" }}>
|
||||
<MapContainer
|
||||
center={center}
|
||||
zoom={zoom}
|
||||
style={{ height: "100%", width: "100%" }}
|
||||
>
|
||||
<TileLayer
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
/>
|
||||
{renderDataCenterMarkers()}
|
||||
{neighbourhoodView
|
||||
? renderNeighbourhoodView(selectedDistrict)
|
||||
: districtView
|
||||
? RenderDistrictView(selectedCity)
|
||||
: cities.map((city, index) => {
|
||||
return (
|
||||
<Polygon
|
||||
key={uuidv4()}
|
||||
ref={(c) => {
|
||||
cityRefs.current[index] = c;
|
||||
if (index === cities.length - 1 && !done) {
|
||||
setDone(true);
|
||||
}
|
||||
}}
|
||||
fillOpacity={1}
|
||||
pathOptions={pathOptions(city.total)}
|
||||
positions={convertCoordinates(city.coordinates)}
|
||||
eventHandlers={{
|
||||
click: () => {
|
||||
setSelectedCity(city);
|
||||
setDistrictView(true);
|
||||
setZoom(8.0);
|
||||
|
||||
let convertCordinates = convertCoordinates(city.coordinates);
|
||||
let length = convertCordinates[0][0][0].length;
|
||||
let mlength = ((length + 1) / 2).toFixed(0);
|
||||
let lat1 = convertCordinates[0][0][0][0];
|
||||
let lng1 = convertCordinates[0][0][0][1];
|
||||
let lat2 = convertCordinates[0][0][mlength][0];
|
||||
let lng2 = convertCordinates[0][0][mlength][1];
|
||||
setCenter({
|
||||
lat: ((lat1 + lat2) / 2).toFixed(2),
|
||||
lng: ((lng1 + lng2) / 2).toFixed(2),
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Tooltip permanent direction="center">
|
||||
{city.name}
|
||||
</Tooltip>
|
||||
</Polygon>
|
||||
);
|
||||
})}
|
||||
{renderDataInputModal()}
|
||||
</MapContainer>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default Map;
|
||||
17
sge-frontend/src/views/NotAuthorized.js
Normal file
17
sge-frontend/src/views/NotAuthorized.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import "../@core/scss/base/pages/page-misc.scss";
|
||||
|
||||
const NotAuthorized = () => {
|
||||
return (
|
||||
<div className="misc-wrapper">
|
||||
<div className="misc-inner p-2 p-sm-3">
|
||||
<div className="w-100 text-center">
|
||||
<h2 className="mb-1">
|
||||
Bu sayfaya erişmek için yetkilendirilmediniz! 🔐
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotAuthorized;
|
||||
282
sge-frontend/src/views/Notifications.js
Normal file
282
sge-frontend/src/views/Notifications.js
Normal file
@@ -0,0 +1,282 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { ChevronDown, Trash } from "react-feather";
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Input,
|
||||
Label,
|
||||
Row,
|
||||
Col,
|
||||
} from "reactstrap";
|
||||
import DataTable from "react-data-table-component";
|
||||
import ReactPaginate from "react-paginate";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
getAllNotifications,
|
||||
setNotificationRead,
|
||||
notificationRemove,
|
||||
} from "../redux/actions/notifications";
|
||||
import { default as SweetAlert } from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
|
||||
const Swal = withReactContent(SweetAlert);
|
||||
|
||||
const Notifications = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const conditionalRowStyles = [
|
||||
{
|
||||
when: (row) => row.read === false,
|
||||
style: {
|
||||
backgroundColor: "#028a4a1a",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const serverSideColumns = [
|
||||
{
|
||||
name: t("Notifications.title"),
|
||||
selector: (row) => row.title,
|
||||
sortable: true,
|
||||
maxWidth: "500px",
|
||||
},
|
||||
{
|
||||
name: t("Notifications.notificationType"),
|
||||
selector: (row) => row.notificationType,
|
||||
sortable: true,
|
||||
maxWidth: "500px",
|
||||
},
|
||||
{
|
||||
name: t("Notifications.content"),
|
||||
selector: (row) => row.message,
|
||||
sortable: true,
|
||||
maxWidth: "500px",
|
||||
},
|
||||
{
|
||||
name: t("Time"),
|
||||
selector: (row) => row.createdDateTime,
|
||||
sortable: true,
|
||||
width: "250px",
|
||||
cell: (row) => (
|
||||
<span>{new Date(row.createdDateTime).toLocaleString("tr-TR")}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: t("Cruds.delete"),
|
||||
allowOverflow: false,
|
||||
maxWidth: "100px",
|
||||
cell: (row) => {
|
||||
return (
|
||||
<div className="d-flex">
|
||||
<a onClick={() => handleDeleteNotification(row)}>
|
||||
<Trash size={15} />
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(5);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
|
||||
const notificationsStore = useSelector((state) => state.notifications);
|
||||
const [notifications, setNotifications] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getAllNotifications({ currentPage, rowsPerPage }));
|
||||
}, [currentPage, rowsPerPage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (notificationsStore.notifications?.content) {
|
||||
setNotifications(notificationsStore.notifications?.content);
|
||||
}
|
||||
}, [
|
||||
notificationsStore.notifications?.content?.length,
|
||||
notificationsStore.notifications,
|
||||
]);
|
||||
|
||||
const handleFilter = (e) => {
|
||||
setSearchValue(e.target.value);
|
||||
|
||||
if (e.target.value !== "") {
|
||||
setCurrentPage(1);
|
||||
setRowsPerPage(notificationsStore.notifications?.pageInfo?.totalElements);
|
||||
setNotifications(
|
||||
notificationsStore.notifications?.content.filter((notification) =>
|
||||
notification.title
|
||||
.toLowerCase()
|
||||
.includes(e.target.value.toLowerCase())
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setCurrentPage(1);
|
||||
setRowsPerPage(5);
|
||||
setNotifications(notificationsStore.notifications?.content);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePagination = (page) => {
|
||||
setCurrentPage(page.selected + 1);
|
||||
setNotifications(notificationsStore.notifications?.content);
|
||||
};
|
||||
|
||||
const handlePerPage = (e) => {
|
||||
setCurrentPage(1);
|
||||
setRowsPerPage(parseInt(e.target.value));
|
||||
setNotifications(notificationsStore.notifications?.content);
|
||||
};
|
||||
|
||||
const CustomPagination = () => {
|
||||
const count = Number(
|
||||
(
|
||||
notificationsStore.notifications?.pageInfo?.totalElements / rowsPerPage
|
||||
).toFixed(1)
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactPaginate
|
||||
previousLabel={""}
|
||||
nextLabel={""}
|
||||
breakLabel="..."
|
||||
pageCount={count || 1}
|
||||
marginPagesDisplayed={2}
|
||||
pageRangeDisplayed={2}
|
||||
activeClassName="active"
|
||||
forcePage={currentPage !== 0 ? currentPage - 1 : 0}
|
||||
onPageChange={(page) => handlePagination(page)}
|
||||
pageClassName={"page-item"}
|
||||
nextLinkClassName={"page-link"}
|
||||
nextClassName={"page-item next"}
|
||||
previousClassName={"page-item prev"}
|
||||
previousLinkClassName={"page-link"}
|
||||
pageLinkClassName={"page-link"}
|
||||
breakClassName="page-item"
|
||||
breakLinkClassName="page-link"
|
||||
containerClassName={
|
||||
"pagination react-paginate separated-pagination pagination-sm justify-content-end pr-1 mt-1"
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const handleDeleteNotification = async (selectedNotification) => {
|
||||
const result = await Swal.fire({
|
||||
title: `${t("Warnings.sureForDelete")}`,
|
||||
text: t("Warnings.notUndone"),
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonText: t("Cruds.delete"),
|
||||
cancelButtonText: t("Cruds.cancel"),
|
||||
customClass: {
|
||||
confirmButton: "btn btn-primary",
|
||||
cancelButton: "btn btn-danger ml-1",
|
||||
},
|
||||
buttonsStyling: false,
|
||||
});
|
||||
if (result.value !== null && result.value === true) {
|
||||
let selectedId = selectedNotification.id;
|
||||
dispatch(notificationRemove({ selectedId, currentPage, rowsPerPage }));
|
||||
}
|
||||
//when notifications size is zero in current page and current page is not 1, change current page to previous page
|
||||
if (notifications?.length == 1 && currentPage != 1) {
|
||||
const newCurrentPage = currentPage - 1;
|
||||
setCurrentPage(newCurrentPage);
|
||||
dispatch(getAllNotifications({ currentPage, rowsPerPage }));
|
||||
}
|
||||
};
|
||||
|
||||
const ExpandableTable = ({ data }) => {
|
||||
if (data.read === false) {
|
||||
dispatch(setNotificationRead(data.id));
|
||||
dispatch(getAllNotifications({ currentPage, rowsPerPage }));
|
||||
}
|
||||
return (
|
||||
<div className="expandable-content p-2">
|
||||
<p className="font-weight-bold">
|
||||
{data?.title} / {data?.notificationType}
|
||||
</p>
|
||||
<p className="font-small-3 mt-2">{data.message}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
let listOfNumber = [5, 10, 25, 50, 75, 100];
|
||||
return (
|
||||
<div style={{ marginTop: "2%" }}>
|
||||
<Card>
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle tag="h4">{t("Notifications.notifications")}</CardTitle>
|
||||
</CardHeader>
|
||||
<Row className="mx-0 mt-1 mb-50">
|
||||
<Col sm="6" md="2">
|
||||
<div className="d-flex align-items-center">
|
||||
<Label for="sort-select">{t("Show")}</Label>
|
||||
<Input
|
||||
className="ml-1 dataTable-select"
|
||||
type="select"
|
||||
id="sort-select"
|
||||
value={rowsPerPage}
|
||||
onChange={(e) => handlePerPage(e)}
|
||||
>
|
||||
<option value={5}>5</option>
|
||||
<option value={10}>10</option>
|
||||
<option value={25}>25</option>
|
||||
<option value={50}>50</option>
|
||||
<option value={75}>75</option>
|
||||
<option value={100}>100</option>
|
||||
{!listOfNumber.includes(rowsPerPage) && (
|
||||
<option value={rowsPerPage}>{rowsPerPage}</option>
|
||||
)}
|
||||
</Input>
|
||||
</div>
|
||||
</Col>
|
||||
<Col
|
||||
className="d-flex align-items-center justify-content-end mt-sm-0 mt-1 ml-md-auto"
|
||||
sm="6"
|
||||
md="3"
|
||||
>
|
||||
<Label className="mr-1" for="search-input">
|
||||
{t("Filter")}
|
||||
</Label>
|
||||
<Input
|
||||
className="dataTable-filter"
|
||||
type="text"
|
||||
bsSize="sm"
|
||||
id="search-input"
|
||||
value={searchValue}
|
||||
onChange={handleFilter}
|
||||
placeholder=""
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<DataTable
|
||||
noHeader
|
||||
pagination
|
||||
paginationServer
|
||||
className="react-dataTable"
|
||||
columns={serverSideColumns}
|
||||
conditionalRowStyles={conditionalRowStyles}
|
||||
expandableRows
|
||||
expandOnRowClicked
|
||||
expandableRowsComponent={ExpandableTable}
|
||||
sortIcon={<ChevronDown size={10} />}
|
||||
paginationComponent={CustomPagination}
|
||||
data={[...notifications]}
|
||||
noDataComponent={
|
||||
<p className="p-2">
|
||||
{t("Notifications.notification")}
|
||||
{t("Warnings.notFound")}
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Notifications;
|
||||
664
sge-frontend/src/views/OrganizationManagement.js
Normal file
664
sge-frontend/src/views/OrganizationManagement.js
Normal file
@@ -0,0 +1,664 @@
|
||||
import React, { useState, useEffect, memo } from "react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import ReactPaginate from "react-paginate";
|
||||
import { ChevronDown, MoreVertical, Plus, Trash } from "react-feather";
|
||||
import DataTable from "react-data-table-component";
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Input,
|
||||
Label,
|
||||
Row,
|
||||
Col,
|
||||
Button,
|
||||
Modal,
|
||||
ModalHeader,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
UncontrolledDropdown,
|
||||
DropdownToggle,
|
||||
DropdownMenu,
|
||||
DropdownItem,
|
||||
} from "reactstrap";
|
||||
import Select from "react-select";
|
||||
import { Edit } from "@mui/icons-material";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { default as SweetAlert } from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import ApplicationService from "../services/ApplicationService";
|
||||
import {
|
||||
getOrganisations,
|
||||
getOrganizationsWithPaginate,
|
||||
addOrganization,
|
||||
updateOrganization,
|
||||
deleteOrganization,
|
||||
} from "../redux/actions/organisations";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getAreas } from "../redux/actions/areas";
|
||||
import { permissionCheck } from "../components/permission-check";
|
||||
import { customFilterForSelect } from "../utility/Utils";
|
||||
|
||||
const Swal = withReactContent(SweetAlert);
|
||||
const OrganizationManagement = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const initialColumns = [
|
||||
{
|
||||
name: t("Organizations.organizationName"),
|
||||
selector: (row) => row.tag,
|
||||
sortable: true,
|
||||
minWidth: "250px",
|
||||
},
|
||||
{
|
||||
name: t("Organizations.organization") + t("Description"),
|
||||
selector: (row) => row.description,
|
||||
sortable: true,
|
||||
minWidth: "350px",
|
||||
},
|
||||
{
|
||||
name: t("Organizations.parentOrganization"),
|
||||
selector: (row) => row.parents,
|
||||
sortable: false,
|
||||
minWidth: "250px",
|
||||
cell: (row) => <span>{row.parents?.[0]?.parent.tag || "-"}</span>,
|
||||
},
|
||||
{
|
||||
name: t("Areas.areas"),
|
||||
selector: (row) => row.areas,
|
||||
sortable: false,
|
||||
minWidth: "350px",
|
||||
cell: (row) => (
|
||||
<span>
|
||||
{row?.areas?.length > 0
|
||||
? row?.areas?.map((area) => area?.tag).join(", ")
|
||||
: "-"}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: t("Status"),
|
||||
selector: (row) => row.deleted,
|
||||
sortable: false,
|
||||
minWidth: "100px",
|
||||
cell: (row) => <span>{row.deleted ? t("Passive") : t("Active")}</span>,
|
||||
},
|
||||
{
|
||||
name: t("Actions"),
|
||||
allowOverflow: false,
|
||||
maxWidth: "100px",
|
||||
cell: (row) => {
|
||||
return (
|
||||
<div className="d-flex">
|
||||
<UncontrolledDropdown>
|
||||
<DropdownToggle className="pl-1" tag="span">
|
||||
<MoreVertical size={15} />
|
||||
</DropdownToggle>
|
||||
<DropdownMenu container={"body"} end>
|
||||
{permissionCheck("organization_update") && (
|
||||
<DropdownItem
|
||||
tag="a"
|
||||
className="w-100"
|
||||
onClick={() => handleEditOrganization(row)}
|
||||
>
|
||||
<Edit size={15} />
|
||||
<span className="align-middle ml-50">
|
||||
{t("Cruds.edit")}
|
||||
</span>
|
||||
</DropdownItem>
|
||||
)}
|
||||
{permissionCheck("organization_delete") && (
|
||||
<DropdownItem
|
||||
tag="a"
|
||||
className="w-100"
|
||||
onClick={() => handleDeleteOrganization(row)}
|
||||
>
|
||||
<Trash size={15} />
|
||||
<span className="align-middle ml-50">
|
||||
{t("Cruds.delete")}
|
||||
</span>
|
||||
</DropdownItem>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
</UncontrolledDropdown>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!(
|
||||
permissionCheck("organization_delete") ||
|
||||
permissionCheck("organization_update")
|
||||
)
|
||||
) {
|
||||
const updatedColumns = initialColumns.filter(
|
||||
(column) => column.name !== ("Aksiyonlar" || "Actions")
|
||||
);
|
||||
setServerSideColumns(updatedColumns);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(10);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
|
||||
const [serverSideColumns, setServerSideColumns] = useState(initialColumns);
|
||||
|
||||
const OrganizationsStore = useSelector((state) => state.organizations);
|
||||
const areasStore = useSelector((state) => state.areas);
|
||||
const [organizations, setOrganizations] = useState([]);
|
||||
const [organizationsOptions, setOrganizationsOptions] = useState([]);
|
||||
const [areasOptions, setAreasOptions] = useState([]);
|
||||
const [showAddOrganizationModal, setShowAddOrganizationModal] =
|
||||
useState(false);
|
||||
const [editingOrganizationData, setEditingOrganizationData] = useState(null);
|
||||
const [showDeletedOrganizations, setShowDeletedOrganizations] =
|
||||
useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getOrganisations());
|
||||
dispatch(
|
||||
getOrganizationsWithPaginate({
|
||||
currentPage,
|
||||
rowsPerPage,
|
||||
showDeletedOrganizations,
|
||||
})
|
||||
);
|
||||
}, [currentPage, rowsPerPage, showDeletedOrganizations]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getAreas());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (OrganizationsStore.organizationWithPaginate) {
|
||||
setOrganizations(OrganizationsStore.organizationWithPaginate?.content);
|
||||
}
|
||||
}, [OrganizationsStore, OrganizationsStore.total]);
|
||||
|
||||
useEffect(() => {
|
||||
setOrganizationsOptions(
|
||||
OrganizationsStore?.dataOrganization?.map((organization) => {
|
||||
return {
|
||||
value: organization?.id,
|
||||
label: organization?.tag,
|
||||
};
|
||||
})
|
||||
);
|
||||
}, [OrganizationsStore]);
|
||||
|
||||
useEffect(() => {
|
||||
setAreasOptions([]);
|
||||
setAreasOptions(
|
||||
areasStore?.areas?.map((area) => {
|
||||
return {
|
||||
value: area?.id,
|
||||
label: area?.tag,
|
||||
};
|
||||
})
|
||||
);
|
||||
}, [areasStore]);
|
||||
|
||||
const handleFilter = (e) => {
|
||||
setSearchValue(e.target.value);
|
||||
if (e.target.value !== "") {
|
||||
setOrganizations(
|
||||
OrganizationsStore.organizationWithPaginate?.content?.filter(
|
||||
(organisation) =>
|
||||
organisation.tag
|
||||
.toUpperCase()
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
.includes(
|
||||
e.target.value
|
||||
.toUpperCase()
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setOrganizations(OrganizationsStore.organizationWithPaginate?.content);
|
||||
}
|
||||
};
|
||||
const ExpandableTable = ({ data }) => {
|
||||
return (
|
||||
<div className="expandable-content p-2">
|
||||
<p className="font-small-3 mt-2">
|
||||
<span className="font-weight-bold">
|
||||
{t("Organizations.childOrganization")}:
|
||||
</span>{" "}
|
||||
{data?.children?.length > 0
|
||||
? " " + data.children?.map((childNumber) => childNumber?.child.tag)
|
||||
: "-"}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const handlePagination = (page) => {
|
||||
setCurrentPage(page.selected + 1);
|
||||
setOrganizations(OrganizationsStore.organizationWithPaginate?.content);
|
||||
};
|
||||
|
||||
const handlePerPage = (e) => {
|
||||
setCurrentPage(1);
|
||||
setRowsPerPage(parseInt(e.target.value));
|
||||
setOrganizations(OrganizationsStore.organizationWithPaginate?.content);
|
||||
};
|
||||
|
||||
const CustomPagination = () => {
|
||||
const count = Number(
|
||||
(
|
||||
OrganizationsStore.organizationWithPaginate?.pageInfo?.totalElements /
|
||||
rowsPerPage
|
||||
).toFixed(1)
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactPaginate
|
||||
previousLabel={""}
|
||||
nextLabel={""}
|
||||
breakLabel="..."
|
||||
pageCount={count || 1}
|
||||
marginPagesDisplayed={2}
|
||||
pageRangeDisplayed={2}
|
||||
activeClassName="active"
|
||||
forcePage={currentPage !== 0 ? currentPage - 1 : 0}
|
||||
onPageChange={(page) => handlePagination(page)}
|
||||
pageClassName={"page-item"}
|
||||
nextLinkClassName={"page-link"}
|
||||
nextClassName={"page-item next"}
|
||||
previousClassName={"page-item prev"}
|
||||
previousLinkClassName={"page-link"}
|
||||
pageLinkClassName={"page-link"}
|
||||
breakClassName="page-item"
|
||||
breakLinkClassName="page-link"
|
||||
containerClassName={
|
||||
"pagination react-paginate separated-pagination pagination-sm justify-content-end pr-1 mt-1"
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const onAddOrganisationButtonPressed = () => {
|
||||
setEditingOrganizationData({ id: null });
|
||||
setShowAddOrganizationModal(true);
|
||||
};
|
||||
|
||||
const onAddOrganisationModalButtonPressed = () => {
|
||||
if (
|
||||
OrganizationsStore.dataOrganization?.some(
|
||||
(organization) =>
|
||||
organization.tag === editingOrganizationData.tag &&
|
||||
organization.id !== editingOrganizationData.id &&
|
||||
organization.tag.toUpperCase() ===
|
||||
editingOrganizationData.tag.toUpperCase()
|
||||
)
|
||||
) {
|
||||
enqueueSnackbar(t("Organizations.existingName"), {
|
||||
variant: "error",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const editedAreas = (editingOrganizationData?.areas || []).map(
|
||||
(s) => s?.value || s.toString()
|
||||
);
|
||||
|
||||
const newOrganisationData = {
|
||||
id: editingOrganizationData?.id || null,
|
||||
tag: editingOrganizationData.tag,
|
||||
parent:
|
||||
editingOrganizationData?.parent?.[0]?.value ||
|
||||
editingOrganizationData?.parent,
|
||||
description: editingOrganizationData?.description || "-",
|
||||
deleted: editingOrganizationData?.deleted || false,
|
||||
isDefault: editingOrganizationData?.isDefault || true,
|
||||
areas: editedAreas,
|
||||
};
|
||||
|
||||
const check = JSON.parse(
|
||||
JSON.stringify(newOrganisationData),
|
||||
(key, value) => (value === null || value === "" ? undefined : value)
|
||||
);
|
||||
|
||||
dispatch(
|
||||
newOrganisationData.id
|
||||
? updateOrganization(check)
|
||||
: addOrganization(check)
|
||||
)
|
||||
.then(() => {
|
||||
enqueueSnackbar(
|
||||
`${newOrganisationData.tag}, ${
|
||||
!newOrganisationData.id
|
||||
? t("Warnings.addedSuccessfully")
|
||||
: t("Warnings.updatedSuccessfully")
|
||||
}`,
|
||||
{
|
||||
variant: "success",
|
||||
}
|
||||
);
|
||||
setEditingOrganizationData(null);
|
||||
setShowAddOrganizationModal(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
enqueueSnackbar(
|
||||
`${newOrganisationData.tag} ${
|
||||
!newOrganisationData.id
|
||||
? t("Warnings.addedFail")
|
||||
: t("Warnings.updatedFail")
|
||||
}`,
|
||||
{
|
||||
variant: "error",
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const renderOrganisationModal = () => {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={showAddOrganizationModal}
|
||||
toggle={() => setShowAddOrganizationModal(!showAddOrganizationModal)}
|
||||
className="modal-dialog-centered"
|
||||
>
|
||||
<ModalHeader
|
||||
toggle={() => setShowAddOrganizationModal(!showAddOrganizationModal)}
|
||||
>
|
||||
{editingOrganizationData?.id
|
||||
? editingOrganizationData?.tag
|
||||
: t("Organizations.addOrganization")}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" for="organization-tag">
|
||||
{t("Organizations.organizationName")}:
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="organization-tag"
|
||||
placeholder=""
|
||||
value={editingOrganizationData?.tag}
|
||||
onChange={(e) =>
|
||||
setEditingOrganizationData((data) => ({
|
||||
...data,
|
||||
tag: e.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" for="organization-description">
|
||||
{t("Organizations.organization") + t("Description")}:
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="organization-description"
|
||||
placeholder=""
|
||||
value={editingOrganizationData?.description}
|
||||
onChange={(e) =>
|
||||
setEditingOrganizationData((data) => ({
|
||||
...data,
|
||||
description: e.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" for="parent-organization">
|
||||
{t("Organizations.parentOrganization")}:
|
||||
</Label>
|
||||
<Select
|
||||
id="parent-organization"
|
||||
isClearable={false}
|
||||
closeMenuOnSelect={true}
|
||||
options={organizationsOptions}
|
||||
className="react-select"
|
||||
placeholder=""
|
||||
filterOption={customFilterForSelect}
|
||||
defaultValue={editingOrganizationData?.parent?.[0] || ""}
|
||||
onChange={(value) =>
|
||||
setEditingOrganizationData({
|
||||
...editingOrganizationData,
|
||||
parent: value?.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" for="areas-organization">
|
||||
{t("Areas.areas")}:
|
||||
</Label>
|
||||
<Select
|
||||
id="areas-organization"
|
||||
isClearable={false}
|
||||
closeMenuOnSelect={false}
|
||||
isMulti
|
||||
options={areasOptions}
|
||||
className="react-select"
|
||||
placeholder=""
|
||||
filterOption={customFilterForSelect}
|
||||
defaultValue={editingOrganizationData?.areas || ""}
|
||||
onChange={(value) =>
|
||||
setEditingOrganizationData({
|
||||
...editingOrganizationData,
|
||||
areas: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="primary" onClick={onAddOrganisationModalButtonPressed}>
|
||||
{!editingOrganizationData?.id ? t("Cruds.save") : t("Cruds.update")}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const handleDeleteOrganization = async (selectedOrganisation) => {
|
||||
const result = await Swal.fire({
|
||||
title: `${selectedOrganisation.tag}, ${t("Warnings.sureForDelete")}`,
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonText: t("Cruds.delete"),
|
||||
cancelButtonText: t("Cruds.cancel"),
|
||||
customClass: {
|
||||
confirmButton: "btn btn-primary",
|
||||
cancelButton: "btn btn-danger ml-1",
|
||||
},
|
||||
buttonsStyling: false,
|
||||
});
|
||||
|
||||
// Only proceed if the user confirmed the deletion
|
||||
if (result.isConfirmed) {
|
||||
dispatch(deleteOrganization(selectedOrganisation.id.trim()))
|
||||
.then((response) => {
|
||||
// Check if there are errors in the response
|
||||
if (response && response.error) {
|
||||
enqueueSnackbar(response.error, {
|
||||
variant: "error",
|
||||
});
|
||||
} else {
|
||||
enqueueSnackbar(
|
||||
`${selectedOrganisation.tag}, ${t("Warnings.deletedSuccessfully")}`,
|
||||
{
|
||||
variant: "success",
|
||||
}
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error deleting organization:", error);
|
||||
enqueueSnackbar(
|
||||
`${selectedOrganisation.tag}, ${t("Warnings.deletedFail")}`,
|
||||
{
|
||||
variant: "error",
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditOrganization = (selectedOrganisation) => {
|
||||
let editOrganizationOptions = [];
|
||||
if (selectedOrganisation.children != null) {
|
||||
//remove selectedOrganisation's children from parentOrganizationOptions
|
||||
OrganizationsStore.dataOrganization?.filter((org) => {
|
||||
for (let i = 0; i < selectedOrganisation.children?.length; i++) {
|
||||
if (selectedOrganisation.children[i]?.child?.id !== org.id) {
|
||||
editOrganizationOptions.push(org);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
OrganizationsStore.dataOrganization?.forEach((org) =>
|
||||
editOrganizationOptions.push(org)
|
||||
);
|
||||
}
|
||||
//remove selectedOrganisation from parentOrganizationOptions
|
||||
editOrganizationOptions = editOrganizationOptions.filter(
|
||||
(option) => option.id != selectedOrganisation.id
|
||||
);
|
||||
//remove duplicate organizations
|
||||
let uniqEditOrganizationOptions = [...new Set(editOrganizationOptions)];
|
||||
setOrganizationsOptions(
|
||||
uniqEditOrganizationOptions?.map((organization) => {
|
||||
return {
|
||||
value: organization?.id,
|
||||
label: organization?.tag,
|
||||
};
|
||||
})
|
||||
);
|
||||
setShowAddOrganizationModal(true);
|
||||
const selectedOrganizationsParents = selectedOrganisation.parents?.map(
|
||||
(org) => ({
|
||||
value: org.parent.id,
|
||||
label: org.parent.tag,
|
||||
})
|
||||
);
|
||||
const selectedAreasOfOrganizations = selectedOrganisation.areas?.map(
|
||||
(area) => ({
|
||||
value: area?.id,
|
||||
label: area?.tag,
|
||||
})
|
||||
);
|
||||
setEditingOrganizationData({
|
||||
...selectedOrganisation,
|
||||
parent: selectedOrganizationsParents,
|
||||
areas: selectedAreasOfOrganizations,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "2%" }}>
|
||||
{" "}
|
||||
<Card>
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle tag="h4">{t("Organizations.organizations")}</CardTitle>
|
||||
{permissionCheck("organization_create") && (
|
||||
<Button
|
||||
className="ml-2"
|
||||
color="primary"
|
||||
onClick={onAddOrganisationButtonPressed}
|
||||
>
|
||||
<Plus size={15} />
|
||||
<span className="align-middle ml-50">
|
||||
{t("Organizations.addOrganization")}
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
</CardHeader>
|
||||
<Row className="mx-0 mt-1 mb-50">
|
||||
<Col sm="6" md="2">
|
||||
<div className="d-flex align-items-center">
|
||||
<Label for="sort-select">{t("Show")}</Label>
|
||||
<Input
|
||||
className="ml-1 dataTable-select"
|
||||
type="select"
|
||||
id="sort-select"
|
||||
value={rowsPerPage}
|
||||
onChange={(e) => handlePerPage(e)}
|
||||
>
|
||||
<option value={5}>5</option>
|
||||
<option value={10}>10</option>
|
||||
<option value={25}>25</option>
|
||||
<option value={50}>50</option>
|
||||
<option value={75}>75</option>
|
||||
<option value={100}>100</option>
|
||||
</Input>
|
||||
</div>
|
||||
</Col>
|
||||
<Col sm="6" md="3" className="mt-1">
|
||||
{permissionCheck("deleted_items") && (
|
||||
<div className="ml-4">
|
||||
<Input
|
||||
className="dataTable-select"
|
||||
id="deleted-checkbox"
|
||||
type="checkbox"
|
||||
checked={showDeletedOrganizations}
|
||||
onChange={(e) => {
|
||||
setShowDeletedOrganizations(e.target.checked);
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
/>
|
||||
<Label for="deleted-checkbox">
|
||||
{t("Organizations.showDeletedOrganizations")}
|
||||
</Label>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
<Col
|
||||
className="d-flex align-items-center justify-content-end mt-sm-0 mt-1 ml-md-auto"
|
||||
sm="6"
|
||||
md="3"
|
||||
>
|
||||
<Label className="mr-1" for="search-input">
|
||||
{t("Filter")}
|
||||
</Label>
|
||||
<Input
|
||||
className="dataTable-filter"
|
||||
type="text"
|
||||
bsSize="sm"
|
||||
id="search-input"
|
||||
value={searchValue}
|
||||
onChange={handleFilter}
|
||||
placeholder={t("ByName")}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<DataTable
|
||||
noHeader
|
||||
pagination
|
||||
paginationServer
|
||||
expandableRows
|
||||
expandOnRowClicked
|
||||
expandableRowsComponent={ExpandableTable}
|
||||
className="react-dataTable"
|
||||
columns={serverSideColumns}
|
||||
sortIcon={<ChevronDown size={10} />}
|
||||
paginationComponent={CustomPagination}
|
||||
data={organizations}
|
||||
noDataComponent={
|
||||
<p className="p-2">
|
||||
{t("Organizations.organization")}
|
||||
{t("Warnings.notFound")}
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
{renderOrganisationModal()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(OrganizationManagement);
|
||||
463
sge-frontend/src/views/ResetPassword.js
Normal file
463
sge-frontend/src/views/ResetPassword.js
Normal file
@@ -0,0 +1,463 @@
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import {
|
||||
Card,
|
||||
Box,
|
||||
Button,
|
||||
FormControl,
|
||||
Grid,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
InputLabel,
|
||||
OutlinedInput,
|
||||
Stack,
|
||||
Typography,
|
||||
FormHelperText,
|
||||
} from "@mui/material";
|
||||
import Visibility from "@mui/icons-material/Visibility";
|
||||
import VisibilityOff from "@mui/icons-material/VisibilityOff";
|
||||
import backgroundImage from "../assets/images/background_dot.png";
|
||||
import { ThemeColors } from "../utility/context/ThemeColors";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSnackbar } from "notistack";
|
||||
import ApplicationService from "../services/ApplicationService.js";
|
||||
|
||||
const ResetPassword = () => {
|
||||
const history = useHistory();
|
||||
const { colors } = useContext(ThemeColors);
|
||||
const { t } = useTranslation();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [resetPasswordData, setResetPasswordData] = useState({
|
||||
email: "",
|
||||
verificationCode: "",
|
||||
newPassword: "",
|
||||
passwordRepeat: "",
|
||||
});
|
||||
const [error, setError] = useState({
|
||||
verificationCode: "",
|
||||
newPassword: "",
|
||||
passwordRepeat: "",
|
||||
});
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [showPasswordRepeat, setShowPasswordRepeat] = useState(false);
|
||||
const [minutes, setMinutes] = useState(
|
||||
sessionStorage.getItem("minutes")
|
||||
? parseInt(sessionStorage.getItem("minutes"))
|
||||
: 0
|
||||
);
|
||||
const [seconds, setSeconds] = useState(
|
||||
sessionStorage.getItem("seconds")
|
||||
? parseInt(sessionStorage.getItem("seconds"))
|
||||
: 59
|
||||
);
|
||||
|
||||
const userEmail = localStorage.getItem("userEmail");
|
||||
|
||||
useEffect(() => {
|
||||
setResetPasswordData({
|
||||
...resetPasswordData,
|
||||
email: userEmail,
|
||||
});
|
||||
}, [userEmail]);
|
||||
|
||||
const onInputChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setResetPasswordData((prev) => ({
|
||||
...prev,
|
||||
[name]: value,
|
||||
}));
|
||||
validateInput(e);
|
||||
};
|
||||
|
||||
const validateInput = (e) => {
|
||||
let { name, value } = e.target;
|
||||
setError((prev) => {
|
||||
const stateObj = { ...prev, [name]: "" };
|
||||
|
||||
switch (name) {
|
||||
case "verificationCode":
|
||||
if (!value) {
|
||||
stateObj[name] =
|
||||
t("PasswordLabel.verificationCode") +
|
||||
t("PasswordLabel.cannotbeleftblank");
|
||||
}
|
||||
break;
|
||||
|
||||
case "newPassword":
|
||||
if (!value) {
|
||||
stateObj[name] =
|
||||
t("PasswordLabel.passwordLabel") +
|
||||
t("PasswordLabel.cannotbeleftblank");
|
||||
} else if (
|
||||
resetPasswordData.newPassword &&
|
||||
value !== resetPasswordData.passwordRepeat
|
||||
) {
|
||||
stateObj["passwordRepeat"] = t("PasswordLabel.passwordNotSame");
|
||||
} else {
|
||||
stateObj["passwordRepeat"] = resetPasswordData.passwordRepeat
|
||||
? ""
|
||||
: error.passwordRepeat;
|
||||
}
|
||||
break;
|
||||
|
||||
case "passwordRepeat":
|
||||
if (!value) {
|
||||
stateObj[name] =
|
||||
t("PasswordLabel.confirmPassword") +
|
||||
t("PasswordLabel.cannotbeleftblank");
|
||||
} else if (
|
||||
resetPasswordData.newPassword &&
|
||||
value !== resetPasswordData.newPassword
|
||||
) {
|
||||
stateObj[name] = t("PasswordLabel.passwordNotSame");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return stateObj;
|
||||
});
|
||||
};
|
||||
|
||||
const isPassword = (password) =>
|
||||
/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,20}$/.test(password);
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
if (!isPassword(resetPasswordData.newPassword)) {
|
||||
setError({
|
||||
...error,
|
||||
newPassword: t("PasswordLabel.passwordValidation"),
|
||||
});
|
||||
} else if (
|
||||
resetPasswordData.newPassword != "" &&
|
||||
resetPasswordData.passwordRepeat != "" &&
|
||||
resetPasswordData.verificationCode != "" &&
|
||||
resetPasswordData.newPassword === resetPasswordData.passwordRepeat
|
||||
) {
|
||||
setLoading(true);
|
||||
await ApplicationService.http()
|
||||
.post(
|
||||
"/graphql",
|
||||
{
|
||||
query: `
|
||||
mutation {
|
||||
resetPassword(
|
||||
code: "${resetPasswordData.verificationCode}",
|
||||
password: "${resetPasswordData.newPassword}")
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer " + localStorage.getItem("accessToken"),
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.data?.errors) {
|
||||
enqueueSnackbar(t("PasswordLabel.invalidVerificationCode"), {
|
||||
variant: "error",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
setLoading(false);
|
||||
} else {
|
||||
enqueueSnackbar(t("PasswordLabel.redirectToLogin"), {
|
||||
variant: "success",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
history.push("/login");
|
||||
}
|
||||
});
|
||||
} else if (resetPasswordData.verificationCode === "") {
|
||||
setError({
|
||||
verificationCode:
|
||||
t("PasswordLabel.verificationCode") +
|
||||
t("PasswordLabel.cannotbeleftblank"),
|
||||
});
|
||||
} else if (
|
||||
resetPasswordData.newPassword === "" ||
|
||||
resetPasswordData.passwordRepeat === ""
|
||||
) {
|
||||
setError({
|
||||
newPassword:
|
||||
t("PasswordLabel.passwordLabel") +
|
||||
t("PasswordLabel.cannotbeleftblank"),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
if (seconds > 0) {
|
||||
setSeconds(seconds - 1);
|
||||
sessionStorage.setItem("seconds", (seconds - 1).toString());
|
||||
}
|
||||
|
||||
if (seconds === 0) {
|
||||
if (minutes === 0) {
|
||||
clearInterval(interval);
|
||||
} else {
|
||||
setSeconds(59);
|
||||
sessionStorage.setItem("seconds", "59");
|
||||
setMinutes(minutes - 1);
|
||||
sessionStorage.setItem("minutes", (minutes - 1).toString());
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [seconds]);
|
||||
|
||||
const handleResendButtonPressed = () => {
|
||||
//send verification code
|
||||
setSeconds(59);
|
||||
setMinutes(0);
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
direction="column"
|
||||
justifyContent="flex-end"
|
||||
sx={{ minHeight: "100vh" }}
|
||||
style={{
|
||||
backgroundImage: `url(${backgroundImage})`,
|
||||
backgroundRepeat: "repeat",
|
||||
}}
|
||||
>
|
||||
<Grid
|
||||
container
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
sx={{ minHeight: "calc(100vh - 68px)" }}
|
||||
>
|
||||
<Grid item sx={{ m: { xs: 1, sm: 3 }, mb: 0 }}>
|
||||
<Card
|
||||
sx={{
|
||||
maxWidth: { xs: 400, lg: 475 },
|
||||
margin: { xs: 2.5, md: 3 },
|
||||
"& > *": {
|
||||
flexGrow: 1,
|
||||
flexBasis: "50%",
|
||||
},
|
||||
}}
|
||||
content="false"
|
||||
>
|
||||
<Box sx={{ p: { xs: 2, sm: 3, xl: 5 } }}>
|
||||
<Grid
|
||||
container
|
||||
spacing={2}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Grid item xs={12}>
|
||||
<Grid
|
||||
container
|
||||
direction="column-reverse"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Grid item>
|
||||
<Stack
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
spacing={1}
|
||||
>
|
||||
<Typography
|
||||
color="#028a4a"
|
||||
gutterBottom
|
||||
variant="h3"
|
||||
style={{
|
||||
fontSize: "1.25rem",
|
||||
color: colors.primary.dark,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{t("PasswordLabel.resetPassword")}
|
||||
</Typography>
|
||||
<Grid>
|
||||
<Button
|
||||
disableElevation
|
||||
disabled={seconds > 0 || minutes > 0}
|
||||
fullWidth
|
||||
onClick={handleResendButtonPressed}
|
||||
size="large"
|
||||
color="success"
|
||||
id="resend-button"
|
||||
>
|
||||
{t("PasswordLabel.resend")} (
|
||||
{minutes < 10 ? `0${minutes}` : minutes}:
|
||||
{seconds < 10 ? `0${seconds}` : seconds})
|
||||
</Button>
|
||||
</Grid>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<form
|
||||
noValidate
|
||||
onSubmit={handleSubmit}
|
||||
id="reset-password-form"
|
||||
>
|
||||
<FormControl fullWidth sx={{ ...colors.primary.light }}>
|
||||
<InputLabel htmlFor="outlined-adornment-email">
|
||||
Email
|
||||
</InputLabel>
|
||||
<OutlinedInput
|
||||
id="outlined-adornment-email"
|
||||
type="text"
|
||||
value={resetPasswordData?.email}
|
||||
name="email"
|
||||
label="Email"
|
||||
readOnly
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormControl
|
||||
fullWidth
|
||||
error={Boolean(error?.verificationCode)}
|
||||
sx={{ ...colors.primary.light }}
|
||||
style={{
|
||||
marginTop: "1rem",
|
||||
}}
|
||||
>
|
||||
<InputLabel htmlFor="outlined-adornment-verification-code">
|
||||
{t("PasswordLabel.verificationCode")}
|
||||
</InputLabel>
|
||||
<OutlinedInput
|
||||
id="outlined-adornment-verification-code"
|
||||
type="text"
|
||||
value={resetPasswordData?.verificationCode}
|
||||
name="verificationCode"
|
||||
onBlur={validateInput}
|
||||
onChange={onInputChange}
|
||||
label={t("PasswordLabel.verificationCode")}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormControl
|
||||
fullWidth
|
||||
error={Boolean(error?.newPassword)}
|
||||
sx={{ ...colors.primary.light }}
|
||||
style={{
|
||||
marginTop: "1rem",
|
||||
}}
|
||||
>
|
||||
<InputLabel htmlFor="outlined-adornment-reset-password">
|
||||
{t("PasswordLabel.passwordLabel")}
|
||||
</InputLabel>
|
||||
<OutlinedInput
|
||||
id="outlined-adornment-reset-password"
|
||||
type={showPassword ? "text" : "password"}
|
||||
value={resetPasswordData?.newPassword}
|
||||
name="newPassword"
|
||||
onBlur={validateInput}
|
||||
onChange={onInputChange}
|
||||
endAdornment={
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label="toggle password visibility"
|
||||
onClick={() => setShowPassword(true)}
|
||||
onMouseDown={() => setShowPassword(false)}
|
||||
edge="end"
|
||||
size="large"
|
||||
>
|
||||
{showPassword ? (
|
||||
<Visibility />
|
||||
) : (
|
||||
<VisibilityOff />
|
||||
)}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
}
|
||||
label={t("PasswordLabel.passwordLabel")}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormControl
|
||||
fullWidth
|
||||
error={Boolean(error?.passwordRepeat)}
|
||||
sx={{ ...colors.primary.light }}
|
||||
style={{
|
||||
marginTop: "1rem",
|
||||
}}
|
||||
>
|
||||
<InputLabel htmlFor="outlined-adornment-reset-passwordRepeat">
|
||||
{t("PasswordLabel.confirmPassword")}
|
||||
</InputLabel>
|
||||
<OutlinedInput
|
||||
id="outlined-adornment-reset-passwordRepeat"
|
||||
type={showPasswordRepeat ? "text" : "password"}
|
||||
value={resetPasswordData?.passwordRepeat}
|
||||
name="passwordRepeat"
|
||||
onBlur={validateInput}
|
||||
onChange={onInputChange}
|
||||
endAdornment={
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label="toggle password visibility"
|
||||
onClick={() => setShowPasswordRepeat(true)}
|
||||
onMouseDown={() => setShowPasswordRepeat(false)}
|
||||
edge="end"
|
||||
size="large"
|
||||
>
|
||||
{showPasswordRepeat ? (
|
||||
<Visibility />
|
||||
) : (
|
||||
<VisibilityOff />
|
||||
)}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
}
|
||||
label={t("PasswordLabel.confirmPassword")}
|
||||
/>
|
||||
</FormControl>
|
||||
{(error?.passwordRepeat ||
|
||||
error?.newPassword ||
|
||||
error?.verificationCode) && (
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<FormHelperText error>
|
||||
{error?.verificationCode}
|
||||
</FormHelperText>
|
||||
<FormHelperText error>
|
||||
{error?.newPassword}
|
||||
</FormHelperText>
|
||||
<FormHelperText error>
|
||||
{error?.passwordRepeat}
|
||||
</FormHelperText>
|
||||
</Box>
|
||||
)}
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Button
|
||||
disableElevation
|
||||
disabled={loading}
|
||||
fullWidth
|
||||
size="large"
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="success"
|
||||
id="reset-password-form-submit-button"
|
||||
>
|
||||
{t("Cruds.save")}
|
||||
</Button>
|
||||
</Box>
|
||||
</form>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResetPassword;
|
||||
441
sge-frontend/src/views/RoleManagement.js
Normal file
441
sge-frontend/src/views/RoleManagement.js
Normal file
@@ -0,0 +1,441 @@
|
||||
import React, { useState, useEffect, memo } from "react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import ReactPaginate from "react-paginate";
|
||||
import { ChevronDown, MoreVertical, Plus, Trash } from "react-feather";
|
||||
import DataTable from "react-data-table-component";
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Input,
|
||||
Label,
|
||||
Row,
|
||||
Col,
|
||||
Button,
|
||||
Modal,
|
||||
ModalHeader,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
UncontrolledDropdown,
|
||||
DropdownToggle,
|
||||
DropdownMenu,
|
||||
DropdownItem,
|
||||
} from "reactstrap";
|
||||
import { Edit } from "@mui/icons-material";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { default as SweetAlert } from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import Select from "react-select";
|
||||
import makeAnimated from "react-select/animated";
|
||||
import {
|
||||
getRolesWithPaginate,
|
||||
deleteRoles,
|
||||
addRoles,
|
||||
updateRoles,
|
||||
} from "../redux/actions/roles";
|
||||
import { getUserPermissions, getAllPermissions } from "../redux/actions/permissions";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { permissionCheck } from "../components/permission-check";
|
||||
import { customFilterForSelect } from "../utility/Utils";
|
||||
|
||||
const Swal = withReactContent(SweetAlert);
|
||||
const animatedComponents = makeAnimated();
|
||||
|
||||
const RoleManagement = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const initialColumns = [
|
||||
{
|
||||
name: t("Roles.roleName"),
|
||||
selector: (row) => row.tag,
|
||||
sortable: true,
|
||||
maxWidth: "250px",
|
||||
},
|
||||
{
|
||||
name: t("Roles.permissions"),
|
||||
selector: (row) => row.permissions,
|
||||
sortable: true,
|
||||
minWidth: "250px",
|
||||
cell: (row) => (
|
||||
<span>{row.permissions?.map((perm) => perm?.description + " │ ")}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: t("Actions"),
|
||||
allowOverflow: false,
|
||||
maxWidth: "100px",
|
||||
cell: (row) => {
|
||||
return (
|
||||
<div className="d-flex">
|
||||
<UncontrolledDropdown>
|
||||
<DropdownToggle className="pl-1" tag="span">
|
||||
<MoreVertical size={15} />
|
||||
</DropdownToggle>
|
||||
<DropdownMenu container={"body"} left={"true"}>
|
||||
{permissionCheck("role_update") && (
|
||||
<DropdownItem
|
||||
tag="a"
|
||||
className="w-100"
|
||||
onClick={() => handleEditRole(row)}
|
||||
>
|
||||
<Edit size={15} />
|
||||
<span className="align-middle ml-50">
|
||||
{t("Cruds.edit")}
|
||||
</span>
|
||||
</DropdownItem>
|
||||
)}
|
||||
{permissionCheck("role_delete") && (
|
||||
<DropdownItem
|
||||
tag="a"
|
||||
className="w-100"
|
||||
onClick={() => handleDeleteRole(row)}
|
||||
>
|
||||
<Trash size={15} />
|
||||
<span className="align-middle ml-50">
|
||||
{t("Cruds.delete")}
|
||||
</span>
|
||||
</DropdownItem>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
</UncontrolledDropdown>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (!(permissionCheck("role_delete") || permissionCheck("role_update"))) {
|
||||
const updatedColumns = initialColumns.filter(
|
||||
(column) => column.name !== ("Aksiyonlar" || "Actions")
|
||||
);
|
||||
setServerSideColumns(updatedColumns);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(5);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const [serverSideColumns, setServerSideColumns] = useState(initialColumns);
|
||||
|
||||
const permissionsStore = useSelector((state) => state.permissionsReducer);
|
||||
const rolesStore = useSelector(
|
||||
(state) => state.rolesReducer.rolesWithPaginate
|
||||
);
|
||||
const [permissionsOptions, setPermissionsOptions] = useState([]);
|
||||
const [roles, setRoles] = useState([]);
|
||||
const [showAddRoleModal, setShowAddRoleModal] = useState(false);
|
||||
const [editingRoleData, setEditingRoleData] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(
|
||||
getRolesWithPaginate({
|
||||
currentPage,
|
||||
rowsPerPage,
|
||||
})
|
||||
);
|
||||
}, [currentPage, rowsPerPage]);
|
||||
|
||||
useEffect(() => {
|
||||
const roleTag = localStorage.getItem("roleTag");
|
||||
if (roleTag === "SUPER_ADMIN") {
|
||||
dispatch(getAllPermissions());
|
||||
} else {
|
||||
dispatch(getUserPermissions());
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (rolesStore?.content) {
|
||||
setRoles(rolesStore?.content);
|
||||
}
|
||||
}, [rolesStore]);
|
||||
|
||||
useEffect(() => {
|
||||
setPermissionsOptions(
|
||||
permissionsStore?.permissions?.map((permission) => {
|
||||
return {
|
||||
value: permission?.id,
|
||||
label: permission?.description,
|
||||
};
|
||||
})
|
||||
);
|
||||
}, [permissionsStore]);
|
||||
|
||||
const handlePagination = (page) => {
|
||||
setCurrentPage(page.selected + 1);
|
||||
setRoles(rolesStore?.content);
|
||||
};
|
||||
|
||||
const handlePerPage = (e) => {
|
||||
setCurrentPage(1);
|
||||
setRowsPerPage(parseInt(e.target.value));
|
||||
setRoles(rolesStore?.content);
|
||||
};
|
||||
const CustomPagination = () => {
|
||||
const count = Number(
|
||||
(rolesStore?.pageInfo?.totalElements / rowsPerPage).toFixed(1)
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactPaginate
|
||||
previousLabel={""}
|
||||
nextLabel={""}
|
||||
breakLabel="..."
|
||||
pageCount={count || 1}
|
||||
marginPagesDisplayed={2}
|
||||
pageRangeDisplayed={2}
|
||||
activeClassName="active"
|
||||
forcePage={currentPage !== 0 ? currentPage - 1 : 0}
|
||||
onPageChange={(page) => handlePagination(page)}
|
||||
pageClassName={"page-item"}
|
||||
nextLinkClassName={"page-link"}
|
||||
nextClassName={"page-item next"}
|
||||
previousClassName={"page-item prev"}
|
||||
previousLinkClassName={"page-link"}
|
||||
pageLinkClassName={"page-link"}
|
||||
breakClassName="page-item"
|
||||
breakLinkClassName="page-link"
|
||||
containerClassName={
|
||||
"pagination react-paginate separated-pagination pagination-sm justify-content-end pr-1 mt-1"
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const onAddRoleButtonPressed = () => {
|
||||
setEditingRoleData({ id: null });
|
||||
setShowAddRoleModal(true);
|
||||
};
|
||||
const onAddRoleModalButtonPressed = () => {
|
||||
const arr = [];
|
||||
const r = editingRoleData?.permissions?.forEach((s) => {
|
||||
if (s?.value) {
|
||||
arr.push('"' + s?.value + '"');
|
||||
} else {
|
||||
arr.push('"' + s + '"');
|
||||
}
|
||||
});
|
||||
setLoading(true);
|
||||
if (
|
||||
rolesStore.roles?.some(
|
||||
(c) => c.tag === editingRoleData.tag && c.id != editingRoleData.id
|
||||
)
|
||||
) {
|
||||
enqueueSnackbar(t("Roles.existingRole"), {
|
||||
variant: "error",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!editingRoleData.id) {
|
||||
const newRoleData = {
|
||||
tag: editingRoleData.tag?.toUpperCase(),
|
||||
permissions: arr,
|
||||
id: editingRoleData.id,
|
||||
};
|
||||
|
||||
dispatch(addRoles(newRoleData))
|
||||
.then(() => {
|
||||
setLoading(false);
|
||||
setShowAddRoleModal(false);
|
||||
enqueueSnackbar(t("Warnings.addedSuccessfully"), {
|
||||
variant: "success",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
setShowAddRoleModal(false);
|
||||
enqueueSnackbar(t("Warnings.addedFail"), {
|
||||
variant: "error",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
const newRoleData = {
|
||||
id: editingRoleData.id,
|
||||
tag: editingRoleData.tag?.toUpperCase(),
|
||||
permissions: arr,
|
||||
};
|
||||
dispatch(updateRoles(newRoleData))
|
||||
.then(() => {
|
||||
enqueueSnackbar(t("Warnings.updatedSuccessfully"), {
|
||||
variant: "success",
|
||||
});
|
||||
setLoading(false);
|
||||
setEditingRoleData(null);
|
||||
setShowAddRoleModal(false);
|
||||
if (!editingRoleData.value) setEditingRoleData(null);
|
||||
})
|
||||
.catch(() => {
|
||||
enqueueSnackbar(`${newRoleData.tag}, ${t("Warnings.updatedFail")}`, {
|
||||
variant: "error",
|
||||
});
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const renderRoleModal = () => {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={showAddRoleModal}
|
||||
toggle={() => setShowAddRoleModal(!showAddRoleModal)}
|
||||
className="modal-dialog-centered"
|
||||
>
|
||||
<ModalHeader toggle={() => setShowAddRoleModal(!showAddRoleModal)}>
|
||||
{editingRoleData?.id ? editingRoleData.tag : t("Roles.addRole")}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" for="role-name">
|
||||
{t("Roles.roleName")}:
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="role-name"
|
||||
placeholder=""
|
||||
value={editingRoleData?.tag || ""}
|
||||
onChange={(e) =>
|
||||
setEditingRoleData({
|
||||
...editingRoleData,
|
||||
tag: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" for="permissions-select">
|
||||
{t("Roles.permissions")}:
|
||||
</Label>
|
||||
<Select
|
||||
id="permissions-select"
|
||||
isClearable={false}
|
||||
closeMenuOnSelect={false}
|
||||
components={animatedComponents}
|
||||
isMulti
|
||||
options={permissionsOptions}
|
||||
className="react-select"
|
||||
placeholder=""
|
||||
filterOption={customFilterForSelect}
|
||||
defaultValue={editingRoleData?.permissions}
|
||||
onChange={(value) => {
|
||||
setEditingRoleData({
|
||||
...editingRoleData,
|
||||
permissions: value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
disabled={!(editingRoleData?.permissions && editingRoleData?.tag)}
|
||||
color="primary"
|
||||
onClick={onAddRoleModalButtonPressed}
|
||||
>
|
||||
{loading
|
||||
? t("Cruds.saving")
|
||||
: !editingRoleData?.id
|
||||
? t("Cruds.save")
|
||||
: t("Cruds.update")}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const handleDeleteRole = async (selectedRole) => {
|
||||
const result = await Swal.fire({
|
||||
title: `${selectedRole.tag}, ${t("Warnings.sureForDelete")}`,
|
||||
text: t("Warnings.notUndone"),
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonText: t("Cruds.delete"),
|
||||
cancelButtonText: t("Cruds.cancel"),
|
||||
customClass: {
|
||||
confirmButton: "btn btn-primary",
|
||||
cancelButton: "btn btn-danger ml-1",
|
||||
},
|
||||
buttonsStyling: false,
|
||||
});
|
||||
if (result.value !== null && result.value === true) {
|
||||
dispatch(deleteRoles(selectedRole.id));
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditRole = (selectedRole) => {
|
||||
setShowAddRoleModal(true);
|
||||
const selectedRolePermissions = selectedRole.permissions?.map((x) => ({
|
||||
value: x.id,
|
||||
label: x.description,
|
||||
}));
|
||||
setEditingRoleData({
|
||||
...selectedRole,
|
||||
permissions: selectedRolePermissions,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "2%" }}>
|
||||
<Card>
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle tag="h4">{t("Roles.roles")}</CardTitle>
|
||||
{permissionCheck("role_create") && (
|
||||
<Button
|
||||
className="ml-2"
|
||||
color="primary"
|
||||
onClick={onAddRoleButtonPressed}
|
||||
>
|
||||
<Plus size={15} />
|
||||
<span className="align-middle ml-50">{t("Roles.addRole")}</span>
|
||||
</Button>
|
||||
)}
|
||||
</CardHeader>
|
||||
<Row className="mx-0 mt-1 mb-50">
|
||||
<Col sm="6" md="2">
|
||||
<div className="d-flex align-items-center">
|
||||
<Label for="sort-select">{t("Show")}</Label>
|
||||
<Input
|
||||
className="ml-1 dataTable-select"
|
||||
type="select"
|
||||
id="sort-select"
|
||||
value={rowsPerPage}
|
||||
onChange={(e) => handlePerPage(e)}
|
||||
>
|
||||
<option value={5}>5</option>
|
||||
<option value={10}>10</option>
|
||||
<option value={25}>25</option>
|
||||
<option value={50}>50</option>
|
||||
<option value={75}>75</option>
|
||||
<option value={100}>100</option>
|
||||
</Input>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<DataTable
|
||||
noHeader
|
||||
pagination
|
||||
paginationServer
|
||||
className="react-dataTable"
|
||||
columns={serverSideColumns}
|
||||
sortIcon={<ChevronDown size={10} />}
|
||||
paginationComponent={CustomPagination}
|
||||
data={[...roles]}
|
||||
noDataComponent={<p className="p-2">{t("Warnings.notFound")}</p>}
|
||||
/>
|
||||
</Card>
|
||||
{renderRoleModal()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(RoleManagement);
|
||||
420
sge-frontend/src/views/Survey/Answer.js
Normal file
420
sge-frontend/src/views/Survey/Answer.js
Normal file
@@ -0,0 +1,420 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ChevronDown, Edit, MoreVertical, Plus, Trash } from "react-feather";
|
||||
import ReactPaginate from "react-paginate";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useSnackbar } from "notistack";
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Input,
|
||||
Label,
|
||||
Row,
|
||||
Col,
|
||||
Button,
|
||||
Modal,
|
||||
ModalHeader,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
UncontrolledDropdown,
|
||||
DropdownToggle,
|
||||
DropdownMenu,
|
||||
DropdownItem,
|
||||
} from "reactstrap";
|
||||
import { default as SweetAlert } from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import DataTable from "react-data-table-component";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
getAnswersWithPaginate,
|
||||
addAnswer,
|
||||
updateAnswer,
|
||||
deleteAnswer,
|
||||
} from "../../redux/actions/answer";
|
||||
import { permissionCheck } from "../../components/permission-check";
|
||||
|
||||
const Answer = () => {
|
||||
const { t } = useTranslation();
|
||||
const initialColumns = [
|
||||
{
|
||||
name: t("Survey.value"),
|
||||
selector: (row) => row.value,
|
||||
sortable: true,
|
||||
minWidth: "250px",
|
||||
},
|
||||
{
|
||||
name: t("Survey.answer"),
|
||||
selector: (row) => row.answer,
|
||||
sortable: true,
|
||||
minWidth: "250px",
|
||||
},
|
||||
{
|
||||
name: t("Actions"),
|
||||
allowOverflow: false,
|
||||
maxWidth: "150px",
|
||||
cell: (row) => {
|
||||
return (
|
||||
<div className="d-flex">
|
||||
<UncontrolledDropdown>
|
||||
<DropdownToggle className="pl-1" tag="span">
|
||||
<MoreVertical size={15} />
|
||||
</DropdownToggle>
|
||||
<DropdownMenu container={"body"} end>
|
||||
{permissionCheck("answer_update") && (
|
||||
<DropdownItem
|
||||
tag="a"
|
||||
className="w-100"
|
||||
onClick={() => handleEditAnswer(row)}
|
||||
>
|
||||
<Edit size={15} />
|
||||
<span className="align-middle ml-50">
|
||||
{t("Cruds.edit")}
|
||||
</span>
|
||||
</DropdownItem>
|
||||
)}
|
||||
{permissionCheck("answer_delete") && (
|
||||
<DropdownItem
|
||||
tag="a"
|
||||
className="w-100"
|
||||
onClick={() => handleDeleteAnswer(row)}
|
||||
>
|
||||
<Trash size={15} />
|
||||
<span className="align-middle ml-50">
|
||||
{t("Cruds.delete")}
|
||||
</span>
|
||||
</DropdownItem>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
</UncontrolledDropdown>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!(permissionCheck("answer_update") || permissionCheck("answer_delete"))
|
||||
) {
|
||||
const updatedColumns = initialColumns.filter(
|
||||
(column) => column.name !== ("Aksiyonlar" || "Actions")
|
||||
);
|
||||
setServerSideColumns(updatedColumns);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const Swal = withReactContent(SweetAlert);
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(5);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
|
||||
const [serverSideColumns, setServerSideColumns] = useState(initialColumns);
|
||||
|
||||
const answersStore = useSelector((state) => state.answers);
|
||||
const [answer, setAnswers] = useState([]);
|
||||
const [showAddAnswerModal, setShowAddAnswerModal] = useState(false);
|
||||
const [editingAnswerData, setEditingAnswerData] = useState();
|
||||
useEffect(() => {
|
||||
dispatch(getAnswersWithPaginate({ currentPage, rowsPerPage }));
|
||||
}, [currentPage, rowsPerPage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (answersStore?.answersWithPaginate?.content) {
|
||||
setAnswers(answersStore?.answersWithPaginate?.content);
|
||||
}
|
||||
}, [answersStore.answersWithPaginate?.content]);
|
||||
|
||||
const handleFilter = (e) => {
|
||||
setSearchValue(e.target.value);
|
||||
|
||||
if (e.target.value !== "") {
|
||||
setAnswers(
|
||||
answersStore.answersWithPaginate?.content.filter((answer) =>
|
||||
answer.value.toLowerCase().includes(e.target.value.toLowerCase())
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setAnswers(answersStore.answersWithPaginate?.content);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePagination = (page) => {
|
||||
setCurrentPage(page.selected + 1);
|
||||
setAnswers(answersStore.answersWithPaginate?.content);
|
||||
};
|
||||
|
||||
const handlePerPage = (e) => {
|
||||
setCurrentPage(1);
|
||||
setRowsPerPage(parseInt(e.target.value));
|
||||
setAnswers(answersStore.answersWithPaginate?.content);
|
||||
};
|
||||
|
||||
const CustomPagination = () => {
|
||||
const count = Number(
|
||||
(
|
||||
answersStore?.answersWithPaginate?.pageInfo?.totalElements / rowsPerPage
|
||||
).toFixed(1)
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactPaginate
|
||||
previousLabel={""}
|
||||
nextLabel={""}
|
||||
breakLabel="..."
|
||||
pageCount={count || 1}
|
||||
marginPagesDisplayed={2}
|
||||
pageRangeDisplayed={2}
|
||||
activeClassName="active"
|
||||
forcePage={currentPage !== 0 ? currentPage - 1 : 0}
|
||||
onPageChange={(page) => handlePagination(page)}
|
||||
pageClassName={"page-item"}
|
||||
nextLinkClassName={"page-link"}
|
||||
nextClassName={"page-item next"}
|
||||
previousClassName={"page-item prev"}
|
||||
previousLinkClassName={"page-link"}
|
||||
pageLinkClassName={"page-link"}
|
||||
breakClassName="page-item"
|
||||
breakLinkClassName="page-link"
|
||||
containerClassName={
|
||||
"pagination react-paginate separated-pagination pagination-sm justify-content-end pr-1 mt-1"
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const onAddAnswerButtonPressed = () => {
|
||||
setEditingAnswerData({});
|
||||
setShowAddAnswerModal(true);
|
||||
};
|
||||
|
||||
const onAddAnswerModalButtonPressed = () => {
|
||||
const newAnswerData = {
|
||||
id: editingAnswerData?.id,
|
||||
value: editingAnswerData?.value,
|
||||
answer: editingAnswerData?.answer,
|
||||
};
|
||||
const checkData = JSON.parse(JSON.stringify(newAnswerData), (key, value) =>
|
||||
value === null || value === "" ? undefined : value
|
||||
);
|
||||
|
||||
dispatch(newAnswerData?.id ? updateAnswer(checkData) : addAnswer(checkData))
|
||||
.then(() => {
|
||||
enqueueSnackbar(
|
||||
`${newAnswerData.value}, ${
|
||||
!checkData?.id
|
||||
? t("Warnings.addedSuccessfully")
|
||||
: t("Warnings.updatedSuccessfully")
|
||||
}`,
|
||||
{
|
||||
variant: "success",
|
||||
}
|
||||
);
|
||||
setEditingAnswerData(null);
|
||||
setShowAddAnswerModal(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
enqueueSnackbar(
|
||||
`${newAnswerData.value} ${
|
||||
!newAnswerData?.id
|
||||
? t("Warnings.addedFail")
|
||||
: t("Warnings.updatedFail")
|
||||
}`,
|
||||
{
|
||||
variant: "error",
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
const renderAnswerModal = () => {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={showAddAnswerModal}
|
||||
toggle={() => {
|
||||
setShowAddAnswerModal(!showAddAnswerModal);
|
||||
//setEditingAnswerData(null);
|
||||
}}
|
||||
className="modal-dialog-centered"
|
||||
>
|
||||
<ModalHeader
|
||||
toggle={() => {
|
||||
setShowAddAnswerModal(!showAddAnswerModal);
|
||||
//setEditingAnswerData(null);
|
||||
}}
|
||||
>
|
||||
{editingAnswerData?.id
|
||||
? editingAnswerData?.value
|
||||
: t("Survey.addAnswer")}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" for="survey-value">
|
||||
{t("Survey.value")}:
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="survey-value"
|
||||
placeholder=""
|
||||
defaultValue={editingAnswerData?.value}
|
||||
onChange={(e) => {
|
||||
setEditingAnswerData({
|
||||
...editingAnswerData,
|
||||
value: e.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Label className="form-label" for="survey-answer">
|
||||
{t("Survey.answer")}:
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="survey-answer"
|
||||
placeholder=""
|
||||
defaultValue={editingAnswerData?.answer}
|
||||
onChange={(e) => {
|
||||
setEditingAnswerData({
|
||||
...editingAnswerData,
|
||||
answer: e.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={(e) => {
|
||||
onAddAnswerModalButtonPressed();
|
||||
}}
|
||||
>
|
||||
{editingAnswerData?.id ? t("Cruds.update") : t("Cruds.save")}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const handleDeleteAnswer = async (selectedAnswer) => {
|
||||
const result = await Swal.fire({
|
||||
title: `${selectedAnswer.value} ${t("Warnings.sureForDelete")}`,
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonText: t("Cruds.delete"),
|
||||
cancelButtonText: t("Cruds.cancel"),
|
||||
customClass: {
|
||||
confirmButton: "btn btn-primary",
|
||||
cancelButton: "btn btn-danger ml-1",
|
||||
},
|
||||
buttonsStyling: false,
|
||||
});
|
||||
if (result.value !== null && result.value === true) {
|
||||
dispatch(deleteAnswer(selectedAnswer.id.trim()))
|
||||
.then(() => {
|
||||
enqueueSnackbar(
|
||||
`${selectedAnswer.value} ${t("Warnings.deletedSuccessfully")}`,
|
||||
{
|
||||
variant: "success",
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch((e) => {
|
||||
enqueueSnackbar(
|
||||
`${selectedAnswer.value} ${t("Warnings.deletedFail")}`,
|
||||
{
|
||||
variant: "error",
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditAnswer = (selectedAnswer) => {
|
||||
setShowAddAnswerModal(true);
|
||||
setEditingAnswerData({
|
||||
...selectedAnswer,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "2%" }}>
|
||||
<Card>
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle value="h4">{t("Survey.answers")}</CardTitle>
|
||||
{permissionCheck("answer_create") && (
|
||||
<Button
|
||||
className="ml-2"
|
||||
color="primary"
|
||||
onClick={onAddAnswerButtonPressed}
|
||||
>
|
||||
<Plus size={15} />
|
||||
<span className="align-middle ml-50">
|
||||
{t("Survey.addAnswer")}
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
</CardHeader>
|
||||
<Row className="mx-0 mt-1 mb-50">
|
||||
<Col sm="6" md="2">
|
||||
<div className="d-flex align-items-center">
|
||||
<Label for="sort-select">{t("Show")}</Label>
|
||||
<Input
|
||||
className="ml-1 dataTable-select"
|
||||
type="select"
|
||||
id="sort-select"
|
||||
value={rowsPerPage}
|
||||
onChange={(e) => handlePerPage(e)}
|
||||
>
|
||||
<option value={5}>5</option>
|
||||
<option value={10}>10</option>
|
||||
<option value={25}>25</option>
|
||||
<option value={50}>50</option>
|
||||
<option value={75}>75</option>
|
||||
<option value={100}>100</option>
|
||||
</Input>
|
||||
</div>
|
||||
</Col>
|
||||
<Col
|
||||
className="d-flex align-items-center justify-content-end mt-sm-0 mt-1 ml-md-auto"
|
||||
sm="6"
|
||||
md="3"
|
||||
>
|
||||
<Label className="mr-1" for="search-input">
|
||||
{t("Filter")}
|
||||
</Label>
|
||||
<Input
|
||||
className="dataTable-filter"
|
||||
type="text"
|
||||
bsSize="sm"
|
||||
id="search-input"
|
||||
value={searchValue}
|
||||
onChange={handleFilter}
|
||||
placeholder={t("ByName")}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<DataTable
|
||||
noHeader
|
||||
pagination
|
||||
paginationServer
|
||||
className="react-dataTable"
|
||||
columns={serverSideColumns}
|
||||
sortIcon={<ChevronDown size={10} />}
|
||||
//onSort={onSort}
|
||||
paginationComponent={CustomPagination}
|
||||
data={[...answer]}
|
||||
noDataComponent={
|
||||
<p className="p-2">
|
||||
{t("Survey.answer")}
|
||||
{t("Warnings.notFound")}
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
{renderAnswerModal()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Answer;
|
||||
442
sge-frontend/src/views/Survey/Question.js
Normal file
442
sge-frontend/src/views/Survey/Question.js
Normal file
@@ -0,0 +1,442 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ChevronDown, Edit, MoreVertical, Plus, Trash } from "react-feather";
|
||||
import ReactPaginate from "react-paginate";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useSnackbar } from "notistack";
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Input,
|
||||
Label,
|
||||
Row,
|
||||
Col,
|
||||
Button,
|
||||
Modal,
|
||||
ModalHeader,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
UncontrolledDropdown,
|
||||
DropdownToggle,
|
||||
DropdownMenu,
|
||||
DropdownItem,
|
||||
} from "reactstrap";
|
||||
import { default as SweetAlert } from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import DataTable from "react-data-table-component";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
getQuestionsWithPaginate,
|
||||
addQuestion,
|
||||
updateQuestion,
|
||||
deleteQuestion,
|
||||
} from "../../redux/actions/question";
|
||||
import { permissionCheck } from "../../components/permission-check";
|
||||
|
||||
const Question = () => {
|
||||
const { t } = useTranslation();
|
||||
const initialColumns = [
|
||||
{
|
||||
name: t("Survey.tag"),
|
||||
selector: (row) => row.tag,
|
||||
sortable: true,
|
||||
minWidth: "250px",
|
||||
},
|
||||
{
|
||||
name: t("Survey.question"),
|
||||
selector: (row) => row.question,
|
||||
sortable: true,
|
||||
minWidth: "250px",
|
||||
},
|
||||
{
|
||||
name: t("Actions"),
|
||||
allowOverflow: false,
|
||||
maxWidth: "150px",
|
||||
cell: (row) => {
|
||||
return (
|
||||
<div className="d-flex">
|
||||
<UncontrolledDropdown>
|
||||
<DropdownToggle className="pl-1" tag="span">
|
||||
<MoreVertical size={15} />
|
||||
</DropdownToggle>
|
||||
<DropdownMenu container={"body"} end>
|
||||
{permissionCheck("question_update") && (
|
||||
<DropdownItem
|
||||
tag="a"
|
||||
className="w-100"
|
||||
onClick={() => handleEditQuestion(row)}
|
||||
>
|
||||
<Edit size={15} />
|
||||
<span className="align-middle ml-50">
|
||||
{t("Cruds.edit")}
|
||||
</span>
|
||||
</DropdownItem>
|
||||
)}
|
||||
{permissionCheck("question_delete") && (
|
||||
<DropdownItem
|
||||
tag="a"
|
||||
className="w-100"
|
||||
onClick={() => handleDeleteQuestion(row)}
|
||||
>
|
||||
<Trash size={15} />
|
||||
<span className="align-middle ml-50">
|
||||
{t("Cruds.delete")}
|
||||
</span>
|
||||
</DropdownItem>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
</UncontrolledDropdown>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!(
|
||||
permissionCheck("question_delete") || permissionCheck("question_update")
|
||||
)
|
||||
) {
|
||||
const updatedColumns = initialColumns.filter(
|
||||
(column) => column.name !== ("Aksiyonlar" || "Actions")
|
||||
);
|
||||
setServerSideColumns(updatedColumns);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const Swal = withReactContent(SweetAlert);
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(5);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
|
||||
const [serverSideColumns, setServerSideColumns] = useState(initialColumns);
|
||||
|
||||
const questionsStore = useSelector((state) => state.questions);
|
||||
const [question, setQuestions] = useState([]);
|
||||
const [showAddQuestionModal, setShowAddQuestionModal] = useState(false);
|
||||
const [editingQuestionData, setEditingQuestionData] = useState();
|
||||
useEffect(() => {
|
||||
dispatch(getQuestionsWithPaginate({ currentPage, rowsPerPage }));
|
||||
}, [currentPage, rowsPerPage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (questionsStore?.questionsWithPaginate?.content) {
|
||||
setQuestions(questionsStore?.questionsWithPaginate?.content);
|
||||
}
|
||||
}, [questionsStore.questionsWithPaginate?.content]);
|
||||
|
||||
const handleFilter = (e) => {
|
||||
setSearchValue(e.target.value);
|
||||
|
||||
if (e.target.value !== "") {
|
||||
setQuestions(
|
||||
questionsStore.questionsWithPaginate?.content.filter((question) =>
|
||||
question.tag.toLowerCase().includes(e.target.value.toLowerCase())
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setQuestions(questionsStore.questionsWithPaginate?.content);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePagination = (page) => {
|
||||
setCurrentPage(page.selected + 1);
|
||||
setQuestions(questionsStore.questionsWithPaginate?.content);
|
||||
};
|
||||
|
||||
const handlePerPage = (e) => {
|
||||
setCurrentPage(1);
|
||||
setRowsPerPage(parseInt(e.target.value));
|
||||
setQuestions(questionsStore.questionsWithPaginate?.content);
|
||||
};
|
||||
|
||||
const CustomPagination = () => {
|
||||
const count = Number(
|
||||
(
|
||||
questionsStore?.questionsWithPaginate?.pageInfo?.totalElements /
|
||||
rowsPerPage
|
||||
).toFixed(1)
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactPaginate
|
||||
previousLabel={""}
|
||||
nextLabel={""}
|
||||
breakLabel="..."
|
||||
pageCount={count || 1}
|
||||
marginPagesDisplayed={2}
|
||||
pageRangeDisplayed={2}
|
||||
activeClassName="active"
|
||||
forcePage={currentPage !== 0 ? currentPage - 1 : 0}
|
||||
onPageChange={(page) => handlePagination(page)}
|
||||
pageClassName={"page-item"}
|
||||
nextLinkClassName={"page-link"}
|
||||
nextClassName={"page-item next"}
|
||||
previousClassName={"page-item prev"}
|
||||
previousLinkClassName={"page-link"}
|
||||
pageLinkClassName={"page-link"}
|
||||
breakClassName="page-item"
|
||||
breakLinkClassName="page-link"
|
||||
containerClassName={
|
||||
"pagination react-paginate separated-pagination pagination-sm justify-content-end pr-1 mt-1"
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const onAddQuestionButtonPressed = () => {
|
||||
setEditingQuestionData({});
|
||||
setShowAddQuestionModal(true);
|
||||
};
|
||||
|
||||
const onAddQuestionModalButtonPressed = () => {
|
||||
const newQuestionData = {
|
||||
id: editingQuestionData?.id || null,
|
||||
tag: editingQuestionData?.tag,
|
||||
question: editingQuestionData?.question,
|
||||
};
|
||||
|
||||
const check = JSON.parse(JSON.stringify(newQuestionData), (key, value) =>
|
||||
value === null || value === "" ? undefined : value
|
||||
);
|
||||
|
||||
const deleteQuete = (list) => {
|
||||
return list?.map((a) => a?.value || a.toString());
|
||||
};
|
||||
|
||||
const updateQuestionData = {
|
||||
id: editingQuestionData?.id,
|
||||
tag: editingQuestionData?.tag,
|
||||
question: editingQuestionData?.question,
|
||||
};
|
||||
const checkUpdateData = JSON.parse(
|
||||
JSON.stringify(updateQuestionData),
|
||||
(key, value) => (value === null || value === "" ? undefined : value)
|
||||
);
|
||||
|
||||
dispatch(
|
||||
newQuestionData.id ? updateQuestion(checkUpdateData) : addQuestion(check)
|
||||
)
|
||||
.then(() => {
|
||||
enqueueSnackbar(
|
||||
`${newQuestionData.tag}, ${
|
||||
!newQuestionData.id
|
||||
? t("Warnings.addedSuccessfully")
|
||||
: t("Warnings.updatedSuccessfully")
|
||||
}`,
|
||||
{
|
||||
variant: "success",
|
||||
}
|
||||
);
|
||||
setEditingQuestionData(null);
|
||||
setShowAddQuestionModal(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
enqueueSnackbar(
|
||||
`${newQuestionData.tag} ${
|
||||
!newQuestionData.id
|
||||
? t("Warnings.addedFail")
|
||||
: t("Warnings.updatedFail")
|
||||
}`,
|
||||
{
|
||||
variant: "error",
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
const renderQuestionModal = () => {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={showAddQuestionModal}
|
||||
toggle={() => {
|
||||
setShowAddQuestionModal(!showAddQuestionModal);
|
||||
//setEditingQuestionData(null);
|
||||
}}
|
||||
className="modal-dialog-centered"
|
||||
>
|
||||
<ModalHeader
|
||||
toggle={() => {
|
||||
setShowAddQuestionModal(!showAddQuestionModal);
|
||||
//setEditingQuestionData(null);
|
||||
}}
|
||||
>
|
||||
{editingQuestionData?.id
|
||||
? editingQuestionData?.tag
|
||||
: t("Survey.addQuestion")}
|
||||
|
||||
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" for="survey-tag">
|
||||
{t("Survey.tag")}:
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="survey-tag"
|
||||
placeholder=""
|
||||
defaultValue={editingQuestionData?.tag}
|
||||
onChange={(e) => {
|
||||
setEditingQuestionData({
|
||||
...editingQuestionData,
|
||||
tag: e.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Label className="form-label" for="survey-question">
|
||||
{t("Survey.question")}:
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="survey-question"
|
||||
placeholder=""
|
||||
defaultValue={editingQuestionData?.question}
|
||||
onChange={(e) => {
|
||||
setEditingQuestionData({
|
||||
...editingQuestionData,
|
||||
question: e.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={(e) => {
|
||||
onAddQuestionModalButtonPressed();
|
||||
}}
|
||||
>
|
||||
{editingQuestionData?.id ? t("Cruds.update") : t("Cruds.save")}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const handleDeleteQuestion = async (selectedQuestion) => {
|
||||
const result = await Swal.fire({
|
||||
title: `${selectedQuestion.tag} ${t("Warnings.sureForDelete")}`,
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonText: t("Cruds.delete"),
|
||||
cancelButtonText: t("Cruds.cancel"),
|
||||
customClass: {
|
||||
confirmButton: "btn btn-primary",
|
||||
cancelButton: "btn btn-danger ml-1",
|
||||
},
|
||||
buttonsStyling: false,
|
||||
});
|
||||
if (result.value !== null && result.value === true) {
|
||||
dispatch(deleteQuestion(selectedQuestion.id.trim()))
|
||||
.then(() => {
|
||||
enqueueSnackbar(
|
||||
`${selectedQuestion.tag} ${t("Warnings.deletedSuccessfully")}`,
|
||||
{
|
||||
variant: "success",
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch((e) => {
|
||||
enqueueSnackbar(
|
||||
`${selectedQuestion.tag} ${t("Warnings.deletedFail")}`,
|
||||
{
|
||||
variant: "error",
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditQuestion = (selectedQuestion) => {
|
||||
setShowAddQuestionModal(true);
|
||||
setEditingQuestionData({
|
||||
...selectedQuestion,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "2%" }}>
|
||||
<Card>
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle tag="h4">{t("Survey.questions")}</CardTitle>
|
||||
{permissionCheck("question_create") && (
|
||||
<Button
|
||||
className="ml-2"
|
||||
color="primary"
|
||||
onClick={onAddQuestionButtonPressed}
|
||||
>
|
||||
<Plus size={15} />
|
||||
<span className="align-middle ml-50">
|
||||
{t("Survey.addQuestion")}
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
</CardHeader>
|
||||
<Row className="mx-0 mt-1 mb-50">
|
||||
<Col sm="6" md="2">
|
||||
<div className="d-flex align-items-center">
|
||||
<Label for="sort-select">{t("Show")}</Label>
|
||||
<Input
|
||||
className="ml-1 dataTable-select"
|
||||
type="select"
|
||||
id="sort-select"
|
||||
value={rowsPerPage}
|
||||
onChange={(e) => handlePerPage(e)}
|
||||
>
|
||||
<option value={5}>5</option>
|
||||
<option value={10}>10</option>
|
||||
<option value={25}>25</option>
|
||||
<option value={50}>50</option>
|
||||
<option value={75}>75</option>
|
||||
<option value={100}>100</option>
|
||||
</Input>
|
||||
</div>
|
||||
</Col>
|
||||
<Col
|
||||
className="d-flex align-items-center justify-content-end mt-sm-0 mt-1 ml-md-auto"
|
||||
sm="6"
|
||||
md="3"
|
||||
>
|
||||
<Label className="mr-1" for="search-input">
|
||||
{t("Filter")}
|
||||
</Label>
|
||||
<Input
|
||||
className="dataTable-filter"
|
||||
type="text"
|
||||
bsSize="sm"
|
||||
id="search-input"
|
||||
value={searchValue}
|
||||
onChange={handleFilter}
|
||||
placeholder={t("ByName")}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<DataTable
|
||||
noHeader
|
||||
pagination
|
||||
paginationServer
|
||||
className="react-dataTable"
|
||||
columns={serverSideColumns}
|
||||
sortIcon={<ChevronDown size={10} />}
|
||||
//onSort={onSort}
|
||||
paginationComponent={CustomPagination}
|
||||
data={[...question]}
|
||||
noDataComponent={
|
||||
<p className="p-2">
|
||||
{t("Survey.question")}
|
||||
{t("Warnings.notFound")}
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
{renderQuestionModal()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Question;
|
||||
288
sge-frontend/src/views/Survey/SavedSurvey.js
Normal file
288
sge-frontend/src/views/Survey/SavedSurvey.js
Normal file
@@ -0,0 +1,288 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { ChevronDown } from "react-feather";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { Card, CardHeader, CardTitle } from "reactstrap";
|
||||
import DataTable from "react-data-table-component";
|
||||
import { savedSurveysFromSurveys } from "../../redux/actions/surveys/index";
|
||||
import { Bar } from "react-chartjs-2";
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
ArcElement,
|
||||
Tooltip,
|
||||
Legend,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
BarElement,
|
||||
Title,
|
||||
} from "chart.js";
|
||||
import ChartDataLabels from "chartjs-plugin-datalabels";
|
||||
|
||||
ChartJS.register(
|
||||
ArcElement,
|
||||
Tooltip,
|
||||
Legend,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
BarElement,
|
||||
Title,
|
||||
ChartDataLabels
|
||||
);
|
||||
|
||||
const calculateStatistics = ({ data }) => {
|
||||
const statistics = {};
|
||||
|
||||
data?.forEach((entry) => {
|
||||
const savedSurveys = entry.savedSurveys?.[0];
|
||||
|
||||
if (savedSurveys) {
|
||||
savedSurveys.questionValue.forEach((question, index) => {
|
||||
const answer = savedSurveys.answerValue?.[index];
|
||||
|
||||
if (!statistics[question]) {
|
||||
statistics[question] = {};
|
||||
}
|
||||
|
||||
if (answer !== undefined && answer !== null) {
|
||||
if (!statistics[question][answer]) {
|
||||
statistics[question][answer] = 0;
|
||||
}
|
||||
statistics[question][answer]++;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return statistics;
|
||||
};
|
||||
|
||||
const ChartComponent = ({ question, statistics, index }) => {
|
||||
const answerLabels = Object.keys(statistics[question]);
|
||||
const answerData = answerLabels.map(
|
||||
(answer) => statistics[question][answer] || 0
|
||||
);
|
||||
const colors = ["#8bb05b", "#1b6fd1"];
|
||||
|
||||
const chartData = {
|
||||
labels: answerLabels,
|
||||
datasets: [
|
||||
{
|
||||
data: answerData,
|
||||
backgroundColor: index % 2 === 0 ? colors[0] : colors[1],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const chartOptions = {
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: index + 1 + ") " + question,
|
||||
},
|
||||
datalabels: {
|
||||
color: "white",
|
||||
font: {
|
||||
size: 17,
|
||||
family: "Montserrat",
|
||||
weight: "bold",
|
||||
},
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
stepSize: 1,
|
||||
precision: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Bar data={chartData} options={chartOptions} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SavedSurvey = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const serverSideColumns = [
|
||||
{
|
||||
name: t("Survey.surveyName"),
|
||||
selector: (row) => row.tag,
|
||||
sortable: true,
|
||||
minWidth: "250px",
|
||||
},
|
||||
{
|
||||
name: t("Survey.survey") + t("Description"),
|
||||
selector: (row) => row.description,
|
||||
sortable: true,
|
||||
minWidth: "250px",
|
||||
},
|
||||
{
|
||||
name: "Oluşturulma Zamanı",
|
||||
selector: (row) => row.createdAt,
|
||||
sortable: true,
|
||||
minWidth: "250px",
|
||||
cell: (row) => (
|
||||
<span>
|
||||
{row?.createdAt
|
||||
? new Date(row?.createdAt).toLocaleString("tr-TR")
|
||||
: "-"}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
const serverSideColumnsForSavedSurveys = [
|
||||
{
|
||||
name: "Email",
|
||||
selector: (row) => row.participant_email,
|
||||
sortable: true,
|
||||
minWidth: "250px",
|
||||
},
|
||||
{
|
||||
name: "Anket Kodu",
|
||||
selector: (row) => row.participant_id,
|
||||
sortable: true,
|
||||
minWidth: "250px",
|
||||
},
|
||||
{
|
||||
name: "Başlangıç Zamanı",
|
||||
selector: (row) => row.start_at,
|
||||
sortable: true,
|
||||
minWidth: "250px",
|
||||
cell: (row) => (
|
||||
<span>
|
||||
{row.start_at ? new Date(row.start_at).toLocaleString("tr-TR") : "-"}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Bitiş Zamanı",
|
||||
selector: (row) => row.expire_at,
|
||||
sortable: true,
|
||||
minWidth: "250px",
|
||||
cell: (row) => (
|
||||
<span>
|
||||
{row.expire_at
|
||||
? new Date(row.expire_at).toLocaleString("tr-TR")
|
||||
: "-"}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const savedSurveysFromSurveysStore = useSelector(
|
||||
(state) => state.surveys.savedSurveysFromSurveys
|
||||
);
|
||||
const [savedSurveys, setSavedSurveys] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(savedSurveysFromSurveys());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (savedSurveysFromSurveysStore)
|
||||
setSavedSurveys(savedSurveysFromSurveysStore);
|
||||
}, [savedSurveysFromSurveysStore]);
|
||||
|
||||
const ExpandableTable = ({ data }) => {
|
||||
const graphicsData = calculateStatistics({ data: data.publishedSurveys });
|
||||
return (
|
||||
<div className="ml-5">
|
||||
<div className="d-flex">
|
||||
<li className="m-2">
|
||||
<b>Gönderilen kişi sayısı: </b>
|
||||
{data.publishedSurveys.length}
|
||||
</li>
|
||||
<li className="m-2">
|
||||
<b>Cevaplayan kişi sayısı: </b>
|
||||
{
|
||||
data.publishedSurveys?.filter((b) => b.savedSurveys.length != 0)
|
||||
.length
|
||||
}
|
||||
</li>
|
||||
</div>
|
||||
<div className="row row-cols-auto">
|
||||
{Object.keys(graphicsData).map((question, index) => (
|
||||
<ChartComponent
|
||||
key={index}
|
||||
question={question}
|
||||
statistics={graphicsData}
|
||||
index={index}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<DataTable
|
||||
noHeader
|
||||
expandableRows
|
||||
expandOnRowClicked
|
||||
expandableRowsComponent={ExpandableTable2}
|
||||
className="react-dataTable"
|
||||
columns={serverSideColumnsForSavedSurveys}
|
||||
sortIcon={<ChevronDown size={10} />}
|
||||
data={[...data.publishedSurveys]}
|
||||
noDataComponent={<p className="p-2"></p>}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ExpandableTable2 = ({ data }) => {
|
||||
data = data.savedSurveys[0];
|
||||
return (
|
||||
<div className="expandable-content p-2">
|
||||
{data?.questionValue?.length > 0 &&
|
||||
data.questionValue?.map((question, index) => (
|
||||
<div className="mt-2" key={index}>
|
||||
<p className="font-small-4 mt-2">
|
||||
<span className="font-weight-bold">{index + 1}. Soru: </span>
|
||||
{question}
|
||||
</p>
|
||||
{data.answerValue?.length > index && (
|
||||
<p className="font-small-4 mt-2 ml-1">
|
||||
<span className="font-weight-bold">Cevap: </span>
|
||||
{data.answerValue[index]}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "2%" }}>
|
||||
<Card>
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle tag="h4">Anket Sonuçları</CardTitle>
|
||||
</CardHeader>
|
||||
<DataTable
|
||||
noHeader
|
||||
pagination
|
||||
paginationServer
|
||||
expandableRows
|
||||
expandOnRowClicked
|
||||
expandableRowsComponent={ExpandableTable}
|
||||
className="react-dataTable"
|
||||
columns={serverSideColumns}
|
||||
sortIcon={<ChevronDown size={10} />}
|
||||
data={[...savedSurveys]}
|
||||
noDataComponent={
|
||||
<p className="p-2">
|
||||
{t("Survey.surveys")}
|
||||
{t("Warnings.notFound")}
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SavedSurvey;
|
||||
236
sge-frontend/src/views/Survey/SurveyPage.js
Normal file
236
sge-frontend/src/views/Survey/SurveyPage.js
Normal file
@@ -0,0 +1,236 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getSurvey } from "../../redux/actions/surveys/index";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { publishedSurvey } from "../../redux/actions/surveys/index";
|
||||
import ApplicationService from "../../services/ApplicationService";
|
||||
|
||||
const SurveyPage = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const [isUsed, setIsUsed] = useState(false);
|
||||
//const [selectedChoose, setSelectedChoose] = useState(null);
|
||||
const [editingSurveyData, setEditingSurveyData] = useState({});
|
||||
|
||||
const surveyStore = useSelector((state) => state.surveys.survey);
|
||||
const publishedSurveyStore = useSelector(
|
||||
(state) => state.surveys.publishedSurvey
|
||||
);
|
||||
const currentURL = window.location.href;
|
||||
const currentURL1 = currentURL.split("/");
|
||||
const publishedId = currentURL1[4];
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(publishedSurvey(publishedId));
|
||||
}, [publishedId]);
|
||||
|
||||
useEffect(() => {
|
||||
ApplicationService.http()
|
||||
.post(
|
||||
"/graphql",
|
||||
{
|
||||
query: `
|
||||
mutation {
|
||||
checkSurveyValidity(
|
||||
publishedSurveyId: "${publishedId}",
|
||||
)
|
||||
{
|
||||
isUsed
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer " + localStorage.getItem("accessToken"),
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.data?.errors) {
|
||||
enqueueSnackbar("Doğrulama başarısız." + t("Warnings.notFound"), {
|
||||
variant: "error",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
} else {
|
||||
setIsUsed(response.data?.data?.checkSurveyValidity.isUsed);
|
||||
enqueueSnackbar("Doğrulama başarılı.", {
|
||||
variant: "success",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const surveyId = publishedSurveyStore?.survey?.id;
|
||||
useEffect(() => {
|
||||
if (surveyId) {
|
||||
dispatch(getSurvey(surveyId));
|
||||
}
|
||||
}, [surveyId]);
|
||||
|
||||
useEffect(() => {
|
||||
setEditingSurveyData(surveyStore);
|
||||
}, [surveyStore]);
|
||||
|
||||
const handleSendButtonPressed = () => {
|
||||
const questions = [];
|
||||
const answers = [];
|
||||
|
||||
editingSurveyData.relationSurveyQuestions.forEach((a) => {
|
||||
questions.push(`"${a.question.question}"`);
|
||||
const selectedAnswers = a.question.relationQuestionAnswers
|
||||
.filter((b) => b.isChecked)
|
||||
.map((b) => `"${b.answer.answer}"`)
|
||||
.join(", ");
|
||||
answers.push(`${selectedAnswers}`);
|
||||
});
|
||||
const surveyData = {
|
||||
publishedSurvey: publishedId,
|
||||
answerValue: answers,
|
||||
questionValue: questions,
|
||||
};
|
||||
|
||||
ApplicationService.http()
|
||||
.post(
|
||||
"/graphql",
|
||||
{
|
||||
query: `
|
||||
mutation {
|
||||
addSavedSurvey( input: {
|
||||
publishedSurvey: "${surveyData?.publishedSurvey}",
|
||||
answerValue: [${surveyData?.answerValue}],
|
||||
questionValue: [${surveyData?.questionValue}]}
|
||||
){
|
||||
questionValue
|
||||
answerValue
|
||||
isDeleted
|
||||
isApproved
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer " + localStorage.getItem("accessToken"),
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.data?.errors) {
|
||||
enqueueSnackbar("Anket yanıtları gönderilemedi.", {
|
||||
variant: "error",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
} else {
|
||||
enqueueSnackbar("Anket yanıtları gönderildi.", {
|
||||
variant: "success",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
marginTop: "2%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
minHeight: "100vh",
|
||||
}}
|
||||
>
|
||||
<div className="d-flex flex-column w-75">
|
||||
{isUsed === false && editingSurveyData?.relationSurveyQuestions ? (
|
||||
<>
|
||||
<div
|
||||
className="d-flex justify-content-center"
|
||||
style={{
|
||||
boxShadow: "rgba(2, 138, 74, 0.2) 0px 7px 29px 0px",
|
||||
marginBottom: "10px",
|
||||
}}
|
||||
>
|
||||
<h2>{editingSurveyData?.tag}</h2>
|
||||
</div>
|
||||
<div style={{ minHeight: "100%" }}>
|
||||
{editingSurveyData.relationSurveyQuestions.length > 0 &&
|
||||
editingSurveyData.relationSurveyQuestions
|
||||
.sort((a, b) => a.orderNo - b.orderNo)
|
||||
.map((dat, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "rgba(2, 138, 74, 0.2) 0px 7px 29px 0px",
|
||||
marginBottom: "10px",
|
||||
padding: "5px",
|
||||
}}
|
||||
>
|
||||
<div className="d-flex flex-row">
|
||||
<p className="h4">{dat.orderNo}</p>
|
||||
<p className="h4">{`) ${dat.question.question}`}</p>
|
||||
</div>
|
||||
<div>
|
||||
{dat.question.relationQuestionAnswers.map(
|
||||
(answer, answerIndex) => (
|
||||
<div
|
||||
style={{
|
||||
marginBottom: "10px",
|
||||
padding: "10px",
|
||||
}}
|
||||
className="d-flex form-check-inline"
|
||||
key={answer.id}
|
||||
>
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="radio"
|
||||
name={`question-${dat.orderNo}`}
|
||||
id={`option-${answer.id}`}
|
||||
checked={answer.isChecked}
|
||||
onChange={() => {
|
||||
const updatedQuestions = [
|
||||
...editingSurveyData.relationSurveyQuestions,
|
||||
];
|
||||
const selectedQuestion =
|
||||
updatedQuestions[index];
|
||||
selectedQuestion.question.relationQuestionAnswers =
|
||||
selectedQuestion.question.relationQuestionAnswers.map(
|
||||
(a, idx) => ({
|
||||
...a,
|
||||
isChecked: idx === answerIndex,
|
||||
})
|
||||
);
|
||||
setEditingSurveyData({
|
||||
...editingSurveyData,
|
||||
relationSurveyQuestions: updatedQuestions,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<label
|
||||
className="form-check-label ml-2 h6"
|
||||
htmlFor={`option-${answer.id}`}
|
||||
>
|
||||
{answer.answer.answer}
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<button onClick={() => handleSendButtonPressed()}>Gönder</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p>No survey questions found.</p>
|
||||
)}
|
||||
{isUsed && (
|
||||
<p>Anket görüntülenmiyor, çünkü anketin zamanı dolmuştur.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SurveyPage;
|
||||
929
sge-frontend/src/views/Survey/Surveys.js
Normal file
929
sge-frontend/src/views/Survey/Surveys.js
Normal file
@@ -0,0 +1,929 @@
|
||||
/* eslint-disable no-lone-blocks */
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ChevronDown, Edit, MoreVertical, Plus, Trash } from "react-feather";
|
||||
import ReactPaginate from "react-paginate";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useSnackbar } from "notistack";
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Input,
|
||||
Label,
|
||||
Row,
|
||||
Col,
|
||||
Button,
|
||||
Modal,
|
||||
ModalHeader,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
UncontrolledDropdown,
|
||||
DropdownToggle,
|
||||
DropdownMenu,
|
||||
DropdownItem,
|
||||
} from "reactstrap";
|
||||
import { default as SweetAlert } from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import Select from "react-select";
|
||||
import DataTable from "react-data-table-component";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
getSurveysWithPaginate,
|
||||
addSurvey,
|
||||
addQuestionToSurvey,
|
||||
deleteQuestionFromSurvey,
|
||||
deleteSurvey,
|
||||
} from "../../redux/actions/surveys";
|
||||
import { getQuestions } from "../../redux/actions/question";
|
||||
import { getAnswers } from "../../redux/actions/answer";
|
||||
import ApplicationService from "../../services/ApplicationService";
|
||||
import DatePicker, { registerLocale } from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import tr from "date-fns/locale/tr";
|
||||
import { permissionCheck } from "../../components/permission-check";
|
||||
registerLocale("tr", tr);
|
||||
|
||||
const Surveys = () => {
|
||||
const { t } = useTranslation();
|
||||
const initialColumns = [
|
||||
{
|
||||
name: t("Survey.surveyName"),
|
||||
selector: (row) => row.tag,
|
||||
sortable: true,
|
||||
minWidth: "250px",
|
||||
},
|
||||
{
|
||||
name: t("Survey.survey") + t("Description"),
|
||||
selector: (row) => row.description,
|
||||
sortable: true,
|
||||
minWidth: "250px",
|
||||
},
|
||||
{
|
||||
name: t("CreatedTime"),
|
||||
selector: (row) => row.createdAt,
|
||||
sortable: true,
|
||||
minWidth: "250px",
|
||||
cell: (row) => (
|
||||
<span>{new Date(row.createdAt).toLocaleString("tr-TR")}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: t("Actions"),
|
||||
allowOverflow: false,
|
||||
maxWidth: "150px",
|
||||
cell: (row) => {
|
||||
return (
|
||||
<div className="d-flex">
|
||||
<UncontrolledDropdown>
|
||||
<DropdownToggle className="pl-1" tag="span">
|
||||
<MoreVertical size={15} />
|
||||
</DropdownToggle>
|
||||
<DropdownMenu container={"body"} end>
|
||||
{permissionCheck("published_survey_create") && (
|
||||
<DropdownItem
|
||||
tag="a"
|
||||
className="w-100"
|
||||
onClick={() => handleSendSurvey(row)}
|
||||
>
|
||||
<Edit size={15} />
|
||||
<span className="align-middle ml-50">{"Gönder"}</span>
|
||||
</DropdownItem>
|
||||
)}
|
||||
{permissionCheck("survey_update") && (
|
||||
<>
|
||||
{" "}
|
||||
<DropdownItem
|
||||
tag="a"
|
||||
className="w-100"
|
||||
onClick={() => handleEditSurvey(row)}
|
||||
>
|
||||
<Edit size={15} />
|
||||
<span className="align-middle ml-50">{"Soru Ekle"}</span>
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
tag="a"
|
||||
className="w-100"
|
||||
onClick={() => handleDeleteQuestionFromSurvey(row)}
|
||||
>
|
||||
<Trash size={15} />
|
||||
<span className="align-middle ml-50">{"Soru Sil"}</span>
|
||||
</DropdownItem>
|
||||
</>
|
||||
)}
|
||||
{permissionCheck("survey_delete") && (
|
||||
<DropdownItem
|
||||
tag="a"
|
||||
className="w-100"
|
||||
onClick={() => handleDeleteSurvey(row)}
|
||||
>
|
||||
<Trash size={15} />
|
||||
<span className="align-middle ml-50">
|
||||
{t("Cruds.delete")}
|
||||
</span>
|
||||
</DropdownItem>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
</UncontrolledDropdown>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!(
|
||||
permissionCheck("published_survey_create") ||
|
||||
permissionCheck("survey_update") ||
|
||||
permissionCheck("survey_delete")
|
||||
)
|
||||
) {
|
||||
const updatedColumns = initialColumns.filter(
|
||||
(column) => column.name !== ("Aksiyonlar" || "Actions")
|
||||
);
|
||||
setServerSideColumns(updatedColumns);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const Swal = withReactContent(SweetAlert);
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(5);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
|
||||
const [serverSideColumns, setServerSideColumns] = useState(initialColumns);
|
||||
|
||||
const surveysStore = useSelector((state) => state.surveys);
|
||||
const [survey, setSurveys] = useState([]);
|
||||
const questionStore = useSelector((state) => state.questions);
|
||||
const [questionsOptions, setQuestionsOptions] = useState([]);
|
||||
const answerStore = useSelector((state) => state.answers);
|
||||
const [answersOptions, setAnswersOptions] = useState([]);
|
||||
const [showAddSurveyModal, setShowAddSurveyModal] = useState(false);
|
||||
const [showSurveySendModal, setShowSurveySendModal] = useState(false);
|
||||
const [editingSurveyData, setEditingSurveyData] = useState();
|
||||
const [inputList, setInputList] = useState([{ question: "", answers: "" }]);
|
||||
const [sendList, setSendList] = useState({
|
||||
startTime: "",
|
||||
finishTime: "",
|
||||
email: "",
|
||||
tc: "",
|
||||
surveyId: "",
|
||||
});
|
||||
const [deleteQuestion, setDeleteQuestion] = useState(false);
|
||||
const [deleteQuestionId, setDeleteQuestionId] = useState("");
|
||||
const [error, setError] = useState({
|
||||
email: "",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getQuestions());
|
||||
dispatch(getAnswers());
|
||||
dispatch(getSurveysWithPaginate({ currentPage, rowsPerPage }));
|
||||
}, [currentPage, rowsPerPage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (surveysStore?.surveysWithPaginate?.content) {
|
||||
setSurveys(surveysStore?.surveysWithPaginate?.content);
|
||||
}
|
||||
}, [surveysStore.surveysWithPaginate?.content]);
|
||||
|
||||
useEffect(() => {
|
||||
setQuestionsOptions(
|
||||
questionStore?.questions?.map((questions) => {
|
||||
return {
|
||||
value: questions?.id,
|
||||
label: questions?.question,
|
||||
};
|
||||
})
|
||||
);
|
||||
}, [questionStore]);
|
||||
|
||||
useEffect(() => {
|
||||
setAnswersOptions(
|
||||
answerStore?.answers?.map((answers) => {
|
||||
return {
|
||||
value: answers?.id,
|
||||
label: answers?.answer,
|
||||
};
|
||||
})
|
||||
);
|
||||
}, [answerStore]);
|
||||
|
||||
const handleFilter = (e) => {
|
||||
setSearchValue(e.target.value);
|
||||
|
||||
if (e.target.value !== "") {
|
||||
setSurveys(
|
||||
surveysStore.surveysWithPaginate?.content.filter((survey) =>
|
||||
survey.tag.toLowerCase().includes(e.target.value.toLowerCase())
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setSurveys(surveysStore.surveysWithPaginate?.content);
|
||||
}
|
||||
};
|
||||
|
||||
const ExpandableTable = ({ data }) => {
|
||||
data.relationSurveyQuestions.sort((a, b) => a.orderNo - b.orderNo);
|
||||
return (
|
||||
<div className="expandable-content p-2">
|
||||
{data?.relationSurveyQuestions?.length > 0 &&
|
||||
data.relationSurveyQuestions?.map((dat, index) => (
|
||||
<div className="mt-2" key={index}>
|
||||
<p className="font-small-4 mt-2">
|
||||
<span className="font-weight-bold">
|
||||
{dat.orderNo}. {t("Survey.question")}:
|
||||
</span>
|
||||
{dat.question.question}
|
||||
</p>
|
||||
<span className="font-weight-bold ml-1">Seçenekler:</span>
|
||||
{dat.question.relationQuestionAnswers.map((answer, index) => (
|
||||
<li className="font-small-3 mt-2 ml-1" key={index}>
|
||||
{answer.answer.answer}
|
||||
</li>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const handlePagination = (page) => {
|
||||
setCurrentPage(page.selected + 1);
|
||||
setSurveys(surveysStore.surveysWithPaginate?.content);
|
||||
};
|
||||
|
||||
const handlePerPage = (e) => {
|
||||
setCurrentPage(1);
|
||||
setRowsPerPage(parseInt(e.target.value));
|
||||
setSurveys(surveysStore.surveysWithPaginate?.content);
|
||||
};
|
||||
|
||||
const CustomPagination = () => {
|
||||
const count = Number(
|
||||
(
|
||||
surveysStore?.surveysWithPaginate?.pageInfo?.totalElements / rowsPerPage
|
||||
).toFixed(1)
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactPaginate
|
||||
previousLabel={""}
|
||||
nextLabel={""}
|
||||
breakLabel="..."
|
||||
pageCount={count || 1}
|
||||
marginPagesDisplayed={2}
|
||||
pageRangeDisplayed={2}
|
||||
activeClassName="active"
|
||||
forcePage={currentPage !== 0 ? currentPage - 1 : 0}
|
||||
onPageChange={(page) => handlePagination(page)}
|
||||
pageClassName={"page-item"}
|
||||
nextLinkClassName={"page-link"}
|
||||
nextClassName={"page-item next"}
|
||||
previousClassName={"page-item prev"}
|
||||
previousLinkClassName={"page-link"}
|
||||
pageLinkClassName={"page-link"}
|
||||
breakClassName="page-item"
|
||||
breakLinkClassName="page-link"
|
||||
containerClassName={
|
||||
"pagination react-paginate separated-pagination pagination-sm justify-content-end pr-1 mt-1"
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const onAddSurveyButtonPressed = () => {
|
||||
setEditingSurveyData({});
|
||||
setInputList([{ question: "", answers: "" }]);
|
||||
setShowAddSurveyModal(true);
|
||||
};
|
||||
|
||||
const onAddSurveyModalButtonPressed = () => {
|
||||
function convertSurveyQuestionInputs(surveyQuestionInputs) {
|
||||
return surveyQuestionInputs.map((input) => {
|
||||
return {
|
||||
question: input.question.value,
|
||||
answers: input.answers.map((answer) => answer.value),
|
||||
};
|
||||
});
|
||||
}
|
||||
const newSurveyData = {
|
||||
id: editingSurveyData?.id || null,
|
||||
tag: editingSurveyData?.tag,
|
||||
description: editingSurveyData?.description,
|
||||
surveyQuestionInputs: convertSurveyQuestionInputs(inputList),
|
||||
};
|
||||
|
||||
const check = JSON.parse(JSON.stringify(newSurveyData), (key, value) =>
|
||||
value === null || value === "" ? undefined : value
|
||||
);
|
||||
const addQuestionToSurveyData = {
|
||||
id: editingSurveyData?.id,
|
||||
surveyQuestionInputs: convertSurveyQuestionInputs(inputList),
|
||||
};
|
||||
dispatch(
|
||||
newSurveyData?.id
|
||||
? addQuestionToSurvey(addQuestionToSurveyData)
|
||||
: addSurvey(check)
|
||||
)
|
||||
.then(() => {
|
||||
enqueueSnackbar(
|
||||
`${newSurveyData.tag}, ${
|
||||
!newSurveyData?.id
|
||||
? t("Warnings.addedSuccessfully")
|
||||
: t("Warnings.updatedSuccessfully")
|
||||
}`,
|
||||
{
|
||||
variant: "success",
|
||||
}
|
||||
);
|
||||
setEditingSurveyData(null);
|
||||
setShowAddSurveyModal(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
enqueueSnackbar(
|
||||
`${newSurveyData.tag} ${
|
||||
!newSurveyData?.id
|
||||
? t("Warnings.addedFail")
|
||||
: t("Warnings.updatedFail")
|
||||
}`,
|
||||
{
|
||||
variant: "error",
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const handleInputChange = (e, index, name) => {
|
||||
const value = e;
|
||||
const list = [...inputList];
|
||||
if (name === "question") {
|
||||
list[index][name] = value;
|
||||
} else if (name === "answers") {
|
||||
list[index][name] = value;
|
||||
}
|
||||
setInputList(list);
|
||||
};
|
||||
|
||||
const handleInputChange2 = (e, name) => {
|
||||
const value = e?.toISOString();
|
||||
let list = sendList;
|
||||
if (name === "startTime") {
|
||||
list[name] = value;
|
||||
} else if (name === "finishTime") {
|
||||
list[name] = value;
|
||||
}
|
||||
setSendList({ ...list });
|
||||
};
|
||||
|
||||
const handleRemoveClick = (index) => {
|
||||
if (deleteQuestion) setDeleteQuestionId(inputList[index].relationId);
|
||||
const list = [...inputList];
|
||||
list.splice(index, 1);
|
||||
setInputList(list);
|
||||
};
|
||||
|
||||
const handleAddClick = () => {
|
||||
setInputList([...inputList, { question: "", answers: "" }]);
|
||||
};
|
||||
const renderSurveyModal = () => {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={showAddSurveyModal}
|
||||
toggle={() => {
|
||||
setShowAddSurveyModal(!showAddSurveyModal);
|
||||
}}
|
||||
className="modal-dialog-centered"
|
||||
>
|
||||
<ModalHeader
|
||||
toggle={() => {
|
||||
setShowAddSurveyModal(!showAddSurveyModal);
|
||||
}}
|
||||
>
|
||||
{editingSurveyData?.id
|
||||
? editingSurveyData?.tag
|
||||
: t("Survey.addSurvey")}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" for="survey-surveyName">
|
||||
{t("Survey.surveyName")}:
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="survey-tag"
|
||||
placeholder=""
|
||||
defaultValue={editingSurveyData?.tag}
|
||||
onChange={(e) => {
|
||||
setEditingSurveyData({
|
||||
...editingSurveyData,
|
||||
tag: e.target.value,
|
||||
});
|
||||
}}
|
||||
disabled={!!editingSurveyData?.id}
|
||||
/>
|
||||
<Label className="form-label mt-1" for="survey-description">
|
||||
{t("Survey.survey") + t(t("Description"))}:
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="survey-description"
|
||||
placeholder=""
|
||||
defaultValue={editingSurveyData?.description}
|
||||
onChange={(e) => {
|
||||
setEditingSurveyData({
|
||||
...editingSurveyData,
|
||||
description: e.target.value,
|
||||
});
|
||||
}}
|
||||
disabled={!!editingSurveyData?.id}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
{inputList.map((x, i) => {
|
||||
{
|
||||
let question = "question";
|
||||
let answers = "answers";
|
||||
|
||||
return (
|
||||
<div key={i}>
|
||||
<Label className="form-label" for="question-name">
|
||||
{i + 1}. {t("Survey.question")}:
|
||||
</Label>
|
||||
{
|
||||
<Select
|
||||
id="question-name"
|
||||
closeMenuOnSelect={true}
|
||||
menuPlacement="auto"
|
||||
placeholder=""
|
||||
className="react-select"
|
||||
value={x.question || ""}
|
||||
options={questionsOptions}
|
||||
onChange={(e) => handleInputChange(e, i, question)}
|
||||
/>
|
||||
}
|
||||
<Label className="form-label mt-1" for="answer-name">
|
||||
{"Seçenek(ler)"}:
|
||||
</Label>
|
||||
{
|
||||
<Select
|
||||
id="answer-name"
|
||||
closeMenuOnSelect={false}
|
||||
menuPlacement="auto"
|
||||
placeholder=""
|
||||
isMulti
|
||||
className="react-select"
|
||||
value={x.answers || []}
|
||||
options={answersOptions}
|
||||
onChange={(e) => handleInputChange(e, i, answers)}
|
||||
/>
|
||||
}
|
||||
<div className="d-flex justify-content-end">
|
||||
{/* tek soru silinebildiği için düzenle */}
|
||||
{inputList.length !== 1 && !deleteQuestionId && (
|
||||
<button
|
||||
style={{
|
||||
backgroundColor: "#028a4a",
|
||||
color: "white",
|
||||
borderColor: "grey",
|
||||
}}
|
||||
onClick={() => handleRemoveClick(i)}
|
||||
>
|
||||
<Trash size={15} />
|
||||
</button>
|
||||
)}
|
||||
{inputList.length - 1 === i && (
|
||||
<button
|
||||
style={{
|
||||
backgroundColor: "#028a4a",
|
||||
color: "white",
|
||||
borderColor: "grey",
|
||||
}}
|
||||
onClick={handleAddClick}
|
||||
>
|
||||
<Plus size={13} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={(e) => {
|
||||
deleteQuestion
|
||||
? onDeleteQuestionButtonPressed()
|
||||
: onAddSurveyModalButtonPressed();
|
||||
}}
|
||||
>
|
||||
{editingSurveyData?.id ? t("Cruds.update") : t("Cruds.save")}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const renderSendSurveyModal = () => {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={showSurveySendModal}
|
||||
toggle={() => {
|
||||
setShowSurveySendModal(!showSurveySendModal);
|
||||
}}
|
||||
className="modal-dialog-centered"
|
||||
>
|
||||
<ModalHeader
|
||||
toggle={() => {
|
||||
setShowSurveySendModal(!showSurveySendModal);
|
||||
}}
|
||||
>
|
||||
{editingSurveyData?.id
|
||||
? editingSurveyData?.tag
|
||||
: t("Survey.addSurvey")}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" for="survey-sendEmail">
|
||||
{t("Email")}:
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="survey-email"
|
||||
placeholder=""
|
||||
value={sendList?.email}
|
||||
onChange={(e) =>
|
||||
setSendList({
|
||||
...sendList,
|
||||
email: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Label className="form-label mt-1" for="survey-tc">
|
||||
{"Anket Kodu"}:
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="survey-tc"
|
||||
placeholder=""
|
||||
value={sendList?.tc}
|
||||
onChange={(e) =>
|
||||
setSendList({
|
||||
...sendList,
|
||||
tc: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<Col className="p-0 m-0" style={{ marginBottom: 5 }}>
|
||||
<div className="input-group2">
|
||||
<div className="d-flex">
|
||||
<DatePicker
|
||||
styles={{ width: 700 }}
|
||||
name="startTime"
|
||||
placeholderText={"Anket Başlangıç Zamanı"}
|
||||
selected={
|
||||
sendList?.startTime != ""
|
||||
? new Date(sendList?.startTime)
|
||||
: null
|
||||
}
|
||||
onChange={(e) => handleInputChange2(e, "startTime")}
|
||||
timeInputLabel="Saat:"
|
||||
dateFormat="yyyy-MM-dd H:mm"
|
||||
showTimeInput
|
||||
locale="tr"
|
||||
/>
|
||||
<DatePicker
|
||||
name="finishTime"
|
||||
placeholderText={"Anket Bitiş Zamanı"}
|
||||
selected={
|
||||
sendList?.finishTime != ""
|
||||
? new Date(sendList?.finishTime)
|
||||
: null
|
||||
}
|
||||
onChange={(e) => handleInputChange2(e, "finishTime")}
|
||||
timeInputLabel="Saat:"
|
||||
dateFormat="yyyy-MM-dd H:mm"
|
||||
showTimeInput
|
||||
locale="tr"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
onSendSurveyModalButtonPressed();
|
||||
}}
|
||||
>
|
||||
Gönder
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const handleSendSurvey = async (selectedSurvey) => {
|
||||
setShowSurveySendModal(true);
|
||||
setSendList({
|
||||
...sendList,
|
||||
surveyId: selectedSurvey.id,
|
||||
});
|
||||
};
|
||||
const isEmail = (email) => /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]/i.test(email);
|
||||
|
||||
const onSendSurveyModalButtonPressed = async () => {
|
||||
if (!isEmail(sendList?.email) || sendList?.email === "") {
|
||||
setError({
|
||||
email: t("PasswordLabel.enterEmail"),
|
||||
});
|
||||
} else {
|
||||
let publishedSurveyId;
|
||||
await ApplicationService.http()
|
||||
.post(
|
||||
"/graphql",
|
||||
{
|
||||
query: `
|
||||
mutation {
|
||||
createPublishedSurvey(
|
||||
input:{
|
||||
participant_email: "${sendList?.email}",
|
||||
participant_id: "${sendList?.tc}",
|
||||
start_at: "${sendList?.startTime}",
|
||||
expire_at: "${sendList?.finishTime}",
|
||||
survey: "${sendList?.surveyId}"
|
||||
}
|
||||
) {
|
||||
id
|
||||
q_link
|
||||
participant_email
|
||||
participant_id
|
||||
isDefault
|
||||
isDeleted
|
||||
isUsed
|
||||
created_at
|
||||
start_at
|
||||
expire_at
|
||||
survey{
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer " + localStorage.getItem("accessToken"),
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
publishedSurveyId = response.data?.data?.createPublishedSurvey?.id;
|
||||
|
||||
if (response.data?.errors) {
|
||||
enqueueSnackbar("Email " + t("Warnings.notFound"), {
|
||||
variant: "error",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
} else {
|
||||
enqueueSnackbar("işlem başarılı", {
|
||||
variant: "success",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
}
|
||||
setShowSurveySendModal(false);
|
||||
});
|
||||
await ApplicationService.http()
|
||||
.post(
|
||||
"/graphql",
|
||||
{
|
||||
query: `
|
||||
mutation {
|
||||
sendLink(
|
||||
toEmail: "${sendList?.email}",
|
||||
participant_id: "${sendList?.tc}",
|
||||
publishedSurveyId: "${publishedSurveyId}",
|
||||
participant_email: "${sendList?.email}"
|
||||
)
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer " + localStorage.getItem("accessToken"),
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.data?.errors) {
|
||||
enqueueSnackbar("Anket gönderilemedi." + t("Warnings.notFound"), {
|
||||
variant: "error",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
} else {
|
||||
enqueueSnackbar("Anket gönderildi.", {
|
||||
variant: "success",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteQuestionFromSurvey = async (selectedSurvey) => {
|
||||
setDeleteQuestion(true);
|
||||
const a = selectedSurvey.relationSurveyQuestions.map((item) => {
|
||||
return {
|
||||
relationId: item.id,
|
||||
question: { value: item.question.id, label: item.question.question },
|
||||
answers: item.question.relationQuestionAnswers.map((a) => {
|
||||
return {
|
||||
value: a.answer.id,
|
||||
label: a.answer.answer,
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
setShowAddSurveyModal(true);
|
||||
setInputList(a);
|
||||
setEditingSurveyData({
|
||||
...selectedSurvey,
|
||||
});
|
||||
};
|
||||
|
||||
const onDeleteQuestionButtonPressed = () => {
|
||||
let surveyId = editingSurveyData?.id;
|
||||
|
||||
dispatch(deleteQuestionFromSurvey({ surveyId, deleteQuestionId }))
|
||||
.then(() => {
|
||||
setDeleteQuestion(false);
|
||||
setEditingSurveyData(null);
|
||||
setShowAddSurveyModal(false);
|
||||
enqueueSnackbar(` ${t("Warnings.deletedSuccessfully")}`, {
|
||||
variant: "success",
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
enqueueSnackbar(` ${t("Warnings.deletedFail")}`, {
|
||||
variant: "error",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteSurvey = async (selectedSurvey) => {
|
||||
const result = await Swal.fire({
|
||||
title: `${selectedSurvey.tag} ${t("Warnings.sureForDelete")}`,
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonText: t("Cruds.delete"),
|
||||
cancelButtonText: t("Cruds.cancel"),
|
||||
customClass: {
|
||||
confirmButton: "btn btn-primary",
|
||||
cancelButton: "btn btn-danger ml-1",
|
||||
},
|
||||
buttonsStyling: false,
|
||||
});
|
||||
if (result.value !== null && result.value === true) {
|
||||
dispatch(deleteSurvey(selectedSurvey.id.trim()))
|
||||
.then(() => {
|
||||
enqueueSnackbar(
|
||||
`${selectedSurvey.tag} ${t("Warnings.deletedSuccessfully")}`,
|
||||
{
|
||||
variant: "success",
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch((e) => {
|
||||
enqueueSnackbar(
|
||||
`${selectedSurvey.tag} ${t("Warnings.deletedFail")}`,
|
||||
{
|
||||
variant: "error",
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditSurvey = (selectedSurvey) => {
|
||||
setShowAddSurveyModal(true);
|
||||
setEditingSurveyData({
|
||||
...selectedSurvey,
|
||||
});
|
||||
};
|
||||
|
||||
// const handleEdit2Survey = (selectedSurvey) => {
|
||||
// const a = selectedSurvey.relationSurveyQuestions.map((item) => {
|
||||
// return {
|
||||
// question: { value: item.question.id, label: item.question.question },
|
||||
// answers: item.question.relationQuestionAnswers.map((a) => {
|
||||
// return {
|
||||
// value: a.answer.id,
|
||||
// label: a.answer.answer,
|
||||
// };
|
||||
// }),
|
||||
// };
|
||||
// });
|
||||
|
||||
// setShowAddSurveyModal(true);
|
||||
// setInputList(a);
|
||||
// setEditingSurveyData({
|
||||
// ...selectedSurvey,
|
||||
// });
|
||||
// };
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "2%" }}>
|
||||
<Card>
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle tag="h4">{t("Survey.surveys")}</CardTitle>
|
||||
{permissionCheck("survey_add") && (
|
||||
<Button
|
||||
className="ml-2"
|
||||
color="primary"
|
||||
onClick={onAddSurveyButtonPressed}
|
||||
>
|
||||
<Plus size={15} />
|
||||
<span className="align-middle ml-50">
|
||||
{t("Survey.addSurvey")}
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
</CardHeader>
|
||||
<Row className="mx-0 mt-1 mb-50">
|
||||
<Col sm="6" md="2">
|
||||
<div className="d-flex align-items-center">
|
||||
<Label for="sort-select">{t("Show")}</Label>
|
||||
<Input
|
||||
className="ml-1 dataTable-select"
|
||||
type="select"
|
||||
id="sort-select"
|
||||
value={rowsPerPage}
|
||||
onChange={(e) => handlePerPage(e)}
|
||||
>
|
||||
<option value={5}>5</option>
|
||||
<option value={10}>10</option>
|
||||
<option value={25}>25</option>
|
||||
<option value={50}>50</option>
|
||||
<option value={75}>75</option>
|
||||
<option value={100}>100</option>
|
||||
</Input>
|
||||
</div>
|
||||
</Col>
|
||||
<Col
|
||||
className="d-flex align-items-center justify-content-end mt-sm-0 mt-1 ml-md-auto"
|
||||
sm="6"
|
||||
md="3"
|
||||
>
|
||||
<Label className="mr-1" for="search-input">
|
||||
{t("Filter")}
|
||||
</Label>
|
||||
<Input
|
||||
className="dataTable-filter"
|
||||
type="text"
|
||||
bsSize="sm"
|
||||
id="search-input"
|
||||
value={searchValue}
|
||||
onChange={handleFilter}
|
||||
placeholder={t("ByName")}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<DataTable
|
||||
noHeader
|
||||
pagination
|
||||
paginationServer
|
||||
expandableRows
|
||||
expandOnRowClicked
|
||||
expandableRowsComponent={ExpandableTable}
|
||||
className="react-dataTable"
|
||||
columns={serverSideColumns}
|
||||
sortIcon={<ChevronDown size={10} />}
|
||||
//onSort={onSort}
|
||||
paginationComponent={CustomPagination}
|
||||
data={[...survey]}
|
||||
noDataComponent={
|
||||
<p className="p-2">
|
||||
{t("Survey.surveys")}
|
||||
{t("Warnings.notFound")}
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
{renderSurveyModal()}
|
||||
{renderSendSurveyModal()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Surveys;
|
||||
190
sge-frontend/src/views/SystemActivity.js
Normal file
190
sge-frontend/src/views/SystemActivity.js
Normal file
@@ -0,0 +1,190 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ChevronDown } from "react-feather";
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Input,
|
||||
Label,
|
||||
Row,
|
||||
Col,
|
||||
} from "reactstrap";
|
||||
import DataTable from "react-data-table-component";
|
||||
import ReactPaginate from "react-paginate";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { getSystemHistory } from "../redux/actions/systemHistory";
|
||||
|
||||
const SystemActivity = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const serverSideColumns = [
|
||||
{
|
||||
name: t("Activities.type"),
|
||||
selector: (row) => row.logType,
|
||||
sortable: true,
|
||||
maxWidth: "500px",
|
||||
},
|
||||
{
|
||||
name: t("Activities.message"),
|
||||
selector: (row) => row.logMessage,
|
||||
sortable: true,
|
||||
maxWidth: "500px",
|
||||
},
|
||||
{
|
||||
name: t("Time"),
|
||||
selector: (row) => row.createdDateTime,
|
||||
sortable: false,
|
||||
minWidth: "350px",
|
||||
cell: (row) => (
|
||||
<span>
|
||||
{row.createdDateTime
|
||||
? new Date(row.createdDateTime).toLocaleString("tr-TR")
|
||||
: "-"}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(5);
|
||||
|
||||
const systemHistoriesStore = useSelector((state) => state.systemHistory);
|
||||
const [systemHistories, setSystemHistories] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getSystemHistory());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (systemHistoriesStore.systemHistories) {
|
||||
if (systemHistoriesStore.total <= currentPage * rowsPerPage) {
|
||||
setCurrentPage(1);
|
||||
setSystemHistories(
|
||||
systemHistoriesStore.systemHistories?.slice(0, rowsPerPage)
|
||||
);
|
||||
} else {
|
||||
setSystemHistories(
|
||||
systemHistoriesStore.systemHistories?.slice(
|
||||
currentPage * rowsPerPage - rowsPerPage,
|
||||
currentPage * rowsPerPage
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [systemHistoriesStore.total, systemHistoriesStore]);
|
||||
|
||||
const handlePagination = (page) => {
|
||||
setCurrentPage(page.selected + 1);
|
||||
setSystemHistories(
|
||||
systemHistoriesStore.systemHistories.slice(
|
||||
(page.selected + 1) * rowsPerPage - rowsPerPage,
|
||||
(page.selected + 1) * rowsPerPage
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const handlePerPage = (e) => {
|
||||
setCurrentPage(1);
|
||||
setRowsPerPage(parseInt(e.target.value));
|
||||
setSystemHistories(
|
||||
systemHistoriesStore.systemHistories.slice(
|
||||
currentPage * parseInt(e.target.value) - parseInt(e.target.value),
|
||||
currentPage * parseInt(e.target.value)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const CustomPagination = () => {
|
||||
const count = Number(
|
||||
(systemHistoriesStore?.systemHistories?.length / rowsPerPage).toFixed(1)
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactPaginate
|
||||
previousLabel={""}
|
||||
nextLabel={""}
|
||||
breakLabel="..."
|
||||
pageCount={count || 1}
|
||||
marginPagesDisplayed={2}
|
||||
pageRangeDisplayed={2}
|
||||
activeClassName="active"
|
||||
forcePage={currentPage !== 0 ? currentPage - 1 : 0}
|
||||
onPageChange={(page) => handlePagination(page)}
|
||||
pageClassName={"page-item"}
|
||||
nextLinkClassName={"page-link"}
|
||||
nextClassName={"page-item next"}
|
||||
previousClassName={"page-item prev"}
|
||||
previousLinkClassName={"page-link"}
|
||||
pageLinkClassName={"page-link"}
|
||||
breakClassName="page-item"
|
||||
breakLinkClassName="page-link"
|
||||
containerClassName={
|
||||
"pagination react-paginate separated-pagination pagination-sm justify-content-end pr-1 mt-1"
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ExpandableTable = ({ data }) => {
|
||||
return (
|
||||
<div className="expandable-content p-2">
|
||||
<p className="font-weight-bold">{data?.logType}</p>
|
||||
<p className="font-small-3 mt-2">{data.logMessage}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "2%" }}>
|
||||
<Card>
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle tag="h4">Sistem Aktiviteleri</CardTitle>
|
||||
</CardHeader>
|
||||
<Row className="mx-0 mt-1 mb-50">
|
||||
<Col sm="6" md="2">
|
||||
<div className="d-flex align-items-center">
|
||||
<Label for="sort-select">{t("Show")}</Label>
|
||||
<Input
|
||||
className="ml-1 dataTable-select"
|
||||
type="select"
|
||||
id="sort-select"
|
||||
value={rowsPerPage}
|
||||
onChange={(e) => handlePerPage(e)}
|
||||
>
|
||||
<option value={5}>5</option>
|
||||
<option value={10}>10</option>
|
||||
<option value={25}>25</option>
|
||||
<option value={50}>50</option>
|
||||
<option value={75}>75</option>
|
||||
<option value={100}>100</option>
|
||||
</Input>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<DataTable
|
||||
noHeader
|
||||
pagination
|
||||
paginationServer
|
||||
className="react-dataTable"
|
||||
columns={serverSideColumns}
|
||||
sortIcon={<ChevronDown size={10} />}
|
||||
paginationComponent={CustomPagination}
|
||||
expandableRows
|
||||
expandOnRowClicked
|
||||
expandableRowsComponent={ExpandableTable}
|
||||
data={[...systemHistories]}
|
||||
noDataComponent={
|
||||
<p className="p-2">
|
||||
{t("Activities.activity")}
|
||||
{t("Warnings.notFound")}
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SystemActivity;
|
||||
172
sge-frontend/src/views/UserActivity.js
Normal file
172
sge-frontend/src/views/UserActivity.js
Normal file
@@ -0,0 +1,172 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ChevronDown } from "react-feather";
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Input,
|
||||
Label,
|
||||
Row,
|
||||
Col,
|
||||
} from "reactstrap";
|
||||
import DataTable from "react-data-table-component";
|
||||
import ReactPaginate from "react-paginate";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { getUserHistory } from "../redux/actions/userHistory";
|
||||
|
||||
const UserActivity = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const serverSideColumns = [
|
||||
{
|
||||
name: t("Activities.type"),
|
||||
selector: (row) => row.logType,
|
||||
sortable: true,
|
||||
maxWidth: "500px",
|
||||
},
|
||||
{
|
||||
name: t("Activities.message"),
|
||||
selector: (row) => row.logMessage,
|
||||
sortable: true,
|
||||
maxWidth: "500px",
|
||||
},
|
||||
{
|
||||
name: t("Time"),
|
||||
selector: (row) => row.createdDateTime,
|
||||
sortable: false,
|
||||
minWidth: "350px",
|
||||
cell: (row) => (
|
||||
<span>
|
||||
{row.createdDateTime
|
||||
? new Date(row.createdDateTime).toLocaleString("tr-TR")
|
||||
: "-"}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(5);
|
||||
|
||||
const userHistoriesStore = useSelector((state) => state.userHistory);
|
||||
const [userHistories, setUserHistories] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getUserHistory({ currentPage, rowsPerPage }));
|
||||
}, [currentPage, rowsPerPage]);
|
||||
useEffect(() => {
|
||||
if (userHistoriesStore.userHistories?.content) {
|
||||
setUserHistories(userHistoriesStore.userHistories?.content);
|
||||
}
|
||||
}, [
|
||||
userHistoriesStore?.userHistories?.content?.length,
|
||||
userHistoriesStore?.userHistories,
|
||||
]);
|
||||
|
||||
const handlePagination = (page) => {
|
||||
setCurrentPage(page.selected + 1);
|
||||
setUserHistories(userHistoriesStore.userHistories?.content);
|
||||
};
|
||||
|
||||
const handlePerPage = (e) => {
|
||||
setCurrentPage(1);
|
||||
setRowsPerPage(parseInt(e.target.value));
|
||||
setUserHistories(userHistoriesStore.userHistories?.content);
|
||||
};
|
||||
|
||||
const CustomPagination = () => {
|
||||
const count = Number(
|
||||
(
|
||||
userHistoriesStore?.userHistories?.pageInfo?.totalElements / rowsPerPage
|
||||
).toFixed(1)
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactPaginate
|
||||
previousLabel={""}
|
||||
nextLabel={""}
|
||||
breakLabel="..."
|
||||
pageCount={count || 1}
|
||||
marginPagesDisplayed={2}
|
||||
pageRangeDisplayed={2}
|
||||
activeClassName="active"
|
||||
forcePage={currentPage !== 0 ? currentPage - 1 : 0}
|
||||
onPageChange={(page) => handlePagination(page)}
|
||||
pageClassName={"page-item"}
|
||||
nextLinkClassName={"page-link"}
|
||||
nextClassName={"page-item next"}
|
||||
previousClassName={"page-item prev"}
|
||||
previousLinkClassName={"page-link"}
|
||||
pageLinkClassName={"page-link"}
|
||||
breakClassName="page-item"
|
||||
breakLinkClassName="page-link"
|
||||
containerClassName={
|
||||
"pagination react-paginate separated-pagination pagination-sm justify-content-end pr-1 mt-1"
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ExpandableTable = ({ data }) => {
|
||||
return (
|
||||
<div className="expandable-content p-2">
|
||||
<p className="font-weight-bold">{data?.logType}</p>
|
||||
<p className="font-small-3 mt-2">{data.logMessage}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "2%" }}>
|
||||
<Card>
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle tag="h4">Kullanıcı Aktiviteleri</CardTitle>
|
||||
</CardHeader>
|
||||
<Row className="mx-0 mt-1 mb-50">
|
||||
<Col sm="6" md="2">
|
||||
<div className="d-flex align-items-center">
|
||||
<Label for="sort-select">{t("Show")}</Label>
|
||||
<Input
|
||||
className="ml-1 dataTable-select"
|
||||
type="select"
|
||||
id="sort-select"
|
||||
value={rowsPerPage}
|
||||
onChange={(e) => handlePerPage(e)}
|
||||
>
|
||||
<option value={5}>5</option>
|
||||
<option value={10}>10</option>
|
||||
<option value={25}>25</option>
|
||||
<option value={50}>50</option>
|
||||
<option value={75}>75</option>
|
||||
<option value={100}>100</option>
|
||||
</Input>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<DataTable
|
||||
noHeader
|
||||
pagination
|
||||
paginationServer
|
||||
className="react-dataTable"
|
||||
columns={serverSideColumns}
|
||||
sortIcon={<ChevronDown size={10} />}
|
||||
paginationComponent={CustomPagination}
|
||||
expandableRows
|
||||
expandOnRowClicked
|
||||
expandableRowsComponent={ExpandableTable}
|
||||
data={[...userHistories]}
|
||||
noDataComponent={
|
||||
<p className="p-2">
|
||||
{t("Activities.activity")}
|
||||
{t("Warnings.notFound")}
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserActivity;
|
||||
800
sge-frontend/src/views/UserManagement.js
Normal file
800
sge-frontend/src/views/UserManagement.js
Normal file
@@ -0,0 +1,800 @@
|
||||
import React, { Fragment, useState, useEffect, memo } from "react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import ReactPaginate from "react-paginate";
|
||||
import { ChevronDown, MoreVertical, Plus, Trash } from "react-feather";
|
||||
import DataTable from "react-data-table-component";
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Input,
|
||||
Label,
|
||||
Row,
|
||||
Col,
|
||||
Button,
|
||||
Modal,
|
||||
ModalHeader,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
UncontrolledDropdown,
|
||||
DropdownToggle,
|
||||
DropdownMenu,
|
||||
DropdownItem,
|
||||
} from "reactstrap";
|
||||
import { Edit } from "@mui/icons-material";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { default as SweetAlert } from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import Select from "react-select";
|
||||
import makeAnimated from "react-select/animated";
|
||||
import InputPasswordToggle from "../@core/components/input-password-toggle/index";
|
||||
import {
|
||||
getUsersHttp,
|
||||
updateUser,
|
||||
addUser,
|
||||
deleteUser,
|
||||
} from "../redux/actions/users";
|
||||
import {
|
||||
getOrganisations,
|
||||
getOrganisationById,
|
||||
} from "../redux/actions/organisations";
|
||||
import { getRoles } from "../redux/actions/roles";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { permissionCheck } from "../components/permission-check";
|
||||
import { customFilterForSelect } from "../utility/Utils";
|
||||
|
||||
const Swal = withReactContent(SweetAlert);
|
||||
const animatedComponents = makeAnimated();
|
||||
|
||||
const UserManagement = () => {
|
||||
const { t } = useTranslation();
|
||||
const userId = localStorage.getItem("userId");
|
||||
const initialColumns = [
|
||||
{
|
||||
name: t("UserProfile.firstName"),
|
||||
selector: (row) => row.firstName,
|
||||
sortable: true,
|
||||
minWidth: "150px",
|
||||
},
|
||||
{
|
||||
name: t("UserProfile.lastName"),
|
||||
selector: (row) => row.lastName,
|
||||
sortable: true,
|
||||
minWidth: "150px",
|
||||
},
|
||||
{
|
||||
name: "Email",
|
||||
selector: (row) => row.email,
|
||||
sortable: true,
|
||||
minWidth: "150px",
|
||||
},
|
||||
{
|
||||
name: t("UserProfile.phoneNumber"),
|
||||
selector: (row) => row.phoneNumber,
|
||||
sortable: false,
|
||||
minWidth: "150px",
|
||||
},
|
||||
{
|
||||
name: t("Roles.role"),
|
||||
selector: (row) => row.phoneNumber,
|
||||
sortable: true,
|
||||
minWidth: "150px",
|
||||
},
|
||||
{
|
||||
name: t("Organizations.organization"),
|
||||
selector: (row) => row.organizations,
|
||||
sortable: true,
|
||||
minWidth: "150px",
|
||||
cell: (row) => (
|
||||
<span>
|
||||
{row.organizations?.length != 1
|
||||
? row.organizations?.map((org) => org?.tag + " - ")
|
||||
: row.organizations?.map((org) => org?.tag)}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: t("Status"),
|
||||
selector: (row) => row.delete,
|
||||
sortable: false,
|
||||
minWidth: "150px",
|
||||
cell: (row) => <span>{row.delete ? t("Passive") : t("Active")}</span>,
|
||||
},
|
||||
{
|
||||
name: t("Actions"),
|
||||
allowOverflow: false,
|
||||
maxWidth: "150px",
|
||||
cell: (row) => {
|
||||
return (
|
||||
<div className="d-flex">
|
||||
<UncontrolledDropdown>
|
||||
<DropdownToggle className="pl-1" tag="span">
|
||||
<MoreVertical size={15} />
|
||||
</DropdownToggle>
|
||||
{userId != row?.id && (
|
||||
<DropdownMenu container={"body"} end>
|
||||
{permissionCheck("user_update") && (
|
||||
<DropdownItem
|
||||
tag="a"
|
||||
className="w-100"
|
||||
onClick={() => handleEditUser(row)}
|
||||
>
|
||||
<Edit size={15} />
|
||||
<span className="align-middle ml-50">
|
||||
{t("Cruds.edit")}
|
||||
</span>
|
||||
</DropdownItem>
|
||||
)}
|
||||
{permissionCheck("user_delete") && (
|
||||
<DropdownItem
|
||||
tag="a"
|
||||
className="w-100"
|
||||
onClick={() => handleDeleteUser(row)}
|
||||
>
|
||||
<Trash size={15} />
|
||||
<span className="align-middle ml-50">
|
||||
{t("Cruds.delete")}
|
||||
</span>
|
||||
</DropdownItem>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</UncontrolledDropdown>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (!(permissionCheck("user_delete") || permissionCheck("user_update"))) {
|
||||
const updatedColumns = initialColumns.filter(
|
||||
(column) => column.name !== ("Aksiyonlar" || "Actions")
|
||||
);
|
||||
setServerSideColumns(updatedColumns);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(5);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const [serverSideColumns, setServerSideColumns] = useState(initialColumns);
|
||||
|
||||
const usersStore = useSelector((state) => state.users);
|
||||
const OrganisationsStore = useSelector((state) => state.organizations);
|
||||
const rolesStore = useSelector((state) => state.rolesReducer);
|
||||
const [organizationOptions, setOrganizationOptions] = useState([]);
|
||||
const [rolesOptions, setRolesOptions] = useState([]);
|
||||
const [users, setUsers] = useState([]);
|
||||
const [showAddUserModal, setShowAddUserModal] = useState(false);
|
||||
const [editingUserData, setEditingUserData] = useState(null);
|
||||
const [error, setError] = useState({
|
||||
password: "",
|
||||
});
|
||||
const roleTag = localStorage.getItem("roleTag");
|
||||
const organizationId = localStorage.getItem("organizationId");
|
||||
const [selectedOrganization, setSelectedOrganization] = useState(
|
||||
roleTag != "SUPER_ADMIN" ? organizationId : ""
|
||||
);
|
||||
const [showDeletedUsers, setShowDeletedUsers] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(
|
||||
getUsersHttp({
|
||||
currentPage,
|
||||
rowsPerPage,
|
||||
selectedOrganization,
|
||||
showDeletedUsers,
|
||||
})
|
||||
);
|
||||
}, [currentPage, rowsPerPage, selectedOrganization, showDeletedUsers]);
|
||||
|
||||
useEffect(() => {
|
||||
if (roleTag === "SUPER_ADMIN") {
|
||||
dispatch(getOrganisations());
|
||||
} else {
|
||||
dispatch(getOrganisationById(organizationId));
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getRoles());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (usersStore.data?.content) {
|
||||
setUsers(usersStore.data?.content);
|
||||
}
|
||||
}, [usersStore.total, usersStore.data]);
|
||||
|
||||
useEffect(() => {
|
||||
let organizationOptions = [];
|
||||
|
||||
if (
|
||||
OrganisationsStore.organization &&
|
||||
OrganisationsStore.organization.length !== 0
|
||||
) {
|
||||
organizationOptions.push({
|
||||
value: OrganisationsStore.organization.id,
|
||||
label: OrganisationsStore.organization.tag,
|
||||
});
|
||||
|
||||
if (OrganisationsStore.organization.children) {
|
||||
organizationOptions = [
|
||||
...organizationOptions,
|
||||
...OrganisationsStore.organization.children.map((organization) => ({
|
||||
value: organization.child.id,
|
||||
label: organization.child.tag,
|
||||
})),
|
||||
];
|
||||
}
|
||||
} else {
|
||||
organizationOptions = OrganisationsStore.dataOrganization.map(
|
||||
(organization) => ({
|
||||
value: organization.id,
|
||||
label: organization.tag,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
setOrganizationOptions(organizationOptions);
|
||||
}, [OrganisationsStore]);
|
||||
|
||||
useEffect(() => {
|
||||
setRolesOptions(
|
||||
rolesStore.roles.map((role) => ({
|
||||
value: role.id,
|
||||
label: role.tag,
|
||||
}))
|
||||
);
|
||||
}, [rolesStore, roleTag]);
|
||||
|
||||
const handleFilter = (e) => {
|
||||
setSearchValue(e.target.value);
|
||||
|
||||
if (e.target.value !== "") {
|
||||
setUsers(
|
||||
usersStore.data?.content.filter((user) =>
|
||||
user.firstName
|
||||
.toUpperCase()
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
.includes(
|
||||
e.target.value
|
||||
.toUpperCase()
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setUsers(usersStore.data?.content);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePagination = (page) => {
|
||||
setCurrentPage(page.selected + 1);
|
||||
setUsers(usersStore.data?.content);
|
||||
};
|
||||
|
||||
const handlePerPage = (e) => {
|
||||
setCurrentPage(1);
|
||||
setRowsPerPage(parseInt(e.target.value));
|
||||
setUsers(usersStore.data?.content);
|
||||
};
|
||||
|
||||
const CustomPagination = () => {
|
||||
const count = Number(
|
||||
(usersStore?.data?.pageInfo?.totalElements / rowsPerPage).toFixed(1)
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactPaginate
|
||||
previousLabel={""}
|
||||
nextLabel={""}
|
||||
breakLabel="..."
|
||||
pageCount={count || 1}
|
||||
marginPagesDisplayed={2}
|
||||
pageRangeDisplayed={2}
|
||||
activeClassName="active"
|
||||
forcePage={currentPage !== 0 ? currentPage - 1 : 0}
|
||||
onPageChange={(page) => handlePagination(page)}
|
||||
pageClassName={"page-item"}
|
||||
nextLinkClassName={"page-link"}
|
||||
nextClassName={"page-item next"}
|
||||
previousClassName={"page-item prev"}
|
||||
previousLinkClassName={"page-link"}
|
||||
pageLinkClassName={"page-link"}
|
||||
breakClassName="page-item"
|
||||
breakLinkClassName="page-link"
|
||||
containerClassName={
|
||||
"pagination react-paginate separated-pagination pagination-sm justify-content-end pr-1 mt-1"
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const onAddUserButtonPressed = () => {
|
||||
setShowAddUserModal(true);
|
||||
};
|
||||
|
||||
const onAddUserModalButtonPressed = () => {
|
||||
setLoading(true);
|
||||
if (
|
||||
usersStore.data?.content?.some(
|
||||
(user) =>
|
||||
user.email === editingUserData.email && user.id !== editingUserData.id
|
||||
)
|
||||
) {
|
||||
enqueueSnackbar(t("Users.existingEmail"), {
|
||||
variant: "error",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
usersStore.data?.content?.some(
|
||||
(user) =>
|
||||
user.phoneNumber === editingUserData.phoneNumber &&
|
||||
user.id !== editingUserData.id
|
||||
)
|
||||
) {
|
||||
enqueueSnackbar(t("Users.existingPhoneNumber"), {
|
||||
variant: "error",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (editingUserData.password && editingUserData.password <= 6) {
|
||||
enqueueSnackbar(t("PasswordLabel.passwordValidation"), {
|
||||
variant: "error",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
const userOrganizations = [];
|
||||
if (editingUserData?.organizations?.length > 0) {
|
||||
editingUserData?.organizations?.forEach((organization) => {
|
||||
if (organization?.value) {
|
||||
userOrganizations.push('"' + organization?.value + '"');
|
||||
} else {
|
||||
userOrganizations.push('"' + organization + '"');
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!editingUserData.id) {
|
||||
const newUserData = {
|
||||
firstName: editingUserData.firstName,
|
||||
lastName: editingUserData.lastName,
|
||||
email: editingUserData.email,
|
||||
password: editingUserData?.password,
|
||||
phoneNumber: editingUserData?.phoneNumber,
|
||||
status: editingUserData?.status || "Aktif",
|
||||
organizations: userOrganizations,
|
||||
role: editingUserData?.role,
|
||||
};
|
||||
|
||||
dispatch(addUser(newUserData))
|
||||
.then(() => {
|
||||
setLoading(false);
|
||||
setEditingUserData(null);
|
||||
setShowAddUserModal(false);
|
||||
enqueueSnackbar(t("Warnings.addedSuccessfully"), {
|
||||
variant: "success",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
setShowAddUserModal(false);
|
||||
enqueueSnackbar(t("Warnings.addedFail"), {
|
||||
variant: "error",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
const updateUserData = {
|
||||
id: editingUserData.id,
|
||||
firstName: editingUserData.firstName,
|
||||
lastName: editingUserData.lastName,
|
||||
email: editingUserData.email,
|
||||
phoneNumber: editingUserData?.phoneNumber,
|
||||
status: editingUserData?.status || "Aktif",
|
||||
organizations: userOrganizations,
|
||||
role: editingUserData?.role?.value || editingUserData?.role,
|
||||
};
|
||||
dispatch(updateUser(updateUserData))
|
||||
.then(() => {
|
||||
enqueueSnackbar(t("Warnings.updatedSuccessfully"), {
|
||||
variant: "success",
|
||||
});
|
||||
setLoading(false);
|
||||
setEditingUserData(null);
|
||||
setShowAddUserModal(false);
|
||||
})
|
||||
.catch(() => {
|
||||
enqueueSnackbar(
|
||||
`${updateUserData.firstName + " " + updateUserData.lastName}, ${t(
|
||||
"Warnings.updatedFail"
|
||||
)},`,
|
||||
{
|
||||
variant: "error",
|
||||
}
|
||||
);
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const isPassword = (password) =>
|
||||
/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,20}$/.test(password);
|
||||
|
||||
const handlePasswordChange = (e) => {
|
||||
if (!isPassword(e.target.value)) {
|
||||
setError({
|
||||
...error,
|
||||
password: t("PasswordLabel.passwordValidation"),
|
||||
});
|
||||
} else {
|
||||
setError({
|
||||
...error,
|
||||
password: "",
|
||||
});
|
||||
setEditingUserData({
|
||||
...editingUserData,
|
||||
password: e.target.value,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const renderUserModal = () => {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={showAddUserModal}
|
||||
toggle={() => setShowAddUserModal(!showAddUserModal)}
|
||||
className="modal-dialog-centered"
|
||||
>
|
||||
<ModalHeader toggle={() => setShowAddUserModal(!showAddUserModal)}>
|
||||
{editingUserData?.id
|
||||
? editingUserData.firstName + " " + editingUserData.lastName
|
||||
: t("Users.addUser")}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" for="user-firstName">
|
||||
{t("UserProfile.firstName")}:
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="user-firstName"
|
||||
placeholder=""
|
||||
value={editingUserData?.firstName || ""}
|
||||
onChange={(e) =>
|
||||
setEditingUserData({
|
||||
...editingUserData,
|
||||
firstName: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" for="user-lastName">
|
||||
{t("UserProfile.lastName")}:
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="user-lastName"
|
||||
placeholder=""
|
||||
value={editingUserData?.lastName || ""}
|
||||
onChange={(e) =>
|
||||
setEditingUserData({
|
||||
...editingUserData,
|
||||
lastName: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Fragment>
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" for="email-address">
|
||||
Email:
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="email-address"
|
||||
placeholder=""
|
||||
value={editingUserData?.email || ""}
|
||||
onChange={(e) =>
|
||||
setEditingUserData({
|
||||
...editingUserData,
|
||||
email: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{!editingUserData?.id && (
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" for="password">
|
||||
{t("PasswordLabel.passwordLabel")}:
|
||||
</Label>
|
||||
<InputPasswordToggle
|
||||
id="password"
|
||||
className="input-group-merge mb-2"
|
||||
htmlFor="password"
|
||||
placeholder=""
|
||||
defaultValue={editingUserData?.password || ""}
|
||||
onChange={(e) => handlePasswordChange(e)}
|
||||
/>
|
||||
{error?.password && (
|
||||
<Label className="text-danger">
|
||||
{t("PasswordLabel.passwordValidation")}
|
||||
</Label>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" for="phoneNumber">
|
||||
{t("UserProfile.phoneNumber")}:
|
||||
</Label>
|
||||
<Input
|
||||
id="phoneNumber"
|
||||
type="number"
|
||||
className="input-group-merge mb-2"
|
||||
placeholder=""
|
||||
defaultValue={editingUserData?.phoneNumber || ""}
|
||||
onChange={(e) =>
|
||||
setEditingUserData({
|
||||
...editingUserData,
|
||||
phoneNumber: e.target.value,
|
||||
})
|
||||
}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "e" || e.key === "E") {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Label>5XXXXXXXXX formatında giriniz.</Label>
|
||||
</div>
|
||||
</Fragment>
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" for="organization-name">
|
||||
{t("Organizations.organization")}:
|
||||
</Label>
|
||||
<Select
|
||||
id="organization-name"
|
||||
closeMenuOnSelect={true}
|
||||
menuPlacement="auto"
|
||||
placeholder=""
|
||||
isMulti
|
||||
components={animatedComponents}
|
||||
filterOption={customFilterForSelect}
|
||||
className="react-select"
|
||||
defaultValue={editingUserData?.organizations || ""}
|
||||
options={organizationOptions}
|
||||
onChange={(value) => {
|
||||
setEditingUserData({
|
||||
...editingUserData,
|
||||
organizations: value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<Label className="form-label" for="role-select">
|
||||
{t("Roles.role")}:
|
||||
</Label>
|
||||
<Select
|
||||
id="role-select"
|
||||
menuPlacement="auto"
|
||||
placeholder=""
|
||||
closeMenuOnSelect={true}
|
||||
components={animatedComponents}
|
||||
options={rolesOptions}
|
||||
className="react-select"
|
||||
filterOption={customFilterForSelect}
|
||||
defaultValue={editingUserData?.role || ""}
|
||||
onChange={(value) => {
|
||||
setEditingUserData({
|
||||
...editingUserData,
|
||||
role: value?.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
disabled={
|
||||
!(
|
||||
editingUserData?.firstName &&
|
||||
editingUserData?.lastName &&
|
||||
editingUserData?.email &&
|
||||
!error?.password &&
|
||||
editingUserData?.phoneNumber &&
|
||||
editingUserData?.role &&
|
||||
editingUserData?.organizations
|
||||
)
|
||||
}
|
||||
color="primary"
|
||||
onClick={onAddUserModalButtonPressed}
|
||||
>
|
||||
{loading
|
||||
? t("Cruds.saving")
|
||||
: !editingUserData?.id
|
||||
? t("Cruds.save")
|
||||
: t("Cruds.update")}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const handleDeleteUser = async (selectedUser) => {
|
||||
const result = await Swal.fire({
|
||||
title: `${selectedUser.firstName + " " + selectedUser.lastName}, ${t(
|
||||
"Warnings.sureForDelete"
|
||||
)}`,
|
||||
text: t("Warnings.notUndone"),
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonText: t("Cruds.delete"),
|
||||
cancelButtonText: t("Cruds.cancel"),
|
||||
customClass: {
|
||||
confirmButton: "btn btn-primary",
|
||||
cancelButton: "btn btn-danger ml-1",
|
||||
},
|
||||
buttonsStyling: false,
|
||||
});
|
||||
if (result.value !== null && result.value === true) {
|
||||
dispatch(deleteUser(selectedUser.id));
|
||||
setCurrentPage(1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditUser = (selectedUser) => {
|
||||
setShowAddUserModal(true);
|
||||
|
||||
const selectedUserRole = {
|
||||
value: selectedUser.role.id,
|
||||
label: selectedUser.role.tag,
|
||||
};
|
||||
|
||||
const selectedUserOrganization = selectedUser?.organizations.map(
|
||||
(organization) => ({
|
||||
value: organization.id,
|
||||
label: organization.tag,
|
||||
})
|
||||
);
|
||||
|
||||
setEditingUserData({
|
||||
...selectedUser,
|
||||
role: selectedUserRole,
|
||||
organizations: selectedUserOrganization,
|
||||
});
|
||||
};
|
||||
|
||||
let listOfNumber = [5, 10, 25, 50, 75, 100];
|
||||
return (
|
||||
<div style={{ marginTop: "2%" }}>
|
||||
<Card>
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle tag="h4">{t("Users.users")}</CardTitle>
|
||||
{permissionCheck("user_create") && (
|
||||
<Button
|
||||
className="ml-2"
|
||||
color="primary"
|
||||
onClick={onAddUserButtonPressed}
|
||||
>
|
||||
<Plus size={15} />
|
||||
<span className="align-middle ml-50">{t("Users.addUser")}</span>
|
||||
</Button>
|
||||
)}
|
||||
</CardHeader>
|
||||
<Row className="mx-0 mt-1 mb-50">
|
||||
<Col sm="6" md="2">
|
||||
<div className="d-flex align-items-center">
|
||||
<Label for="sort-select">{t("Show")}</Label>
|
||||
<Input
|
||||
className="ml-1 dataTable-select"
|
||||
type="select"
|
||||
id="sort-select"
|
||||
value={rowsPerPage}
|
||||
onChange={(e) => handlePerPage(e)}
|
||||
>
|
||||
<option value={5}>5</option>
|
||||
<option value={10}>10</option>
|
||||
<option value={25}>25</option>
|
||||
<option value={50}>50</option>
|
||||
<option value={75}>75</option>
|
||||
<option value={100}>100</option>
|
||||
{!listOfNumber.includes(rowsPerPage) && (
|
||||
<option value={rowsPerPage}>{rowsPerPage}</option>
|
||||
)}
|
||||
</Input>
|
||||
</div>
|
||||
</Col>
|
||||
<Col sm="6" md="3" className="mt-1">
|
||||
{permissionCheck("deleted_items") && (
|
||||
<div className="ml-4">
|
||||
<Input
|
||||
className="dataTable-select"
|
||||
id="deleted-checkbox"
|
||||
type="checkbox"
|
||||
checked={showDeletedUsers}
|
||||
onChange={(e) => {
|
||||
setShowDeletedUsers(e.target.checked);
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
/>
|
||||
<Label for="deleted-checkbox">
|
||||
{t("Users.showDeletedUsers")}
|
||||
</Label>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
<Col
|
||||
className="d-flex align-items-center justify-content-end mt-1 ml-md-auto"
|
||||
sm="6"
|
||||
md="4"
|
||||
>
|
||||
<Input
|
||||
className="ml-1 mr-1 dataTable-select"
|
||||
type="select"
|
||||
id="sort-select"
|
||||
value={selectedOrganization ? selectedOrganization : ""}
|
||||
onChange={(e) => {
|
||||
setSelectedOrganization(e.target.value);
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
>
|
||||
{roleTag === "SUPER_ADMIN" && (
|
||||
<option value={""}>{t("Organizations.organizations")}</option>
|
||||
)}
|
||||
{organizationOptions.map((organization, index) => (
|
||||
<option key={index} value={organization.value}>
|
||||
{organization.label}
|
||||
</option>
|
||||
))}
|
||||
</Input>
|
||||
<Input
|
||||
className="dataTable-filter"
|
||||
type="text"
|
||||
bsSize="ml"
|
||||
id="search-input"
|
||||
value={searchValue}
|
||||
onChange={handleFilter}
|
||||
placeholder={t("ByName") + " " + t("Filter")}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<DataTable
|
||||
noHeader
|
||||
pagination
|
||||
paginationServer
|
||||
className="react-dataTable"
|
||||
columns={serverSideColumns}
|
||||
sortIcon={<ChevronDown size={10} />}
|
||||
paginationComponent={CustomPagination}
|
||||
data={[...users]}
|
||||
noDataComponent={<p className="p-2">{t("Warnings.notFound")}</p>}
|
||||
/>
|
||||
</Card>
|
||||
{renderUserModal()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(UserManagement);
|
||||
453
sge-frontend/src/views/UserProfile.js
Normal file
453
sge-frontend/src/views/UserProfile.js
Normal file
@@ -0,0 +1,453 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Check, Lock } from "react-feather";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Col,
|
||||
Input,
|
||||
Label,
|
||||
Row,
|
||||
} from "reactstrap";
|
||||
import PasswordModal from "../components/password-change";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getUser, updateUserInfo } from "../redux/actions/user/index";
|
||||
import {
|
||||
getUserProfileSettings,
|
||||
updateUserProfileSettings,
|
||||
} from "../redux/actions/userProfileSettings/index";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useSnackbar } from "notistack";
|
||||
|
||||
const UserProfile = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const userId = localStorage.getItem("userId");
|
||||
const userStore = useSelector((state) => state.user);
|
||||
const userProfileSettingsStore = useSelector(
|
||||
(state) => state.userProfileSettings.userProfileSettings
|
||||
);
|
||||
const [userInfo, setUserInfo] = useState([]);
|
||||
const [userProfileSettings, setUserProfileSettings] = useState([]);
|
||||
const [showPasswordModal, setShowPasswordModal] = useState(false);
|
||||
const [showAvatarModal, setShowAvatarModal] = useState(false);
|
||||
const [selectedImage, setSelectedImage] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getUser(userId));
|
||||
dispatch(getUserProfileSettings());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setUserInfo({
|
||||
id: userId,
|
||||
firstName: userStore.data?.firstName,
|
||||
lastName: userStore.data?.lastName,
|
||||
email: userStore.data?.email,
|
||||
phoneNumber: userStore.data?.phoneNumber,
|
||||
});
|
||||
}, [userStore]);
|
||||
|
||||
useEffect(() => {
|
||||
setUserProfileSettings({
|
||||
infoMail: userProfileSettingsStore?.infoMail || false,
|
||||
errorMail: userProfileSettingsStore?.errorMail || false,
|
||||
infoNotification: userProfileSettingsStore?.infoNotification || false,
|
||||
errorNotification: userProfileSettingsStore?.errorNotification || false,
|
||||
});
|
||||
}, [userProfileSettingsStore]);
|
||||
|
||||
const onPasswordButtonPressed = () => {
|
||||
setShowPasswordModal(true);
|
||||
};
|
||||
|
||||
const onSaveUserInfosButtonPressed = () => {
|
||||
setLoading(true);
|
||||
|
||||
dispatch(updateUserInfo(userInfo))
|
||||
.then(() => {
|
||||
enqueueSnackbar(t("Warnings.updatedSuccessfully"), {
|
||||
variant: "success",
|
||||
});
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => {
|
||||
enqueueSnackbar(` ${t("Warnings.updatedFail")},`, {
|
||||
variant: "error",
|
||||
});
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const onSaveUserProfileSettingsButtonPressed = () => {
|
||||
setLoading(true);
|
||||
|
||||
dispatch(updateUserProfileSettings(userProfileSettings))
|
||||
.then(() => {
|
||||
enqueueSnackbar(t("Warnings.updatedSuccessfully"), {
|
||||
variant: "success",
|
||||
});
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => {
|
||||
enqueueSnackbar(` ${t("Warnings.updatedFail")},`, {
|
||||
variant: "error",
|
||||
});
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
// const onEditAvatarButtonPressed = () => {
|
||||
// setShowAvatarModal(true);
|
||||
// };
|
||||
|
||||
// const renderAvatarModal = () => {
|
||||
// return (
|
||||
// <Modal
|
||||
// isOpen={showAvatarModal}
|
||||
// toggle={() => setShowAvatarModal(!showAvatarModal)}
|
||||
// className="modal-dialog-centered"
|
||||
// >
|
||||
// <ModalHeader toggle={() => setShowAvatarModal(!showAvatarModal)}>
|
||||
// {t("UserProfile.updateAvatar")}
|
||||
// </ModalHeader>
|
||||
// <ModalBody>
|
||||
// <div className="mb-3 d-flex justify-content-center">
|
||||
// <img
|
||||
// className="rounded-circle border border-3"
|
||||
// width="150"
|
||||
// height="150"
|
||||
// src={
|
||||
// selectedImage
|
||||
// ? URL.createObjectURL(selectedImage)
|
||||
// : "/logo192.png"
|
||||
// }
|
||||
// />
|
||||
// </div>
|
||||
// <div className="mb-2">
|
||||
// <Input
|
||||
// type="file"
|
||||
// name="newAvatar"
|
||||
// onChange={(event) => {
|
||||
// setSelectedImage(event.target.files[0]);
|
||||
// }}
|
||||
// />
|
||||
// </div>
|
||||
// </ModalBody>
|
||||
// <ModalFooter>
|
||||
// <Button
|
||||
// color="primary"
|
||||
// //onClick={onUpdateAvatarModalButtonPressed}
|
||||
// >
|
||||
// {loading ? t("Cruds.saving") : t("Cruds.update")}
|
||||
// </Button>
|
||||
// </ModalFooter>
|
||||
// </Modal>
|
||||
// );
|
||||
// };
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "2%" }}>
|
||||
<Card className="border-bottom">
|
||||
<CardHeader className="border-bottom">
|
||||
<CardTitle tag="h4" className="row ml-md-3 align-items-center">
|
||||
{/* <div className="d-flex position-relative mr-2">
|
||||
<img
|
||||
className="rounded-circle"
|
||||
width="50"
|
||||
height="50"
|
||||
src="/logo192.png"
|
||||
/>
|
||||
<button
|
||||
style={{
|
||||
position: "absolute",
|
||||
right: "-5px",
|
||||
border: "none",
|
||||
borderRadius: "50%",
|
||||
backgroundColor: "#028a4a",
|
||||
padding: "2px 4px",
|
||||
color: "whitesmoke",
|
||||
fontSize: "small",
|
||||
}}
|
||||
>
|
||||
<Edit size={15} />
|
||||
</button>
|
||||
</div> */}
|
||||
{/* <div className="d-flex position-relative mr-2">
|
||||
<svg
|
||||
className="rounded-circle"
|
||||
fill="currentColor"
|
||||
width="50"
|
||||
height="50"
|
||||
version="1.1"
|
||||
id="Capa_1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 311.541 311.541"
|
||||
>
|
||||
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
|
||||
<g
|
||||
id="SVGRepo_tracerCarrier"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M155.771,26.331C69.74,26.331,0,96.071,0,182.102c0,37.488,13.25,71.883,35.314,98.761 c3.404-27.256,30.627-50.308,68.8-61.225c13.946,12.994,31.96,20.878,51.656,20.878c19.233,0,36.894-7.487,50.698-19.936 c38.503,11.871,65.141,36.27,66.017,64.63c24.284-27.472,39.056-63.555,39.056-103.108 C311.541,96.071,241.801,26.331,155.771,26.331z M155.771,222.069c-9.944,0-19.314-2.732-27.634-7.464 c-20.05-11.409-33.855-34.756-33.855-61.711c0-38.143,27.583-69.176,61.489-69.176c33.909,0,61.489,31.033,61.489,69.176 c0,27.369-14.237,51.004-34.786,62.215C174.379,219.523,165.346,222.069,155.771,222.069z"></path>{" "}
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<button
|
||||
style={{
|
||||
position: "absolute",
|
||||
right: "-5px",
|
||||
border: "none",
|
||||
borderRadius: "50%",
|
||||
backgroundColor: "#028a4a",
|
||||
padding: "2px 4px",
|
||||
color: "whitesmoke",
|
||||
fontSize: "small",
|
||||
}}
|
||||
onClick={onEditAvatarButtonPressed}
|
||||
>
|
||||
<Edit size={15} />
|
||||
</button>
|
||||
</div> */}
|
||||
{t("UserProfile.myProfile")}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<Row>
|
||||
<Col className="pl-md-5 pl-2 col-md-6 col-12">
|
||||
<Row className="mx-0 mt-1">
|
||||
<Col sm="6" md="5" className="mr-2">
|
||||
<div className="d-flex flex-column align-items-start mb-2">
|
||||
<Label for="user-firstName">
|
||||
{t("UserProfile.firstName")}
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="user-firstName"
|
||||
defaultValue={userInfo?.firstName}
|
||||
onChange={(e) =>
|
||||
setUserInfo({
|
||||
...userInfo,
|
||||
firstName: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col sm="6" md="5" className="mr-2">
|
||||
<div className="d-flex flex-column align-items-start mb-2">
|
||||
<Label for="user-lastName">{t("UserProfile.lastName")}</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="user-lastName"
|
||||
defaultValue={userInfo?.lastName}
|
||||
onChange={(e) =>
|
||||
setUserInfo({
|
||||
...userInfo,
|
||||
lastName: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mx-0 mt-1">
|
||||
<Col sm="6" md="5" className="mr-2">
|
||||
<div className="d-flex flex-column align-items-start mb-2">
|
||||
<Label for="user-email">Email </Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="user-email"
|
||||
defaultValue={userInfo?.email}
|
||||
onChange={(e) =>
|
||||
setUserInfo({
|
||||
...userInfo,
|
||||
email: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col sm="6" md="5" className="mr-2">
|
||||
<div className="d-flex flex-column align-items-start mb-2">
|
||||
<Label for="user-phoneNumber">
|
||||
{t("UserProfile.phoneNumber")}
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="user-phoneNumber"
|
||||
defaultValue={userInfo?.phoneNumber}
|
||||
onChange={(e) =>
|
||||
setUserInfo({
|
||||
...userInfo,
|
||||
phoneNumber: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mx-0 mt-1 mb-5">
|
||||
<Col sm="6" md="5" className="mr-2">
|
||||
<Button color="primary" onClick={onSaveUserInfosButtonPressed}>
|
||||
<Check size={15} />
|
||||
<span className="align-middle ml-50">
|
||||
{loading ? t("Cruds.saving") : t("Cruds.save")}
|
||||
</span>
|
||||
</Button>
|
||||
</Col>
|
||||
<Col sm="6" md="5" className="ml-md-auto mt-md-0 mt-2">
|
||||
<Button color="primary" onClick={onPasswordButtonPressed}>
|
||||
<Lock size={15} />
|
||||
<span className="align-middle ml-50">
|
||||
{t("UserProfile.changePassword")}
|
||||
</span>
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
|
||||
<Col className="pl-md-5 pl-2 col-md-6 col-12 pb-2 border-left">
|
||||
<Row className="mx-0 mt-1">
|
||||
<Col sm="6" md="6" className="mr-2">
|
||||
<Label className="h4">
|
||||
{t("UserProfile.communicationPreferences")}
|
||||
</Label>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="mx-0 mt-1">
|
||||
<Col sm="6" md="4" className="mr-2">
|
||||
<div className="form-check form-switch mb-2">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
id="info-mail-checkbox"
|
||||
checked={
|
||||
userProfileSettings?.infoMail === true ? true : false
|
||||
}
|
||||
onChange={(e) => {
|
||||
setUserProfileSettings({
|
||||
...userProfileSettings,
|
||||
infoMail: e.target.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<label
|
||||
className="form-check-label h6"
|
||||
htmlFor="info-mail-checkbox"
|
||||
>
|
||||
Info Email
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-check form-switch mb-2">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
id="error-mail-checkbox"
|
||||
checked={
|
||||
userProfileSettings?.errorMail === true ? true : false
|
||||
}
|
||||
onChange={(e) => {
|
||||
setUserProfileSettings({
|
||||
...userProfileSettings,
|
||||
errorMail: e.target.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<label
|
||||
className="form-check-label h6"
|
||||
htmlFor="error-mail-checkbox"
|
||||
>
|
||||
Error Email
|
||||
</label>
|
||||
</div>
|
||||
</Col>
|
||||
<Col sm="6" md="4" className="mr-2">
|
||||
<div className="form-check form-switch mb-2">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
id="info-notification-checkbox"
|
||||
checked={
|
||||
userProfileSettings?.errorNotification === true
|
||||
? true
|
||||
: false
|
||||
}
|
||||
onChange={(e) => {
|
||||
setUserProfileSettings({
|
||||
...userProfileSettings,
|
||||
errorNotification: e.target.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<label
|
||||
className="form-check-label h6"
|
||||
htmlFor="info-notification-checkbox"
|
||||
>
|
||||
Info {t("Notifications.notification")}
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-check form-switch mb-2">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
id="error-notification-checkbox"
|
||||
checked={
|
||||
userProfileSettings?.infoNotification === true
|
||||
? true
|
||||
: false
|
||||
}
|
||||
onChange={(e) => {
|
||||
setUserProfileSettings({
|
||||
...userProfileSettings,
|
||||
infoNotification: e.target.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<label
|
||||
className="form-check-label h6"
|
||||
htmlFor="error-notification-checkbox"
|
||||
>
|
||||
Error {t("Notifications.notification")}
|
||||
</label>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mx-0 mt-1">
|
||||
<Col sm="6" md="5" className="mr-2">
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={onSaveUserProfileSettingsButtonPressed}
|
||||
>
|
||||
<Check size={15} />
|
||||
<span className="align-middle ml-50">{t("Cruds.save")}</span>
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
<PasswordModal
|
||||
showPasswordModal={showPasswordModal}
|
||||
setShowPasswordModal={setShowPasswordModal}
|
||||
userId={userId}
|
||||
/>
|
||||
{/* {renderAvatarModal()} */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserProfile;
|
||||
248
sge-frontend/src/views/VerifyAndAccessSurvey.js
Normal file
248
sge-frontend/src/views/VerifyAndAccessSurvey.js
Normal file
@@ -0,0 +1,248 @@
|
||||
import React, { useState, useContext } from "react";
|
||||
import {
|
||||
Card,
|
||||
Box,
|
||||
Button,
|
||||
FormControl,
|
||||
Grid,
|
||||
InputLabel,
|
||||
OutlinedInput,
|
||||
Stack,
|
||||
Typography,
|
||||
FormHelperText,
|
||||
} from "@mui/material";
|
||||
import backgroundImage from "../assets/images/background_dot.png";
|
||||
import { ThemeColors } from "../utility/context/ThemeColors.js";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSnackbar } from "notistack";
|
||||
import ApplicationService from "../services/ApplicationService.js";
|
||||
|
||||
const VerifyAndAccessSurvey = () => {
|
||||
const history = useHistory();
|
||||
const { t } = useTranslation();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const [error, setError] = useState({
|
||||
email: "",
|
||||
});
|
||||
const [sendList, setSendList] = useState({
|
||||
email: "",
|
||||
tc: "",
|
||||
surveyLink: "",
|
||||
});
|
||||
|
||||
const { colors } = useContext(ThemeColors);
|
||||
const isEmail = (email) => /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]/i.test(email);
|
||||
const currentURL = window.location.href;
|
||||
|
||||
const onSendSurveyVerificationModalButtonPressed = async (e) => {
|
||||
e.preventDefault();
|
||||
if (!isEmail(sendList?.email) || sendList?.email === "") {
|
||||
setError({
|
||||
email: t("PasswordLabel.enterEmail"),
|
||||
});
|
||||
} else {
|
||||
await ApplicationService.http()
|
||||
.post(
|
||||
"/graphql",
|
||||
{
|
||||
query: `
|
||||
mutation {
|
||||
verifyAndAccessSurvey(
|
||||
participant_email: "${sendList?.email}",
|
||||
participant_id: "${sendList?.tc}",
|
||||
hashedLink: "${currentURL}",
|
||||
)
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer " + localStorage.getItem("accessToken"),
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.data?.errors) {
|
||||
enqueueSnackbar("Doğrulama başarısız." + t("Warnings.notFound"), {
|
||||
variant: "error",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
} else {
|
||||
enqueueSnackbar("Doğrulama başarılı.", {
|
||||
variant: "success",
|
||||
preventDuplicate: true,
|
||||
});
|
||||
const response1 =
|
||||
response.data.data.verifyAndAccessSurvey.split("redirect:");
|
||||
const redirectUrl = response1[1].trim();
|
||||
if (redirectUrl) {
|
||||
history.push(redirectUrl);
|
||||
} else {
|
||||
console.error("Invalid redirect URL.");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const renderVerify = () => {
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
sx={{ minHeight: "calc(100vh - 68px)" }}
|
||||
>
|
||||
<Grid item sx={{ m: { xs: 1, sm: 3 }, mb: 0 }}>
|
||||
<Card
|
||||
sx={{
|
||||
maxWidth: { xs: 400, lg: 475 },
|
||||
margin: { xs: 2.5, md: 3 },
|
||||
"& > *": {
|
||||
flexGrow: 1,
|
||||
flexBasis: "50%",
|
||||
},
|
||||
}}
|
||||
content="false"
|
||||
>
|
||||
<Box sx={{ p: { xs: 2, sm: 3, xl: 5 } }}>
|
||||
<Grid
|
||||
container
|
||||
spacing={2}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Grid item xs={12}>
|
||||
<Grid
|
||||
container
|
||||
direction="column-reverse"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Grid item>
|
||||
<Stack
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
spacing={1}
|
||||
>
|
||||
<Typography
|
||||
color="#028a4a"
|
||||
gutterBottom
|
||||
variant="h3"
|
||||
style={{
|
||||
fontSize: "1.25rem",
|
||||
color: colors.primary.dark,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{"Anket Doğrulama"}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="caption"
|
||||
fontSize="15px"
|
||||
textAlign="center"
|
||||
style={{
|
||||
color: "rgba(158, 158, 158, 0.7)",
|
||||
fontWeight: 400,
|
||||
lineHeight: 1.66,
|
||||
maxWidth: 300,
|
||||
}}
|
||||
>
|
||||
{/* {t("PasswordLabel.enterEmail")} */}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<form
|
||||
noValidate
|
||||
onSubmit={onSendSurveyVerificationModalButtonPressed}
|
||||
id="login-form"
|
||||
>
|
||||
<FormControl
|
||||
fullWidth
|
||||
error={Boolean(error.email)}
|
||||
sx={{ ...colors.primary.light }}
|
||||
>
|
||||
<InputLabel htmlFor="survey-email">Email</InputLabel>
|
||||
<OutlinedInput
|
||||
id="survey-email"
|
||||
type="email"
|
||||
value={sendList?.email}
|
||||
name="email"
|
||||
onChange={(e) =>
|
||||
setSendList({
|
||||
...sendList,
|
||||
email: e.target.value,
|
||||
})
|
||||
}
|
||||
label="Email"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
fullWidth
|
||||
error={Boolean(error.email)}
|
||||
sx={{ ...colors.primary.light }}
|
||||
>
|
||||
<InputLabel htmlFor="survey-email">Anket Kodu</InputLabel>
|
||||
<OutlinedInput
|
||||
id="survey-tc"
|
||||
type="tc"
|
||||
value={sendList?.tc}
|
||||
name="tc"
|
||||
onChange={(e) =>
|
||||
setSendList({
|
||||
...sendList,
|
||||
tc: e.target.value,
|
||||
})
|
||||
}
|
||||
label="tc"
|
||||
/>
|
||||
</FormControl>
|
||||
{error?.email && (
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<FormHelperText error>{error.email}</FormHelperText>
|
||||
</Box>
|
||||
)}
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Button
|
||||
disableElevation
|
||||
fullWidth
|
||||
size="large"
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="success"
|
||||
id="login-form-submit-button"
|
||||
>
|
||||
{"Görüntüle"}
|
||||
</Button>
|
||||
</Box>
|
||||
</form>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
direction="column"
|
||||
justifyContent="flex-end"
|
||||
sx={{ minHeight: "100vh" }}
|
||||
style={{
|
||||
backgroundImage: `url(${backgroundImage})`,
|
||||
backgroundRepeat: "repeat",
|
||||
}}
|
||||
>
|
||||
{renderVerify()}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerifyAndAccessSurvey;
|
||||
Reference in New Issue
Block a user