├── .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 |
Ant Design ©2018 Created by Ant UED
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 | 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 | 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 | 5 | 10 | 12 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /frontend/src/style/images/gitlab-gray.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 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 --------------------------------------------------------------------------------