create discount and ads section

This commit is contained in:
HAM!DREZA
2025-01-20 15:36:50 +03:30
parent ad20ce5d18
commit 12865a12f2
16 changed files with 1350 additions and 1 deletions
+237
View File
@@ -11,9 +11,11 @@
"@headlessui/react": "^2.2.0",
"@material-tailwind/react": "^2.1.10",
"@tanstack/react-query": "^5.62.8",
"draft-js": "^0.11.7",
"formik": "^2.4.6",
"i18next": "^24.2.0",
"iconsax-react": "^0.0.8",
"quill": "^2.0.3",
"rc-rate": "^2.13.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
@@ -1839,6 +1841,18 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.10.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz",
"integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"undici-types": "~6.20.0"
}
},
"node_modules/@types/prop-types": {
"version": "15.7.14",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
@@ -2227,6 +2241,12 @@
"node": ">=10"
}
},
"node_modules/asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
"license": "MIT"
},
"node_modules/attr-accept": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz",
@@ -2515,6 +2535,26 @@
"node": ">=18"
}
},
"node_modules/core-js": {
"version": "3.40.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz",
"integrity": "sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==",
"hasInstallScript": true,
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/cross-fetch": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz",
"integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==",
"license": "MIT",
"dependencies": {
"node-fetch": "^2.7.0"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -2597,6 +2637,21 @@
"dev": true,
"license": "MIT"
},
"node_modules/draft-js": {
"version": "0.11.7",
"resolved": "https://registry.npmjs.org/draft-js/-/draft-js-0.11.7.tgz",
"integrity": "sha512-ne7yFfN4sEL82QPQEn80xnADR8/Q6ALVworbC5UOSzOvjffmYfFsr3xSZtxbIirti14R7Y33EZC5rivpLgIbsg==",
"license": "MIT",
"dependencies": {
"fbjs": "^2.0.0",
"immutable": "~3.7.4",
"object-assign": "^4.1.1"
},
"peerDependencies": {
"react": ">=0.14.0",
"react-dom": ">=0.14.0"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -2858,6 +2913,12 @@
"node": ">=0.10.0"
}
},
"node_modules/eventemitter3": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
"license": "MIT"
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -2865,6 +2926,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/fast-diff": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
"license": "Apache-2.0"
},
"node_modules/fast-glob": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
@@ -2919,6 +2986,28 @@
"reusify": "^1.0.4"
}
},
"node_modules/fbjs": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-2.0.0.tgz",
"integrity": "sha512-8XA8ny9ifxrAWlyhAbexXcs3rRMtxWcs3M0lctLfB49jRDHiaxj+Mo0XxbwE7nKZYzgCFoq64FS+WFd4IycPPQ==",
"license": "MIT",
"dependencies": {
"core-js": "^3.6.4",
"cross-fetch": "^3.0.4",
"fbjs-css-vars": "^1.0.0",
"loose-envify": "^1.0.0",
"object-assign": "^4.1.0",
"promise": "^7.1.1",
"setimmediate": "^1.0.5",
"ua-parser-js": "^0.7.18"
}
},
"node_modules/fbjs-css-vars": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz",
"integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==",
"license": "MIT"
},
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -3296,6 +3385,15 @@
"node": ">= 4"
}
},
"node_modules/immutable": {
"version": "3.7.6",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz",
"integrity": "sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -3566,6 +3664,18 @@
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
},
"node_modules/lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
"license": "MIT"
},
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
"license": "MIT"
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -3693,6 +3803,26 @@
"dev": true,
"license": "MIT"
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-releases": {
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
@@ -3796,6 +3926,12 @@
"dev": true,
"license": "BlueOak-1.0.0"
},
"node_modules/parchment": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/parchment/-/parchment-3.0.0.tgz",
"integrity": "sha512-HUrJFQ/StvgmXRcQ1ftY6VEZUq3jA2t9ncFN4F84J/vN0/FPpQF+8FKXb3l6fLces6q0uOHj6NJn+2xvZnxO6A==",
"license": "BSD-3-Clause"
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -4072,6 +4208,15 @@
"node": ">= 0.8.0"
}
},
"node_modules/promise": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
"license": "MIT",
"dependencies": {
"asap": "~2.0.3"
}
},
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -4120,6 +4265,35 @@
],
"license": "MIT"
},
"node_modules/quill": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/quill/-/quill-2.0.3.tgz",
"integrity": "sha512-xEYQBqfYx/sfb33VJiKnSJp8ehloavImQ2A6564GAbqG55PGw1dAWUn1MUbQB62t0azawUS2CZZhWCjO8gRvTw==",
"license": "BSD-3-Clause",
"dependencies": {
"eventemitter3": "^5.0.1",
"lodash-es": "^4.17.21",
"parchment": "^3.0.0",
"quill-delta": "^5.1.0"
},
"engines": {
"npm": ">=8.2.3"
}
},
"node_modules/quill-delta": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz",
"integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==",
"license": "MIT",
"dependencies": {
"fast-diff": "^1.3.0",
"lodash.clonedeep": "^4.5.0",
"lodash.isequal": "^4.5.0"
},
"engines": {
"node": ">= 12.0.0"
}
},
"node_modules/rc-rate": {
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.13.0.tgz",
@@ -4499,6 +4673,12 @@
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
"license": "MIT"
},
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
"license": "MIT"
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -4848,6 +5028,12 @@
"integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==",
"license": "MIT"
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/ts-api-utils": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
@@ -4942,6 +5128,41 @@
"typescript": ">=4.8.4 <5.8.0"
}
},
"node_modules/ua-parser-js": {
"version": "0.7.40",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.40.tgz",
"integrity": "sha512-us1E3K+3jJppDBa3Tl0L3MOJiGhe1C6P0+nIvQAFYbxlMAx0h81eOwLmU57xgqToduDDPx3y5QsdjPfDu+FgOQ==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
},
{
"type": "github",
"url": "https://github.com/sponsors/faisalman"
}
],
"license": "MIT",
"bin": {
"ua-parser-js": "script/cli.js"
},
"engines": {
"node": "*"
}
},
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/update-browserslist-db": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
@@ -5071,6 +5292,22 @@
"node": ">=0.10.0"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+2
View File
@@ -13,9 +13,11 @@
"@headlessui/react": "^2.2.0",
"@material-tailwind/react": "^2.1.10",
"@tanstack/react-query": "^5.62.8",
"draft-js": "^0.11.7",
"formik": "^2.4.6",
"i18next": "^24.2.0",
"iconsax-react": "^0.0.8",
"quill": "^2.0.3",
"rc-rate": "^2.13.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
-1
View File
@@ -11,7 +11,6 @@ import { IApiErrorRepsonse } from './types/error.types'
import MainRouter from './router/Main'
import AuthRouter from './router/Auth'
import { Pages } from './config/Pages'
i18next.init({
interpolation: { escapeValue: false },
lng: 'fa',
+55
View File
@@ -0,0 +1,55 @@
import { forwardRef, useEffect, useLayoutEffect, useRef } from 'react';
// Editor is an uncontrolled React component
const Editor = forwardRef(
({ readOnly, defaultValue, onTextChange, onSelectionChange }, ref) => {
const containerRef = useRef(null);
const defaultValueRef = useRef(defaultValue);
const onTextChangeRef = useRef(onTextChange);
const onSelectionChangeRef = useRef(onSelectionChange);
useLayoutEffect(() => {
onTextChangeRef.current = onTextChange;
onSelectionChangeRef.current = onSelectionChange;
});
useEffect(() => {
ref.current?.enable(!readOnly);
}, [ref, readOnly]);
useEffect(() => {
const container = containerRef.current;
const editorContainer = container.appendChild(
container.ownerDocument.createElement('div'),
);
const quill = new Quill(editorContainer, {
theme: 'snow',
});
ref.current = quill;
if (defaultValueRef.current) {
quill.setContents(defaultValueRef.current);
}
quill.on(Quill.events.TEXT_CHANGE, (...args) => {
onTextChangeRef.current?.(...args);
});
quill.on(Quill.events.SELECTION_CHANGE, (...args) => {
onSelectionChangeRef.current?.(...args);
});
return () => {
ref.current = null;
container.innerHTML = '';
};
}, [ref]);
return <div ref={containerRef}></div>;
},
);
Editor.displayName = 'Editor';
export default Editor;
+3
View File
@@ -40,6 +40,7 @@ export const Pages = {
},
discount: {
list: "/discounts/list",
create: "/discounts/create"
},
referralCode: {
list: "/referral-code/list",
@@ -49,9 +50,11 @@ export const Pages = {
},
ads: {
list: "/ads/list",
create: "/ads/create"
},
blog: {
list: "/blog/list",
create: "/blog/create"
},
messages: {
list: "/messages/list",
+76
View File
@@ -158,6 +158,82 @@
"info_ticket": "اطلاعات تیکت",
"update_sms": "به روزرسانی این تیکت از طریق پیامک اطلاع داده میشود"
},
"ads": {
"ads": "تبلیغات",
"upload": "آپلود",
"deactive": "غیرفعال",
"submit": "ثبت تبلیغ",
"upload_picture": "تصویر نمایش تبلیغ",
"to_upload_message": "تصویر مورد نظر را آپلود کنید",
"new_ads": "افزودن تبلیغ",
"location": "جایگاه",
"startDate": "تاریخ شروع نمایش",
"endDate": "تاریخ پایان نمایش",
"search": "جستجو",
"number": "شماره",
"title": "عنوان",
"date": "تاریخ",
"status": "وضعیت",
"enter_your_title": "عنوان تبلیغ را وارد کنید",
"link": "لینک",
"enter_your_link": "لینک تبلیغ را وارد کنید"
},
"discount": {
"list": "لیست تخفیفات",
"type": "نوع تخفیف",
"price": "مبلغ تخفیف",
"title": "عنوان کد",
"code": "کد تخفیف",
"enter_discount_description": "عنوان کد تخفیف را وارد کنید",
"add_new_discount": "افزودن تخفیف",
"auto_generate_code": "ساخت کد",
"price_or_percent": "درصد / مبلغ تخفیف",
"enter_price_or_percent": "درصد / مبلغ تخفیف را وارد کنید",
"service": "سرویس",
"status": "وضعیت",
"number": "شماره",
"startDate": "تاریخ شروع",
"endDate": "تاریخ پایان",
"service_status": "وضعیت سرویس",
"availabe_service": "سرویس هایی که شامل تخفیف میشوند",
"new_discount": "کد تخفیف جدید",
"submit": "ثبت تخفیف",
"discount_code": "کد تخفیف",
"direct_discount": "تخفیف مستقیم",
"fixed_amount": "مبلغ ثابت",
"percent": "درصد"
},
"blog": {
"blog": "بلاگ",
"category": "دسته بندی",
"new_blog": "افزودن بلاگ",
"blog_title": "عنوان بلاگ",
"blog_Summary": "خلاصه بلاگ",
"shareDate": "تاریخ انتشار",
"enter_your_title": "عنوان بلاگ را وارد کنید",
"add_blog": "افزودن بلاگ",
"price": "مبلغ تخفیف",
"title": "عنوان کد",
"code": "کد تخفیف",
"enter_discount_description": "عنوان کد تخفیف را وارد کنید",
"add_new_discount": "افزودن تخفیف",
"auto_generate_code": "ساخت کد",
"price_or_percent": "درصد / مبلغ تخفیف",
"enter_price_or_percent": "درصد / مبلغ تخفیف را وارد کنید",
"service": "سرویس",
"status": "وضعیت",
"number": "شماره",
"startDate": "تاریخ شروع",
"endDate": "تاریخ پایان",
"service_status": "وضعیت سرویس",
"availabe_service": "سرویس هایی که شامل تخفیف میشوند",
"new_discount": "کد تخفیف جدید",
"submit": "ثبت بلاگ",
"discount_code": "کد تخفیف",
"direct_discount": "تخفیف مستقیم",
"fixed_amount": "مبلغ ثابت",
"percent": "درصد"
},
"receip": {
"receip_list": "لیست صورتحساب ها",
"dont_pay": "پرداخت نشده",
+135
View File
@@ -0,0 +1,135 @@
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import Button from '../../components/Button'
import { Add, Eye } from 'iconsax-react'
import Td from '../../components/Td'
import { Link } from 'react-router-dom'
import { Pages } from '../../config/Pages'
import SwitchComponent from '../../components/Switch'
import DatePickerComponent from '../../components/DatePicker'
import Select from '../../components/Select'
import Input from '../../components/Input'
const TicketList: FC = () => {
const { t } = useTranslation('global')
return (
<div className='mt-4 min-h-[500px]'>
<div className='flex justify-between items-center'>
<div>
{t('ads.ads')}
</div>
<Link to={Pages.ads.create}>
<Button
className='w-[172px]'
>
<div className='flex gap-2 items-center'>
<Add size={20} color='white' />
<div>
{t('ads.new_ads')}
</div>
</div>
</Button>
</Link>
</div>
<div className='mt-4'>
<div className='flex justify-between items-center'>
<div className='flex items-center gap-4'>
<div className='w-[200px]'>
<DatePickerComponent
label={t('ads.date')}
onChange={() => { }}
placeholder=''
defaulValue='1400-01-01'
/>
</div>
<div className='w-[200px]'>
<Select
label={t('ads.status')}
items={[
{
label: 'فعال',
value: 'active'
}
]}
className='bg-white border'
/>
</div>
</div>
<div className='xl:w-[300px] w-full'>
<Input
variant='search'
placeholder={t('ads.search')}
className='bg-white w-full'
/>
</div>
</div>
</div>
<div className='relative overflow-x-auto rounded-3xl mt-9 w-full'>
<table className='w-full text-sm '>
<thead className='thead'>
<tr>
<Td text={t('ads.number')} />
<Td text={t('ads.title')} />
<Td text={t('ads.location')} />
<Td text={t('ads.startDate')} />
<Td text={t('ads.endDate')} />
<Td text={t('ads.status')} />
<Td text={''} />
</tr>
</thead>
<tbody>
<tr className='tr'>
<Td text={t('ads.number')} />
<Td text={t('ads.title')} />
<Td text={t('ads.location')} />
<Td text={t('ads.startDate')} />
<Td text={t('ads.endDate')} />
<Td text={t('')}>
<SwitchComponent
active={true}
onChange={() => { }}
/>
</Td>
<Td text={''}>
<Link to={Pages.ticket.detail + '1'}>
<Eye size={20} color='#8C90A3' />
</Link>
</Td>
</tr>
<tr className='tr'>
<Td text={t('ads.number')} />
<Td text={t('ads.title')} />
<Td text={t('ads.location')} />
<Td text={t('ads.startDate')} />
<Td text={t('ads.endDate')} />
<Td text={t('')}>
<SwitchComponent
active={true}
onChange={() => { }}
/>
</Td>
<Td text={''}>
<Link to={Pages.discount + '1'}>
<Eye size={20} color='#8C90A3' />
</Link>
</Td>
</tr>
</tbody>
</table>
</div>
</div>
)
}
export default TicketList
+108
View File
@@ -0,0 +1,108 @@
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import Input from '../../components/Input'
import Button from '../../components/Button'
import SwitchComponent from '../../components/Switch'
import { TickCircle, TickSquare, Link2 } from 'iconsax-react'
import DatePickerComponent from '../../components/DatePicker'
import ImageUploader from './components/ImageUploader'
const CreateTicket: FC = () => {
const { t } = useTranslation('global')
return (
<div className='mt-4'>
<div className='flex justify-between items-center'>
<div>
{t('ads.new_ads')}
</div>
<Button
className='w-[172px]'
>
<div className='flex gap-2 items-center'>
<TickCircle size={20} color='white' />
<div>
{t('ads.submit')}
</div>
</div>
</Button>
</div>
<div className='flex gap-6 xl:mt-8 mt-4'>
<div className='flex-1 min-h-[calc(100vh-201px)] bg-white py-8 xl:px-10 px-4 rounded-3xl'>
<div className='mt-6'>
<Input
label={t('ads.title')}
placeholder={t('ads.enter_your_title')}
/>
</div>
<div className='mt-6 rowTwoInput'>
<DatePickerComponent
label={t('ads.startDate')}
onChange={() => { }}
placeholder=''
defaulValue='1400-01-01'
/>
<DatePickerComponent
label={t('ads.endDate')}
onChange={() => { }}
placeholder=''
defaulValue='1400-01-01'
/>
</div>
<div className='mt-6 relative'>
<Input
label={t('ads.link')}
placeholder={t('ads.enter_your_link')}
/>
<Link2 size={20} color='blue' className='absolute left-4 bottom-[10px]' />
</div>
</div>
<div className='min-h-[calc(100vh-201px)] bg-white w-sidebar xl:block hidden py-10 px-5 h-fit rounded-3xl'>
<div className='text-sm flex items-center justify-between'>
<p>
وضعیت تبلیغ
</p>
<div className='flex gap-1 text-xs items-center text-description'>
<div>
{t('ads.deactive')}
</div>
<SwitchComponent
active={false}
onChange={() => { }}
/>
</div>
</div>
<p className='mt-6 text-sm'>{t('ads.location')}</p>
<div className='p-2 border border-[##D0D0D0] rounded-lg mt-1'>
<div className='flex items-start gap-2 py-2'>
<div>
<TickSquare size={20} color='black' variant='Bold' />
</div>
<div className='text-description text-xs leading-5'>
لورم ایپسوم
</div>
</div>
<div className='flex items-start gap-2 py-2'>
<div>
<TickSquare size={20} color='black' variant='Bold' />
</div>
<div className='text-description text-xs leading-5'>
لورم ایپسوم
</div>
</div>
<div className='flex items-start gap-2 py-2'>
<div>
<TickSquare size={20} color='black' variant='Bold' />
</div>
<div className='text-description text-xs leading-5'>
لورم ایپسوم
</div>
</div>
</div>
<ImageUploader />
</div>
</div>
</div>
)
}
export default CreateTicket
@@ -0,0 +1,49 @@
import { FC, useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Image, DocumentUpload } from 'iconsax-react'
import { useDropzone } from 'react-dropzone'
const ImageUploader: FC = () => {
const { t } = useTranslation('global')
const [file, setFile] = useState<File>()
const onDrop = useCallback((acceptedFiles: File[]) => {
setFile(acceptedFiles[0])
}, [])
const { getRootProps, getInputProps } = useDropzone({ onDrop })
return(
<>
<p className='mt-6 text-sm'>{t('ads.upload_picture')}</p>
<div
{...getRootProps()}
className='py-12 border border-dashed border-[#8C90A3] rounded-lg mt-2 flex items-center justify-center'>
<div className='flex flex-col items-center space-y-4'>
<Image size={45} color='#8C90A3' variant="Bold" className='border-[3px] border-[#8C90A3] rounded-md'
/>
<p className='text-sm text-[#8C90A3]'>
{
file ?
file.name
:
t('ads.to_upload_message')
}
</p>
<div className='flex gap-1'>
<DocumentUpload
size={20}
color='black'
/>
<button className='text-sm'>
<input {...getInputProps()} />
{t('ads.upload')}</button>
</div>
</div>
</div>
</>
)
}
export default ImageUploader
+147
View File
@@ -0,0 +1,147 @@
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import Button from '../../components/Button'
import { Add, Eye } from 'iconsax-react'
import Td from '../../components/Td'
import { Link } from 'react-router-dom'
import { Pages } from '../../config/Pages'
import SwitchComponent from '../../components/Switch'
import DatePickerComponent from '../../components/DatePicker'
import Select from '../../components/Select'
import Input from '../../components/Input'
const BlogList: FC = () => {
const { t } = useTranslation('global')
return (
<div className='mt-4 min-h-[500px]'>
<div className='flex justify-between items-center'>
<div>
{t('blog.blog')}
</div>
<Link to={Pages.blog.create}>
<Button
className='w-[172px]'
>
<div className='flex gap-2 items-center'>
<Add size={20} color='white' />
<div>
{t('blog.new_blog')}
</div>
</div>
</Button>
</Link>
</div>
<div className='mt-4'>
<div className='flex justify-between items-center'>
<div className='flex items-center gap-4'>
<div className='w-[200px]'>
<Select
label={t('blog.category')}
items={[
{
label: 'تست',
value: 'active'
}
]}
className='bg-white border'
/>
</div>
<div className='w-[200px]'>
<Select
label={t('ads.status')}
items={[
{
label: 'فعال',
value: 'active'
}
]}
className='bg-white border'
/>
</div>
<div className='w-[200px]'>
<DatePickerComponent
label={t('ads.date')}
onChange={() => { }}
placeholder=''
defaulValue='1400-01-01'
/>
</div>
</div>
<div className='xl:w-[300px] w-full'>
<Input
variant='search'
placeholder={t('ads.search')}
className='bg-white w-full'
/>
</div>
</div>
</div>
<div className='relative overflow-x-auto rounded-3xl mt-9 w-full'>
<table className='w-full text-sm '>
<thead className='thead'>
<tr>
<Td text={t('blog.number')} />
<Td text={t('blog.blog_title')} />
<Td text={t('blog.blog_Summary')} />
<Td text={t('blog.category')} />
<Td text={t('blog.shareDate')} />
<Td text={t('blog.status')} />
<Td text={''} />
</tr>
</thead>
<tbody>
<tr className='tr'>
<Td text={t('blog.number')} />
<Td text={t('blog.blog_title')} />
<Td text={t('blog.blog_Summary')} />
<Td text={t('blog.category')} />
<Td text={t('blog.shareDate')} />
<Td text={t('')}>
<SwitchComponent
active={true}
onChange={() => { }}
/>
</Td>
<Td text={''}>
<Link to={Pages.ticket.detail + '1'}>
<Eye size={20} color='#8C90A3' />
</Link>
</Td>
</tr>
<tr className='tr'>
<Td text={t('blog.number')} />
<Td text={t('blog.blog_title')} />
<Td text={t('blog.blog_Summary')} />
<Td text={t('blog.category')} />
<Td text={t('blog.shareDate')} />
<Td text={t('')}>
<SwitchComponent
active={true}
onChange={() => { }}
/>
</Td>
<Td text={''}>
<Link to={Pages.discount + '1'}>
<Eye size={20} color='#8C90A3' />
</Link>
</Td>
</tr>
</tbody>
</table>
</div>
</div>
)
}
export default BlogList
+122
View File
@@ -0,0 +1,122 @@
import { FC, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import Input from '../../components/Input'
import Button from '../../components/Button'
import SwitchComponent from '../../components/Switch'
import { TickCircle, TickSquare, Link2 } from 'iconsax-react'
import DatePickerComponent from '../../components/DatePicker'
import ImageUploader from './components/ImageUploader'
import "quill/dist/quill.snow.css";
import Quill from 'quill';
const CreateTicket: FC = () => {
const { t } = useTranslation('global')
useEffect(() => {
const quill = new Quill('#editor', {
theme: 'snow',
});
}, []);
return (
<div className='mt-4'>
<div className='flex justify-between items-center'>
<div>
{t('blog.add_blog')}
</div>
<Button
className='w-[172px]'
>
<div className='flex gap-2 items-center'>
<TickCircle size={20} color='white' />
<div>
{t('blog.submit')}
</div>
</div>
</Button>
</div>
<div className='flex gap-6 xl:mt-8 mt-4'>
<div className='flex-1 min-h-[calc(100vh-201px)] bg-white py-8 xl:px-10 px-4 rounded-3xl'>
<div className='mt-6'>
<Input
label={t('blog.blog_title')}
placeholder={t('blog.enter_your_title')}
/>
</div>
<div className='mt-6'>
<div id='editor'></div>
</div>
<div className='mt-6 rowTwoInput'>
<DatePickerComponent
label={t('ads.startDate')}
onChange={() => { }}
placeholder=''
defaulValue='1400-01-01'
/>
<DatePickerComponent
label={t('ads.endDate')}
onChange={() => { }}
placeholder=''
defaulValue='1400-01-01'
/>
</div>
<div className='mt-6 relative'>
<Input
label={t('ads.link')}
placeholder={t('ads.enter_your_link')}
/>
<Link2 size={20} color='blue' className='absolute left-4 bottom-[10px]' />
</div>
</div>
<div className='min-h-[calc(100vh-201px)] bg-white w-sidebar xl:block hidden py-10 px-5 h-fit rounded-3xl'>
<div className='text-sm flex items-center justify-between'>
<p>
وضعیت تبلیغ
</p>
<div className='flex gap-1 text-xs items-center text-description'>
<div>
{t('ads.deactive')}
</div>
<SwitchComponent
active={false}
onChange={() => { }}
/>
</div>
</div>
<p className='mt-6 text-sm'>{t('ads.location')}</p>
<div className='p-2 border border-[##D0D0D0] rounded-lg mt-1'>
<div className='flex items-start gap-2 py-2'>
<div>
<TickSquare size={20} color='black' variant='Bold' />
</div>
<div className='text-description text-xs leading-5'>
لورم ایپسوم
</div>
</div>
<div className='flex items-start gap-2 py-2'>
<div>
<TickSquare size={20} color='black' variant='Bold' />
</div>
<div className='text-description text-xs leading-5'>
لورم ایپسوم
</div>
</div>
<div className='flex items-start gap-2 py-2'>
<div>
<TickSquare size={20} color='black' variant='Bold' />
</div>
<div className='text-description text-xs leading-5'>
لورم ایپسوم
</div>
</div>
</div>
<ImageUploader />
</div>
</div>
</div>
)
}
export default CreateTicket
@@ -0,0 +1,49 @@
import { FC, useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Image, DocumentUpload } from 'iconsax-react'
import { useDropzone } from 'react-dropzone'
const ImageUploader: FC = () => {
const { t } = useTranslation('global')
const [file, setFile] = useState<File>()
const onDrop = useCallback((acceptedFiles: File[]) => {
setFile(acceptedFiles[0])
}, [])
const { getRootProps, getInputProps } = useDropzone({ onDrop })
return(
<>
<p className='mt-6 text-sm'>{t('ads.upload_picture')}</p>
<div
{...getRootProps()}
className='py-12 border border-dashed border-[#8C90A3] rounded-lg mt-2 flex items-center justify-center'>
<div className='flex flex-col items-center space-y-4'>
<Image size={45} color='#8C90A3' variant="Bold" className='border-[3px] border-[#8C90A3] rounded-md'
/>
<p className='text-sm text-[#8C90A3]'>
{
file ?
file.name
:
t('ads.to_upload_message')
}
</p>
<div className='flex gap-1'>
<DocumentUpload
size={20}
color='black'
/>
<button className='text-sm'>
<input {...getInputProps()} />
{t('ads.upload')}</button>
</div>
</div>
</div>
</>
)
}
export default ImageUploader
+190
View File
@@ -0,0 +1,190 @@
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import Input from '../../components/Input'
import Button from '../../components/Button'
import SwitchComponent from '../../components/Switch'
import { TickCircle, TickSquare, Refresh } from 'iconsax-react'
import DatePickerComponent from '../../components/DatePicker'
import ImageUploader from './components/ImageUploader'
import RadioGroup from '../../components/RadioGroup'
const CreateTicket: FC = () => {
const { t } = useTranslation('global')
return (
<div className='mt-4'>
<div className='flex justify-between items-center'>
<div>
{t('discount.new_discount')}
</div>
<Button
className='w-[172px]'
>
<div className='flex gap-2 items-center'>
<TickCircle size={20} color='white' />
<div>
{t('discount.submit')}
</div>
</div>
</Button>
</div>
<div className='flex gap-6 xl:mt-8 mt-4'>
<div className='flex-1 min-h-[calc(100vh-201px)] bg-white py-8 xl:px-10 px-4 rounded-3xl'>
<div className='mt-6'>
{t('discount.type')}
<div className='rowTwoInput mt-2'>
<RadioGroup
items={[
{
label: t('discount.direct_discount'),
value: '3month'
},
{
label: t('discount.discount_code'),
value: 'year'
}
]}
onChange={''}
selected={''}
/>
</div>
</div>
<div className='mt-6 rowTwoInput'>
<Input
label={t('discount.title')}
placeholder={t('discount.enter_discount_description')}
/>
<div className='w-full mt-7 flex items-center'>
<button className='text-xs border h-full w-[142px] border-black rounded-xl flex justify-center items-center gap-2'>
<Refresh
size={18} color='black'
/>
<p>
{t('discount.auto_generate_code')}
</p>
</button>
</div>
</div>
<div className='mt-6 rowTwoInput'>
<div className='w-full'>
{t('discount.price')}
<div className='rowTwoInput mt-2'>
<RadioGroup
items={[
{
label: t('discount.fixed_amount'),
value: '3month'
},
{
label: t('discount.percent'),
value: 'year'
}
]}
onChange={''}
selected={''}
/>
</div>
</div>
<Input
label={t('discount.price_or_percent')}
placeholder={t('discount.enter_price_or_percent')}
/>
</div>
<div className='mt-6 rowTwoInput'>
<DatePickerComponent
label={t('discount.startDate')}
onChange={() => { }}
placeholder=''
defaulValue='1400-01-01'
/>
<DatePickerComponent
label={t('discount.endDate')}
onChange={() => { }}
placeholder=''
defaulValue='1400-01-01'
/>
</div>
</div>
<div className='min-h-[calc(100vh-201px)] bg-white w-sidebar xl:block hidden py-10 px-5 h-fit rounded-3xl'>
<div className='text-sm flex items-center justify-between'>
<p>
{t('discount.service_status')}
</p>
<div className='flex gap-1 text-xs items-center text-description'>
<div>
{t('ads.deactive')}
</div>
<SwitchComponent
active={false}
onChange={() => { }}
/>
</div>
</div>
<p className='mt-6 text-sm'>{t('discount.availabe_service')}</p>
<div className='p-2 border border-[##D0D0D0] rounded-lg mt-2'>
<Input
variant='search'
placeholder={t('header.search')}
/>
<div className='flex items-start gap-2 py-2'>
<div>
<TickSquare size={20} color='black' variant='Bold' />
</div>
<div className='text-xs leading-5'>
لورم ایپسوم
</div>
</div>
<div className='flex items-start gap-2 py-2'>
<div>
<TickSquare size={20} color='black' variant='Bold' />
</div>
<div className='text-xs leading-5'>
لورم ایپسوم
</div>
</div>
<div className='flex items-start gap-2 py-2'>
<div>
<TickSquare size={20} color='black' variant='Bold' />
</div>
<div className='text-xs leading-5'>
لورم ایپسوم
</div>
</div>
</div>
</div>
</div>
</div>
)
}
export default CreateTicket
+116
View File
@@ -0,0 +1,116 @@
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import Button from '../../components/Button'
import { Add, Eye } from 'iconsax-react'
import Td from '../../components/Td'
import { Link } from 'react-router-dom'
import { Pages } from '../../config/Pages'
import SwitchComponent from '../../components/Switch'
import Input from '../../components/Input'
const TicketList: FC = () => {
const { t } = useTranslation('global')
return (
<div className='mt-4 min-h-[500px]'>
<div className='flex justify-between items-center'>
<div>
{t('discount.list')}
</div>
<Link to={Pages.discount.create}>
<Button
className='w-[172px]'
>
<div className='flex gap-2 items-center'>
<Add size={20} color='white' />
<div>
{t('discount.add_new_discount')}
</div>
</div>
</Button>
</Link>
</div>
<div className='mt-4'>
<div className='flex justify-end items-center'>
<div className='xl:w-[300px] w-full'>
<Input
variant='search'
placeholder={t('ads.search')}
className='bg-white w-full'
/>
</div>
</div>
</div>
<div className='relative overflow-x-auto rounded-3xl mt-9 w-full'>
<table className='w-full text-sm '>
<thead className='thead'>
<tr>
<Td text={t('discount.number')} />
<Td text={t('discount.type')} />
<Td text={t('discount.code')} />
<Td text={t('discount.startDate')} />
<Td text={t('discount.endDate')} />
<Td text={t('discount.price_or_percent')} />
<Td text={t('discount.service')} />
<Td text={t('discount.status')} />
<Td text={''} />
</tr>
</thead>
<tbody>
<tr className='tr'>
<Td text={t('discount.number')} />
<Td text={t('discount.type')} />
<Td text={t('discount.code')} />
<Td text={t('discount.startDate')} />
<Td text={t('discount.endDate')} />
<Td text={t('discount.price_or_percent')} />
<Td text={t('discount.service')} />
<Td text={t('')}>
<SwitchComponent
active={true}
onChange={() => { }}
/>
</Td>
<Td text={''}>
<Link to={Pages.ticket.detail + '1'}>
<Eye size={20} color='#8C90A3' />
</Link>
</Td>
</tr>
<tr className='tr'>
<Td text={t('discount.number')} />
<Td text={t('discount.type')} />
<Td text={t('discount.code')} />
<Td text={t('discount.startDate')} />
<Td text={t('discount.endDate')} />
<Td text={t('discount.price_or_percent')} />
<Td text={t('discount.service')} />
<Td text={t('')}>
<SwitchComponent
active={true}
onChange={() => { }}
/>
</Td>
<Td text={''}>
<Link to={Pages.discount + '1'}>
<Eye size={20} color='#8C90A3' />
</Link>
</Td>
</tr>
</tbody>
</table>
</div>
</div>
)
}
export default TicketList
@@ -0,0 +1,49 @@
import { FC, useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Image, DocumentUpload } from 'iconsax-react'
import { useDropzone } from 'react-dropzone'
const ImageUploader: FC = () => {
const { t } = useTranslation('global')
const [file, setFile] = useState<File>()
const onDrop = useCallback((acceptedFiles: File[]) => {
setFile(acceptedFiles[0])
}, [])
const { getRootProps, getInputProps } = useDropzone({ onDrop })
return(
<>
<p className='mt-6 text-sm'>{t('ads.upload_picture')}</p>
<div
{...getRootProps()}
className='py-12 border border-dashed border-[#8C90A3] rounded-lg mt-2 flex items-center justify-center'>
<div className='flex flex-col items-center space-y-4'>
<Image size={45} color='#8C90A3' variant="Bold" className='border-[3px] border-[#8C90A3] rounded-md'
/>
<p className='text-sm text-[#8C90A3]'>
{
file ?
file.name
:
t('ads.to_upload_message')
}
</p>
<div className='flex gap-1'>
<DocumentUpload
size={20}
color='black'
/>
<button className='text-sm'>
<input {...getInputProps()} />
{t('ads.upload')}</button>
</div>
</div>
</div>
</>
)
}
export default ImageUploader
+12
View File
@@ -5,6 +5,12 @@ import { Route, Routes } from 'react-router-dom'
import { Pages } from '../config/Pages'
import Home from '../pages/home/Home'
import MyServices from '../pages/service/MyServices'
import DiscountsList from '../pages/discounts/DiscountsList'
import CreateDiscount from '../pages/discounts/CreateDiscount'
import AdsList from '../pages/ads/AdsList'
import BlogList from '../pages/blog/BlogList'
import CreateBlog from '../pages/blog/CreateBlog'
import CreateAds from '../pages/ads/CreateAds'
import TicketList from '../pages/ticket/TicketList'
import CreateTicket from '../pages/ticket/CreateTicket'
import TicketDetail from '../pages/ticket/Detail'
@@ -38,6 +44,12 @@ const MainRouter: FC = () => {
<Route path={Pages.ticket.list} element={<TicketList />} />
<Route path={Pages.ticket.create} element={<CreateTicket />} />
<Route path={Pages.ticket.detail + ':id'} element={<TicketDetail />} />
<Route path={Pages.ads.list} element={<AdsList />} />
<Route path={Pages.ads.create} element={<CreateAds />} />
<Route path={Pages.discount.list} element={<DiscountsList />} />
<Route path={Pages.discount.create} element={<CreateDiscount />} />
<Route path={Pages.blog.list} element={<BlogList />} />
<Route path={Pages.blog.create} element={<CreateBlog />} />
<Route path={Pages.transactions} element={<TransactionList />} />
<Route path={Pages.receipts.index} element={<ReceiptsList />} />
<Route path={Pages.receipts.detail + ':id'} element={<ReceiptsDetail />} />