diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..894437f --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local +pnpm-lock.ymal +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-lock.yaml diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..6a521b2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "cSpell.words": [ + "aldeen", + "Datepicker", + "formik", + "Karim", + "queryqlent", + "szhsin", + "Viewelement" + ] +} \ No newline at end of file diff --git a/db.json b/db.json new file mode 100644 index 0000000..7d19fac --- /dev/null +++ b/db.json @@ -0,0 +1,76 @@ +{ + "example": [ + { + "id": 1, + "name": "ibrahim", + "email": "ibrahim@gmail.com" + }, + { + "id": 2, + "name": "gregr", + "email": "ibrahimgmail.com" + }, + { + "id": 3, + "name": "mhmad", + "email": "mhmad@gmial.com" + }, + { + "id": 4, + "name": "soso", + "email": "soso@gmail.com" + }, + { + "id": 5, + "name": "few", + "email": "jfpwrej" + }, + { + "id": 6, + "name": "sos", + "email": "fdwf" + }, + { + "id": 7, + "name": "sos", + "email": "fdwf" + }, + { + "id": 8, + "name": "sos", + "email": "fdwf" + } + ], + "test2":[ + + + { + "id":1, + "email":"admin@adamin.com" + } + , + { + "id":1, + "email":"admin@adamin.com" + } + , + { + "id":1, + "email":"admin@adamin.com" + } + + , { + "id":1, + "email":"admin@adamin.com" + } + + ], + "users": [ + { + "id": 1, + "email": "admin@adamin.com", + "password": "password", + "token": "token" + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..6084c26 --- /dev/null +++ b/package.json @@ -0,0 +1,104 @@ +{ + "name": "my-app", + "version": "0.1.0", + "private": true, + "dependencies": { + "@ant-design/icons": "^4.8.3", + "@emotion/styled": "^11.11.0", + "@mui/icons-material": "^5.14.19", + "@mui/x-charts": "^6.19.4", + "@react-google-maps/api": "^2.19.2", + "@szhsin/react-menu": "github:szhsin/react-menu", + "@testing-library/jest-dom": "^5.17.0", + "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^13.5.0", + "@tinymce/tinymce-react": "^4.3.2", + "@types/jest": "^27.5.2", + "@types/node": "^16.18.60", + "@types/react": "^18.2.33", + "@types/react-dom": "^18.2.14", + "@types/socket.io-client": "^3.0.0", + "@wojtekmaj/react-daterange-picker": "^5.4.4", + "antd": "^5.12.1", + "apexcharts": "^3.44.2", + "axios": "^1.6.0", + "bootstrap": "^5.3.2", + "chart.js": "^4.4.0", + "dayjs": "^1.11.10", + "formik": "^2.4.5", + "history": "^5.3.0", + "i18next": "^23.6.0", + "i18next-browser-languagedetector": "^7.1.0", + "json-server": "^0.17.4", + "moment": "^2.30.1", + "prop-types": "^15.8.1", + "react": "^18.2.0", + "react-apexcharts": "^1.4.1", + "react-bootstrap": "^2.9.1", + "react-bootstrap-sweetalert": "^5.2.0", + "react-chartjs-2": "^5.2.0", + "react-confirm-alert": "^3.0.6", + "react-data-table-component": "^7.5.4", + "react-dom": "^18.2.0", + "react-feather": "^2.0.10", + "react-i18next": "^13.3.1", + "react-icons": "^4.11.0", + "react-input-range": "^1.3.0", + "react-number-format": "^5.3.4", + "react-query": "^3.39.3", + "react-redux": "^8.1.3", + "react-router-dom": "^6.18.0", + "react-scripts": "5.0.1", + "react-select": "^5.7.7", + "react-simple-star-rating": "^5.1.7", + "react-switch": "^7.0.0", + "react-tabs": "^6.0.2", + "react-toastify": "^9.1.3", + "react-toggle": "^4.1.3", + "reactstrap": "^9.2.0", + "redux": "^4.2.1", + "sass": "^1.69.5", + "socket.io-client": "^4.7.2", + "styled-components": "5.3.3", + "ts-xlsx": "^0.0.11", + "typescript": "^4.9.5", + "web-vitals": "^2.1.4", + "xlsx": "^0.18.5", + "yup": "^1.3.2", + "zustand": "^4.4.5" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject", + "g:api": "node src/Extensions/FileGenerator/generateApi.js", + "g:column": "node src/Extensions/FileGenerator/generateColumn.js", + "g:formutil": "node src/Extensions/FileGenerator/generateformUtils.js", + "g:page": "node src/Extensions/FileGenerator/generatePage.js", + "g:dashboard": "node src/Extensions/FileGenerator/generateDashboard.js ", + "g:modal:add": "node src/Extensions/FileGenerator/generateEditModal.js ", + "g:model:edit": "node src/Extensions/FileGenerator/generateEditModal.js " + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@types/react-toggle": "^4.0.5" + } +} diff --git a/public/Layout/Ar.svg b/public/Layout/Ar.svg new file mode 100644 index 0000000..c409129 --- /dev/null +++ b/public/Layout/Ar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/Layout/En.svg b/public/Layout/En.svg new file mode 100644 index 0000000..b1db6ff --- /dev/null +++ b/public/Layout/En.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/Layout/LoginBg.jpg b/public/Layout/LoginBg.jpg new file mode 100644 index 0000000..fc3a090 Binary files /dev/null and b/public/Layout/LoginBg.jpg differ diff --git a/public/Logo.svg b/public/Logo.svg new file mode 100644 index 0000000..eb87722 --- /dev/null +++ b/public/Logo.svg @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..ce82bbd --- /dev/null +++ b/public/index.html @@ -0,0 +1,17 @@ + + + + + + + + Point - App + + +
+ + + diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..080d6c7 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..e9e57dc --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..722fd7d --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,69 @@ +import { Fragment, lazy, Suspense } from 'react'; +import { Route, Routes } from 'react-router-dom' +import Loading from './Components/Utils/Loading/Loading'; +import { RoutesLinks } from './Routes'; +import Layout from './Layout/app/Layout'; +import Auth from './Pages/Auth/Page'; +const Page404 = lazy(() => import("./Layout/app/NotFoundPage")) + +const App = () => { + + + return ( + + {/* 404 Page */} + }> } /> + {/* Login Page */} + }> } /> + + {/* route not in navigation */} + + + {/* All Routes */} + {RoutesLinks?.map((item: any, index: number) => ( + + + + + if(item?.element){ + } > + + {item?.element ?? "Please Add Element Props in Routes"} + + + } + /> + }else{ + item?.children?.map((item:any)=>{ + return( + } > + + {item?.element ?? "Please Add Element Props in Routes"} + + + } + /> + ) + }) + + } + + + + )) + } + + + + + ) +} + +export default App \ No newline at end of file diff --git a/src/Components/Columns/ColumnsImage.tsx b/src/Components/Columns/ColumnsImage.tsx new file mode 100644 index 0000000..0247df7 --- /dev/null +++ b/src/Components/Columns/ColumnsImage.tsx @@ -0,0 +1,97 @@ +import { + DownloadOutlined, + RotateLeftOutlined, + RotateRightOutlined, + SwapOutlined, + ZoomInOutlined, + ZoomOutOutlined, +} from '@ant-design/icons'; +import React from 'react'; +import { Image, Space } from 'antd'; +import useImageError from '../../Hooks/useImageError'; + + +const ColumnsImage= ({src}:any) => { + const ErrorImage = "https://upload.wikimedia.org/wikipedia/commons/thumb/6/65/No-Image-Placeholder.svg/832px-No-Image-Placeholder.svg.png" + + const imageUrl = src || ErrorImage; + + const handleError = useImageError; + // or you can download flipped and rotated image + // https://codesandbox.io/s/zi-ding-yi-gong-ju-lan-antd-5-7-0-forked-c9jvmp + const onDownload = (url:any) => { + + fetch(url) + .then((response) => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.blob(); + }) + .then((blob) => { + const link = document.createElement('a'); + link.href = URL.createObjectURL(blob); + + // Extract file name from URL + const fileName = url.split('/').pop(); + link.download = fileName || 'download'; // If file name cannot be extracted, set a default name + + document.body.appendChild(link); + link.click(); + URL.revokeObjectURL(link.href); + link.remove(); + }) + .catch((error) => { + console.error('Error fetching resource:', error); + }); + }; + + + return ( + ( + + {/* */} + + + + + + + + ), + }} + onError={handleError} + + /> + ); +}; + +export default ColumnsImage; + + + + + + + +// { +// name: t("image"), +// center: "true", +// cell: (row: any) => { +// return ( +// +// ) +// } +// }, \ No newline at end of file diff --git a/src/Components/Columns/ColumnsSwitch.tsx b/src/Components/Columns/ColumnsSwitch.tsx new file mode 100644 index 0000000..1b155b4 --- /dev/null +++ b/src/Components/Columns/ColumnsSwitch.tsx @@ -0,0 +1,42 @@ +import { Switch } from 'antd' +import React from 'react' +import { CheckOutlined, CloseOutlined } from '@ant-design/icons'; +import { useFormikContext } from 'formik'; +export interface ColumnsSwitchProps { + name: string; + Front?: string; + Back?: string; + onChange?: (checked:any,event:any) => any; + icon?: boolean + Checked?:boolean + +} +const ColumnsSwitch = (props: ColumnsSwitchProps) => { + const { name, Front, Back, icon, onChange } = props; + const formik = useFormikContext(); + const onSwitchChange = (checked: boolean, event: Event) => { + // formik.setFieldValue("status", checked) + + + + }; + return ( + : Front} + unCheckedChildren={icon ? : Back} + onChange={ (checked:any,event:any)=> onChange ? onChange(checked,event) : onSwitchChange(checked,event)} + /> + ) +} + +export default ColumnsSwitch + + +ColumnsSwitch.defaultProps = { + Front: "Front", + Back: "Back", + onChange: undefined, + icon: false, + Checked:false + +}; \ No newline at end of file diff --git a/src/Components/Ui/Alert.tsx b/src/Components/Ui/Alert.tsx new file mode 100644 index 0000000..c232160 --- /dev/null +++ b/src/Components/Ui/Alert.tsx @@ -0,0 +1,50 @@ +import React from "react"; +import { confirmAlert } from "react-confirm-alert"; +import SweetAlert from "react-bootstrap-sweetalert"; +import { useTranslation } from "react-i18next"; + +export default function CustomConfirmAlert(options : any) { + confirmAlert({ + customUI: ({ onClose }) => , + }); +} + +type CustomUIProps ={ + onClose :()=> void + options:{ + title?:string + confirmBtnText:string + cancelBtnText:string + body:string + onConfirm:()=>void + + + } +} +function CustomUI({ onClose, options }:CustomUIProps) { + + + const [t] = useTranslation() + return ( +
+ { + options.onConfirm(); + onClose(); + }} + onCancel={onClose} + + /> +
+ + ); +} diff --git a/src/Components/Ui/CheckboxesVuexy.tsx b/src/Components/Ui/CheckboxesVuexy.tsx new file mode 100644 index 0000000..5bc635e --- /dev/null +++ b/src/Components/Ui/CheckboxesVuexy.tsx @@ -0,0 +1,49 @@ +import React from 'react'; + +interface CheckBoxesVuexyProps { + className?: string; + color: string; + defaultChecked?: boolean; + checked?: boolean; + value?: string; + disabled?: boolean; + onClick?: () => void; + onChange?: () => void; + size?: string; + icon: React.ReactNode; + label: string; +} + +const CheckBoxesVuexy: React.FC = ({ + className = '', + color, + defaultChecked, + checked, + value, + disabled, + onClick, + onChange, + size = 'md', + icon, + label, +}) => { + return ( +
+ + + {icon} + + {label} +
+ ); +}; + +export default CheckBoxesVuexy; diff --git a/src/Components/Ui/FileInput.tsx b/src/Components/Ui/FileInput.tsx new file mode 100644 index 0000000..94c5df0 --- /dev/null +++ b/src/Components/Ui/FileInput.tsx @@ -0,0 +1,27 @@ +import React from 'react' +import 'bootstrap/dist/css/bootstrap.min.css'; +import { Input } from 'reactstrap'; +import { useFormikContext } from 'formik'; + +type FileInputProps = { + name:string, + label:string, + accept:string, + onChange:any +} +function FileInput({name , accept="image/*" ,label , onChange} :FileInputProps) { + + + return ( +
+ + + +
+ ) +} + +export default FileInput \ No newline at end of file diff --git a/src/Components/Ui/HovarableImage.tsx b/src/Components/Ui/HovarableImage.tsx new file mode 100644 index 0000000..c56b31c --- /dev/null +++ b/src/Components/Ui/HovarableImage.tsx @@ -0,0 +1,37 @@ +import React from "react"; +import { Tooltip } from "reactstrap"; + +const tooltipStyle = { + backgroundColor: "white", + border: "2px solid lightgrey", + maxWidth: "400px", +}; + +const HovarableImage = ({ id, src, imgPreviewProps = {}, ...props }:any) => { + const [isOpen, setIsOpen] = React.useState(false); + const ID = `image_hover_tooltip_${id}`; + const toggleTooltip = React.useCallback(() => setIsOpen((prev) => !prev), []); + + return ( +
+ {props.alt} + + {props.alt} + +
+ ); +}; + +export default HovarableImage; + diff --git a/src/Components/Ui/ImagePreview.tsx b/src/Components/Ui/ImagePreview.tsx new file mode 100644 index 0000000..04e2177 --- /dev/null +++ b/src/Components/Ui/ImagePreview.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; + +const ImagePreview = ({ preview, height = 200 }:any) => { + const [t] = useTranslation(); + + return ( +
+ {preview ? ( + + ) : ( +
{t("image_preview")}
+ )} +
+ ); +}; + +export default ImagePreview; diff --git a/src/Components/Ui/LoadingButton.tsx b/src/Components/Ui/LoadingButton.tsx new file mode 100644 index 0000000..248c486 --- /dev/null +++ b/src/Components/Ui/LoadingButton.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import { Button, Spinner } from "reactstrap"; + +const LoadingButton = ({ isLoading = false, ...props }) => { + return ( + + ); +}; + +export { LoadingButton }; diff --git a/src/Components/Ui/LoadingSpinner.tsx b/src/Components/Ui/LoadingSpinner.tsx new file mode 100644 index 0000000..efa5055 --- /dev/null +++ b/src/Components/Ui/LoadingSpinner.tsx @@ -0,0 +1,10 @@ +import { Spin } from 'antd' +import React from 'react' + +function LoadingSpinner() { + return ( + + ) +} + +export default LoadingSpinner \ No newline at end of file diff --git a/src/Components/Ui/PasswordField/PasswordField.tsx b/src/Components/Ui/PasswordField/PasswordField.tsx new file mode 100644 index 0000000..32489d7 --- /dev/null +++ b/src/Components/Ui/PasswordField/PasswordField.tsx @@ -0,0 +1,57 @@ +import React, { FC, useState } from "react"; +import { ErrorMessage, useField, Field } from "formik"; +import { FormGroup } from "reactstrap"; +// import PropTypes from "prop-types"; +import { Eye, EyeOff } from "react-feather"; +import "./index.css"; + +interface PasswordFieldProps { + name: string; + label?: string; +} + +const PasswordField: FC = ({ name, label, ...props }) => { + const [field, meta] = useField({ name, ...props }); + const [showPassword, setShowPassword] = useState(false); + const EyeIcon = showPassword ? Eye : EyeOff; + + const toggleShow = () => { + setShowPassword((prev) => !prev); + }; + + return ( + <> + {label && } + + +
+ +
+ +
+ + ); +}; + +// PasswordField.propTypes = { +// name: PropTypes.string.isRequired, +// }; + +export { PasswordField }; \ No newline at end of file diff --git a/src/Components/Ui/PasswordField/index.css b/src/Components/Ui/PasswordField/index.css new file mode 100644 index 0000000..7a26d93 --- /dev/null +++ b/src/Components/Ui/PasswordField/index.css @@ -0,0 +1,10 @@ +.back-icon { + font-size: 18px; + margin: 0; + margin-right: 5px; +} + +.back-btn { + padding-left: 0.8rem; + padding-right: 1rem; +} diff --git a/src/Components/Ui/ProgressBar.tsx b/src/Components/Ui/ProgressBar.tsx new file mode 100644 index 0000000..146339f --- /dev/null +++ b/src/Components/Ui/ProgressBar.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import { Progress } from "reactstrap"; + +const ProgressBar = ({ value,isLoading, isSuccess, isError }:any) => { + + + let color = ""; + if (!isLoading && isSuccess) { + color = "success"; + } + if (!isLoading && isError) { + color = "danger"; + } + return ( +
+ + {value}% + +
+ ); +}; + + +export default ProgressBar; diff --git a/src/Components/Ui/SelectField.tsx b/src/Components/Ui/SelectField.tsx new file mode 100644 index 0000000..f794e31 --- /dev/null +++ b/src/Components/Ui/SelectField.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import Select from "react-select"; +import { ValidatedField } from "./ValidatedField"; +import { useFormikContext } from "formik"; + +const SelectField = ({ label, name, options, ...props }:any) => { + const formik = useFormikContext(); + + return ( + opt.value === formik?.values[name]) || ""} + onChange={(opt:any) => formik.setFieldValue(name, opt.value)} + onBlur={() => formik.setFieldTouched(name)} + key={name} + {...props} + /> + ); +}; + + +export default SelectField; + + diff --git a/src/Components/Ui/StaticsCard/StaticCard.tsx b/src/Components/Ui/StaticsCard/StaticCard.tsx new file mode 100644 index 0000000..bd5fddc --- /dev/null +++ b/src/Components/Ui/StaticsCard/StaticCard.tsx @@ -0,0 +1,58 @@ +import React from "react"; +import { Card, CardBody } from "reactstrap"; +import { ChartTypeEnum } from "../../../enums/ChartTypeEnum"; +import { history } from "../../../ProviderContainer"; +import { useNavigate } from "react-router-dom"; + +interface StatisticsCardProps { + className?: string; + iconLeft?: boolean; + icon: React.ReactNode; + count?: string; + CardTitle?: string; + CardContent?: string; + height?: number; + pathWhenClick :string ; + +} + +const StatisticsCard = (props :StatisticsCardProps) => { + + const navigate = useNavigate() + const { + className, + iconLeft = false , + icon, + count, + CardTitle, + CardContent, + pathWhenClick, + height, + ...rest + } = props; + + return ( + navigate(pathWhenClick )}> + +
+
+ {/*

{CardTitle}

*/} +
{icon}
+
+
+
+ {/*

{count}

*/} +

{CardContent}

+
+
+
+ ); +}; + +export default StatisticsCard; diff --git a/src/Components/Ui/StatusBadge.tsx b/src/Components/Ui/StatusBadge.tsx new file mode 100644 index 0000000..1fb55e6 --- /dev/null +++ b/src/Components/Ui/StatusBadge.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { Badge } from "reactstrap"; + +const StatusBadge = ({ status }:any) => { + const [t] = useTranslation(); + + return ( + + {status ? t("active") : t("inacticve")} + + ); +}; + + + +export default StatusBadge; diff --git a/src/Components/Ui/TableActions.tsx b/src/Components/Ui/TableActions.tsx new file mode 100644 index 0000000..73c9563 --- /dev/null +++ b/src/Components/Ui/TableActions.tsx @@ -0,0 +1,38 @@ +import React , {ReactNode} from "react"; +import confirmAlert from "./Alert"; +import { FaEdit, FaTrash } from "react-icons/fa"; + +type TableActionsProps = { + onDelete: () => any; + onEdit: () => void; + showEdit?: boolean; + showDelete?: boolean; + children?: ReactNode; +}; + + +const TableActions = ({ onDelete , onEdit,showEdit=true,showDelete=true, children }:TableActionsProps) => { + + + return ( +
+ {showEdit && } + {showDelete && ( + + confirmAlert({ + onConfirm: () => { + onDelete(); + + }, + }) + } + className="cursor-pointer" + size={20} + /> + )} + {children} +
+); +}; +export default TableActions; diff --git a/src/Components/Ui/ThreeSwitchState/TripleSwitch.tsx b/src/Components/Ui/ThreeSwitchState/TripleSwitch.tsx new file mode 100644 index 0000000..c36ffbd --- /dev/null +++ b/src/Components/Ui/ThreeSwitchState/TripleSwitch.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import type { RadioChangeEvent } from 'antd'; +import { Radio } from 'antd'; + +const onChange = (e: RadioChangeEvent) => { + console.log(`radio checked:${e.target.value}`); +}; + +const App: React.FC = () => ( + <> + + + + Hangzhou + + Shanghai + + Beijing + Chengdu + + + + Hangzhou + Shanghai + Beijing + Chengdu + + +); + +export default App; diff --git a/src/Components/Ui/ToggleStatus.tsx b/src/Components/Ui/ToggleStatus.tsx new file mode 100644 index 0000000..40df144 --- /dev/null +++ b/src/Components/Ui/ToggleStatus.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import Toggle from "react-toggle"; +import "react-toggle/style.css"; +import StatusBadge from "./StatusBadge"; +import { useTranslation } from "react-i18next"; + +interface ToggleStatusProps { + +} +export const ToggleStatus = ({ object, handleSwitch, toggleMutation, ...props }:any) => { + const [t] = useTranslation(); + + // const handleSwitch = () => { + // toggleMutation.mutate({ + // id: object.id, + // new_status: !object.is_active, + // }); + // }; + + return ( + <> +
+

+ {object.is_active ? t("active") : t("inactive")} +

+ +
+ + ); +}; diff --git a/src/Components/Ui/ValidatedField.tsx b/src/Components/Ui/ValidatedField.tsx new file mode 100644 index 0000000..c2bd089 --- /dev/null +++ b/src/Components/Ui/ValidatedField.tsx @@ -0,0 +1,64 @@ +import React from "react"; +import { ErrorMessage, useField, Field } from "formik"; +import { FormGroup } from "reactstrap"; +import { useTranslation } from "react-i18next"; + +const ValidatedField = ({ + name, + label, + CustomField, + icon: Icon, + optional, + labelIcon = null, + formProps, + isRequired, + + ...props +}:any) => { + + const [field, meta] = useField({ name, ...props }); + const [t] = useTranslation(); + + let Wrapper = Field; + + if (CustomField) { + Wrapper = CustomField; + } + const fieldProps = props.type === "file" ? {} : { ...field }; + + return ( + <> + {label && ( + + )} + + + {Icon && ( +
+ +
+ )} + + {(msg) => {t(msg)}} + +
+ + ); +}; + + +export { ValidatedField }; diff --git a/src/Components/Ui/index.tsx b/src/Components/Ui/index.tsx new file mode 100644 index 0000000..ae6a6f0 --- /dev/null +++ b/src/Components/Ui/index.tsx @@ -0,0 +1,17 @@ +import Checkbox from './CheckboxesVuexy' +import ImagePreview from './ImagePreview' +import SelectField from './SelectField' +import { useImagePreview } from './useImagePreview' +import {ValidatedField} from './ValidatedField' +import StatusBadge from './StatusBadge' +import HovarableImage from './HovarableImage' + +export { + Checkbox, + ImagePreview, + SelectField, + useImagePreview, + ValidatedField, + StatusBadge, + HovarableImage +} \ No newline at end of file diff --git a/src/Components/Ui/tables/Actions.tsx b/src/Components/Ui/tables/Actions.tsx new file mode 100644 index 0000000..f78e1ad --- /dev/null +++ b/src/Components/Ui/tables/Actions.tsx @@ -0,0 +1,58 @@ +import React , {ReactNode} from "react"; +import { FaEdit, FaEye, FaTrash } from "react-icons/fa"; +import CustomConfirmAlert from "../Alert"; +import { usePageState } from "../../../lib/state mangment/LayoutPagestate"; + +type TableActionsProps = { + onDelete?: () => any; + onEdit?: () => any; + onView?:() => any; + showView?: boolean; + showEdit?: boolean; + showDelete?: boolean; + children?: ReactNode; + objectToEdit:any + className?:string + +}; + + +const TableActions = ({ onDelete=()=>{} , objectToEdit,onEdit=()=>{},onView,showEdit=true,showDelete=true,showView=true,children,className }:TableActionsProps) => { +// const TableActions = ({ onDelete=()=>{} , objectToEdit,onEdit=()=>{},onView,showEdit=true,showDelete=true,showView=true,children }:TableActionsProps) => { + + // console.log(objectToEdit); + + const {setObjectToEdit , setIsOpenEditModel} = usePageState() + return ( +
+ {showEdit && { + setObjectToEdit(objectToEdit) + setIsOpenEditModel() + onEdit() + + }} className="cursor-pointer m-2" size={20} />} + {showView && } + + + {showDelete && ( + + CustomConfirmAlert({ + onConfirm: () => { + + + onDelete(); + + }, + }) + } + className="cursor-pointer" + size={20} + /> + )} + + {children} +
+); +}; +export default TableActions; diff --git a/src/Components/Ui/tables/ConfirmAlert.tsx b/src/Components/Ui/tables/ConfirmAlert.tsx new file mode 100644 index 0000000..bb99f66 --- /dev/null +++ b/src/Components/Ui/tables/ConfirmAlert.tsx @@ -0,0 +1,40 @@ +import React from "react"; +import { confirmAlert } from "react-confirm-alert"; +import SweetAlert from "react-bootstrap-sweetalert"; + +interface CustomUIProps { + onClose: () => void; + options: { + title?: string; + confirmBtnText?: string; + cancelBtnText?: string; + onConfirm: () => void; + body?: string; + }; +} + +export default function CustomConfirmAlert(options: any) { + confirmAlert({ + customUI: ({ onClose }) => , + }); +} + +function CustomUI({ onClose, options }: CustomUIProps) { + const sweetAlertProps: any = { + title: options.title || `DELETE, Are you sure?`, + warning: true, + show: true, + showCancel: true, + reverseButtons: true, + cancelBtnBsStyle: "danger", + confirmBtnText: options.confirmBtnText || "Yes, delete it!", + cancelBtnText: options.cancelBtnText || "Cancel", + onConfirm: () => { + options.onConfirm(); + onClose(); + }, + onCancel: onClose, + }; + + return {options.body || "You won't be able to revert this!"}; +} diff --git a/src/Components/Ui/useImagePreview.tsx b/src/Components/Ui/useImagePreview.tsx new file mode 100644 index 0000000..818c68e --- /dev/null +++ b/src/Components/Ui/useImagePreview.tsx @@ -0,0 +1,24 @@ +import { useState, useEffect } from "react"; + +export const useImagePreview = (defaultValue:any = null) => { + const [preview, setPreview] = useState(defaultValue || null); + + useEffect(() => { + return () => { + URL.revokeObjectURL(preview); + + }; + }, [preview]); + + const handleImageChange = (event:any) => { + + setPreview(URL.createObjectURL(event.target.files[0])); + + }; + + return { + preview, + handleImageChange, + setPreview, + }; +}; diff --git a/src/Components/Utils/BlockModal.tsx b/src/Components/Utils/BlockModal.tsx new file mode 100644 index 0000000..d6c47f0 --- /dev/null +++ b/src/Components/Utils/BlockModal.tsx @@ -0,0 +1,62 @@ + +import React, { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { BsExclamationCircle } from 'react-icons/bs'; +import { Button, Card, CardBody, Input, Label, Modal, ModalHeader } from 'reactstrap'; +import { useCommonModelState } from '../../lib/state mangment/driver&customer/ModelState'; +import { LoadingButton } from '../Ui/LoadingButton'; + +interface BlockModelProps { + Mutation:any, + type :'customer' |'driver' +} + +const BlockModel: React.FC = ({Mutation ,type}) => { + const {t} = useTranslation(); + const key_to_api = type == t('customer') ? t('customer_id') : t("driver_id") + + const {isOpenBlock:isOpen , objectID , setIsopenBlock:setIsOpen} = useCommonModelState() + + const handleSubmit = () => { + const blockInput = document.getElementById('block_input') as HTMLInputElement; + if (blockInput) { + Mutation.mutate({ [key_to_api]: objectID, block_timer: blockInput.value }); + } + }; + + useEffect(() => { + if (Mutation.isSuccess) { + setIsOpen(); + } + }, [Mutation.isSuccess, setIsOpen]); + + return ( + + setIsOpen()}> + {t("al")}{type}{t('_block_page')} + + + +
+

{t('blocking_')}{type}

+ +
+ + +
+ + + {t('add_block_for_')}{type} + +
+
+
+
+
+
+ ); +}; + +export default BlockModel; diff --git a/src/Components/Utils/GiftModal.tsx b/src/Components/Utils/GiftModal.tsx new file mode 100644 index 0000000..bbd8d3e --- /dev/null +++ b/src/Components/Utils/GiftModal.tsx @@ -0,0 +1,63 @@ +import React, { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { BsExclamationCircle } from 'react-icons/bs'; +import { Button, Card, CardBody, Col, Input, Label, Modal, ModalHeader, Row } from 'reactstrap'; +import { useCommonModelState } from '../../lib/state mangment/driver&customer/ModelState'; +import { LoadingButton } from '../Ui/LoadingButton'; + + +interface GiftModalProps { + Mutation:any, + type :'customer' |'driver' +} + +const GiftModal: React.FC = ({Mutation ,type }) => { + const {t} = useTranslation(); + + const {isOpenGift:isOpen , objectID , setIsopenGift:setIsOpen} = useCommonModelState() + + useEffect(() => { + if (Mutation.isSuccess) { + setIsOpen(); + } + }, [Mutation.isSuccess, setIsOpen]); + + const handleGift = () => { + const enterCodesInput = document.getElementById('enter_codes') as HTMLInputElement; + if (enterCodesInput && enterCodesInput.value) { + Mutation.mutate({ type, id: objectID, value: enterCodesInput.value }); + } + }; + + return ( + + setIsOpen()}> + {t("al")}{type} {t('_gift_page')} + + + + + + + + + + + {t('give')} + + + + + + + + ); +}; + +export default GiftModal; diff --git a/src/Components/Utils/Loading/Loading.scss b/src/Components/Utils/Loading/Loading.scss new file mode 100644 index 0000000..9ffb658 --- /dev/null +++ b/src/Components/Utils/Loading/Loading.scss @@ -0,0 +1,93 @@ +.Loading{ + + .wrapper { + width: 200px; + height: 60px; + position: relative; + left: 40%; + z-index: 1; + } + + .circle { + width: 20px; + height: 20px; + position: absolute; + border-radius: 50%; + background-color: var(--primary); + left: 15%; + transform-origin: 50%; + animation: circle7124 .5s alternate infinite ease; + } + + @keyframes circle7124 { + 0% { + top: 60px; + height: 5px; + border-radius: 50px 50px 25px 25px; + transform: scaleX(1.7); + } + + 40% { + height: 20px; + border-radius: 50%; + transform: scaleX(1); + } + + 100% { + top: 0%; + } + } + + .circle:nth-child(2) { + left: 45%; + animation-delay: .2s; + } + + .circle:nth-child(3) { + left: auto; + right: 15%; + animation-delay: .3s; + } + + .shadow { + width: 20px; + height: 4px; + border-radius: 50%; + background-color: rgba(0,0,0,0.9); + position: absolute; + top: 62px; + transform-origin: 50%; + z-index: -1; + left: 15%; + filter: blur(1px); + animation: shadow046 .5s alternate infinite ease; + } + + @keyframes shadow046 { + 0% { + transform: scaleX(1.5); + } + + 40% { + transform: scaleX(1); + opacity: .7; + } + + 100% { + transform: scaleX(.2); + opacity: .4; + } + } + + .shadow:nth-child(4) { + left: 45%; + animation-delay: .2s + } + + .shadow:nth-child(5) { + left: auto; + right: 15%; + animation-delay: .3s; + } + +} diff --git a/src/Components/Utils/Loading/Loading.tsx b/src/Components/Utils/Loading/Loading.tsx new file mode 100644 index 0000000..19651b4 --- /dev/null +++ b/src/Components/Utils/Loading/Loading.tsx @@ -0,0 +1,19 @@ +import React from 'react' +import './Loading.scss' +const Loading = () => { + + return ( +
+
+
+
+
+
+
+
+
+
+ ) +} + +export default Loading \ No newline at end of file diff --git a/src/Components/Utils/SearchBar/SearchBar.scss b/src/Components/Utils/SearchBar/SearchBar.scss new file mode 100644 index 0000000..6834dcf --- /dev/null +++ b/src/Components/Utils/SearchBar/SearchBar.scss @@ -0,0 +1,43 @@ +.SearchBar{ + .group { + display: flex; + align-items: center; + position: relative; + max-width: 190px; + } + + .input { + width: 100%; + height: 40px; + padding: 0 1rem; + padding-left: 2.5rem; + border-radius: 8px; + outline: none; + font-weight: 500; + background: var(--primary); + color: var(--bg); + border: none; + box-shadow: 2px 2px 7px 0 var(--primary); + + } + + .input::placeholder { + color: var(--bg); + } + + + + .icon { + position: absolute; + left: 1rem; + fill: var(--bg); + width: 1rem; + height: 1rem; + } + + + + + + +} \ No newline at end of file diff --git a/src/Components/Utils/SearchBar/SearchBar.tsx b/src/Components/Utils/SearchBar/SearchBar.tsx new file mode 100644 index 0000000..f1f5cd2 --- /dev/null +++ b/src/Components/Utils/SearchBar/SearchBar.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import './SearchBar.scss' +const SearchBar = () => { + return ( +
+
+ + +
+
+ ) +} + +export default SearchBar \ No newline at end of file diff --git a/src/Components/Utils/Theme.tsx b/src/Components/Utils/Theme.tsx new file mode 100644 index 0000000..7681ffd --- /dev/null +++ b/src/Components/Utils/Theme.tsx @@ -0,0 +1,84 @@ +import { Menu, MenuItem, MenuButton } from '@szhsin/react-menu'; +import { useTranslation } from 'react-i18next'; +import { usePageState } from '../../lib/state mangment/LayoutPagestate'; +import { BsFillMoonStarsFill, BsFillSunFill, BsSunglasses } from 'react-icons/bs'; + + +let What_the_Theme = localStorage.getItem('theme') ?? "light"; + +if (What_the_Theme === "dark") { + + document.body.classList.add('dark')} + else if (What_the_Theme === "glass") { + + document.body.classList.add('glass') + } + + + + +export default function Theme() { + const {t} = useTranslation(); + + const {setThemChange} = usePageState() + + const changeTheme = (newTheme : any) => { + + + if(newTheme === "dark"){ + document.body.classList.remove('glass'); + document.body.classList.add('dark');localStorage.setItem("theme", "dark"); + What_the_Theme = "dark" + } + else if(newTheme === "light"){ + document.body.classList.remove('glass'); + document.body.classList.remove('dark');localStorage.setItem("theme", "light"); + What_the_Theme = "light" + + } + else if(newTheme === "glass"){ + document.body.classList.remove('dark'); document.body.classList.add('glass'); localStorage.setItem("theme", "glass"); + What_the_Theme = "glass" + + } + setThemChange() + }; + /// BsSunglasses BsFillSunFill BsFillMoonStarsFill + + + return ( +
+ + {(What_the_Theme === "light") ? + <> + + {t("light")} + + + : (What_the_Theme === "dark") ? + <> + + {t("dark")} + + : + <> + + {t("glass")} + + } + } transition> + changeTheme('light')}> + + {t("light")} + + changeTheme('dark')}> + + {t("dark")} + changeTheme('glass')}> + + {t("glass")} + + +
+ ); +} diff --git a/src/Components/Utils/Translate.tsx b/src/Components/Utils/Translate.tsx new file mode 100644 index 0000000..3dde7be --- /dev/null +++ b/src/Components/Utils/Translate.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { Menu, MenuItem, MenuButton } from '@szhsin/react-menu'; +import { useLanguage, useLanguageMenu } from '../../Hooks/useChangeLanguage'; +import i18next from 'i18next'; + +export default function Translate() { + const { changeLanguage } = useLanguage(); + const { languageOptions } = useLanguageMenu(); + + const handleLanguageChange = (newLanguage:string) => { + changeLanguage(newLanguage); + }; + + return ( +
+ + {languageOptions.map((option:any,index:number) => ( + option.code === i18next.language ? + + {option.label} + + : null + ))} + } transition> + {languageOptions.map((option:any) => ( + handleLanguageChange(option.code)}> + {option.label} + + ))} + +
+ ); +} diff --git a/src/Components/Utils/UnBlockModal.tsx b/src/Components/Utils/UnBlockModal.tsx new file mode 100644 index 0000000..cf3fac1 --- /dev/null +++ b/src/Components/Utils/UnBlockModal.tsx @@ -0,0 +1,60 @@ + +import React, { useEffect } from 'react'; +import { BsExclamationCircle } from 'react-icons/bs'; +import { Button, Card, CardBody, Input, Label, Modal, ModalHeader } from 'reactstrap'; +import { useTranslation } from 'react-i18next'; +import { LoadingButton } from '../Ui/LoadingButton'; +import { useCommonModelState } from '../../lib/state mangment/driver&customer/ModelState'; +import { CiLock } from "react-icons/ci"; + +interface UnBlockModalProps { + + Mutation:any, + type :'customer' |'driver' +} + +const UnBlockModal: React.FC = ({Mutation ,type }) => { + const {t} = useTranslation(); + + const key_to_api = type == t('customer') ? t('customer_id') : t("driver_id") + + const {isOpenUnBlock:isOpen , objectID , setIsopenUnBlock:setIsopen} = useCommonModelState() + + const handleSubmit = () => { + Mutation.mutate({ [key_to_api]: objectID }); + }; + + useEffect(() => { + if (Mutation.isSuccess) { + setIsopen(); + } + }, [Mutation.isSuccess, setIsopen]); + + return ( + + setIsopen()}> + {t("al")}{type} {t('un_block_page')} + + + +
+

{t('un_blocking_')}{type}

+ +
+
+ + + {t('un_block_for_')}{type} + +
+
+
+
+
+
+ ); +}; + +export default UnBlockModal; diff --git a/src/Components/ValidationField/Ui/KarimSpinner.tsx b/src/Components/ValidationField/Ui/KarimSpinner.tsx new file mode 100644 index 0000000..12206f9 --- /dev/null +++ b/src/Components/ValidationField/Ui/KarimSpinner.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { Spin } from 'antd'; + +interface Props { + loading: boolean; + children: React.ReactNode; + className?: string; +} + +const KarimSpinner: React.FC = ({ loading, className, children }) => { + return ( +
+ {loading ?
: children} +
+ ); +}; + +export default KarimSpinner; diff --git a/src/Components/ValidationField/Ui/SearchBar.scss b/src/Components/ValidationField/Ui/SearchBar.scss new file mode 100644 index 0000000..2ddadd9 --- /dev/null +++ b/src/Components/ValidationField/Ui/SearchBar.scss @@ -0,0 +1,48 @@ +.SearchBar{ + // margin-top: 20px; + .group { + display: flex; + align-items: center; + position: relative; + max-width: 350px; + width: 350px; + + } + + .input { + width: 100%; + height: 40px; + padding: 0 1rem; + padding-left: 2.5rem; + border-radius: 8px; + outline: none; + font-weight: 500; + background: var(--bg); + color: var(--text); + border: none; + box-shadow: 2px 2px 7px 0 var(--bg); + + } + + .input::placeholder { + color: var(--subtext); + opacity: .4; + + } + + + + .icon { + position: absolute; + left: 1rem; + fill: var(--subtext); + width: 1rem; + height: 1rem; + } + + + + + + +} \ No newline at end of file diff --git a/src/Components/ValidationField/Ui/SearchBar.tsx b/src/Components/ValidationField/Ui/SearchBar.tsx new file mode 100644 index 0000000..f70b289 --- /dev/null +++ b/src/Components/ValidationField/Ui/SearchBar.tsx @@ -0,0 +1,35 @@ +import React, { useState } from 'react' +import './SearchBar.scss' +import { useNavigate, useSearchParams } from 'react-router-dom'; +const SearchBar = () => { + const [searchQuery, setSearchQuery] = useState(''); + const [searchParams] = useSearchParams() + const navigate = useNavigate(); + + const handleChange = (event:any) => { + const { value } = event.target; + setSearchQuery(value); + updateUrlParams(value); + }; + + const updateUrlParams = (value:any) => { + navigate(`?search=${value}`); + + + }; + + return ( +
+
+ + +
+
+ ) +} + +export default SearchBar \ No newline at end of file diff --git a/src/Components/ValidationField/ValidationField.scss b/src/Components/ValidationField/ValidationField.scss new file mode 100644 index 0000000..97e9f5e --- /dev/null +++ b/src/Components/ValidationField/ValidationField.scss @@ -0,0 +1,78 @@ +.ValidationField{ + >*{ + width: 100%; + } + .text,.ant-form-item{ + margin-bottom:7px !important; + + } + + >span{ + margin-bottom: 0px !important; + &:focus-within{ + border-color: var(--primary) ; + box-shadow: 0 0 0 1px var(--primary); + cursor: pointer; + } + &:has(.is-invalid){ + border-color: red !important; + + } + input{ + color: var(--text); + background: var(--bg); + } + +input:-webkit-autofill, +input:-webkit-autofill:hover, +input:-webkit-autofill:focus, +input:-webkit-autofill:active{ + -webkit-box-shadow: 0 0 0 30px white inset !important; +} + } +} + +.ant-upload-select{ + width: 100%; +} +.Checkboxs{ + padding: 4%; +} +.SearchField{ + button{ + background: var(--primary); + } +} +.text{ + color: var(--text); +} + +input:disabled{ + color: var(--text) !important; +} + +.isError{ + outline: red 1px solid; + color: red; +} +.Error_color{ + color: red; + +} +input:-webkit-autofill { + -webkit-box-shadow: 0 0 0 1000px white inset !important; /* Change the color to your desired background color */ +} +input:-webkit-autofill:focus { + -webkit-box-shadow: 0 0 0 1000px white inset !important; /* Change the color to your desired background color */ +} + +/* Remove autofill background color on hover */ +input:-webkit-autofill:hover { + -webkit-box-shadow: 0 0 0 1000px white inset !important; /* Change the color to your desired background color */ +} + + +.ant-upload-list .ant-upload-list-item{ + height:78px !important; + } + \ No newline at end of file diff --git a/src/Components/ValidationField/ValidationField.tsx b/src/Components/ValidationField/ValidationField.tsx new file mode 100644 index 0000000..6e25672 --- /dev/null +++ b/src/Components/ValidationField/ValidationField.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import "./ValidationField.scss"; +import { Date, Time, File, DataRange, SelectField, Default, CheckboxField ,TextAreaField} from './View'; +import { ValidationFieldProps } from "./types"; +import MaltyFile from "./View/MaltyFile"; +import SearchField from "./View/SearchField"; + +const ValidationField: React.FC = ({type , ...otherProps}) => { + + switch (type) { + case 'Select': + return ; + case 'Search': + return ; + case "DataRange": + return ; + case "Date": + return ; + case "Time": + return