From 60e4349628b25ce1a3dbfdd8c19b31ea638c2bcc Mon Sep 17 00:00:00 2001 From: Moaz Dawalibi Date: Tue, 12 Nov 2024 14:10:49 +0300 Subject: [PATCH] add notification page --- .../AddNotification/Add/Page.tsx | 76 +++++++++++++++ .../AddNotification/Form/AttachmentForm.tsx | 30 ++++++ .../AddNotification/Form/FilterForm.tsx | 25 +++++ .../Form/ImageBoxField/ImageBoxField.scss | 38 ++++++++ .../Form/ImageBoxField/ImageBoxField.tsx | 87 +++++++++++++++++ .../Form/ImageBoxField/ImageCancelIcon.tsx | 18 ++++ .../Form/ImageBoxField/ImageIcon.tsx | 18 ++++ .../ImageBoxField/generateImagePreview.ts | 10 ++ .../Form/PersonalDetailsForm.tsx | 63 ++++++++++++ .../AddNotification/Form/TitleDetailsForm.tsx | 31 ++++++ .../AddNotification/Form/formUtils.ts | 95 +++++++++++++++++++ .../Notifications/AddNotification/Page.tsx | 7 +- src/Routes.tsx | 12 +++ src/translate/ar.json | 7 +- 14 files changed, 513 insertions(+), 4 deletions(-) create mode 100644 src/Pages/Admin/Notifications/AddNotification/Add/Page.tsx create mode 100644 src/Pages/Admin/Notifications/AddNotification/Form/AttachmentForm.tsx create mode 100644 src/Pages/Admin/Notifications/AddNotification/Form/FilterForm.tsx create mode 100644 src/Pages/Admin/Notifications/AddNotification/Form/ImageBoxField/ImageBoxField.scss create mode 100644 src/Pages/Admin/Notifications/AddNotification/Form/ImageBoxField/ImageBoxField.tsx create mode 100644 src/Pages/Admin/Notifications/AddNotification/Form/ImageBoxField/ImageCancelIcon.tsx create mode 100644 src/Pages/Admin/Notifications/AddNotification/Form/ImageBoxField/ImageIcon.tsx create mode 100644 src/Pages/Admin/Notifications/AddNotification/Form/ImageBoxField/generateImagePreview.ts create mode 100644 src/Pages/Admin/Notifications/AddNotification/Form/PersonalDetailsForm.tsx create mode 100644 src/Pages/Admin/Notifications/AddNotification/Form/TitleDetailsForm.tsx create mode 100644 src/Pages/Admin/Notifications/AddNotification/Form/formUtils.ts diff --git a/src/Pages/Admin/Notifications/AddNotification/Add/Page.tsx b/src/Pages/Admin/Notifications/AddNotification/Add/Page.tsx new file mode 100644 index 0000000..229e064 --- /dev/null +++ b/src/Pages/Admin/Notifications/AddNotification/Add/Page.tsx @@ -0,0 +1,76 @@ +import { useTranslation } from "react-i18next"; +import useSetPageTitle from "../../../../../Hooks/useSetPageTitle"; +import PageHeader from "../../../../../Layout/Dashboard/PageHeader"; +import { Suspense, useEffect } from "react"; +import { Spin } from "antd"; +import { ModalEnum } from "../../../../../enums/Model"; +import PersonalDetailsForm from "../Form/PersonalDetailsForm"; +import { Formik, Form } from "formik"; +import { getInitialValues, getValidationSchema } from "../Form/formUtils"; +import TitleDetailsForm from "../Form/TitleDetailsForm"; +import AttachmentForm from "../Form/AttachmentForm"; +import { useNavigate } from "react-router-dom"; +import { QueryStatusEnum } from "../../../../../enums/QueryStatus"; +import { useAddNotification } from "../../../../../api/notification"; + +const TableHeader = () => { + const [t] = useTranslation(); + const Navigate = useNavigate(); + const { mutate, isSuccess, status } = useAddNotification(); + useSetPageTitle(t(`page_header.add_notification`)); + const handleSubmit = (values: any) => { + const DataToSend = { + ...values, + location: { + lat: values.lat, + lng: values.lng, + }, + }; + mutate(DataToSend); + }; + useEffect(() => { + if (isSuccess === true) { + console.log(1); + Navigate('/add_Notifications') + } + }, [isSuccess]) + + return ( +
+ }> + +
+ + {({ dirty }) => ( +
+ + +
+ + +
+ + )} +
+
+
+
+ ); +}; + +export default TableHeader; diff --git a/src/Pages/Admin/Notifications/AddNotification/Form/AttachmentForm.tsx b/src/Pages/Admin/Notifications/AddNotification/Form/AttachmentForm.tsx new file mode 100644 index 0000000..a47ec54 --- /dev/null +++ b/src/Pages/Admin/Notifications/AddNotification/Form/AttachmentForm.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { FaImage } from "react-icons/fa"; +import ImageBoxField from "./ImageBoxField/ImageBoxField"; +import ValidationField from "../../../../../Components/ValidationField/ValidationField"; + +const AttachmentForm = () => { + const [t] = useTranslation(); + + return ( +
+
+ +

{t("header.attachment")}

+
+
+
+ + +
+
+ + +
+
+
+ ); +}; + +export default AttachmentForm; diff --git a/src/Pages/Admin/Notifications/AddNotification/Form/FilterForm.tsx b/src/Pages/Admin/Notifications/AddNotification/Form/FilterForm.tsx new file mode 100644 index 0000000..c91b911 --- /dev/null +++ b/src/Pages/Admin/Notifications/AddNotification/Form/FilterForm.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import ValidationField from "../../../../../Components/ValidationField/ValidationField"; +import { Col, Row } from "reactstrap"; + +const FilterForm = () => { + return ( +
+ + + + {/* */} + + {/* + + */} + +
+ ); +}; + +export default FilterForm; diff --git a/src/Pages/Admin/Notifications/AddNotification/Form/ImageBoxField/ImageBoxField.scss b/src/Pages/Admin/Notifications/AddNotification/Form/ImageBoxField/ImageBoxField.scss new file mode 100644 index 0000000..66e2f2f --- /dev/null +++ b/src/Pages/Admin/Notifications/AddNotification/Form/ImageBoxField/ImageBoxField.scss @@ -0,0 +1,38 @@ +.ImageBoxField { + .ImageBox { + width: 120px; + height: 120px; + display: flex; + align-items: center; + justify-content: center; + border: max(1.5px, 0.1vw) dashed #a9c3f1; + margin-block: 10px; + border-radius: 5px; + z-index: 9999999 !important; + .ImageBoxIcon { + cursor: pointer; + } + .imagePreview { + max-width: 99%; + height: auto; + max-height: 99%; + object-fit: contain; + border-radius: 5px; + } + } + .ImageHeader { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 10px; + } + + .ImageCancelIcon { + width: 16px !important; + height: 16px !important; + } + .ImageBoxIcon { + width: 20px !important; + height: 20px !important; + } +} diff --git a/src/Pages/Admin/Notifications/AddNotification/Form/ImageBoxField/ImageBoxField.tsx b/src/Pages/Admin/Notifications/AddNotification/Form/ImageBoxField/ImageBoxField.tsx new file mode 100644 index 0000000..fae09b2 --- /dev/null +++ b/src/Pages/Admin/Notifications/AddNotification/Form/ImageBoxField/ImageBoxField.tsx @@ -0,0 +1,87 @@ +import { useFormikContext } from "formik"; +import { useState, useRef, useEffect } from "react"; +import "./ImageBoxField.scss"; +import ImageIcon from "./ImageIcon"; +import ImageCancelIcon from "./ImageCancelIcon"; +import { generateImagePreview } from "./generateImagePreview"; +import { getNestedValue } from "../../../../../../utils/getNestedValue"; +import { useTranslation } from "react-i18next"; + +// Helper function to generate image preview from a File + +const ImageBoxField = ({ name }: any) => { + const formik = useFormikContext(); + const value = getNestedValue(formik.values, name); + const [imagePreview, setImagePreview] = useState(null); + const fileInputRef = useRef(null); + + useEffect(() => { + if (value instanceof File) { + generateImagePreview(value, setImagePreview); + } else if (typeof value === "string") { + setImagePreview(value); + } else { + setImagePreview(null); + } + }, [value]); + + const handleFileChange = (event: any) => { + const file = event.target.files[0]; + if (file) { + generateImagePreview(file, setImagePreview); + formik.setFieldValue(name, file); + } + }; + + const handleButtonClick = () => { + const fileInput = fileInputRef.current; + if (fileInput) { + fileInput.click(); + } + }; + + const handleCancel = () => { + setImagePreview(""); + formik.setFieldValue(name, ""); + + if (fileInputRef.current) { + fileInputRef.current.value = ""; + } + }; + const [t] = useTranslation(); + return ( +
+
{t(`input.${name}`)}
+
+ {imagePreview ? ( + <> + + + + ) : ( +
hidden
+ )} +
+
+ {imagePreview ? ( + Preview + ) : ( + + )} +
+ +
+ ); +}; + +export default ImageBoxField; diff --git a/src/Pages/Admin/Notifications/AddNotification/Form/ImageBoxField/ImageCancelIcon.tsx b/src/Pages/Admin/Notifications/AddNotification/Form/ImageBoxField/ImageCancelIcon.tsx new file mode 100644 index 0000000..d42ba53 --- /dev/null +++ b/src/Pages/Admin/Notifications/AddNotification/Form/ImageBoxField/ImageCancelIcon.tsx @@ -0,0 +1,18 @@ +import React from "react"; + +interface ImageCancelIconProps extends React.HTMLAttributes {} + +const ImageCancelIcon: React.FC = (props) => { + return ( +
+ + + +
+ ); +}; + +export default ImageCancelIcon; diff --git a/src/Pages/Admin/Notifications/AddNotification/Form/ImageBoxField/ImageIcon.tsx b/src/Pages/Admin/Notifications/AddNotification/Form/ImageBoxField/ImageIcon.tsx new file mode 100644 index 0000000..4ca597a --- /dev/null +++ b/src/Pages/Admin/Notifications/AddNotification/Form/ImageBoxField/ImageIcon.tsx @@ -0,0 +1,18 @@ +import React from "react"; + +interface ImageIconProps extends React.HTMLAttributes {} + +const ImageIcon: React.FC = (props) => { + return ( +
+ + + +
+ ); +}; + +export default ImageIcon; diff --git a/src/Pages/Admin/Notifications/AddNotification/Form/ImageBoxField/generateImagePreview.ts b/src/Pages/Admin/Notifications/AddNotification/Form/ImageBoxField/generateImagePreview.ts new file mode 100644 index 0000000..3f754d3 --- /dev/null +++ b/src/Pages/Admin/Notifications/AddNotification/Form/ImageBoxField/generateImagePreview.ts @@ -0,0 +1,10 @@ +export const generateImagePreview = ( + file: File, + setImagePreview: (result: string) => void, +) => { + const reader = new FileReader(); + reader.onloadend = () => { + setImagePreview(reader.result as string); + }; + reader.readAsDataURL(file); +}; diff --git a/src/Pages/Admin/Notifications/AddNotification/Form/PersonalDetailsForm.tsx b/src/Pages/Admin/Notifications/AddNotification/Form/PersonalDetailsForm.tsx new file mode 100644 index 0000000..719233b --- /dev/null +++ b/src/Pages/Admin/Notifications/AddNotification/Form/PersonalDetailsForm.tsx @@ -0,0 +1,63 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { FaStore } from "react-icons/fa"; +import ValidationField from "../../../../../Components/ValidationField/ValidationField"; +import { convert_data_to_select } from "../../../../../Layout/app/Const"; +import { userTypeOptions } from "../../../../../config/userTypeOptions"; +import { statusType } from "../../../../../config/statusType"; +import { IoIosNotifications } from "react-icons/io"; + +const PersonalDetailsForm = ({isEdit}:{isEdit?:boolean}) => { + const [t] = useTranslation(); + return ( +
+
+ +

{t("header.notification_details")}

+
+
+ + + + + + + {isEdit? "" : + + } + +
+
+ ); +}; + +export default PersonalDetailsForm; diff --git a/src/Pages/Admin/Notifications/AddNotification/Form/TitleDetailsForm.tsx b/src/Pages/Admin/Notifications/AddNotification/Form/TitleDetailsForm.tsx new file mode 100644 index 0000000..cb04ba6 --- /dev/null +++ b/src/Pages/Admin/Notifications/AddNotification/Form/TitleDetailsForm.tsx @@ -0,0 +1,31 @@ +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { FaRegAddressBook } from "react-icons/fa"; +import ValidationField from "../../../../../Components/ValidationField/ValidationField"; +import { useGetAllArea } from "../../../../../api/Area"; +import { useGetAllCity } from "../../../../../api/City"; +import { FaUsers } from "react-icons/fa6"; + +const TitleDetailsForm = () => { + const [t] = useTranslation(); + const {data:city} = useGetAllCity(); + const [CityId, setCityId] = useState() + + const {data} = useGetAllArea({ + city_id:CityId + }); + + return ( +
+
+ +

{t("header.users")}

+
+
+ {/* */} +
+
+ ); +}; + +export default TitleDetailsForm; diff --git a/src/Pages/Admin/Notifications/AddNotification/Form/formUtils.ts b/src/Pages/Admin/Notifications/AddNotification/Form/formUtils.ts new file mode 100644 index 0000000..373d192 --- /dev/null +++ b/src/Pages/Admin/Notifications/AddNotification/Form/formUtils.ts @@ -0,0 +1,95 @@ +import * as Yup from "yup"; +import { objectToKeyValueArray } from "../../../../../utils/objectToKeyValueArray"; + +interface Location { + lat: number; + lng: number; +} +interface User { + username: string; + phone_number?: number; + type?:string +} + + +interface PersonalDetailsForm { + id: number; + first_name: string | null; + last_name: string | null; + location: Location[]; + contact_number1: string | null; + contact_number2: string | null; + card_number: string | null; + username: string | null; + password: string | null; + area_id: number | null; + lat:number, + lng:number +} + +interface PersonalDetailsEditForm { + id: number; + first_name: string | null; + last_name: string | null; + location: Location[]; + contact_number1: string | null; + contact_number2: string | null; + card_number: string | null; + user: User; + area_id: any | null; + lat:number, + lng:number +} + +export const getInitialValues = (objectToEdit: Partial) => { + const location = objectToEdit?.location?.[0] || { lat: 33.5138, lng: 36.2765 }; + + return { + id: objectToEdit?.id ?? 0, + first_name: objectToEdit?.first_name ?? null, + last_name: objectToEdit?.last_name ?? null, + location_lat: location.lat, + location_lng: location.lng, + contact_number1: objectToEdit?.contact_number1 ?? null, + contact_number2: objectToEdit?.contact_number2 ?? null, + card_number: objectToEdit?.card_number ?? null, + username: objectToEdit?.username ?? null, + password: objectToEdit?.password ?? null, + area_id: objectToEdit?.area_id ?? null, + lat: location.lat ?? 33.5138, + lng: location.lng ?? 36.2765, + }; +}; + +export const getValidationSchema = () => { + // validate input + return Yup.object().shape({ + id: Yup.number().required(), + first_name: Yup.string().required('first_name is required'), + last_name: Yup.string().required('last_name is required'), + location_lat: Yup.string().required('lat is required'), + location_lng: Yup.string().required('lng is required'), + contact_number1: Yup.string().required('contact_number1 is required'), + contact_number2: Yup.string().required('contact_number2 is required'), + username: Yup.string().required('username is required'), + area_id: Yup.mixed().required('area_id is required'), + }); +}; + + +export const getInitialValuesEdit = (objectToEdit: Partial) => { + const location = objectToEdit?.location || { lat: 0, lng: 0 }; + + return { + id: objectToEdit?.id ?? 0, + first_name: objectToEdit?.first_name ?? null, + last_name: objectToEdit?.last_name ?? null, + location_lat: location.lat, + location_lng: location.lng, + contact_number1: objectToEdit?.contact_number1 ?? null, + contact_number2: objectToEdit?.contact_number2 ?? null, + card_number: objectToEdit?.card_number ?? null, + username: objectToEdit?.user?.username ?? null, + area_id: objectToEdit?.area_id ?? null, + }; +}; \ No newline at end of file diff --git a/src/Pages/Admin/Notifications/AddNotification/Page.tsx b/src/Pages/Admin/Notifications/AddNotification/Page.tsx index f600b15..e4e2e99 100644 --- a/src/Pages/Admin/Notifications/AddNotification/Page.tsx +++ b/src/Pages/Admin/Notifications/AddNotification/Page.tsx @@ -8,6 +8,7 @@ import FilterLayout from "../../../../Layout/Dashboard/FilterLayout"; import FilterForm from "./Model/FilterForm"; import { canAddNotification } from "../../../../utils/hasAbilityFn"; import { useDeleteNotification } from "../../../../api/notification"; +import { ABILITIES_ENUM } from "../../../../enums/abilities"; const Table = lazy(() => import("./Table")); const AddModalForm = lazy(() => import("./Model/AddModel")); @@ -30,14 +31,16 @@ const TableHeader = () => { }> } filterTitle="table.notification" /> - + {/* */} import("./Pages/Admin/User/Page")); const QuestionBank = React.lazy(() => import("./Pages/Admin/QuestionBank/Page")); const AllNotifications = React.lazy(() => import("./Pages/Admin/Notifications/Page")); const Notifications = React.lazy(() => import("./Pages/Admin/Notifications/AddNotification/Page")); +const AddNotification = React.lazy(() => import("./Pages/Admin/Notifications/AddNotification/Add/Page")); + const Profile = React.lazy(() => import("./Pages/Admin/Profile/Page")); const Setting = React.lazy(() => import("./Pages/Admin/Setting/Page")); @@ -401,6 +403,16 @@ export const CrudRoute: TCrudRoute[] = [ // abilities_value: ABILITIES_VALUES_ENUM.INDEX, // prevPath: 0, // }, + + { + header: "page_header.add_notification", + element: , + path: `/${ABILITIES_ENUM?.NOTIFICATIONS}/add`, + abilities: ABILITIES_ENUM?.NOTIFICATIONS, + abilities_value: ABILITIES_VALUES_ENUM.INDEX, + prevPath: 0, + }, + //// RE_SELLER { header: "page_header.notifications", diff --git a/src/translate/ar.json b/src/translate/ar.json index 95a2dc7..d354c29 100644 --- a/src/translate/ar.json +++ b/src/translate/ar.json @@ -154,7 +154,9 @@ "show_preview":"عرض المعاينة", "show_MMl":" MML عرض", "financial_collection":"التحصيلات", - "change_direction":"تغيير الاتجاه" + "change_direction":"تغيير الاتجاه", + "notification_details":"تفاصيل الإشعار", + "users":"المستخدمون" }, "columns": { "id": "الرقم التعريفي", @@ -971,7 +973,8 @@ "Coupon":"قسيمة", "financial_collection":"التحصيلات", "show_collection":"حصيلة", - "notification":"إدارة الاشعارات" + "notification":"إدارة الاشعارات", + "manage_notification":"إدارة الإشعارات" }, "page_header": { "home": "لوحة القيادة",