├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── custom.md
│ └── feature_request.md
├── pull_request_template.md
└── workflows
│ ├── CODEOWNERS
│ ├── codesee-arch-diagram.yml
│ └── github-repo-stats.yml
├── .vscode
└── settings.json
├── CODE-OF-CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── LICENSE_EE
├── README.md
├── SECURITY.md
├── backend
├── .env
├── .eslintrc.js
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .vscode
│ └── settings.json
├── app.js
├── controllers
│ ├── appControllers
│ │ ├── clientController
│ │ │ ├── index.js
│ │ │ ├── remove.js
│ │ │ └── summary.js
│ │ ├── employeeController.js
│ │ ├── expenseCategoryController.js
│ │ ├── expenseController.js
│ │ ├── inventoryController.js
│ │ ├── invoiceController
│ │ │ ├── create.js
│ │ │ ├── index.js
│ │ │ ├── mailInvoiceController.js
│ │ │ ├── paginatedList.js
│ │ │ ├── read.js
│ │ │ ├── remove.js
│ │ │ ├── schemaValidate.js
│ │ │ ├── summary.js
│ │ │ └── update.js
│ │ ├── itemController.js
│ │ ├── kycController.js
│ │ ├── leadController.js
│ │ ├── offerController
│ │ │ ├── create.js
│ │ │ ├── index.js
│ │ │ ├── paginatedList.js
│ │ │ ├── read.js
│ │ │ ├── summary.js
│ │ │ └── update.js
│ │ ├── orderController.js
│ │ ├── paymentController
│ │ │ ├── create.js
│ │ │ ├── index.js
│ │ │ ├── remove.js
│ │ │ ├── summary.js
│ │ │ └── update.js
│ │ ├── paymentModeController.js
│ │ ├── quoteController
│ │ │ ├── convertQuoteToInvoice.js
│ │ │ ├── create.js
│ │ │ ├── index.js
│ │ │ ├── mailQuoteController.js
│ │ │ ├── paginatedList.js
│ │ │ ├── read.js
│ │ │ ├── summary.js
│ │ │ └── update.js
│ │ ├── supplierController.js
│ │ ├── supplierOrderController
│ │ │ ├── create.js
│ │ │ ├── index.js
│ │ │ ├── summary.js
│ │ │ └── update.js
│ │ └── taxController.js
│ ├── coreControllers
│ │ ├── adminController
│ │ │ ├── create.js
│ │ │ ├── filter.js
│ │ │ ├── index.js
│ │ │ ├── listAll.js
│ │ │ ├── paginatedList.js
│ │ │ ├── photo.js
│ │ │ ├── profile.js
│ │ │ ├── read.js
│ │ │ ├── remove.js
│ │ │ ├── search.js
│ │ │ ├── status.js
│ │ │ ├── update.js
│ │ │ ├── updatePassword.js
│ │ │ └── updateProfile.js
│ │ ├── authJwtController
│ │ │ ├── index.js
│ │ │ ├── isValidAdminToken.js
│ │ │ ├── login.js
│ │ │ └── logout.js
│ │ ├── emailController
│ │ │ └── index.js
│ │ └── settingController
│ │ │ ├── index.js
│ │ │ ├── listBySettingKey.js
│ │ │ ├── readBySettingKey.js
│ │ │ ├── updateBySettingKey.js
│ │ │ └── updateManySetting.js
│ └── middlewaresControllers
│ │ ├── createCRUDController
│ │ ├── create.js
│ │ ├── filter.js
│ │ ├── index.js
│ │ ├── listAll.js
│ │ ├── paginatedList.js
│ │ ├── read.js
│ │ ├── remove.js
│ │ ├── search.js
│ │ └── update.js
│ │ └── pdfController
│ │ └── index.js
├── emailTemplate
│ └── SendInvoice.js
├── handlers
│ ├── downloadHandler
│ │ └── downloadPdf.js
│ └── errorHandlers.js
├── helpers.js
├── jsconfig.json
├── middlewares
│ ├── permission.js
│ ├── serverData.js
│ ├── settings
│ │ ├── increaseBySettingKey.js
│ │ ├── index.js
│ │ ├── listAllSetting.js
│ │ ├── listBySettingKey.js
│ │ ├── readBySettingKey.js
│ │ └── updateBySettingKey.js
│ └── uploadMiddleware
│ │ ├── createMultipleUpload.js
│ │ ├── createSingleUpload.js
│ │ ├── fileFilter.js
│ │ ├── index.js
│ │ ├── setFilePathToBody.js
│ │ ├── singleStorageUpload.js
│ │ ├── uploadMultipleToStorage.js
│ │ └── uploadSingleToStorage.js
├── models
│ ├── .gitkeep
│ ├── appModels
│ │ ├── Client.js
│ │ ├── Employee.js
│ │ ├── Expense.js
│ │ ├── ExpenseCategory.js
│ │ ├── Inventory.js
│ │ ├── Invoice.js
│ │ ├── Item.js
│ │ ├── Kyc.js
│ │ ├── Lead.js
│ │ ├── Offer.js
│ │ ├── Order.js
│ │ ├── Payment.js
│ │ ├── PaymentMode.js
│ │ ├── Quote.js
│ │ ├── Supplier.js
│ │ ├── SupplierOrder.js
│ │ └── Tax.js
│ └── coreModels
│ │ ├── Admin.js
│ │ ├── Email.js
│ │ ├── NoCodeCollections.js
│ │ ├── Setting.js
│ │ └── Upload.js
├── package-lock.json
├── package.json
├── public
│ └── uploads
│ │ └── setting
│ │ └── idurar-crm-erp-giy0y.svg
├── routes
│ ├── appRoutes
│ │ └── appApi.js
│ └── coreRoutes
│ │ ├── coreApi.js
│ │ ├── coreAuth.js
│ │ ├── coreDownloadRouter.js
│ │ └── corePublicRouter.js
├── server.js
├── setup
│ ├── config
│ │ ├── appConfig.json
│ │ ├── companyConfig.json
│ │ ├── crmConfig.json
│ │ ├── customConfig.json
│ │ ├── emailTemplate.json
│ │ ├── financeConfig.json
│ │ └── moneyFormatConfig.json
│ ├── reset.js
│ └── setup.js
└── views
│ └── pdf
│ ├── Invoice.pug
│ ├── Offer.pug
│ ├── Payment.pug
│ ├── Quote.pug
│ ├── SupplierOrder.pug
│ └── layout.pug
├── frontend
├── .env
├── .eslintrc.cjs
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .vscode
│ └── settings.json
├── README.md
├── index.html
├── jsconfig.json
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
│ ├── RootApp.jsx
│ ├── apps
│ │ ├── ErpApp.jsx
│ │ ├── IdurarOs.jsx
│ │ └── components
│ │ │ ├── HeaderContent.jsx
│ │ │ └── Navigation.jsx
│ ├── auth
│ │ ├── auth.service.js
│ │ └── index.js
│ ├── components
│ │ ├── AutoCompleteAsync
│ │ │ └── index.jsx
│ │ ├── CollapseBox
│ │ │ └── index.jsx
│ │ ├── CreateForm
│ │ │ └── index.jsx
│ │ ├── CrudModal
│ │ │ └── index.jsx
│ │ ├── DataTable
│ │ │ └── DataTable.jsx
│ │ ├── DeleteModal
│ │ │ └── index.jsx
│ │ ├── IconMenu
│ │ │ └── index.jsx
│ │ ├── Loading
│ │ │ └── index.jsx
│ │ ├── MoneyInputFormItem
│ │ │ └── index.jsx
│ │ ├── MultiStepSelectAsync
│ │ │ └── index.jsx
│ │ ├── NotFound
│ │ │ └── index.jsx
│ │ ├── Notification
│ │ │ └── index.jsx
│ │ ├── PageLoader
│ │ │ └── index.jsx
│ │ ├── ReadItem
│ │ │ ├── index.jsx
│ │ │ └── index.test.jsx
│ │ ├── SearchItem
│ │ │ └── index.jsx
│ │ ├── SelectAsync
│ │ │ └── index.jsx
│ │ ├── SelectLanguage
│ │ │ └── index.jsx
│ │ ├── SideContent
│ │ │ └── index.jsx
│ │ ├── SidePanel
│ │ │ └── index.jsx
│ │ ├── TabsContent
│ │ │ └── TabsContent.jsx
│ │ ├── Tag
│ │ │ └── index.jsx
│ │ ├── UpdateForm
│ │ │ └── index.jsx
│ │ ├── Visibility
│ │ │ └── index.jsx
│ │ └── outsideClick.js
│ │ │ ├── demo.js
│ │ │ └── index.js
│ ├── config
│ │ ├── defaultInvoiceSettings.js
│ │ └── serverApiConfig.js
│ ├── context
│ │ ├── appContext
│ │ │ ├── actions.jsx
│ │ │ ├── index.jsx
│ │ │ ├── reducer.jsx
│ │ │ └── types.jsx
│ │ ├── crud
│ │ │ ├── actions.jsx
│ │ │ ├── index.jsx
│ │ │ ├── reducer.jsx
│ │ │ ├── selectors.jsx
│ │ │ └── types.jsx
│ │ ├── erp
│ │ │ ├── actions.jsx
│ │ │ ├── index.jsx
│ │ │ ├── reducer.jsx
│ │ │ ├── selectors.jsx
│ │ │ └── types.jsx
│ │ └── profileContext
│ │ │ ├── actions.jsx
│ │ │ ├── index.jsx
│ │ │ ├── reducer.jsx
│ │ │ ├── selectors.jsx
│ │ │ └── types.jsx
│ ├── favicon.ico
│ ├── forms
│ │ ├── AdminForm.jsx
│ │ ├── AdvancedSettingsForm.jsx
│ │ ├── CurrencyForm.jsx
│ │ ├── CustomerForm.jsx
│ │ ├── DynamicForm
│ │ │ └── index.jsx
│ │ ├── EmployeeForm.jsx
│ │ ├── InventoryForm.jsx
│ │ ├── LeadForm.jsx
│ │ ├── LoginForm.jsx
│ │ ├── OrderForm.jsx
│ │ ├── PaymentForm.jsx
│ │ ├── PaymentModeForm.jsx
│ │ ├── RegisterForm.jsx
│ │ ├── TaxForm.jsx
│ │ └── UpdateEmail.jsx
│ ├── hooks
│ │ ├── useDebounce.jsx
│ │ ├── useFetch.jsx
│ │ ├── useIsMobile.jsx
│ │ ├── useMail.jsx
│ │ ├── useNetwork.jsx
│ │ ├── useOnFetch.jsx
│ │ ├── useResponsiveTable.jsx
│ │ └── useTimeoutFn.jsx
│ ├── layout
│ │ ├── AuthLayout
│ │ │ └── index.jsx
│ │ ├── CrudLayout
│ │ │ └── index.jsx
│ │ ├── DashboardLayout
│ │ │ └── index.jsx
│ │ ├── DefaultLayout
│ │ │ └── index.jsx
│ │ ├── ErpLayout
│ │ │ └── index.jsx
│ │ ├── Footer
│ │ │ └── index.jsx
│ │ ├── ProfileLayout
│ │ │ └── index.jsx
│ │ ├── SettingsLayout
│ │ │ └── index.jsx
│ │ └── index.jsx
│ ├── locale
│ │ ├── Localization.jsx
│ │ ├── antdLocale.js
│ │ ├── languages.js
│ │ ├── translation
│ │ │ ├── ar_eg.js
│ │ │ ├── bg_bg.js
│ │ │ ├── bn_bd.js
│ │ │ ├── ca_es.js
│ │ │ ├── cs_cz.js
│ │ │ ├── da_dk.js
│ │ │ ├── de_de.js
│ │ │ ├── el_gr.js
│ │ │ ├── en_us.js
│ │ │ ├── es_es.js
│ │ │ ├── et_ee.js
│ │ │ ├── fa_ir.js
│ │ │ ├── fi_fi.js
│ │ │ ├── fr_fr.js
│ │ │ ├── hi_in.js
│ │ │ ├── hr_hr.js
│ │ │ ├── hu_hu.js
│ │ │ ├── id_id.js
│ │ │ ├── it_it.js
│ │ │ ├── ja_jp.js
│ │ │ ├── ko_kr.js
│ │ │ ├── lt_lt.js
│ │ │ ├── lv_lv.js
│ │ │ ├── mk_mk.js
│ │ │ ├── ms_my.js
│ │ │ ├── nb_no.js
│ │ │ ├── nl_nl.js
│ │ │ ├── pl_pl.js
│ │ │ ├── pt_br.js
│ │ │ ├── pt_pt.js
│ │ │ ├── ro_ro.js
│ │ │ ├── ru_ru.js
│ │ │ ├── sk_sk.js
│ │ │ ├── sl_si.js
│ │ │ ├── sr_rs.js
│ │ │ ├── sv_se.js
│ │ │ ├── th_th.js
│ │ │ ├── tr_tr.js
│ │ │ ├── translation.js
│ │ │ ├── uk_ua.js
│ │ │ ├── ur_pk.js
│ │ │ ├── vi_vn.js
│ │ │ └── zh_cn.js
│ │ └── useLanguage.jsx
│ ├── logo-icon.svg
│ ├── main.jsx
│ ├── modules
│ │ ├── AdminCrudModule
│ │ │ ├── AdminDataTable.jsx
│ │ │ ├── UpdatePassword.jsx
│ │ │ └── index.jsx
│ │ ├── CrudModule
│ │ │ └── CrudModule.jsx
│ │ ├── DashboardModule
│ │ │ ├── components
│ │ │ │ ├── CustomerPreviewCard.jsx
│ │ │ │ ├── PreviewCard.jsx
│ │ │ │ ├── RecentTable
│ │ │ │ │ └── index.jsx
│ │ │ │ └── SummaryCard.jsx
│ │ │ └── index.jsx
│ │ ├── EmailModule
│ │ │ ├── EmailDataTableModule
│ │ │ │ └── index.jsx
│ │ │ ├── ReadEmailModule
│ │ │ │ ├── components
│ │ │ │ │ └── ReadItem.jsx
│ │ │ │ └── index.jsx
│ │ │ └── UpdateEmailModule
│ │ │ │ ├── componenets
│ │ │ │ └── EmailForm.jsx
│ │ │ │ └── index.jsx
│ │ ├── ErpPanelModule
│ │ │ ├── CreateItem.jsx
│ │ │ ├── DataTable.jsx
│ │ │ ├── DeleteItem.jsx
│ │ │ ├── ItemRow.jsx
│ │ │ ├── ReadItem.jsx
│ │ │ ├── SearchItem.jsx
│ │ │ ├── UpdateItem.jsx
│ │ │ └── index.jsx
│ │ ├── InvoiceModule
│ │ │ ├── CreateInvoiceModule
│ │ │ │ └── index.jsx
│ │ │ ├── Forms
│ │ │ │ └── InvoiceForm.jsx
│ │ │ ├── InvoiceDataTableModule
│ │ │ │ └── index.jsx
│ │ │ ├── ReadInvoiceModule
│ │ │ │ └── index.jsx
│ │ │ ├── RecordPaymentModule
│ │ │ │ ├── components
│ │ │ │ │ ├── Payment.jsx
│ │ │ │ │ └── RecordPayment.jsx
│ │ │ │ └── index.jsx
│ │ │ └── UpdateInvoiceModule
│ │ │ │ └── index.jsx
│ │ ├── OfferModule
│ │ │ ├── CreateOfferModule
│ │ │ │ └── index.jsx
│ │ │ ├── Forms
│ │ │ │ └── OfferForm.jsx
│ │ │ ├── OfferDataTableModule
│ │ │ │ └── index.jsx
│ │ │ ├── ReadOfferModule
│ │ │ │ ├── ReadOfferItem.jsx
│ │ │ │ └── index.jsx
│ │ │ └── UpdateOfferModule
│ │ │ │ └── index.jsx
│ │ ├── PaymentModule
│ │ │ ├── PaymentDataTableModule
│ │ │ │ └── index.jsx
│ │ │ ├── ReadPaymentModule
│ │ │ │ ├── components
│ │ │ │ │ └── ReadItem.jsx
│ │ │ │ └── index.jsx
│ │ │ └── UpdatePaymentModule
│ │ │ │ ├── components
│ │ │ │ ├── Payment.jsx
│ │ │ │ └── UpdatePayment.jsx
│ │ │ │ └── index.jsx
│ │ ├── ProfileModule
│ │ │ ├── components
│ │ │ │ ├── AdminInfo.jsx
│ │ │ │ ├── PasswordModal.jsx
│ │ │ │ ├── Profile.jsx
│ │ │ │ ├── UpdateAdmin.jsx
│ │ │ │ └── UploadImg.jsx
│ │ │ └── index.jsx
│ │ ├── QuoteModule
│ │ │ ├── CreateQuoteModule
│ │ │ │ └── index.jsx
│ │ │ ├── Forms
│ │ │ │ └── QuoteForm.jsx
│ │ │ ├── QuoteDataTableModule
│ │ │ │ └── index.jsx
│ │ │ ├── ReadQuoteModule
│ │ │ │ └── index.jsx
│ │ │ └── UpdateQuoteModule
│ │ │ │ └── index.jsx
│ │ └── SettingModule
│ │ │ ├── CompanyLogoSettingsModule
│ │ │ ├── forms
│ │ │ │ └── AppSettingForm.jsx
│ │ │ └── index.jsx
│ │ │ ├── FinanceSettingsModule
│ │ │ ├── SettingsForm.jsx
│ │ │ └── index.jsx
│ │ │ ├── GeneralSettingsModule
│ │ │ ├── forms
│ │ │ │ └── GeneralSettingForm.jsx
│ │ │ └── index.jsx
│ │ │ ├── MoneyFormatSettingsModule
│ │ │ ├── SettingsForm.jsx
│ │ │ └── index.jsx
│ │ │ └── components
│ │ │ ├── SetingsSection.jsx
│ │ │ ├── UpdateSettingForm.jsx
│ │ │ └── UpdateSettingModule.jsx
│ ├── pages
│ │ ├── Admin
│ │ │ └── index.jsx
│ │ ├── AdvancedSettings
│ │ │ └── index.jsx
│ │ ├── Currency
│ │ │ └── index.jsx
│ │ ├── Customer
│ │ │ └── index.jsx
│ │ ├── Dashboard.jsx
│ │ ├── Email
│ │ │ ├── EmailRead.jsx
│ │ │ ├── EmailUpdate.jsx
│ │ │ └── index.jsx
│ │ ├── Employee
│ │ │ └── index.jsx
│ │ ├── Inventory
│ │ │ └── index.jsx
│ │ ├── Invoice
│ │ │ ├── InvoiceCreate.jsx
│ │ │ ├── InvoiceRead.jsx
│ │ │ ├── InvoiceRecordPayment.jsx
│ │ │ ├── InvoiceUpdate.jsx
│ │ │ └── index.jsx
│ │ ├── Lead
│ │ │ └── index.jsx
│ │ ├── Login.jsx
│ │ ├── Logout.jsx
│ │ ├── NotFound.jsx
│ │ ├── Offer
│ │ │ ├── OfferCreate.jsx
│ │ │ ├── OfferRead.jsx
│ │ │ ├── OfferUpdate.jsx
│ │ │ └── index.jsx
│ │ ├── Order
│ │ │ └── index.jsx
│ │ ├── Payment
│ │ │ ├── PaymentRead.jsx
│ │ │ ├── PaymentUpdate.jsx
│ │ │ └── index.jsx
│ │ ├── PaymentMode
│ │ │ └── index.jsx
│ │ ├── Profile.jsx
│ │ ├── Quote
│ │ │ ├── QuoteCreate.jsx
│ │ │ ├── QuoteRead.jsx
│ │ │ ├── QuoteUpdate.jsx
│ │ │ └── index.jsx
│ │ ├── Register.jsx
│ │ ├── Settings
│ │ │ ├── CompanyLogoSettings.jsx
│ │ │ ├── GeneralSettings.jsx
│ │ │ ├── InvoiceSettings.jsx
│ │ │ ├── MoneyFormatSettings.jsx
│ │ │ ├── PaymentSettings.jsx
│ │ │ └── Settings.jsx
│ │ └── Taxes
│ │ │ └── index.jsx
│ ├── redux
│ │ ├── auth
│ │ │ ├── actions.js
│ │ │ ├── index.js
│ │ │ ├── reducer.js
│ │ │ ├── selectors.js
│ │ │ └── types.js
│ │ ├── crud
│ │ │ ├── actions.js
│ │ │ ├── index.js
│ │ │ ├── reducer.js
│ │ │ ├── selectors.js
│ │ │ └── types.js
│ │ ├── erp
│ │ │ ├── actions.js
│ │ │ ├── index.js
│ │ │ ├── reducer.js
│ │ │ ├── selectors.js
│ │ │ └── types.js
│ │ ├── rootReducer.js
│ │ ├── settings
│ │ │ ├── actions.js
│ │ │ ├── index.js
│ │ │ ├── reducer.js
│ │ │ ├── selectors.js
│ │ │ └── types.js
│ │ ├── store.js
│ │ ├── storePersist.js
│ │ └── translate
│ │ │ ├── actions.js
│ │ │ ├── index.js
│ │ │ ├── reducer.js
│ │ │ ├── selectors.js
│ │ │ └── types.js
│ ├── request
│ │ ├── checkImage.js
│ │ ├── codeMessage.js
│ │ ├── errorHandler.js
│ │ ├── index.js
│ │ ├── request.js
│ │ └── successHandler.js
│ ├── router
│ │ ├── AppRouter.jsx
│ │ ├── AuthRouter.jsx
│ │ ├── PrivateRoute.jsx
│ │ └── PublicRoute.jsx
│ ├── settings
│ │ ├── index.jsx
│ │ └── useMoney.jsx
│ ├── style
│ │ ├── app.css
│ │ ├── images
│ │ │ ├── checklist.svg
│ │ │ ├── fitbit-gray.svg
│ │ │ ├── flow-xo-gray.svg
│ │ │ ├── gitlab-gray.svg
│ │ │ ├── layar-gray.svg
│ │ │ ├── logo-icon.png
│ │ │ ├── logo-icon.svg
│ │ │ ├── logo-menu.png
│ │ │ ├── logo-text.png
│ │ │ ├── logo-text.svg
│ │ │ ├── logo.png
│ │ │ ├── logo.svg
│ │ │ ├── logo1.png
│ │ │ ├── logo2.png
│ │ │ ├── logo3.png
│ │ │ ├── logo4.png
│ │ │ └── photo.png
│ │ └── partials
│ │ │ ├── auth.css
│ │ │ ├── collapseBox.css
│ │ │ ├── core.css
│ │ │ ├── customAntd.css
│ │ │ ├── erp.css
│ │ │ ├── header.css
│ │ │ ├── layout.css
│ │ │ ├── navigation.css
│ │ │ ├── rest.css
│ │ │ ├── sidePanel.css
│ │ │ └── transition.css
│ └── utils
│ │ ├── calculate.js
│ │ ├── currenciesList.js
│ │ ├── helpers.js
│ │ └── tagColor.js
├── temp.env
└── vite.config.js
├── idurar-crm-erp.svg
└── image.png
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/custom.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Custom issue template
3 | about: Describe this issue template's purpose here.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
3 | Please provide a brief description of the changes or additions made in this pull request.
4 |
5 | ## Related Issues
6 |
7 | If this pull request is related to any issue(s), please list them here.
8 |
9 | ## Steps to Test
10 |
11 | Provide steps on how to test the changes introduced in this pull request.
12 |
13 | ## Screenshots (if applicable)
14 |
15 | If your changes include visual updates, it would be helpful to provide screenshots of the before and after.
16 |
17 | ## Checklist
18 |
19 | - [ ] I have tested these changes
20 | - [ ] I have updated the relevant documentation
21 | - [ ] I have commented my code, particularly in hard-to-understand areas
22 | - [ ] I have made corresponding changes to the codebase
23 | - [ ] My changes generate no new warnings or errors
24 | - [ ] The title of my pull request is clear and descriptive
25 |
--------------------------------------------------------------------------------
/.github/workflows/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Default reviewers
2 | * @salahlalami @polymahh @onfranciis @Ando22 @Fernando7181
--------------------------------------------------------------------------------
/.github/workflows/codesee-arch-diagram.yml:
--------------------------------------------------------------------------------
1 | # This workflow was added by CodeSee. Learn more at https://codesee.io/
2 | # This is v2.0 of this workflow file
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request_target:
8 | types: [opened, synchronize, reopened]
9 |
10 | name: CodeSee
11 |
12 | permissions: read-all
13 |
14 | jobs:
15 | codesee:
16 | runs-on: ubuntu-latest
17 | continue-on-error: true
18 | name: Analyze the repo with CodeSee
19 | steps:
20 | - uses: Codesee-io/codesee-action@v2
21 | with:
22 | codesee-token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }}
23 | codesee-url: https://app.codesee.io
24 |
--------------------------------------------------------------------------------
/.github/workflows/github-repo-stats.yml:
--------------------------------------------------------------------------------
1 | name: github-repo-stats
2 |
3 | on:
4 | schedule:
5 | # Run this once per day, towards the end of the day for keeping the most
6 | # recent data point most meaningful (hours are interpreted in UTC).
7 | - cron: "50 23 * * *"
8 | workflow_dispatch: # Allow for running this manually.
9 |
10 | jobs:
11 | j1:
12 | name: github-repo-stats
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: run-ghrs
16 | # Use latest release.
17 | uses: jgehrcke/github-repo-stats@RELEASE
18 | with:
19 | ghtoken: ${{ secrets.ghrs_github_api_token }}
20 |
21 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "githubPullRequests.ignoredPullRequestBranches": ["master"]
3 | }
4 |
--------------------------------------------------------------------------------
/backend/.env:
--------------------------------------------------------------------------------
1 | #DATABASE = "mongodb+srv://your_mongodb_server_database_url"
2 | #RESEND_API = "your_resend_api_key"
3 | JWT_SECRET= "thiscanbechangedlater123654789"
4 | KEY= "asdasda"
5 | NODE_ENV = "developement"
6 | SECRET = "asdasdasd"
7 | OPENSSL_CONF='/dev/null'
8 |
--------------------------------------------------------------------------------
/backend/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es2020: true,
5 | node: true,
6 | },
7 | extends: 'eslint:recommended',
8 | parserOptions: {
9 | ecmaVersion: 12,
10 | sourceType: 'module',
11 | },
12 | rules: {
13 | 'no-console': 0,
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/backend/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | frontend/node_modules/
3 | *.zip
4 | .DS_Store
5 | .idea
6 | notes.md
7 |
8 | .env.local
9 |
10 | *.pdf
--------------------------------------------------------------------------------
/backend/.prettierignore:
--------------------------------------------------------------------------------
1 | # Ignore artifacts:
2 | build
3 | coverage
4 | node_modules
--------------------------------------------------------------------------------
/backend/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "trailingComma": "es5",
4 | "tabWidth": 2,
5 | "semi": true,
6 | "singleQuote": true
7 | }
8 |
--------------------------------------------------------------------------------
/backend/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnPaste": true,
3 | "editor.formatOnSave": true,
4 | "editor.defaultFormatter": "esbenp.prettier-vscode",
5 | "[javascript]": {
6 | "editor.defaultFormatter": "esbenp.prettier-vscode"
7 | },
8 | "[jsonc]": {
9 | "editor.defaultFormatter": "esbenp.prettier-vscode"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/backend/controllers/appControllers/clientController/index.js:
--------------------------------------------------------------------------------
1 | const createCRUDController = require('@/controllers/middlewaresControllers/createCRUDController');
2 | const methods = createCRUDController('Client');
3 |
4 | const remove = require('./remove');
5 | const summary = require('./summary');
6 |
7 | methods.delete = remove;
8 | methods.summary = summary;
9 |
10 | module.exports = methods;
11 |
--------------------------------------------------------------------------------
/backend/controllers/appControllers/employeeController.js:
--------------------------------------------------------------------------------
1 | const createCRUDController = require('@/controllers/middlewaresControllers/createCRUDController');
2 | module.exports = createCRUDController('Employee');
3 |
--------------------------------------------------------------------------------
/backend/controllers/appControllers/expenseCategoryController.js:
--------------------------------------------------------------------------------
1 | const createCRUDController = require('@/controllers/middlewaresControllers/createCRUDController');
2 | module.exports = createCRUDController('ExpenseCategory');
3 |
--------------------------------------------------------------------------------
/backend/controllers/appControllers/expenseController.js:
--------------------------------------------------------------------------------
1 | const createCRUDController = require('@/controllers/middlewaresControllers/createCRUDController');
2 | module.exports = createCRUDController('Expense');
3 |
--------------------------------------------------------------------------------
/backend/controllers/appControllers/inventoryController.js:
--------------------------------------------------------------------------------
1 | const createCRUDController = require('@/controllers/middlewaresControllers/createCRUDController');
2 | module.exports = createCRUDController('Inventory');
3 |
--------------------------------------------------------------------------------
/backend/controllers/appControllers/invoiceController/index.js:
--------------------------------------------------------------------------------
1 | const createCRUDController = require('@/controllers/middlewaresControllers/createCRUDController');
2 | const methods = createCRUDController('Invoice');
3 |
4 | const sendMail = require('./mailInvoiceController');
5 | const create = require('./create');
6 | const summary = require('./summary');
7 | const update = require('./update');
8 | const remove = require('./remove');
9 | const paginatedList = require('./paginatedList');
10 | const read = require('./read');
11 |
12 | methods.sendMail = sendMail;
13 | methods.create = create;
14 | methods.update = update;
15 | methods.delete = remove;
16 | methods.summary = summary;
17 | methods.list = paginatedList;
18 | methods.read = read;
19 |
20 | module.exports = methods;
21 |
--------------------------------------------------------------------------------
/backend/controllers/appControllers/invoiceController/read.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const Model = mongoose.model('Invoice');
4 |
5 | const read = async (req, res) => {
6 | try {
7 | // Find document by id
8 | const result = await Model.findOne({ _id: req.params.id, removed: false }).populate(
9 | 'createdBy',
10 | 'name'
11 | );
12 | // If no results found, return document not found
13 | if (!result) {
14 | return res.status(404).json({
15 | success: false,
16 | result: null,
17 | message: 'No document found by this id: ' + req.params.id,
18 | });
19 | } else {
20 | // Return success resposne
21 | return res.status(200).json({
22 | success: true,
23 | result,
24 | message: 'we found this document by this id: ' + req.params.id,
25 | });
26 | }
27 | } catch (error) {
28 | // Server Error
29 | return res.status(500).json({
30 | success: false,
31 | result: null,
32 | message: error.message,
33 | error: error,
34 | });
35 | }
36 | };
37 |
38 | module.exports = read;
39 |
--------------------------------------------------------------------------------
/backend/controllers/appControllers/invoiceController/remove.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const Model = mongoose.model('Invoice');
4 | const ModalPayment = mongoose.model('Payment');
5 |
6 | const remove = async (req, res) => {
7 | try {
8 | const deletedInvoice = await Model.findOneAndUpdate(
9 | {
10 | _id: req.params.id,
11 | removed: false,
12 | },
13 | {
14 | $set: {
15 | removed: true,
16 | },
17 | }
18 | ).exec();
19 |
20 | if (!deletedInvoice) {
21 | return res.status(404).json({
22 | success: false,
23 | result: null,
24 | message: 'Invoice not found',
25 | });
26 | }
27 | const paymentsInvoices = await ModalPayment.updateMany(
28 | { invoice: deletedInvoice._id },
29 | { $set: { removed: true } }
30 | );
31 | return res.status(200).json({
32 | success: true,
33 | result: deletedInvoice,
34 | message: 'Invoice deleted successfully',
35 | });
36 | } catch (error) {
37 | return res.status(500).json({
38 | success: false,
39 | result: null,
40 | error: error,
41 | message: error.message,
42 | });
43 | }
44 | };
45 |
46 | module.exports = remove;
47 |
--------------------------------------------------------------------------------
/backend/controllers/appControllers/invoiceController/schemaValidate.js:
--------------------------------------------------------------------------------
1 | const Joi = require('joi');
2 | const schema = Joi.object({
3 | client: Joi.alternatives().try(Joi.string(), Joi.object()).required(),
4 | number: Joi.number().required(),
5 | year: Joi.number().required(),
6 | status: Joi.string().required(),
7 | note: Joi.string().allow(''),
8 | expiredDate: Joi.date().required(),
9 | date: Joi.date().required(),
10 | // array cannot be empty
11 | items: Joi.array()
12 | .items(
13 | Joi.object({
14 | _id: Joi.string().allow('').optional(),
15 | itemName: Joi.string().required(),
16 | description: Joi.string().allow(''),
17 | quantity: Joi.number().required(),
18 | price: Joi.number().required(),
19 | total: Joi.number().required(),
20 | }).required()
21 | )
22 | .required(),
23 | taxRate: Joi.alternatives().try(Joi.number(), Joi.string()).required(),
24 | });
25 |
26 | module.exports = schema;
27 |
--------------------------------------------------------------------------------
/backend/controllers/appControllers/itemController.js:
--------------------------------------------------------------------------------
1 | const createCRUDController = require('@/controllers/middlewaresControllers/createCRUDController');
2 | module.exports = createCRUDController('Item');
3 |
--------------------------------------------------------------------------------
/backend/controllers/appControllers/kycController.js:
--------------------------------------------------------------------------------
1 | const createCRUDController = require('@/controllers/middlewaresControllers/createCRUDController');
2 | module.exports = createCRUDController('Kyc');
3 |
--------------------------------------------------------------------------------
/backend/controllers/appControllers/leadController.js:
--------------------------------------------------------------------------------
1 | const createCRUDController = require('@/controllers/middlewaresControllers/createCRUDController');
2 | module.exports = createCRUDController('Lead');
3 |
--------------------------------------------------------------------------------
/backend/controllers/appControllers/offerController/index.js:
--------------------------------------------------------------------------------
1 | const createCRUDController = require('@/controllers/middlewaresControllers/createCRUDController');
2 | const methods = createCRUDController('Offer');
3 |
4 | const create = require('./create');
5 | const summary = require('./summary');
6 | const update = require('./update');
7 | const paginatedList = require('./paginatedList');
8 | const read = require('./read');
9 |
10 | methods.list = paginatedList;
11 | methods.read = read;
12 |
13 | methods.create = create;
14 | methods.update = update;
15 | methods.summary = summary;
16 |
17 | module.exports = methods;
18 |
--------------------------------------------------------------------------------
/backend/controllers/appControllers/offerController/read.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const Model = mongoose.model('Offer');
4 |
5 | const read = async (req, res) => {
6 | try {
7 | // Find document by id
8 | const result = await Model.findOne({ _id: req.params.id, removed: false }).populate(
9 | 'createdBy',
10 | 'name'
11 | );
12 | // If no results found, return document not found
13 | if (!result) {
14 | return res.status(404).json({
15 | success: false,
16 | result: null,
17 | message: 'No document found by this id: ' + req.params.id,
18 | });
19 | } else {
20 | // Return success resposne
21 | return res.status(200).json({
22 | success: true,
23 | result,
24 | message: 'we found this document by this id: ' + req.params.id,
25 | });
26 | }
27 | } catch (error) {
28 | // Server Error
29 | return res.status(500).json({
30 | success: false,
31 | result: null,
32 | message: error.message,
33 | error: error,
34 | });
35 | }
36 | };
37 |
38 | module.exports = read;
39 |
--------------------------------------------------------------------------------
/backend/controllers/appControllers/orderController.js:
--------------------------------------------------------------------------------
1 | const createCRUDController = require('@/controllers/middlewaresControllers/createCRUDController');
2 | module.exports = createCRUDController('Order');
3 |
--------------------------------------------------------------------------------
/backend/controllers/appControllers/paymentController/index.js:
--------------------------------------------------------------------------------
1 | const createCRUDController = require('@/controllers/middlewaresControllers/createCRUDController');
2 | const methods = createCRUDController('Payment');
3 |
4 | const create = require('./create');
5 | const summary = require('./summary');
6 | const update = require('./update');
7 | const remove = require('./remove');
8 |
9 | methods.create = create;
10 | methods.update = update;
11 | methods.delete = remove;
12 | methods.summary = summary;
13 |
14 | module.exports = methods;
15 |
--------------------------------------------------------------------------------
/backend/controllers/appControllers/paymentModeController.js:
--------------------------------------------------------------------------------
1 | const createCRUDController = require('@/controllers/middlewaresControllers/createCRUDController');
2 | module.exports = createCRUDController('PaymentMode');
3 |
--------------------------------------------------------------------------------
/backend/controllers/appControllers/quoteController/index.js:
--------------------------------------------------------------------------------
1 | const createCRUDController = require('@/controllers/middlewaresControllers/createCRUDController');
2 | const methods = createCRUDController('Quote');
3 |
4 | const sendMail = require('./mailQuoteController');
5 | const create = require('./create');
6 | const summary = require('./summary');
7 | const update = require('./update');
8 | const convertQuoteToInvoice = require('./convertQuoteToInvoice');
9 | const paginatedList = require('./paginatedList');
10 | const read = require('./read');
11 |
12 | methods.list = paginatedList;
13 | methods.read = read;
14 |
15 | methods.sendMail = sendMail;
16 | methods.create = create;
17 | methods.update = update;
18 | methods.convertQuoteToInvoice = convertQuoteToInvoice;
19 | methods.summary = summary;
20 |
21 | module.exports = methods;
22 |
--------------------------------------------------------------------------------
/backend/controllers/appControllers/quoteController/read.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const Model = mongoose.model('Quote');
4 |
5 | const read = async (req, res) => {
6 | try {
7 | // Find document by id
8 | const result = await Model.findOne({ _id: req.params.id, removed: false }).populate(
9 | 'createdBy',
10 | 'name'
11 | );
12 | // If no results found, return document not found
13 | if (!result) {
14 | return res.status(404).json({
15 | success: false,
16 | result: null,
17 | message: 'No document found by this id: ' + req.params.id,
18 | });
19 | } else {
20 | // Return success resposne
21 | return res.status(200).json({
22 | success: true,
23 | result,
24 | message: 'we found this document by this id: ' + req.params.id,
25 | });
26 | }
27 | } catch (error) {
28 | // Server Error
29 | return res.status(500).json({
30 | success: false,
31 | result: null,
32 | message: error.message,
33 | error: error,
34 | });
35 | }
36 | };
37 |
38 | module.exports = read;
39 |
--------------------------------------------------------------------------------
/backend/controllers/appControllers/supplierController.js:
--------------------------------------------------------------------------------
1 | const createCRUDController = require('@/controllers/middlewaresControllers/createCRUDController');
2 | module.exports = createCRUDController('Supplier');
3 |
--------------------------------------------------------------------------------
/backend/controllers/appControllers/supplierOrderController/index.js:
--------------------------------------------------------------------------------
1 | const createCRUDController = require('@/controllers/middlewaresControllers/createCRUDController');
2 | const methods = createCRUDController('Quote');
3 |
4 | const create = require('./create');
5 | const summary = require('./summary');
6 | const update = require('./update');
7 |
8 | methods.create = create;
9 | methods.update = update;
10 |
11 | methods.summary = summary;
12 |
13 | module.exports = methods;
14 |
--------------------------------------------------------------------------------
/backend/controllers/coreControllers/adminController/filter.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const Admin = mongoose.model('Admin');
3 |
4 | const filter = async (req, res) => {
5 | try {
6 | if (req.query.filter === undefined || req.query.equal === undefined) {
7 | return res.status(403).json({
8 | success: false,
9 | result: null,
10 | message: 'filter not provided correctly',
11 | });
12 | }
13 | const result = await Admin.find({ removed: false })
14 | .where(req.query.filter)
15 | .equals(req.query.equal);
16 |
17 | for (let admin of result) {
18 | admin.password = undefined;
19 | }
20 | return res.status(200).json({
21 | success: true,
22 | result,
23 | message: 'Successfully found all documents where equal to : ' + req.params.equal,
24 | });
25 | } catch (error) {
26 | return res.status(500).json({
27 | success: false,
28 | result: null,
29 | message: error.message,
30 | error,
31 | });
32 | }
33 | };
34 |
35 | module.exports = filter;
36 |
--------------------------------------------------------------------------------
/backend/controllers/coreControllers/adminController/index.js:
--------------------------------------------------------------------------------
1 | const create = require('./create');
2 | const read = require('./read');
3 | const update = require('./update');
4 | const updateProfile = require('./updateProfile');
5 | const remove = require('./remove');
6 | const updatePassword = require('./updatePassword');
7 | const profile = require('./profile');
8 | const photo = require('./photo');
9 | const status = require('./status');
10 | const search = require('./search');
11 | const filter = require('./filter');
12 | const listAll = require('./listAll');
13 | const list = require('./paginatedList');
14 |
15 | const adminController = {
16 | create,
17 | read,
18 | update,
19 | updateProfile,
20 | photo,
21 | delete: remove,
22 | updatePassword,
23 | profile,
24 | status,
25 | search,
26 | filter,
27 | listAll,
28 | list,
29 | };
30 |
31 | module.exports = adminController;
32 |
--------------------------------------------------------------------------------
/backend/controllers/coreControllers/adminController/listAll.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const Admin = mongoose.model('Admin');
3 |
4 | const listAll = async (req, res) => {
5 | const limit = parseInt(req.query.items) || 100;
6 |
7 | try {
8 | // Query the database for a list of all results
9 | const result = await Admin.find({ removed: false })
10 | .limit(limit)
11 | .sort({ created: 'desc' })
12 | .populate();
13 | // Counting the total documents
14 | // Resolving both promises
15 | // Calculating total pages
16 |
17 | // Getting Pagination Object
18 | if (result.length > 0) {
19 | for (let admin of result) {
20 | admin.password = undefined;
21 | admin.loggedSessions = undefined;
22 | }
23 | return res.status(200).json({
24 | success: true,
25 | result,
26 | message: 'Successfully found all documents',
27 | });
28 | } else {
29 | return res.status(203).json({
30 | success: false,
31 | result: [],
32 | message: 'Collection is Empty',
33 | });
34 | }
35 | } catch (error) {
36 | return res.status(500).json({ success: false, result: [], message: error.message, error });
37 | }
38 | };
39 |
40 | module.exports = listAll;
41 |
--------------------------------------------------------------------------------
/backend/controllers/coreControllers/adminController/profile.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const Admin = mongoose.model('Admin');
3 |
4 | const profile = async (req, res) => {
5 | try {
6 | // Query the database for a list of all results
7 | if (!req.admin) {
8 | return res.status(404).json({
9 | success: false,
10 | result: null,
11 | message: "couldn't found admin Profile ",
12 | });
13 | }
14 | let result = {
15 | _id: req.admin._id,
16 | email: req.admin.email,
17 | name: req.admin.name,
18 | surname: req.admin.surname,
19 | photo: req.admin.photo,
20 | role: req.admin.role,
21 | };
22 |
23 | return res.status(200).json({
24 | success: true,
25 | result,
26 | message: 'Successfully found Profile',
27 | });
28 | } catch (error) {
29 | return res.status(500).json({
30 | success: false,
31 | result: null,
32 | message: error.message,
33 | error,
34 | });
35 | }
36 | };
37 | module.exports = profile;
38 |
--------------------------------------------------------------------------------
/backend/controllers/coreControllers/adminController/read.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const Admin = mongoose.model('Admin');
3 |
4 | const read = async (req, res) => {
5 | try {
6 | // Find document by id
7 | const tmpResult = await Admin.findOne({
8 | _id: req.params.id,
9 | removed: false,
10 | });
11 | // If no results found, return document not found
12 | if (!tmpResult) {
13 | return res.status(404).json({
14 | success: false,
15 | result: null,
16 | message: 'No document found by this id: ' + req.params.id,
17 | });
18 | } else {
19 | // Return success resposne
20 | let result = {
21 | _id: tmpResult._id,
22 | enabled: tmpResult.enabled,
23 | email: tmpResult.email,
24 | name: tmpResult.name,
25 | surname: tmpResult.surname,
26 | photo: tmpResult.photo,
27 | role: tmpResult.role,
28 | };
29 |
30 | return res.status(200).json({
31 | success: true,
32 | result,
33 | message: 'we found this document by this id: ' + req.params.id,
34 | });
35 | }
36 | } catch (error) {
37 | // Server Error
38 | return res.status(500).json({
39 | success: false,
40 | result: null,
41 | message: error.message,
42 | error,
43 | });
44 | }
45 | };
46 |
47 | module.exports = read;
48 |
--------------------------------------------------------------------------------
/backend/controllers/coreControllers/adminController/remove.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const Admin = mongoose.model('Admin');
3 |
4 | const remove = async (req, res) => {
5 | try {
6 | let updates = {
7 | removed: true,
8 | };
9 | // Find the document by id and delete it
10 | const result = await Admin.findOneAndUpdate(
11 | { _id: req.params.id, removed: false },
12 | { $set: updates },
13 | {
14 | new: true, // return the new result instead of the old one
15 | }
16 | ).exec();
17 | // If no results found, return document not found
18 | if (!result) {
19 | return res.status(404).json({
20 | success: false,
21 | result: null,
22 | message: 'No document found by this id: ' + req.params.id,
23 | });
24 | } else {
25 | return res.status(200).json({
26 | success: true,
27 | result,
28 | message: 'Successfully Deleted the document by id: ' + req.params.id,
29 | });
30 | }
31 | } catch (error) {
32 | return res.status(500).json({
33 | success: false,
34 | result: null,
35 | message: error.message,
36 | error,
37 | });
38 | }
39 | };
40 |
41 | module.exports = remove;
42 |
--------------------------------------------------------------------------------
/backend/controllers/coreControllers/adminController/status.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const Admin = mongoose.model('Admin');
3 |
4 | const status = async (req, res) => {
5 | try {
6 | if (req.query.enabled === true || req.query.enabled === false) {
7 | let updates = {
8 | enabled: req.query.enabled,
9 | };
10 | // Find the document by id and delete it
11 | const result = await Admin.findOneAndUpdate(
12 | { _id: req.params.id, removed: false },
13 | { $set: updates },
14 | {
15 | new: true, // return the new result instead of the old one
16 | }
17 | ).exec();
18 | // If no results found, return document not found
19 | if (!result) {
20 | return res.status(404).json({
21 | success: false,
22 | result: null,
23 | message: 'No document found by this id: ' + req.params.id,
24 | });
25 | } else {
26 | return res.status(200).json({
27 | success: true,
28 | result,
29 | message: 'Successfully update status of this document by id: ' + req.params.id,
30 | });
31 | }
32 | } else {
33 | return res
34 | .status(202)
35 | .json({
36 | success: false,
37 | result: [],
38 | message: "couldn't change admin status by this request",
39 | })
40 | .end();
41 | }
42 | } catch (error) {
43 | return res.status(500).json({
44 | success: false,
45 | result: null,
46 | message: error.message,
47 | });
48 | }
49 | };
50 | module.exports = status;
51 |
--------------------------------------------------------------------------------
/backend/controllers/coreControllers/authJwtController/index.js:
--------------------------------------------------------------------------------
1 | const isValidAdminToken = require('./isValidAdminToken');
2 | const login = require('./login');
3 | const logout = require('./logout');
4 |
5 | const authJwtController = {
6 | isValidAdminToken,
7 | login,
8 | logout,
9 | };
10 |
11 | module.exports = authJwtController;
12 |
--------------------------------------------------------------------------------
/backend/controllers/coreControllers/authJwtController/logout.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const Admin = mongoose.model('Admin');
4 |
5 | const logout = async (req, res) => {
6 | try {
7 | const token = req.cookies.token;
8 | const result = await Admin.findOneAndUpdate(
9 | { _id: req.admin._id },
10 | { $pull: { loggedSessions: token } },
11 | {
12 | new: true,
13 | }
14 | ).exec();
15 |
16 | res
17 | .clearCookie('token', {
18 | maxAge: null,
19 | sameSite: 'none',
20 | httpOnly: true,
21 | secure: true,
22 | domain: req.hostname,
23 | Path: '/',
24 | })
25 | .json({ isLoggedOut: true });
26 | } catch (error) {
27 | res.status(500).json({ success: false, result: null, message: error.message, error: error });
28 | }
29 | };
30 |
31 | module.exports = logout;
32 |
--------------------------------------------------------------------------------
/backend/controllers/coreControllers/emailController/index.js:
--------------------------------------------------------------------------------
1 | const createCRUDController = require('@/controllers/middlewaresControllers/createCRUDController');
2 | const crudController = createCRUDController('Email');
3 |
4 | const emailMethods = {
5 | create:crudController.create,
6 | read: crudController.read,
7 | update: crudController.update,
8 | list: crudController.list,
9 | listAll: crudController.listAll,
10 | filter: crudController.filter,
11 | search: crudController.search,
12 | };
13 |
14 | module.exports = emailMethods;
15 |
--------------------------------------------------------------------------------
/backend/controllers/coreControllers/settingController/index.js:
--------------------------------------------------------------------------------
1 | const createCRUDController = require('@/controllers/middlewaresControllers/createCRUDController');
2 | const crudController = createCRUDController('Setting');
3 |
4 | const listBySettingKey = require('./listBySettingKey');
5 | const readBySettingKey = require('./readBySettingKey');
6 | const updateBySettingKey = require('./updateBySettingKey');
7 | const updateManySetting = require('./updateManySetting');
8 |
9 | const settingMethods = {
10 | read: crudController.read,
11 | create: crudController.create,
12 | update: crudController.update,
13 | list: crudController.list,
14 | listAll: crudController.listAll,
15 | filter: crudController.filter,
16 | search: crudController.search,
17 | listBySettingKey,
18 | readBySettingKey,
19 | updateBySettingKey,
20 | updateManySetting,
21 | };
22 |
23 | module.exports = settingMethods;
24 |
--------------------------------------------------------------------------------
/backend/controllers/coreControllers/settingController/readBySettingKey.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const Model = mongoose.model('Setting');
4 |
5 | const readBySettingKey = async (req, res) => {
6 | try {
7 | // Find document by id
8 | const settingKey = req.params.settingKey || undefined;
9 |
10 | if (!settingKey) {
11 | return res.status(202).json({
12 | success: false,
13 | result: null,
14 | message: 'No settingKey provided ',
15 | });
16 | }
17 |
18 | const result = await Model.findOne({ settingKey });
19 |
20 | // If no results found, return document not found
21 | if (!result) {
22 | return res.status(404).json({
23 | success: false,
24 | result: null,
25 | message: 'No document found by this settingKey: ' + settingKey,
26 | });
27 | } else {
28 | // Return success resposne
29 | return res.status(200).json({
30 | success: true,
31 | result,
32 | message: 'we found this document by this settingKey: ' + settingKey,
33 | });
34 | }
35 | } catch (error) {
36 | // Server Error
37 | return res.status(500).json({
38 | success: false,
39 | result: null,
40 | message: error.message,
41 | error: error,
42 | });
43 | }
44 | };
45 |
46 | module.exports = readBySettingKey;
47 |
--------------------------------------------------------------------------------
/backend/controllers/middlewaresControllers/createCRUDController/create.js:
--------------------------------------------------------------------------------
1 | const create = async (Model, req, res) => {
2 | try {
3 | // Creating a new document in the collection
4 |
5 | const result = await new Model(req.body).save();
6 |
7 | // Returning successfull response
8 | return res.status(200).json({
9 | success: true,
10 | result,
11 | message: 'Successfully Created the document in Model ',
12 | });
13 | } catch (error) {
14 | // If error is thrown by Mongoose due to required validations
15 | if (error.name == 'ValidationError') {
16 | return res.status(400).json({
17 | success: false,
18 | result: null,
19 | message: 'Required fields are not supplied',
20 | error: error,
21 | });
22 | } else {
23 | // Server Error
24 | return res.status(500).json({
25 | success: false,
26 | result: null,
27 | message: error.message,
28 | error: error,
29 | });
30 | }
31 | }
32 | };
33 |
34 | module.exports = create;
35 |
--------------------------------------------------------------------------------
/backend/controllers/middlewaresControllers/createCRUDController/filter.js:
--------------------------------------------------------------------------------
1 | const filter = async (Model, req, res) => {
2 | try {
3 | if (req.query.filter === undefined || req.query.equal === undefined) {
4 | return res.status(403).json({
5 | success: false,
6 | result: null,
7 | message: 'filter not provided correctly',
8 | });
9 | }
10 | const result = await Model.find({ removed: false })
11 | .where(req.query.filter)
12 | .equals(req.query.equal);
13 | return res.status(200).json({
14 | success: true,
15 | result,
16 | message: 'Successfully found all documents where equal to : ' + req.params.equal,
17 | });
18 | } catch (error) {
19 | return res.status(500).json({
20 | success: false,
21 | result: null,
22 | message: error.message,
23 | error: error,
24 | });
25 | }
26 | };
27 |
28 | module.exports = filter;
29 |
--------------------------------------------------------------------------------
/backend/controllers/middlewaresControllers/createCRUDController/index.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const create = require('./create');
4 | const read = require('./read');
5 | const update = require('./update');
6 | const remove = require('./remove');
7 | const search = require('./search');
8 | const filter = require('./filter');
9 | const listAll = require('./listAll');
10 | const paginatedList = require('./paginatedList');
11 |
12 | const createCRUDController = (modelName) => {
13 | const Model = mongoose.model(modelName);
14 | let crudMethods = {};
15 |
16 | crudMethods.create = async (req, res) => {
17 | create(Model, req, res);
18 | };
19 | crudMethods.read = async (req, res) => {
20 | read(Model, req, res);
21 | };
22 | crudMethods.update = async (req, res) => {
23 | update(Model, req, res);
24 | };
25 | crudMethods.delete = async (req, res) => {
26 | remove(Model, req, res);
27 | };
28 | crudMethods.list = async (req, res) => {
29 | paginatedList(Model, req, res);
30 | };
31 | crudMethods.listAll = async (req, res) => {
32 | listAll(Model, req, res);
33 | };
34 | crudMethods.search = async (req, res) => {
35 | search(Model, req, res);
36 | };
37 |
38 | crudMethods.filter = async (req, res) => {
39 | filter(Model, req, res);
40 | };
41 |
42 | return crudMethods;
43 | };
44 |
45 | module.exports = createCRUDController;
46 |
--------------------------------------------------------------------------------
/backend/controllers/middlewaresControllers/createCRUDController/listAll.js:
--------------------------------------------------------------------------------
1 | const listAll = async (Model, req, res) => {
2 | const sort = parseInt(req.query.sort) || 'desc';
3 | try {
4 | // Query the database for a list of all results
5 | const result = await Model.find({ removed: false }).sort({ created: sort }).populate();
6 |
7 | if (result.length > 0) {
8 | return res.status(200).json({
9 | success: true,
10 | result,
11 | message: 'Successfully found all documents',
12 | });
13 | } else {
14 | return res.status(203).json({
15 | success: true,
16 | result: [],
17 | message: 'Collection is Empty',
18 | });
19 | }
20 | } catch (error) {
21 | return res.status(500).json({
22 | success: false,
23 | result: [],
24 | message: error.message,
25 | error: error,
26 | });
27 | }
28 | };
29 |
30 | module.exports = listAll;
31 |
--------------------------------------------------------------------------------
/backend/controllers/middlewaresControllers/createCRUDController/paginatedList.js:
--------------------------------------------------------------------------------
1 | const paginatedList = async (Model, req, res) => {
2 | const page = req.query.page || 1;
3 | const limit = parseInt(req.query.items) || 10;
4 | const skip = page * limit - limit;
5 | try {
6 | // Query the database for a list of all results
7 | const resultsPromise = Model.find({ removed: false })
8 | .skip(skip)
9 | .limit(limit)
10 | .sort({ created: 'desc' })
11 | .populate();
12 | // Counting the total documents
13 | const countPromise = Model.countDocuments({ removed: false });
14 | // Resolving both promises
15 | const [result, count] = await Promise.all([resultsPromise, countPromise]);
16 | // Calculating total pages
17 | const pages = Math.ceil(count / limit);
18 |
19 | // Getting Pagination Object
20 | const pagination = { page, pages, count };
21 | if (count > 0) {
22 | return res.status(200).json({
23 | success: true,
24 | result,
25 | pagination,
26 | message: 'Successfully found all documents',
27 | });
28 | } else {
29 | return res.status(203).json({
30 | success: true,
31 | result: [],
32 | pagination,
33 | message: 'Collection is Empty',
34 | });
35 | }
36 | } catch (error) {
37 | return res.status(500).json({
38 | success: false,
39 | result: [],
40 | message: error.message,
41 | error: error,
42 | });
43 | }
44 | };
45 |
46 | module.exports = paginatedList;
47 |
--------------------------------------------------------------------------------
/backend/controllers/middlewaresControllers/createCRUDController/read.js:
--------------------------------------------------------------------------------
1 | const read = async (Model, req, res) => {
2 | try {
3 | // Find document by id
4 | const result = await Model.findOne({ _id: req.params.id, removed: false });
5 | // If no results found, return document not found
6 | if (!result) {
7 | return res.status(404).json({
8 | success: false,
9 | result: null,
10 | message: 'No document found by this id: ' + req.params.id,
11 | });
12 | } else {
13 | // Return success resposne
14 | return res.status(200).json({
15 | success: true,
16 | result,
17 | message: 'we found this document by this id: ' + req.params.id,
18 | });
19 | }
20 | } catch (error) {
21 | // Server Error
22 | return res.status(500).json({
23 | success: false,
24 | result: null,
25 | message: error.message,
26 | error: error,
27 | });
28 | }
29 | };
30 |
31 | module.exports = read;
32 |
--------------------------------------------------------------------------------
/backend/controllers/middlewaresControllers/createCRUDController/remove.js:
--------------------------------------------------------------------------------
1 | const remove = async (Model, req, res) => {
2 | try {
3 | // Find the document by id and delete it
4 | let updates = {
5 | removed: true,
6 | };
7 | // Find the document by id and delete it
8 | const result = await Model.findOneAndUpdate(
9 | { _id: req.params.id, removed: false },
10 | { $set: updates },
11 | {
12 | new: true, // return the new result instead of the old one
13 | }
14 | ).exec();
15 | // If no results found, return document not found
16 | if (!result) {
17 | return res.status(404).json({
18 | success: false,
19 | result: null,
20 | message: 'No document found by this id: ' + req.params.id,
21 | });
22 | } else {
23 | return res.status(200).json({
24 | success: true,
25 | result,
26 | message: 'Successfully Deleted the document by id: ' + req.params.id,
27 | });
28 | }
29 | } catch (error) {
30 | return res.status(500).json({
31 | success: false,
32 | result: null,
33 | message: error.message,
34 | error: error,
35 | });
36 | }
37 | };
38 |
39 | module.exports = remove;
40 |
--------------------------------------------------------------------------------
/backend/controllers/middlewaresControllers/createCRUDController/search.js:
--------------------------------------------------------------------------------
1 | const search = async (Model, req, res) => {
2 | // console.log(req.query.fields)
3 | if (req.query.q === undefined || req.query.q.trim() === '') {
4 | return res
5 | .status(202)
6 | .json({
7 | success: false,
8 | result: [],
9 | message: 'No document found by this request',
10 | })
11 | .end();
12 | }
13 | const fieldsArray = req.query.fields
14 | ? req.query.fields.split(',')
15 | : ['name', 'surname', 'birthday'];
16 |
17 | const fields = { $or: [] };
18 |
19 | for (const field of fieldsArray) {
20 | fields.$or.push({ [field]: { $regex: new RegExp(req.query.q, 'i') } });
21 | }
22 | // console.log(fields)
23 | try {
24 | let results = await Model.find(fields).where('removed', false).limit(10);
25 |
26 | if (results.length >= 1) {
27 | return res.status(200).json({
28 | success: true,
29 | result: results,
30 | message: 'Successfully found all documents',
31 | });
32 | } else {
33 | return res
34 | .status(202)
35 | .json({
36 | success: false,
37 | result: [],
38 | message: 'No document found by this request',
39 | })
40 | .end();
41 | }
42 | } catch (error) {
43 | return res.status(500).json({
44 | success: false,
45 | result: null,
46 | message: error.message,
47 | error: error,
48 | });
49 | }
50 | };
51 |
52 | module.exports = search;
53 |
--------------------------------------------------------------------------------
/backend/controllers/middlewaresControllers/createCRUDController/update.js:
--------------------------------------------------------------------------------
1 | const update = async (Model, req, res) => {
2 | try {
3 | // Find document by id and updates with the required fields
4 | const result = await Model.findOneAndUpdate({ _id: req.params.id, removed: false }, req.body, {
5 | new: true, // return the new result instead of the old one
6 | runValidators: true,
7 | }).exec();
8 | if (!result) {
9 | return res.status(404).json({
10 | success: false,
11 | result: null,
12 | message: 'No document found by this id: ' + req.params.id,
13 | });
14 | } else {
15 | return res.status(200).json({
16 | success: true,
17 | result,
18 | message: 'we update this document by this id: ' + req.params.id,
19 | });
20 | }
21 | } catch (error) {
22 | // If error is thrown by Mongoose due to required validations
23 | if (error.name == 'ValidationError') {
24 | return res.status(400).json({
25 | success: false,
26 | result: null,
27 | message: 'Required fields are not supplied',
28 | error: error,
29 | });
30 | } else {
31 | // Server Error
32 | return res.status(500).json({
33 | success: false,
34 | result: null,
35 | message: error.message,
36 | error: error,
37 | });
38 | }
39 | }
40 | };
41 |
42 | module.exports = update;
43 |
--------------------------------------------------------------------------------
/backend/controllers/middlewaresControllers/pdfController/index.js:
--------------------------------------------------------------------------------
1 | let pdf = require('html-pdf');
2 | const pug = require('pug');
3 | const fs = require('fs');
4 | const moment = require('moment');
5 |
6 | exports.generatePdf = async (
7 | modelName,
8 | info = { filename: 'pdf_file', format: 'A5' },
9 | result,
10 | callback
11 | ) => {
12 | try {
13 | const fileId = info.filename + '-' + result._id + '.pdf';
14 | const folderPath = modelName.toLowerCase();
15 | const targetLocation = `./public/download/${folderPath}/${fileId}`;
16 |
17 | // if PDF already exist, then delete it and create new PDF
18 | if (fs.existsSync(targetLocation)) {
19 | fs.unlinkSync(targetLocation);
20 | }
21 |
22 | // render pdf html
23 |
24 | const pugFiles = ['invoice', 'offer', 'quote', 'payment', 'quote', 'supplierOrder'];
25 | if (pugFiles.includes(modelName.toLowerCase())) {
26 | const html = pug.renderFile('views/pdf/' + modelName + '.pug', {
27 | model: result,
28 | moment: moment,
29 | });
30 |
31 | await pdf
32 | .create(html, {
33 | format: info.format,
34 | orientation: 'portrait',
35 | border: '12mm',
36 | })
37 | .toFile(targetLocation, function (error) {
38 | if (error) return console.log('this pdf create error ' + error);
39 | if (callback) callback(targetLocation);
40 | });
41 | }
42 | } catch (error) {
43 | throw new Error('Something went wrong! generatePdf');
44 | }
45 | };
46 |
--------------------------------------------------------------------------------
/backend/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "@/*": ["./*"]
6 | }
7 | },
8 | "exclude": ["node_modules", "medical-frontend", "erp-frontend"]
9 | }
10 |
--------------------------------------------------------------------------------
/backend/middlewares/permission.js:
--------------------------------------------------------------------------------
1 | //this middleware will check if the user has permission
2 |
3 | const roles = {
4 | admin: ['create', 'read', 'update', 'delete', 'download', 'upload'],
5 | staffAdmin: ['create', 'read', 'update', 'delete', 'download', 'upload'],
6 | staff: ['create', 'read', 'update', 'download', 'upload'],
7 | createOnly: ['create', 'read', 'download', 'upload'],
8 | readOnly: ['read', 'download'],
9 | };
10 | exports.roles = roles;
11 | exports.hasPermission = (permissionName = 'all') => {
12 | return function (req, res, next) {
13 | const currentUserRole = req.admin.role;
14 |
15 | if (roles[currentUserRole].includes(permissionName) || req.admin.role === 'admin') {
16 | next();
17 | } else {
18 | return res.status(403).json({
19 | success: false,
20 | result: null,
21 | message: 'Access denied : you are not granted permission.',
22 | });
23 | }
24 | };
25 | };
26 |
--------------------------------------------------------------------------------
/backend/middlewares/serverData.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | exports.getData = (model) => {
3 | const Model = mongoose.model(model);
4 | const result = Model.find({ removed: false });
5 | return result;
6 | };
7 |
8 | exports.getOne = (model, id) => {
9 | const Model = mongoose.model(model);
10 | const result = Model.findOne({ _id: id, removed: false });
11 | return result;
12 | };
13 |
--------------------------------------------------------------------------------
/backend/middlewares/settings/increaseBySettingKey.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const Model = mongoose.model('Setting');
4 |
5 | const increaseBySettingKey = async ({ settingKey }) => {
6 | try {
7 | if (!settingKey) {
8 | return null;
9 | }
10 |
11 | const result = await Model.findOneAndUpdate(
12 | { settingKey },
13 | {
14 | $inc: { settingValue: 1 },
15 | },
16 | {
17 | new: true, // return the new result instead of the old one
18 | runValidators: true,
19 | }
20 | ).exec();
21 |
22 | // If no results found, return document not found
23 | if (!result) {
24 | return null;
25 | } else {
26 | // Return success resposne
27 | return result;
28 | }
29 | } catch {
30 | return null;
31 | }
32 | };
33 |
34 | module.exports = increaseBySettingKey;
35 |
--------------------------------------------------------------------------------
/backend/middlewares/settings/index.js:
--------------------------------------------------------------------------------
1 | const listBySettingKey = require('./listBySettingKey');
2 | const readBySettingKey = require('./readBySettingKey');
3 | const listAllSetting = require('./listAllSetting');
4 | const updateBySettingKey = require('./updateBySettingKey');
5 | const increaseBySettingKey = require('./increaseBySettingKey');
6 |
7 | module.exports = {
8 | listAllSetting,
9 | listBySettingKey,
10 | readBySettingKey,
11 | updateBySettingKey,
12 | increaseBySettingKey,
13 | };
14 |
--------------------------------------------------------------------------------
/backend/middlewares/settings/listAllSetting.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const Model = mongoose.model('Setting');
4 |
5 | const listAllSetting = async () => {
6 | const sort = parseInt(req.query.sort) || 'desc';
7 | try {
8 | // Query the database for a list of all results
9 | const result = await Model.find({ removed: false }).sort({ created: sort }).populate();
10 |
11 | if (result.length > 0) {
12 | return result;
13 | } else {
14 | return [];
15 | }
16 | } catch (error) {
17 | return [];
18 | }
19 | };
20 |
21 | module.exports = listAllSetting;
22 |
--------------------------------------------------------------------------------
/backend/middlewares/settings/listBySettingKey.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const Model = mongoose.model('Setting');
4 |
5 | const listBySettingKey = async ({ settingKeyArray = [] }) => {
6 | try {
7 | // Find document by id
8 |
9 | const settingsToShow = { $or: [] };
10 |
11 | if (settingKeyArray.length === 0) {
12 | return [];
13 | }
14 |
15 | for (const settingKey of settingKeyArray) {
16 | settingsToShow.$or.push({ settingKey });
17 | }
18 | let results = await Model.find(settings).where('removed', false);
19 |
20 | // If no results found, return document not found
21 | if (results.length >= 1) {
22 | return results;
23 | } else {
24 | return [];
25 | }
26 | } catch {
27 | return [];
28 | }
29 | };
30 |
31 | module.exports = listBySettingKey;
32 |
--------------------------------------------------------------------------------
/backend/middlewares/settings/readBySettingKey.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const Model = mongoose.model('Setting');
4 |
5 | const readBySettingKey = async ({ settingKey }) => {
6 | try {
7 | // Find document by id
8 |
9 | if (!settingKey) {
10 | return null;
11 | }
12 |
13 | const result = await Model.findOne({ settingKey });
14 | // If no results found, return document not found
15 | if (!result) {
16 | return null;
17 | } else {
18 | // Return success resposne
19 | return result;
20 | }
21 | } catch {
22 | return null;
23 | }
24 | };
25 |
26 | module.exports = readBySettingKey;
27 |
--------------------------------------------------------------------------------
/backend/middlewares/settings/updateBySettingKey.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const Model = mongoose.model('Setting');
4 |
5 | const updateBySettingKey = async ({ settingKey, settingValue }) => {
6 | try {
7 | console.log(
8 | '🚀 ~ file: updateBySettingKey.js:8 ~ updateBySettingKey ~ settingKey:',
9 | settingKey
10 | );
11 | if (!settingKey || !settingValue) {
12 | return null;
13 | }
14 |
15 | const result = await Model.findOneAndUpdate(
16 | { settingKey },
17 | {
18 | settingValue,
19 | },
20 | {
21 | new: true, // return the new result instead of the old one
22 | runValidators: true,
23 | }
24 | ).exec();
25 | // If no results found, return document not found
26 | if (!result) {
27 | return null;
28 | } else {
29 | // Return success resposne
30 | return result;
31 | }
32 | } catch {
33 | return null;
34 | }
35 | };
36 |
37 | module.exports = updateBySettingKey;
38 |
--------------------------------------------------------------------------------
/backend/middlewares/uploadMiddleware/createSingleUpload.js:
--------------------------------------------------------------------------------
1 | const Upload = require('@/models/coreModels/Upload');
2 |
3 | // cmiddleware to upload the private document
4 | const createSingleUpload = async (req, res, next) => {
5 | const modelName = req.params.model;
6 | const fieldId = req.params.fieldId;
7 | const isPublic = req.query.ispublic == true ? true : false;
8 | const userID = req.admin._id;
9 |
10 | if (req.upload) {
11 | let { fileName, fieldExt } = req.upload;
12 | try {
13 | const upload = await Upload.create({
14 | modelName: modelName,
15 | fieldId: fieldId,
16 | fileName: fileName,
17 | fileType: fieldExt.slice(1), //removing the dot from the fileExt
18 | enabled: true,
19 | isPublic: isPublic,
20 | userID: userID,
21 | isSecure: true,
22 | removed: false,
23 | path: `/upload/${modelName}/${fileName}${fieldExt}`,
24 | });
25 |
26 | if (upload) {
27 | next();
28 | } else {
29 | return res.status(500).json({ success: false, message: error.message });
30 | }
31 | } catch (error) {
32 | return res.status(500).json({ success: false, message: error.message });
33 | }
34 | } else {
35 | return res.status(500).json({ success: false, message: error.message });
36 | }
37 | };
38 |
39 | module.exports = createSingleUpload;
40 |
--------------------------------------------------------------------------------
/backend/middlewares/uploadMiddleware/index.js:
--------------------------------------------------------------------------------
1 | const createMultipleUpload = require('./createMultipleUpload');
2 | const uploadMultipleToStorage = require('./uploadMultipleToStorage');
3 | const createSingleUpload = require('./createSingleUpload');
4 | const uploadSingleToStorage = require('./uploadSingleToStorage');
5 | const singleStorageUpload = require('./singleStorageUpload');
6 | const setFilePathToBody = require('./setFilePathToBody');
7 |
8 | module.exports = {
9 | singleStorageUpload,
10 | uploadMultipleToStorage,
11 | createMultipleUpload,
12 | uploadSingleToStorage,
13 | createSingleUpload,
14 | setFilePathToBody,
15 | };
16 |
--------------------------------------------------------------------------------
/backend/middlewares/uploadMiddleware/setFilePathToBody.js:
--------------------------------------------------------------------------------
1 | //this middleware will check if the admin is logged in, if not he will be redirected to the login page :)
2 | module.exports = (fieldName = 'filePath') => {
3 | return (req, res, next) => {
4 | if (req.file) {
5 | req.body[fieldName] = req.file.path;
6 | }
7 | // if (req.files) {
8 | // req.body[req.files.fieldName] = req.files.path
9 | // }
10 | next();
11 | };
12 | };
13 |
--------------------------------------------------------------------------------
/backend/middlewares/uploadMiddleware/uploadMultipleToStorage.js:
--------------------------------------------------------------------------------
1 | const multer = require('multer');
2 | const fs = require('fs');
3 | const path = require('path');
4 | const { slugify } = require('transliteration');
5 |
6 | const fileFilter = require('./fileFilter');
7 |
8 | // middleware to upload the public document
9 | const storage = multer.diskStorage({
10 | //--> public/upload/:model/:fieldId.
11 | destination: function (req, file, cb) {
12 | const modelName = req.params.model;
13 | fs.mkdir(`upload/${modelName}`, (error) => {
14 | return cb(null, `upload/${modelName}`);
15 | });
16 | },
17 | filename: function (req, file, cb) {
18 | // fetching the file extention of the uploaded file
19 | let fileExtension = path.extname(file.originalname);
20 | let uniqueFileID = Math.random().toString(36).slice(2, 7); //generates unique ID of length 5
21 |
22 | let originalname = slugify(file.originalname.split('.')[0].toLocaleLowerCase()); //convert any language to english characters
23 | let _fileName = `${originalname}-${uniqueFileID}${fileExtension}`;
24 |
25 | // saving file name and extention in request upload object
26 | let files = req?.upload?.files ?? [];
27 | const _data = {
28 | fileName: _fileName,
29 | fieldExt: fileExtension,
30 | };
31 | files.push(_data);
32 | req.upload = {
33 | files: files,
34 | };
35 | return cb(null, _fileName);
36 | },
37 | });
38 |
39 | const uploadMultipleToStorage = multer({ storage: storage, fileFilter: fileFilter });
40 |
41 | module.exports = uploadMultipleToStorage;
42 |
--------------------------------------------------------------------------------
/backend/middlewares/uploadMiddleware/uploadSingleToStorage.js:
--------------------------------------------------------------------------------
1 | const multer = require('multer');
2 | const fs = require('fs');
3 | const path = require('path');
4 | const { slugify } = require('transliteration');
5 |
6 | const fileFilter = require('./fileFilter');
7 |
8 | // middleware to upload the public document
9 | const storage = multer.diskStorage({
10 | //--> public/upload/:model/:fieldId.
11 | destination: function (req, file, cb) {
12 | const modelName = req.params.model;
13 | fs.mkdir(`upload/${modelName}`, (error) => {
14 | return cb(null, `upload/${modelName}`);
15 | });
16 | },
17 |
18 | filename: function (req, file, cb) {
19 | // fetching the file extention of the uploaded file
20 | let fileExtension = path.extname(file.originalname);
21 | let uniqueFileID = Math.random().toString(36).slice(2, 7); //generates unique ID of length 5
22 |
23 | let originalname = slugify(file.originalname.split('.')[0].toLocaleLowerCase()); //convert any language to english characters
24 | let _fileName = `${originalname}-${uniqueFileID}${fileExtension}`;
25 |
26 | // saving file name and extention in request upload object
27 | req.upload = {
28 | fileName: _fileName,
29 | fieldExt: fileExtension,
30 | };
31 |
32 | return cb(null, _fileName);
33 | },
34 | });
35 |
36 | const uploadSingleToStorage = multer({ storage: storage, fileFilter: fileFilter });
37 |
38 | module.exports = uploadSingleToStorage;
39 |
--------------------------------------------------------------------------------
/backend/models/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-ninja11/nextGPT/248aea2c8e9f5b95a69529e0c3a1dd51bdd39d50/backend/models/.gitkeep
--------------------------------------------------------------------------------
/backend/models/appModels/Expense.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | mongoose.Promise = global.Promise;
3 |
4 | const expenseSchema = new mongoose.Schema({
5 | removed: {
6 | type: Boolean,
7 | default: false,
8 | },
9 | date: {
10 | type: Date,
11 | default: Date.now,
12 | },
13 | name: {
14 | type: String,
15 | trim: true,
16 | required: true,
17 | },
18 | description: {
19 | type: String,
20 | trim: true,
21 | required: true,
22 | },
23 | ref: {
24 | type: String,
25 | trim: true,
26 | },
27 | supplier: {
28 | type: mongoose.Schema.ObjectId,
29 | },
30 | OrderForm: {
31 | type: mongoose.Schema.ObjectId,
32 | },
33 | expenseCategory: {
34 | type: mongoose.Schema.ObjectId,
35 | ref: 'ExpenseCategory',
36 | required: true,
37 | },
38 | taxRate: {
39 | type: Number,
40 | },
41 | subTotal: {
42 | type: Number,
43 | },
44 | taxTotal: {
45 | type: Number,
46 | },
47 | total: {
48 | type: Number,
49 | },
50 | paymentMode: {
51 | type: mongoose.Schema.ObjectId,
52 | ref: 'PaymentMode',
53 | },
54 | attachedFile: {
55 | type: String,
56 | },
57 | updated: {
58 | type: Date,
59 | default: Date.now,
60 | },
61 | created: {
62 | type: Date,
63 | default: Date.now,
64 | },
65 | });
66 |
67 | module.exports = mongoose.model('Expense', expenseSchema);
68 |
--------------------------------------------------------------------------------
/backend/models/appModels/ExpenseCategory.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | mongoose.Promise = global.Promise;
3 |
4 | const expenseCategorySchema = new mongoose.Schema({
5 | removed: {
6 | type: Boolean,
7 | default: false,
8 | },
9 | enabled: {
10 | type: Boolean,
11 | default: true,
12 | },
13 | name: {
14 | type: String,
15 | trim: true,
16 | required: true,
17 | },
18 | description: {
19 | type: String,
20 | trim: true,
21 | required: true,
22 | },
23 | created: {
24 | type: Date,
25 | default: Date.now,
26 | },
27 | });
28 |
29 | module.exports = mongoose.model('ExpenseCategory', expenseCategorySchema);
30 |
--------------------------------------------------------------------------------
/backend/models/appModels/Inventory.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | mongoose.Promise = global.Promise;
3 |
4 | const inventorySchema = new mongoose.Schema({
5 | removed: {
6 | type: Boolean,
7 | default: false,
8 | },
9 | enabled: {
10 | type: Boolean,
11 | default: true,
12 | },
13 | product: {
14 | type: String,
15 | trim: true,
16 | required: true,
17 | },
18 | quantity: {
19 | type: Number,
20 | required: true,
21 | min: 0, // Ensure non-negative numbers
22 | },
23 | unitPrice: {
24 | type: Number,
25 | required: true,
26 | min: 0, // Ensure non-negative numbers
27 | },
28 | created: {
29 | type: Date,
30 | default: Date.now,
31 | },
32 | });
33 |
34 | module.exports = mongoose.model('Inventory', inventorySchema);
35 |
--------------------------------------------------------------------------------
/backend/models/appModels/Item.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | mongoose.Promise = global.Promise;
3 |
4 | const itemSchema = new mongoose.Schema({
5 | removed: {
6 | type: Boolean,
7 | default: false,
8 | },
9 | enabled: {
10 | type: Boolean,
11 | default: true,
12 | },
13 | name: {
14 | type: String,
15 | trim: true,
16 | required: true,
17 | },
18 | description: {
19 | type: String,
20 | trim: true,
21 | },
22 | price: {
23 | type: Number,
24 | },
25 | });
26 |
27 | module.exports = mongoose.model('Item', itemSchema);
28 |
--------------------------------------------------------------------------------
/backend/models/appModels/Kyc.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | mongoose.Promise = global.Promise;
3 |
4 | const kycSchema = new mongoose.Schema({
5 | removed: {
6 | type: Boolean,
7 | default: false,
8 | },
9 | enabled: {
10 | type: Boolean,
11 | default: true,
12 | },
13 | created: {
14 | type: Date,
15 | default: Date.now,
16 | },
17 | // Fields for shipping
18 | name: {
19 | type: String,
20 | trim: true,
21 | required: true,
22 | },
23 | address: {
24 | type: String,
25 | trim: true,
26 | required: true,
27 | },
28 | contact: {
29 | type: String,
30 | required: true,
31 | },
32 | filePath: {
33 | type: String,
34 | trim: true,
35 | },
36 | });
37 |
38 | module.exports = mongoose.model('Kyc', kycSchema);
39 |
--------------------------------------------------------------------------------
/backend/models/appModels/Lead.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | mongoose.Promise = global.Promise;
3 |
4 | const leadSchema = new mongoose.Schema({
5 | removed: {
6 | type: Boolean,
7 | default: false,
8 | },
9 | enabled: {
10 | type: Boolean,
11 | default: true,
12 | },
13 | firstName: {
14 | type: String,
15 | trim: true,
16 | required: true,
17 | },
18 | lastName: {
19 | type: String,
20 | trim: true,
21 | required: true,
22 | },
23 | company: {
24 | type: String,
25 | trim: true,
26 | },
27 | jobTitle: {
28 | type: String,
29 | trim: true,
30 | },
31 | email: {
32 | type: String,
33 | trim: true,
34 | lowercase: true,
35 | unique: true,
36 | },
37 | phone: {
38 | type: String,
39 | trim: true,
40 | required: true,
41 | },
42 | address: {
43 | type: String,
44 | trim: true,
45 | },
46 | country: {
47 | type: String,
48 | trim: true,
49 | },
50 | customField: [
51 | {
52 | fieldName: {
53 | type: String,
54 | trim: true,
55 | },
56 | fieldValue: {
57 | type: String,
58 | trim: true,
59 | },
60 | },
61 | ],
62 | source: {
63 | type: String,
64 | trim: true,
65 | },
66 | notes: {
67 | type: String,
68 | trim: true,
69 | },
70 | status: {
71 | type: String,
72 | default: 'new',
73 | },
74 | created: {
75 | type: Date,
76 | default: Date.now,
77 | },
78 | });
79 |
80 | module.exports = mongoose.model('Lead', leadSchema);
81 |
--------------------------------------------------------------------------------
/backend/models/appModels/Order.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | mongoose.Promise = global.Promise;
3 |
4 | const orderSchema = new mongoose.Schema({
5 | removed: {
6 | type: Boolean,
7 | default: false,
8 | },
9 | enabled: {
10 | type: Boolean,
11 | default: true,
12 | },
13 | created: {
14 | type: Date,
15 | default: Date.now,
16 | },
17 | // Fields for shipping
18 | orderId: {
19 | type: String,
20 | trim: true,
21 | required: true,
22 | },
23 | products: {
24 | type: String, // Consider changing this to an array of objects if you have multiple products
25 | trim: true,
26 | required: true,
27 | },
28 | quantity: {
29 | type: Number,
30 | required: true,
31 | },
32 | price: {
33 | type: Number,
34 | required: true,
35 | },
36 | status: {
37 | type: String,
38 | enum: ['pending', 'shipped', 'delivered', 'cancelled'],
39 | required: true,
40 | },
41 | notes: {
42 | type: String,
43 | trim: true,
44 | },
45 | });
46 |
47 | module.exports = mongoose.model('Order', orderSchema);
48 |
--------------------------------------------------------------------------------
/backend/models/appModels/Payment.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | mongoose.Promise = global.Promise;
3 |
4 | const paymentSchema = new mongoose.Schema({
5 | removed: {
6 | type: Boolean,
7 | default: false,
8 | },
9 | createdBy: { type: mongoose.Schema.ObjectId, ref: 'Admin', autopopulate: true, required: true },
10 | number: {
11 | type: Number,
12 | required: true,
13 | },
14 | client: {
15 | type: mongoose.Schema.ObjectId,
16 | ref: 'Client',
17 | autopopulate: true,
18 | required: true,
19 | },
20 | invoice: {
21 | type: mongoose.Schema.ObjectId,
22 | ref: 'Invoice',
23 | required: true,
24 | autopopulate: true,
25 | },
26 | date: {
27 | type: Date,
28 | default: Date.now,
29 | required: true,
30 | },
31 | amount: {
32 | type: Number,
33 | required: true,
34 | },
35 | paymentMode: {
36 | type: mongoose.Schema.ObjectId,
37 | ref: 'PaymentMode',
38 | autopopulate: true,
39 | },
40 | ref: {
41 | type: String,
42 | },
43 | description: {
44 | type: String,
45 | },
46 | updated: {
47 | type: Date,
48 | default: Date.now,
49 | },
50 | created: {
51 | type: Date,
52 | default: Date.now,
53 | },
54 | });
55 | paymentSchema.plugin(require('mongoose-autopopulate'));
56 | module.exports = mongoose.model('Payment', paymentSchema);
57 |
--------------------------------------------------------------------------------
/backend/models/appModels/PaymentMode.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | mongoose.Promise = global.Promise;
3 |
4 | const paymentModeSchema = new mongoose.Schema({
5 | removed: {
6 | type: Boolean,
7 | default: false,
8 | },
9 | enabled: {
10 | type: Boolean,
11 | default: true,
12 | },
13 | isDefault: {
14 | type: Boolean,
15 | default: false,
16 | },
17 | name: {
18 | type: String,
19 | required: true,
20 | },
21 | description: {
22 | type: String,
23 | required: true,
24 | },
25 | ref: {
26 | type: String,
27 | },
28 | created: {
29 | type: Date,
30 | default: Date.now,
31 | },
32 | });
33 |
34 | module.exports = mongoose.model('PaymentMode', paymentModeSchema);
35 |
--------------------------------------------------------------------------------
/backend/models/appModels/Supplier.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | mongoose.Promise = global.Promise;
3 |
4 | const supplierSchema = new mongoose.Schema({
5 | removed: {
6 | type: Boolean,
7 | default: false,
8 | },
9 | enabled: {
10 | type: Boolean,
11 | default: true,
12 | },
13 | company: {
14 | type: String,
15 | trim: true,
16 | required: true,
17 | },
18 | managerName: {
19 | type: String,
20 | trim: true,
21 | required: true,
22 | },
23 | managerSurname: {
24 | type: String,
25 | trim: true,
26 | required: true,
27 | },
28 | bankAccount: {
29 | type: String,
30 | trim: true,
31 | },
32 | RC: {
33 | type: String,
34 | trim: true,
35 | },
36 | AI: {
37 | type: String,
38 | trim: true,
39 | },
40 | NIF: {
41 | type: String,
42 | trim: true,
43 | },
44 | NIS: {
45 | type: String,
46 | trim: true,
47 | },
48 | address: {
49 | type: String,
50 | trim: true,
51 | },
52 | tel: {
53 | type: String,
54 | trim: true,
55 | required: true,
56 | },
57 | fax: {
58 | type: String,
59 | trim: true,
60 | },
61 | cell: {
62 | type: String,
63 | trim: true,
64 | },
65 | email: {
66 | type: String,
67 | trim: true,
68 | },
69 | website: {
70 | type: String,
71 | trim: true,
72 | },
73 | created: {
74 | type: Date,
75 | default: Date.now,
76 | },
77 | });
78 |
79 | module.exports = mongoose.model('Supplier', supplierSchema);
80 |
--------------------------------------------------------------------------------
/backend/models/appModels/Tax.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const {Mongoose} = require("mongoose");
3 | mongoose.Promise = global.Promise;
4 |
5 | const TaxSchema = new mongoose.Schema({
6 | taxName: {
7 | type: String,
8 | required: true,
9 | },
10 | taxValue: {
11 | type: Number,
12 | required: true,
13 | },
14 | isDefault: {
15 | type: Boolean,
16 | default: false,
17 | },
18 | created: {
19 | type: Date,
20 | default: Date.now,
21 | },
22 | removed: {
23 | type: Boolean,
24 | default: false,
25 | },
26 | enabled: {
27 | type: Boolean,
28 | default: true,
29 | },
30 | });
31 |
32 | module.exports = mongoose.model('Tax', TaxSchema);
33 |
--------------------------------------------------------------------------------
/backend/models/coreModels/Admin.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const Schema = mongoose.Schema;
3 | mongoose.Promise = global.Promise;
4 | const bcrypt = require('bcryptjs');
5 |
6 | const adminSchema = new Schema({
7 | removed: {
8 | type: Boolean,
9 | default: false,
10 | },
11 | enabled: {
12 | type: Boolean,
13 | default: true,
14 | },
15 | password: {
16 | type: String,
17 | required: true,
18 | },
19 | email: {
20 | type: String,
21 | unique: true,
22 | lowercase: true,
23 | trim: true,
24 | required: true,
25 | },
26 | name: { type: String, required: true, lowercase: true },
27 | surname: { type: String, required: true, lowercase: true },
28 | photo: {
29 | type: String,
30 | trim: true,
31 | },
32 | created: {
33 | type: Date,
34 | default: Date.now,
35 | },
36 | role: {
37 | type: String,
38 | default: 'staff',
39 | enum: ['admin', 'staffAdmin', 'staff', 'createOnly', 'readOnly'],
40 | },
41 | isLoggedIn: { type: Number },
42 | loggedSessions: {
43 | type: [String],
44 | default: [],
45 | },
46 | });
47 |
48 | adminSchema.plugin(require('mongoose-autopopulate'));
49 |
50 | // generating a hash
51 | adminSchema.methods.generateHash = function (password) {
52 | return bcrypt.hashSync(password, bcrypt.genSaltSync(), null);
53 | };
54 |
55 | // checking if password is valid
56 | adminSchema.methods.validPassword = function (password) {
57 | return bcrypt.compareSync(password, this.password);
58 | };
59 |
60 | module.exports = mongoose.model('Admin', adminSchema);
61 |
--------------------------------------------------------------------------------
/backend/models/coreModels/Email.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | mongoose.Promise = global.Promise;
3 |
4 | const emailSchema = new mongoose.Schema({
5 | emailKey: {
6 | type: String,
7 | unique: true,
8 | lowercase: true,
9 | required: true,
10 | },
11 | emailName: {
12 | type: String,
13 | unique:true,
14 | required:true,
15 | },
16 | emailVariables:{
17 | type:Array
18 | },
19 | emailBody: {
20 | type: String,
21 | required: true,
22 | },
23 | emailSubject: {
24 | type: String,
25 | required: true,
26 | },
27 | language:{
28 | type:String,
29 | default:"en"
30 | },
31 | removed: {
32 | type: Boolean,
33 | default: false,
34 | },
35 | enabled: {
36 | type: Boolean,
37 | default: true,
38 | },
39 | created: {
40 | type: Date,
41 | default: Date.now,
42 | },
43 | updated: {
44 | type: Date,
45 | default: Date.now,
46 | },
47 | });
48 |
49 | module.exports = mongoose.model('Email', emailSchema);
50 |
--------------------------------------------------------------------------------
/backend/models/coreModels/Setting.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | mongoose.Promise = global.Promise;
3 |
4 | const settingSchema = new mongoose.Schema({
5 | removed: {
6 | type: Boolean,
7 | default: false,
8 | },
9 | enabled: {
10 | type: Boolean,
11 | default: true,
12 | },
13 | settingCategory: {
14 | type: String,
15 | required: true,
16 | lowercase: true,
17 | },
18 | settingKey: {
19 | type: String,
20 | unique: true,
21 | lowercase: true,
22 | required: true,
23 | },
24 | settingValue: {
25 | type: mongoose.Schema.Types.Mixed,
26 | required: true,
27 | },
28 | valueType: {
29 | type: String,
30 | default: 'String',
31 | },
32 | isCoreSetting: {
33 | type: Boolean,
34 | default: false,
35 | },
36 | });
37 |
38 | module.exports = mongoose.model('Setting', settingSchema);
39 |
--------------------------------------------------------------------------------
/backend/models/coreModels/Upload.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | mongoose.Promise = global.Promise;
3 |
4 | const uploadSchema = new mongoose.Schema({
5 | modelName: {
6 | type: String,
7 | trim: true,
8 | },
9 | fieldId: {
10 | type: String,
11 | required: true,
12 | },
13 | fileName: {
14 | type: String,
15 | required: true,
16 | },
17 | fileType: {
18 | type: String,
19 | enum: [
20 | 'jpeg',
21 | 'jpg',
22 | 'png',
23 | 'gif',
24 | 'webp',
25 | 'doc',
26 | 'txt',
27 | 'csv',
28 | 'docx',
29 | 'xls',
30 | 'xlsx',
31 | 'pdf',
32 | 'zip',
33 | 'rar',
34 | 'mp4',
35 | 'mov',
36 | 'avi',
37 | 'mp3',
38 | 'm4a',
39 | 'webm',
40 | ],
41 | required: true,
42 | },
43 | enabled: {
44 | type: Boolean,
45 | default: true,
46 | },
47 | isPublic: {
48 | type: Boolean,
49 | required: true,
50 | },
51 | userID: {
52 | type: mongoose.SchemaTypes.ObjectId,
53 | required: true,
54 | },
55 | isSecure: {
56 | type: Boolean,
57 | required: true,
58 | },
59 | removed: {
60 | type: Boolean,
61 | default: false,
62 | required: true,
63 | },
64 | path: {
65 | type: String,
66 | unique: true,
67 | required: true,
68 | },
69 | });
70 |
71 | module.exports = mongoose.model('Upload ', uploadSchema);
72 |
--------------------------------------------------------------------------------
/backend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "idurar-erp-crm",
3 | "version": "3.0.0-beta.14",
4 | "engines": {
5 | "npm": "10.2.4",
6 | "node": "20.9.0"
7 | },
8 | "scripts": {
9 | "start": "node server.js",
10 | "dev": "nodemon server.js --ignore public/",
11 | "production": "NODE_ENV=production",
12 | "setup": "node setup/setup.js",
13 | "reset": "node setup/reset.js"
14 | },
15 | "dependencies": {
16 | "bcryptjs": "^2.4.3",
17 | "cookie-parser": "^1.4.5",
18 | "cors": "^2.8.5",
19 | "currency.js": "2.0.4",
20 | "dotenv": "4.0.0",
21 | "express": "^4.18.1",
22 | "glob": "7.1.1",
23 | "helmet": "^4.6.0",
24 | "html-pdf": "^3.0.1",
25 | "joi": "^17.10.1",
26 | "jsonwebtoken": "^8.5.1",
27 | "lodash": "^4.17.20",
28 | "module-alias": "^2.2.2",
29 | "moment": "^2.26.0",
30 | "mongoose": "^8.0.1",
31 | "mongoose-autopopulate": "^1.1.0",
32 | "multer": "^1.4.2",
33 | "pug": "^3.0.2",
34 | "resend": "^0.17.2",
35 | "transliteration": "^2.3.5"
36 | },
37 | "devDependencies": {
38 | "nodemon": "^1.19.2"
39 | },
40 | "_moduleAliases": {
41 | "@": "."
42 | },
43 | "description": "Just you wait folks!",
44 | "main": "app.js",
45 | "keywords": [],
46 | "author": "IDURAR",
47 | "license": "Fair-code License",
48 | "repository": {
49 | "type": "git",
50 | "url": "git+https://github.com/idurar/idurar-erp-crm/.git"
51 | },
52 | "bugs": {
53 | "url": "https://github.com/idurar/idurar-erp-crm//issues"
54 | },
55 | "homepage": "https://github.com/idurar/idurar-erp-crm/#readme"
56 | }
57 |
--------------------------------------------------------------------------------
/backend/routes/coreRoutes/coreAuth.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 |
3 | const router = express.Router();
4 |
5 | const { catchErrors } = require('@/handlers/errorHandlers');
6 | const {
7 | isValidAdminToken,
8 | login,
9 | logout,
10 | } = require('@/controllers/coreControllers/authJwtController');
11 |
12 | router.route('/login').post(catchErrors(login));
13 | router.route('/logout').post(isValidAdminToken, catchErrors(logout));
14 |
15 | module.exports = router;
16 |
--------------------------------------------------------------------------------
/backend/routes/coreRoutes/coreDownloadRouter.js:
--------------------------------------------------------------------------------
1 | const downloadPdf = require('@/handlers/downloadHandler/downloadPdf');
2 | const express = require('express');
3 | const path = require('path');
4 | const router = express.Router();
5 | const { hasPermission } = require('@/middlewares/permission');
6 |
7 | // router.route('/:directory/:file').get(function (req, res) {
8 | // const { directory, file } = req.params;
9 |
10 | // // Handle the /payment/* route
11 |
12 | // const options = {
13 | // root: path.join(__dirname, `../../public/download/${directory}`),
14 | // dotfiles: 'deny',
15 | // headers: {
16 | // 'Content-type': 'application/pdf',
17 | // 'Content-disposition': 'inline; filename="' + file + '"',
18 | // },
19 | // };
20 |
21 | // res.status(200).sendFile(file, options, function (error) {
22 | // if (error) {
23 | // const id = file.slice(directory.length + 1).slice(0, -4); // extract id from file name
24 | // downloadPdf(req, res, { directory, id });
25 | // }
26 | // });
27 | // });
28 |
29 | router.route('/:directory/:file').get(function (req, res) {
30 | const { directory, file } = req.params;
31 | const id = file.slice(directory.length + 1).slice(0, -4); // extract id from file name
32 | downloadPdf(req, res, { directory, id });
33 | });
34 |
35 | module.exports = router;
36 |
--------------------------------------------------------------------------------
/backend/routes/coreRoutes/corePublicRouter.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 |
3 | const router = express.Router();
4 | const path = require('path');
5 |
6 | const { hasPermission } = require('@/middlewares/permission');
7 |
8 | // Without middleware
9 |
10 | router.route('/:subPath/:directory/:file').get(function (req, res) {
11 | const { subPath, directory, file } = req.params;
12 |
13 | const options = {
14 | root: path.join(__dirname, `../../public/${subPath}/${directory}`),
15 | };
16 | const fileName = file;
17 | res.sendFile(fileName, options, function (error) {
18 | if (error) {
19 | res.sendStatus(404);
20 | }
21 | });
22 | });
23 |
24 | module.exports = router;
25 |
--------------------------------------------------------------------------------
/backend/server.js:
--------------------------------------------------------------------------------
1 | require('module-alias/register');
2 | const mongoose = require('mongoose');
3 |
4 | // Make sure we are running node 7.6+
5 | const [major, minor] = process.versions.node.split('.').map(parseFloat);
6 | if (major < 16 || (major === 16 && minor <= 20)) {
7 | console.log('Please upgrade your node.js version at least 16.20.2 or greater. 👌\n ');
8 | process.exit();
9 | }
10 |
11 | // import environmental variables from our variables.env file
12 | require('dotenv').config({ path: '.env' });
13 | require('dotenv').config({ path: '.env.local' });
14 |
15 | // Connect to our Database and handle any bad connections
16 | // mongoose.connect(process.env.DATABASE);
17 |
18 | mongoose.connect(process.env.DATABASE);
19 | mongoose.Promise = global.Promise; // Tell Mongoose to use ES6 promises
20 | mongoose.connection.on('error', (error) => {
21 | console.log(
22 | `1. 🔥 Common Error caused issue → : check your .env file first and add your mongodb url`
23 | );
24 | console.error(`2. 🚫 Error → : ${error.message}`);
25 | });
26 |
27 | const glob = require('glob');
28 | const path = require('path');
29 |
30 | glob.sync('./models/**/*.js').forEach(function (file) {
31 | require(path.resolve(file));
32 | });
33 |
34 | // Start our app!
35 | const app = require('./app');
36 | app.set('port', process.env.PORT || 8888);
37 | const server = app.listen(app.get('port'), () => {
38 | console.log(`Express running → On PORT : ${server.address().port}`);
39 | });
40 |
--------------------------------------------------------------------------------
/backend/setup/config/appConfig.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "settingCategory": "app_settings",
4 | "settingKey": "app_name",
5 | "settingValue": "IDURAR ERP/CRM",
6 | "valueType": "string",
7 | "isCoreSetting": true
8 | },
9 | {
10 | "settingCategory": "app_settings",
11 | "settingKey": "app_icon",
12 | "settingValue": "https://www.idurarapp.com/Theme/idurar-no-code-app/assets/img/idurar-ai-no-code-app-logo.svg",
13 | "valueType": "image",
14 | "isCoreSetting": true
15 | },
16 | {
17 | "settingCategory": "app_settings",
18 | "settingKey": "app_logo",
19 | "settingValue": "https://www.idurarapp.com/Theme/idurar-no-code-app/assets/img/idurar-ai-no-code-app-logo.svg",
20 | "valueType": "image",
21 | "isCoreSetting": true
22 | },
23 | {
24 | "settingCategory": "app_settings",
25 | "settingKey": "language",
26 | "settingValue": "en_us",
27 | "valueType": "string",
28 | "isCoreSetting": true
29 | },
30 | {
31 | "settingCategory": "app_settings",
32 | "settingKey": "allowed_role",
33 | "settingValue": ["admin", "staff"],
34 | "valueType": "array",
35 | "isCoreSetting": true
36 | },
37 | {
38 | "settingCategory": "app_settings",
39 | "settingKey": "email_settings",
40 | "settingValue": {
41 | "smtpServer": "smtp.example.com",
42 | "port": 587,
43 | "username": "example@example.com",
44 | "password": "mysecretpassword"
45 | },
46 | "valueType": "object",
47 | "isCoreSetting": true
48 | }
49 | ]
50 |
--------------------------------------------------------------------------------
/backend/setup/config/crmConfig.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "settingCategory": "crm_settings",
4 | "settingKey": "lead_source",
5 | "settingValue": ["recomendation", "facebook", "linkedin", "newsletter", "website"],
6 | "valueType": "array",
7 | "isCoreSetting": true
8 | },
9 | {
10 | "settingCategory": "crm_settings",
11 | "settingKey": "lead_status",
12 | "settingValue": ["new", "contacted", "waiting", "in negosation", "won", "loose"],
13 | "valueType": "array",
14 | "isCoreSetting": true
15 | }
16 | ]
17 |
--------------------------------------------------------------------------------
/backend/setup/config/customConfig.json:
--------------------------------------------------------------------------------
1 | []
2 |
--------------------------------------------------------------------------------
/backend/setup/config/moneyFormatConfig.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "settingCategory": "money_format_settings",
4 | "settingKey": "currency",
5 | "settingValue": "usd",
6 | "valueType": "string",
7 | "isCoreSetting": true
8 | },
9 | {
10 | "settingCategory": "money_format_settings",
11 | "settingKey": "currency_symbol",
12 | "settingValue": "$",
13 | "valueType": "string",
14 | "isCoreSetting": true
15 | },
16 | {
17 | "settingCategory": "money_format_settings",
18 | "settingKey": "currency_position",
19 | "settingValue": "before",
20 | "valueType": "string",
21 | "isCoreSetting": true
22 | },
23 | {
24 | "settingCategory": "money_format_settings",
25 | "settingKey": "decimal_sep",
26 | "settingValue": ".",
27 | "valueType": "string",
28 | "isCoreSetting": true
29 | },
30 | {
31 | "settingCategory": "money_format_settings",
32 | "settingKey": "thousand_sep",
33 | "settingValue": ",",
34 | "valueType": "string",
35 | "isCoreSetting": true
36 | },
37 | {
38 | "settingCategory": "money_format_settings",
39 | "settingKey": "cent_precision",
40 | "settingValue": 2,
41 | "valueType": "number",
42 | "isCoreSetting": true
43 | },
44 | {
45 | "settingCategory": "money_format_settings",
46 | "settingKey": "zero_format",
47 | "settingValue": false,
48 | "valueType": "boolean",
49 | "isCoreSetting": true
50 | }
51 | ]
52 |
--------------------------------------------------------------------------------
/backend/setup/reset.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config({ path: __dirname + '/../.env' });
2 | require('dotenv').config({ path: __dirname + '/../.env.local' });
3 |
4 | const mongoose = require('mongoose');
5 | mongoose.connect(process.env.DATABASE);
6 | mongoose.Promise = global.Promise; // Tell Mongoose to use ES6 promises
7 |
8 | async function deleteData() {
9 | const Admin = require('../models/coreModels/Admin');
10 | const Setting = require('../models/coreModels/Setting');
11 | const Email = require('../models/coreModels/Email');
12 | await Admin.deleteMany();
13 | console.log('👍 admin Deleted. To setup demo admin data, run\n\n\t npm run setup\n\n');
14 | await Setting.deleteMany();
15 | console.log('👍 Setting Deleted. To setup demo admin data, run\n\n\t npm run setup\n\n');
16 | await Email.deleteMany();
17 | console.log('👍 Email Deleted. To setup demo admin data, run\n\n\t npm run setup\n\n');
18 | process.exit();
19 | }
20 |
21 | deleteData();
22 |
--------------------------------------------------------------------------------
/frontend/.env:
--------------------------------------------------------------------------------
1 | # ----> Remove # comment
2 |
3 | #VITE_BACKEND_SERVER="http://your_backend_url_server.com/"
4 | #PROD = false
--------------------------------------------------------------------------------
/frontend/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:react/recommended',
7 | 'plugin:react/jsx-runtime',
8 | 'plugin:react-hooks/recommended',
9 | ],
10 | ignorePatterns: ['dist', '.eslintrc.cjs'],
11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
12 | settings: { react: { version: '18.2' } },
13 | plugins: ['react-refresh'],
14 | rules: {
15 | 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
16 | 'react/prop-types': 0,
17 | },
18 | };
19 |
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /dist
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 |
--------------------------------------------------------------------------------
/frontend/.prettierignore:
--------------------------------------------------------------------------------
1 | # Ignore artifacts:
2 | build
3 | coverage
4 | node_modules
5 | public
--------------------------------------------------------------------------------
/frontend/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "trailingComma": "es5",
4 | "tabWidth": 2,
5 | "semi": true,
6 | "singleQuote": true
7 | }
8 |
--------------------------------------------------------------------------------
/frontend/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnPaste": true,
3 | "editor.formatOnSave": true,
4 | "editor.defaultFormatter": "esbenp.prettier-vscode",
5 | "[javascript]": {
6 | "editor.defaultFormatter": "esbenp.prettier-vscode"
7 | },
8 | "[jsonc]": {
9 | "editor.defaultFormatter": "esbenp.prettier-vscode"
10 | },
11 | "[markdown]": {
12 | "editor.quickSuggestions": {
13 | "comments": "on",
14 | "strings": "on",
15 | "other": "on"
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/frontend/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | IDURAR ERP CRM | Open Fair-Code Source
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/frontend/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "src",
4 | "paths": {
5 | "@/*": ["./*"]
6 | }
7 | },
8 | "exclude": ["node_modules", "build"]
9 | }
10 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "idurar-erp-crm",
3 | "version": "3.0.0-beta.14",
4 | "engines": {
5 | "npm": "10.2.4"
6 | },
7 | "dependencies": {
8 | "@ant-design/icons": "^5.2.6",
9 | "@ant-design/pro-layout": "^7.17.12",
10 | "@rollup/plugin-dynamic-import-vars": "^2.1.0",
11 | "@vitejs/plugin-react": "^4.0.4",
12 | "antd": "^5.11.0",
13 | "axios": "^1.6.0",
14 | "cross-env": "7.0.3",
15 | "currency.js": "2.0.4",
16 | "just-compare": "^2.3.0",
17 | "react": "^18.2.0",
18 | "react-dom": "^18.2.0",
19 | "react-quill": "^2.0.0",
20 | "react-redux": "^8.1.3",
21 | "react-router-dom": "^6.17.0",
22 | "redux": "^4.2.1",
23 | "redux-thunk": "^2.4.2",
24 | "reselect": "^4.1.8",
25 | "shortid": "^2.2.16",
26 | "vite": "^4.5.0"
27 | },
28 | "scripts": {
29 | "dev": "vite",
30 | "build": "vite build",
31 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
32 | "preview": "vite preview",
33 | "dev:remote": "cross-env VITE_DEV_REMOTE=remote npm run dev"
34 | },
35 | "devDependencies": {
36 | "@types/react": "^18.2.18",
37 | "@types/react-dom": "^18.2.7",
38 | "eslint": "^8.46.0",
39 | "eslint-plugin-react": "^7.33.1",
40 | "eslint-plugin-react-hooks": "^4.6.0",
41 | "eslint-plugin-react-refresh": "^0.4.3",
42 | "prettier": "2.5.1"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/frontend/rollup.config.js:
--------------------------------------------------------------------------------
1 | import dynamicImportVars from '@rollup/plugin-dynamic-import-vars';
2 |
3 | // function importModule(path) {
4 | // // who knows what will be imported here?
5 | // return import(path);
6 | // }
7 | export default {
8 | plugins: [
9 | dynamicImportVars({
10 | // options
11 | }),
12 | ],
13 | };
14 |
--------------------------------------------------------------------------------
/frontend/src/RootApp.jsx:
--------------------------------------------------------------------------------
1 | import './style/app.css';
2 |
3 | import { Suspense, lazy } from 'react';
4 | import { BrowserRouter } from 'react-router-dom';
5 | import { Provider } from 'react-redux';
6 | import store from '@/redux/store';
7 | import PageLoader from '@/components/PageLoader';
8 |
9 | const IdurarOs = lazy(() => import('./apps/IdurarOs'));
10 |
11 | export default function RoutApp() {
12 | return (
13 |
14 |
15 | }>
16 |
17 |
18 |
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/frontend/src/apps/IdurarOs.jsx:
--------------------------------------------------------------------------------
1 | import { lazy, Suspense } from 'react';
2 |
3 | import { useSelector } from 'react-redux';
4 | import { selectAuth } from '@/redux/auth/selectors';
5 | import { AppContextProvider } from '@/context/appContext';
6 | import PageLoader from '@/components/PageLoader';
7 | import Localization from '@/locale/Localization';
8 | import AuthRouter from '@/router/AuthRouter';
9 |
10 | const ErpApp = lazy(() => import('./ErpApp'));
11 |
12 | export default function IdurarOs() {
13 | const { isLoggedIn } = useSelector(selectAuth);
14 |
15 | if (!isLoggedIn)
16 | return (
17 |
18 | }>
19 |
20 |
21 |
22 | );
23 | else {
24 | return (
25 |
26 |
27 | }>
28 |
29 |
30 |
31 |
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/frontend/src/auth/auth.service.js:
--------------------------------------------------------------------------------
1 | import { API_BASE_URL } from '@/config/serverApiConfig';
2 |
3 | import axios from 'axios';
4 | import errorHandler from '@/request/errorHandler';
5 | import successHandler from '@/request/successHandler';
6 |
7 | export const login = async ({ loginData }) => {
8 | try {
9 | const response = await axios.post(
10 | API_BASE_URL + `login?timestamp=${new Date().getTime()}`,
11 | loginData
12 | );
13 |
14 | const { status, data } = response;
15 |
16 | successHandler(
17 | { data, status },
18 | {
19 | notifyOnSuccess: false,
20 | notifyOnFailed: true,
21 | }
22 | );
23 | return data;
24 | } catch (error) {
25 | return errorHandler(error);
26 | }
27 | };
28 | export const logout = async () => {
29 | axios.defaults.withCredentials = true;
30 | try {
31 | // window.localStorage.clear();
32 | // window.localStorage.removeItem('isLoggedIn');
33 | // window.localStorage.removeItem('auth');
34 | await axios.post(API_BASE_URL + `logout?timestamp=${new Date().getTime()}`);
35 | } catch (error) {
36 | return errorHandler(error);
37 | }
38 | };
39 |
--------------------------------------------------------------------------------
/frontend/src/auth/index.js:
--------------------------------------------------------------------------------
1 | export * from './auth.service';
2 |
--------------------------------------------------------------------------------
/frontend/src/components/CrudModal/index.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { Modal } from 'antd';
3 |
4 | import { useDispatch, useSelector } from 'react-redux';
5 | import { crud } from '@/redux/crud/actions';
6 | import { useCrudContext } from '@/context/crud';
7 | import { selectDeletedItem } from '@/redux/crud/selectors';
8 |
9 | import useLanguage from '@/locale/useLanguage';
10 |
11 | export default function DeleteModal({ config, children }) {
12 | const translate = useLanguage();
13 | let { entity, modalTitle = translate('delete_confirmation') } = config;
14 | const dispatch = useDispatch();
15 | const { current, isLoading, isSuccess } = useSelector(selectDeletedItem);
16 | const { state, crudContextAction } = useCrudContext();
17 | const { isModalOpen } = state;
18 | const { modal } = crudContextAction;
19 |
20 | useEffect(() => {
21 | if (isSuccess) {
22 | modal.close();
23 | dispatch(crud.list({ entity }));
24 | }
25 | }, [isSuccess]);
26 |
27 | const handleOk = () => {
28 | const id = current._id;
29 | dispatch(crud.delete({ entity, id }));
30 | };
31 | const handleCancel = () => {
32 | if (!isLoading) modal.close();
33 | };
34 | return (
35 |
42 | {children}
43 |
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/frontend/src/components/IconMenu/index.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | DesktopOutlined,
3 | SettingOutlined,
4 | CustomerServiceOutlined,
5 | FileTextOutlined,
6 | FileSyncOutlined,
7 | DashboardOutlined,
8 | TeamOutlined,
9 | UserOutlined,
10 | CreditCardOutlined,
11 | BankOutlined,
12 | } from '@ant-design/icons';
13 |
14 | export const IconMenu = ({ name }) => {
15 | const elements = {
16 | DesktopOutlined: DesktopOutlined,
17 | SettingOutlined: SettingOutlined,
18 | CustomerServiceOutlined: CustomerServiceOutlined,
19 | FileTextOutlined: FileTextOutlined,
20 | FileSyncOutlined: FileSyncOutlined,
21 | DashboardOutlined: DashboardOutlined,
22 | TeamOutlined: TeamOutlined,
23 | UserOutlined: UserOutlined,
24 | CreditCardOutlined: CreditCardOutlined,
25 | BankOutlined: BankOutlined,
26 | Default: DesktopOutlined,
27 | };
28 |
29 | const IconTag = elements[name || 'Default'] || SettingOutlined;
30 | return ;
31 | };
32 |
--------------------------------------------------------------------------------
/frontend/src/components/Loading/index.jsx:
--------------------------------------------------------------------------------
1 | import { Spin } from 'antd';
2 | import { LoadingOutlined } from '@ant-design/icons';
3 |
4 | export default function Loading({ isLoading, children }) {
5 | const antIcon = ;
6 |
7 | return (
8 |
9 | {children}
10 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/frontend/src/components/MoneyInputFormItem/index.jsx:
--------------------------------------------------------------------------------
1 | import { Form, InputNumber } from 'antd';
2 | import { useMoney } from '@/settings';
3 |
4 | export default function MoneyInputFormItem({ updatePrice, value = 0, readOnly = false }) {
5 | const money = useMoney();
6 |
7 | return (
8 |
9 |
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/frontend/src/components/NotFound/index.jsx:
--------------------------------------------------------------------------------
1 | import { Result, Button } from 'antd';
2 | import useLanguage from '@/locale/useLanguage';
3 | import { useNavigate } from 'react-router-dom';
4 |
5 | export default function NotFound({ entity }) {
6 | const translate = useLanguage();
7 |
8 | const navigate = useNavigate();
9 |
10 | return (
11 | {
19 | navigate(`/${entity?.toLowerCase()}`);
20 | }}
21 | >
22 | {translate('Back')}
23 |
24 | }
25 | />
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/frontend/src/components/PageLoader/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Spin } from 'antd';
3 |
4 | const PageLoader = () => {
5 | return (
6 |
7 |
8 |
9 | );
10 | };
11 | export default PageLoader;
12 |
--------------------------------------------------------------------------------
/frontend/src/components/Tag/index.jsx:
--------------------------------------------------------------------------------
1 | import { Tag } from 'antd';
2 | import useLanguage from '@/locale/useLanguage';
3 |
4 | export function StatusTag({ status = 'draft' }) {
5 | const translate = useLanguage();
6 | let color = () => {
7 | return status === 'draft'
8 | ? 'cyan'
9 | : status === 'sent'
10 | ? 'blue'
11 | : status === 'accepted'
12 | ? 'green'
13 | : status === 'expired'
14 | ? 'orange'
15 | : 'red';
16 | };
17 |
18 | return {translate(status)};
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/src/components/Visibility/index.jsx:
--------------------------------------------------------------------------------
1 | export default function Visibility({ isOpen, children }) {
2 | const show = isOpen ? { display: 'block', opacity: 1 } : { display: 'none', opacity: 0 };
3 | return {children}
;
4 | }
5 |
--------------------------------------------------------------------------------
/frontend/src/components/outsideClick.js/demo.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | import Dropdown from './Dropdown';
5 | import './styles.css';
6 |
7 | function App() {
8 | const [vegetagle, setVegetable] = useState(undefined);
9 | const [fruit, setFruit] = useState(undefined);
10 |
11 | return (
12 |
13 |
Hello CodeSandbox
14 | Start editing to see some magic happen!
15 | setVegetable(v)}
19 | options={['Tomato', 'Cucumber', 'Potato']}
20 | />
21 | setFruit(v)}
25 | options={['Apple', 'Banana', 'Orange', 'Mango']}
26 | />
27 |
28 | );
29 | }
30 |
31 | const rootElement = document.getElementById('root');
32 | ReactDOM.render(, rootElement);
33 |
--------------------------------------------------------------------------------
/frontend/src/components/outsideClick.js/index.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState, useRef } from 'react';
2 |
3 | const Dropdown = ({ value, options, placeholder = 'Select', onChange }) => {
4 | const node = useRef();
5 |
6 | const [open, setOpen] = useState(false);
7 |
8 | const handleClick = (e) => {
9 | if (node.current.contains(e.target)) {
10 | // inside click
11 | return;
12 | }
13 | // outside click
14 | setOpen(false);
15 | };
16 |
17 | const handleChange = (selectedValue) => {
18 | onChange(selectedValue);
19 | setOpen(false);
20 | };
21 |
22 | useEffect(() => {
23 | document.addEventListener('mousedown', handleClick);
24 |
25 | return () => {
26 | document.removeEventListener('mousedown', handleClick);
27 | };
28 | }, [open]);
29 |
30 | return (
31 |
32 |
35 | {open && (
36 |
37 | {options.map((opt) => (
38 | - handleChange(opt)}>
39 | {opt}
40 |
41 | ))}
42 |
43 | )}
44 |
45 | );
46 | };
47 |
48 | export default Dropdown;
49 |
--------------------------------------------------------------------------------
/frontend/src/config/defaultInvoiceSettings.js:
--------------------------------------------------------------------------------
1 | export default {
2 | companyName: '',
3 | gstNumber: '',
4 | taxPercent: 18,
5 | taxEnable: 'true',
6 | billableType: 'product',
7 | taxType: 'exc',
8 | companyAddress: '',
9 | note: 'Thank You For Shopping',
10 | currency: 'inr',
11 | currentErpNum: '0001',
12 | };
13 |
--------------------------------------------------------------------------------
/frontend/src/config/serverApiConfig.js:
--------------------------------------------------------------------------------
1 | export const API_BASE_URL =
2 | import.meta.env.PROD || import.meta.env.VITE_DEV_REMOTE == 'remote'
3 | ? import.meta.env.VITE_BACKEND_SERVER + 'api/'
4 | : 'http://localhost:8888/api/';
5 | export const BASE_URL =
6 | import.meta.env.PROD || import.meta.env.VITE_DEV_REMOTE
7 | ? import.meta.env.VITE_BACKEND_SERVER
8 | : 'http://localhost:8888/';
9 | export const DOWNLOAD_BASE_URL =
10 | import.meta.env.PROD || import.meta.env.VITE_DEV_REMOTE
11 | ? import.meta.env.VITE_BACKEND_SERVER + 'download/'
12 | : 'http://localhost:8888/download/';
13 | export const ACCESS_TOKEN_NAME = 'x-auth-token';
14 |
--------------------------------------------------------------------------------
/frontend/src/context/appContext/actions.jsx:
--------------------------------------------------------------------------------
1 | import * as actionTypes from './types';
2 |
3 | const contextActions = (dispatch) => {
4 | return {
5 | navMenu: {
6 | open: () => {
7 | dispatch({ type: actionTypes.OPEN_NAV_MENU });
8 | },
9 | close: () => {
10 | dispatch({ type: actionTypes.CLOSE_NAV_MENU });
11 | },
12 | collapse: () => {
13 | dispatch({ type: actionTypes.COLLAPSE_NAV_MENU });
14 | },
15 | },
16 | };
17 | };
18 |
19 | export default contextActions;
20 |
--------------------------------------------------------------------------------
/frontend/src/context/appContext/index.jsx:
--------------------------------------------------------------------------------
1 | import { useMemo, useReducer, createContext, useContext } from 'react';
2 | import { initialState, contextReducer } from './reducer';
3 | import contextActions from './actions';
4 |
5 | const AppContext = createContext();
6 |
7 | function AppContextProvider({ children }) {
8 | const [state, dispatch] = useReducer(contextReducer, initialState);
9 | const value = useMemo(() => [state, dispatch], [state]);
10 |
11 | return {children};
12 | }
13 |
14 | function useAppContext() {
15 | const context = useContext(AppContext);
16 | if (context === undefined) {
17 | throw new Error('useAppContext must be used within a AppContextProvider');
18 | }
19 | const [state, dispatch] = context;
20 | const appContextAction = contextActions(dispatch);
21 | // const appContextSelector = contextSelectors(state);
22 | return { state, appContextAction };
23 | }
24 |
25 | export { AppContextProvider, useAppContext };
26 |
--------------------------------------------------------------------------------
/frontend/src/context/appContext/reducer.jsx:
--------------------------------------------------------------------------------
1 | import * as actionTypes from './types';
2 |
3 | export const initialState = {
4 | isNavMenuClose: false,
5 | };
6 |
7 | export function contextReducer(state, action) {
8 | switch (action.type) {
9 | case actionTypes.OPEN_NAV_MENU:
10 | return {
11 | isNavMenuClose: false,
12 | };
13 | case actionTypes.CLOSE_NAV_MENU:
14 | return {
15 | isNavMenuClose: true,
16 | };
17 | case actionTypes.COLLAPSE_NAV_MENU:
18 | return {
19 | isNavMenuClose: !state.isNavMenuClose,
20 | };
21 |
22 | default: {
23 | throw new Error(`Unhandled action type: ${action.type}`);
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/frontend/src/context/appContext/types.jsx:
--------------------------------------------------------------------------------
1 | export const OPEN_NAV_MENU = 'OPEN_NAV_MENU';
2 | export const CLOSE_NAV_MENU = 'CLOSE_NAV_MENU';
3 | export const COLLAPSE_NAV_MENU = 'COLLAPSE_NAV_MENU';
4 |
--------------------------------------------------------------------------------
/frontend/src/context/crud/index.jsx:
--------------------------------------------------------------------------------
1 | import { useMemo, useReducer, createContext, useContext } from 'react';
2 | import { initialState, contextReducer } from './reducer';
3 | import contextActions from './actions';
4 | import contextSelectors from './selectors';
5 |
6 | const CrudContext = createContext();
7 |
8 | function CrudContextProvider({ children }) {
9 | const [state, dispatch] = useReducer(contextReducer, initialState);
10 | const value = useMemo(() => [state, dispatch], [state]);
11 |
12 | return {children};
13 | }
14 |
15 | function useCrudContext() {
16 | const context = useContext(CrudContext);
17 | if (context === undefined) {
18 | throw new Error('useCrudContext must be used within a CrudContextProvider');
19 | }
20 | const [state, dispatch] = context;
21 | const crudContextAction = contextActions(dispatch);
22 | const crudContextSelector = contextSelectors(state);
23 | return { state, crudContextAction, crudContextSelector };
24 | }
25 |
26 | export { CrudContextProvider, useCrudContext };
27 |
--------------------------------------------------------------------------------
/frontend/src/context/crud/selectors.jsx:
--------------------------------------------------------------------------------
1 | const contextSelectors = (state) => {
2 | return {
3 | isModalOpen: () => {
4 | return state.isModalOpen;
5 | },
6 | isPanelOpen: () => {
7 | return state.isPanelOpen;
8 | },
9 | isBoxOpen: () => {
10 | return state.isBoxOpen;
11 | },
12 | };
13 | };
14 |
15 | export default contextSelectors;
16 |
--------------------------------------------------------------------------------
/frontend/src/context/crud/types.jsx:
--------------------------------------------------------------------------------
1 | export const OPEN_MODAL = 'OPEN_MODAL';
2 | export const CLOSE_MODAL = 'CLOSE_MODAL';
3 |
4 | export const OPEN_ADVANCED_BOX = 'OPEN_ADVANCED_BOX';
5 | export const CLOSE_ADVANCED_BOX = 'CLOSE_ADVANCED_BOX';
6 |
7 | export const OPEN_EDIT_BOX = 'OPEN_EDIT_BOX';
8 | export const CLOSE_EDIT_BOX = 'CLOSE_EDIT_BOX';
9 |
10 | export const OPEN_PANEL = 'OPEN_PANEL';
11 | export const CLOSE_PANEL = 'CLOSE_PANEL';
12 | export const COLLAPSE_PANEL = 'COLLAPSE_PANEL';
13 |
14 | export const OPEN_BOX = 'OPEN_BOX';
15 | export const CLOSE_BOX = 'CLOSE_BOX';
16 | export const COLLAPSE_BOX = 'COLLAPSE_BOX';
17 |
18 | export const OPEN_READ_BOX = 'OPEN_READ_BOX';
19 | export const CLOSE_READ_BOX = 'CLOSE_READ_BOX';
20 | export const COLLAPSE_READ_BOX = 'COLLAPSE_READ_BOX';
21 |
--------------------------------------------------------------------------------
/frontend/src/context/erp/actions.jsx:
--------------------------------------------------------------------------------
1 | import * as actionTypes from './types';
2 |
3 | const contextActions = (dispatch) => {
4 | return {
5 | modal: {
6 | open: () => {
7 | dispatch({ type: actionTypes.OPEN_MODAL });
8 | },
9 | close: () => {
10 | dispatch({ type: actionTypes.CLOSE_MODAL });
11 | },
12 | },
13 | readPanel: {
14 | open: () => {
15 | dispatch({ type: actionTypes.OPEN_PANEL, keyState: 'read' });
16 | },
17 | close: () => {
18 | dispatch({ type: actionTypes.CLOSE_PANEL });
19 | },
20 | },
21 | updatePanel: {
22 | open: () => {
23 | dispatch({ type: actionTypes.OPEN_PANEL, keyState: 'update' });
24 | },
25 | close: () => {
26 | dispatch({ type: actionTypes.CLOSE_PANEL });
27 | },
28 | },
29 | createPanel: {
30 | open: () => {
31 | dispatch({ type: actionTypes.OPEN_PANEL, keyState: 'create' });
32 | },
33 | close: () => {
34 | dispatch({ type: actionTypes.CLOSE_PANEL });
35 | },
36 | },
37 | recordPanel: {
38 | open: () => {
39 | dispatch({
40 | type: actionTypes.OPEN_PANEL,
41 | keyState: 'recordPayment',
42 | });
43 | },
44 | close: () => {
45 | dispatch({ type: actionTypes.CLOSE_PANEL });
46 | },
47 | },
48 | };
49 | };
50 |
51 | export default contextActions;
52 |
--------------------------------------------------------------------------------
/frontend/src/context/erp/index.jsx:
--------------------------------------------------------------------------------
1 | import { useMemo, useReducer, createContext, useContext } from 'react';
2 | import { initialState, contextReducer } from './reducer';
3 | import contextActions from './actions';
4 | import contextSelectors from './selectors';
5 |
6 | const ErpContext = createContext();
7 |
8 | function ErpContextProvider({ children }) {
9 | const [state, dispatch] = useReducer(contextReducer, initialState);
10 | const value = useMemo(() => [state, dispatch], [state]);
11 |
12 | return {children};
13 | }
14 |
15 | function useErpContext() {
16 | const context = useContext(ErpContext);
17 | if (context === undefined) {
18 | throw new Error('useErpContext must be used within a ErpContextProvider');
19 | }
20 | const [state, dispatch] = context;
21 | const erpContextAction = contextActions(dispatch);
22 | const erpContextSelector = contextSelectors(state);
23 | return { state, erpContextAction, erpContextSelector };
24 | }
25 |
26 | export { ErpContextProvider, useErpContext };
27 |
--------------------------------------------------------------------------------
/frontend/src/context/erp/reducer.jsx:
--------------------------------------------------------------------------------
1 | import * as actionTypes from './types';
2 |
3 | export const initialState = {
4 | create: {
5 | isOpen: false,
6 | },
7 | update: {
8 | isOpen: false,
9 | },
10 | read: {
11 | isOpen: false,
12 | },
13 | recordPayment: {
14 | isOpen: false,
15 | },
16 | deleteModal: {
17 | isOpen: false,
18 | },
19 | dataTableList: {
20 | isOpen: true,
21 | },
22 | last: null,
23 | };
24 |
25 | export function contextReducer(state, action) {
26 | const { keyState = null } = action;
27 | switch (action.type) {
28 | case actionTypes.OPEN_MODAL:
29 | return {
30 | ...state,
31 | deleteModal: { isOpen: true },
32 | };
33 | case actionTypes.CLOSE_MODAL:
34 | return {
35 | ...state,
36 | deleteModal: { isOpen: false },
37 | };
38 | case actionTypes.OPEN_PANEL:
39 | return {
40 | ...initialState,
41 | dataTableList: {
42 | isOpen: false,
43 | },
44 | [keyState]: { isOpen: true },
45 | };
46 | case actionTypes.CLOSE_PANEL:
47 | return {
48 | ...initialState,
49 | };
50 |
51 | default: {
52 | throw new Error(`Unhandled action type: ${action.type}`);
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/frontend/src/context/erp/selectors.jsx:
--------------------------------------------------------------------------------
1 | const contextSelectors = (state) => {
2 | return {
3 | isModalOpen: () => {
4 | return state.isModalOpen;
5 | },
6 | isPanelOpen: () => {
7 | return state.isPanelOpen;
8 | },
9 | isBoxOpen: () => {
10 | return state.isBoxOpen;
11 | },
12 | };
13 | };
14 |
15 | export default contextSelectors;
16 |
--------------------------------------------------------------------------------
/frontend/src/context/erp/types.jsx:
--------------------------------------------------------------------------------
1 | export const OPEN_MODAL = 'OPEN_MODAL';
2 | export const CLOSE_MODAL = 'CLOSE_MODAL';
3 |
4 | export const OPEN_PANEL = 'OPEN_PANEL';
5 | export const CLOSE_PANEL = 'CLOSE_PANEL';
6 | export const COLLAPSE_PANEL = 'COLLAPSE_PANEL';
7 |
--------------------------------------------------------------------------------
/frontend/src/context/profileContext/actions.jsx:
--------------------------------------------------------------------------------
1 | import * as actionTypes from './types';
2 |
3 | const contextActions = (dispatch) => {
4 | return {
5 | modal: {
6 | open: () => {
7 | dispatch({ type: actionTypes.OPEN_MODAL });
8 | },
9 | close: () => {
10 | dispatch({ type: actionTypes.CLOSE_MODAL });
11 | },
12 | },
13 | updatePanel: {
14 | open: () => {
15 | dispatch({ type: actionTypes.OPEN_PANEL, keyState: 'update' });
16 | },
17 | close: () => {
18 | dispatch({ type: actionTypes.CLOSE_PANEL });
19 | },
20 | },
21 | };
22 | };
23 |
24 | export default contextActions;
25 |
--------------------------------------------------------------------------------
/frontend/src/context/profileContext/index.jsx:
--------------------------------------------------------------------------------
1 | import { useMemo, useReducer, createContext, useContext } from 'react';
2 | import { initialState, contextReducer } from './reducer';
3 | import contextActions from './actions';
4 | import contextSelectors from './selectors';
5 |
6 | const ProfileContext = createContext();
7 |
8 | function ProfileContextProvider({ children }) {
9 | const [state, dispatch] = useReducer(contextReducer, initialState);
10 | const value = useMemo(() => [state, dispatch], [state]);
11 |
12 | return {children};
13 | }
14 |
15 | function useProfileContext() {
16 | const context = useContext(ProfileContext);
17 | if (context === undefined) {
18 | throw new Error('useProfileContext must be used within a ProfileContextProvider');
19 | }
20 | const [state, dispatch] = context;
21 | const profileContextAction = contextActions(dispatch);
22 | const profileContextSelector = contextSelectors(state);
23 | return { state, profileContextAction, profileContextSelector };
24 | }
25 |
26 | export { ProfileContextProvider, useProfileContext };
27 |
--------------------------------------------------------------------------------
/frontend/src/context/profileContext/reducer.jsx:
--------------------------------------------------------------------------------
1 | import * as actionTypes from './types';
2 |
3 | export const initialState = {
4 | read: {
5 | isOpen: true,
6 | },
7 | update: {
8 | isOpen: false,
9 | },
10 | passwordModal: {
11 | isOpen: false,
12 | },
13 | };
14 |
15 | export function contextReducer(state, action) {
16 | const { keyState = null } = action;
17 | switch (action.type) {
18 | case actionTypes.OPEN_MODAL:
19 | return {
20 | ...state,
21 | passwordModal: { isOpen: true },
22 | };
23 | case actionTypes.CLOSE_MODAL:
24 | return {
25 | ...state,
26 | passwordModal: { isOpen: false },
27 | };
28 | case actionTypes.OPEN_PANEL:
29 | return {
30 | ...initialState,
31 | read: {
32 | isOpen: false,
33 | },
34 | [keyState]: { isOpen: true },
35 | };
36 | case actionTypes.CLOSE_PANEL:
37 | return {
38 | ...initialState,
39 | };
40 |
41 | default: {
42 | throw new Error(`Unhandled action type: ${action.type}`);
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/frontend/src/context/profileContext/selectors.jsx:
--------------------------------------------------------------------------------
1 | const contextSelectors = (state) => {
2 | return {
3 | isModalOpen: () => {
4 | return state.isModalOpen;
5 | },
6 | isPanelOpen: () => {
7 | return state.isPanelOpen;
8 | },
9 | };
10 | };
11 |
12 | export default contextSelectors;
13 |
--------------------------------------------------------------------------------
/frontend/src/context/profileContext/types.jsx:
--------------------------------------------------------------------------------
1 | export const OPEN_MODAL = 'OPEN_PASSWORD_MODAL';
2 | export const CLOSE_MODAL = 'CLOSE_PASSWORD_MODAL';
3 |
4 | export const OPEN_PANEL = 'OPEN_PROFILE_PANEL';
5 | export const CLOSE_PANEL = 'CLOSE_PROFILE_PANEL';
6 |
--------------------------------------------------------------------------------
/frontend/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-ninja11/nextGPT/248aea2c8e9f5b95a69529e0c3a1dd51bdd39d50/frontend/src/favicon.ico
--------------------------------------------------------------------------------
/frontend/src/forms/DynamicForm/index.jsx:
--------------------------------------------------------------------------------
1 | import { Input, Form, Checkbox, Select, InputNumber } from 'antd';
2 | import { DatePicker } from 'antd';
3 | // mapping of our components
4 | const componentMapping = {
5 | input: Input,
6 | number: InputNumber,
7 | password: Input.Password,
8 | checkbox: Checkbox,
9 | };
10 |
11 | function DynamicForm({ fields }) {
12 | return (
13 | <>
14 | {fields.map((fieldElement) => (
15 |
16 | ))}
17 | >
18 | );
19 | }
20 |
21 | function FormElement({
22 | fieldType,
23 | label,
24 | name,
25 | isMultiSelect = false,
26 | selectOptions = [],
27 | required = false,
28 | fieldProps = {},
29 | message = 'Field is required!',
30 | }) {
31 | // dinamically select a component from componentMapping object
32 | const { Option } = Select;
33 | const Component = componentMapping[fieldType];
34 |
35 | return (
36 |
37 | if (fieldType === "select")
38 | {
39 |
46 | }
47 | else if(fieldType === "date"){}
48 | else {}
49 |
50 | );
51 | }
52 |
53 | export default DynamicForm;
54 |
--------------------------------------------------------------------------------
/frontend/src/forms/InventoryForm.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Form, Input, InputNumber } from 'antd';
3 |
4 | export default function InventoryForm() {
5 | // Renamed to InventoryForm for clarity
6 | return (
7 | <>
8 |
18 |
19 |
20 |
21 |
33 |
34 |
35 |
36 |
48 | `$ ${value}`} // Optional: format value as currency
50 | parser={(value) => value.replace(/\$\s?|(,*)/g, '')} // Optional: parse input as number
51 | />
52 |
53 | >
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/frontend/src/forms/UpdateEmail.jsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-ninja11/nextGPT/248aea2c8e9f5b95a69529e0c3a1dd51bdd39d50/frontend/src/forms/UpdateEmail.jsx
--------------------------------------------------------------------------------
/frontend/src/hooks/useDebounce.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import useTimeoutFn from './useTimeoutFn';
3 |
4 | export default function useDebounce(fn, ms = 0, deps = []) {
5 | const [isReady, cancel, reset] = useTimeoutFn(fn, ms);
6 |
7 | useEffect(reset, deps);
8 |
9 | return [isReady, cancel];
10 | }
11 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useFetch.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | function useFetchData(fetchFunction) {
4 | const [data, setData] = useState(null);
5 | const [isLoading, setLoading] = useState(true);
6 | const [isSuccess, setSuccess] = useState(false);
7 | const [error, setError] = useState(null);
8 |
9 | useEffect(() => {
10 | async function fetchData() {
11 | try {
12 | const data = await fetchFunction();
13 | setData(data.result);
14 | setSuccess(true);
15 | } catch (error) {
16 | setError(error);
17 | } finally {
18 | setLoading(false);
19 | }
20 | }
21 |
22 | fetchData();
23 | }, [isLoading]);
24 |
25 | return { data, isLoading, isSuccess, error };
26 | }
27 |
28 | export default function useFetch(fetchFunction) {
29 | const { data, isLoading, isSuccess, error } = useFetchData(fetchFunction);
30 |
31 | return { result: data, isLoading, isSuccess, error };
32 | }
33 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useIsMobile.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 |
3 | export default function useIsMobile(width = 768) {
4 | const [isMobile, setIsMobile] = useState(false);
5 |
6 | useEffect(() => {
7 | function handleResize() {
8 | setIsMobile(window.innerWidth < width); // Adjust the breakpoint as per your requirements
9 | }
10 |
11 | handleResize(); // Initial check
12 |
13 | window.addEventListener('resize', handleResize);
14 |
15 | return () => {
16 | window.removeEventListener('resize', handleResize);
17 | };
18 | }, [width]);
19 |
20 | return isMobile;
21 | }
22 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useMail.jsx:
--------------------------------------------------------------------------------
1 | import { erp } from '@/redux/erp/actions';
2 | import { useDispatch } from 'react-redux';
3 |
4 | export default function useMail({ entity }) {
5 | const dispatch = useDispatch();
6 |
7 | const send = (id) => {
8 | const jsonData = { id };
9 | dispatch(erp.mail({ entity, jsonData }));
10 | };
11 |
12 | return { send };
13 | }
14 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useNetwork.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | export default function useNetwork() {
4 | const [isOnline, setNetwork] = useState(window.navigator.onLine);
5 | useEffect(() => {
6 | setNetwork(window.navigator.onLine);
7 | }, [window.navigator.onLine]);
8 | return [isOnline];
9 | }
10 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useOnFetch.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | export default function useOnFetch() {
4 | const [result, setResult] = useState(null);
5 | const [isSuccess, setIsSuccess] = useState(false);
6 | const [isLoading, setIsLoading] = useState(false);
7 |
8 | let onFetch = async (fetchingFn) => {
9 | setIsLoading(true);
10 |
11 | const data = await fetchingFn();
12 | setResult(data.result);
13 | if (data.success === true) {
14 | setIsSuccess(true);
15 | } else {
16 | setIsSuccess(false);
17 | }
18 |
19 | setIsLoading(false);
20 | };
21 |
22 | return { onFetch, result, isSuccess, isLoading };
23 | }
24 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useTimeoutFn.jsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useRef } from 'react';
2 |
3 | export default function useTimeoutFn(fn, ms = 0) {
4 | const ready = useRef(false);
5 | const timeout = useRef();
6 | const callback = useRef(fn);
7 |
8 | const isReady = useCallback(() => ready.current, []);
9 |
10 | const set = useCallback(() => {
11 | ready.current = false;
12 | timeout.current && clearTimeout(timeout.current);
13 |
14 | timeout.current = setTimeout(() => {
15 | ready.current = true;
16 | callback.current();
17 | }, ms);
18 | }, [ms]);
19 |
20 | const clear = useCallback(() => {
21 | ready.current = null;
22 | timeout.current && clearTimeout(timeout.current);
23 | }, []);
24 |
25 | // update ref when function changes
26 | useEffect(() => {
27 | callback.current = fn;
28 | }, [fn]);
29 |
30 | // set on mount, clear on unmount
31 | useEffect(() => {
32 | set();
33 |
34 | return clear;
35 | }, [ms]);
36 |
37 | return [isReady, clear, set];
38 | }
39 |
--------------------------------------------------------------------------------
/frontend/src/layout/AuthLayout/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Layout, Row, Col } from 'antd';
3 | export default function AuthLayout({ sideContent, children }) {
4 | return (
5 |
6 |
7 |
16 | {sideContent}
17 |
18 |
25 | {children}
26 |
27 |
28 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/frontend/src/layout/DashboardLayout/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Layout } from 'antd';
4 |
5 | const { Content } = Layout;
6 |
7 | export default function DashboardLayout({ children }) {
8 | return (
9 |
14 | {children}
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/frontend/src/layout/DefaultLayout/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { CrudContextProvider } from '@/context/crud';
4 |
5 | function DefaultLayout({ children }) {
6 | return {children};
7 | }
8 |
9 | export default DefaultLayout;
10 |
--------------------------------------------------------------------------------
/frontend/src/layout/ErpLayout/index.jsx:
--------------------------------------------------------------------------------
1 | import { ErpContextProvider } from '@/context/erp';
2 |
3 | import { Layout } from 'antd';
4 |
5 | const { Content } = Layout;
6 |
7 | export default function ErpLayout({ children }) {
8 | return (
9 |
10 |
19 | {children}
20 |
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/frontend/src/layout/Footer/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Layout } from 'antd';
3 |
4 | const { Footer } = Layout;
5 |
6 | const FooterContent = () => (
7 |
8 | );
9 |
10 | export default FooterContent;
11 |
--------------------------------------------------------------------------------
/frontend/src/layout/ProfileLayout/index.jsx:
--------------------------------------------------------------------------------
1 | import { ProfileContextProvider } from '@/context/profileContext';
2 | import React from 'react';
3 |
4 | const ProfileLayout = ({ children }) => {
5 | return {children};
6 | };
7 |
8 | export default ProfileLayout;
9 |
--------------------------------------------------------------------------------
/frontend/src/layout/index.jsx:
--------------------------------------------------------------------------------
1 | export { default as CrudLayout } from './CrudLayout';
2 | export { default as ErpLayout } from './ErpLayout';
3 | export { default as DefaultLayout } from './DefaultLayout';
4 | export { default as DashboardLayout } from './DashboardLayout';
5 | export { default as SettingsLayout } from './SettingsLayout';
6 |
--------------------------------------------------------------------------------
/frontend/src/locale/Localization.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 |
3 | import { ConfigProvider } from 'antd';
4 |
5 | import { useSelector } from 'react-redux';
6 |
7 | import { selectLangState } from '@/redux/translate/selectors';
8 |
9 | import antdLocale from './antdLocale';
10 |
11 | export default function Localization({ children }) {
12 | const { langCode, langDirection } = useSelector(selectLangState);
13 |
14 | const [locale, setLocal] = useState();
15 | const [direction, setDirection] = useState();
16 |
17 | useEffect(() => {
18 | const lang = antdLocale[langCode];
19 | setDirection(langDirection);
20 | setLocal(lang);
21 | }, [langCode]);
22 |
23 | return (
24 |
25 | {children}
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/frontend/src/main.jsx:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client';
2 |
3 | import RootApp from './RootApp';
4 |
5 | const container = document.getElementById('root');
6 | const root = createRoot(container);
7 | root.render();
8 |
--------------------------------------------------------------------------------
/frontend/src/modules/AdminCrudModule/AdminDataTable.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { EyeOutlined } from '@ant-design/icons';
4 | import DataTable from '@/components/DataTable/DataTable';
5 |
6 | import useLanguage from '@/locale/useLanguage';
7 |
8 | export default function AdminCrudModule({ config }) {
9 | const translate = useLanguage();
10 |
11 | return (
12 | ,
19 | },
20 | ]}
21 | />
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/frontend/src/modules/EmailModule/EmailDataTableModule/index.jsx:
--------------------------------------------------------------------------------
1 | import { ErpLayout } from '@/layout';
2 | import ErpPanel from '@/modules/ErpPanelModule';
3 |
4 | export default function EmailDataTableModule({ config }) {
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/frontend/src/modules/EmailModule/ReadEmailModule/index.jsx:
--------------------------------------------------------------------------------
1 | import NotFound from '@/components/NotFound';
2 | import { ErpLayout } from '@/layout';
3 | import ReadItem from './components/ReadItem';
4 |
5 | import PageLoader from '@/components/PageLoader';
6 | import { erp } from '@/redux/erp/actions';
7 | import { selectReadItem } from '@/redux/erp/selectors';
8 | import { useLayoutEffect } from 'react';
9 | import { useDispatch, useSelector } from 'react-redux';
10 |
11 | import { useParams } from 'react-router-dom';
12 |
13 | export default function ReadEmailModule({ config }) {
14 | const dispatch = useDispatch();
15 | const { id } = useParams();
16 |
17 | useLayoutEffect(() => {
18 | dispatch(erp.read({ entity: config.entity, id }));
19 | }, [id]);
20 |
21 | const { result: currentResult, isSuccess, isLoading = true } = useSelector(selectReadItem);
22 |
23 | if (isLoading) {
24 | return (
25 |
26 |
27 |
28 | );
29 | } else
30 | return (
31 |
32 | {isSuccess ? (
33 |
34 | ) : (
35 |
36 | )}
37 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/frontend/src/modules/EmailModule/UpdateEmailModule/index.jsx:
--------------------------------------------------------------------------------
1 | import NotFound from '@/components/NotFound';
2 |
3 | import { ErpLayout } from '@/layout';
4 | import UpdateItem from '@/modules/ErpPanelModule/UpdateItem';
5 | import EmailForm from './componenets/EmailForm';
6 |
7 | import PageLoader from '@/components/PageLoader';
8 |
9 | import { erp } from '@/redux/erp/actions';
10 | import { selectReadItem } from '@/redux/erp/selectors';
11 | import { useLayoutEffect } from 'react';
12 | import { useDispatch, useSelector } from 'react-redux';
13 |
14 | import { useParams } from 'react-router-dom';
15 |
16 | export default function UpdateEmailModule({ config }) {
17 | const dispatch = useDispatch();
18 |
19 | const { id } = useParams();
20 |
21 | useLayoutEffect(() => {
22 | dispatch(erp.read({ entity: config.entity, id }));
23 | }, [id]);
24 |
25 | const { result: currentResult, isSuccess, isLoading = true } = useSelector(selectReadItem);
26 |
27 | useLayoutEffect(() => {
28 | if (currentResult) {
29 | dispatch(erp.currentAction({ actionType: 'update', data: currentResult }));
30 | }
31 | }, [currentResult]);
32 |
33 | if (isLoading) {
34 | return (
35 |
36 |
37 |
38 | );
39 | } else
40 | return (
41 |
42 | {isSuccess ? (
43 |
44 | ) : (
45 |
46 | )}
47 |
48 | );
49 | }
50 |
--------------------------------------------------------------------------------
/frontend/src/modules/ErpPanelModule/index.jsx:
--------------------------------------------------------------------------------
1 | import { useLayoutEffect } from 'react';
2 |
3 | import DataTable from './DataTable';
4 |
5 | import Delete from './DeleteItem';
6 |
7 | import { useDispatch } from 'react-redux';
8 | import { erp } from '@/redux/erp/actions';
9 |
10 | import { useErpContext } from '@/context/erp';
11 |
12 | export default function ErpPanel({ config, extra }) {
13 | const dispatch = useDispatch();
14 | const { state } = useErpContext();
15 | const { deleteModal } = state;
16 |
17 | const dispatcher = () => {
18 | dispatch(erp.resetState());
19 | };
20 |
21 | useLayoutEffect(() => {
22 | const controller = new AbortController();
23 | dispatcher();
24 | return () => {
25 | controller.abort();
26 | };
27 | }, []);
28 |
29 | return (
30 | <>
31 |
32 |
33 | >
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/frontend/src/modules/InvoiceModule/CreateInvoiceModule/index.jsx:
--------------------------------------------------------------------------------
1 | import { ErpLayout } from '@/layout';
2 | import CreateItem from '@/modules/ErpPanelModule/CreateItem';
3 | import InvoiceForm from '@/modules/InvoiceModule/Forms/InvoiceForm';
4 |
5 | export default function CreateInvoiceModule({ config }) {
6 | return (
7 |
8 |
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/frontend/src/modules/InvoiceModule/InvoiceDataTableModule/index.jsx:
--------------------------------------------------------------------------------
1 | import { ErpLayout } from '@/layout';
2 | import ErpPanel from '@/modules/ErpPanelModule';
3 | import useLanguage from '@/locale/useLanguage';
4 | import { CreditCardOutlined } from '@ant-design/icons';
5 |
6 | export default function InvoiceDataTableModule({ config }) {
7 | const translate = useLanguage();
8 | return (
9 |
10 | ,
17 | },
18 | ]}
19 | >
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/frontend/src/modules/InvoiceModule/ReadInvoiceModule/index.jsx:
--------------------------------------------------------------------------------
1 | import NotFound from '@/components/NotFound';
2 | import { ErpLayout } from '@/layout';
3 | import ReadItem from '@/modules/ErpPanelModule/ReadItem';
4 |
5 | import PageLoader from '@/components/PageLoader';
6 | import { erp } from '@/redux/erp/actions';
7 | import { selectReadItem } from '@/redux/erp/selectors';
8 | import { useLayoutEffect } from 'react';
9 | import { useDispatch, useSelector } from 'react-redux';
10 |
11 | import { useParams } from 'react-router-dom';
12 |
13 | export default function ReadInvoiceModule({ config }) {
14 | const dispatch = useDispatch();
15 | const { id } = useParams();
16 |
17 | useLayoutEffect(() => {
18 | dispatch(erp.read({ entity: config.entity, id }));
19 | }, [id]);
20 |
21 | const { result: currentResult, isSuccess, isLoading = true } = useSelector(selectReadItem);
22 |
23 | if (isLoading) {
24 | return (
25 |
26 |
27 |
28 | );
29 | } else
30 | return (
31 |
32 | {isSuccess ? (
33 |
34 | ) : (
35 |
36 | )}
37 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/frontend/src/modules/InvoiceModule/RecordPaymentModule/index.jsx:
--------------------------------------------------------------------------------
1 | import { ErpLayout } from '@/layout';
2 |
3 | import PageLoader from '@/components/PageLoader';
4 | import { erp } from '@/redux/erp/actions';
5 | import { selectItemById, selectCurrentItem, selectRecordPaymentItem } from '@/redux/erp/selectors';
6 | import { useEffect } from 'react';
7 | import { useDispatch, useSelector } from 'react-redux';
8 | import { useParams } from 'react-router-dom';
9 | import Payment from './components/Payment';
10 |
11 | export default function RecordPaymentModule({ config }) {
12 | const dispatch = useDispatch();
13 | const { id } = useParams();
14 |
15 | let item = useSelector(selectItemById(id));
16 |
17 | useEffect(() => {
18 | if (item) {
19 | dispatch(erp.currentItem({ data: item }));
20 | } else {
21 | dispatch(erp.read({ entity: config.entity, id }));
22 | }
23 | }, [item, id]);
24 |
25 | const { result: currentResult } = useSelector(selectCurrentItem);
26 | item = currentResult;
27 |
28 | useEffect(() => {
29 | dispatch(erp.currentAction({ actionType: 'recordPayment', data: item }));
30 | }, [item]);
31 |
32 | return (
33 |
34 | {item ? : }
35 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/frontend/src/modules/InvoiceModule/UpdateInvoiceModule/index.jsx:
--------------------------------------------------------------------------------
1 | import NotFound from '@/components/NotFound';
2 |
3 | import { ErpLayout } from '@/layout';
4 | import UpdateItem from '@/modules/ErpPanelModule/UpdateItem';
5 | import InvoiceForm from '@/modules/InvoiceModule/Forms/InvoiceForm';
6 |
7 | import PageLoader from '@/components/PageLoader';
8 |
9 | import { erp } from '@/redux/erp/actions';
10 |
11 | import { selectReadItem } from '@/redux/erp/selectors';
12 | import { useLayoutEffect } from 'react';
13 | import { useDispatch, useSelector } from 'react-redux';
14 | import { useParams } from 'react-router-dom';
15 |
16 | export default function UpdateInvoiceModule({ config }) {
17 | const dispatch = useDispatch();
18 |
19 | const { id } = useParams();
20 |
21 | useLayoutEffect(() => {
22 | dispatch(erp.read({ entity: config.entity, id }));
23 | }, [id]);
24 |
25 | const { result: currentResult, isSuccess, isLoading = true } = useSelector(selectReadItem);
26 |
27 | useLayoutEffect(() => {
28 | if (currentResult) {
29 | dispatch(erp.currentAction({ actionType: 'update', data: currentResult }));
30 | }
31 | }, [currentResult]);
32 |
33 | if (isLoading) {
34 | return (
35 |
36 |
37 |
38 | );
39 | } else
40 | return (
41 |
42 | {isSuccess ? (
43 |
44 | ) : (
45 |
46 | )}
47 |
48 | );
49 | }
50 |
--------------------------------------------------------------------------------
/frontend/src/modules/OfferModule/CreateOfferModule/index.jsx:
--------------------------------------------------------------------------------
1 | import { ErpLayout } from '@/layout';
2 | import CreateItem from '@/modules/ErpPanelModule/CreateItem';
3 | import OfferForm from '@/modules/OfferModule/Forms/OfferForm';
4 |
5 | export default function CreateOfferModule({ config }) {
6 | return (
7 |
8 |
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/frontend/src/modules/OfferModule/OfferDataTableModule/index.jsx:
--------------------------------------------------------------------------------
1 | import { ErpLayout } from '@/layout';
2 | import ErpPanel from '@/modules/ErpPanelModule';
3 |
4 | export default function OffereDataTableModule({ config }) {
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/frontend/src/modules/OfferModule/ReadOfferModule/index.jsx:
--------------------------------------------------------------------------------
1 | import NotFound from '@/components/NotFound';
2 | import { ErpLayout } from '@/layout';
3 | import ReadOfferItem from './ReadOfferItem';
4 |
5 | import PageLoader from '@/components/PageLoader';
6 | import { erp } from '@/redux/erp/actions';
7 | import useLanguage from '@/locale/useLanguage';
8 | import { selectReadItem } from '@/redux/erp/selectors';
9 | import { useLayoutEffect } from 'react';
10 | import { useDispatch, useSelector } from 'react-redux';
11 | import { useParams, useNavigate } from 'react-router-dom';
12 |
13 | export default function ReadOfferModule({ config }) {
14 | const dispatch = useDispatch();
15 | const { id } = useParams();
16 | const navigate = useNavigate();
17 |
18 | useLayoutEffect(() => {
19 | dispatch(erp.read({ entity: config.entity, id }));
20 | }, [id]);
21 |
22 | const { result: currentResult, isSuccess, isLoading = true } = useSelector(selectReadItem);
23 |
24 | if (isLoading) {
25 | return (
26 |
27 |
28 |
29 | );
30 | } else
31 | return (
32 |
33 | {isSuccess ? (
34 |
35 | ) : (
36 |
37 | )}
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/frontend/src/modules/PaymentModule/PaymentDataTableModule/index.jsx:
--------------------------------------------------------------------------------
1 | import { ErpLayout } from '@/layout';
2 | import ErpPanel from '@/modules/ErpPanelModule';
3 |
4 | export default function PaymentDataTableModule({ config }) {
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/frontend/src/modules/PaymentModule/ReadPaymentModule/index.jsx:
--------------------------------------------------------------------------------
1 | import { ErpLayout } from '@/layout';
2 | import ReadItem from './components/ReadItem';
3 |
4 | import PageLoader from '@/components/PageLoader';
5 | import { erp } from '@/redux/erp/actions';
6 | import { selectItemById, selectCurrentItem } from '@/redux/erp/selectors';
7 | import { useEffect } from 'react';
8 | import { useDispatch, useSelector } from 'react-redux';
9 | import { useParams } from 'react-router-dom';
10 |
11 | export default function ReadPaymentModule({ config }) {
12 | const dispatch = useDispatch();
13 |
14 | const { id } = useParams();
15 | let item = useSelector(selectItemById(id));
16 |
17 | useEffect(() => {
18 | if (item) {
19 | dispatch(erp.currentItem({ data: item }));
20 | } else {
21 | dispatch(erp.read({ entity: config.entity, id }));
22 | }
23 | }, [item]);
24 |
25 | const { result: currentResult } = useSelector(selectCurrentItem);
26 |
27 | item = currentResult;
28 | return (
29 |
30 | {item ? : }
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/frontend/src/modules/PaymentModule/UpdatePaymentModule/index.jsx:
--------------------------------------------------------------------------------
1 | import { ErpLayout } from '@/layout';
2 |
3 | import PageLoader from '@/components/PageLoader';
4 | import { erp } from '@/redux/erp/actions';
5 | import { selectItemById } from '@/redux/erp/selectors';
6 | import { useEffect, useLayoutEffect } from 'react';
7 | import { useDispatch, useSelector } from 'react-redux';
8 | import { useParams, useNavigate } from 'react-router-dom';
9 | import Payment from './components/Payment';
10 | import { selectReadItem } from '@/redux/erp/selectors';
11 |
12 | export default function UpdatePaymentModule({ config }) {
13 | const dispatch = useDispatch();
14 | const navigate = useNavigate();
15 | const { id } = useParams();
16 |
17 | let item = useSelector(selectItemById(id));
18 |
19 | useLayoutEffect(() => {
20 | dispatch(erp.read({ entity: config.entity, id }));
21 | }, [item, id]);
22 |
23 | const { result: currentResult } = useSelector(selectReadItem);
24 |
25 | item = item ? item : currentResult;
26 |
27 | useLayoutEffect(() => {
28 | dispatch(erp.currentAction({ actionType: 'update', id, data: item }));
29 | }, []);
30 |
31 | return (
32 | {item ? : }
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/frontend/src/modules/ProfileModule/components/Profile.jsx:
--------------------------------------------------------------------------------
1 | import { useProfileContext } from '@/context/profileContext';
2 | import AdminInfo from './AdminInfo';
3 | import UpdateAdmin from './UpdateAdmin';
4 | import PasswordModal from './PasswordModal';
5 |
6 | const Visibility = ({ isOpen, children }) => {
7 | const show = isOpen ? { display: 'block', opacity: 1 } : { display: 'none', opacity: 0 };
8 | return {children}
;
9 | };
10 |
11 | export default function Profile({ config }) {
12 | const { state } = useProfileContext();
13 | const { update, read, passwordModal } = state;
14 |
15 | return (
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/frontend/src/modules/ProfileModule/components/UploadImg.jsx:
--------------------------------------------------------------------------------
1 | import { UploadOutlined } from '@ant-design/icons';
2 | import { message, Upload, Form, Button } from 'antd';
3 | import useLanguage from '@/locale/useLanguage';
4 |
5 | // import photo from '@/style/images/photo.png';
6 |
7 | const beforeUpload = (file) => {
8 | const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
9 | if (!isJpgOrPng) {
10 | message.error('You can only upload JPG/PNG file!');
11 | }
12 | const isLt2M = file.size / 1024 / 1024 < 2;
13 | if (!isLt2M) {
14 | message.error('Image must smaller than 2MB!');
15 | }
16 | return isJpgOrPng && isLt2M;
17 | };
18 | export default function UploadImg() {
19 | const translate = useLanguage();
20 | return (
21 | e.fileList}
26 | >
27 |
28 | }>Click to Upload
29 |
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/frontend/src/modules/ProfileModule/index.jsx:
--------------------------------------------------------------------------------
1 | import Profile from './components/Profile';
2 | import ProfileLayout from '@/layout/ProfileLayout';
3 | import { Layout } from 'antd';
4 | import { Content } from 'antd/lib/layout/layout';
5 |
6 | export default function ProfileModule({ config }) {
7 | return (
8 |
9 |
10 |
19 |
20 |
21 |
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/frontend/src/modules/QuoteModule/CreateQuoteModule/index.jsx:
--------------------------------------------------------------------------------
1 | import { ErpLayout } from '@/layout';
2 | import CreateItem from '@/modules/ErpPanelModule/CreateItem';
3 | import QuoteForm from '@/modules/QuoteModule/Forms/QuoteForm';
4 |
5 | export default function CreateQuoteModule({ config }) {
6 | return (
7 |
8 |
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/frontend/src/modules/QuoteModule/QuoteDataTableModule/index.jsx:
--------------------------------------------------------------------------------
1 | import { ErpLayout } from '@/layout';
2 | import ErpPanel from '@/modules/ErpPanelModule';
3 |
4 | export default function QuoteDataTableModule({ config }) {
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/frontend/src/modules/QuoteModule/ReadQuoteModule/index.jsx:
--------------------------------------------------------------------------------
1 | import NotFound from '@/components/NotFound';
2 | import { ErpLayout } from '@/layout';
3 | import ReadItem from '@/modules/ErpPanelModule/ReadItem';
4 |
5 | import PageLoader from '@/components/PageLoader';
6 | import { erp } from '@/redux/erp/actions';
7 |
8 | import { selectReadItem } from '@/redux/erp/selectors';
9 | import { useLayoutEffect } from 'react';
10 | import { useDispatch, useSelector } from 'react-redux';
11 | import { useParams } from 'react-router-dom';
12 |
13 | export default function ReadQuoteModule({ config }) {
14 | const dispatch = useDispatch();
15 | const { id } = useParams();
16 |
17 | useLayoutEffect(() => {
18 | dispatch(erp.read({ entity: config.entity, id }));
19 | }, [id]);
20 |
21 | const { result: currentResult, isSuccess, isLoading = true } = useSelector(selectReadItem);
22 |
23 | if (isLoading) {
24 | return (
25 |
26 |
27 |
28 | );
29 | } else
30 | return (
31 |
32 | {isSuccess ? (
33 |
34 | ) : (
35 |
36 | )}
37 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/frontend/src/modules/SettingModule/CompanyLogoSettingsModule/forms/AppSettingForm.jsx:
--------------------------------------------------------------------------------
1 | import { Button, Form, message, Upload } from 'antd';
2 |
3 | import { UploadOutlined } from '@ant-design/icons';
4 |
5 | export default function AppSettingForm() {
6 | const beforeUpload = (file) => {
7 | const isJpgOrPng =
8 | file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/svg+xml';
9 | if (!isJpgOrPng) {
10 | message.error('You can only upload JPG/PNG or SVG file!');
11 | }
12 | const isLt2M = file.size / 1024 / 1024 < 5;
13 | if (!isLt2M) {
14 | message.error('Image must smaller than 5MB!');
15 | }
16 | return false;
17 | };
18 | return (
19 | <>
20 | e.fileList}
25 | >
26 |
32 | }>Click to Upload
33 |
34 |
35 | >
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/frontend/src/modules/SettingModule/CompanyLogoSettingsModule/index.jsx:
--------------------------------------------------------------------------------
1 | import SetingsSection from '../components/SetingsSection';
2 | import UpdateSettingModule from '../components/UpdateSettingModule';
3 | import AppSettingForm from './forms/AppSettingForm';
4 |
5 | import useLanguage from '@/locale/useLanguage';
6 |
7 | export default function CompanyLogoSettingsModule({ config }) {
8 | const translate = useLanguage();
9 | return (
10 |
11 |
15 |
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/src/modules/SettingModule/FinanceSettingsModule/index.jsx:
--------------------------------------------------------------------------------
1 | import SetingsSection from '../components/SetingsSection';
2 | import UpdateSettingModule from '../components/UpdateSettingModule';
3 | import MoneyFormSettingForm from './SettingsForm';
4 | import useLanguage from '@/locale/useLanguage';
5 |
6 | export default function MoneyFormatSettingsModule({ config }) {
7 | const translate = useLanguage();
8 | return (
9 |
10 |
14 |
15 |
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/frontend/src/modules/SettingModule/GeneralSettingsModule/index.jsx:
--------------------------------------------------------------------------------
1 | import SetingsSection from '../components/SetingsSection';
2 | import UpdateSettingModule from '../components/UpdateSettingModule';
3 | import GeneralSettingForm from './forms/GeneralSettingForm';
4 | import useLanguage from '@/locale/useLanguage';
5 |
6 | export default function GeneralSettingsModule({ config }) {
7 | const translate = useLanguage();
8 | return (
9 |
10 |
14 |
15 |
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/frontend/src/modules/SettingModule/MoneyFormatSettingsModule/index.jsx:
--------------------------------------------------------------------------------
1 | import SetingsSection from '../components/SetingsSection';
2 | import UpdateSettingModule from '../components/UpdateSettingModule';
3 | import MoneyFormSettingForm from './SettingsForm';
4 | import useLanguage from '@/locale/useLanguage';
5 |
6 | export default function MoneyFormatSettingsModule({ config }) {
7 | const translate = useLanguage();
8 | return (
9 |
10 |
14 |
15 |
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/frontend/src/modules/SettingModule/components/SetingsSection.jsx:
--------------------------------------------------------------------------------
1 | import { Col, Divider, Row, Typography } from 'antd';
2 |
3 | const { Title, Text } = Typography;
4 |
5 | export default function SetingsSection({ title, description, children }) {
6 | return (
7 |
8 |
9 | {title}
10 | {description}
11 |
12 |
13 |
20 | {children}
21 |
22 |
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/frontend/src/modules/SettingModule/components/UpdateSettingModule.jsx:
--------------------------------------------------------------------------------
1 | // import { generate as uniqueId } from 'shortid';
2 | // import { SyncOutlined } from '@ant-design/icons';
3 | import { Divider } from 'antd';
4 | import { PageHeader } from '@ant-design/pro-layout';
5 | import UpdateSettingForm from './UpdateSettingForm';
6 |
7 | export default function UpdateSettingModule({
8 | config,
9 | children,
10 | withUpload = false,
11 | uploadSettingKey = null,
12 | }) {
13 | return (
14 | <>
15 | }>
20 | // Update
21 | // ,
22 | // ]}
23 | style={{
24 | padding: '20px 0px',
25 | }}
26 | >
27 |
28 |
29 |
34 | {children}
35 |
36 | >
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/frontend/src/pages/Dashboard.jsx:
--------------------------------------------------------------------------------
1 | import DashboardModule from '@/modules/DashboardModule';
2 | export default function Dashboard() {
3 | return ;
4 | }
5 |
--------------------------------------------------------------------------------
/frontend/src/pages/Email/EmailRead.jsx:
--------------------------------------------------------------------------------
1 | import useLanguage from '@/locale/useLanguage';
2 | import ReadEmailModule from '@/modules/EmailModule/ReadEmailModule';
3 |
4 | export default function EmailRead() {
5 | const entity = 'email';
6 | const translate = useLanguage();
7 |
8 | const Labels = {
9 | PANEL_TITLE: translate('email_template'),
10 | DATATABLE_TITLE: translate('email_template_list'),
11 | ADD_NEW_ENTITY: translate('add_new_email_template'),
12 | ENTITY_NAME: translate('email_template'),
13 | CREATE_ENTITY: translate('save'),
14 | UPDATE_ENTITY: translate('update'),
15 | };
16 |
17 | const configPage = {
18 | entity,
19 | create: false,
20 | ...Labels,
21 | };
22 | return ;
23 | }
24 |
--------------------------------------------------------------------------------
/frontend/src/pages/Email/EmailUpdate.jsx:
--------------------------------------------------------------------------------
1 | import useLanguage from '@/locale/useLanguage';
2 | import UpdateEmailModule from '@/modules/EmailModule/UpdateEmailModule';
3 |
4 | export default function EmailUpdate() {
5 | const entity = 'email';
6 | const translate = useLanguage();
7 |
8 | const Labels = {
9 | PANEL_TITLE: translate('email_template'),
10 | DATATABLE_TITLE: translate('email_template_list'),
11 | ADD_NEW_ENTITY: translate('add_new_email_template'),
12 | ENTITY_NAME: translate('email_template'),
13 | CREATE_ENTITY: translate('save'),
14 | UPDATE_ENTITY: translate('update'),
15 | };
16 |
17 | const configPage = {
18 | entity,
19 | create: false,
20 | ...Labels,
21 | };
22 |
23 | return ;
24 | }
25 |
--------------------------------------------------------------------------------
/frontend/src/pages/Invoice/InvoiceCreate.jsx:
--------------------------------------------------------------------------------
1 | import useLanguage from '@/locale/useLanguage';
2 | import CreateInvoiceModule from '@/modules/InvoiceModule/CreateInvoiceModule';
3 |
4 | export default function InvoiceCreate() {
5 | const entity = 'invoice';
6 | const translate = useLanguage();
7 | const Labels = {
8 | PANEL_TITLE: translate('invoice'),
9 | DATATABLE_TITLE: translate('invoice_list'),
10 | ADD_NEW_ENTITY: translate('add_new_invoice'),
11 | ENTITY_NAME: translate('invoice'),
12 | CREATE_ENTITY: translate('save'),
13 | UPDATE_ENTITY: translate('update'),
14 | RECORD_ENTITY: translate('record_payment'),
15 | };
16 |
17 | const configPage = {
18 | entity,
19 | ...Labels,
20 | };
21 | return ;
22 | }
23 |
--------------------------------------------------------------------------------
/frontend/src/pages/Invoice/InvoiceRead.jsx:
--------------------------------------------------------------------------------
1 | import useLanguage from '@/locale/useLanguage';
2 | import ReadInvoiceModule from '@/modules/InvoiceModule/ReadInvoiceModule';
3 |
4 | export default function InvoiceRead() {
5 | const entity = 'invoice';
6 | const translate = useLanguage();
7 | const Labels = {
8 | PANEL_TITLE: translate('invoice'),
9 | DATATABLE_TITLE: translate('invoice_list'),
10 | ADD_NEW_ENTITY: translate('add_new_invoice'),
11 | ENTITY_NAME: translate('invoice'),
12 | CREATE_ENTITY: translate('save'),
13 | UPDATE_ENTITY: translate('update'),
14 | RECORD_ENTITY: translate('record_payment'),
15 | };
16 |
17 | const configPage = {
18 | entity,
19 | ...Labels,
20 | };
21 | return ;
22 | }
23 |
--------------------------------------------------------------------------------
/frontend/src/pages/Invoice/InvoiceRecordPayment.jsx:
--------------------------------------------------------------------------------
1 | import useLanguage from '@/locale/useLanguage';
2 | import RecordPaymentModule from '@/modules/InvoiceModule/RecordPaymentModule';
3 |
4 | export default function InvoiceRecord() {
5 | const entity = 'invoice';
6 | const translate = useLanguage();
7 | const Labels = {
8 | PANEL_TITLE: translate('invoice'),
9 | DATATABLE_TITLE: translate('invoice_list'),
10 | ADD_NEW_ENTITY: translate('add_new_invoice'),
11 | ENTITY_NAME: translate('invoice'),
12 | CREATE_ENTITY: translate('save'),
13 | UPDATE_ENTITY: translate('update'),
14 | RECORD_ENTITY: translate('record_payment'),
15 | };
16 |
17 | const configPage = {
18 | entity,
19 | ...Labels,
20 | };
21 | return ;
22 | }
23 |
--------------------------------------------------------------------------------
/frontend/src/pages/Invoice/InvoiceUpdate.jsx:
--------------------------------------------------------------------------------
1 | import useLanguage from '@/locale/useLanguage';
2 | import UpdateInvoiceModule from '@/modules/InvoiceModule/UpdateInvoiceModule';
3 |
4 | export default function InvoiceUpdate() {
5 | const entity = 'invoice';
6 | const translate = useLanguage();
7 | const Labels = {
8 | PANEL_TITLE: translate('invoice'),
9 | DATATABLE_TITLE: translate('invoice_list'),
10 | ADD_NEW_ENTITY: translate('add_new_invoice'),
11 | ENTITY_NAME: translate('invoice'),
12 | CREATE_ENTITY: translate('save'),
13 | UPDATE_ENTITY: translate('update'),
14 | RECORD_ENTITY: translate('record_payment'),
15 | };
16 |
17 | const configPage = {
18 | entity,
19 | ...Labels,
20 | };
21 | return ;
22 | }
23 |
--------------------------------------------------------------------------------
/frontend/src/pages/Logout.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useNavigate } from 'react-router-dom';
3 | import { useDispatch } from 'react-redux';
4 | import { logout as logoutAction } from '@/redux/auth/actions';
5 | import PageLoader from '@/components/PageLoader';
6 |
7 | const Logout = () => {
8 | const dispatch = useDispatch();
9 | const navigate = useNavigate();
10 | function asyncLogout() {
11 | dispatch(logoutAction());
12 | }
13 | useEffect(() => {
14 | asyncLogout();
15 | window.localStorage.removeItem('isLoggedIn');
16 | window.localStorage.removeItem('auth');
17 | navigate('/login');
18 | }, []);
19 |
20 | return ;
21 | };
22 | export default Logout;
23 |
--------------------------------------------------------------------------------
/frontend/src/pages/NotFound.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useNavigate } from 'react-router-dom';
3 |
4 | import NotFound from '@/components/NotFound';
5 |
6 | const NotFoundPage = () => {
7 | let navigate = useNavigate();
8 | useEffect(() => {
9 | navigate(`notfound`, { replace: true });
10 | }, []);
11 | return ;
12 | };
13 | export default NotFoundPage;
14 |
--------------------------------------------------------------------------------
/frontend/src/pages/Offer/OfferCreate.jsx:
--------------------------------------------------------------------------------
1 | import useLanguage from '@/locale/useLanguage';
2 | import CreateOfferModule from '@/modules/OfferModule/CreateOfferModule';
3 |
4 | export default function OfferCreate() {
5 | const translate = useLanguage();
6 |
7 | const entity = 'offer';
8 | const Labels = {
9 | PANEL_TITLE: translate('offer'),
10 | DATATABLE_TITLE: translate('offer_list'),
11 | ADD_NEW_ENTITY: translate('add_new_offer'),
12 | ENTITY_NAME: translate('offer'),
13 | CREATE_ENTITY: translate('save'),
14 | UPDATE_ENTITY: translate('update'),
15 | };
16 |
17 | const configPage = {
18 | entity,
19 | ...Labels,
20 | };
21 | return ;
22 | }
23 |
--------------------------------------------------------------------------------
/frontend/src/pages/Offer/OfferRead.jsx:
--------------------------------------------------------------------------------
1 | import useLanguage from '@/locale/useLanguage';
2 | import ReadOfferModule from '@/modules/OfferModule/ReadOfferModule';
3 |
4 | export default function OfferRead() {
5 | const translate = useLanguage();
6 |
7 | const entity = 'offer';
8 | const Labels = {
9 | PANEL_TITLE: translate('offer'),
10 | DATATABLE_TITLE: translate('offer_list'),
11 | ADD_NEW_ENTITY: translate('add_new_offer'),
12 | ENTITY_NAME: translate('offer'),
13 | CREATE_ENTITY: translate('save'),
14 | UPDATE_ENTITY: translate('update'),
15 | };
16 |
17 | const configPage = {
18 | entity,
19 | ...Labels,
20 | };
21 | return ;
22 | }
23 |
--------------------------------------------------------------------------------
/frontend/src/pages/Offer/OfferUpdate.jsx:
--------------------------------------------------------------------------------
1 | import useLanguage from '@/locale/useLanguage';
2 | import UpdateOfferModule from '@/modules/OfferModule/UpdateOfferModule';
3 |
4 | export default function OfferUpdate() {
5 | const translate = useLanguage();
6 |
7 | const entity = 'offer';
8 | const Labels = {
9 | PANEL_TITLE: translate('offer'),
10 | DATATABLE_TITLE: translate('offer_list'),
11 | ADD_NEW_ENTITY: translate('add_new_offer'),
12 | ENTITY_NAME: translate('offer'),
13 | CREATE_ENTITY: translate('save'),
14 | UPDATE_ENTITY: translate('update'),
15 | };
16 |
17 | const configPage = {
18 | entity,
19 | ...Labels,
20 | };
21 | return ;
22 | }
23 |
--------------------------------------------------------------------------------
/frontend/src/pages/Payment/PaymentRead.jsx:
--------------------------------------------------------------------------------
1 | import useLanguage from '@/locale/useLanguage';
2 | import ReadPaymentModule from '@/modules/PaymentModule/ReadPaymentModule';
3 |
4 | export default function PaymentRead() {
5 | const translate = useLanguage();
6 |
7 | const entity = 'payment';
8 |
9 | const Labels = {
10 | PANEL_TITLE: translate('payment'),
11 | DATATABLE_TITLE: translate('payment_list'),
12 | ADD_NEW_ENTITY: translate('add_new_payment'),
13 | ENTITY_NAME: translate('payment'),
14 | CREATE_ENTITY: translate('save'),
15 | UPDATE_ENTITY: translate('update'),
16 | };
17 |
18 | const configPage = {
19 | entity,
20 | ...Labels,
21 | };
22 | return ;
23 | }
24 |
--------------------------------------------------------------------------------
/frontend/src/pages/Payment/PaymentUpdate.jsx:
--------------------------------------------------------------------------------
1 | import useLanguage from '@/locale/useLanguage';
2 | import UpdatePaymentModule from '@/modules/PaymentModule/UpdatePaymentModule';
3 |
4 | export default function PaymentUpdate() {
5 | const translate = useLanguage();
6 |
7 | const entity = 'payment';
8 |
9 | const Labels = {
10 | PANEL_TITLE: translate('payment'),
11 | DATATABLE_TITLE: translate('payment_list'),
12 | ADD_NEW_ENTITY: translate('add_new_payment'),
13 | ENTITY_NAME: translate('payment'),
14 | CREATE_ENTITY: translate('save'),
15 | UPDATE_ENTITY: translate('update'),
16 | };
17 |
18 | const configPage = {
19 | entity,
20 | ...Labels,
21 | };
22 | return ;
23 | }
24 |
--------------------------------------------------------------------------------
/frontend/src/pages/Profile.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ProfileModule from '@/modules/ProfileModule';
3 |
4 | import useLanguage from '@/locale/useLanguage';
5 |
6 | export default function Profile() {
7 | const entity = 'profile';
8 | const translate = useLanguage();
9 |
10 | const Labels = {
11 | PANEL_TITLE: translate('profile'),
12 | ENTITY_NAME: translate('profile'),
13 | CREATE_ENTITY: translate('save'),
14 | UPDATE_ENTITY: translate('update'),
15 | };
16 |
17 | const config = {
18 | entity,
19 | ...Labels,
20 | };
21 | return ;
22 | }
23 |
--------------------------------------------------------------------------------
/frontend/src/pages/Quote/QuoteCreate.jsx:
--------------------------------------------------------------------------------
1 | import useLanguage from '@/locale/useLanguage';
2 | import CreateQuoteModule from '@/modules/QuoteModule/CreateQuoteModule';
3 |
4 | export default function QuoteCreate() {
5 | const translate = useLanguage();
6 |
7 | const entity = 'quote';
8 |
9 | const Labels = {
10 | PANEL_TITLE: translate('quote'),
11 | DATATABLE_TITLE: translate('quote_list'),
12 | ADD_NEW_ENTITY: translate('add_new_quote'),
13 | ENTITY_NAME: translate('quote'),
14 | CREATE_ENTITY: translate('save'),
15 | UPDATE_ENTITY: translate('update'),
16 | };
17 |
18 | const configPage = {
19 | entity,
20 | ...Labels,
21 | };
22 | return ;
23 | }
24 |
--------------------------------------------------------------------------------
/frontend/src/pages/Quote/QuoteRead.jsx:
--------------------------------------------------------------------------------
1 | import useLanguage from '@/locale/useLanguage';
2 | import ReadQuoteModule from '@/modules/QuoteModule/ReadQuoteModule';
3 |
4 | export default function QuoteRead() {
5 | const translate = useLanguage();
6 |
7 | const entity = 'quote';
8 |
9 | const Labels = {
10 | PANEL_TITLE: translate('quote'),
11 | DATATABLE_TITLE: translate('quote_list'),
12 | ADD_NEW_ENTITY: translate('add_new_quote'),
13 | ENTITY_NAME: translate('quote'),
14 | CREATE_ENTITY: translate('save'),
15 | UPDATE_ENTITY: translate('update'),
16 | };
17 |
18 | const configPage = {
19 | entity,
20 | ...Labels,
21 | };
22 | return ;
23 | }
24 |
--------------------------------------------------------------------------------
/frontend/src/pages/Quote/QuoteUpdate.jsx:
--------------------------------------------------------------------------------
1 | import useLanguage from '@/locale/useLanguage';
2 | import UpdateQuoteModule from '@/modules/QuoteModule/UpdateQuoteModule';
3 |
4 | export default function QuoteUpdate() {
5 | const translate = useLanguage();
6 |
7 | const entity = 'quote';
8 |
9 | const Labels = {
10 | PANEL_TITLE: translate('quote'),
11 | DATATABLE_TITLE: translate('quote_list'),
12 | ADD_NEW_ENTITY: translate('add_new_quote'),
13 | ENTITY_NAME: translate('quote'),
14 | CREATE_ENTITY: translate('save'),
15 | UPDATE_ENTITY: translate('update'),
16 | };
17 |
18 | const configPage = {
19 | entity,
20 | ...Labels,
21 | };
22 | return ;
23 | }
24 |
--------------------------------------------------------------------------------
/frontend/src/pages/Settings/CompanyLogoSettings.jsx:
--------------------------------------------------------------------------------
1 | import useLanguage from '@/locale/useLanguage';
2 |
3 | import CompanyLogoSettingsModule from '@/modules/SettingModule/CompanyLogoSettingsModule';
4 |
5 | export default function AppSettings() {
6 | const translate = useLanguage();
7 |
8 | const entity = 'setting';
9 |
10 | const Labels = {
11 | PANEL_TITLE: translate('settings'),
12 | DATATABLE_TITLE: translate('settings_list'),
13 | ADD_NEW_ENTITY: translate('add_new_settings'),
14 | ENTITY_NAME: translate('settings'),
15 | CREATE_ENTITY: translate('save'),
16 | UPDATE_ENTITY: translate('update'),
17 | SETTINGS_TITLE: translate('General Settings'),
18 | };
19 |
20 | const configPage = {
21 | entity,
22 | settingsCategory: 'app_settings',
23 | ...Labels,
24 | };
25 |
26 | return ;
27 | }
28 |
--------------------------------------------------------------------------------
/frontend/src/pages/Settings/GeneralSettings.jsx:
--------------------------------------------------------------------------------
1 | import useLanguage from '@/locale/useLanguage';
2 |
3 | import GeneralSettingsModule from '@/modules/SettingModule/GeneralSettingsModule';
4 |
5 | export default function GeneralSettings() {
6 | const translate = useLanguage();
7 |
8 | const entity = 'setting';
9 |
10 | const Labels = {
11 | PANEL_TITLE: translate('settings'),
12 | DATATABLE_TITLE: translate('settings_list'),
13 | ADD_NEW_ENTITY: translate('add_new_settings'),
14 | ENTITY_NAME: translate('settings'),
15 | CREATE_ENTITY: translate('save'),
16 | UPDATE_ENTITY: translate('update'),
17 | SETTINGS_TITLE: translate('General Settings'),
18 | };
19 |
20 | const configPage = {
21 | entity,
22 | settingsCategory: 'app_settings',
23 | ...Labels,
24 | };
25 | return ;
26 | }
27 |
--------------------------------------------------------------------------------
/frontend/src/pages/Settings/InvoiceSettings.jsx:
--------------------------------------------------------------------------------
1 | import useLanguage from '@/locale/useLanguage';
2 |
3 | import GeneralSettingsModule from '@/modules/SettingModule/GeneralSettingsModule';
4 |
5 | export default function GeneralSettings() {
6 | const translate = useLanguage();
7 |
8 | const entity = 'setting';
9 |
10 | const Labels = {
11 | PANEL_TITLE: translate('settings'),
12 | DATATABLE_TITLE: translate('settings_list'),
13 | ADD_NEW_ENTITY: translate('add_new_settings'),
14 | ENTITY_NAME: translate('settings'),
15 | CREATE_ENTITY: translate('save'),
16 | UPDATE_ENTITY: translate('update'),
17 | SETTINGS_TITLE: translate('General Settings'),
18 | };
19 |
20 | const configPage = {
21 | entity,
22 | settingsCategory: 'app_settings',
23 | ...Labels,
24 | };
25 | return ;
26 | }
27 |
--------------------------------------------------------------------------------
/frontend/src/pages/Settings/MoneyFormatSettings.jsx:
--------------------------------------------------------------------------------
1 | import useLanguage from '@/locale/useLanguage';
2 |
3 | import MoneyFormatSettingsModule from '@/modules/SettingModule/MoneyFormatSettingsModule';
4 |
5 | export default function MoneyFormatSettings() {
6 | const translate = useLanguage();
7 |
8 | const entity = 'setting';
9 |
10 | const Labels = {
11 | PANEL_TITLE: translate('settings'),
12 | DATATABLE_TITLE: translate('settings_list'),
13 | ADD_NEW_ENTITY: translate('add_new_settings'),
14 | ENTITY_NAME: translate('settings'),
15 | CREATE_ENTITY: translate('save'),
16 | UPDATE_ENTITY: translate('update'),
17 | SETTINGS_TITLE: translate('Money Format Settings'),
18 | };
19 |
20 | const configPage = {
21 | entity,
22 | settingsCategory: 'money_format_settings',
23 | ...Labels,
24 | };
25 | return ;
26 | }
27 |
--------------------------------------------------------------------------------
/frontend/src/pages/Settings/PaymentSettings.jsx:
--------------------------------------------------------------------------------
1 | import useLanguage from '@/locale/useLanguage';
2 |
3 | import GeneralSettingsModule from '@/modules/SettingModule/GeneralSettingsModule';
4 |
5 | export default function GeneralSettings() {
6 | const translate = useLanguage();
7 |
8 | const entity = 'setting';
9 |
10 | const Labels = {
11 | PANEL_TITLE: translate('settings'),
12 | DATATABLE_TITLE: translate('settings_list'),
13 | ADD_NEW_ENTITY: translate('add_new_settings'),
14 | ENTITY_NAME: translate('settings'),
15 | CREATE_ENTITY: translate('save'),
16 | UPDATE_ENTITY: translate('update'),
17 | SETTINGS_TITLE: translate('Money Format Settings'),
18 | };
19 |
20 | const configPage = {
21 | entity,
22 | settingsCategory: 'finance_settings',
23 | ...Labels,
24 | };
25 | return ;
26 | }
27 |
--------------------------------------------------------------------------------
/frontend/src/pages/Settings/Settings.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | SettingOutlined,
3 | FileTextOutlined,
4 | CreditCardOutlined,
5 | DollarOutlined,
6 | FileImageOutlined,
7 | } from '@ant-design/icons';
8 |
9 | import TabsContent from '@/components/TabsContent/TabsContent';
10 |
11 | import CompanyLogoSettings from './CompanyLogoSettings';
12 | import GeneralSettings from './GeneralSettings';
13 | import PaymentSettings from './PaymentSettings';
14 | import InvoiceSettings from './InvoiceSettings';
15 | import MoneyFormatSettings from './MoneyFormatSettings';
16 |
17 | import useLanguage from '@/locale/useLanguage';
18 |
19 | export default function Settings() {
20 | const translate = useLanguage();
21 | const content = [
22 | {
23 | label: translate('General Settings'),
24 | icon: ,
25 | children: ,
26 | },
27 | {
28 | label: translate('Company Logo'),
29 | icon: ,
30 | children: ,
31 | },
32 | {
33 | label: translate('Currency Settings'),
34 | icon: ,
35 | children: ,
36 | },
37 | {
38 | label: translate('Finance Settings'),
39 | icon: ,
40 | children: ,
41 | },
42 | {
43 | label: translate('Crm Settings'),
44 | icon: ,
45 | children: ,
46 | },
47 | ];
48 |
49 | const pageTitle = translate('Settings');
50 |
51 | return ;
52 | }
53 |
--------------------------------------------------------------------------------
/frontend/src/redux/auth/index.js:
--------------------------------------------------------------------------------
1 | export { default as reducer } from './reducer';
2 |
--------------------------------------------------------------------------------
/frontend/src/redux/auth/reducer.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from './types';
2 |
3 | const INITIAL_STATE = {
4 | current: {},
5 | isLoggedIn: false,
6 | isLoading: false,
7 | isSuccess: false,
8 | };
9 |
10 | const authReducer = (state = INITIAL_STATE, action) => {
11 | switch (action.type) {
12 | case actionTypes.REQUEST_LOADING:
13 | return {
14 | ...state,
15 | isLoading: true,
16 | };
17 | case actionTypes.REQUEST_FAILED:
18 | return {
19 | ...state,
20 | isLoading: false,
21 | isSuccess: false,
22 | };
23 |
24 | case actionTypes.REQUEST_SUCCESS:
25 | return {
26 | current: action.payload,
27 | isLoading: false,
28 | isLoggedIn: true,
29 | isSuccess: true,
30 | };
31 | case actionTypes.LOGOUT_SUCCESS:
32 | return INITIAL_STATE;
33 |
34 | default:
35 | return state;
36 | }
37 | };
38 |
39 | export default authReducer;
40 |
--------------------------------------------------------------------------------
/frontend/src/redux/auth/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 | const authSelect = (state) => state.auth;
3 |
4 | export const selectAuth = createSelector([authSelect], (auth) => auth);
5 | export const selectCurrentAdmin = createSelector([authSelect], (auth) => auth.current);
6 |
7 | export const isLoggedIn = createSelector([authSelect], (auth) => auth.isLoggedIn);
8 |
--------------------------------------------------------------------------------
/frontend/src/redux/auth/types.js:
--------------------------------------------------------------------------------
1 | export const FAILED_REQUEST = 'AUTH_FAILED_REQUEST';
2 | export const LOADING_REQUEST = 'AUTH_LOADING_REQUEST';
3 |
4 | export const LOGIN_SUCCESS = 'AUTH_LOGIN_SUCCESS';
5 |
6 | export const LOGOUT_SUCCESS = 'AUTH_LOGOUT_SUCCESS';
7 |
8 | export const RESET_STATE = 'AUTH_RESET_STATE';
9 |
10 | export const REQUEST_LOADING = 'AUTH_REQUEST_LOADING';
11 | export const REQUEST_SUCCESS = 'AUTH_REQUEST_SUCCESS';
12 | export const REQUEST_FAILED = 'AUTH_REQUEST_FAILED';
13 |
--------------------------------------------------------------------------------
/frontend/src/redux/crud/index.js:
--------------------------------------------------------------------------------
1 | export { default as reducer } from './reducer';
2 |
--------------------------------------------------------------------------------
/frontend/src/redux/crud/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 |
3 | const selectCrud = (state) => state.crud;
4 |
5 | export const selectCurrentItem = createSelector([selectCrud], (crud) => crud.current);
6 |
7 | export const selectListItems = createSelector([selectCrud], (crud) => crud.list);
8 | export const selectItemById = (itemId) =>
9 | createSelector(selectListItems, (list) => list.result.items.find((item) => item._id === itemId));
10 |
11 | export const selectCreatedItem = createSelector([selectCrud], (crud) => crud.create);
12 |
13 | export const selectUpdatedItem = createSelector([selectCrud], (crud) => crud.update);
14 |
15 | export const selectReadItem = createSelector([selectCrud], (crud) => crud.read);
16 |
17 | export const selectDeletedItem = createSelector([selectCrud], (crud) => crud.delete);
18 |
19 | export const selectSearchedItems = createSelector([selectCrud], (crud) => crud.search);
20 |
--------------------------------------------------------------------------------
/frontend/src/redux/crud/types.js:
--------------------------------------------------------------------------------
1 | export const RESET_STATE = 'CRUD_RESET_STATE';
2 | export const CURRENT_ITEM = 'CRUD_CURRENT_ITEM';
3 |
4 | export const REQUEST_LOADING = 'CRUD_REQUEST_LOADING';
5 | export const REQUEST_SUCCESS = 'CRUD_REQUEST_SUCCESS';
6 | export const REQUEST_FAILED = 'CRUD_REQUEST_FAILED';
7 |
8 | export const CURRENT_ACTION = 'CRUD_CURRENT_ACTION';
9 | export const RESET_ACTION = 'CRUD_RESET_ACTION';
10 |
--------------------------------------------------------------------------------
/frontend/src/redux/erp/index.js:
--------------------------------------------------------------------------------
1 | export { default as reducer } from './reducer';
2 |
--------------------------------------------------------------------------------
/frontend/src/redux/erp/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 |
3 | const selectErp = (state) => state.erp;
4 |
5 | export const selectCurrentItem = createSelector([selectErp], (erp) => erp.current);
6 |
7 | export const selectListItems = createSelector([selectErp], (erp) => erp.list);
8 | export const selectItemById = (itemId) =>
9 | createSelector(selectListItems, (list) => list.result.items.find((item) => item._id === itemId));
10 |
11 | export const selectCreatedItem = createSelector([selectErp], (erp) => erp.create);
12 |
13 | export const selectUpdatedItem = createSelector([selectErp], (erp) => erp.update);
14 |
15 | export const selectRecordPaymentItem = createSelector([selectErp], (erp) => erp.recordPayment);
16 |
17 | export const selectReadItem = createSelector([selectErp], (erp) => erp.read);
18 |
19 | export const selectDeletedItem = createSelector([selectErp], (erp) => erp.delete);
20 |
21 | export const selectSearchedItems = createSelector([selectErp], (erp) => erp.search);
22 |
--------------------------------------------------------------------------------
/frontend/src/redux/erp/types.js:
--------------------------------------------------------------------------------
1 | export const RESET_STATE = 'ERP_RESET_STATE';
2 | export const CURRENT_ITEM = 'ERP_CURRENT_ITEM';
3 |
4 | export const REQUEST_LOADING = 'ERP_REQUEST_LOADING';
5 | export const REQUEST_SUCCESS = 'ERP_REQUEST_SUCCESS';
6 | export const REQUEST_FAILED = 'ERP_REQUEST_FAILED';
7 |
8 | export const CURRENT_ACTION = 'ERP_CURRENT_ACTION';
9 | export const RESET_ACTION = 'ERP_RESET_ACTION';
10 |
--------------------------------------------------------------------------------
/frontend/src/redux/rootReducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import { reducer as authReducer } from './auth';
4 | import { reducer as crudReducer } from './crud';
5 | import { reducer as erpReducer } from './erp';
6 | import { reducer as settingsReducer } from './settings';
7 | import { reducer as translateReducer } from './translate';
8 |
9 | import * as actionTypes from './auth/types';
10 |
11 | // Combine all reducers.
12 |
13 | const appReducer = combineReducers({
14 | auth: authReducer,
15 | crud: crudReducer,
16 | erp: erpReducer,
17 | settings: settingsReducer,
18 | translate: translateReducer,
19 | });
20 |
21 | const rootReducer = (state, action) => {
22 | if (action.type === actionTypes.LOGOUT_SUCCESS) {
23 | state = undefined;
24 | }
25 | return appReducer(state, action);
26 | };
27 |
28 | export default rootReducer;
29 |
--------------------------------------------------------------------------------
/frontend/src/redux/settings/index.js:
--------------------------------------------------------------------------------
1 | export { default as reducer } from './reducer';
2 |
--------------------------------------------------------------------------------
/frontend/src/redux/settings/reducer.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from './types';
2 |
3 | const INITIAL_SETTINGS_STATE = {
4 | crm_settings: {},
5 | finance_settings: {},
6 | company_settings: {},
7 | app_settings: {},
8 | money_format_settings: {
9 | currency: 'USD',
10 | currency_symbol: '$',
11 | currency_position: 'before',
12 | decimal_sep: '.',
13 | thousand_sep: ',',
14 | cent_precision: 2,
15 | zero_format: false,
16 | },
17 | };
18 |
19 | const INITIAL_STATE = {
20 | result: INITIAL_SETTINGS_STATE,
21 | isLoading: false,
22 | isSuccess: false,
23 | };
24 |
25 | const settingsReducer = (state = INITIAL_STATE, action) => {
26 | const { payload = null } = action;
27 | switch (action.type) {
28 | case actionTypes.RESET_STATE:
29 | return INITIAL_STATE;
30 | case actionTypes.REQUEST_LOADING:
31 | return {
32 | ...state,
33 | isLoading: true,
34 | };
35 | case actionTypes.REQUEST_FAILED:
36 | return {
37 | ...state,
38 | isLoading: false,
39 | isSuccess: false,
40 | };
41 |
42 | case actionTypes.REQUEST_SUCCESS:
43 | return {
44 | result: payload,
45 | isLoading: false,
46 | isSuccess: true,
47 | };
48 | default:
49 | return state;
50 | }
51 | };
52 |
53 | export default settingsReducer;
54 |
--------------------------------------------------------------------------------
/frontend/src/redux/settings/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 |
3 | export const selectSettings = (state) => state.settings;
4 |
5 | export const selectCurrentSettings = createSelector(
6 | [selectSettings],
7 | (settings) => settings.result
8 | );
9 |
10 | export const selectMoneyFormat = createSelector(
11 | [selectCurrentSettings],
12 | (settings) => settings.money_format_settings
13 | );
14 |
15 | export const selectAppSettings = createSelector(
16 | [selectCurrentSettings],
17 | (settings) => settings.app_settings
18 | );
19 |
20 | export const selectFinanceSettings = createSelector(
21 | [selectCurrentSettings],
22 | (settings) => settings.finance_settings
23 | );
24 |
25 | export const selectCrmSettings = createSelector(
26 | [selectCurrentSettings],
27 | (settings) => settings.crm_settings
28 | );
29 |
30 | export const selectCompanySettings = createSelector(
31 | [selectCurrentSettings],
32 | (settings) => settings.company_settings
33 | );
34 |
--------------------------------------------------------------------------------
/frontend/src/redux/settings/types.js:
--------------------------------------------------------------------------------
1 | export const RESET_STATE = 'SETTINGS_RESET_STATE';
2 |
3 | export const REQUEST_LOADING = 'SETTINGS_REQUEST_LOADING';
4 | export const REQUEST_SUCCESS = 'SETTINGS_REQUEST_SUCCESS';
5 | export const REQUEST_FAILED = 'SETTINGS_REQUEST_FAILED';
6 |
--------------------------------------------------------------------------------
/frontend/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from 'redux';
2 | import thunk from 'redux-thunk';
3 |
4 | import lang from '@/locale/translation/en_us';
5 |
6 | import rootReducer from './rootReducer';
7 | import storePersist from './storePersist';
8 |
9 | let middleware = [thunk];
10 |
11 | let configStore = applyMiddleware(...middleware);
12 |
13 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
14 |
15 | configStore = composeEnhancers(applyMiddleware(...middleware));
16 |
17 | const INITIAL_LANG_STATE = {
18 | ...lang,
19 | };
20 |
21 | const LANG_INITIAL_STATE = {
22 | result: INITIAL_LANG_STATE,
23 | langCode: 'en_us',
24 | isLoading: false,
25 | isSuccess: false,
26 | };
27 |
28 | const lang_state = storePersist.get('translate')
29 | ? storePersist.get('translate')
30 | : LANG_INITIAL_STATE;
31 |
32 | const AUTH_INITIAL_STATE = {
33 | current: null,
34 | isLoggedIn: false,
35 | isLoading: false,
36 | isSuccess: false,
37 | };
38 |
39 | const auth_state = storePersist.get('auth') ? storePersist.get('auth') : AUTH_INITIAL_STATE;
40 |
41 | const initialState = { translate: lang_state, auth: auth_state };
42 | const store = createStore(rootReducer, initialState, configStore);
43 |
44 | export default store;
45 |
--------------------------------------------------------------------------------
/frontend/src/redux/translate/actions.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from './types';
2 |
3 | import languages from '@/locale/languages';
4 |
5 | async function fetchTranslation() {
6 | try {
7 | let translation = await import('@/locale/translation/translation');
8 | return translation.default;
9 | } catch (error) {
10 | console.error(
11 | 'Error fetching translation file :~ file: actions.js:7 ~ fetchTranslation ~ fetchTranslation:',
12 | error
13 | );
14 | }
15 | }
16 |
17 | export const translateAction = {
18 | resetState: () => (dispatch) => {
19 | dispatch({
20 | type: actionTypes.RESET_STATE,
21 | });
22 | },
23 | translate: (value) => async (dispatch) => {
24 | dispatch({
25 | type: actionTypes.REQUEST_LOADING,
26 | });
27 | const translation = await fetchTranslation();
28 | let data = await translation[value];
29 |
30 | const isRtl = languages.find((l) => l.value === value).isRtl || false;
31 | const LANG_STATE = {
32 | result: data,
33 | isRtl: isRtl,
34 | langDirection: isRtl ? 'rtl' : 'ltr',
35 | langCode: value,
36 | isLoading: false,
37 | isSuccess: false,
38 | };
39 | window.localStorage.setItem('translate', JSON.stringify(LANG_STATE));
40 | if (data) {
41 | dispatch({
42 | type: actionTypes.REQUEST_SUCCESS,
43 | payload: data,
44 | langCode: value,
45 | isRtl: isRtl,
46 | });
47 | } else {
48 | dispatch({
49 | type: actionTypes.REQUEST_FAILED,
50 | });
51 | }
52 | },
53 | };
54 |
--------------------------------------------------------------------------------
/frontend/src/redux/translate/index.js:
--------------------------------------------------------------------------------
1 | export { default as reducer } from './reducer';
2 |
--------------------------------------------------------------------------------
/frontend/src/redux/translate/reducer.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from './types';
2 | import en_us from '@/locale/translation/en_us';
3 | import storePersist from '../storePersist';
4 |
5 | const LANG_INITIAL_STATE = {
6 | result: en_us,
7 | langCode: 'en_us',
8 | langDirection: 'ltr',
9 | isLoading: false,
10 | isSuccess: false,
11 | };
12 |
13 | const INITIAL_STATE = storePersist.get('translate')
14 | ? storePersist.get('translate')
15 | : LANG_INITIAL_STATE;
16 |
17 | const translateReducer = (state = INITIAL_STATE, action) => {
18 | const { payload = null, langCode, isRtl = false } = action;
19 | switch (action.type) {
20 | case actionTypes.RESET_STATE:
21 | return INITIAL_STATE;
22 | case actionTypes.REQUEST_LOADING:
23 | return {
24 | ...state,
25 | isLoading: true,
26 | };
27 | case actionTypes.REQUEST_FAILED:
28 | return {
29 | ...state,
30 | isLoading: false,
31 | isSuccess: false,
32 | };
33 |
34 | case actionTypes.REQUEST_SUCCESS:
35 | return {
36 | result: { ...state.result, ...payload },
37 | langCode: langCode.toLowerCase(),
38 | langDirection: isRtl ? 'rtl' : 'ltr',
39 | isLoading: false,
40 | isSuccess: true,
41 | };
42 | default:
43 | return state;
44 | }
45 | };
46 |
47 | export default translateReducer;
48 |
--------------------------------------------------------------------------------
/frontend/src/redux/translate/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 |
3 | export const selectLangState = (state) => state.translate;
4 |
5 | export const selectCurrentLang = createSelector([selectLangState], (translate) => translate.result);
6 | export const selectLangCode = createSelector([selectLangState], (translate) => translate.langCode);
7 |
--------------------------------------------------------------------------------
/frontend/src/redux/translate/types.js:
--------------------------------------------------------------------------------
1 | export const RESET_STATE = 'TRANSLATE_RESET_STATE';
2 |
3 | export const REQUEST_LOADING = 'TRANSLATE_REQUEST_LOADING';
4 | export const REQUEST_SUCCESS = 'TRANSLATE_REQUEST_SUCCESS';
5 | export const REQUEST_FAILED = 'TRANSLATE_REQUEST_FAILED';
6 |
--------------------------------------------------------------------------------
/frontend/src/request/checkImage.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | export default async function checkImage(path) {
3 | const result = await axios
4 | .get(path)
5 | .then((response) => {
6 | if (response.status === 200) return true;
7 | else return false;
8 | })
9 | .catch(() => {
10 | return false;
11 | });
12 |
13 | return result;
14 | }
15 |
--------------------------------------------------------------------------------
/frontend/src/request/codeMessage.js:
--------------------------------------------------------------------------------
1 | const codeMessage = {
2 | 200: 'The server successfully returned the requested data. ',
3 | 201: 'Create or modify data successfully. ',
4 | 202: 'A request has entered the background queue (asynchronous task). ',
5 | 204: 'Delete data successfully. ',
6 | 400: 'There was an error in the request sent, and the server did not create or modify data. ',
7 | 401: 'The admin does not have permission please try to login again. ',
8 | 403: 'The admin is authorized, but access is forbidden. ',
9 | 404: 'The request sent is for a record that does not exist, and the server is not operating. ',
10 | 406: 'The requested format is not available. ',
11 | 410: 'The requested resource has been permanently deleted and will no longer be available. ',
12 | 422: 'When creating an object, a validation error occurred. ',
13 | 500: 'An error occurred in the server, please check the server. ',
14 | 502: 'Gateway error. ',
15 | 503: 'The service is unavailable, the server is temporarily overloaded or maintained. ',
16 | 504: 'The gateway has timed out. ',
17 | };
18 |
19 | export default codeMessage;
20 |
--------------------------------------------------------------------------------
/frontend/src/request/errorHandler.js:
--------------------------------------------------------------------------------
1 | import { notification } from 'antd';
2 |
3 | import codeMessage from './codeMessage';
4 |
5 | const errorHandler = (error) => {
6 | const { response } = error;
7 |
8 | if (response && response.status) {
9 | const message = response.data && response.data.message;
10 |
11 | const errorText = message || codeMessage[response.status];
12 | const { status } = response;
13 | notification.config({
14 | duration: 10,
15 | });
16 | notification.error({
17 | message: `Request error ${status}`,
18 | description: errorText,
19 | });
20 | // if (response.data && response.data.jwtExpired) {
21 | // navigate('/logout');
22 | // }
23 | return response.data;
24 | } else {
25 | notification.config({
26 | duration: 5,
27 | });
28 | notification.error({
29 | message: 'No internet connection',
30 | description: 'Cannot connect to the server, Check your internet network',
31 | });
32 | return {
33 | success: false,
34 | result: null,
35 | message: 'Cannot connect to the server, Check your internet network',
36 | };
37 | }
38 | };
39 |
40 | export default errorHandler;
41 |
--------------------------------------------------------------------------------
/frontend/src/request/index.js:
--------------------------------------------------------------------------------
1 | export { default as request } from './request';
2 | export { default as checkImage } from './checkImage';
3 |
--------------------------------------------------------------------------------
/frontend/src/request/successHandler.js:
--------------------------------------------------------------------------------
1 | import { notification } from 'antd';
2 |
3 | import codeMessage from './codeMessage';
4 |
5 | const successHandler = (response, options = { notifyOnSuccess: false, notifyOnFailed: true }) => {
6 | const { data } = response;
7 | if (data && data.success === true) {
8 | const message = response.data && data.message;
9 | const successText = message || codeMessage[response.status];
10 |
11 | if (options.notifyOnSuccess) {
12 | notification.config({
13 | duration: 5,
14 | });
15 | notification.success({
16 | message: `Request success`,
17 | description: successText,
18 | });
19 | }
20 | } else {
21 | const message = response.data && data.message;
22 | const errorText = message || codeMessage[response.status];
23 | const { status } = response;
24 | if (options.notifyOnFailed) {
25 | notification.config({
26 | duration: 5,
27 | });
28 | notification.error({
29 | message: `Request error ${status}`,
30 | description: errorText,
31 | });
32 | }
33 | }
34 | };
35 |
36 | export default successHandler;
37 |
--------------------------------------------------------------------------------
/frontend/src/router/AuthRouter.jsx:
--------------------------------------------------------------------------------
1 | import { Routes, Route } from 'react-router-dom';
2 |
3 | import Login from '@/pages/Login';
4 |
5 | import NotFound from '@/pages/NotFound';
6 |
7 | import Register from '@/pages/Register';
8 |
9 | export default function AuthRouter() {
10 | return (
11 |
12 | } path="/" />
13 | } path="/login" />
14 | } path="/logout" />
15 | } path="/register" />
16 | } />
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/src/router/PrivateRoute.jsx:
--------------------------------------------------------------------------------
1 | import { Navigate } from 'react-router-dom';
2 |
3 | const PrivateRoute = ({ children }) => {
4 | if (window.localStorage.getItem('isLoggedIn')) {
5 | return <>{children}>;
6 | } else return ;
7 | };
8 |
9 | export default PrivateRoute;
10 |
--------------------------------------------------------------------------------
/frontend/src/router/PublicRoute.jsx:
--------------------------------------------------------------------------------
1 | import { Navigate } from 'react-router-dom';
2 |
3 | const PublicRoute = ({ children }) => {
4 | if (window.localStorage.getItem('isLoggedIn')) {
5 | return ;
6 | } else return <>{children}>;
7 | };
8 |
9 | export default PublicRoute;
10 |
--------------------------------------------------------------------------------
/frontend/src/settings/index.jsx:
--------------------------------------------------------------------------------
1 | export { default as useMoney } from './useMoney';
2 |
--------------------------------------------------------------------------------
/frontend/src/style/app.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,600;0,700;0,800;1,600;1,700&display=swap');
2 |
3 | @import './partials/rest.css';
4 | @import './partials/customAntd.css';
5 | @import './partials/layout.css';
6 | @import './partials/core.css';
7 |
8 | @import './partials/auth.css';
9 | @import './partials/navigation.css';
10 | @import './partials/header.css';
11 | @import './partials/sidePanel.css';
12 | @import './partials/collapseBox.css';
13 | @import './partials/erp.css';
14 |
--------------------------------------------------------------------------------
/frontend/src/style/images/checklist.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/frontend/src/style/images/flow-xo-gray.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
20 |
--------------------------------------------------------------------------------
/frontend/src/style/images/gitlab-gray.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
--------------------------------------------------------------------------------
/frontend/src/style/images/logo-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-ninja11/nextGPT/248aea2c8e9f5b95a69529e0c3a1dd51bdd39d50/frontend/src/style/images/logo-icon.png
--------------------------------------------------------------------------------
/frontend/src/style/images/logo-menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-ninja11/nextGPT/248aea2c8e9f5b95a69529e0c3a1dd51bdd39d50/frontend/src/style/images/logo-menu.png
--------------------------------------------------------------------------------
/frontend/src/style/images/logo-text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-ninja11/nextGPT/248aea2c8e9f5b95a69529e0c3a1dd51bdd39d50/frontend/src/style/images/logo-text.png
--------------------------------------------------------------------------------
/frontend/src/style/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-ninja11/nextGPT/248aea2c8e9f5b95a69529e0c3a1dd51bdd39d50/frontend/src/style/images/logo.png
--------------------------------------------------------------------------------
/frontend/src/style/images/logo1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-ninja11/nextGPT/248aea2c8e9f5b95a69529e0c3a1dd51bdd39d50/frontend/src/style/images/logo1.png
--------------------------------------------------------------------------------
/frontend/src/style/images/logo2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-ninja11/nextGPT/248aea2c8e9f5b95a69529e0c3a1dd51bdd39d50/frontend/src/style/images/logo2.png
--------------------------------------------------------------------------------
/frontend/src/style/images/logo3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-ninja11/nextGPT/248aea2c8e9f5b95a69529e0c3a1dd51bdd39d50/frontend/src/style/images/logo3.png
--------------------------------------------------------------------------------
/frontend/src/style/images/logo4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-ninja11/nextGPT/248aea2c8e9f5b95a69529e0c3a1dd51bdd39d50/frontend/src/style/images/logo4.png
--------------------------------------------------------------------------------
/frontend/src/style/images/photo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-ninja11/nextGPT/248aea2c8e9f5b95a69529e0c3a1dd51bdd39d50/frontend/src/style/images/photo.png
--------------------------------------------------------------------------------
/frontend/src/style/partials/auth.css:
--------------------------------------------------------------------------------
1 | .list-checked {
2 | padding-left: 0px;
3 | list-style: none;
4 | }
5 | .list-checked-item {
6 | position: relative;
7 | display: block;
8 | color: #677788;
9 | padding-left: 1.75rem;
10 | margin-bottom: 30px;
11 | list-style: none;
12 | }
13 |
14 | .list-checked-item::before {
15 | position: absolute;
16 | top: 0;
17 | left: 0;
18 | width: 1rem;
19 | height: 1rem;
20 | background-image: url(../images/checklist.svg);
21 | content: '';
22 | margin-top: 0.125rem;
23 | }
24 |
25 | .sideContent * {
26 | color: #4f5d75 !important;
27 | }
28 |
--------------------------------------------------------------------------------
/frontend/src/style/partials/collapseBox.css:
--------------------------------------------------------------------------------
1 | .panelBox,
2 | .panelBox * {
3 | transition: all 0.3s ease;
4 | }
5 | .collapseBoxHeader {
6 | /* width: 100%; */
7 | padding: 17px 30px;
8 | margin-bottom: 30px;
9 | text-align: center;
10 | font-size: 14px;
11 | text-transform: uppercase;
12 | cursor: pointer;
13 | background-color: #f9fafc;
14 | border-top: 1px solid #edf0f5;
15 | border-bottom: 1px solid #edf0f5;
16 | }
17 |
18 | .box {
19 | width: 100%;
20 | padding: 20px;
21 | }
22 | .BottomCollapseBox {
23 | /* padding: 20px; */
24 | opacity: 1;
25 | }
26 | .TopCollapseBox {
27 | min-height: 450px;
28 | opacity: 1;
29 | }
30 | .collapseBox {
31 | margin-top: -400px;
32 | position: relative;
33 |
34 | background: #fff;
35 | z-index: 1;
36 | }
37 | .collapsed {
38 | overflow: hidden;
39 | height: 250px;
40 | margin-top: 0 !important;
41 | }
42 |
43 | .collapseBox .whiteBg {
44 | display: none;
45 | background-color: hsla(0, 0%, 100%, 0);
46 | }
47 |
48 | .collapsed .whiteBg {
49 | position: absolute;
50 | top: 58px;
51 | width: 100%;
52 | height: 100%;
53 | display: inline-block;
54 | background-color: hsla(0, 0%, 100%, 0.8);
55 | z-index: 9;
56 | }
57 |
--------------------------------------------------------------------------------
/frontend/src/style/partials/erp.css:
--------------------------------------------------------------------------------
1 | .moneyInput {
2 | width: 100%;
3 | }
4 |
5 | .moneyInput input {
6 | text-align: right;
7 | }
8 |
--------------------------------------------------------------------------------
/frontend/src/style/partials/header.css:
--------------------------------------------------------------------------------
1 | .notification::-webkit-scrollbar {
2 | width: 4px;
3 | height: 60px;
4 | }
5 | .notification::-webkit-scrollbar-thumb {
6 | background: #1b98f5;
7 | border-radius: 4px;
8 | }
9 | .headerIcon {
10 | position: relative;
11 | }
12 |
13 | .headerIcon.ant-avatar {
14 | float: right;
15 | margin-left: 10px;
16 | margin-top: 15px;
17 | color: #4f5d75;
18 | background: transparent;
19 | }
20 | .headerIcon.ant-avatar :hover {
21 | background: #fff;
22 | box-shadow: 0px 0px 10px 4px rgba(150, 190, 238, 0.3);
23 | cursor: pointer;
24 | }
25 |
26 | .headerIcon .last {
27 | margin-right: 30px;
28 | }
29 |
30 | .profileDropdown {
31 | display: flex;
32 | min-width: 200px;
33 | }
34 | .profileDropdownInfo {
35 | float: left;
36 | display: inline;
37 | padding-left: 15px;
38 | }
39 |
40 | .profileDropdownInfo p {
41 | margin: 0;
42 | }
43 |
--------------------------------------------------------------------------------
/frontend/src/style/partials/layout.css:
--------------------------------------------------------------------------------
1 | .wideAppContainer {
2 | max-width: 1150px;
3 | }
4 |
5 | .appContainer {
6 | max-width: 1050px;
7 | }
8 |
9 | .smallNavigation {
10 | margin-left: 100px;
11 | }
12 |
13 | .wideNavigation {
14 | margin-left: 220px;
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/src/style/partials/navigation.css:
--------------------------------------------------------------------------------
1 | .navigation {
2 | position: sticky;
3 | height: 100vh;
4 | overflow-y: auto;
5 | overflow-x: hidden;
6 | top: 0;
7 | left: 0;
8 | z-index: 1000;
9 | background: #fff;
10 | border-right: 1px solid #edf0f5;
11 | }
12 |
13 | .logo {
14 | height: 32px;
15 | margin: 16px 16px 30px 16px;
16 | padding-left: 6px;
17 | }
18 |
19 | .sidebar-wraper {
20 | background: #fff;
21 | display: block;
22 | }
23 |
24 | .mobile-sidebar-wraper {
25 | display: none;
26 | }
27 |
28 | .mobile-sidebar-wraper .ant-drawer-body {
29 | padding: 12px 0px !important;
30 | }
31 |
32 | .ant-btn.mobile-sidebar-btn {
33 | display: none;
34 | }
35 |
36 | @media only screen and (max-width: 768px) {
37 | .sidebar-wraper {
38 | display: none;
39 | }
40 |
41 | .navigation {
42 | height: 100%;
43 | }
44 |
45 | .ant-btn.mobile-sidebar-btn {
46 | display: block;
47 | position: absolute;
48 | top: 10px;
49 | }
50 |
51 | .mobile-sidebar-wraper {
52 | display: block;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/frontend/src/style/partials/rest.css:
--------------------------------------------------------------------------------
1 | * {
2 | font-family: 'Nunito', sans-serif !important;
3 | -webkit-font-smoothing: antialiased;
4 | font-weight: 700;
5 | }
6 | body {
7 | margin: 0;
8 | padding: 0;
9 | background: #f9fafc !important;
10 | }
11 |
--------------------------------------------------------------------------------
/frontend/src/style/partials/sidePanel.css:
--------------------------------------------------------------------------------
1 | .sidePanel {
2 | border-right: 1px solid #edf0f5;
3 | background: #fff;
4 | }
5 |
6 | .sidePanelContent {
7 | transition: all 0.3s ease-in-out;
8 | margin-top: 0;
9 | }
10 |
--------------------------------------------------------------------------------
/frontend/src/style/partials/transition.css:
--------------------------------------------------------------------------------
1 | .fade-enter {
2 | opacity: 0;
3 | transform: translateX(-30px);
4 | }
5 |
6 | .fade-enter-active,
7 | .fade-exit-active {
8 | opacity: 1;
9 | transition: all 500ms ease-out;
10 | transform: translateX(0);
11 | }
12 |
13 | .fade-exit {
14 | opacity: 0;
15 | transform: translateX(30px);
16 | }
17 |
--------------------------------------------------------------------------------
/frontend/src/utils/calculate.js:
--------------------------------------------------------------------------------
1 | import currency from 'currency.js';
2 |
3 | const calculate = {
4 | add: (firstValue, secondValue) => {
5 | return currency(firstValue).add(secondValue).value;
6 | },
7 | sub: (firstValue, secondValue) => {
8 | return currency(firstValue).subtract(secondValue).value;
9 | },
10 | multiply: (firstValue, secondValue) => {
11 | return currency(firstValue).multiply(secondValue).value;
12 | },
13 | divide: (firstValue, secondValue) => {
14 | return currency(firstValue).divide(secondValue).value;
15 | },
16 | };
17 |
18 | export default calculate;
19 |
--------------------------------------------------------------------------------
/frontend/src/utils/tagColor.js:
--------------------------------------------------------------------------------
1 | const tagColor = [
2 | 'magenta',
3 | 'red',
4 | 'volcano',
5 | 'orange',
6 | 'gold',
7 | 'lime',
8 | 'green',
9 | 'cyan',
10 | 'blue',
11 | 'geekblue',
12 | 'purple',
13 | ];
14 |
15 | export default tagColor;
16 |
--------------------------------------------------------------------------------
/frontend/temp.env:
--------------------------------------------------------------------------------
1 | # to connect your frontend to remote backend server
2 | VITE_BACKEND_SERVER="http://your_url_backend_server.com/"
--------------------------------------------------------------------------------
/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-ninja11/nextGPT/248aea2c8e9f5b95a69529e0c3a1dd51bdd39d50/image.png
--------------------------------------------------------------------------------