Add 'sge-frontend/' from commit '5fa787e054b25ac53edc7ff0275ea7960a709401'

git-subtree-dir: sge-frontend
git-subtree-mainline: 876c278ac4
git-subtree-split: 5fa787e054
This commit is contained in:
2025-08-04 00:27:23 +03:00
337 changed files with 854877 additions and 0 deletions

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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='&copy; <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);

View 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;

View 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;

File diff suppressed because it is too large Load Diff

View 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;

View 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;

View 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;

View 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;

View 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;

File diff suppressed because it is too large Load Diff

View 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;

View 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;

View 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='&copy; <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;

View 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;

View 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;

View 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);

View 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;

View 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);

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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);

View 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;

View 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;