From cc744ebac9c574126b05a5b54fa7ed20998e3d1d Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Thu, 24 Jul 2025 17:17:55 +0330 Subject: [PATCH] profile + summerize --- src/components/DefaulModal.tsx | 57 ++++ src/components/HeaderModal.tsx | 21 ++ src/components/Table.tsx | 101 ++++++- src/components/types/TableTypes.ts | 8 + src/index.css | 14 + src/langs/fa.json | 31 +++ src/pages/favorite/List.tsx | 9 + src/pages/profile/Profile.tsx | 255 ++++++++++++++++++ src/pages/profile/components/Email.tsx | 64 +++++ src/pages/profile/components/Phone.tsx | 143 ++++++++++ src/pages/profile/components/Username.tsx | 131 +++++++++ src/pages/profile/hooks/useProfileData.tsx | 46 ++++ src/pages/profile/service/ProfileService.ts | 43 +++ src/pages/profile/types/ProfileTypes.ts | 28 ++ .../hooks/useProfileData.ts | 0 .../{profile => profile1}/service/Service.ts | 0 src/pages/received/Components/Favorite.tsx | 4 +- src/pages/received/Components/Summerize.tsx | 77 ++++++ src/pages/received/List.tsx | 22 +- src/pages/received/hooks/useEmailData.tsx | 8 +- src/pages/received/service/EmailService.ts | 6 + src/pages/received/types/Types.ts | 7 + src/router/AppRouter.tsx | 2 + src/shared/Header.tsx | 6 +- 24 files changed, 1063 insertions(+), 20 deletions(-) create mode 100644 src/components/DefaulModal.tsx create mode 100644 src/components/HeaderModal.tsx create mode 100644 src/pages/profile/Profile.tsx create mode 100644 src/pages/profile/components/Email.tsx create mode 100644 src/pages/profile/components/Phone.tsx create mode 100644 src/pages/profile/components/Username.tsx create mode 100644 src/pages/profile/hooks/useProfileData.tsx create mode 100644 src/pages/profile/service/ProfileService.ts create mode 100644 src/pages/profile/types/ProfileTypes.ts rename src/pages/{profile => profile1}/hooks/useProfileData.ts (100%) rename src/pages/{profile => profile1}/service/Service.ts (100%) create mode 100644 src/pages/received/Components/Summerize.tsx diff --git a/src/components/DefaulModal.tsx b/src/components/DefaulModal.tsx new file mode 100644 index 0000000..89766bd --- /dev/null +++ b/src/components/DefaulModal.tsx @@ -0,0 +1,57 @@ +import { FC, Fragment, ReactNode, useEffect } from 'react' +import HeaderModal from './HeaderModal' + +interface Props { + open: boolean, + close: () => void, + children: ReactNode, + isHeader?: boolean, + title_header?: string, + width?: number +} + +const DefaulModal: FC = (props: Props) => { + + useEffect(() => { + if (props.open) { + document.body.style.overflow = 'hidden' + } else { + document.body.style.overflow = 'auto' + } + }, [props.open]) + + + return ( + + { + props.open && ( + +
+
+
+ + + { + props.isHeader && props.title_header && +
+
+ +
+ } + + {props.children} + + +
+
+
+ +
+
+ ) + } +
+ ) +} + +export default DefaulModal \ No newline at end of file diff --git a/src/components/HeaderModal.tsx b/src/components/HeaderModal.tsx new file mode 100644 index 0000000..972fbbc --- /dev/null +++ b/src/components/HeaderModal.tsx @@ -0,0 +1,21 @@ +import { FC } from 'react' +import { CloseCircle } from 'iconsax-react' + + +type Props = { + label: string, + close: () => void, +} + +const HeaderModal: FC = (props: Props) => { + return ( +
+
{props.label}
+
+ +
+
+ ) +} + +export default HeaderModal \ No newline at end of file diff --git a/src/components/Table.tsx b/src/components/Table.tsx index e25403e..e768ac1 100644 --- a/src/components/Table.tsx +++ b/src/components/Table.tsx @@ -25,6 +25,7 @@ const Table = ({ selectedRows: externalSelectedRows, clearSelection = false, rowActions, + inlineActions, pagination, }: TableProps): React.ReactElement => { @@ -145,9 +146,30 @@ const Table = ({ checked={isRowSelected(item)} onCheckedChange={(checked) => handleSingleRowSelect(item, !!checked)} /> + + {/* {inlineActions && ( +
e.stopPropagation()} + > + {inlineActions(item).map((action, actionIndex) => ( +
+ {action.icon} +
+ ))} +
+ )} */} )} + {/* Inline Actions - بعد از checkbox */} + +
{/* محتوای اصلی - ستون اول */}
@@ -185,7 +207,7 @@ const Table = ({ if (isLoading) { return ( - + ); } @@ -193,7 +215,7 @@ const Table = ({ if (data.length === 0) { return ( - + {noDataMessage} @@ -217,12 +239,32 @@ const Table = ({ className="px-3 md:px-6 py-3 md:py-4 w-8 md:w-10 relative z-[5]" onClick={(e) => e.stopPropagation()} > - handleSingleRowSelect(item, !!checked)} - /> +
+ handleSingleRowSelect(item, !!checked)} + /> + { + inlineActions && ( +
+ {inlineActions(item).map((action, actionIndex) => ( +
+ {action.icon} +
+ ))} +
+ ) + } +
+ )} + {columns.map((col) => ( ({ /> )} + {inlineActions && ( + عملیات + )} {columns.map((col) => ( ))} @@ -343,7 +388,7 @@ const Table = ({ {actionsPosition === 'header-replace' && ( - + {renderActions()} @@ -405,4 +450,46 @@ const MyComponent = () => { /> ); }; + +مثال استفاده با inline actions: + +import Table from '@/components/Table'; +import { Star1, Archive } from 'iconsax-react'; + +const MyEmailComponent = () => { + const columns = [ + { title: 'موضوع', key: 'subject' }, + { title: 'فرستنده', key: 'from' }, + ]; + + const data = [ + { id: 1, subject: 'سلام', from: 'ali@example.com', flagged: false }, + { id: 2, subject: 'خبرنامه', from: 'newsletter@example.com', flagged: true }, + ]; + + const getInlineActions = (item) => [ + { + icon: , + onClick: () => handleFavoriteToggle(item.id), + tooltip: item.flagged ? 'حذف از علاقه‌مندی‌ها' : 'اضافه به علاقه‌مندی‌ها' + }, + { + icon: , + onClick: () => handleArchive(item.id), + tooltip: 'بایگانی' + } + ]; + + return ( + + ); +}; */ \ No newline at end of file diff --git a/src/components/types/TableTypes.ts b/src/components/types/TableTypes.ts index 66caa77..23aaf3a 100644 --- a/src/components/types/TableTypes.ts +++ b/src/components/types/TableTypes.ts @@ -19,6 +19,13 @@ export interface PaginationConfig { onPageChange: (page: number) => void; } +export interface InlineActionItem { + icon: React.ReactNode; + onClick: () => void; + tooltip?: string; + className?: string; +} + export interface TableProps { columns: ColumnType[]; data: T[]; @@ -39,5 +46,6 @@ export interface TableProps { selectedRows?: T[]; clearSelection?: boolean; rowActions?: (item: T) => RowActionItem[]; + inlineActions?: (item: T) => InlineActionItem[]; pagination?: PaginationConfig; } diff --git a/src/index.css b/src/index.css index 06b74fe..1aec8aa 100644 --- a/src/index.css +++ b/src/index.css @@ -342,6 +342,20 @@ textarea::placeholder { padding: 8px; } +.modalGlass { + background: rgba(0, 0, 0, 0.436); + background-blend-mode: multiply; + + backdrop-filter: blur(7px); +} +.modalGlass2 { + /* background: rgba(255, 255, 255, 0.502); */ + background-color: white; + background-blend-mode: multiply; + + /* backdrop-filter: blur(5px); */ +} + .modalGlass3 { background: rgba(62, 61, 61, 0.2); background-blend-mode: multiply; diff --git a/src/langs/fa.json b/src/langs/fa.json index 55cbd17..efdbb9c 100644 --- a/src/langs/fa.json +++ b/src/langs/fa.json @@ -27,6 +27,37 @@ "common": { "close": "بستن" }, + "profile": { + "account_user": "حساب کاربری", + "image_profile": "تصویر حساب کاربری", + "image_profile_desc": "تصویر خود را در حساب کاربری قرار دهید.", + "upload_image": "آپلود تصویر", + "formats": "فرمت های", + "max_size": "حداکثر ۱مگابایت", + "format_image": " jpg, png,jpeg.", + "info_account": "اطلاعات حساب", + "auth_after_change_info": "در صورت تغییر اطلاعات حساب باید مجددا احراز هویت انجام شود.", + "username": "نام کاربری", + "phone_call": "شماره تماس", + "personal_information": "اطلاعات شخصی", + "enter_carefully_your_information": "اطلاعت شخصی خود را با دقت وارد کنید.", + "date_of_birth": "تاریخ تولد", + "national_code": "کد ملی", + "address": "آدرس", + "address_live": "آدرس محل سکونت خود را با دقت وارد کنید", + "province": "استان", + "city": "شهر", + "postal_code": "کد پستی", + "username_available": "نام کاربری موجود", + "username_not_available": "نام کاربری موجود نیست", + "username_save_success": "نام کاربری با موفقیت ذخیره شد", + "image_uploaded_successfully": "تصویر با موفقیت آپلود شد", + "confrim_email": "تایید ایمیل", + "confrim": "تایید صورتحساب ", + "link_email": "یک لینک برای تایید ایمیل برای شما ارسال شد.", + "email_not_verified": "ایمیل تایید نشده" + }, + "email": "ایمیل", "received": { "title": "دریافتی ها", "from_date": "از تاریخ", diff --git a/src/pages/favorite/List.tsx b/src/pages/favorite/List.tsx index c2985f0..976b45c 100644 --- a/src/pages/favorite/List.tsx +++ b/src/pages/favorite/List.tsx @@ -9,6 +9,7 @@ import { useGetFavoriteMessages } from './hooks/useFavoriteData'; import { FavoriteMessage } from './types/FavoriteTypes'; import { formatDate } from '@/config/func'; import { useEmailActions } from '@/hooks/useEmailActions'; +import Favorite from '../received/Components/Favorite'; const List: FC = () => { const navigate = useNavigate(); @@ -99,6 +100,13 @@ const List: FC = () => { setSelectedMessages([]); }; + const getInlineActions = (message: FavoriteMessage) => [ + { + icon: , + onClick: () => { }, + }, + ]; + const getRowActions = (message: FavoriteMessage): RowActionItem[] => [ { label: 'حذف از علاقه‌مندی‌ها', @@ -161,6 +169,7 @@ const List: FC = () => { currentPage: pager.page, onPageChange: handlePageChange } : undefined} + inlineActions={getInlineActions} /> ) diff --git a/src/pages/profile/Profile.tsx b/src/pages/profile/Profile.tsx new file mode 100644 index 0000000..d885e3c --- /dev/null +++ b/src/pages/profile/Profile.tsx @@ -0,0 +1,255 @@ +import { FC, useCallback, useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import AvatarImage from '../../assets/images/avatar_image.png' +import Button from '../../components/Button' +import { DocumentUpload } from 'iconsax-react' +import { useGetProfile, useSingleUpload, useUpdateProfile } from './hooks/useProfileData' +import Username from './components/Username' +import { useDropzone } from 'react-dropzone' +import { toast } from '@/components/Toast' +import { ErrorType } from '@/helpers/types' +import { UpdateProfileType } from './types/ProfileTypes' +import { useFormik } from 'formik' +import * as Yup from 'yup' +import Email from './components/Email' + +const Profile: FC = () => { + + const { t } = useTranslation('global') + const [file, setFile] = useState() + const getProfile = useGetProfile() + const updateProfile = useUpdateProfile() + const singleUpload = useSingleUpload() + + const formik = useFormik({ + initialValues: { + cityId: '', + userAddress: '', + postalCode: '', + }, + validationSchema: Yup.object({ + cityId: Yup.string().required(t('errors.required')), + userAddress: Yup.string().required(t('errors.required')), + postalCode: Yup.string().required(t('errors.required')), + }), + onSubmit: (values) => { + updateProfile.mutate(values, { + onSuccess: () => { + toast(t('success'), 'success') + }, + onError: (error: ErrorType) => { + toast(error.response?.data?.error?.message[0], 'error') + } + }) + } + }) + + useEffect(() => { + + if (getProfile.data) { + formik.setValues({ + cityId: getProfile.data.data.user.city?.id || '', + userAddress: getProfile.data.data.user.userAddress || '', + postalCode: getProfile.data.data.user.postalCode || '', + }) + } + + }, [getProfile.data]) + + + const onDrop = useCallback((acceptedFiles: File[]) => { + + if (acceptedFiles.length > 0) { + const file = acceptedFiles[0]; + if (file.type.startsWith('image/')) { + setFile(file); + const formData = new FormData() + formData.append('file', file) + singleUpload.mutate(formData, { + onSuccess: (data) => { + const params: UpdateProfileType = { + profilePic: data.data?.url + } + updateProfile.mutate(params, { + onSuccess: () => { + toast(t('profile.image_uploaded_successfully'), 'success') + }, + onError: (error: ErrorType) => { + toast(error.response?.data?.error?.message[0], 'error') + } + }) + }, + onError: (error: ErrorType) => { + toast(error.response?.data?.error?.message[0], 'error') + } + }) + } else { + toast(t('errors.is_not_image'), 'error') + } + } + + }, []) + const { getRootProps, getInputProps } = useDropzone({ onDrop }) + + return ( +
+
+ {t('profile.account_user')} +
+ + { + getProfile.isPending ? +
+ : +
+
+
+
+ {t('profile.image_profile')} +
+
+ {t('profile.image_profile_desc')} +
+
+
+ +
+ +

+ {t('profile.formats')} +

{t('profile.format_image')}
+ {t('profile.max_size')} +

+
+
+
+ +
+
+
+ {t('profile.info_account')} +
+
+ {t('profile.auth_after_change_info')} +
+
+
+ + +
+
+ + {/*
+
+
+ {t('profile.personal_information')} +
+
+ {t('profile.enter_carefully_your_information')} +
+
+
+ { }} + placeholder='' + defaultValue={getProfile.data?.data?.user?.birthDate} + /> + +
+ +
+
+
*/} + + {/*
+
+
+ {t('profile.address')} +
+
+ {t('profile.address_live')} +
+
+
+
+ ({ label: item.name, value: item.id })) : []} + placeholder={t('select')} + {...formik.getFieldProps('cityId')} + error_text={formik.touched.cityId && formik.errors.cityId ? formik.errors.cityId : ''} + /> +
+ +
+ +
+
+