add note process (api) , user-management table | 330

main
alireza khaji 2 years ago
parent d92199f6b0
commit 3e3bea6402
  1. 1
      package.json
  2. 6
      public/icons/table/ReactTable4arrow-icon.svg
  3. 12
      public/icons/table/ReactTable4double-left-arrow copy.svg
  4. 12
      public/icons/table/ReactTable4double-left-arrow.svg
  5. 6
      public/icons/table/ReactTable4edit.svg
  6. 3
      public/icons/table/ReactTable4filter.svg
  7. 8
      public/icons/table/ReactTable4left-arrow.svg
  8. 8
      public/icons/table/ReactTable4map.svg
  9. 6
      public/icons/table/ReactTable4plus.svg
  10. 6
      public/icons/table/ReactTable4right-arrow.svg
  11. 10
      public/icons/table/ReactTable4search-normal copy.svg
  12. 10
      public/icons/table/ReactTable4search-normal.svg
  13. 14
      public/icons/table/ReactTable4settings.svg
  14. 6
      public/icons/table/ReactTable4zarbedar-icon copy.svg
  15. 6
      public/icons/table/ReactTable4zarbedar-icon.svg
  16. 6
      public/icons/table/ReactTable5zarbedar-icon.svg
  17. BIN
      public/icons/table/search.png
  18. 2
      src/App.js
  19. BIN
      src/assets/img/Rolling-1s-200px.gif
  20. 2
      src/components/AnswerSheet/index.js
  21. 245
      src/components/CalendarComponents/Calendar.js
  22. 203
      src/components/CalendarComponents/DatePicker.js
  23. 93
      src/components/CalendarComponents/DatePickerInput.js
  24. 399
      src/components/CalendarComponents/components/DaysList.js
  25. 205
      src/components/CalendarComponents/components/Header.js
  26. 74
      src/components/CalendarComponents/components/MonthSelector.js
  27. 99
      src/components/CalendarComponents/components/YearSelector.js
  28. 4
      src/components/CalendarComponents/components/index.js
  29. 3
      src/components/CalendarComponents/index.js
  30. 104
      src/components/CalendarComponents/shared/constants.js
  31. 69
      src/components/CalendarComponents/shared/generalUtils.js
  32. 10
      src/components/CalendarComponents/shared/hooks.js
  33. 44
      src/components/CalendarComponents/shared/keyboardNavigation.js
  34. 85
      src/components/CalendarComponents/shared/localeLanguages.js
  35. 61
      src/components/CalendarComponents/shared/localeUtils.js
  36. 33
      src/components/CalendarComponents/shared/sliderHelpers.js
  37. 48
      src/components/Search/index.js
  38. 37
      src/components/SimpleDatePicker/index.js
  39. 23
      src/components/Table/Layouts/Footer/components/ShowMoreRows.js
  40. 31
      src/components/Table/Layouts/Footer/index.js
  41. 185
      src/components/Table/Layouts/Main/components/Table.js
  42. 61
      src/components/Table/Layouts/Main/index.js
  43. 21
      src/components/Table/Layouts/Toolbar/components/GoToPage.js
  44. 61
      src/components/Table/Layouts/Toolbar/components/Pagination.js
  45. 60
      src/components/Table/Layouts/Toolbar/components/Settings.js
  46. 128
      src/components/Table/Layouts/Toolbar/components/Tabbar.js
  47. 21
      src/components/Table/Layouts/Toolbar/components/TableSearch.js
  48. 69
      src/components/Table/Layouts/Toolbar/index.js
  49. 14
      src/components/Table/components/Checkbox.js
  50. 15
      src/components/Table/components/ColumnSearch.js
  51. 98
      src/components/Table/components/Columns/components/Buttons.js
  52. 53
      src/components/Table/components/Columns/components/Dropdown.js
  53. 75
      src/components/Table/components/Columns/components/EditButtons.js
  54. 34
      src/components/Table/components/Columns/components/OrderLevel.js
  55. 35
      src/components/Table/components/Columns/components/OrderStatus.js
  56. 52
      src/components/Table/components/Columns/components/SelectFilter.js
  57. 39
      src/components/Table/components/Columns/components/SliderColumnFilter.js
  58. 95
      src/components/Table/components/Columns/index.js
  59. 57
      src/components/Table/components/RowDetail.js
  60. 213
      src/components/Table/index.js
  61. 4
      src/redux/actions/examQuestion.js
  62. 1
      src/redux/actions/index.js
  63. 8
      src/redux/actions/table.js
  64. 3
      src/redux/reducers/examQuestion.js
  65. 1
      src/redux/reducers/index.js
  66. 126
      src/redux/reducers/table.js
  67. 161
      src/view/addExam/views/addQuestionnaireExam.js
  68. 32
      src/view/admin/index.js
  69. 105
      src/view/user-management/index.js
  70. 202
      src/view/user-management/layouts/Editor.js
  71. 5
      yarn.lock

@ -30,6 +30,7 @@
"react-router-dom": "^6.2.1", "react-router-dom": "^6.2.1",
"react-scripts": "5.0.0", "react-scripts": "5.0.0",
"react-slideshow-image": "^3.7.0", "react-slideshow-image": "^3.7.0",
"react-table": "^7.8.0",
"react-toastify": "^8.2.0", "react-toastify": "^8.2.0",
"redux": "^4.1.2", "redux": "^4.1.2",
"redux-devtools-extension": "^2.13.9", "redux-devtools-extension": "^2.13.9",

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 15 15">
<g id="arrow-down" transform="translate(0)">
<path id="Vector" d="M9.9,0,5.825,4.075a1.241,1.241,0,0,1-1.75,0L0,0" transform="translate(2.55 5.594)" fill="none" stroke="rgba(0,37,58,0.32)" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/>
<path id="Vector-2" data-name="Vector" d="M0,0H15V15H0Z" transform="translate(15 15) rotate(180)" fill="none" opacity="0"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 489 B

@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" width="15.988" height="11.988" viewBox="0 0 15.988 11.988">
<g id="vuesax_linear_arrow-right" data-name="vuesax/linear/arrow-right" transform="translate(15.988 11.988) rotate(180)">
<g id="arrow-right" transform="translate(4)">
<path id="Vector" d="M0,7.912,3.257,4.655a.992.992,0,0,0,0-1.4L0,0" transform="translate(4.451 2.038)" fill="none" stroke="#65a9ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
<path id="Vector-2" data-name="Vector" d="M0,0H11.988V11.988H0Z" transform="translate(11.988 11.988) rotate(180)" fill="none" opacity="0"/>
</g>
<g id="arrow-right-2" data-name="arrow-right">
<path id="Vector-3" data-name="Vector" d="M0,7.912,3.257,4.655a.992.992,0,0,0,0-1.4L0,0" transform="translate(4.451 2.038)" fill="none" stroke="#65a9ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
<path id="Vector-4" data-name="Vector" d="M0,0H11.988V11.988H0Z" transform="translate(11.988 11.988) rotate(180)" fill="none" opacity="0"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" width="15.988" height="11.988" viewBox="0 0 15.988 11.988">
<g id="vuesax_linear_arrow-right" data-name="vuesax/linear/arrow-right" transform="translate(15.988 11.988) rotate(180)">
<g id="arrow-right" transform="translate(4)">
<path id="Vector" d="M0,7.912,3.257,4.655a.992.992,0,0,0,0-1.4L0,0" transform="translate(4.451 2.038)" fill="none" stroke="#65a9ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
<path id="Vector-2" data-name="Vector" d="M0,0H11.988V11.988H0Z" transform="translate(11.988 11.988) rotate(180)" fill="none" opacity="0"/>
</g>
<g id="arrow-right-2" data-name="arrow-right">
<path id="Vector-3" data-name="Vector" d="M0,7.912,3.257,4.655a.992.992,0,0,0,0-1.4L0,0" transform="translate(4.451 2.038)" fill="none" stroke="#65a9ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
<path id="Vector-4" data-name="Vector" d="M0,0H11.988V11.988H0Z" transform="translate(11.988 11.988) rotate(180)" fill="none" opacity="0"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16.715" height="16.703" viewBox="0 0 16.715 16.703">
<g id="edit" transform="translate(-626.295 -252.999)">
<path id="Vector" d="M8.991,1.271l-7.88,7.88a2.712,2.712,0,0,0-.66,1.32l-.43,3.01a1.424,1.424,0,0,0,1.7,1.7l3.01-.43a2.8,2.8,0,0,0,1.32-.66l7.88-7.88c1.36-1.36,2-2.94,0-4.94S10.351-.089,8.991,1.271Z" transform="translate(627.049 253.749)" fill="none" stroke="#00253a" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/>
<path id="Vector-2" data-name="Vector" d="M0,0A7.144,7.144,0,0,0,4.94,4.94" transform="translate(634.91 256.15)" fill="none" stroke="#00253a" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 725 B

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="19.631" height="22.407" viewBox="0 0 19.631 22.407">
<path id="Vector" d="M2.105,0h13.92a2.107,2.107,0,0,1,2.105,2.105V4.428A3.933,3.933,0,0,1,17.078,6.86L13.613,9.924l-1.072.947A3.417,3.417,0,0,0,11.488,13.3v4.537a2.367,2.367,0,0,1-.944,1.8l-1.47.926a2.145,2.145,0,0,1-3.267-1.8v-5.59a3.962,3.962,0,0,0-.853-2.214L.944,6.733A3.43,3.43,0,0,1,0,4.628V2.214A2.133,2.133,0,0,1,2.105,0Z" transform="translate(0.75 0.75)" fill="none" stroke="#65a9ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 570 B

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="11.988" height="11.988" viewBox="0 0 11.988 11.988">
<g id="vuesax_linear_arrow-right" data-name="vuesax/linear/arrow-right" transform="translate(11.988 11.988) rotate(180)">
<g id="arrow-right">
<path id="Vector" d="M0,7.912,3.257,4.655a.992.992,0,0,0,0-1.4L0,0" transform="translate(4.451 2.038)" fill="none" stroke="#65a9ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
<path id="Vector-2" data-name="Vector" d="M0,0H11.988V11.988H0Z" transform="translate(11.988 11.988) rotate(180)" fill="none" opacity="0"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 622 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 378 KiB

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="11.72" height="11.72" viewBox="0 0 11.72 11.72">
<g id="Group_1916" data-name="Group 1916" transform="translate(-34.5 -316.547)">
<path id="Vector" d="M0,0H9.72" transform="translate(35.5 322.407)" fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
<path id="Vector-2" data-name="Vector" d="M0,9.72V0" transform="translate(40.36 317.547)" fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 543 B

@ -0,0 +1,6 @@
<svg id="vuesax_linear_arrow-right" data-name="vuesax/linear/arrow-right" xmlns="http://www.w3.org/2000/svg" width="11.988" height="11.988" viewBox="0 0 11.988 11.988">
<g id="arrow-right">
<path id="Vector" d="M0,7.912,3.257,4.655a.992.992,0,0,0,0-1.4L0,0" transform="translate(4.451 2.038)" fill="none" stroke="#65a9ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
<path id="Vector-2" data-name="Vector" d="M0,0H11.988V11.988H0Z" transform="translate(11.988 11.988) rotate(180)" fill="none" opacity="0"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 552 B

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<g id="search-normal" transform="translate(-428 -188)">
<g id="Vector" transform="translate(430 190)" fill="none">
<path d="M19,9.5A9.5,9.5,0,1,1,9.5,0,9.5,9.5,0,0,1,19,9.5Z" stroke="none"/>
<path d="M 9.5 1.5 C 5.088789939880371 1.5 1.5 5.088789939880371 1.5 9.5 C 1.5 13.91121006011963 5.088789939880371 17.5 9.5 17.5 C 13.91121006011963 17.5 17.5 13.91121006011963 17.5 9.5 C 17.5 5.088789939880371 13.91121006011963 1.5 9.5 1.5 M 9.5 0 C 14.74670028686523 0 19 4.253290176391602 19 9.5 C 19 14.74670028686523 14.74670028686523 19 9.5 19 C 4.253290176391602 19 0 14.74670028686523 0 9.5 C 0 4.253290176391602 4.253290176391602 0 9.5 0 Z" stroke="none" fill="#65a9ff"/>
</g>
<path id="Vector-2" data-name="Vector" d="M2.552,3.252a.7.7,0,0,1-.49-.2L.2,1.192A.706.706,0,0,1,.2.2a.706.706,0,0,1,.99,0l1.86,1.86a.706.706,0,0,1,0,.99A.738.738,0,0,1,2.552,3.252Z" transform="translate(446.747 206.747)" fill="#65a9ff"/>
<path id="Vector-3" data-name="Vector" d="M0,0H24V24H0Z" transform="translate(452 212) rotate(180)" fill="none" opacity="0"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<g id="search-normal" transform="translate(-428 -188)">
<g id="Vector" transform="translate(430 190)" fill="none">
<path d="M19,9.5A9.5,9.5,0,1,1,9.5,0,9.5,9.5,0,0,1,19,9.5Z" stroke="none"/>
<path d="M 9.5 1.5 C 5.088789939880371 1.5 1.5 5.088789939880371 1.5 9.5 C 1.5 13.91121006011963 5.088789939880371 17.5 9.5 17.5 C 13.91121006011963 17.5 17.5 13.91121006011963 17.5 9.5 C 17.5 5.088789939880371 13.91121006011963 1.5 9.5 1.5 M 9.5 0 C 14.74670028686523 0 19 4.253290176391602 19 9.5 C 19 14.74670028686523 14.74670028686523 19 9.5 19 C 4.253290176391602 19 0 14.74670028686523 0 9.5 C 0 4.253290176391602 4.253290176391602 0 9.5 0 Z" stroke="none" fill="#65a9ff"/>
</g>
<path id="Vector-2" data-name="Vector" d="M2.552,3.252a.7.7,0,0,1-.49-.2L.2,1.192A.706.706,0,0,1,.2.2a.706.706,0,0,1,.99,0l1.86,1.86a.706.706,0,0,1,0,.99A.738.738,0,0,1,2.552,3.252Z" transform="translate(446.747 206.747)" fill="#65a9ff"/>
<path id="Vector-3" data-name="Vector" d="M0,0H24V24H0Z" transform="translate(452 212) rotate(180)" fill="none" opacity="0"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.7 KiB

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="5.397" height="5.397" viewBox="0 0 5.397 5.397">
<g id="Group_1887" data-name="Group 1887" transform="translate(0.707 0.707)">
<path id="Vector" d="M3.943,3.943,0,0" transform="translate(0.04)" fill="none" stroke="#65a9ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path id="Vector-2" data-name="Vector" d="M3.943,0,0,3.943" transform="translate(0 0.04)" fill="none" stroke="#65a9ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 545 B

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="5.397" height="5.397" viewBox="0 0 5.397 5.397">
<g id="Group_1887" data-name="Group 1887" transform="translate(0.707 0.707)">
<path id="Vector" d="M3.943,3.943,0,0" transform="translate(0.04)" fill="none" stroke="#65a9ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path id="Vector-2" data-name="Vector" d="M3.943,0,0,3.943" transform="translate(0 0.04)" fill="none" stroke="#65a9ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 545 B

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="5.397" height="5.397" viewBox="0 0 5.397 5.397">
<g id="Group_1887" data-name="Group 1887" transform="translate(0.707 0.707)">
<path id="Vector" d="M3.943,3.943,0,0" transform="translate(0.04)" fill="none" stroke="#65a9ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path id="Vector-2" data-name="Vector" d="M3.943,0,0,3.943" transform="translate(0 0.04)" fill="none" stroke="#65a9ff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 545 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 857 B

@ -44,6 +44,7 @@ import ShoppingCart from "./components/ShoppingCart";
import MyServices from "./view/my-services/MyServices"; import MyServices from "./view/my-services/MyServices";
import MobileMyServices from "./view/my-services/mobile/MyServices"; import MobileMyServices from "./view/my-services/mobile/MyServices";
import Exam from "./view/Exam2/Exam"; import Exam from "./view/Exam2/Exam";
import UserManagement from "./view/user-management";
let data = [ let data = [
{ {
@ -741,6 +742,7 @@ function App() {
element={<AddProduct />} element={<AddProduct />}
/> />
<Route path="/adminpanel/add-exam" element={<AddExam />} /> <Route path="/adminpanel/add-exam" element={<AddExam />} />
<Route path="/adminpanel/user-management" element={<UserManagement />} />
<Route <Route
path="/register" path="/register"

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

@ -7,7 +7,7 @@ const AnswerSheet = ({ questionStart, questionEnd, optionsQuantity, selectedCour
useEffect(() => { useEffect(() => {
let array = []; let array = [];
let optionsArray = []; let optionsArray = [];
for (var i = questionStart; i < questionEnd; i++) { for (var i = questionStart; i <= questionEnd; i++) {
array.push({ array.push({
questionId:i, questionId:i,
answer: null, answer: null,

@ -0,0 +1,245 @@
import React, { useState, useRef, useEffect } from "react";
import {
getDateAccordingToMonth,
shallowClone,
getValueType,
} from "./shared/generalUtils";
import {
TYPE_SINGLE_DATE,
TYPE_RANGE,
TYPE_MUTLI_DATE,
} from "./shared/constants";
import { useLocaleUtils, useLocaleLanguage } from "./shared/hooks";
import { Header, MonthSelector, YearSelector, DaysList } from "./components";
const Calendar = ({
value,
onChange,
onDisabledDayError,
calendarClassName,
calendarTodayClassName,
calendarSelectedDayClassName,
calendarRangeStartClassName,
calendarRangeBetweenClassName,
calendarRangeEndClassName,
disabledDays,
colorPrimary,
colorPrimaryLight,
slideAnimationDuration,
minimumDate,
maximumDate,
selectorStartingYear,
selectorEndingYear,
locale,
shouldHighlightWeekends,
renderFooter,
customDaysClassName,
events,
renderEvents,
type,
}) => {
const calendarElement = useRef(null);
const [mainState, setMainState] = useState({
activeDate: null,
monthChangeDirection: "",
isMonthSelectorOpen: false,
isYearSelectorOpen: false,
});
const [eventInFooter, setEventInFooter] = useState(null);
useEffect(() => {
const handleKeyUp = ({ key }) => {
/* istanbul ignore else */
if (key === "Tab")
calendarElement.current.classList.remove("-noFocusOutline");
};
calendarElement.current.addEventListener("keyup", handleKeyUp, false);
return () => {
// calendarElement.current.removeEventListener('keyup', handleKeyUp, false);
};
});
const { getToday } = useLocaleUtils(locale);
const { weekDays: weekDaysList, isRtl } = useLocaleLanguage(locale);
const today = getToday();
const createStateToggler = (property) => () => {
setMainState({ ...mainState, [property]: !mainState[property] });
};
const toggleMonthSelector = createStateToggler("isMonthSelectorOpen");
const toggleYearSelector = createStateToggler("isYearSelectorOpen");
const getComputedActiveDate = () => {
const valueType = getValueType(value);
if (valueType === TYPE_MUTLI_DATE && value.length)
return shallowClone(value[0]);
if (valueType === TYPE_SINGLE_DATE && value) return shallowClone(value);
if (valueType === TYPE_RANGE && value.from) return shallowClone(value.from);
return shallowClone(today);
};
const activeDate = mainState.activeDate
? shallowClone(mainState.activeDate)
: getComputedActiveDate();
const weekdays = weekDaysList.map((weekDay) => (
<abbr
key={weekDay.name}
title={weekDay.name}
className={`Calendar__weekDay ${type}`}
>
{type == "dashboard" ? weekDay.name : weekDay.short}
</abbr>
));
const handleMonthChange = (direction) => {
setMainState({
...mainState,
monthChangeDirection: direction,
});
};
const updateDate = () => {
setMainState({
...mainState,
activeDate: getDateAccordingToMonth(
activeDate,
mainState.monthChangeDirection
),
monthChangeDirection: "",
});
};
const selectMonth = (newMonthNumber) => {
setMainState({
...mainState,
activeDate: { ...activeDate, month: newMonthNumber },
isMonthSelectorOpen: false,
});
};
const selectYear = (year) => {
setMainState({
...mainState,
activeDate: { ...activeDate, year },
isYearSelectorOpen: false,
});
};
return (
<div
className={`Calendar ${type} -noFocusOutline ${calendarClassName} -${
isRtl ? "rtl" : "ltr"
}`}
role="grid"
style={{
"--cl-color-primary": colorPrimary,
"--cl-color-primary-light": colorPrimaryLight,
"--animation-duration": slideAnimationDuration,
}}
ref={calendarElement}
>
<Header
maximumDate={maximumDate}
minimumDate={minimumDate}
activeDate={activeDate}
onMonthChange={handleMonthChange}
onMonthSelect={toggleMonthSelector}
onYearSelect={toggleYearSelector}
monthChangeDirection={mainState.monthChangeDirection}
isMonthSelectorOpen={mainState.isMonthSelectorOpen}
isYearSelectorOpen={mainState.isYearSelectorOpen}
locale={locale}
type={type}
/>
<MonthSelector
isOpen={mainState.isMonthSelectorOpen}
activeDate={activeDate}
onMonthSelect={selectMonth}
maximumDate={maximumDate}
minimumDate={minimumDate}
locale={locale}
/>
<YearSelector
isOpen={mainState.isYearSelectorOpen}
activeDate={activeDate}
onYearSelect={selectYear}
selectorStartingYear={selectorStartingYear}
selectorEndingYear={selectorEndingYear}
maximumDate={maximumDate}
minimumDate={minimumDate}
locale={locale}
/>
<div className={`Calendar__weekDays ${type}`}>{weekdays}</div>
<DaysList
activeDate={activeDate}
value={value}
monthChangeDirection={mainState.monthChangeDirection}
onSlideChange={updateDate}
disabledDays={disabledDays}
onDisabledDayError={onDisabledDayError}
minimumDate={minimumDate}
maximumDate={maximumDate}
onChange={onChange}
calendarTodayClassName={calendarTodayClassName}
calendarSelectedDayClassName={calendarSelectedDayClassName}
calendarRangeStartClassName={calendarRangeStartClassName}
calendarRangeEndClassName={calendarRangeEndClassName}
calendarRangeBetweenClassName={calendarRangeBetweenClassName}
locale={locale}
shouldHighlightWeekends={shouldHighlightWeekends}
customDaysClassName={customDaysClassName}
events={events}
renderFooter={renderFooter}
renderEvents={renderEvents}
isQuickSelectorOpen={
mainState.isYearSelectorOpen || mainState.isMonthSelectorOpen
}
showEvent={(event) => setEventInFooter(event)}
type={type}
/>
<div className="Calendar__footer">
<div className={`footer__Events ${type}`}>
{type == "dashboard" ? (
<>
<div className="text-xl font-black text-blue-400">
رویداد ها:{" "}
</div>
<p className="text-blue-300 font-sansbold">
{eventInFooter
? eventInFooter
: "رویدادی در این روز وجود ندارد!"}
</p>
</>
) : (
eventInFooter
)}
</div>
{renderFooter()}
</div>
</div>
);
};
Calendar.defaultProps = {
minimumDate: null,
maximumDate: null,
colorPrimary: "#0eca2d",
colorPrimaryLight: "#cff4d5",
slideAnimationDuration: "0.4s",
calendarClassName: "",
locale: "en",
value: null,
renderFooter: () => null,
renderEvents: () => null,
customDaysClassName: [],
events: [],
};
export default Calendar;

@ -0,0 +1,203 @@
import React, { useState, useEffect, useRef, useLayoutEffect } from 'react';
import Calendar from './Calendar';
import DatePickerInput from './DatePickerInput';
import { getValueType } from './shared/generalUtils';
import { TYPE_SINGLE_DATE, TYPE_MUTLI_DATE, TYPE_RANGE } from './shared/constants';
const DatePicker = ({
value,
onChange,
formatInputText,
inputPlaceholder,
inputClassName,
inputName,
renderInput,
wrapperClassName,
calendarClassName,
calendarTodayClassName,
calendarSelectedDayClassName,
calendarRangeStartClassName,
calendarRangeBetweenClassName,
calendarRangeEndClassName,
calendarPopperPosition,
disabledDays,
onDisabledDayError,
colorPrimary,
colorPrimaryLight,
slideAnimationDuration,
minimumDate,
maximumDate,
selectorStartingYear,
selectorEndingYear,
locale,
shouldHighlightWeekends,
renderFooter,
customDaysClassName,
}) => {
const calendarContainerElement = useRef(null);
const inputElement = useRef(null);
const shouldPreventToggle = useRef(false);
const [isCalendarOpen, setCalendarVisiblity] = useState(false);
useEffect(() => {
const handleBlur = () => {
setCalendarVisiblity(false);
};
window.addEventListener('blur', handleBlur, false);
return () => {
window.removeEventListener('blur', handleBlur, false);
};
}, []);
// handle input focus/blur
useEffect(() => {
const valueType = getValueType(value);
if (valueType === TYPE_MUTLI_DATE) return; // no need to close the calendar
const shouldCloseCalendar =
valueType === TYPE_SINGLE_DATE ? !isCalendarOpen : !isCalendarOpen && value.from && value.to;
if (shouldCloseCalendar) inputElement.current.blur();
}, [value, isCalendarOpen]);
const handleBlur = e => {
e.persist();
if (!isCalendarOpen) return;
const isInnerElementFocused = calendarContainerElement.current.contains(e.relatedTarget);
if (shouldPreventToggle.current) {
shouldPreventToggle.current = false;
inputElement.current.focus();
} else if (isInnerElementFocused && e.relatedTarget) {
e.relatedTarget.focus();
} else {
setCalendarVisiblity(false);
}
};
const openCalendar = () => {
if (!shouldPreventToggle.current) setCalendarVisiblity(true);
};
// Keep the calendar in the screen bounds if input is near the window edges
useLayoutEffect(() => {
if (!isCalendarOpen) return;
const { left, width, height, top } = calendarContainerElement.current.getBoundingClientRect();
const { clientWidth, clientHeight } = document.documentElement;
const isOverflowingFromRight = left + width > clientWidth;
const isOverflowingFromLeft = left < 0;
const isOverflowingFromBottom = top + height > clientHeight;
const getLeftStyle = () => {
const overflowFromRightDistance = left + width - clientWidth;
if (!isOverflowingFromRight && !isOverflowingFromLeft) return;
const overflowFromLeftDistance = Math.abs(left);
const rightPosition = isOverflowingFromLeft ? overflowFromLeftDistance : 0;
const leftStyle = isOverflowingFromRight
? `calc(50% - ${overflowFromRightDistance}px)`
: `calc(50% + ${rightPosition}px)`;
return leftStyle;
};
calendarContainerElement.current.style.left = getLeftStyle();
if (
(calendarPopperPosition === 'auto' && isOverflowingFromBottom) ||
calendarPopperPosition === 'top'
) {
calendarContainerElement.current.classList.add('-top');
}
}, [isCalendarOpen]);
const handleCalendarChange = newValue => {
const valueType = getValueType(value);
onChange(newValue);
if (valueType === TYPE_SINGLE_DATE) setCalendarVisiblity(false);
else if (valueType === TYPE_RANGE && newValue.from && newValue.to) setCalendarVisiblity(false);
};
const handleKeyUp = ({ key }) => {
switch (key) {
case 'Enter':
setCalendarVisiblity(true);
break;
case 'Escape':
setCalendarVisiblity(false);
shouldPreventToggle.current = true;
break;
}
};
useEffect(() => {
if (!isCalendarOpen && shouldPreventToggle.current) {
inputElement.current.focus();
shouldPreventToggle.current = false;
}
}, [shouldPreventToggle, isCalendarOpen]);
return (
<div
onFocus={openCalendar}
onBlur={handleBlur}
onKeyUp={handleKeyUp}
className={`DatePicker ${wrapperClassName}`}
role="presentation"
>
<DatePickerInput
ref={inputElement}
formatInputText={formatInputText}
value={value}
inputPlaceholder={inputPlaceholder}
inputClassName={inputClassName}
renderInput={renderInput}
inputName={inputName}
locale={locale}
/>
{isCalendarOpen && (
<>
<div
ref={calendarContainerElement}
className="DatePicker__calendarContainer"
data-testid="calendar-container"
role="presentation"
onMouseDown={() => {
shouldPreventToggle.current = true;
}}
>
<Calendar
value={value}
onChange={handleCalendarChange}
calendarClassName={calendarClassName}
calendarTodayClassName={calendarTodayClassName}
calendarSelectedDayClassName={calendarSelectedDayClassName}
calendarRangeStartClassName={calendarRangeStartClassName}
calendarRangeBetweenClassName={calendarRangeBetweenClassName}
calendarRangeEndClassName={calendarRangeEndClassName}
disabledDays={disabledDays}
colorPrimary={colorPrimary}
colorPrimaryLight={colorPrimaryLight}
slideAnimationDuration={slideAnimationDuration}
onDisabledDayError={onDisabledDayError}
minimumDate={minimumDate}
maximumDate={maximumDate}
selectorStartingYear={selectorStartingYear}
selectorEndingYear={selectorEndingYear}
locale={locale}
shouldHighlightWeekends={shouldHighlightWeekends}
renderFooter={renderFooter}
customDaysClassName={customDaysClassName}
/>
</div>
<div className="DatePicker__calendarArrow" />
</>
)}
</div>
);
};
DatePicker.defaultProps = {
wrapperClassName: '',
locale: 'en',
calendarPopperPosition: 'auto',
};
export default DatePicker;

@ -0,0 +1,93 @@
import React from 'react';
import { useLocaleUtils, useLocaleLanguage } from './shared/hooks';
import { putZero, getValueType } from './shared/generalUtils';
import { TYPE_SINGLE_DATE, TYPE_RANGE, TYPE_MUTLI_DATE } from './shared/constants';
const DatePickerInput = React.forwardRef(
(
{ value, inputPlaceholder, inputClassName, inputName, formatInputText, renderInput, locale },
ref,
) => {
const { getLanguageDigits } = useLocaleUtils(locale);
const {
from: fromWord,
to: toWord,
yearLetterSkip,
digitSeparator,
defaultPlaceholder,
isRtl,
} = useLocaleLanguage(locale);
const getSingleDayValue = () => {
if (!value) return '';
const year = getLanguageDigits(value.year);
const month = getLanguageDigits(putZero(value.month));
const day = getLanguageDigits(putZero(value.day));
return `${year}/${month}/${day}`;
};
const getDayRangeValue = () => {
if (!value.from || !value.to) return '';
const { from, to } = value;
const fromText = `${getLanguageDigits(putZero(from.year))
.toString()
.slice(yearLetterSkip)}/${getLanguageDigits(putZero(from.month))}/${getLanguageDigits(
putZero(from.day),
)}`;
const toText = `${getLanguageDigits(putZero(to.year))
.toString()
.slice(yearLetterSkip)}/${getLanguageDigits(putZero(to.month))}/${getLanguageDigits(
putZero(to.day),
)}`;
return `${fromWord} ${fromText} ${toWord} ${toText}`;
};
const getMultiDateValue = () => {
return value.map(date => getLanguageDigits(date.day)).join(`${digitSeparator} `);
};
const getValue = () => {
if (formatInputText()) return formatInputText();
const valueType = getValueType(value);
switch (valueType) {
case TYPE_SINGLE_DATE:
return getSingleDayValue();
case TYPE_RANGE:
return getDayRangeValue();
case TYPE_MUTLI_DATE:
return getMultiDateValue();
}
};
const placeholderValue = inputPlaceholder || defaultPlaceholder;
const render = () => {
return (
renderInput({ ref }) || (
<input
data-testid="datepicker-input"
readOnly
ref={ref}
value={getValue()}
name={inputName}
placeholder={placeholderValue}
className={`DatePicker__input -${isRtl ? 'rtl' : 'ltr'} ${inputClassName}`}
aria-label={placeholderValue}
/>
)
);
};
return render();
},
);
DatePickerInput.defaultProps = {
formatInputText: () => '',
renderInput: () => null,
inputPlaceholder: '',
inputClassName: '',
inputName: '',
};
export default DatePickerInput;

@ -0,0 +1,399 @@
import React, { useRef, useEffect } from 'react';
import {
getSlideDate,
handleSlideAnimationEnd,
animateContent,
} from '../shared/sliderHelpers';
import {
deepCloneObject,
isSameDay,
createUniqueRange,
getValueType,
} from '../shared/generalUtils';
import {
TYPE_SINGLE_DATE,
TYPE_RANGE,
TYPE_MUTLI_DATE,
} from '../shared/constants';
import handleKeyboardNavigation from '../shared/keyboardNavigation';
import { useLocaleUtils, useLocaleLanguage } from '../shared/hooks';
const DaysList = ({
activeDate,
value,
monthChangeDirection,
onSlideChange,
disabledDays,
onDisabledDayError,
minimumDate,
maximumDate,
onChange,
locale,
calendarTodayClassName,
calendarSelectedDayClassName,
calendarRangeStartClassName,
calendarRangeEndClassName,
calendarRangeBetweenClassName,
shouldHighlightWeekends,
isQuickSelectorOpen,
customDaysClassName,
events,
showEvent,
type
}) => {
const calendarSectionWrapper = useRef(null);
const { isRtl, weekDays: weekDaysList } = useLocaleLanguage(locale);
const {
getToday,
isBeforeDate,
checkDayInDayRange,
getMonthFirstWeekday,
getMonthLength,
getLanguageDigits,
getMonthName,
} = useLocaleUtils(locale);
const today = getToday();
useEffect(() => {
if (!monthChangeDirection) return;
animateContent({
direction: monthChangeDirection,
parent: calendarSectionWrapper.current,
});
}, [monthChangeDirection]);
const getDayRangeValue = (day) => {
const clonedDayRange = deepCloneObject(value);
const dayRangeValue =
clonedDayRange.from && clonedDayRange.to
? { from: null, to: null }
: clonedDayRange;
const dayRangeProp = !dayRangeValue.from ? 'from' : 'to';
dayRangeValue[dayRangeProp] = day;
const { from, to } = dayRangeValue;
// swap from and to values if from is later than to
if (isBeforeDate(dayRangeValue.to, dayRangeValue.from)) {
dayRangeValue.from = to;
dayRangeValue.to = from;
}
const checkIncludingDisabledDay = (disabledDay) => {
return checkDayInDayRange({
day: disabledDay,
from: dayRangeValue.from,
to: dayRangeValue.to,
});
};
const includingDisabledDay = disabledDays.find(checkIncludingDisabledDay);
if (includingDisabledDay) {
onDisabledDayError(includingDisabledDay);
return value;
}
return dayRangeValue;
};
const getMultiDateValue = (day) => {
const isAlreadyExisting = value.some((valueDay) =>
isSameDay(valueDay, day)
);
const addedToValue = [...value, day];
const removedFromValue = value.filter(
(valueDay) => !isSameDay(valueDay, day)
);
return isAlreadyExisting ? removedFromValue : addedToValue;
};
const handleDayClick = (day) => {
const getNewValue = () => {
const valueType = getValueType(value);
switch (valueType) {
case TYPE_SINGLE_DATE:
return day;
case TYPE_RANGE:
return getDayRangeValue(day);
case TYPE_MUTLI_DATE:
return getMultiDateValue(day);
}
};
const newValue = getNewValue();
onChange(newValue);
};
const isSingleDateSelected = (day) => {
const valueType = getValueType(value);
if (valueType === TYPE_SINGLE_DATE) return isSameDay(day, value);
if (valueType === TYPE_MUTLI_DATE)
return value.some((valueDay) => isSameDay(valueDay, day));
};
const getDayStatus = (dayItem) => {
const isToday = isSameDay(dayItem, today);
const isSelected = isSingleDateSelected(dayItem);
const { from: startingDay, to: endingDay } = value || {};
const isStartingDayRange = isSameDay(dayItem, startingDay);
const isEndingDayRange = isSameDay(dayItem, endingDay);
const isWithinRange = checkDayInDayRange({
day: dayItem,
from: startingDay,
to: endingDay,
});
return {
isToday,
isSelected,
isStartingDayRange,
isEndingDayRange,
isWithinRange,
};
};
const getDayClassNames = (dayItem) => {
const {
isToday,
isSelected,
isStartingDayRange,
isEndingDayRange,
isWithinRange,
} = getDayStatus(dayItem);
const customDayItemClassName = customDaysClassName.find((day) =>
isSameDay(dayItem, day)
);
const classNames = ''
.concat(isToday && !isSelected ? ` -today ${calendarTodayClassName}` : '')
.concat(!dayItem.isStandard ? ' -blank' : '')
.concat(dayItem.isWeekend && shouldHighlightWeekends ? ' -weekend' : '')
.concat(
customDayItemClassName ? ` ${customDayItemClassName.className}` : ''
)
.concat(isSelected ? ` -selected ${calendarSelectedDayClassName}` : '')
.concat(
isStartingDayRange
? ` -selectedStart ${calendarRangeStartClassName}`
: ''
)
.concat(
isEndingDayRange ? ` -selectedEnd ${calendarRangeEndClassName}` : ''
)
.concat(
isWithinRange
? ` -selectedBetween ${calendarRangeBetweenClassName}`
: ''
)
.concat(dayItem.isDisabled ? ' -disabled' : '');
return classNames;
};
const getViewMonthDays = (date) => {
// to match month starting date with the correct weekday label
const prependingBlankDays = createUniqueRange(
getMonthFirstWeekday(date),
'starting-blank'
);
const standardDays = createUniqueRange(getMonthLength(date)).map((day) => ({
...day,
isStandard: true,
month: date.month,
year: date.year,
}));
const allDays = [...prependingBlankDays, ...standardDays];
return allDays;
};
const handleDayPress = ({ isDisabled, ...dayItem }) => {
if (isDisabled) {
onDisabledDayError(dayItem); // good for showing error messages
} else handleDayClick(dayItem);
};
const isDayReachableByKeyboard = ({
isOnActiveSlide,
isStandard,
isSelected,
isStartingDayRange,
isToday,
day,
}) => {
if (isQuickSelectorOpen || !isOnActiveSlide || !isStandard) return false;
if (isSelected || isStartingDayRange || isToday || day === 1) return true;
};
const renderEachWeekDays = (
{ id, value: day, month, year, isStandard },
index
) => {
const dayItem = { day, month, year };
const isInDisabledDaysRange = disabledDays.some((disabledDay) =>
isSameDay(dayItem, disabledDay)
);
const isBeforeMinimumDate = isBeforeDate(dayItem, minimumDate);
const isAfterMaximumDate = isBeforeDate(maximumDate, dayItem);
const isNotInValidRange =
isStandard && (isBeforeMinimumDate || isAfterMaximumDate);
const isDisabled = isInDisabledDaysRange || isNotInValidRange;
const isWeekend = weekDaysList.some(
(weekDayItem, weekDayItemIndex) =>
weekDayItem.isWeekend && weekDayItemIndex === index
);
const additionalClass = getDayClassNames({
...dayItem,
isWeekend,
isStandard,
isDisabled,
});
const dayLabel = `${weekDaysList[index].name}, ${day} ${getMonthName(
month
)} ${year}`;
const isOnActiveSlide = month === activeDate.month;
const dayStatus = getDayStatus(dayItem);
const { isSelected, isStartingDayRange, isEndingDayRange, isWithinRange } =
dayStatus;
const shouldEnableKeyboardNavigation = isDayReachableByKeyboard({
...dayItem,
...dayStatus,
isOnActiveSlide,
isStandard,
});
// showing Dots Of Events
const specialDate = (sDay, sMonth, sYear) => {
return events.map((e) => {
if (sDay == e.day && sMonth == e.month && sYear == e.year) {
return <span key={'dot' + e.id} style={{
color: e.color
}}>.</span>;
}
})
};
const showEventDescription = (sDay, sMonth, sYear) => {
return events.map((e) => {
if (sDay == e.day && sMonth == e.month && sYear == e.year) {
return (<div key={'description' + e.id}><p>{e.description}</p></div>)
}
})
}
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
tabIndex={shouldEnableKeyboardNavigation ? '0' : '-1'}
key={id}
className={`Calendar__day ${type} -${isRtl ? 'rtl' : 'ltr'} ${additionalClass}`}
onClick={() => {
handleDayPress({ ...dayItem, isDisabled });
// console.log(specialDate(day));
// console.log(showEventDescription(day))
showEvent(showEventDescription(day, month, year))
}}
onKeyDown={({ key }) => {
/* istanbul ignore else */
if (key === 'Enter') handleDayPress({ ...dayItem, isDisabled });
}}
aria-disabled={isDisabled}
aria-label={dayLabel}
aria-selected={
isSelected || isStartingDayRange || isEndingDayRange || isWithinRange
}
{...(!isStandard || !isOnActiveSlide || isQuickSelectorOpen
? { 'aria-hidden': true }
: {})}
role="gridcell"
data-is-default-selectable={shouldEnableKeyboardNavigation}
>
<p>{!isStandard ? '' : getLanguageDigits(day)}</p>
{/* Dots Of Events */}
<span>{specialDate(day, month, year)}</span>
</div>
);
};
const renderMonthDays = (isInitialActiveChild) => {
const date = getSlideDate({
activeDate,
isInitialActiveChild,
monthChangeDirection,
parent: calendarSectionWrapper.current,
});
const allDays = getViewMonthDays(date);
const renderSingleWeekRow = (weekRowIndex) => {
const eachWeekDays = allDays
.slice(weekRowIndex * 7, weekRowIndex * 7 + 7)
.map(renderEachWeekDays);
return (
<div
key={String(weekRowIndex)}
className="Calendar__weekRow"
role="row"
>
{eachWeekDays}
</div>
);
};
return Array.from(Array(6).keys()).map(renderSingleWeekRow);
};
const handleKeyDown = (e) => {
handleKeyboardNavigation(e, { allowVerticalArrows: true });
};
return (
<div
ref={calendarSectionWrapper}
className="Calendar__sectionWrapper"
role="presentation"
data-testid="days-section-wrapper"
onKeyDown={handleKeyDown}
>
<div
onAnimationEnd={(e) => {
handleSlideAnimationEnd(e);
onSlideChange();
}}
className="Calendar__section -shown"
role="rowgroup"
>
{renderMonthDays(true)}
</div>
<div
onAnimationEnd={(e) => {
handleSlideAnimationEnd(e);
onSlideChange();
}}
className="Calendar__section -hiddenNext"
role="rowgroup"
>
{renderMonthDays(false)}
</div>
</div>
);
};
DaysList.defaultProps = {
onChange: () => { },
onDisabledDayError: () => { },
disabledDays: [],
calendarTodayClassName: '',
calendarSelectedDayClassName: '',
calendarRangeStartClassName: '',
calendarRangeBetweenClassName: '',
calendarRangeEndClassName: '',
shouldHighlightWeekends: false,
};
export default DaysList;

@ -0,0 +1,205 @@
import React, { useEffect, useRef } from 'react';
import { isSameDay } from '../shared/generalUtils';
import { getSlideDate, animateContent, handleSlideAnimationEnd } from '../shared/sliderHelpers';
import { useLocaleUtils, useLocaleLanguage } from '../shared/hooks';
const Header = ({
maximumDate,
minimumDate,
onMonthChange,
activeDate,
monthChangeDirection,
onMonthSelect,
onYearSelect,
isMonthSelectorOpen,
isYearSelectorOpen,
locale,
type
}) => {
const headerElement = useRef(null);
const monthYearWrapperElement = useRef(null);
const { getMonthName, isBeforeDate, getLanguageDigits } = useLocaleUtils(locale);
const {
isRtl,
nextMonth,
previousMonth,
openMonthSelector,
closeMonthSelector,
openYearSelector,
closeYearSelector,
} = useLocaleLanguage(locale);
useEffect(() => {
if (!monthChangeDirection) return;
animateContent({
direction: monthChangeDirection,
parent: monthYearWrapperElement.current,
});
}, [monthChangeDirection]);
useEffect(() => {
const isOpen = isMonthSelectorOpen || isYearSelectorOpen;
const monthText = headerElement.current.querySelector(
'.Calendar__monthYear.-shown .Calendar__monthText',
);
const yearText = monthText.nextSibling;
const hasActiveBackground = element => element.classList.contains('-activeBackground');
const isInitialRender =
!isOpen && !hasActiveBackground(monthText) && !hasActiveBackground(yearText);
if (isInitialRender) return;
const arrows = [...headerElement.current.querySelectorAll('.Calendar__monthArrowWrapper')];
const hasMonthSelectorToggled = isMonthSelectorOpen || hasActiveBackground(monthText);
const primaryElement = hasMonthSelectorToggled ? monthText : yearText;
const secondaryElement = hasMonthSelectorToggled ? yearText : monthText;
let translateXDirection = hasMonthSelectorToggled ? 1 : -1;
if (isRtl) translateXDirection *= -1;
const scale = !isOpen ? 0.95 : 1;
const translateX = !isOpen ? 0 : `${(translateXDirection * secondaryElement.offsetWidth) / 2}`;
if (!isOpen) {
secondaryElement.removeAttribute('aria-hidden');
} else {
secondaryElement.setAttribute('aria-hidden', true);
}
secondaryElement.setAttribute('tabindex', isOpen ? '-1' : '0');
secondaryElement.style.transform = '';
primaryElement.style.transform = `scale(${scale}) ${
translateX ? `translateX(${translateX}px)` : ''
}`;
primaryElement.classList.toggle('-activeBackground');
secondaryElement.classList.toggle('-hidden');
arrows.forEach(arrow => {
const isHidden = arrow.classList.contains('-hidden');
arrow.classList.toggle('-hidden');
if (isHidden) {
arrow.removeAttribute('aria-hidden');
arrow.setAttribute('tabindex', '0');
} else {
arrow.setAttribute('aria-hidden', true);
arrow.setAttribute('tabindex', '-1');
}
});
}, [isMonthSelectorOpen, isYearSelectorOpen]);
const getMonthYearText = isInitialActiveChild => {
const date = getSlideDate({
isInitialActiveChild,
monthChangeDirection,
activeDate,
parent: monthYearWrapperElement.current,
});
const year = getLanguageDigits(date.year);
const month = getMonthName(date.month);
return { month, year };
};
const isNextMonthArrowDisabled =
maximumDate &&
isBeforeDate(maximumDate, { ...activeDate, month: activeDate.month + 1, day: 1 });
const isPreviousMonthArrowDisabled =
minimumDate &&
(isBeforeDate({ ...activeDate, day: 1 }, minimumDate) ||
isSameDay(minimumDate, { ...activeDate, day: 1 }));
const onMonthChangeTrigger = direction => {
const isMonthChanging = Array.from(monthYearWrapperElement.current.children).some(child =>
child.classList.contains('-shownAnimated'),
);
if (isMonthChanging) return;
onMonthChange(direction);
};
// first button text is the one who shows the current month and year(initial active child)
const monthYearButtons = [true, false].map(isInitialActiveChild => {
const { month, year } = getMonthYearText(isInitialActiveChild);
const isActiveMonth = month === getMonthName(activeDate.month);
const hiddenStatus = {
...(isActiveMonth ? {} : { 'aria-hidden': true }),
};
return (
<div
onAnimationEnd={handleSlideAnimationEnd}
className={`Calendar__monthYear ${isInitialActiveChild ? '-shown' : '-hiddenNext'}`}
role="presentation"
key={String(isInitialActiveChild)}
{...hiddenStatus}
>
<button
onClick={onMonthSelect}
type="button"
className="Calendar__monthText"
aria-label={isMonthSelectorOpen ? closeMonthSelector : openMonthSelector}
tabIndex={isActiveMonth ? '0' : '-1'}
{...hiddenStatus}
>
{month}
</button>
<button
onClick={onYearSelect}
type="button"
className="Calendar__yearText"
aria-label={isYearSelectorOpen ? closeYearSelector : openYearSelector}
tabIndex={isActiveMonth ? '0' : '-1'}
{...hiddenStatus}
>
{year}
</button>
</div>
);
});
return (
<div ref={headerElement} className={`Calendar__header ${type}`}>
{type == 'dashboard' ?
<div
className={`Calendar__monthYearContainer ${type}`}
ref={monthYearWrapperElement}
data-testid="month-year-container"
>
&nbsp;
{monthYearButtons}
</div>
:
<>
<button
className="Calendar__monthArrowWrapper -right"
onClick={() => {
onMonthChangeTrigger('PREVIOUS');
}}
aria-label={previousMonth}
type="button"
disabled={isPreviousMonthArrowDisabled}
>
<span className="Calendar__monthArrow" />
</button>
<div
className="Calendar__monthYearContainer"
ref={monthYearWrapperElement}
data-testid="month-year-container"
>
&nbsp;
{monthYearButtons}
</div>
<button
className="Calendar__monthArrowWrapper -left"
onClick={() => {
onMonthChangeTrigger('NEXT');
}}
aria-label={nextMonth}
type="button"
disabled={isNextMonthArrowDisabled}
>
<span className="Calendar__monthArrow" />
</button>
</>
}
</div>
);
};
export default Header;

@ -0,0 +1,74 @@
import React, { useRef, useEffect } from 'react';
import { isSameDay } from '../shared/generalUtils';
import handleKeyboardNavigation from '../shared/keyboardNavigation';
import { useLocaleUtils, useLocaleLanguage } from '../shared/hooks';
const MonthSelector = ({ activeDate, maximumDate, minimumDate, onMonthSelect, isOpen, locale }) => {
const monthSelector = useRef(null);
useEffect(() => {
const classToggleMethod = isOpen ? 'add' : 'remove';
monthSelector.current.classList[classToggleMethod]('-open');
}, [isOpen]);
const { getMonthNumber, isBeforeDate } = useLocaleUtils(locale);
const { months: monthsList } = useLocaleLanguage(locale);
const handleKeyDown = e => {
handleKeyboardNavigation(e, { allowVerticalArrows: false });
};
const renderMonthSelectorItems = () =>
monthsList.map(persianMonth => {
const monthNumber = getMonthNumber(persianMonth);
const monthDate = { day: 1, month: monthNumber, year: activeDate.year };
const isAfterMaximumDate =
maximumDate && isBeforeDate(maximumDate, { ...monthDate, month: monthNumber });
const isBeforeMinimumDate =
minimumDate &&
(isBeforeDate({ ...monthDate, month: monthNumber + 1 }, minimumDate) ||
isSameDay({ ...monthDate, month: monthNumber + 1 }, minimumDate));
const isSelected = monthNumber === activeDate.month;
return (
<li
key={persianMonth}
className={`Calendar__monthSelectorItem ${isSelected ? '-active' : ''}`}
>
<button
tabIndex={isSelected && isOpen ? '0' : '-1'}
onClick={() => {
onMonthSelect(monthNumber);
}}
className="Calendar__monthSelectorItemText"
type="button"
disabled={isAfterMaximumDate || isBeforeMinimumDate}
aria-pressed={isSelected}
data-is-default-selectable={isSelected}
>
{persianMonth}
</button>
</li>
);
});
return (
<div
role="presentation"
className="Calendar__monthSelectorAnimationWrapper"
{...(isOpen ? {} : { 'aria-hidden': true })}
>
<div
role="presentation"
data-testid="month-selector-wrapper"
className="Calendar__monthSelectorWrapper"
onKeyDown={handleKeyDown}
>
<ul ref={monthSelector} className="Calendar__monthSelector" data-testid="month-selector">
{renderMonthSelectorItems()}
</ul>
</div>
</div>
);
};
export default MonthSelector;

@ -0,0 +1,99 @@
import React, { useRef, useEffect } from 'react';
import { MINIMUM_SELECTABLE_YEAR_SUBTRACT, MAXIMUM_SELECTABLE_YEAR_SUM } from '../shared/constants';
import handleKeyboardNavigation from '../shared/keyboardNavigation';
import { useLocaleUtils } from '../shared/hooks';
const YearSelector = ({
isOpen,
activeDate,
onYearSelect,
selectorStartingYear,
selectorEndingYear,
maximumDate,
minimumDate,
locale,
}) => {
const wrapperElement = useRef(null);
const yearListElement = useRef(null);
const { getLanguageDigits, getToday } = useLocaleUtils(locale);
const startingYearValue =
selectorStartingYear || getToday().year - MINIMUM_SELECTABLE_YEAR_SUBTRACT;
const endingYearValue = selectorEndingYear || getToday().year + MAXIMUM_SELECTABLE_YEAR_SUM;
const allYears = [];
for (let i = startingYearValue; i <= endingYearValue; i += 1) {
allYears.push(i);
}
useEffect(() => {
const classToggleMethod = isOpen ? 'add' : 'remove';
const activeSelectorYear = wrapperElement.current.querySelector(
'.Calendar__yearSelectorItem.-active',
);
if (!activeSelectorYear) {
throw new RangeError(
`Provided value for year is out of selectable year range. You're probably using a wrong locale prop value or your provided value's locale is different from the date picker locale. Try changing the 'locale' prop or the value you've provided.`,
);
}
wrapperElement.current.classList[classToggleMethod]('-faded');
yearListElement.current.scrollTop =
activeSelectorYear.offsetTop - activeSelectorYear.offsetHeight * 5;
yearListElement.current.classList[classToggleMethod]('-open');
}, [isOpen]);
const renderSelectorYears = () => {
return allYears.map(item => {
const isAfterMaximumDate = maximumDate && item > maximumDate.year;
const isBeforeMinimumDate = minimumDate && item < minimumDate.year;
const isSelected = activeDate.year === item;
return (
<li key={item} className={`Calendar__yearSelectorItem ${isSelected ? '-active' : ''}`}>
<button
tabIndex={isSelected && isOpen ? '0' : '-1'}
className="Calendar__yearSelectorText"
type="button"
onClick={() => {
onYearSelect(item);
}}
disabled={isAfterMaximumDate || isBeforeMinimumDate}
aria-pressed={isSelected}
data-is-default-selectable={isSelected}
>
{getLanguageDigits(item)}
</button>
</li>
);
});
};
const handleKeyDown = e => {
handleKeyboardNavigation(e, { allowVerticalArrows: false });
};
return (
<div
className="Calendar__yearSelectorAnimationWrapper"
role="presentation"
{...(isOpen ? {} : { 'aria-hidden': true })}
>
<div
ref={wrapperElement}
className="Calendar__yearSelectorWrapper"
role="presentation"
data-testid="year-selector-wrapper"
onKeyDown={handleKeyDown}
>
<ul ref={yearListElement} className="Calendar__yearSelector" data-testid="year-selector">
{renderSelectorYears()}
</ul>
</div>
</div>
);
};
YearSelector.defaultProps = {
selectorStartingYear: 0,
selectorEndingYear: 0,
};
export default YearSelector;

@ -0,0 +1,4 @@
export { default as Header } from './Header';
export { default as MonthSelector } from './MonthSelector';
export { default as YearSelector } from './YearSelector';
export { default as DaysList } from './DaysList';

@ -0,0 +1,3 @@
export { default } from "./DatePicker";
export * from "./Calendar";
export { default as utils } from "./shared/localeUtils";

@ -0,0 +1,104 @@
export const PERSIAN_NUMBERS = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
export const PERSIAN_MONTHS = [
'فروردین',
'اردیبهشت',
'خرداد',
'تیر',
'مرداد',
'شهریور',
'مهر',
'آبان',
'آذر',
'دی',
'بهمن',
'اسفند',
];
export const GREGORIAN_MONTHS = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
];
export const PERSIAN_WEEK_DAYS = [
{
name: 'شنبه',
short: 'ش',
},
{
name: 'یکشنبه',
short: 'ی',
},
{
name: 'دوشنبه',
short: 'د',
},
{
name: 'سه شنبه',
short: 'س',
},
{
name: 'چهارشنبه',
short: 'چ',
},
{
name: 'پنجشنبه',
short: 'پ',
},
{
name: 'جمعه',
short: 'ج',
isWeekend: true,
},
];
export const GREGORIAN_WEEK_DAYS = [
{
name: 'Sunday',
short: 'S',
isWeekend: true,
},
{
name: 'Monday',
short: 'M',
},
{
name: 'Tuesday',
short: 'T',
},
{
name: 'Wednesday',
short: 'W',
},
{
name: 'Thursday',
short: 'T',
},
{
name: 'Friday',
short: 'F',
},
{
name: 'Saturday',
short: 'S',
isWeekend: true,
},
];
export const MINIMUM_SELECTABLE_YEAR_SUBTRACT = 100;
export const MAXIMUM_SELECTABLE_YEAR_SUM = 50;
export const TYPE_SINGLE_DATE = 'SINGLE_DATE';
export const TYPE_RANGE = 'RANGE';
export const TYPE_MUTLI_DATE = 'MUTLI_DATE';

@ -0,0 +1,69 @@
import { TYPE_SINGLE_DATE, TYPE_RANGE, TYPE_MUTLI_DATE } from './constants';
/*
These utility functions don't depend on locale of the date picker(Persian or Gregorian)
*/
const createUniqueRange = (number, startingId) =>
Array.from(Array(number).keys()).map(key => ({
value: key + 1,
id: `${startingId}-${key}`,
}));
const isSameDay = (day1, day2) => {
if (!day1 || !day2) return false;
return day1.day === day2.day && day1.month === day2.month && day1.year === day2.year;
};
const putZero = number => (number.toString().length === 1 ? `0${number}` : number);
const toExtendedDay = date => [date.year, date.month, date.day];
const shallowClone = value => ({ ...value });
const deepCloneObject = obj =>
JSON.parse(JSON.stringify(obj, (key, value) => (typeof value === 'undefined' ? null : value)));
const getDateAccordingToMonth = (date, direction) => {
const toSum = direction === 'NEXT' ? 1 : -1;
let newMonthIndex = date.month + toSum;
let newYear = date.year;
if (newMonthIndex < 1) {
newMonthIndex = 12;
newYear -= 1;
}
if (newMonthIndex > 12) {
newMonthIndex = 1;
newYear += 1;
}
const newDate = { year: newYear, month: newMonthIndex, day: 1 };
return newDate;
};
const hasProperty = (object, propertyName) =>
Object.prototype.hasOwnProperty.call(object || {}, propertyName);
const getValueType = value => {
if (Array.isArray(value)) return TYPE_MUTLI_DATE;
if (hasProperty(value, 'from') && hasProperty(value, 'to')) return TYPE_RANGE;
if (
!value ||
(hasProperty(value, 'year') && hasProperty(value, 'month') && hasProperty(value, 'day'))
) {
return TYPE_SINGLE_DATE;
}
throw new TypeError(
`The passed value is malformed! Please make sure you're using one of the valid value types for date picker.`,
);
};
export {
createUniqueRange,
isSameDay,
putZero,
toExtendedDay,
shallowClone,
deepCloneObject,
getDateAccordingToMonth,
getValueType,
};

@ -0,0 +1,10 @@
import { useMemo } from 'react';
import utils from './localeUtils';
import getLanguageText from './localeLanguages';
const useLocaleUtils = locale => useMemo(() => utils(locale), [locale]);
const useLocaleLanguage = locale => useMemo(() => getLanguageText(locale), [locale]);
export { useLocaleUtils, useLocaleLanguage };

@ -0,0 +1,44 @@
const handleArrowKeys = (e, { allowVerticalArrows }) => {
const { activeElement } = document;
const getNthChildSafe = (element, index) => (element ? element.children[index] : null);
const getStandardItem = item => item && (item.hasAttribute('aria-hidden') ? null : item);
const { nextSibling: nextRow, previousSibling: previousRow } = activeElement.parentElement;
const nextSibling = getStandardItem(activeElement.nextSibling || getNthChildSafe(nextRow, 0));
const previousRowLength = previousRow ? previousRow.children.length - 1 : 0;
const previousSibling = getStandardItem(
activeElement.previousSibling || getNthChildSafe(previousRow, previousRowLength),
);
const getVerticalSibling = row =>
getNthChildSafe(row, Array.from(activeElement.parentElement.children).indexOf(activeElement));
const downSibling = getStandardItem(getVerticalSibling(nextRow));
const upSibling = getStandardItem(getVerticalSibling(previousRow));
const isDefaultSelectable = activeElement.dataset.isDefaultSelectable === 'true';
if (!isDefaultSelectable) activeElement.tabIndex = '-1';
const focusIfAvailable = element => {
e.preventDefault();
/* istanbul ignore else */
if (element) {
element.setAttribute('tabindex', '0');
element.focus();
}
};
switch (e.key) {
case 'ArrowRight':
focusIfAvailable(nextSibling);
break;
case 'ArrowLeft':
focusIfAvailable(previousSibling);
break;
case 'ArrowDown':
/* istanbul ignore else */
if (allowVerticalArrows) focusIfAvailable(downSibling);
break;
case 'ArrowUp':
/* istanbul ignore else */
if (allowVerticalArrows) focusIfAvailable(upSibling);
break;
}
};
export default handleArrowKeys;

@ -0,0 +1,85 @@
import jalaali from 'jalaali-js';
import {
GREGORIAN_MONTHS,
PERSIAN_MONTHS,
GREGORIAN_WEEK_DAYS,
PERSIAN_WEEK_DAYS,
PERSIAN_NUMBERS,
} from './constants';
import { toExtendedDay } from './generalUtils';
const localeLanguages = {
en: {
months: GREGORIAN_MONTHS,
weekDays: GREGORIAN_WEEK_DAYS,
weekStartingIndex: 0,
getToday(gregorainTodayObject) {
return gregorainTodayObject;
},
toNativeDate(date) {
return new Date(date.year, date.month - 1, date.day);
},
getMonthLength(date) {
return new Date(date.year, date.month, 0).getDate();
},
transformDigit(digit) {
return digit;
},
nextMonth: 'Next Month',
previousMonth: 'Previous Month',
openMonthSelector: 'Open Month Selector',
openYearSelector: 'Open Year Selector',
closeMonthSelector: 'Close Month Selector',
closeYearSelector: 'Close Year Selector',
from: 'from',
to: 'to',
defaultPlaceholder: 'Select...',
digitSeparator: ',',
yearLetterSkip: 0,
isRtl: false,
},
fa: {
months: PERSIAN_MONTHS,
weekDays: PERSIAN_WEEK_DAYS,
weekStartingIndex: 1,
getToday({ year, month, day }) {
const { jy, jm, jd } = jalaali.toJalaali(year, month, day);
return { year: jy, month: jm, day: jd };
},
toNativeDate(date) {
const gregorian = jalaali.toGregorian(...toExtendedDay(date));
return new Date(gregorian.gy, gregorian.gm - 1, gregorian.gd);
},
getMonthLength(date) {
return jalaali.jalaaliMonthLength(date.year, date.month);
},
transformDigit(digit) {
return digit
.toString()
.split('')
.map(letter => PERSIAN_NUMBERS[Number(letter)])
.join('');
},
nextMonth: 'ماه بعد',
previousMonth: 'ماه قبل',
openMonthSelector: 'نمایش انتخابگر ماه',
openYearSelector: 'نمایش انتخابگر سال',
closeMonthSelector: 'بستن انتخابگر ماه',
closeYearSelector: 'بستن انتخابگر ماه',
from: 'از',
to: 'تا',
defaultPlaceholder: 'انتخاب...',
digitSeparator: '،',
yearLetterSkip: -2,
isRtl: true,
},
};
const getLocaleDetails = locale => {
if (typeof locale === 'string') return localeLanguages[locale];
return locale;
};
export { localeLanguages };
export default getLocaleDetails;

@ -0,0 +1,61 @@
/*
These utility functions highly depend on locale of the date picker(Persian or Gregorian)
*/
import getLocaleDetails from './localeLanguages';
const utils = (locale = 'en') => {
const {
months: monthsList,
getToday: localeGetToday,
toNativeDate,
getMonthLength,
weekStartingIndex,
transformDigit: getLanguageDigits,
} = typeof locale === 'string' ? getLocaleDetails(locale) : locale;
const getToday = () => {
const todayDate = new Date();
const year = todayDate.getFullYear();
const month = todayDate.getMonth() + 1;
const day = todayDate.getDate();
return localeGetToday({ year, month, day });
};
const getMonthName = month => monthsList[month - 1];
const getMonthNumber = monthName => monthsList.indexOf(monthName) + 1;
const getMonthFirstWeekday = date => {
const gregorianDate = toNativeDate({ ...date, day: 1 });
const weekday = gregorianDate.getDay();
const dayIndex = weekday + weekStartingIndex;
return dayIndex % 7;
};
const isBeforeDate = (day1, day2) => {
if (!day1 || !day2) return false;
return toNativeDate(day1) < toNativeDate(day2);
};
const checkDayInDayRange = ({ day, from, to }) => {
if (!day || !from || !to) return false;
const nativeDay = toNativeDate(day);
const nativeFrom = toNativeDate(from);
const nativeTo = toNativeDate(to);
return nativeDay > nativeFrom && nativeDay < nativeTo;
};
return {
getToday,
getMonthName,
getMonthNumber,
getMonthLength,
getMonthFirstWeekday,
isBeforeDate,
checkDayInDayRange,
getLanguageDigits,
};
};
export default utils;

@ -0,0 +1,33 @@
import { getDateAccordingToMonth } from './generalUtils';
const getSlideDate = ({ parent, isInitialActiveChild, activeDate, monthChangeDirection }) => {
if (!parent) {
return isInitialActiveChild ? activeDate : getDateAccordingToMonth(activeDate, 'NEXT');
}
const child = parent.children[isInitialActiveChild ? 0 : 1];
const isActiveSlide =
child.classList.contains('-shown') || child.classList.contains('-shownAnimated'); // check -shownAnimated for Safari bug
return isActiveSlide ? activeDate : getDateAccordingToMonth(activeDate, monthChangeDirection);
};
const animateContent = ({ parent, direction }) => {
const wrapperChildren = Array.from(parent.children);
const shownItem = wrapperChildren.find(child => child.classList.contains('-shown'));
const hiddenItem = wrapperChildren.find(child => child !== shownItem);
const baseClass = shownItem.classList[0];
const isNextMonth = direction === 'NEXT';
const getAnimationClass = value => (value ? '-hiddenNext' : '-hiddenPrevious');
hiddenItem.style.transition = 'none';
shownItem.style.transition = '';
shownItem.className = `${baseClass} ${getAnimationClass(!isNextMonth)}`;
hiddenItem.className = `${baseClass} ${getAnimationClass(isNextMonth)}`;
hiddenItem.classList.add('-shownAnimated');
};
const handleSlideAnimationEnd = ({ target }) => {
target.classList.remove('-hiddenNext');
target.classList.remove('-hiddenPrevious');
target.classList.replace('-shownAnimated', '-shown');
};
export { animateContent, getSlideDate, handleSlideAnimationEnd };

@ -0,0 +1,48 @@
import React from "react";
import { connect } from "react-redux";
function Search({
lang,
textColor,
backgroundColor = "bg-white ",
onChange,
name,
icon = true,
placeholder,
padding = "py-3 lg:py-4 md:py-5",
}) {
return (
<div className="flex items-center justify-center w-full z-50 overflow-hidden rounded-md">
<div className="relative text-gray-600 focus-within:text-gray-400 w-full">
{icon ? (
<span className="absolute inset-y-0 flex items-center pl-2 left-0">
<button
type="submit"
className="p-1 focus:outline-none focus:shadow-outline"
>
<img src="/icons/search.png" className="w-5 h-5" alt="" />
</button>
</span>
) : null}
<input
type="search"
name={name}
className={`z-50 w-full text-xs font-2 ${padding} ${backgroundColor} ${
textColor ? textColor : "text-gray1"
} border border-solid border-surfaceBorder rounded-md pr-3 ${
icon ? "pl-14" : "pl-3"
} outline-none focus:border-primary`}
placeholder={placeholder}
autoComplete="off"
onChange={(e) => onChange(e.target.value)}
/>
</div>
</div>
);
}
const mapStateToProps = (state) => ({});
const mapDispatchToProps = {};
export default connect(mapStateToProps, mapDispatchToProps)(Search);

@ -0,0 +1,37 @@
// it has style inside index.scss ---> // Start SimpleCalendar && end SimpleCalendar
import React, { useEffect, useState } from "react";
import DatePicker from "../CalendarComponents";
import { utils } from "../CalendarComponents";
const SimpleDatePicker = ({returnValue,defaultValue,mode}) => {
const [selectedDay, setSelectedDay] = useState(defaultValue || null);
const gotoToday = () => {
setSelectedDay(utils("fa").getToday());
};
useEffect(() => {
if(mode){
returnValue(selectedDay, mode);
}
else{
returnValue(selectedDay);
}
// returnValue(selectedDay);
}, [selectedDay]);
return (
<div className="flex items-center justify-center w-full">
<DatePicker
value={selectedDay}
onChange={setSelectedDay}
inputPlaceholder="انتخاب تاریخ"
shouldHighlightWeekends
renderFooter={() => <button onClick={gotoToday}>برو به امروز</button>}
locale="fa"
/>
</div>
);
};
export default SimpleDatePicker;

@ -0,0 +1,23 @@
import React from "react";
const ShowMoreRows = ({ pageSize, setPageSize }) => {
return (
<select
className="text-white text-sm rounded cursor-pointer mr-2 bg-[#65A9FF]"
value={pageSize}
onChange={(e) => setPageSize(Number(e.target.value))}
>
{[7, 10, 25, 50].map((pageSize) => (
<option
key={pageSize}
value={pageSize}
className=" text-xs text-right ltr"
>
نمایش {pageSize}
</option>
))}
</select>
);
};
export default ShowMoreRows;

@ -0,0 +1,31 @@
import React from "react";
import { connect } from "react-redux";
//components
import ShowMoreRows from "./components/ShowMoreRows";
import GoToPage from "../Toolbar/components/GoToPage";
export const Footer = ({
pageIndex,
gotoPage,
pageOptions,
pageSize,
setPageSize,
}) => {
return (
<div className="flex items-center gap-6">
<GoToPage
pageIndex={pageIndex}
gotoPage={gotoPage}
pageOptions={pageOptions}
/>
<ShowMoreRows pageSize={pageSize} setPageSize={setPageSize} />
</div>
);
};
const mapStateToProps = (state) => ({});
const mapDispatchToProps = {};
export default connect(mapStateToProps, mapDispatchToProps)(Footer);

@ -0,0 +1,185 @@
import React from "react";
import { connect } from "react-redux";
//components
import RowDetail from "../../../components/RowDetail";
const Table = ({
getTableProps,
headerGroups,
prepareRow,
filtering,
tabs,
setTabs,
getTableBodyProps,
columns,
page,
rows,
footerGroups,
name,
schema,
data,
editor,
activeOtherTab,
setActiveOtherTab,
fake,
setFake,
}) => {
// const bodyComponent = React.useMemo(() => {
// return {
// 1: page.map((row, idx) => {
// prepareRow(row);
// return (
// <React.Fragment key={idx}>
// <tr
// {...row.getRowProps()}
// // className={isEven(idx) ? "bg-gray-600" : "bg-white"}
// >
// {row.cells.map((cell, index) => {
// return (
// <td
// key={index}
// {...cell.getCellProps()}
// className="p-4 whitespace-nowrap text-sm text-[#00253A]"
// role="cell"
// >
// {cell.render("Cell")}
// </td>
// );
// })}
// </tr>
// {row.isExpanded ? (
// <tr className="w-full">
// <td colSpan={headerGroups[0].headers.length}>
// <RowDetail
// data={data}
// schema={schema.filter((e) => e.more)}
// setTabs={setTabs}
// tabs={tabs}
// row={row.original}
// name={name}
// />
// </td>
// </tr>
// ) : null}
// </React.Fragment>
// );
// }),
// 2 : editor
// };
// },[tabs]);
return (
<table
{...getTableProps()}
className="w-full divide-y divide-gray-200 mb-5"
>
<thead className="bg-[#f3fff4]">
{headerGroups.map((headerGroup, index) => (
<tr key={index} {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th
{...column.getHeaderProps(column.getSortByToggleProps())}
scope="col"
className="cursor-pointer py-2 px-2 text-right text-sm bg-[#3287B9] text-background text-white"
>
{column.render("Header")}
{/* {column.render("description")} */}
<span>
{column.isSorted ? (column.isSortedDesc ? " 🔽" : " 🔼") : ""}
</span>
</th>
))}
</tr>
))}
</thead>
{/* filter row */}
{console.log("...............................o")}
{filtering ? (
<thead className="bg-[#F1F5FE]">
{headerGroups.map((headerGroup, index) => (
<tr key={index} {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<>
<th
{...column.getHeaderProps()}
className="py-2 px-2 text-sm text-primaryText"
>
{column.canFilter ? column.render("Filter") : null}
</th>
</>
))}
</tr>
))}
</thead>
) : null}
<tbody
{...getTableBodyProps()}
className="w-full divide-y divide-gray-20"
>
{page.map((row, idx) => {
prepareRow(row);
return (
<React.Fragment key={idx}>
<tr
{...row.getRowProps()}
// className={isEven(idx) ? "bg-gray-600" : "bg-white"}
>
{row.cells.map((cell, index) => {
return (
<td
key={index}
{...cell.getCellProps()}
className="py-4 px-2 whitespace-nowrap text-sm text-[#00253A] text-right"
role="cell"
>
{cell.render("Cell")}
</td>
);
})}
</tr>
{row.isExpanded ? (
<tr className="w-full">
<td colSpan={headerGroups[0].headers.length}>
<RowDetail
data={data}
schema={schema.filter((e) => e.more)}
setTabs={setTabs}
tabs={tabs}
row={row.original}
name={name}
/>
</td>
</tr>
) : null}
</React.Fragment>
);
})}
{/* {tabs?.length > 1 && editor} */}
</tbody>
{/* {tabs?.length > 1 && editor} */}
<tfoot className={`bg-blue-900 ${rows.length > 5 ? "w-full" : "hidden"}`}>
{footerGroups.map((footerGroup, index) => (
<tr key={index} {...footerGroup.getFooterGroupProps()}>
{footerGroup.headers.map((column, index) => (
<td
key={index}
{...column.getFooterProps}
className="py-2 px-4 text-right text-xs text-white"
>
{column.render("Footer")}
</td>
))}
</tr>
))}
</tfoot>
</table>
);
};
const mapStateToProps = (state) => ({});
const mapDispatchToProps = {};
export default connect(mapStateToProps, mapDispatchToProps)(Table);

@ -0,0 +1,61 @@
import React from "react";
import { connect } from "react-redux";
//components
import Table from "./components/Table";
export const Main = ({
getTableProps,
headerGroups,
prepareRow,
filtering,
setFiltering,
tabs,
setTabs,
getTableBodyProps,
columns,
page,
rows,
footerGroups,
name,
schema,
data,
editor,
activeOtherTab,
setActiveOtherTab,
fake,
setFake
}) => {
return (
<main className="w-full flex-1">
<Table
getTableProps={getTableProps}
headerGroups={headerGroups}
prepareRow={prepareRow}
filtering={filtering}
setFiltering={setFiltering}
tabs={tabs}
setTabs={setTabs}
fake={fake}
setFake={setFake}
getTableBodyProps={getTableBodyProps}
columns={columns}
page={page}
rows={rows}
footerGroups={footerGroups}
name={name}
schema={schema}
data={data}
editor={editor}
activeOtherTab={activeOtherTab}
setActiveOtherTab={setActiveOtherTab}
/>
</main>
);
};
const mapStateToProps = (state) => ({});
const mapDispatchToProps = {};
export default connect(mapStateToProps, mapDispatchToProps)(Main);

@ -0,0 +1,21 @@
import React from "react";
const GoToPage = ({ pageIndex, gotoPage, pageOptions }) => {
return (
<span className="text-xs text-blue-400 mr-1">
صفحه{" "}
<input
className="ml-1 text-xs p-1 rounded border-blue-400 w-9"
type="number"
defaultValue={pageIndex + 1}
onChange={(e) => {
const pageNumber = e.target.value ? Number(e.target.value) - 1 : 0;
gotoPage(pageNumber);
}}
/>
از {pageOptions.length}
</span>
);
};
export default GoToPage;

@ -0,0 +1,61 @@
import React from "react";
const Pagination = ({
gotoPage,
canPreviousPage,
previousPage,
canNextPage,
pageCount,
nextPage,
}) => {
return (
<div className="flex flex-row items-center">
<button
className={`rounded py-1 px-2 text-xs font-sansbold text-[#65A9FF] bg-[#F5F9FF]`}
onClick={() => gotoPage(0)}
disabled={!canPreviousPage}
>
<img
src="/icons/table/ReactTable4double-left-arrow.svg"
className="rotate-180 w-4"
alt=""
/>
</button>
<button
className={`rounded py-1 px-2 ml-1 text-xs font-sansbold text-[#65A9FF] bg-[#F5F9FF]`}
onClick={() => previousPage()}
disabled={!canPreviousPage}
>
<img
src="/icons/table/ReactTable4right-arrow.svg"
className="w-3"
alt=""
/>
</button>
<button
className={`rounded py-1 px-2 text-xs font-sansbold text-[#65A9FF] bg-[#F5F9FF]`}
onClick={() => nextPage()}
disabled={!canNextPage}
>
<img
src="/icons/table/ReactTable4left-arrow.svg"
className="w-3"
alt=""
/>
</button>
<button
className={`rounded py-1 px-2 text-xs font-sansbold text-[#65A9FF] bg-[#F5F9FF]`}
onClick={() => gotoPage(pageCount - 1)}
disabled={!canNextPage}
>
<img
src="/icons/table/ReactTable4double-left-arrow.svg"
className="w-4"
alt=""
/>
</button>
</div>
);
};
export default Pagination;

@ -0,0 +1,60 @@
import React, { useState } from "react";
const Settings = ({ allColumns, getToggleHideAllColumnsProps }) => {
const [openModal, setOpenModal] = useState(false);
return (
<section>
<div
onClick={() => setOpenModal(true)}
data-modal-toggle="large-modal"
className="mr-2 flex flex-col items-center justify-center p-1 bg-[#F9FBFF] rounded"
>
<img
src="/icons/table/ReactTable4settings.svg"
className="w-5 cursor-pointer"
alt=""
/>
</div>
{/* modal */}
<div
className={`cursor-pointer w-screen h-screen backdrop-filter backdrop-blur-lg bg-opacity-90 bg-surfaceBorder flex items-center justify-center fixed left-0 top-0
${openModal ? "opacity-80" : "opacity-0 pointer-events-none"}`}
onClick={() => setOpenModal(false)}
>
<div
className="absolute top-[10%] bg-white w-6/12 p-4 rounded"
onClick={(e) => e.stopPropagation()}
>
<div className="">
<h2 className="text-base">
با زدن بر روی هر یک از چک باکس ها میتوانید نمایش ستون هارو خاموش
روشن کنید{" "}
</h2>
<div className="text-sm mt-5">
<input
className="ml-2"
type="checkbox"
{...getToggleHideAllColumnsProps()}
/>{" "}
تاگل کردن همه
</div>
{allColumns.map((column, index) => (
<div key={index}>
<label className="text-sm">
<input
type="checkbox"
className="ml-2 "
{...column.getToggleHiddenProps()}
/>
{column.Footer}
</label>
</div>
))}
</div>
</div>
</div>
</section>
);
};
export default Settings;

@ -0,0 +1,128 @@
import React from "react";
const Tabbar = ({
setTabs,
tabs,
name,
fake,
setFake,
activeOtherTab,
setActiveOtherTab,
}) => {
const tabsActionsHandler = React.useCallback(
(type, id) => {
if (type === "activation") {
setTabs({
table: name,
tabs: tabs.map((tab) => {
return { ...tab, isActive: tab.id === id ? true : false };
}),
});
}
if (type === "delete") {
setTabs({ table: name, tabs: tabs.filter((tab) => tab.id !== id) });
}
},
[tabs]
);
const deleteTab = (e, index) => {
e.stopPropagation();
let newList = [];
for (let i = 0; i < tabs.length; i++) {
if (i !== index) {
newList.push(tabs[i]);
}
}
if (tabs[index].isActive) {
newList[0].isActive = true;
setActiveOtherTab(false);
}
setFake(Math.random() * 1000);
setTabs(newList);
};
const selectTab = (e, index) => {
let newList = [...tabs];
if (index === 0) {
newList[0].isActive = true;
for (let i = 1; i < newList.length; i++) {
newList[i].isActive = false;
}
setActiveOtherTab(false);
} else {
for (let i = 0; i < newList.length; i++) {
if (i === index) {
newList[i].isActive = true;
} else {
newList[i].isActive = false;
}
}
setActiveOtherTab(true);
}
setFake(Math.random() * 1000);
setTabs(newList);
};
// console.log(fake);
// console.log("tabs:");
// console.log(tabs);
return (
<ul
className="tabbar flex no-scrollbar overflow-x-auto scrolling-touch"
onMouseDown={(e) => {
const slider = window.document.querySelector(".tabbar");
window.isDown = true;
window.startX = e.pageX - slider.offsetLeft;
window.scrollLeft = slider.scrollLeft;
}}
onMouseLeave={(e) => {
window.isDown = false;
}}
onMouseUp={(e) => {
window.isDown = false;
}}
onMouseMove={(e) => {
const slider = window.document.querySelector(".tabbar");
if (!window.isDown) return;
e.preventDefault();
const x = e.pageX - slider.offsetLeft;
const walk = (x - window.startX) * 1;
slider.scrollLeft = window.scrollLeft - walk;
}}
>
{tabs?.map((el, index) => (
<li
key={index}
onClick={(e) => {
selectTab(e, index);
}}
aria-current="page"
className={`flex flex-row items-center text-sm text-white px-2 py-0.5 text-center active rounded-sm ${
el?.isActive ? "text-[#fff]" : "text-[#000]"
} ${el?.isActive ? "bg-[#92c1fc]" : "bg-[#F1F6FF]"}`}
>
<div
className="h-full py-2 pointer-events-none"
onClick={(e) => tabsActionsHandler("activation", el.id)}
>
{el.name}
</div>
{el.id ? (
<img
src="/icons/table/ReactTable5zarbedar-icon.svg"
className="w-2 mr-3 cursor-pointer"
alt=""
onClick={(e) => {
deleteTab(e, index);
}}
/>
) : null}
</li>
))}
</ul>
);
};
window.isDown = false;
export default Tabbar;
// if (window.isDown) return e.preventDefault();

@ -0,0 +1,21 @@
import React from "react";
//components
import Search from "../../../../Search";
const TableSearch = ({ globalFilter, setGlobalFilter }) => {
return (
<div className="flex flex-row-reverse items-center relative ml-2 ">
<Search
icon={false}
placeholder="جستجو ..."
padding="py-1"
value={globalFilter || ' '}
onChange={(value) => setGlobalFilter(value)}
/>
</div>
);
};
export default TableSearch;

@ -0,0 +1,69 @@
import React from "react";
//components
import Tabbar from "./components/Tabbar";
import TableSearch from "./components/TableSearch";
import GoToPage from "./components/GoToPage";
import Pagination from "./components/Pagination";
import Settings from "./components/Settings";
export const Toolbar = ({
setTabs,
tabs,
globalFilter,
setGlobalFilter,
pageIndex,
pageOptions,
gotoPage,
canPreviousPage,
previousPage,
canNextPage,
pageCount,
nextPage,
allColumns,
getToggleHideAllColumnsProps,
name,
fake,
setFake,
activeOtherTab,
setActiveOtherTab,
}) => {
return (
<div className="flex justify-between items-center">
<Tabbar
setTabs={setTabs}
tabs={tabs}
name={name}
fake={fake}
setFake={setFake}
activeOtherTab={activeOtherTab}
setActiveOtherTab={setActiveOtherTab}
/>
<div className="flex py-2 bg-surface items-center gap-2 px-2 bg-sky-200">
<TableSearch
globalFilter={globalFilter}
setGlobalFilter={setGlobalFilter}
/>
<GoToPage
pageIndex={pageIndex}
pageOptions={pageOptions}
gotoPage={gotoPage}
/>
<Pagination
gotoPage={gotoPage}
canPreviousPage={canPreviousPage}
previousPage={previousPage}
canNextPage={canNextPage}
pageCount={pageCount}
nextPage={nextPage}
/>
<Settings
allColumns={allColumns}
getToggleHideAllColumnsProps={getToggleHideAllColumnsProps}
/>
</div>
</div>
);
};
export default Toolbar;

@ -0,0 +1,14 @@
import React from "react";
const Checkbox = React.forwardRef(({ indeterminate, ...rest }, ref) => {
const defaultRef = React.useRef();
const resolvedRef = ref || defaultRef;
React.useEffect(() => {
resolvedRef.current.indeterminate = indeterminate;
}, [resolvedRef, indeterminate]);
return <input type="checkbox" ref={resolvedRef} {...rest} />;
});
export default Checkbox;

@ -0,0 +1,15 @@
import React, { useState } from "react";
export const ColumnSearch = ({ column }) => {
const { filterValue, setFilter } = column;
return (
<div className="w-full flex flex-row items-center ">
<input
value={filterValue || ""}
onChange={(e) => setFilter(e.target.value)}
className={`w-full h-8 text-xs text-blue-400 bg-[#F9FBFF] border-[#B5D6FF] rounded focus:outline-none border p-1 focus:border-blue-200 focus:ring focus:ring-blue-200 focus:ring-opacity-20`}
/>
</div>
);
};

@ -0,0 +1,98 @@
import { tab } from "@testing-library/user-event/dist/tab";
import { useState } from "react";
import { connect } from "react-redux";
import { table } from "../../../../../redux/actions";
//components
import DropDown from "./Dropdown";
const Buttons = ({
row,
setTabs,
tabs,
name,
fake,
setFake,
setActiveOtherTab,
activeOtherTab,
}) => {
const [activeDetails, setActiveDetails] = useState(null);
const editHandler = () => {
console.log("edit click");
console.log(fake);
console.log(tabs);
// console.log(row);
// console.log(name);
let newList = [...tabs];
console.log(newList);
for (let i = 0; i < newList.length; i++) {
newList[i].isActive = false;
}
newList.push({
id: Math.floor(Math.random() * 10000),
name: row.original.name,
isActive: true,
data: row,
element: "editor",
});
console.log(newList);
console.log("!!!!!!!!!!!!!!!!!!!!!!!");
setFake(Math.random() * 10000);
setTabs(newList);
setActiveOtherTab(true);
};
return (
<div className="flex flex-row items-center justify-end ">
<div
className=""
onClick={() => {
setActiveDetails(!activeDetails);
}}
>
<button
className="mx-1 flex flex-row items-center justify-around p-2 rounded border border-gray-300 text-[12px]"
{...row.getToggleRowExpandedProps()}
>
جزئیات
<img
src="/icons/table/ReactTable4arrow-icon.svg"
alt=""
className={`mx-1 w-3 cursor-pointer mr-2 transition-all duration-500 ${
activeDetails ? "rotate-60" : "rotate-90"
}`}
/>
</button>
</div>
<div
className="mx-1 p-1 rounded border border-gray-300 cursor-pointer"
onClick={editHandler}
>
<img src="/icons/table/ReactTable4edit.svg" alt="" className="w-10" />
</div>
{/* <DropDown /> */}
</div>
);
};
const mapStateToProps = (state) => ({
tabs: state.table.tabs,
});
const mapDispatchToProps = {
setTabs: table.setTab,
};
export default connect(mapStateToProps, mapDispatchToProps)(Buttons);
// setTabs({
// table: name,
// tabs: [
// ...tabs,
// { name: row.original.name, id: row.original.id, isActive: false },
// ].map((tab) => {
// return {
// ...tab,
// isActive: row.original.id === tab.id ? true : false,
// };
// }),
// });

@ -0,0 +1,53 @@
import React from "react";
const DropDown = ({}) => {
return (
<div className="mr-3">
<button
className=""
id="dropdownButton"
data-dropdown-toggle="dropdown"
type="button"
>
<img
src="/icons/table/DropDownmore2.svg"
alt=""
className={`cursor-pointer w-5`}
/>
</button>
<div
id="dropdown"
className="hidden z-10 text-xs list-none bg-[#65A9FF] rounded divide-y divide-gray-100 shadow"
>
<ul className="" aria-labelledby="dropdownButton">
<li>
<a
href="#"
className="block p-3 text-xs text-white hover:bg-blue-600 hover:rounded"
>
ایتم شماره1
</a>
</li>
<li>
<a
href="#"
className="block p-3 text-xs text-white hover:bg-blue-600 hover:rounded"
>
ایتم شماره1
</a>
</li>
<li>
<a
href="#"
className="block p-3 text-xs text-white hover:bg-blue-600 hover:rounded"
>
ایتم شماره1
</a>
</li>
</ul>
</div>
</div>
);
};
export default DropDown;

@ -0,0 +1,75 @@
import React, { useState } from "react";
import { connect } from "react-redux";
import { table } from "../../../../../redux/actions";
//components
import DropDown from "./Dropdown";
const EditButtons = ({
setFiltering,
filtering,
setTabs,
tabs,
name,
fake,
setFake,
setActiveOtherTab,
}) => {
const [selectedFilterRow, setSelectedFilterRow] = useState(filtering);
const addNewItem = () => {
let newList = [...tabs];
newList[0].isActive = false;
newList.push({
name: "ردیف جدید",
isActive: true,
data: null,
id: Math.floor(Math.random() * 10000),
element: "editor",
});
setFake(Math.random() * 10000);
setTabs(newList);
setActiveOtherTab(true);
};
return (
<div className="flex flex-row items-center justify-end">
{/* add new row */}
<button
className="mx-1 flex flex-row items-center bg-[#65A9FF] p-2 text-xs text-white rounded border-b-2 border-blue-600"
onClick={addNewItem}
>
<div>ثبت جدید</div>
<img
src="/icons/table/ReactTable4plus.svg"
alt=""
className={`cursor-pointer w-2 mr-1 `}
/>
</button>
{/* filter */}
<img
src="/icons/table/ReactTable4filter.svg"
alt=""
className={`mx-1 p-2 rounded border border-blue-300 cursor-pointer w-8
${selectedFilterRow ? "border" : "border-0"}`}
onClick={() => {
setFiltering(!selectedFilterRow);
setSelectedFilterRow(!selectedFilterRow);
}}
/>
{/* dropDown */}
{/* <DropDown /> */}
</div>
);
};
const mapStateToProps = (state) => ({
tabs: state.table.tabs,
});
const mapDispatchToProps = {
setTabs: table.setTab,
};
export default connect(mapStateToProps, mapDispatchToProps)(EditButtons);
EditButtons.defaultProps = {};

@ -0,0 +1,34 @@
const status = {
1: {
color: "#65FFAD",
label: "وضعیت عادی",
},
2: {
color: "#FF6565",
label: "اعلان سطح هشدار",
},
3: {
color: "#65DAFF",
label: "بررسی و پیگیری",
},
4: {
color: "#FFEA65",
label: "وضعیت بحرانی",
},
};
export function OrderLevel({ value }) {
return (
<div className="p-2 rounded-3xl border border-[#aaa] w-fit">
<div className="flex flex-row items-center justify-center gap-2">
<span
className={`w-2 h-2 rounded-full`}
style={{
backgroundColor: status[value].color
}}
></span>
<span className="text-[#00253A]">{status[value].label}</span>
</div>
</div>
);
}

@ -0,0 +1,35 @@
const statusMap = {
1: {
label: "ارسال شده",
bg: "#E2FFE377",
color: "#00613D",
},
2: {
label: "رسیدگی",
bg: "#FFF78733",
color: "#ECF150",
},
3: {
label: "لغو شده",
bg: "#FFD8CC33",
color: "#611100",
},
4: {
label: "پیگیری",
bg: "#E2FFFD33",
color: "#00614A",
},
};
export function OrderStatus({ value }) {
value = +value;
return (
<div className="">
<span
className={`p-2 rounded bg-[${statusMap[value].bg}] text-[${statusMap[value].color}]`}
>
{statusMap[value].label}
</span>
</div>
);
}

@ -0,0 +1,52 @@
import React from "react";
export const SelectFilter = ({
column: { filterValue, setFilter, preFilteredRows, id, render },
}) => {
const options = React.useMemo(() => {
const options = new Set();
preFilteredRows.forEach((row) => {
options.add(row.values[id]);
});
return [...options.values()];
}, [id, preFilteredRows]);
return (
<select
className="text-xs bg-[#F9FBFF] border-[#B5D6FF] flex flex-row items-start justify-start p-1 text-blue-400 rounded focus:outline-none border focus:border-blue-200 focus:ring focus:ring-blue-200 focus:ring-opacity-20"
name={id}
id={id}
value={filterValue}
onChange={(e) => {
setFilter(e.target.value || undefined);
}}
>
<option className="text-white bg-[#65A9FF]" value="">
همه
</option>
{options.map((option, i) => (
<option
key={i}
value={option}
className="text-right text-white px-2 bg-[#65A9FF]"
>
{(() => {
if (option === 1) {
return "ارسال شده";
} else if (option === 2) {
return "رسیدگی";
} else if (option === 3) {
return "لغو شده";
} else if (option === 4) {
return "پیگیری";
} else {
return "خطری";
}
})()}{" "}
</option>
))}
</select>
);
};
export default SelectFilter;

@ -0,0 +1,39 @@
import React from "react";
export const SliderColumnFilter = ({
column: { filterValue, setFilter, preFilteredRows, id },
}) => {
const [min, max] = React.useMemo(() => {
let min = preFilteredRows.length ? preFilteredRows[0].values[id] : 0;
let max = preFilteredRows.length ? preFilteredRows[0].values[id] : 0;
preFilteredRows.forEach((row) => {
min = Math.min(row.values[id], min);
max = Math.max(row.values[id], max);
});
return [min, max];
}, [id, preFilteredRows]);
return (
<div className="flex flex-row items-center justify-between w-10/12">
<input
className=" w-10/12"
type="range"
min={min}
max={max}
value={filterValue || min}
onChange={(e) => {
setFilter(parseInt(e.target.value, 10));
}}
/>
<div className="p-1 rounded bg-blue-300 bg-opacity-20">
<img
src="/icons/table/ReactTable4zarbedar-icon.svg"
onClick={() => setFilter(undefined)}
className="cursor-pointer w-2 h-2"
alt=""
/>
</div>
{/* <button onClick={() => setFilter(undefined)}>ریست</button> */}
</div>
);
};

@ -0,0 +1,95 @@
// import { format } from "date-fns";
//components
import { OrderStatus } from "./components/OrderStatus";
import { OrderLevel } from "./components/OrderLevel";
import Buttons from "./components/Buttons";
import EditButtons from "./components/EditButtons";
import SelectFilter from "./components/SelectFilter.js";
import { SliderColumnFilter } from "./components/SliderColumnFilter.js";
import moment from "jalali-moment";
import { ColumnSearch } from "../ColumnSearch";
const Components = {
date: ({ value }) => {
// return format(new Date(value), "dd/MM/yyyy");
// return moment(value).locale("fa").format("YYYY-MM-DD")
return moment(value, "YYYY/MM/DD")
.locale("fa")
.format("YYYY/MM/DD")
},
OrderStatus,
OrderLevel,
text: ({ value }) => value,
string: ({ value }) => value,
number: ({ value }) => value,
};
const Filters = {
SliderColumnFilter: SliderColumnFilter,
SelectFilter: SelectFilter,
ColumnSrarch:ColumnSearch
};
export const COLUMNS = ({
schema,
setFiltering,
filtering,
setTabs,
name,
tabs,
fake,
setFake,
setActiveOtherTab,
activeOtherTab,
}) => [
...schema.map((e) => ({
accessor: e.name,
Header: e.description,
// Cell: e.renderer ? Components[e.renderer] : Components["text"],
Cell: e.type ? Components[e.type] : Components["text"],
Filter: e.filter ? Filters[e.filter] : Filters["ColumnSrarch"],
disableSortBy: e.sort == false,
Footer: "ستون " + e.description,
})),
{
Header: () => (
<EditButtons
setFiltering={setFiltering}
filtering={filtering}
name={name}
fake={fake}
setFake={setFake}
setActiveOtherTab={setActiveOtherTab}
/>
),
Footer: "ستون ",
accessor: "edit",
Cell: ({ row }) => (
<Buttons
row={row}
name={name}
fake={fake}
setFake={setFake}
setActiveOtherTab={setActiveOtherTab}
activeOtherTab={activeOtherTab}
/>
),
disableSortBy: true,
disableFilters: true,
},
];
// Cell: ({ row }) =>
// Buttons({
// row,
// setTabs,
// tabs,
// name,
// fake,
// setFake,
// setActiveOtherTab,
// activeOtherTab,
// }),

@ -0,0 +1,57 @@
import React from "react";
import { OrderLevel } from "./Columns/components/OrderLevel";
import { OrderStatus } from "./Columns/components/OrderStatus";
const components = {
orderLevel: (val) => <OrderLevel value={val} />,
orderStatus: (val) => <OrderStatus value={val} />,
string: (val) => <span className="text-black">{val}</span>,
number: (val) => <span className="text-black">{val}</span>,
default: (val) => <span className="text-black">{val}</span>,
};
const RowDetail = ({ schema, data, tabs, setTabs, row, name,fake,setFake }) => {
return (
<div className="w-full flex items-center border-r-4 border-blue-600 p-2 bg-[#FBFCFE]">
{/* right */}
<div className="w-[80%] flex flex-wrap items-center ">
{schema.map((field) => (
<div className="flex items-center mr-2 my-2">
<div className="w-1 h-4 ml-1 rounded bg-[#EDF2FB]"></div>
<p className="flex items-center gap-1 text-xs text-blue-400 border-[#65A9FF]">
{field?.description}
{components[field?.type || "default"](data[0][field?.name])}
</p>
</div>
))}
</div>
{/* left */}
<div className="w-[20%] flex flex-col items-end justify-end">
<button className="border border-[#D53921] text-[#D53921] rounded w-8/12 text-xs py-2 mb-2 bg-white">
حذف
</button>
<button
className="border border-[#65A9FF] text-[#65A9FF] rounded w-8/12 text-xs py-2 bg-white"
onClick={() => {
setTabs({
table: name,
tabs: [
...tabs,
{ name: row.name, id: row.id, isActive: true },
].map((tab) => {
return {
...tab,
isActive: row.id === tab.id ? true : false,
};
}),
});
}}
>
نمایش
</button>
</div>
</div>
);
};
export default RowDetail;

@ -0,0 +1,213 @@
import React, { useState, useMemo } from "react";
import {
useTable,
useSortBy,
useGlobalFilter,
useFilters,
usePagination,
useRowSelect,
useColumnOrder,
useExpanded,
} from "react-table";
//components
// import Breadrumbs from "../Breadcrumbs";
import Toolbar from "./Layouts/Toolbar";
import Checkbox from "./components/Checkbox";
import { COLUMNS } from "./components/Columns";
import { ColumnSearch } from "../Table/components/ColumnSearch";
import Footer from "./Layouts/Footer";
import Main from "./Layouts/Main";
const Table = ({
data,
schema,
name,
Editor,
getData,
title,
tabs,
setTabs,
fake,
setFake,
activeOtherTab,
setActiveOtherTab,
add,
update,
}) => {
const [filtering, setFiltering] = useState(false);
const columns = useMemo(
() =>
COLUMNS({
schema: schema.filter((e) => !e.more),
filtering,
setFiltering,
setTabs,
name,
tabs,
fake,
setFake,
activeOtherTab,
setActiveOtherTab,
}),
[filtering, schema]
);
const EditableCell = ({
value: initialValue,
row: { index },
column: { id },
updateMyData, // This is a custom function that we supplied to our table instance
}) => {
// We need to keep and update the state of the cell normally
const [value, setValue] = React.useState(initialValue);
const onChange = (e) => {
setValue(e.target.value);
};
// We'll only update the external data when the input is blurred
const onBlur = () => {
updateMyData(index, id, value);
};
// If the initialValue is changed external, sync it up with our state
React.useEffect(() => {
setValue(initialValue);
}, [initialValue]);
return <input value={value} onChange={onChange} onBlur={onBlur} />;
};
const defaultColumn = React.useMemo(() => {
return {
Filter: ColumnSearch,
};
}, []);
const {
getTableProps,
getTableBodyProps,
headerGroups,
footerGroups,
rows,
page,
nextPage,
previousPage,
canNextPage,
canPreviousPage,
pageOptions,
gotoPage,
pageCount,
setPageSize,
prepareRow,
allColumns,
getToggleHideAllColumnsProps,
setColumnOrder,
selectedFlatRows,
state,
setGlobalFilter,
resetResizing,
} = useTable(
{
columns,
data,
defaultColumn,
},
useColumnOrder,
useFilters,
useGlobalFilter,
useSortBy,
useExpanded,
usePagination,
useRowSelect,
(hooks) => {
hooks.visibleColumns.push((columns) => {
return [
{
id: "selection",
Header: ({ getToggleAllRowsSelectedProps }) => (
<Checkbox {...getToggleAllRowsSelectedProps()} />
),
Cell: ({ row }) => (
<Checkbox {...row.getToggleRowSelectedProps()} />
),
},
...columns,
];
});
}
);
const { globalFilter, pageIndex, pageSize } = state;
const acitvieTab = tabs?.find((e) => e.isActive) || {};
return (
<div className="w-full flex flex-col flex-1">
<Toolbar
setTabs={setTabs}
tabs={tabs}
fake={fake}
setFake={setFake}
activeOtherTab={activeOtherTab}
setActiveOtherTab={setActiveOtherTab}
setGlobalFilter={setGlobalFilter}
globalFilter={globalFilter}
pageIndex={pageIndex}
pageOptions={pageOptions}
gotoPage={gotoPage}
canPreviousPage={canPreviousPage}
previousPage={previousPage}
canNextPage={canNextPage}
pageCount={pageCount}
nextPage={nextPage}
allColumns={allColumns}
getToggleHideAllColumnsProps={getToggleHideAllColumnsProps}
name={name}
/>
{acitvieTab.element == "table" && (
<Main
getTableProps={getTableProps}
headerGroups={headerGroups}
prepareRow={prepareRow}
filtering={filtering}
setTabs={setTabs}
tabs={tabs}
fake={fake}
setFake={setFake}
activeOtherTab={activeOtherTab}
setActiveOtherTab={setActiveOtherTab}
columns={columns}
getTableBodyProps={getTableBodyProps}
page={page}
rows={rows}
footerGroups={footerGroups}
name={name}
schema={schema}
data={data}
/>
)}
{acitvieTab.element == "table" && (
<Footer
pageIndex={pageIndex}
gotoPage={gotoPage}
pageOptions={pageOptions}
pageSize={pageSize}
setPageSize={setPageSize}
/>
)}
{acitvieTab.element == "editor" && (
<Editor
schema={schema}
data={acitvieTab.data}
add={add}
update={update}
/>
)}
{/* {acitvieTab.element == "viewer" && <Viewer />} */}
</div>
);
};
export default Table;

@ -13,6 +13,10 @@ const examQuestion = {
(data = {}) => (data = {}) =>
async (dispatch) => async (dispatch) =>
await proxy.post("examQuestion/add", data, { dispatch }), await proxy.post("examQuestion/add", data, { dispatch }),
addToNote:
(data = {}) =>
async (dispatch) =>
await proxy.post("examQuestion/noteQuestion/add", data, { dispatch }),
delete: delete:
(data = {}) => (data = {}) =>
async (dispatch) => async (dispatch) =>

@ -20,6 +20,7 @@ export { default as question } from "./question";
export { default as category } from "./category"; export { default as category } from "./category";
export { default as examQuestion } from "./examQuestion"; export { default as examQuestion } from "./examQuestion";
export { default as note } from "./note"; export { default as note } from "./note";
export { default as table } from "./table";

@ -0,0 +1,8 @@
const table = {
setTab:
(data = {}) =>
async (dispatch) =>
await dispatch({ type: "table/setTab", data }),
};
export default table;

@ -17,6 +17,9 @@ export default function examQuestion(state = initialState, action) {
case "examQuestion/add": case "examQuestion/add":
// toast.success("درخواست با موفقیت انجام شد"); // toast.success("درخواست با موفقیت انجام شد");
return { ...state, loading: false, error: null, lastAdd: data }; return { ...state, loading: false, error: null, lastAdd: data };
case "examQuestion/noteQuestion/add":
// toast.success("درخواست با موفقیت انجام شد");
return { ...state, loading: false, error: null, lastAdd: data };
case "examQuestion/list": case "examQuestion/list":
return { ...state, loading: false, error: null, list: data }; return { ...state, loading: false, error: null, list: data };
case "examQuestion/loading": case "examQuestion/loading":

@ -20,5 +20,6 @@ export { default as question } from "./question";
export { default as category } from "./category"; export { default as category } from "./category";
export { default as examQuestion } from "./examQuestion"; export { default as examQuestion } from "./examQuestion";
export { default as note } from "./note"; export { default as note } from "./note";
export { default as table } from "./table";

@ -0,0 +1,126 @@
const initialState = {
productTable: {
data: [
{
id: 1,
name: "خسرو سهرابی",
order_date: "1400/11/09",
order_price: 1,
order_number: "09120023100",
user_job: "مهندس ساختمان",
order_status: 1,
order_level: 1,
birth_place: "استان تهران شهر تهران",
office_phone: "02144407010",
education: "کارشناسی",
job: "فروشنده لوازم خانگی",
field_study: "مدیریت صنعتی",
birth_day: "1368/09/02",
uni: "تهران",
address:
"خیابان شریعتی نرسیده به خیابان دولت کوچه عباسی نبش تهیه غذای فارسی پلاک 4 واحد 3",
behaviour:
"بچه پرو و دریده شاخ اما کم آزار اهل رفاقت و مرام خلاق نوآور آشنا به صنایع خلاق",
},
{
id: 1,
name: "خسرو سهرابی",
order_date: "1400/11/09",
order_price: 1,
order_number: "09120023100",
user_job: "مهندس ساختمان",
order_status: 1,
order_level: 1,
birth_place: "استان تهران شهر تهران",
office_phone: "02144407010",
education: "کارشناسی",
job: "فروشنده لوازم خانگی",
field_study: "مدیریت صنعتی",
birth_day: "1368/09/02",
uni: "تهران",
address:
"خیابان شریعتی نرسیده به خیابان دولت کوچه عباسی نبش تهیه غذای فارسی پلاک 4 واحد 3",
behaviour:
"بچه پرو و دریده شاخ اما کم آزار اهل رفاقت و مرام خلاق نوآور آشنا به صنایع خلاق",
},
],
schema: [
{
description: "نام و نام خانوادگی",
name: "name",
type: "text",
sort: false,
},
{
description: "تاریخ سفارش",
name: "order_date",
filter: "SliderColumnFilter",
type: "text",
renderer: "date",
},
{
description: "مبلغ سفارش",
name: "order_price",
type: "text",
filter: "SliderColumnFilter",
},
{
description: "شماره سفارش",
name: "order_number",
type: "text",
},
{
description: "شغل کاربر",
name: "user_job",
type: "text",
more: true,
},
{
description: "وضعیت سفارش",
type: "text",
name: "order_status",
renderer: "OrderStatus",
filter: "SelectFilter",
more: true,
enum: {
1: "ارسال شده",
2: "رسیدگی",
3: "لغو شده",
4: "پیگیری",
},
},
{
description: "سطح اهمیت",
type: "text",
name: "order_level",
renderer: "OrderLevel",
filter: "SelectFilter",
more: true,
enum: {
1: "وضعیت عادی",
2: "اعلان سطح هشدار",
3: "بررسی و پیگیری",
4: "وضعیت بحرانی",
},
},
],
},
tabs: [],
// table2: {
// tabs: [],
// },
};
export default function table(state = initialState, action) {
let { type, data } = action;
switch (type) {
case "table/setTab":
console.log("reducer:")
console.log(data);
return {
...state,tabs:data
};
default:
return state;
}
}

@ -11,12 +11,21 @@ import { DragDropContext } from "react-beautiful-dnd";
import { Droppable } from "react-beautiful-dnd"; import { Droppable } from "react-beautiful-dnd";
import { Draggable } from "react-beautiful-dnd"; import { Draggable } from "react-beautiful-dnd";
import { toMap } from "draft-js/lib/DefaultDraftBlockRenderMap"; import { toMap } from "draft-js/lib/DefaultDraftBlockRenderMap";
import { category, exam, file, lesson, note } from "../../../redux/actions"; import {
category,
exam,
examQuestion,
file,
lesson,
note,
question,
} from "../../../redux/actions";
import loadingGiff from "../../../assets/img/Rolling-2s-223px.gif"; import loadingGiff from "../../../assets/img/Rolling-2s-223px.gif";
let lastCourseIndex; let lastCourseIndex;
let lastQuestionType = 2;
let eqCounter = 0;
let eqCounter2 = 0;
const AddQuestionnaireExam = ({ const AddQuestionnaireExam = ({
grades, grades,
addExamState, addExamState,
@ -39,6 +48,10 @@ const AddQuestionnaireExam = ({
subexamList, subexamList,
subexamAdd, subexamAdd,
lastSubExamAdd, lastSubExamAdd,
lastQuestionAdd,
addQuestion,
lastNoteQuestionAdd,
addNoteQuestion,
}) => { }) => {
const [questionarePopUp, setQuestionairePopUp] = useState(false); const [questionarePopUp, setQuestionairePopUp] = useState(false);
const [addCoursePopup, setAddCoursePopup] = useState(false); const [addCoursePopup, setAddCoursePopup] = useState(false);
@ -61,7 +74,7 @@ const AddQuestionnaireExam = ({
// name: "", // name: "",
// coefficient: 4, // coefficient: 4,
const [selectedCourse, setSelectedCourse] = useState(null); const [selectedCourse, setSelectedCourse] = useState(null);
const [subexamArray,setSubexamArray]=useState([]); const [subexamArray, setSubexamArray] = useState([]);
// { // {
// sortId: 1, // sortId: 1,
// title: "ادبیات فارسی", // title: "ادبیات فارسی",
@ -198,6 +211,7 @@ const AddQuestionnaireExam = ({
break; break;
} }
} }
lastQuestionType = item.optionsNumber || 2;
console.log({ console.log({
noteId: noteSelectedId, noteId: noteSelectedId,
name: item?.name, name: item?.name,
@ -210,7 +224,7 @@ const AddQuestionnaireExam = ({
}); });
subexamAdd({ subexamAdd({
pid: examId, pid: examId,
ord:index+1, ord: index + 1,
noteId: noteSelectedId, noteId: noteSelectedId,
name: item?.name, name: item?.name,
description: " ", description: " ",
@ -223,18 +237,136 @@ const AddQuestionnaireExam = ({
}; };
useEffect(() => { useEffect(() => {
if (lastSubExamAdd){ if (lastSubExamAdd) {
setSubexamArray([ setSubexamArray([
...subexamArray, ...subexamArray,
{ {
index: lastCourseIndex, index: lastCourseIndex,
subexamId: lastSubExamAdd, subexamId: lastSubExamAdd,
noteId: noteSelectedId, noteId: noteSelectedId,
type: lastQuestionType,
coefficient: newCourse?.coefficient,
}, },
]); ]);
} }
}, [lastSubExamAdd]); }, [lastSubExamAdd]);
//add question ===========================================================
const addQuestionHandler = (data) => {
let subExamData = subexamArray[lastCourseIndex];
let categoryId;
for (let i = 0; i < NoteList.length; i++) {
if (
NoteList[i].noteId === noteSelectedId &&
NoteList[i].name === newCourse?.name
) {
categoryId = NoteList[i]?.categoryId;
}
}
console.log("HHHHHHHHHHHHHHHH========================================");
console.log(newCourse);
console.log(data);
// questionStart;
// questionEnd;
let newList = subexamArray;
for (let i = 0; i < data.length; i++) {
if (newList[lastCourseIndex]?.question) {
newList[lastCourseIndex].question = [
...newList[lastCourseIndex]?.question,
{
number: Number(newCourse.questionStart) + i,
coefficient: subexamArray[lastCourseIndex]?.coefficient,
answer: data[i].answer,
},
];
} else {
newList[lastCourseIndex].question = [
{
number: Number(newCourse.questionStart) + i,
coefficient: subexamArray[i]?.coefficient,
answer: data[i].answer,
},
];
}
// addQuestion({
// text: `سوال دفترچه ${newCourse?.name}`,
// type: subExamData?.type,
// answer: String(data[i].answer),
// option1: "1",
// option2: "2",
// option3: subExamData?.type > 2 ? "3" : null,
// option4: subExamData?.type > 3 ? "4" : null,
// option5: subExamData?.type > 4 ? "5" : null,
// noteId: subExamData?.noteId,
// categoryId: categoryId,
// });
}
// let newList = subexamArray;
// if (newList[lastCourseIndex]?.question) {
// newList[lastCourseIndex].question = [
// ...newList[lastCourseIndex]?.question,
// {
// id: lastQuestionAdd,
// coefficient: subexamArray[lastCourseIndex]?.coefficient,
// },
// ];
// } else {
// newList[lastCourseIndex].question = [
// {
// id: lastQuestionAdd,
// coefficient: subexamArray[lastCourseIndex]?.coefficient,
// },
// ];
// }
setSubexamArray(newList);
setFakeState(Math.random() * 10000);
};
// useEffect(() => {
// // if (lastQuestionAdd) {
// // let newList = subexamArray;
// // if (newList[lastCourseIndex]?.question) {
// // newList[lastCourseIndex].question = [
// // ...newList[lastCourseIndex]?.question,
// // {
// // id: lastQuestionAdd,
// // coefficient: subexamArray[lastCourseIndex]?.coefficient,
// // },
// // ];
// // } else {
// // newList[lastCourseIndex].question = [
// // {
// // id: lastQuestionAdd,
// // coefficient: subexamArray[lastCourseIndex]?.coefficient,
// // },
// // ];
// // }
// // setSubexamArray(newList);
// // setFakeState(Math.random() * 10000);
// // }
// }, [lastQuestionAdd]);
// ==========================================================
const nextStepHandler = () => {
for (let i = 0; i < subexamArray.length; i++) {
addNoteQuestion({
examId: subexamArray[i].subexamId,
questions: subexamArray[i].question,
noteId: subexamArray[i].noteId,
});
// eqCounter++;
}
// stepClick();
};
useEffect(() => {
if (lastNoteQuestionAdd) {
eqCounter2++;
if (eqCounter2 === subexamArray.length) {
stepClick();
}
}
}, [lastNoteQuestionAdd]);
return ( return (
<> <>
{questionarePopUp && ( {questionarePopUp && (
@ -580,9 +712,12 @@ const AddQuestionnaireExam = ({
selectedQId={selectedQuestionare?.id} selectedQId={selectedQuestionare?.id}
questionEnd={selectedCourse?.questionEnd} questionEnd={selectedCourse?.questionEnd}
questionStart={selectedCourse?.questionStart} questionStart={selectedCourse?.questionStart}
passAnswers={(data) => passAnswers={(data) => {
dispatch({ type: "ADD_ANSWERS", payload: data }) // dispatch({ type: "ADD_ANSWERS", payload: data })
} console.log("|||||||||||||||||");
console.log(data);
addQuestionHandler(data);
}}
/> />
</div> </div>
</div> </div>
@ -599,9 +734,7 @@ const AddQuestionnaireExam = ({
<Button <Button
title="مرحله بعدی" title="مرحله بعدی"
className="w-1/12" className="w-1/12"
onClick={() => { onClick={nextStepHandler}
stepClick();
}}
/> />
</div> </div>
</> </>
@ -621,6 +754,8 @@ const mapStateToProps = (state) => ({
lastCategoryAdd: state.category.lastAdd, lastCategoryAdd: state.category.lastAdd,
subexamList: state.exam2.subExamList, subexamList: state.exam2.subExamList,
lastSubExamAdd: state.exam2.lastSubExamAdd, lastSubExamAdd: state.exam2.lastSubExamAdd,
lastQuestionAdd: state.question.lastAdd,
lastNoteQuestionAdd: state.examQuestion.lastAdd,
}); });
const mapDispatchToProps = { const mapDispatchToProps = {
@ -631,6 +766,8 @@ const mapDispatchToProps = {
addCategory: category.add, addCategory: category.add,
addQuestionnaireCourse: exam.addQuestionnaireCourse, addQuestionnaireCourse: exam.addQuestionnaireCourse,
subexamAdd: exam.subexamAdd, subexamAdd: exam.subexamAdd,
addQuestion: question.add,
addNoteQuestion: examQuestion.addToNote,
}; };
export default connect( export default connect(

@ -2,16 +2,28 @@ import React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
const AdminPanel = () => { const AdminPanel = () => {
return( return (
<div className='flex justify-center bg-gray-100 items-center w-full h-screen'> <div className="flex justify-center bg-gray-100 items-center w-full h-screen">
<Link to="add-product" className='bg-red52 text-white p-4 mx-2 rounded shadow-xl hover:shadow-none transition duration-500'> <Link
ورود به صفحه افزودن محصول to="add-product"
</Link> className="bg-red52 text-white p-4 mx-2 rounded shadow-xl hover:shadow-none transition duration-500"
<Link to="add-exam" className='bg-red52 text-white p-4 mx-2 rounded shadow-xl hover:shadow-none transition duration-500'> >
ورود به صفحه افزودن آزمون ورود به صفحه افزودن محصول
</Link> </Link>
</div> <Link
) to="add-exam"
className="bg-red52 text-white p-4 mx-2 rounded shadow-xl hover:shadow-none transition duration-500"
>
ورود به صفحه افزودن آزمون
</Link>
<Link
to="user-management"
className="bg-red52 text-white p-4 mx-2 rounded shadow-xl hover:shadow-none transition duration-500"
>
ورود به صفحه مدیریت کاربران
</Link>
</div>
);
} }
export default AdminPanel; export default AdminPanel;

@ -0,0 +1,105 @@
import React, { useMemo, useState } from "react";
import { useEffect } from "react";
import { connect } from "react-redux";
import Table from "../../components/Table";
import * as actions from "../../redux/actions";
import Editor from "./layouts/Editor";
const source = "user";
const title = " مدیریت کاربران";
const UserManagement = ({
info,
getData,
tabs,
setTabs,
add,
update,
getSchoolList,
schoolList
}) => {
useMemo(() => {
// getData({ num: 1000 });
getSchoolList({ productType :7});
}, []);
// console.log(info);
console.log(schoolList);
useEffect(() => {
setTabs([
{
id: 0,
name: title,
isActive: true,
element: "table",
},
]);
}, []);
console.log("ProductManagement render---------------------------------");
// console.log(tabs);
// const [tabs, setTabs] = useState([
// {
// id: 0,
// name: title,
// isActive: true,
// },
// ]);
const [activeOtherTab, setActiveOtherTab] = useState(false);
const [fake, setFake] = useState(0);
return (
<div
className="UserManagement w-[95.5%] h-screen max-h-screen bg-[#F8F8F8] overflow-y-auto rtl relative"
// style={{ width: "calc(100% - 4.5%)" }}
>
<div className="w-full h-full absolute top-0 left-0 bg-[#ffffff10] backdrop-blur z-[100] flex items-center justify-center">
<div className="w-1/2 py-4 px-2 rounded bg-white flex items-center justify-between">
<select
name="UserManagement-school-select"
id="UserManagement-school-select"
className="w-[70%] h-12 rounded px-1 py-0"
>
{schoolList?.map((item,index)=>(
<option key={index} value={item.id}>{item?.name}</option>
))}
</select>
<div className="h-12 w-[25%] rounded flex items-center justify-center text-white bg-red52">
تایید
</div>
</div>
</div>
<div className="flex flex-1 w-full pt-12 pb-4">
<Table
data={info.list || []}
schema={info.schema || []}
Editor={Editor}
add={add}
update={update}
getData={getData}
title={title}
tabs={tabs}
setTabs={setTabs}
fake={fake}
setFake={setFake}
activeOtherTab={activeOtherTab}
setActiveOtherTab={setActiveOtherTab}
/>
</div>
</div>
);
};
const mapStateToProps = (state) => ({
info: state[source],
tabs: state.table.tabs,
schoolList: state.book.list
});
const mapDispatchToProps = {
getData: actions[source].list,
setTabs: actions.table.setTab,
update: actions[source].update,
add: actions[source].add,
getSchoolList : actions.book.list
};
export default connect(mapStateToProps, mapDispatchToProps)(UserManagement);

@ -0,0 +1,202 @@
import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import { file } from "../../../redux/actions";
import loadingGiff from "../../../assets/img/Rolling-1s-200px.gif";
import SimpleDatePicker from "../../../components/SimpleDatePicker";
import moment from "jalali-moment";
const baseUrl = "https://light.barnamenegar.com/api/v1";
const components = (props) => ({
text: (
<input
type="text"
className="table-edit-input w-full py-3 px-2 rounded-md border border-[#D5DEEA]"
{...props}
/>
),
string: (
<textarea
type="text"
className="table-edit-input w-full py-3 px-2 rounded-md border border-[#D5DEEA]"
{...props}
/>
),
number: (
<input
type="number"
className="table-edit-input w-full py-3 px-2 rounded-md border border-[#D5DEEA]"
{...props}
/>
),
select: (
<select
type="text"
className="table-edit-input w-full py-3 px-2 rounded-md border border-[#D5DEEA]"
{...props}
>
{Object.entries(props?.options || {}).map(([v, k]) => (
<option value={v} key={v}>
{k}
</option>
))}
</select>
),
file: (
<div className="table-edit-input w-full py-3 px-2 rounded-md border border-[#D5DEEA] flex justify-between bg-[#fff]">
<div className="w-full flex items-center">
<div
className="w-fit w-24 text-center py-1 rounded bg-[#111] text-white text-sm cursor-pointer"
onClick={props.chooseFile}
>
انتخاب فایل
</div>
{/* <div className="text-xs mt-2 text-right">{props.fileNmae}</div> */}
{props.fileNmae && !props.loading && (
<div
className="w-fit w-24 py-1 rounded bg-[#65A9FF] text-white text-sm mr-2 cursor-pointer"
onClick={props.uploadHandler}
>
آپلود
</div>
)}
{props.loading && (
<img src={loadingGiff} alt="loadingGiff" className="h-10 mr-2" />
)}
<input
className="edit-file-input w-0 h-0 hidden"
id="edit-file-input"
type={"file"}
// onChange={(e) => {
// props.setFileName(e.target.value);
// }}
onChange={(e) => props.fileInputHandler(e)}
/>
</div>
{props.defaultValue && !props?.fileNmae ? (
<img
src={baseUrl + "/file/" + props.defaultValue}
className="edit-file-img-placeholder h-10"
/>
) : null}
{props?.fileNmae && (
<img src={props?.fileNmae} className="edit-file-img-placeholder h-10" />
)}
</div>
),
date: (
<div className="w-full">
<SimpleDatePicker returnValue={props.setDateInfo} mode={props.mode} />
</div>
),
});
const Filed = (props) => {
return components(props)[props.type] || <div>{props.type}</div>;
};
const Editor = ({ data, schema, update, add, loading,lastUpload,uploadFile }) => {
const [info, setInfo] = useState(data ? { id: data?.original?.id } : {});
const changeInputHandler = (e, key) => {
setInfo({ ...info, [key]: e.target.value });
};
useEffect(() => {
if (lastUpload) {
setInfo({ ...info, fileId: String(lastUpload) });
}
}, [lastUpload]);
const submitHandler = () => {
if (data) update(info);
else add(info);
console.log(info);
};
const [fileNmae, setFileName] = useState(null);
const chooseFile = () => {
document.querySelector(".edit-file-input").click();
};
const fileInputHandler = (e) => {
console.log(e.target.value);
if (e.target.files && e.target.files[0]) {
var reader = new FileReader();
reader.onload = function (event) {
setFileName(event.target.result);
};
reader.readAsDataURL(e.target.files[0]);
}
};
const uploadHandler=()=>{
uploadFile({
file: document.querySelector("#edit-file-input").files[0],
});
}
const [dateInfo,setDateInfo]=useState(null);
const datehandler=(value,mode)=>{
setDateInfo({ ...value,mode});
}
useEffect(() => {
console.log("::::::::::::::::::::::::::::::::::::::::::::-")
console.log(dateInfo);
if(dateInfo?.day){
setInfo({
...info,
[dateInfo.mode]: moment(
`${dateInfo?.year}/${dateInfo?.month}/${dateInfo?.day}`
)
.locale("en")
.format("YYYY-MM-DD"),
});
}
}, [dateInfo]);
return (
<div className="w-full py-3" key={data?.id || 0}>
{console.log({ data })}
<div className="w-full flex rtl flex-wrap items-center justify-between gap-y-6">
{schema?.map((item, index) => (
<div key={index} className="w-1/2 shrink-0 flex items-center ">
<div className="w-1/6 text-sm ml-2">{item?.description}:</div>
<Filed
type={
item?.options
? "select"
: item.name == "fileId"
? "file"
: item?.type
}
options={item?.options}
defaultValue={data?.original[item?.name]}
onChange={(e) => changeInputHandler(e, item?.name)}
fileNmae={fileNmae}
setFileName={setFileName}
chooseFile={chooseFile}
fileInputHandler={fileInputHandler}
uploadHandler={uploadHandler}
loading={loading}
setDateInfo={datehandler}
mode={item?.name}
/>
</div>
))}
</div>
<div
className="px-6 py-4 w-fit rounded-md bg-[#3C58EB] text-[#fff] text-base cursor-pointer mx-auto mt-6"
onClick={submitHandler}
>
{data ? "ثبت تغییرات" : "ثبت ردیف"}
</div>
</div>
);
};
const mapStateToProps = (state) => ({
loading: state.file.loading,
lastUpload: state.file.lastUpload,
});
const mapDispatchToProps = {
uploadFile : file.upload
};
export default connect(mapStateToProps, mapDispatchToProps)(Editor);

@ -7768,6 +7768,11 @@ react-slideshow-image@^3.7.0:
"@tweenjs/tween.js" "^18.1.2" "@tweenjs/tween.js" "^18.1.2"
resize-observer-polyfill "^1.5.1" resize-observer-polyfill "^1.5.1"
react-table@^7.8.0:
version "7.8.0"
resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.8.0.tgz#07858c01c1718c09f7f1aed7034fcfd7bda907d2"
integrity sha512-hNaz4ygkZO4bESeFfnfOft73iBUj8K5oKi1EcSHPAibEydfsX2MyU6Z8KCr3mv3C9Kqqh71U+DhZkFvibbnPbA==
react-toastify@^8.2.0: react-toastify@^8.2.0:
version "8.2.0" version "8.2.0"
resolved "https://registry.npmjs.org/react-toastify/-/react-toastify-8.2.0.tgz" resolved "https://registry.npmjs.org/react-toastify/-/react-toastify-8.2.0.tgz"

Loading…
Cancel
Save