diff --git a/.DS_Store b/.DS_Store index 9e41ca39f..f03c90a5a 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 25c8fdbab..000000000 --- a/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -package-lock.json \ No newline at end of file diff --git a/LICENSE b/LICENSE index b366c047a..dc1a90546 100644 --- a/LICENSE +++ b/LICENSE @@ -1,59 +1,661 @@ -Copyright @ 2024 IDURAR +GNU AFFERO GENERAL PUBLIC LICENSE +Version 3, 19 November 2007 -Source code in this repository is covered by (i) the IDURAR License 1.0 , a compatible Elastic License 2.0, in each case, as designated by a licensing file in a subdirectory or file header. The default throughout the repository is a license under the IDURAR License 1.0, unless a file header or a licensing file in a subdirectory specifies another license. +Copyright (C) 2007 Free Software Foundation, Inc. +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. ---- + Preamble -# IDURAR License 1.0 +The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. -## Acceptance +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. -By using the software, you agree to all of the terms and conditions below. +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. -## Copyright License +Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. -The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject to the limitations and conditions below. +A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. -## Limitations +The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. -You may not provide the software to third parties as a hosted or managed service or as softwase as service (Saas), where the service provides users with access to any substantial set of the features or functionality of this software. +An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. -You may not move, change, disable, or circumvent the license key functionality in the software, and you may not remove or obscure any functionality in the software that is protected by the license key. +The precise terms and conditions for copying, distribution and +modification follow. -You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensorโ€™s trademarks is subject to applicable law. + TERMS AND CONDITIONS -## Patents +0. Definitions. -The licensor grants you a license, under any patent claims the licensor can license, or becomes able to license, to make, have made, use, sell, offer for sale, import and have imported the software, in each case subject to the limitations and conditions in this license. This license does not cover any patent claims that you cause to be infringed by modifications or additions to the software. If you or your company make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company. +"This License" refers to version 3 of the GNU Affero General Public License. -## Notices +"Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. -You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms. +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. -If you modify the software, you must include in any modified copies of the software prominent notices stating that you have modified the software. +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. -## No Other Rights +A "covered work" means either the unmodified Program or a work based +on the Program. -These terms do not imply any licenses other than those expressly granted in these terms. +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. -## Termination +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. -If you use the software in violation of these terms, such use is not licensed, and your licenses will automatically terminate. If the licensor provides you with a notice of your violation, and you cease all violation of this license no later than 30 days after you receive that notice, your licenses will be reinstated retroactively. However, if you violate these terms after such reinstatement, any additional violation of these terms will cause your licenses to terminate automatically and permanently. +An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. -## No Liability +1. Source Code. -As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim. +The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. -## Definitions +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. -The licensor is the entity offering these terms, and the software is the software the licensor makes available under these terms, including any portion of it. +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. -you refers to the individual or entity agreeing to these terms. +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. -your company is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. control means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect. +The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. -your licenses are all the licenses granted to you for the software under these terms. +The Corresponding Source for a work in source code form is that +same work. -use means anything you do with the software requiring one of your licenses. +2. Basic Permissions. -trademark means trademarks, service marks, and similar rights. +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + +4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + +8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + +13. Remote Network Interaction; Use with the GNU General Public License. + +Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + +14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + +You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/README.md b/README.md index 80821d946..a42b345cb 100644 --- a/README.md +++ b/README.md @@ -2,44 +2,33 @@ -

Open "Fair-Code" Source ERP / CRM | Node.js React.js

+

Open Source ERP / CRM Accounting Invoice Quote

-

IDURAR ERP CRM | Simple To Use | 44 Languages

+

IDURAR ERP CRM | Simple To Use

- [www.idurarapp.com/open-source-erp-crm/](https://www.idurarapp.com/open-source-erp-crm/) - -## ๐Ÿ‡ฆ๐Ÿ‡ฑ ๐Ÿ‡ฉ๐Ÿ‡ฟ ๐Ÿ‡ง๐Ÿ‡ฉ ๐Ÿ‡ง๐Ÿ‡ฌ ๐Ÿ‡จ๐Ÿ‡ณ ๐Ÿ‡ญ๐Ÿ‡ท ๐Ÿ‡จ๐Ÿ‡ฟ ๐Ÿ‡ฉ๐Ÿ‡ฐ ๐Ÿ‡ณ๐Ÿ‡ฑ ๐Ÿ‡บ๐Ÿ‡ธ ๐Ÿ‡ช๐Ÿ‡ช ๐Ÿ‡ซ๐Ÿ‡ท ๐Ÿ‡ฉ๐Ÿ‡ช ๐Ÿ‡ฌ๐Ÿ‡ท ๐Ÿ‡ฎ๐Ÿ‡ณ ๐Ÿ‡ญ๐Ÿ‡บ ๐Ÿ‡ฎ๐Ÿ‡ฉ ๐Ÿ‡ฎ๐Ÿ‡น ๐Ÿ‡ฏ๐Ÿ‡ต ๐Ÿ‡ฐ๐Ÿ‡ท ๐Ÿ‡ฑ๐Ÿ‡ป ๐Ÿ‡ฑ๐Ÿ‡น ๐Ÿ‡ฒ๐Ÿ‡ฐ ๐Ÿ‡ฒ๐Ÿ‡พ ๐Ÿ‡ณ๐Ÿ‡ด ๐Ÿ‡ต๐Ÿ‡ฑ ๐Ÿ‡ง๐Ÿ‡ท ๐Ÿ‡ต๐Ÿ‡น ๐Ÿ‡ฎ๐Ÿ‡ท ๐Ÿ‡ท๐Ÿ‡ด ๐Ÿ‡ท๐Ÿ‡บ ๐Ÿ‡ธ๐Ÿ‡ฐ ๐Ÿ‡ธ๐Ÿ‡ฎ ๐Ÿ‡ช๐Ÿ‡ธ ๐Ÿ‡ธ๐Ÿ‡ช ๐Ÿ‡น๐Ÿ‡ญ ๐Ÿ‡น๐Ÿ‡ท ๐Ÿ‡บ๐Ÿ‡ฆ ๐Ÿ‡ต๐Ÿ‡ฐ ๐Ÿ‡ป๐Ÿ‡ณ ๐Ÿ‡ท๐Ÿ‡ธ ๐Ÿ‡ช๐Ÿ‡ฆ ๐Ÿ‡ต๐Ÿ‡ญ ๐Ÿ‡ซ๐Ÿ‡ฎ - -IDURAR is Open "Fair-Code" Source ERP / CRM (Invoice / Inventory / Accounting / HR) Based on Advanced Mern Stack (Node.js / Express.js / MongoDb / React.js ) with Ant Design (AntD) and Redux - - - -**Live App Free Cloud Version** : [https://www.idurarapp.com/demo-erp-crm/](https://www.idurarapp.com/demo-erp-crm/) ``` -๐Ÿš€ Give a Star โญ๏ธ & Fork to this project ... Happy coding! ๐Ÿคฉ` + Give a Star โญ๏ธ & Fork to this project ... Happy coding! ๐Ÿคฉ` ``` -## Translation : +IDURAR is Open Source ERP / CRM (Invoice / Quote / Accounting ) Based on Advanced Mern Stack (Node.js / Express.js / MongoDb / React.js ) with Ant Design (AntD) and Redux -[French](doc/README.fr.md#French-Translation) , [Spanish](doc/README.sp.md#Spanish-Translation) -## License + + +**๐Ÿš€ Self-hosted Entreprise Version** : [https://cloud.idurarapp.com](https://cloud.idurarapp.com) -IDURAR is Free Open Code Source [fair-code](http://faircode.io) distributed under the -[**IDURAR License 1.0**](https://github.com/idurar/idurar-erp-crm/blob/master/LICENSE) -## License FAQ : -## Features : +## Features : -Invoice Management ๐Ÿ’ฐ +Invoice Management -Inventory Management ๐Ÿงณ +Payment Management -Accounting Management ๐Ÿ“ˆ +Quote Management -HR Management ๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘ +Customer Management Ant Design Framework(AntD) ๐Ÿœ @@ -49,21 +38,6 @@ Based on Mern Stack (Node.js / Express.js / MongoDb / React.js ) ๐Ÿ‘จโ€๐Ÿ’ป - Yes You can use IDURAR for free for personal or Commercial use. -### May i can customize IDURAR as Saas and provide it to other users ? - -No, you cannot customize IDURAR as a SaaS and provide it to other users , You are not allowed to provide IDURAR software to third parties as a hosted or managed service or as softwase as service (Saas), where the service provides users with access to any substantial set of the features or functionality of this software. - -### For custom developement service or premium Support : - -[Get in touch](mailto:hello@idurarapp.com) - -## How To Deploy IDURAR ERP CRM : - -๐Ÿ”ฅ I would like to invite you for a weekly free IDURAR Webinar (Node.js React.js course), where you learn how to deploy IDURAR on cloud , and create a new api and new crud app with IDURAR in just one hour ? -Please fill this form if you are interested : [https://forms.gle/qz2YZ3xQFQ77bGhS8](https://forms.gle/qz2YZ3xQFQ77bGhS8) - -The Webinar will be this Wednesday at 1pm GMT. - ## Our Sponsors @@ -78,14 +52,11 @@ The Webinar will be this Wednesday at 1pm GMT. IDURAR is Open "Fair-Code" Source ERP / CRM (Invoice / Inventory / Accounting / HR) Based on Mern Stack (Node.js / Express.js / MongoDb / React.js ) with Ant Design (AntD) and Redux -**Live App Demo** : [https://www.idurarapp.com/demo-erp-crm/](https://www.idurarapp.com/demo-erp-crm/) ## Getting started - 1.[Clone the repository](INSTALLATION-INSTRUCTIONS.md#step-1-clone-the-repository) - 2.[Create Your MongoDB Account and Database Cluster](INSTALLATION-INSTRUCTIONS.md#Step-2-Create-Your-MongoDB-Account-and-Database-Cluster) 3.[Edit the Environment File](INSTALLATION-INSTRUCTIONS.md#Step-3-Edit-the-Environment-File) @@ -102,24 +73,6 @@ IDURAR is Open "Fair-Code" Source ERP / CRM (Invoice / Inventory / Accounting / 9.[Run the Frontend Server](INSTALLATION-INSTRUCTIONS.md#Step-9-Run-the-Frontend-Server) -## Docker Compose for local development - -- setup additional env variables, if necessary in the below file - -```bash -docker-compose.yml -``` - -- After the necessary configurations run below command : - -```bash -docker-compose up -d -``` - -This will build the images and bring up the containers for frontend, backend and mongodb. - -**_NOTE:_** This docker-compose setup is associated for local development only. - ## Contributing 1.[How to contribute](https://github.com/idurar/idurar-erp-crm/blob/master/CONTRIBUTING.md#how-to-contribute) @@ -136,10 +89,13 @@ This will build the images and bring up the containers for frontend, backend and 7.[Questions](https://github.com/idurar/idurar-erp-crm/blob/master/CONTRIBUTING.md#questions) -## Custom Development Service - -Custom Development Service are available : [Get in touch](mailto:hello@idurarapp.com) ## Show your support Dont forget to give a โญ๏ธ to this project ... Happy coding! + +**๐Ÿš€ Self-hosted Entreprise Version** : [https://cloud.idurarapp.com](https://cloud.idurarapp.com) + +## License + +IDURAR is Free Open Source Released under the GNU Affero General Public License v3.0. diff --git a/backend/Dockerfile b/backend/Dockerfile deleted file mode 100644 index 69b7179bd..000000000 --- a/backend/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM node:20.9.0-alpine - -WORKDIR /usr/src/app - -RUN npm install -g npm@10.2.4 - -COPY package*.json ./ - -COPY . . - -RUN npm install - -EXPOSE 8888 - -CMD ["npm", "run", "dev"] \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json index 268204e70..3635c8039 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1,12 +1,12 @@ { "name": "idurar-erp-crm", - "version": "4.0.0-beta.3", + "version": "4.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "idurar-erp-crm", - "version": "4.0.0-beta.3", + "version": "4.0.0", "license": "Fair-code License", "dependencies": { "@aws-sdk/client-s3": "^3.509.0", diff --git a/backend/package.json b/backend/package.json index 2e28def1c..ee2b151ea 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "idurar-erp-crm", - "version": "4.0.0", + "version": "4.1.0", "engines": { "npm": "10.2.4", "node": "20.9.0" diff --git a/backend/src/controllers/appControllers/clientController/create.js b/backend/src/controllers/appControllers/clientController/create.js deleted file mode 100644 index fdb96dab3..000000000 --- a/backend/src/controllers/appControllers/clientController/create.js +++ /dev/null @@ -1,91 +0,0 @@ -const mongoose = require('mongoose'); - -const People = mongoose.model('People'); -const Company = mongoose.model('Company'); - -const create = async (Model, req, res) => { - // Creating a new document in the collection - - if (req.body.type === 'people') { - if (!req.body.people) { - return res.status(403).json({ - success: false, - message: 'Please select a people', - }); - } else { - let client = await Model.findOne({ - people: req.body.people, - removed: false, - }); - - if (client) { - return res.status(403).json({ - success: false, - result: null, - message: 'Client Already Exist', - }); - } - - let { firstname, lastname } = await People.findOneAndUpdate( - { - _id: req.body.people, - removed: false, - }, - { isClient: true }, - { - new: true, // return the new result instead of the old one - runValidators: true, - } - ).exec(); - req.body.name = firstname + ' ' + lastname; - req.body.company = undefined; - } - } else { - if (!req.body.company) { - return res.status(403).json({ - success: false, - message: 'Please select a company', - }); - } else { - let client = await Model.findOne({ - company: req.body.company, - removed: false, - }); - - if (client) { - return res.status(403).json({ - success: false, - result: null, - message: 'Client Already Exist', - }); - } - let { name } = await Company.findOneAndUpdate( - { - _id: req.body.company, - removed: false, - }, - { isClient: true }, - { - new: true, // return the new result instead of the old one - runValidators: true, - } - ).exec(); - req.body.name = name; - req.body.people = undefined; - } - } - - req.body.removed = false; - const result = await new Model({ - ...req.body, - }).save(); - - // Returning successfull response - return res.status(200).json({ - success: true, - result, - message: 'Successfully Created the document in Model ', - }); -}; - -module.exports = create; diff --git a/backend/src/controllers/appControllers/clientController/index.js b/backend/src/controllers/appControllers/clientController/index.js index 668727b75..6411c1951 100644 --- a/backend/src/controllers/appControllers/clientController/index.js +++ b/backend/src/controllers/appControllers/clientController/index.js @@ -1,28 +1,13 @@ const mongoose = require('mongoose'); const createCRUDController = require('@/controllers/middlewaresControllers/createCRUDController'); -const remove = require('./remove'); -const summary = require('./summary'); - -const create = require('./create'); -const read = require('./read'); -const search = require('./search'); -const update = require('./update'); -const listAll = require('./listAll'); -const paginatedList = require('./paginatedList'); +const summary = require('./summary'); function modelController() { const Model = mongoose.model('Client'); const methods = createCRUDController('Client'); - methods.read = (req, res) => read(Model, req, res); - methods.delete = (req, res) => remove(Model, req, res); - methods.list = (req, res) => paginatedList(Model, req, res); methods.summary = (req, res) => summary(Model, req, res); - methods.create = (req, res) => create(Model, req, res); - methods.update = (req, res) => update(Model, req, res); - methods.search = (req, res) => search(Model, req, res); - methods.listAll = (req, res) => listAll(Model, req, res); return methods; } diff --git a/backend/src/controllers/appControllers/clientController/listAll.js b/backend/src/controllers/appControllers/clientController/listAll.js deleted file mode 100644 index 82a836d44..000000000 --- a/backend/src/controllers/appControllers/clientController/listAll.js +++ /dev/null @@ -1,30 +0,0 @@ -const { migrate } = require('./migrate'); - -const listAll = async (Model, req, res) => { - const sort = parseInt(req.query.sort) || 'desc'; - - // Query the database for a list of all results - const result = await Model.find({ - removed: false, - }) - .sort({ created: sort }) - .populate() - .exec(); - - const migratedData = result.map((x) => migrate(x)); - if (result.length > 0) { - return res.status(200).json({ - success: true, - result: migratedData, - message: 'Successfully found all documents', - }); - } else { - return res.status(203).json({ - success: true, - result: [], - message: 'Collection is Empty', - }); - } -}; - -module.exports = listAll; diff --git a/backend/src/controllers/appControllers/clientController/migrate.js b/backend/src/controllers/appControllers/clientController/migrate.js deleted file mode 100644 index cc6659d9a..000000000 --- a/backend/src/controllers/appControllers/clientController/migrate.js +++ /dev/null @@ -1,16 +0,0 @@ -exports.migrate = (result) => { - const client = result.type === 'people' ? result.people : result.company; - let newData = {}; - newData._id = result._id; - newData.type = result.type; - newData.name = result.name; - newData.phone = client.phone; - newData.email = client.email; - newData.website = client.website; - newData.country = client.country; - newData.address = client.address; - newData.people = result.people; - newData.company = result.company; - newData.notes = result.notes; - return newData; -}; diff --git a/backend/src/controllers/appControllers/clientController/paginatedList.js b/backend/src/controllers/appControllers/clientController/paginatedList.js deleted file mode 100644 index 9e121a66a..000000000 --- a/backend/src/controllers/appControllers/clientController/paginatedList.js +++ /dev/null @@ -1,67 +0,0 @@ -const { migrate } = require('./migrate'); - -const paginatedList = async (Model, req, res) => { - const page = req.query.page || 1; - - const limit = parseInt(req.query.items) || 10; - const skip = page * limit - limit; - - const { sortBy = 'enabled', sortValue = -1, filter, equal } = req.query; - - const fieldsArray = req.query.fields ? req.query.fields.split(',') : []; - - let fields; - - fields = fieldsArray.length === 0 ? {} : { $or: [] }; - - for (const field of fieldsArray) { - fields.$or.push({ [field]: { $regex: new RegExp(req.query.q, 'i') } }); - } - - // Query the database for a list of all results - const resultsPromise = Model.find({ - removed: false, - [filter]: equal, - ...fields, - }) - .skip(skip) - .limit(limit) - .sort({ [sortBy]: sortValue }) - .populate() - .exec(); - - // Counting the total documents - const countPromise = Model.countDocuments({ - removed: false, - - [filter]: equal, - ...fields, - }); - // Resolving both promises - const [result, count] = await Promise.all([resultsPromise, countPromise]); - // console.log('๐Ÿš€ ~ file: paginatedList.js:23 ~ paginatedList ~ result:', result); - - // Calculating total pages - const pages = Math.ceil(count / limit); - - const pagination = { page, pages, count }; - if (count > 0) { - const migratedData = result.map((x) => migrate(x)); - // console.log('๐Ÿš€ ~ file: paginatedList.js:23 ~ paginatedList ~ migratedData:', migratedData); - return res.status(200).json({ - success: true, - result: migratedData, - pagination, - message: 'Successfully found all documents', - }); - } else { - return res.status(203).json({ - success: true, - result: [], - pagination, - message: 'Collection is Empty', - }); - } -}; - -module.exports = paginatedList; diff --git a/backend/src/controllers/appControllers/clientController/read.js b/backend/src/controllers/appControllers/clientController/read.js deleted file mode 100644 index 9f6c27db0..000000000 --- a/backend/src/controllers/appControllers/clientController/read.js +++ /dev/null @@ -1,29 +0,0 @@ -const { migrate } = require('./migrate'); - -const read = async (Model, req, res) => { - // Find document by id - let result = await Model.findOne({ - _id: req.params.id, - removed: false, - }).exec(); - // If no results found, return document not found - if (!result) { - return res.status(404).json({ - success: false, - result: null, - message: 'No document found ', - }); - } else { - // Return success resposne - - const migratedData = migrate(result); - - return res.status(200).json({ - success: true, - result: migratedData, - message: 'we found this document ', - }); - } -}; - -module.exports = read; diff --git a/backend/src/controllers/appControllers/clientController/remove.js b/backend/src/controllers/appControllers/clientController/remove.js deleted file mode 100644 index 64f301384..000000000 --- a/backend/src/controllers/appControllers/clientController/remove.js +++ /dev/null @@ -1,84 +0,0 @@ -const mongoose = require('mongoose'); - -const QuoteModel = mongoose.model('Quote'); -const InvoiceModel = mongoose.model('Invoice'); -const People = mongoose.model('People'); -const Company = mongoose.model('Company'); - -const remove = async (Model, req, res) => { - // cannot delete client it it have one invoice or quotes: - // check if client have invoice or quotes: - const { id } = req.params; - - // first find if there alt least one quote or invoice exist corresponding to the client - const resultQuotes = QuoteModel.findOne({ - client: id, - removed: false, - }).exec(); - const resultInvoice = InvoiceModel.findOne({ - client: id, - removed: false, - }).exec(); - - const [quotes, invoice] = await Promise.allSettled([resultQuotes, resultInvoice]); - if (quotes?.value) { - return res.status(400).json({ - success: false, - result: null, - message: 'Cannot delete client if client have any quote or invoice', - }); - } - if (invoice?.value) { - return res.status(400).json({ - success: false, - result: null, - message: 'Cannot delete client if client have any quote or invoice', - }); - } - - let result = await Model.findOneAndDelete({ - _id: id, - removed: false, - }).exec(); - - if (!result) { - return res.status(404).json({ - success: false, - result: null, - message: 'No client found by this id: ' + id, - }); - } - - if (result.type === 'people') { - await People.findOneAndUpdate( - { - _id: result.people._id, - removed: false, - }, - { isClient: false }, - { - new: true, // return the new result instead of the old one - runValidators: true, - } - ).exec(); - } else { - await Company.findOneAndUpdate( - { - _id: result.company._id, - removed: false, - }, - { isClient: false }, - { - new: true, // return the new result instead of the old one - runValidators: true, - } - ).exec(); - } - - return res.status(200).json({ - success: true, - result: null, - message: 'Successfully Deleted the client by id: ' + id, - }); -}; -module.exports = remove; diff --git a/backend/src/controllers/appControllers/clientController/search.js b/backend/src/controllers/appControllers/clientController/search.js deleted file mode 100644 index 343d09586..000000000 --- a/backend/src/controllers/appControllers/clientController/search.js +++ /dev/null @@ -1,51 +0,0 @@ -const { migrate } = require('./migrate'); - -const search = async (Model, req, res) => { - // console.log(req.query.fields) - // if (req.query.q === undefined || req.query.q.trim() === '') { - // return res - // .status(202) - // .json({ - // success: false, - // result: [], - // message: 'No document found by this request', - // }) - // .end(); - // } - const fieldsArray = req.query.fields ? req.query.fields.split(',') : ['name']; - - const fields = { $or: [] }; - - for (const field of fieldsArray) { - fields.$or.push({ [field]: { $regex: new RegExp(req.query.q, 'i') } }); - } - // console.log(fields) - - let results = await Model.find({ - ...fields, - }) - .where('removed', false) - .limit(20) - .exec(); - - const migratedData = results.map((x) => migrate(x)); - - if (results.length >= 1) { - return res.status(200).json({ - success: true, - result: migratedData, - message: 'Successfully found all documents', - }); - } else { - return res - .status(202) - .json({ - success: false, - result: [], - message: 'No document found by this request', - }) - .end(); - } -}; - -module.exports = search; diff --git a/backend/src/controllers/appControllers/clientController/update.js b/backend/src/controllers/appControllers/clientController/update.js deleted file mode 100644 index 3068e8a95..000000000 --- a/backend/src/controllers/appControllers/clientController/update.js +++ /dev/null @@ -1,12 +0,0 @@ -const mongoose = require('mongoose'); - -const update = async (Model, req, res) => { - // Find document by id and updates with the required fields - return res.status(200).json({ - success: false, - result: null, - message: 'You cant update client once is created', - }); -}; - -module.exports = update; diff --git a/backend/src/controllers/appControllers/companyController/index.js b/backend/src/controllers/appControllers/companyController/index.js deleted file mode 100644 index 3b3308320..000000000 --- a/backend/src/controllers/appControllers/companyController/index.js +++ /dev/null @@ -1,24 +0,0 @@ -const mongoose = require('mongoose'); -const { modelsFiles } = require('@/models/utils'); -const createCRUDController = require('@/controllers/middlewaresControllers/createCRUDController'); - -const remove = require('./remove'); -const update = require('./update'); - -function modelController() { - const modelName = 'Company'; - - if (!modelsFiles.includes(modelName)) { - throw new Error(`Model ${modelName} does not exist`); - } else { - const Model = mongoose.model(modelName); - const methods = createCRUDController(modelName); - - methods.delete = (req, res) => remove(Model, req, res); - methods.update = (req, res) => update(Model, req, res); - - return methods; - } -} - -module.exports = modelController(); diff --git a/backend/src/controllers/appControllers/companyController/remove.js b/backend/src/controllers/appControllers/companyController/remove.js deleted file mode 100644 index f3257244e..000000000 --- a/backend/src/controllers/appControllers/companyController/remove.js +++ /dev/null @@ -1,57 +0,0 @@ -const mongoose = require('mongoose'); - -const Client = mongoose.model('Client'); -const People = mongoose.model('People'); - -const remove = async (Model, req, res) => { - // cannot delete client it it have one invoice or Client: - // check if client have invoice or quotes: - const { id } = req.params; - - // first find if there alt least one quote or invoice exist corresponding to the client - const client = await Client.findOne({ - company: id, - removed: false, - }).exec(); - if (client) { - return res.status(400).json({ - success: false, - result: null, - message: 'Cannot delete company if company attached to any people or she is client', - }); - } - const people = await People.findOne({ - company: id, - removed: false, - }).exec(); - if (people) { - return res.status(400).json({ - success: false, - result: null, - message: 'Cannot delete company if company attached to any people or she is client', - }); - } - - // if no People or quote, delete the client - const result = await Model.findOneAndUpdate( - { _id: id, removed: false }, - { - $set: { - removed: true, - }, - } - ).exec(); - if (!result) { - return res.status(404).json({ - success: false, - result: null, - message: 'No people found by this id: ' + id, - }); - } - return res.status(200).json({ - success: true, - result, - message: 'Successfully Deleted the people by id: ' + id, - }); -}; -module.exports = remove; diff --git a/backend/src/controllers/appControllers/companyController/update.js b/backend/src/controllers/appControllers/companyController/update.js deleted file mode 100644 index 7dc04138d..000000000 --- a/backend/src/controllers/appControllers/companyController/update.js +++ /dev/null @@ -1,44 +0,0 @@ -const mongoose = require('mongoose'); -const Client = mongoose.model('Client'); -const Lead = mongoose.model('People'); - -const update = async (Model, req, res) => { - // Find document by id and updates with the required fields - req.body.removed = false; - const result = await Model.findOneAndUpdate({ _id: req.params.id, removed: false }, req.body, { - new: true, // return the new result instead of the old one - runValidators: true, - }).exec(); - - if (!result) { - return res.status(404).json({ - success: false, - result: null, - message: 'No document found ', - }); - } else { - await Client.findOneAndUpdate( - { company: result._id }, - { name: result.name }, - { - new: true, // return the new result instead of the old one - } - ).exec(); - - await Lead.findOneAndUpdate( - { company: result._id }, - { name: result.name }, - { - new: true, // return the new result instead of the old one - } - ).exec(); - - return res.status(200).json({ - success: true, - result, - message: 'we update this document ', - }); - } -}; - -module.exports = update; diff --git a/backend/src/controllers/appControllers/invoiceController/remove.js b/backend/src/controllers/appControllers/invoiceController/remove.js index f4798289d..94bc7bfa6 100644 --- a/backend/src/controllers/appControllers/invoiceController/remove.js +++ b/backend/src/controllers/appControllers/invoiceController/remove.js @@ -1,13 +1,36 @@ const mongoose = require('mongoose'); const Model = mongoose.model('Invoice'); -const ModalPayment = mongoose.model('Payment'); +const ModelPayment = mongoose.model('Payment'); const remove = async (req, res) => { + const deletedInvoice = await Model.findOneAndUpdate( + { + _id: req.params.id, + removed: false, + }, + { + $set: { + removed: true, + }, + } + ).exec(); + + if (!deletedInvoice) { + return res.status(404).json({ + success: false, + result: null, + message: 'Invoice not found', + }); + } + const paymentsInvoices = await ModelPayment.updateMany( + { invoice: deletedInvoice._id }, + { $set: { removed: true } } + ); return res.status(200).json({ success: true, - result: null, - message: 'Please Upgrade to Premium Version to have full features', + result: deletedInvoice, + message: 'Invoice deleted successfully', }); }; diff --git a/backend/src/controllers/appControllers/invoiceController/update.js b/backend/src/controllers/appControllers/invoiceController/update.js index 8fe7f9b31..64bcbaa51 100644 --- a/backend/src/controllers/appControllers/invoiceController/update.js +++ b/backend/src/controllers/appControllers/invoiceController/update.js @@ -8,10 +8,75 @@ const { calculate } = require('@/helpers'); const schema = require('./schemaValidate'); const update = async (req, res) => { + let body = req.body; + + const { error, value } = schema.validate(body); + if (error) { + const { details } = error; + return res.status(400).json({ + success: false, + result: null, + message: details[0]?.message, + }); + } + + const previousInvoice = await Model.findOne({ + _id: req.params.id, + removed: false, + }); + + const { credit } = previousInvoice; + + const { items = [], taxRate = 0, discount = 0 } = req.body; + + if (items.length === 0) { + return res.status(400).json({ + success: false, + result: null, + message: 'Items cannot be empty', + }); + } + + // default + let subTotal = 0; + let taxTotal = 0; + let total = 0; + + //Calculate the items array with subTotal, total, taxTotal + items.map((item) => { + let total = calculate.multiply(item['quantity'], item['price']); + //sub total + subTotal = calculate.add(subTotal, total); + //item total + item['total'] = total; + }); + taxTotal = calculate.multiply(subTotal, taxRate / 100); + total = calculate.add(subTotal, taxTotal); + + body['subTotal'] = subTotal; + body['taxTotal'] = taxTotal; + body['total'] = total; + body['items'] = items; + body['pdf'] = 'invoice-' + req.params.id + '.pdf'; + if (body.hasOwnProperty('currency')) { + delete body.currency; + } + // Find document by id and updates with the required fields + + let paymentStatus = + calculate.sub(total, discount) === credit ? 'paid' : credit > 0 ? 'partially' : 'unpaid'; + body['paymentStatus'] = paymentStatus; + + const result = await Model.findOneAndUpdate({ _id: req.params.id, removed: false }, body, { + new: true, // return the new result instead of the old one + }).exec(); + + // Returning successfull response + return res.status(200).json({ success: true, - result: null, - message: 'Please Upgrade to Premium Version to have full features', + result, + message: 'we update this document ', }); }; diff --git a/backend/src/controllers/appControllers/leadController/create.js b/backend/src/controllers/appControllers/leadController/create.js deleted file mode 100644 index 2b70d7ab0..000000000 --- a/backend/src/controllers/appControllers/leadController/create.js +++ /dev/null @@ -1,51 +0,0 @@ -const mongoose = require('mongoose'); -const People = mongoose.model('People'); -const Company = mongoose.model('Company'); - -const create = async (Model, req, res) => { - // Creating a new document in the collection - - if (req.body.type === 'people') { - if (!req.body.people) { - return res.status(403).json({ - success: false, - message: 'Please select a people', - }); - } else { - let { firstname, lastname } = await People.findOne({ - _id: req.body.people, - removed: false, - }).exec(); - req.body.name = firstname + ' ' + lastname; - req.body.company = null; - } - } else { - if (!req.body.company) { - return res.status(403).json({ - success: false, - message: 'Please select a company', - }); - } else { - let { name } = await Company.findOne({ - _id: req.body.company, - removed: false, - }).exec(); - req.body.name = name; - req.body.people = null; - } - } - - req.body.removed = false; - const result = await new Model({ - ...req.body, - }).save(); - - // Returning successfull response - return res.status(200).json({ - success: true, - result, - message: 'Successfully Created the document in Model ', - }); -}; - -module.exports = create; diff --git a/backend/src/controllers/appControllers/leadController/index.js b/backend/src/controllers/appControllers/leadController/index.js deleted file mode 100644 index 59f036bc8..000000000 --- a/backend/src/controllers/appControllers/leadController/index.js +++ /dev/null @@ -1,29 +0,0 @@ -const mongoose = require('mongoose'); -const createCRUDController = require('@/controllers/middlewaresControllers/createCRUDController'); -const remove = require('./remove'); -const summary = require('./summary'); - -const create = require('./create'); -const update = require('./update'); -const read = require('./read'); -const search = require('./search'); - -const listAll = require('./listAll'); -const paginatedList = require('./paginatedList'); - -function modelController() { - const modelName = 'Lead'; - const Model = mongoose.model(modelName); - const methods = createCRUDController(modelName); - methods.read = (req, res) => read(Model, req, res); - methods.delete = (req, res) => remove(Model, req, res); - methods.list = (req, res) => paginatedList(Model, req, res); - methods.summary = (req, res) => summary(Model, req, res); - methods.create = (req, res) => create(Model, req, res); - methods.update = (req, res) => update(Model, req, res); - methods.search = (req, res) => search(Model, req, res); - methods.listAll = (req, res) => listAll(Model, req, res); - return methods; -} - -module.exports = modelController(); diff --git a/backend/src/controllers/appControllers/leadController/listAll.js b/backend/src/controllers/appControllers/leadController/listAll.js deleted file mode 100644 index 82a836d44..000000000 --- a/backend/src/controllers/appControllers/leadController/listAll.js +++ /dev/null @@ -1,30 +0,0 @@ -const { migrate } = require('./migrate'); - -const listAll = async (Model, req, res) => { - const sort = parseInt(req.query.sort) || 'desc'; - - // Query the database for a list of all results - const result = await Model.find({ - removed: false, - }) - .sort({ created: sort }) - .populate() - .exec(); - - const migratedData = result.map((x) => migrate(x)); - if (result.length > 0) { - return res.status(200).json({ - success: true, - result: migratedData, - message: 'Successfully found all documents', - }); - } else { - return res.status(203).json({ - success: true, - result: [], - message: 'Collection is Empty', - }); - } -}; - -module.exports = listAll; diff --git a/backend/src/controllers/appControllers/leadController/migrate.js b/backend/src/controllers/appControllers/leadController/migrate.js deleted file mode 100644 index 69fb308f6..000000000 --- a/backend/src/controllers/appControllers/leadController/migrate.js +++ /dev/null @@ -1,18 +0,0 @@ -exports.migrate = (result) => { - let lead = result.type === 'people' ? result.people : result.company; - let newData = {}; - newData._id = result._id; - newData.type = result.type; - newData.status = result.status; - newData.source = result.source; - newData.name = result.name; - newData.phone = lead.phone; - newData.email = lead.email; - newData.website = lead.website; - newData.country = lead.country; - newData.address = lead.address; - newData.people = result.people; - newData.company = result.company; - newData.notes = result.notes; - return newData; -}; diff --git a/backend/src/controllers/appControllers/leadController/paginatedList.js b/backend/src/controllers/appControllers/leadController/paginatedList.js deleted file mode 100644 index dc39e3001..000000000 --- a/backend/src/controllers/appControllers/leadController/paginatedList.js +++ /dev/null @@ -1,68 +0,0 @@ -const { migrate } = require('./migrate'); - -const paginatedList = async (Model, req, res) => { - const page = req.query.page || 1; - - const limit = parseInt(req.query.items) || 10; - const skip = page * limit - limit; - - const { sortBy = 'enabled', sortValue = -1, filter, equal } = req.query; - - const fieldsArray = req.query.fields ? req.query.fields.split(',') : []; - - let fields; - - fields = fieldsArray.length === 0 ? {} : { $or: [] }; - - for (const field of fieldsArray) { - fields.$or.push({ [field]: { $regex: new RegExp(req.query.q, 'i') } }); - } - - // Query the database for a list of all results - const resultsPromise = Model.find({ - removed: false, - - [filter]: equal, - ...fields, - }) - .skip(skip) - .limit(limit) - .sort({ [sortBy]: sortValue }) - .populate() - .exec(); - - // Counting the total documents - const countPromise = Model.countDocuments({ - removed: false, - - [filter]: equal, - ...fields, - }); - // Resolving both promises - const [result, count] = await Promise.all([resultsPromise, countPromise]); - // console.log('๐Ÿš€ ~ file: paginatedList.js:23 ~ paginatedList ~ result:', result); - - // Calculating total pages - const pages = Math.ceil(count / limit); - - const pagination = { page, pages, count }; - if (count > 0) { - const migratedData = result.map((x) => migrate(x)); - // console.log('๐Ÿš€ ~ file: paginatedList.js:23 ~ paginatedList ~ migratedData:', migratedData); - return res.status(200).json({ - success: true, - result: migratedData, - pagination, - message: 'Successfully found all documents', - }); - } else { - return res.status(203).json({ - success: true, - result: [], - pagination, - message: 'Collection is Empty', - }); - } -}; - -module.exports = paginatedList; diff --git a/backend/src/controllers/appControllers/leadController/read.js b/backend/src/controllers/appControllers/leadController/read.js deleted file mode 100644 index 9f6c27db0..000000000 --- a/backend/src/controllers/appControllers/leadController/read.js +++ /dev/null @@ -1,29 +0,0 @@ -const { migrate } = require('./migrate'); - -const read = async (Model, req, res) => { - // Find document by id - let result = await Model.findOne({ - _id: req.params.id, - removed: false, - }).exec(); - // If no results found, return document not found - if (!result) { - return res.status(404).json({ - success: false, - result: null, - message: 'No document found ', - }); - } else { - // Return success resposne - - const migratedData = migrate(result); - - return res.status(200).json({ - success: true, - result: migratedData, - message: 'we found this document ', - }); - } -}; - -module.exports = read; diff --git a/backend/src/controllers/appControllers/leadController/remove.js b/backend/src/controllers/appControllers/leadController/remove.js deleted file mode 100644 index 00021092d..000000000 --- a/backend/src/controllers/appControllers/leadController/remove.js +++ /dev/null @@ -1,10 +0,0 @@ -const mongoose = require('mongoose'); - -const remove = async (Model, req, res) => { - return res.status(200).json({ - success: true, - result: null, - message: 'Please Upgrade to Premium Version to have full features', - }); -}; -module.exports = remove; diff --git a/backend/src/controllers/appControllers/leadController/search.js b/backend/src/controllers/appControllers/leadController/search.js deleted file mode 100644 index e5ab7f765..000000000 --- a/backend/src/controllers/appControllers/leadController/search.js +++ /dev/null @@ -1,50 +0,0 @@ -const { migrate } = require('./migrate'); - -const search = async (Model, req, res) => { - // if (req.query.q === undefined || req.query.q.trim() === '') { - // return res - // .status(202) - // .json({ - // success: false, - // result: [], - // message: 'No document found by this request', - // }) - // .end(); - // } - const fieldsArray = req.query.fields ? req.query.fields.split(',') : ['name']; - - const fields = { $or: [] }; - - for (const field of fieldsArray) { - fields.$or.push({ [field]: { $regex: new RegExp(req.query.q, 'i') } }); - } - // console.log(fields) - - let results = await Model.find({ - ...fields, - }) - .where('removed', false) - .limit(20) - .exec(); - - const migratedData = results.map((x) => migrate(x)); - - if (results.length >= 1) { - return res.status(200).json({ - success: true, - result: migratedData, - message: 'Successfully found all documents', - }); - } else { - return res - .status(202) - .json({ - success: false, - result: [], - message: 'No document found by this request', - }) - .end(); - } -}; - -module.exports = search; diff --git a/backend/src/controllers/appControllers/leadController/summary.js b/backend/src/controllers/appControllers/leadController/summary.js deleted file mode 100644 index 8bc80d95a..000000000 --- a/backend/src/controllers/appControllers/leadController/summary.js +++ /dev/null @@ -1,97 +0,0 @@ -const mongoose = require('mongoose'); -const moment = require('moment'); - -const OfferModel = mongoose.model('Offer'); - -const summary = async (Model, req, res) => { - let defaultType = 'month'; - const { type } = req.query; - - if (type && ['week', 'month', 'year'].includes(type)) { - defaultType = type; - } else if (type) { - return res.status(400).json({ - success: false, - result: null, - message: 'Invalid type', - }); - } - - const currentDate = moment(); - let startDate = currentDate.clone().startOf(defaultType); - let endDate = currentDate.clone().endOf(defaultType); - - const pipeline = [ - { - $facet: { - totalClients: [ - { - $match: { - removed: false, - enabled: true, - }, - }, - { - $count: 'count', - }, - ], - newClients: [ - { - $match: { - removed: false, - created: { $gte: startDate.toDate(), $lte: endDate.toDate() }, - enabled: true, - }, - }, - { - $count: 'count', - }, - ], - activeClients: [ - { - $lookup: { - from: OfferModel.collection.name, - localField: '_id', // Match _id from ClientModel - foreignField: 'lead', // Match client field in OfferModel - as: 'offer', - }, - }, - { - $match: { - 'offer.removed': false, - }, - }, - { - $group: { - _id: '$_id', - }, - }, - { - $count: 'count', - }, - ], - }, - }, - ]; - - const aggregationResult = await Model.aggregate(pipeline); - - const result = aggregationResult[0]; - const totalClients = result.totalClients[0] ? result.totalClients[0].count : 0; - const totalNewClients = result.newClients[0] ? result.newClients[0].count : 0; - const activeClients = result.activeClients[0] ? result.activeClients[0].count : 0; - - const totalActiveClientsPercentage = totalClients > 0 ? (activeClients / totalClients) * 100 : 0; - const totalNewClientsPercentage = totalClients > 0 ? (totalNewClients / totalClients) * 100 : 0; - - return res.status(200).json({ - success: true, - result: { - new: Math.round(totalNewClientsPercentage), - active: Math.round(totalActiveClientsPercentage), - }, - message: 'Successfully get summary of new clients', - }); -}; - -module.exports = summary; diff --git a/backend/src/controllers/appControllers/leadController/update.js b/backend/src/controllers/appControllers/leadController/update.js deleted file mode 100644 index 002d5023b..000000000 --- a/backend/src/controllers/appControllers/leadController/update.js +++ /dev/null @@ -1,13 +0,0 @@ -const mongoose = require('mongoose'); -const People = mongoose.model('People'); -const Company = mongoose.model('Company'); - -const update = async (Model, req, res) => { - return res.status(200).json({ - success: true, - result: null, - message: 'Please Upgrade to Premium Version to have full features', - }); -}; - -module.exports = update; diff --git a/backend/src/controllers/appControllers/offerController/create.js b/backend/src/controllers/appControllers/offerController/create.js deleted file mode 100644 index 99ca822de..000000000 --- a/backend/src/controllers/appControllers/offerController/create.js +++ /dev/null @@ -1,61 +0,0 @@ -const mongoose = require('mongoose'); - -const Model = mongoose.model('Offer'); - -const custom = require('@/controllers/pdfController'); - -const { calculate } = require('@/helpers'); -const { increaseBySettingKey } = require('@/middlewares/settings'); - -const create = async (req, res) => { - const { items = [], taxRate = 0, discount = 0 } = req.body; - - // default - let subTotal = 0; - let taxTotal = 0; - let total = 0; - // let credit = 0; - - //Calculate the items array with subTotal, total, taxTotal - items.map((item) => { - let total = calculate.multiply(item['quantity'], item['price']); - //sub total - subTotal = calculate.add(subTotal, total); - //item total - item['total'] = total; - }); - taxTotal = calculate.multiply(subTotal, taxRate / 100); - total = calculate.add(subTotal, taxTotal); - - let body = req.body; - - body['subTotal'] = subTotal; - body['taxTotal'] = taxTotal; - body['total'] = total; - body['items'] = items; - body['createdBy'] = req.admin._id; - - // Creating a new document in the collection - const result = await new Model(body).save(); - const fileId = 'offer-' + result._id + '.pdf'; - const updateResult = await Model.findOneAndUpdate( - { _id: result._id }, - { pdf: fileId }, - { - new: true, - } - ).exec(); - // Returning successfull response - - increaseBySettingKey({ - settingKey: 'last_offer_number', - }); - - // Returning successfull response - return res.status(200).json({ - success: true, - result: updateResult, - message: 'Offer created successfully', - }); -}; -module.exports = create; diff --git a/backend/src/controllers/appControllers/offerController/index.js b/backend/src/controllers/appControllers/offerController/index.js deleted file mode 100644 index a9f7c2dd1..000000000 --- a/backend/src/controllers/appControllers/offerController/index.js +++ /dev/null @@ -1,18 +0,0 @@ -const createCRUDController = require('@/controllers/middlewaresControllers/createCRUDController'); -const methods = createCRUDController('Offer'); - -const create = require('./create'); -const summary = require('./summary'); -const update = require('./update'); -const paginatedList = require('./paginatedList'); -const read = require('./read'); -const sendMail = require('./sendMail'); - -methods.list = paginatedList; -methods.read = read; -methods.mail = sendMail; -methods.create = create; -methods.update = update; -methods.summary = summary; - -module.exports = methods; diff --git a/backend/src/controllers/appControllers/offerController/paginatedList.js b/backend/src/controllers/appControllers/offerController/paginatedList.js deleted file mode 100644 index 3fcae7c5a..000000000 --- a/backend/src/controllers/appControllers/offerController/paginatedList.js +++ /dev/null @@ -1,68 +0,0 @@ -const mongoose = require('mongoose'); - -const Model = mongoose.model('Offer'); - -const paginatedList = async (req, res) => { - const page = req.query.page || 1; - const limit = parseInt(req.query.items) || 10; - const skip = page * limit - limit; - - // Query the database for a list of all results - const { sortBy = 'enabled', sortValue = -1, filter, equal } = req.query; - - const fieldsArray = req.query.fields ? req.query.fields.split(',') : []; - - let fields; - - fields = fieldsArray.length === 0 ? {} : { $or: [] }; - - for (const field of fieldsArray) { - fields.$or.push({ [field]: { $regex: new RegExp(req.query.q, 'i') } }); - } - - // Query the database for a list of all results - const resultsPromise = Model.find({ - removed: false, - - [filter]: equal, - ...fields, - }) - .skip(skip) - .limit(limit) - .sort({ [sortBy]: sortValue }) - .populate('createdBy', 'name') - .exec(); - - // Counting the total documents - const countPromise = Model.countDocuments({ - removed: false, - - [filter]: equal, - ...fields, - }); - - // Resolving both promises - const [result, count] = await Promise.all([resultsPromise, countPromise]); - // Calculating total pages - const pages = Math.ceil(count / limit); - - // Getting Pagination Object - const pagination = { page, pages, count }; - if (count > 0) { - return res.status(200).json({ - success: true, - result, - pagination, - message: 'Successfully found all documents', - }); - } else { - return res.status(203).json({ - success: true, - result: [], - pagination, - message: 'Collection is Empty', - }); - } -}; - -module.exports = paginatedList; diff --git a/backend/src/controllers/appControllers/offerController/read.js b/backend/src/controllers/appControllers/offerController/read.js deleted file mode 100644 index c74e7dd8a..000000000 --- a/backend/src/controllers/appControllers/offerController/read.js +++ /dev/null @@ -1,30 +0,0 @@ -const mongoose = require('mongoose'); - -const Model = mongoose.model('Offer'); - -const read = async (req, res) => { - // Find document by id - const result = await Model.findOne({ - _id: req.params.id, - removed: false, - }) - .populate('createdBy', 'name') - .exec(); - // If no results found, return document not found - if (!result) { - return res.status(404).json({ - success: false, - result: null, - message: 'No document found ', - }); - } else { - // Return success resposne - return res.status(200).json({ - success: true, - result, - message: 'we found this document ', - }); - } -}; - -module.exports = read; diff --git a/backend/src/controllers/appControllers/offerController/sendMail.js b/backend/src/controllers/appControllers/offerController/sendMail.js deleted file mode 100644 index 5cdabe784..000000000 --- a/backend/src/controllers/appControllers/offerController/sendMail.js +++ /dev/null @@ -1,9 +0,0 @@ -const mail = async (req, res) => { - return res.status(200).json({ - success: true, - result: null, - message: 'Please Upgrade to Premium Version to have full features', - }); -}; - -module.exports = mail; diff --git a/backend/src/controllers/appControllers/offerController/summary.js b/backend/src/controllers/appControllers/offerController/summary.js deleted file mode 100644 index 56f63b946..000000000 --- a/backend/src/controllers/appControllers/offerController/summary.js +++ /dev/null @@ -1,123 +0,0 @@ -const mongoose = require('mongoose'); -const moment = require('moment'); - -const Model = mongoose.model('Offer'); - -const summary = async (req, res) => { - let defaultType = 'month'; - - const { type } = req.query; - - if (type) { - if (['week', 'month', 'year'].includes(type)) { - defaultType = type; - } else { - return res.status(400).json({ - success: false, - result: null, - message: 'Invalid type', - }); - } - } - - const currentDate = moment(); - let startDate = currentDate.clone().startOf(defaultType); - let endDate = currentDate.clone().endOf(defaultType); - - const statuses = ['draft', 'pending', 'sent', 'expired', 'declined', 'accepted']; - - const response = await Model.aggregate([ - { - $match: { - removed: false, - - // date: { - // $gte: startDate.toDate(), - // $lte: endDate.toDate(), - // }, - }, - }, - { - $facet: { - totalOffer: [ - { - $group: { - _id: null, - total: { - $sum: '$total', - }, - count: { - $sum: 1, - }, - }, - }, - { - $project: { - _id: 0, - total: '$total', - count: '$count', - }, - }, - ], - statusCounts: [ - { - $group: { - _id: '$status', - count: { - $sum: 1, - }, - }, - }, - { - $project: { - _id: 0, - status: '$_id', - count: '$count', - }, - }, - ], - }, - }, - ]); - let result = []; - - const totalOffers = response[0].totalOffer ? response[0].totalOffer[0] : 0; - const statusResult = response[0].statusCounts || []; - // const overdueResult = response[0].overdueCounts || []; - - const statusResultMap = statusResult.map((item) => { - return { - ...item, - percentage: Math.round((item.count / totalOffers.count) * 100), - }; - }); - - // const overdueResultMap = overdueResult.map((item) => { - // return { - // ...item, - // status: 'expired', - // percentage: Math.round((item.count / totalOffers.count) * 100), - // }; - // }); - - statuses.forEach((status) => { - const found = [...statusResultMap].find((item) => item.status === status); - if (found) { - result.push(found); - } - }); - - const finalResult = { - total: totalOffers?.total, - type, - performance: result, - }; - - return res.status(200).json({ - success: true, - result: finalResult, - message: `Successfully found all invoices for the last ${defaultType}`, - }); -}; - -module.exports = summary; diff --git a/backend/src/controllers/appControllers/offerController/update.js b/backend/src/controllers/appControllers/offerController/update.js deleted file mode 100644 index 5d0265e4a..000000000 --- a/backend/src/controllers/appControllers/offerController/update.js +++ /dev/null @@ -1,10 +0,0 @@ -const mongoose = require('mongoose'); - -const update = async (req, res) => { - return res.status(200).json({ - success: true, - result: null, - message: 'Please Upgrade to Premium Version to have full features', - }); -}; -module.exports = update; diff --git a/backend/src/controllers/appControllers/paymentController/remove.js b/backend/src/controllers/appControllers/paymentController/remove.js index 5d329047e..93939da85 100644 --- a/backend/src/controllers/appControllers/paymentController/remove.js +++ b/backend/src/controllers/appControllers/paymentController/remove.js @@ -1,8 +1,67 @@ +const mongoose = require('mongoose'); + +const Model = mongoose.model('Payment'); +const Invoice = mongoose.model('Invoice'); + const remove = async (req, res) => { + // Find document by id and updates with the required fields + const previousPayment = await Model.findOne({ + _id: req.params.id, + removed: false, + }); + + if (!previousPayment) { + return res.status(404).json({ + success: false, + result: null, + message: 'No document found ', + }); + } + + const { _id: paymentId, amount: previousAmount } = previousPayment; + const { id: invoiceId, total, discount, credit: previousCredit } = previousPayment.invoice; + + // Find the document by id and delete it + let updates = { + removed: true, + }; + // Find the document by id and delete it + const result = await Model.findOneAndUpdate( + { _id: req.params.id, removed: false }, + { $set: updates }, + { + new: true, // return the new result instead of the old one + } + ).exec(); + // If no results found, return document not found + + let paymentStatus = + total - discount === previousCredit - previousAmount + ? 'paid' + : previousCredit - previousAmount > 0 + ? 'partially' + : 'unpaid'; + + const updateInvoice = await Invoice.findOneAndUpdate( + { _id: invoiceId }, + { + $pull: { + payment: paymentId, + }, + $inc: { credit: -previousAmount }, + $set: { + paymentStatus: paymentStatus, + }, + }, + { + new: true, // return the new result instead of the old one + } + ).exec(); + return res.status(200).json({ success: true, - result: null, - message: 'Please Upgrade to Premium Version to have full features', + result, + message: 'Successfully Deleted the document ', }); }; module.exports = remove; diff --git a/backend/src/controllers/appControllers/paymentController/update.js b/backend/src/controllers/appControllers/paymentController/update.js index ca4f0c3b7..0691048eb 100644 --- a/backend/src/controllers/appControllers/paymentController/update.js +++ b/backend/src/controllers/appControllers/paymentController/update.js @@ -1,8 +1,85 @@ +const mongoose = require('mongoose'); + +const Model = mongoose.model('Payment'); +const Invoice = mongoose.model('Invoice'); +const custom = require('@/controllers/pdfController'); + +const { calculate } = require('@/helpers'); + const update = async (req, res) => { + if (req.body.amount === 0) { + return res.status(202).json({ + success: false, + result: null, + message: `The Minimum Amount couldn't be 0`, + }); + } + // Find document by id and updates with the required fields + const previousPayment = await Model.findOne({ + _id: req.params.id, + removed: false, + }); + + const { amount: previousAmount } = previousPayment; + const { id: invoiceId, total, discount, credit: previousCredit } = previousPayment.invoice; + + const { amount: currentAmount } = req.body; + + const changedAmount = calculate.sub(currentAmount, previousAmount); + const maxAmount = calculate.sub(total, calculate.add(discount, previousCredit)); + + if (changedAmount > maxAmount) { + return res.status(202).json({ + success: false, + result: null, + message: `The Max Amount you can add is ${maxAmount + previousAmount}`, + error: `The Max Amount you can add is ${maxAmount + previousAmount}`, + }); + } + + let paymentStatus = + calculate.sub(total, discount) === calculate.add(previousCredit, changedAmount) + ? 'paid' + : calculate.add(previousCredit, changedAmount) > 0 + ? 'partially' + : 'unpaid'; + + const updatedDate = new Date(); + const updates = { + number: req.body.number, + date: req.body.date, + amount: req.body.amount, + paymentMode: req.body.paymentMode, + ref: req.body.ref, + description: req.body.description, + updated: updatedDate, + }; + + const result = await Model.findOneAndUpdate( + { _id: req.params.id, removed: false }, + { $set: updates }, + { + new: true, // return the new result instead of the old one + } + ).exec(); + + const updateInvoice = await Invoice.findOneAndUpdate( + { _id: result.invoice._id.toString() }, + { + $inc: { credit: changedAmount }, + $set: { + paymentStatus: paymentStatus, + }, + }, + { + new: true, // return the new result instead of the old one + } + ).exec(); + return res.status(200).json({ success: true, - result: null, - message: 'Please Upgrade to Premium Version to have full features', + result, + message: 'Successfully updated the Payment ', }); }; diff --git a/backend/src/controllers/appControllers/peopleController/index.js b/backend/src/controllers/appControllers/peopleController/index.js deleted file mode 100644 index 7adf66966..000000000 --- a/backend/src/controllers/appControllers/peopleController/index.js +++ /dev/null @@ -1,21 +0,0 @@ -const mongoose = require('mongoose'); - -const createCRUDController = require('@/controllers/middlewaresControllers/createCRUDController'); -const read = require('./read'); -const remove = require('./remove'); -const update = require('./update'); -const paginatedList = require('./paginatedList'); - -function modelController() { - const Model = mongoose.model('People'); - const methods = createCRUDController('People'); - - methods.read = (req, res) => read(Model, req, res); - methods.update = (req, res) => update(Model, req, res); - methods.delete = (req, res) => remove(Model, req, res); - methods.list = (req, res) => paginatedList(Model, req, res); - - return methods; -} - -module.exports = modelController(); diff --git a/backend/src/controllers/appControllers/peopleController/paginatedList.js b/backend/src/controllers/appControllers/peopleController/paginatedList.js deleted file mode 100644 index 43b6fe28c..000000000 --- a/backend/src/controllers/appControllers/peopleController/paginatedList.js +++ /dev/null @@ -1,64 +0,0 @@ -const paginatedList = async (Model, req, res) => { - const page = req.query.page || 1; - const limit = parseInt(req.query.items) || 10; - const skip = page * limit - limit; - - const { sortBy = 'enabled', sortValue = -1, filter, equal } = req.query; - - const fieldsArray = req.query.fields ? req.query.fields.split(',') : []; - - let fields; - - fields = fieldsArray.length === 0 ? {} : { $or: [] }; - - for (const field of fieldsArray) { - fields.$or.push({ [field]: { $regex: new RegExp(req.query.q, 'i') } }); - } - - // Query the database for a list of all results - const resultsPromise = Model.find({ - removed: false, - - [filter]: equal, - ...fields, - }) - .skip(skip) - .limit(limit) - .sort({ [sortBy]: sortValue }) - .populate('company', 'name') - .exec(); - - // Counting the total documents - const countPromise = Model.countDocuments({ - removed: false, - - [filter]: equal, - ...fields, - }); - - // Resolving both promises - const [result, count] = await Promise.all([resultsPromise, countPromise]); - - // Calculating total pages - const pages = Math.ceil(count / limit); - - // Getting Pagination Object - const pagination = { page, pages, count }; - if (count > 0) { - return res.status(200).json({ - success: true, - result, - pagination, - message: 'Successfully found all documents', - }); - } else { - return res.status(203).json({ - success: true, - result: [], - pagination, - message: 'Collection is Empty', - }); - } -}; - -module.exports = paginatedList; diff --git a/backend/src/controllers/appControllers/peopleController/read.js b/backend/src/controllers/appControllers/peopleController/read.js deleted file mode 100644 index 8b0f39786..000000000 --- a/backend/src/controllers/appControllers/peopleController/read.js +++ /dev/null @@ -1,26 +0,0 @@ -const read = async (Model, req, res) => { - // Find document by id - const result = await Model.findOne({ - _id: req.params.id, - removed: false, - }) - .populate('company', 'name') - .exec(); - // If no results found, return document not found - if (!result) { - return res.status(404).json({ - success: false, - result: null, - message: 'No document found ', - }); - } else { - // Return success resposne - return res.status(200).json({ - success: true, - result, - message: 'we found this document ', - }); - } -}; - -module.exports = read; diff --git a/backend/src/controllers/appControllers/peopleController/remove.js b/backend/src/controllers/appControllers/peopleController/remove.js deleted file mode 100644 index 7e851a28e..000000000 --- a/backend/src/controllers/appControllers/peopleController/remove.js +++ /dev/null @@ -1,8 +0,0 @@ -const remove = async (Model, req, res) => { - return res.status(200).json({ - success: true, - result: null, - message: 'Please Upgrade to Premium Version to have full features', - }); -}; -module.exports = remove; diff --git a/backend/src/controllers/appControllers/peopleController/update.js b/backend/src/controllers/appControllers/peopleController/update.js deleted file mode 100644 index 7a2511b4d..000000000 --- a/backend/src/controllers/appControllers/peopleController/update.js +++ /dev/null @@ -1,13 +0,0 @@ -const mongoose = require('mongoose'); -const Client = mongoose.model('Client'); -const Lead = mongoose.model('People'); - -const update = async (Model, req, res) => { - return res.status(200).json({ - success: true, - result: null, - message: 'Please Upgrade to Premium Version to have full features', - }); -}; - -module.exports = update; diff --git a/backend/src/controllers/appControllers/quoteController/update.js b/backend/src/controllers/appControllers/quoteController/update.js index a649e634d..a4f32696d 100644 --- a/backend/src/controllers/appControllers/quoteController/update.js +++ b/backend/src/controllers/appControllers/quoteController/update.js @@ -1,8 +1,61 @@ +const mongoose = require('mongoose'); + +const Model = mongoose.model('Quote'); + +const custom = require('@/controllers/pdfController'); + +const { calculate } = require('@/helpers'); + const update = async (req, res) => { + const { items = [], taxRate = 0, discount = 0 } = req.body; + + if (items.length === 0) { + return res.status(400).json({ + success: false, + result: null, + message: 'Items cannot be empty', + }); + } + // default + let subTotal = 0; + let taxTotal = 0; + let total = 0; + // let credit = 0; + + //Calculate the items array with subTotal, total, taxTotal + items.map((item) => { + let total = calculate.multiply(item['quantity'], item['price']); + //sub total + subTotal = calculate.add(subTotal, total); + //item total + item['total'] = total; + }); + taxTotal = calculate.multiply(subTotal, taxRate / 100); + total = calculate.add(subTotal, taxTotal); + + let body = req.body; + + body['subTotal'] = subTotal; + body['taxTotal'] = taxTotal; + body['total'] = total; + body['items'] = items; + body['pdf'] = 'quote-' + req.params.id + '.pdf'; + + if (body.hasOwnProperty('currency')) { + delete body.currency; + } + // Find document by id and updates with the required fields + + const result = await Model.findOneAndUpdate({ _id: req.params.id, removed: false }, body, { + new: true, // return the new result instead of the old one + }).exec(); + + // Returning successfull response + return res.status(200).json({ success: true, - result: null, - message: 'Please Upgrade to Premium Version to have full features', + result, + message: 'we update this document ', }); }; module.exports = update; diff --git a/backend/src/controllers/appControllers/taxesController/index.js b/backend/src/controllers/appControllers/taxesController/index.js index 4f6f167dc..ba31f817a 100644 --- a/backend/src/controllers/appControllers/taxesController/index.js +++ b/backend/src/controllers/appControllers/taxesController/index.js @@ -38,10 +38,43 @@ methods.delete = async (req, res) => { }; methods.update = async (req, res) => { + const { id } = req.params; + const tax = await Model.findOne({ + _id: req.params.id, + removed: false, + }).exec(); + const { isDefault = tax.isDefault, enabled = tax.enabled } = req.body; + + // if isDefault:false , we update first - isDefault:true + // if enabled:false and isDefault:true , we update first - isDefault:true + if (!isDefault || (!enabled && isDefault)) { + await Model.findOneAndUpdate({ _id: { $ne: id }, enabled: true }, { isDefault: true }); + } + + // if isDefault:true and enabled:true, we update other taxes and make is isDefault:false + if (isDefault && enabled) { + await Model.updateMany({ _id: { $ne: id } }, { isDefault: false }); + } + + const taxesCount = await Model.countDocuments({}); + + // if enabled:false and it's only one exist, we can't disable + if ((!enabled || !isDefault) && taxesCount <= 1) { + return res.status(422).json({ + success: false, + result: null, + message: 'You cannot disable the tax because it is the only existing one', + }); + } + + const result = await Model.findOneAndUpdate({ _id: id }, req.body, { + new: true, + }); + return res.status(200).json({ success: true, - result: null, - message: 'Please Upgrade to Premium Version to have full features', + message: 'Tax updated successfully', + result, }); }; diff --git a/backend/src/controllers/coreControllers/emailController/index.js b/backend/src/controllers/coreControllers/emailController/index.js deleted file mode 100644 index 98072d97c..000000000 --- a/backend/src/controllers/coreControllers/emailController/index.js +++ /dev/null @@ -1,14 +0,0 @@ -const createCRUDController = require('@/controllers/middlewaresControllers/createCRUDController'); -const crudController = createCRUDController('Email'); - -const emailMethods = { - create:crudController.create, - read: crudController.read, - update: crudController.update, - list: crudController.list, - listAll: crudController.listAll, - filter: crudController.filter, - search: crudController.search, -}; - -module.exports = emailMethods; diff --git a/backend/src/controllers/coreControllers/settingController/updateBySettingKey.js b/backend/src/controllers/coreControllers/settingController/updateBySettingKey.js index d93a0c317..f6e1aebc1 100644 --- a/backend/src/controllers/coreControllers/settingController/updateBySettingKey.js +++ b/backend/src/controllers/coreControllers/settingController/updateBySettingKey.js @@ -1,9 +1,49 @@ +const mongoose = require('mongoose'); + +const Model = mongoose.model('Setting'); + const updateBySettingKey = async (req, res) => { - return res.status(200).json({ - success: true, - result: null, - message: 'Please Upgrade to Premium Version to have full features', - }); + const settingKey = req.params.settingKey || undefined; + + if (!settingKey) { + return res.status(202).json({ + success: false, + result: null, + message: 'No settingKey provided ', + }); + } + const { settingValue } = req.body; + + if (!settingValue) { + return res.status(202).json({ + success: false, + result: null, + message: 'No settingValue provided ', + }); + } + const result = await Model.findOneAndUpdate( + { settingKey }, + { + settingValue, + }, + { + new: true, // return the new result instead of the old one + runValidators: true, + } + ).exec(); + if (!result) { + return res.status(404).json({ + success: false, + result: null, + message: 'No document found by this settingKey: ' + settingKey, + }); + } else { + return res.status(200).json({ + success: true, + result, + message: 'we update this document by this settingKey: ' + settingKey, + }); + } }; module.exports = updateBySettingKey; diff --git a/backend/src/controllers/coreControllers/settingController/updateManySetting.js b/backend/src/controllers/coreControllers/settingController/updateManySetting.js index 39b99af8e..cdd7c41f2 100644 --- a/backend/src/controllers/coreControllers/settingController/updateManySetting.js +++ b/backend/src/controllers/coreControllers/settingController/updateManySetting.js @@ -1,9 +1,58 @@ +const mongoose = require('mongoose'); + +const Model = mongoose.model('Setting'); + const updateManySetting = async (req, res) => { - return res.status(200).json({ - success: true, - result: null, - message: 'Please Upgrade to Premium Version to have full features', - }); + // req/body = [{settingKey:"",settingValue}] + let settingsHasError = false; + const updateDataArray = []; + const { settings } = req.body; + + for (const setting of settings) { + if (!setting.hasOwnProperty('settingKey') || !setting.hasOwnProperty('settingValue')) { + settingsHasError = true; + break; + } + + const { settingKey, settingValue } = setting; + + updateDataArray.push({ + updateOne: { + filter: { settingKey: settingKey }, + update: { settingValue: settingValue }, + }, + }); + } + + if (updateDataArray.length === 0) { + return res.status(202).json({ + success: false, + result: null, + message: 'No settings provided ', + }); + } + if (settingsHasError) { + return res.status(202).json({ + success: false, + result: null, + message: 'Settings provided has Error', + }); + } + const result = await Model.bulkWrite(updateDataArray); + + if (!result || result.nMatched < 1) { + return res.status(404).json({ + success: false, + result: null, + message: 'No settings found by to update', + }); + } else { + return res.status(200).json({ + success: true, + result: [], + message: 'we update all settings', + }); + } }; module.exports = updateManySetting; diff --git a/backend/src/controllers/middlewaresControllers/createAuthMiddleware/authUser.js b/backend/src/controllers/middlewaresControllers/createAuthMiddleware/authUser.js index e9cbe7359..546d56311 100644 --- a/backend/src/controllers/middlewaresControllers/createAuthMiddleware/authUser.js +++ b/backend/src/controllers/middlewaresControllers/createAuthMiddleware/authUser.js @@ -28,29 +28,29 @@ const authUser = async (req, res, { user, databasePassword, password, UserPasswo } ).exec(); - res - .status(200) - .cookie('token', token, { - maxAge: req.body.remember ? 365 * 24 * 60 * 60 * 1000 : null, - sameSite: 'Lax', - httpOnly: true, - secure: false, - domain: req.hostname, - path: '/', - Partitioned: true, - }) - .json({ - success: true, - result: { - _id: user._id, - name: user.name, - surname: user.surname, - role: user.role, - email: user.email, - photo: user.photo, - }, - message: 'Successfully login user', - }); + // .cookie(`token_${user.cloud}`, token, { + // maxAge: req.body.remember ? 365 * 24 * 60 * 60 * 1000 : null, + // sameSite: 'None', + // httpOnly: true, + // secure: true, + // domain: req.hostname, + // path: '/', + // Partitioned: true, + // }) + res.status(200).json({ + success: true, + result: { + _id: user._id, + name: user.name, + surname: user.surname, + role: user.role, + email: user.email, + photo: user.photo, + token: token, + maxAge: req.body.remember ? 365 : null, + }, + message: 'Successfully login user', + }); } else { return res.status(403).json({ success: false, diff --git a/backend/src/controllers/middlewaresControllers/createAuthMiddleware/forgetPassword.js b/backend/src/controllers/middlewaresControllers/createAuthMiddleware/forgetPassword.js index ec90dfe84..176cdd1ce 100644 --- a/backend/src/controllers/middlewaresControllers/createAuthMiddleware/forgetPassword.js +++ b/backend/src/controllers/middlewaresControllers/createAuthMiddleware/forgetPassword.js @@ -35,13 +35,6 @@ const forgetPassword = async (req, res, { userModel }) => { const user = await User.findOne({ email: email, removed: false }); const databasePassword = await UserPassword.findOne({ user: user._id, removed: false }); - if (!user.enabled) - return res.status(409).json({ - success: false, - result: null, - message: 'Your account is disabled, contact your account adminstrator', - }); - // console.log(user); if (!user) return res.status(404).json({ diff --git a/backend/src/controllers/middlewaresControllers/createAuthMiddleware/isValidAuthToken.js b/backend/src/controllers/middlewaresControllers/createAuthMiddleware/isValidAuthToken.js index ce79f4829..33a963f5e 100644 --- a/backend/src/controllers/middlewaresControllers/createAuthMiddleware/isValidAuthToken.js +++ b/backend/src/controllers/middlewaresControllers/createAuthMiddleware/isValidAuthToken.js @@ -6,7 +6,11 @@ const isValidAuthToken = async (req, res, next, { userModel, jwtSecret = 'JWT_SE try { const UserPassword = mongoose.model(userModel + 'Password'); const User = mongoose.model(userModel); - const token = req.cookies.token; + + // const token = req.cookies[`token_${cloud._id}`]; + const authHeader = req.headers['authorization']; + const token = authHeader && authHeader.split(' ')[1]; // Extract the token + if (!token) return res.status(401).json({ success: false, @@ -39,6 +43,7 @@ const isValidAuthToken = async (req, res, next, { userModel, jwtSecret = 'JWT_SE }); const { loggedSessions } = userPassword; + if (!loggedSessions.includes(token)) return res.status(401).json({ success: false, @@ -52,12 +57,13 @@ const isValidAuthToken = async (req, res, next, { userModel, jwtSecret = 'JWT_SE next(); } } catch (error) { - return res.status(503).json({ + return res.status(500).json({ success: false, result: null, message: error.message, error: error, controller: 'isValidAuthToken', + jwtExpired: true, }); } }; diff --git a/backend/src/controllers/middlewaresControllers/createAuthMiddleware/login.js b/backend/src/controllers/middlewaresControllers/createAuthMiddleware/login.js index 7b57c615e..7b1eccee8 100644 --- a/backend/src/controllers/middlewaresControllers/createAuthMiddleware/login.js +++ b/backend/src/controllers/middlewaresControllers/createAuthMiddleware/login.js @@ -1,15 +1,7 @@ -const bcrypt = require('bcryptjs'); -const jwt = require('jsonwebtoken'); const Joi = require('joi'); const mongoose = require('mongoose'); -const checkAndCorrectURL = require('./checkAndCorrectURL'); -const sendMail = require('./sendMail'); - -const { loadSettings } = require('@/middlewares/settings'); -const { useAppSettings } = require('@/settings'); - const authUser = require('./authUser'); const login = async (req, res, { userModel }) => { @@ -56,7 +48,12 @@ const login = async (req, res, { userModel }) => { }); // authUser if your has correct password - authUser(req, res, { user, databasePassword, password, UserPasswordModel }); + authUser(req, res, { + user, + databasePassword, + password, + UserPasswordModel, + }); }; module.exports = login; diff --git a/backend/src/controllers/middlewaresControllers/createAuthMiddleware/logout.js b/backend/src/controllers/middlewaresControllers/createAuthMiddleware/logout.js index 253bf753a..9eea8aedd 100644 --- a/backend/src/controllers/middlewaresControllers/createAuthMiddleware/logout.js +++ b/backend/src/controllers/middlewaresControllers/createAuthMiddleware/logout.js @@ -3,29 +3,33 @@ const mongoose = require('mongoose'); const logout = async (req, res, { userModel }) => { const UserPassword = mongoose.model(userModel + 'Password'); - const token = req.cookies.token; - await UserPassword.findOneAndUpdate( - { user: req.admin._id }, - { $pull: { loggedSessions: token } }, - { - new: true, - } - ).exec(); + // const token = req.cookies[`token_${cloud._id}`]; - res - .clearCookie('token', { - maxAge: null, - sameSite: 'none', - httpOnly: true, - secure: true, - domain: req.hostname, - Path: '/', - }) - .json({ - success: true, - result: {}, - message: 'Successfully logout', - }); + const authHeader = req.headers['authorization']; + const token = authHeader && authHeader.split(' ')[1]; // Extract the token + + if (token) + await UserPassword.findOneAndUpdate( + { user: req.admin._id }, + { $pull: { loggedSessions: token } }, + { + new: true, + } + ).exec(); + else + await UserPassword.findOneAndUpdate( + { user: req.admin._id }, + { loggedSessions: [] }, + { + new: true, + } + ).exec(); + + return res.json({ + success: true, + result: {}, + message: 'Successfully logout', + }); }; module.exports = logout; diff --git a/backend/src/controllers/middlewaresControllers/createAuthMiddleware/resetPassword.js b/backend/src/controllers/middlewaresControllers/createAuthMiddleware/resetPassword.js index de8eb1a3a..d9bb0a811 100644 --- a/backend/src/controllers/middlewaresControllers/createAuthMiddleware/resetPassword.js +++ b/backend/src/controllers/middlewaresControllers/createAuthMiddleware/resetPassword.js @@ -13,31 +13,6 @@ const resetPassword = async (req, res, { userModel }) => { const databasePassword = await UserPassword.findOne({ user: userId, removed: false }); const user = await User.findOne({ _id: userId, removed: false }).exec(); - if (!user.enabled && user.role === 'owner') { - const settings = useAppSettings(); - const idurar_app_email = settings['idurar_app_email']; - const idurar_base_url = settings['idurar_base_url']; - - const url = checkAndCorrectURL(idurar_base_url); - - const link = url + '/verify/' + user._id + '/' + databasePassword.emailToken; - - await sendMail({ - email, - name: user.name, - link, - idurar_app_email, - emailToken: databasePassword.emailToken, - }); - - return res.status(403).json({ - success: false, - result: null, - message: - 'your email account is not verified , check your email inbox to activate your account', - }); - } - if (!user.enabled) return res.status(409).json({ success: false, @@ -110,29 +85,29 @@ const resetPassword = async (req, res, { userModel }) => { databasePassword.resetToken !== undefined && databasePassword.resetToken !== null ) - return res - .status(200) - .cookie('token', token, { - maxAge: 24 * 60 * 60 * 1000, - sameSite: 'Lax', - httpOnly: true, - secure: false, - domain: req.hostname, - path: '/', - Partitioned: true, - }) - .json({ - success: true, - result: { - _id: user._id, - name: user.name, - surname: user.surname, - role: user.role, - email: user.email, - photo: user.photo, - }, - message: 'Successfully resetPassword user', - }); + // .cookie(`token_${user.cloud}`, token, { + // maxAge: 24 * 60 * 60 * 1000, + // sameSite: 'None', + // httpOnly: true, + // secure: true, + // domain: req.hostname, + // path: '/', + // Partitioned: true, + // }) + return res.status(200).json({ + success: true, + result: { + _id: user._id, + name: user.name, + surname: user.surname, + role: user.role, + email: user.email, + photo: user.photo, + token: token, + maxAge: req.body.remember ? 365 : null, + }, + message: 'Successfully resetPassword user', + }); }; module.exports = resetPassword; diff --git a/backend/src/controllers/middlewaresControllers/createAuthMiddleware/sendIdurarOffer.js b/backend/src/controllers/middlewaresControllers/createAuthMiddleware/sendIdurarOffer.js deleted file mode 100644 index c2826e4fb..000000000 --- a/backend/src/controllers/middlewaresControllers/createAuthMiddleware/sendIdurarOffer.js +++ /dev/null @@ -1,18 +0,0 @@ -const { afterRegistrationSuccess } = require('@/emailTemplate/emailVerfication'); - -const { Resend } = require('resend'); - -const sendIdurarOffer = async ({ email, name }) => { - const resend = new Resend(process.env.RESEND_API); - - const { data } = await resend.emails.send({ - from: 'hello@idurarapp.com', - to: email, - subject: 'Customize IDURAR ERP CRM or build your own SaaS', - html: afterRegistrationSuccess({ name }), - }); - - return data; -}; - -module.exports = sendIdurarOffer; diff --git a/backend/src/controllers/middlewaresControllers/createAuthMiddleware/sendMail.js b/backend/src/controllers/middlewaresControllers/createAuthMiddleware/sendMail.js index c1d85fa0c..b16d2e487 100644 --- a/backend/src/controllers/middlewaresControllers/createAuthMiddleware/sendMail.js +++ b/backend/src/controllers/middlewaresControllers/createAuthMiddleware/sendMail.js @@ -1,4 +1,4 @@ -const { emailVerfication, passwordVerfication } = require('@/emailTemplate/emailVerfication'); +const { passwordVerfication } = require('@/emailTemplate/emailVerfication'); const { Resend } = require('resend'); @@ -17,10 +17,7 @@ const sendMail = async ({ from: idurar_app_email, to: email, subject, - html: - type === 'emailVerfication' - ? emailVerfication({ name, link, emailToken }) - : passwordVerfication({ name, link }), + html: passwordVerfication({ name, link }), }); return data; diff --git a/backend/src/controllers/middlewaresControllers/createCRUDController/remove.js b/backend/src/controllers/middlewaresControllers/createCRUDController/remove.js index c39be4395..5072eae18 100644 --- a/backend/src/controllers/middlewaresControllers/createCRUDController/remove.js +++ b/backend/src/controllers/middlewaresControllers/createCRUDController/remove.js @@ -1,9 +1,32 @@ const remove = async (Model, req, res) => { - return res.status(200).json({ - success: true, - result: null, - message: 'Please Upgrade to Premium Version to have full features', - }); + // Find the document by id and delete it + let updates = { + removed: true, + }; + // Find the document by id and delete it + const result = await Model.findOneAndUpdate( + { + _id: req.params.id, + }, + { $set: updates }, + { + new: true, // return the new result instead of the old one + } + ).exec(); + // If no results found, return document not found + if (!result) { + return res.status(404).json({ + success: false, + result: null, + message: 'No document found ', + }); + } else { + return res.status(200).json({ + success: true, + result, + message: 'Successfully Deleted the document ', + }); + } }; module.exports = remove; diff --git a/backend/src/controllers/middlewaresControllers/createCRUDController/update.js b/backend/src/controllers/middlewaresControllers/createCRUDController/update.js index 2e4e0f977..8438598d6 100644 --- a/backend/src/controllers/middlewaresControllers/createCRUDController/update.js +++ b/backend/src/controllers/middlewaresControllers/createCRUDController/update.js @@ -1,9 +1,30 @@ const update = async (Model, req, res) => { - return res.status(200).json({ - success: true, - result: null, - message: 'Please Upgrade to Premium Version to have full features', - }); + // Find document by id and updates with the required fields + req.body.removed = false; + const result = await Model.findOneAndUpdate( + { + _id: req.params.id, + removed: false, + }, + req.body, + { + new: true, // return the new result instead of the old one + runValidators: true, + } + ).exec(); + if (!result) { + return res.status(404).json({ + success: false, + result: null, + message: 'No document found ', + }); + } else { + return res.status(200).json({ + success: true, + result, + message: 'we update this document ', + }); + } }; module.exports = update; diff --git a/backend/src/controllers/middlewaresControllers/createUserController/updatePassword.js b/backend/src/controllers/middlewaresControllers/createUserController/updatePassword.js index df57cb545..f9b6d3fb8 100644 --- a/backend/src/controllers/middlewaresControllers/createUserController/updatePassword.js +++ b/backend/src/controllers/middlewaresControllers/createUserController/updatePassword.js @@ -17,6 +17,14 @@ const updatePassword = async (userModel, req, res) => { // Find document by id and updates with the required fields + if (userProfile.email === 'admin@demo.com') { + return res.status(403).json({ + success: false, + result: null, + message: "you couldn't update demo password", + }); + } + const salt = uniqueId(); const passwordHash = bcrypt.hashSync(salt + password); diff --git a/backend/src/controllers/middlewaresControllers/createUserController/updateProfile.js b/backend/src/controllers/middlewaresControllers/createUserController/updateProfile.js index 9a17247c8..8842e71be 100644 --- a/backend/src/controllers/middlewaresControllers/createUserController/updateProfile.js +++ b/backend/src/controllers/middlewaresControllers/createUserController/updateProfile.js @@ -5,6 +5,15 @@ const updateProfile = async (userModel, req, res) => { const reqUserName = userModel.toLowerCase(); const userProfile = req[reqUserName]; + + if (userProfile.email === 'admin@demo.com') { + return res.status(403).json({ + success: false, + result: null, + message: "you couldn't update demo informations", + }); + } + let updates = req.body.photo ? { email: req.body.email, diff --git a/backend/src/controllers/middlewaresControllers/createUserController/updateProfilePassword.js b/backend/src/controllers/middlewaresControllers/createUserController/updateProfilePassword.js index c3c0c4130..f8ce21dd9 100644 --- a/backend/src/controllers/middlewaresControllers/createUserController/updateProfilePassword.js +++ b/backend/src/controllers/middlewaresControllers/createUserController/updateProfilePassword.js @@ -32,6 +32,13 @@ const updateProfilePassword = async (userModel, req, res) => { salt: salt, }; + if (userProfile.email === 'admin@demo.com') { + return res.status(403).json({ + success: false, + result: null, + message: "you couldn't update demo password", + }); + } const resultPassword = await UserPassword.findOneAndUpdate( { user: userProfile._id, removed: false }, { $set: UserPasswordData }, diff --git a/backend/src/emailTemplate/emailVerfication.js b/backend/src/emailTemplate/emailVerfication.js index 393bbcdc5..cae578940 100644 --- a/backend/src/emailTemplate/emailVerfication.js +++ b/backend/src/emailTemplate/emailVerfication.js @@ -1,37 +1,3 @@ -exports.emailVerfication = ({ - title = 'Verify your email', - name = '', - link = '', - time = new Date(), - emailToken, -}) => { - return ` -
- - - - ${title} - - - - -

${title}

-
-

Hello ${name},

-

Code :
${emailToken}

-

Thank you for signing up for IDURAR ! Before we can activate your account, we kindly ask you to verify your email address by clicking on the link provided below:

-

${link}

-

Thank you for choosing IDURAR. We look forward to having you as a valued user!

-
-

Best regards,

-

Salah Eddine Lalami

-

Founder @ IDURAR

- -
- `; -}; - exports.passwordVerfication = ({ title = 'Reset your Password', name = '', @@ -54,43 +20,9 @@ exports.passwordVerfication = ({

Hello ${name},

We have received a request to reset the password for your account on IDURAR. To proceed with the password reset, please click on the link provided below:

${link}

-
-

Best regards,

-

Salah Eddine Lalami

-

Founder @ IDURAR

- - - `; -}; - -exports.afterRegistrationSuccess = ({ - title = 'Customize IDURAR ERP CRM or build your own SaaS', - name = '', -}) => { - return ` -
- - - - ${title} - - - - -

${title}

-
-

Hello ${name},

-

I would like to invite you to book a call if you need :

-

* Customize or adding new features to IDURAR ERP CRM.

-

* Build your own custom SaaS solution based on IDURAR ERP CRM , With IDURAR SaaS license , instead of investing in an uncertain developer team. This opportunity allows you to build a tailored SaaS platform that meets your specific business needs.

-

Book a call here https://calendly.com/lalami/meeting

-
-

Best regards,

-

Salah Eddine Lalami

-

Founder @ IDURAR

+ +
- `; }; diff --git a/backend/src/locale/languages.js b/backend/src/locale/languages.js index 71d48008e..e0a30c5df 100644 --- a/backend/src/locale/languages.js +++ b/backend/src/locale/languages.js @@ -1,62 +1 @@ -module.exports = [ - { icon: '๐Ÿ‡ฆ๐Ÿ‡ฑ ', label: 'Albanian', value: 'sq_al' }, - { icon: '๐Ÿ‡ฉ๐Ÿ‡ฟ ', label: 'Arabic', value: 'ar_eg', isRtl: true }, - { icon: '๐Ÿ‡ฆ๐Ÿ‡ฒ ', label: 'Armenian', value: 'hy_am' }, - { icon: '๐Ÿ‡ฆ๐Ÿ‡ฟ ', label: 'Azerbaijani', value: 'az_az' }, - { icon: '๐Ÿ‡ช๐Ÿ‡ฆ ', label: 'Basque', value: 'eu_es' }, - { icon: '๐Ÿ‡ง๐Ÿ‡พ ', label: 'Belarusian', value: 'by_by' }, - { icon: '๐Ÿ‡ท๐Ÿ‡ธ ', label: 'Serbian', value: 'sr_rs' }, - { icon: '๐Ÿ‡ง๐Ÿ‡ฉ ', label: 'Bengali', value: 'bn_bd' }, - { icon: '๐Ÿ‡ง๐Ÿ‡ฌ ', label: 'Bulgarian', value: 'bg_bg' }, - { icon: '๐Ÿ‡ช๐Ÿ‡ฆ ', label: 'Catalonian', value: 'ca_es' }, - { icon: '๐Ÿ‡จ๐Ÿ‡ณ ', label: 'Chinese', value: 'zh_cn' }, - { icon: '๐Ÿ‡ญ๐Ÿ‡ท ', label: 'Croatian', value: 'hr_hr' }, - { icon: '๐Ÿ‡จ๐Ÿ‡ฟ ', label: 'Czech', value: 'cs_cz' }, - { icon: '๐Ÿ‡ฉ๐Ÿ‡ฐ ', label: 'Danish', value: 'da_dk' }, - { icon: '๐Ÿ‡ณ๐Ÿ‡ฑ ', label: 'Dutch', value: 'nl_nl' }, - { icon: '๐Ÿ‡ช๐Ÿ‡ช ', label: 'Estonian', value: 'et_ee' }, - { icon: '๐Ÿ‡ต๐Ÿ‡ญ ', label: 'Filipino', value: 'fil_ph' }, - { icon: '๐Ÿ‡ซ๐Ÿ‡ฎ ', label: 'Finnish', value: 'fi_fi' }, - { icon: '๐Ÿ‡ซ๐Ÿ‡ท ', label: 'French', value: 'fr_fr' }, - { icon: '๐Ÿ‡ช๐Ÿ‡ธ ', label: 'Galician', value: 'gl_es' }, - { icon: '๐Ÿ‡ฌ๐Ÿ‡ช ', label: 'Georgian', value: 'ka_ge' }, - { icon: '๐Ÿ‡ฉ๐Ÿ‡ช ', label: 'German', value: 'de_de' }, - { icon: '๐Ÿ‡ฌ๐Ÿ‡ท ', label: 'Greek', value: 'el_gr' }, - { icon: '๐Ÿ‡ฎ๐Ÿ‡ฑ ', label: 'Hebrew', value: 'he_il', isRtl: true }, - { icon: '๐Ÿ‡ฎ๐Ÿ‡ณ ', label: 'Hindi', value: 'hi_in' }, - { icon: '๐Ÿ‡ญ๐Ÿ‡บ ', label: 'Hungarian', value: 'hu_hu' }, - { icon: '๐Ÿ‡ฎ๐Ÿ‡ฉ ', label: 'Indonesian', value: 'id_id' }, - { icon: '๐Ÿ‡ฎ๐Ÿ‡ธ ', label: 'Icelandic', value: 'is_is' }, - { icon: '๐Ÿ‡ฎ๐Ÿ‡น ', label: 'Italian', value: 'it_it' }, - { icon: '๐Ÿ‡ฏ๐Ÿ‡ต ', label: 'Japanese ', value: 'ja_jp' }, - { icon: '๐Ÿ‡ฉ๐Ÿ‡ฟ ', label: 'Kabyle', value: 'kb_dz' }, - { icon: '๐Ÿ‡ฎ๐Ÿ‡ถ ', label: 'Kurdish', value: 'kmr_iq' }, - { icon: '๐Ÿ‡ฐ๐Ÿ‡ฟ ', label: 'Kazakh', value: 'kk_kz' }, - { icon: '๐Ÿ‡ฐ๐Ÿ‡ท ', label: 'Korean', value: 'ko_kr' }, - { icon: '๐Ÿ‡ฑ๐Ÿ‡ป ', label: 'Latvian', value: 'lv_lv' }, - { icon: '๐Ÿ‡ฑ๐Ÿ‡น ', label: 'Lithuanian', value: 'lt_lt' }, - { icon: '๐Ÿ‡ฒ๐Ÿ‡ฐ ', label: 'Macedonian', value: 'mk_mk' }, - { icon: '๐Ÿ‡ฒ๐Ÿ‡พ ', label: 'Malay', value: 'ms_my' }, - { icon: '๐Ÿ‡ฒ๐Ÿ‡น ', label: 'Maltese', value: 'mt_mt' }, - { icon: '๐Ÿ‡ฒ๐Ÿ‡ณ ', label: 'Mongolian', value: 'mn_mn' }, - { icon: '๐Ÿ‡ณ๐Ÿ‡ต ', label: 'Nepali', value: 'ne_np' }, - { icon: '๐Ÿ‡ณ๐Ÿ‡ด ', label: 'Norwegian', value: 'nb_no' }, - { icon: '๐Ÿ‡ฎ๐Ÿ‡ท ', label: 'Persian', value: 'fa_ir', isRtl: true }, - { icon: '๐Ÿ‡ต๐Ÿ‡ฑ ', label: 'Polish', value: 'pl_pl' }, - { icon: '๐Ÿ‡ง๐Ÿ‡ท ', label: 'Portuguese Brazil', value: 'pt_br' }, - { icon: '๐Ÿ‡ต๐Ÿ‡น ', label: 'Portuguese Portugal', value: 'pt_pt' }, - { icon: '๐Ÿ‡ท๐Ÿ‡ด ', label: 'Romanian', value: 'ro_ro' }, - { icon: '๐Ÿ‡ท๐Ÿ‡บ ', label: 'Russian', value: 'ru_ru' }, - { icon: '๐Ÿ‡ธ๐Ÿ‡ฐ ', label: 'Slovak', value: 'sk_sk' }, - { icon: '๐Ÿ‡ธ๐Ÿ‡ฎ ', label: 'Slovenian', value: 'sl_si' }, - { icon: '๐Ÿ‡ช๐Ÿ‡ธ ', label: 'Spanish', value: 'es_es' }, - { icon: '๐Ÿ‡ฐ๐Ÿ‡ช ', label: 'Swahili', value: 'sw_ke' }, - { icon: '๐Ÿ‡ธ๐Ÿ‡ช ', label: 'Swedish', value: 'sv_se' }, - { icon: '๐Ÿ‡ฎ๐Ÿ‡ณ ', label: 'Tamil', value: 'ta_in' }, - { icon: '๐Ÿ‡น๐Ÿ‡ญ ', label: 'Thai', value: 'th_th' }, - { icon: '๐Ÿ‡น๐Ÿ‡ท ', label: 'Turkish', value: 'tr_tr' }, - { icon: '๐Ÿ‡บ๐Ÿ‡ฆ ', label: 'Ukrainian', value: 'uk_ua' }, - { icon: '๐Ÿ‡ต๐Ÿ‡ฐ ', label: 'Urdu', value: 'ur_pk', isRtl: true }, - { icon: '๐Ÿ‡บ๐Ÿ‡ฟ ', label: 'Uzbek', value: 'uz_uz' }, - { icon: '๐Ÿ‡ป๐Ÿ‡ณ ', label: 'Vietnamese', value: 'vi_vn' }, -]; +module.exports = []; diff --git a/backend/src/locale/translation/en_us.js b/backend/src/locale/translation/en_us.js index 0ae0c617c..27f9df51c 100644 --- a/backend/src/locale/translation/en_us.js +++ b/backend/src/locale/translation/en_us.js @@ -448,5 +448,5 @@ module.exports = { create_only: 'Create Only', enter_code: 'Enter Code', offers: 'Offers', - proforma_invoices: 'Proforma Invoices', + proforma_invoices: 'quote', }; diff --git a/backend/src/models/appModels/Client.js b/backend/src/models/appModels/Client.js index 70357766b..f8407a89f 100644 --- a/backend/src/models/appModels/Client.js +++ b/backend/src/models/appModels/Client.js @@ -10,24 +10,16 @@ const schema = new mongoose.Schema({ default: true, }, - type: { - type: String, - default: 'company', - enum: ['company', 'people'], - required: true, - }, name: { type: String, required: true, }, - company: { type: mongoose.Schema.ObjectId, ref: 'Company', autopopulate: true }, - people: { type: mongoose.Schema.ObjectId, ref: 'People', autopopulate: true }, - convertedFrom: { type: mongoose.Schema.ObjectId, ref: 'Lead' }, - interestedIn: [{ type: mongoose.Schema.ObjectId, ref: 'Product' }], + phone: String, + country: String, + address: String, + email: String, createdBy: { type: mongoose.Schema.ObjectId, ref: 'Admin' }, assigned: { type: mongoose.Schema.ObjectId, ref: 'Admin' }, - source: String, - category: String, created: { type: Date, default: Date.now, diff --git a/backend/src/models/appModels/Company.js b/backend/src/models/appModels/Company.js deleted file mode 100644 index dc98c565c..000000000 --- a/backend/src/models/appModels/Company.js +++ /dev/null @@ -1,217 +0,0 @@ -const mongoose = require('mongoose'); - -const schema = new mongoose.Schema({ - removed: { - type: Boolean, - default: false, - }, - enabled: { - type: Boolean, - default: true, - }, - - name: { - type: String, - trim: true, - required: true, - }, - legalName: { - type: String, - trim: true, - }, - hasParentCompany: { - type: Boolean, - default: false, - }, - parentCompany: { - type: mongoose.Schema.ObjectId, - ref: 'Company', - }, - isClient: { - type: Boolean, - default: false, - }, - peoples: [{ type: mongoose.Schema.ObjectId, ref: 'People', autopopulate: true }], - mainContact: { type: mongoose.Schema.ObjectId, ref: 'People', autopopulate: true }, - icon: { - type: String, - trim: true, - }, - logo: { - type: String, - trim: true, - }, - imageHeader: String, - bankName: { - type: String, - trim: true, - }, - bankIban: { - type: String, - trim: true, - }, - bankSwift: { - type: String, - trim: true, - }, - bankNumber: { - type: String, - trim: true, - }, - bankRouting: { - type: String, - trim: true, - }, - bankCountry: { - type: String, - trim: true, - }, - companyRegNumber: { - type: String, - trim: true, - }, - companyTaxNumber: { - type: String, - trim: true, - }, - companyTaxId: { - type: String, - trim: true, - }, - companyRegId: { - type: String, - trim: true, - }, - securitySocialNbr: String, - customField: [ - { - fieldName: { - type: String, - trim: true, - lowercase: true, - }, - fieldType: { - type: String, - trim: true, - lowercase: true, - default: 'string', - }, - fieldValue: {}, - }, - ], - location: { - latitude: Number, - longitude: Number, - }, - address: { - type: String, - }, - city: { - type: String, - }, - State: { - type: String, - }, - postalCode: { - type: Number, - }, - country: { - type: String, - trim: true, - }, - phone: { - type: String, - trim: true, - }, - otherPhone: [ - { - type: String, - trim: true, - }, - ], - fax: { - type: String, - trim: true, - }, - email: { - type: String, - trim: true, - lowercase: true, - }, - otherEmail: [ - { - type: String, - trim: true, - lowercase: true, - }, - ], - website: { - type: String, - trim: true, - lowercase: true, - }, - socialMedia: { - facebook: String, - instagram: String, - twitter: String, - linkedin: String, - tiktok: String, - youtube: String, - snapchat: String, - }, - images: [ - { - id: String, - name: String, - path: String, - description: String, - isPublic: { - type: Boolean, - default: false, - }, - }, - ], - files: [ - { - id: String, - name: String, - path: String, - description: String, - isPublic: { - type: Boolean, - default: false, - }, - }, - ], - category: String, - approved: { - type: Boolean, - default: true, - }, - verified: { - type: Boolean, - }, - notes: String, - tags: [ - { - type: String, - trim: true, - lowercase: true, - }, - ], - created: { - type: Date, - default: Date.now, - }, - updated: { - type: Date, - default: Date.now, - }, - isPublic: { - type: Boolean, - default: false, - }, -}); - -schema.plugin(require('mongoose-autopopulate')); -module.exports = mongoose.model('Company', schema); diff --git a/backend/src/models/appModels/Employee.js b/backend/src/models/appModels/Employee.js deleted file mode 100644 index 645f3fa7a..000000000 --- a/backend/src/models/appModels/Employee.js +++ /dev/null @@ -1,186 +0,0 @@ -const mongoose = require('mongoose'); - -const employeeSchema = new mongoose.Schema({ - removed: { - type: Boolean, - default: false, - }, - enabled: { - type: Boolean, - default: true, - }, - - isAdmin: { type: mongoose.Schema.ObjectId, ref: 'Admin' }, - firstname: { - type: String, - trim: true, - required: true, - }, - lastname: { - type: String, - trim: true, - required: true, - }, - birthplace: String, - gender: String, - idCardNumber: { - type: String, - trim: true, - }, - idCardType: String, - birthday: Date, - securitySocialNbr: String, - taxNumber: String, - nationality: { - type: String, - trim: true, - }, - photo: { - type: String, - trim: true, - }, - headerImage: { - type: String, - trim: true, - }, - - bankName: { - type: String, - trim: true, - }, - bankIban: { - type: String, - trim: true, - }, - bankSwift: { - type: String, - trim: true, - }, - bankNumber: { - type: String, - trim: true, - }, - bankRouting: { - type: String, - trim: true, - }, - customField: [ - { - fieldName: { - type: String, - trim: true, - lowercase: true, - }, - fieldType: { - type: String, - trim: true, - lowercase: true, - default: 'string', - }, - fieldValue: {}, - }, - ], - location: { - latitude: Number, - longitude: Number, - }, - address: { - type: String, - }, - city: { - type: String, - }, - State: { - type: String, - }, - postalCode: { - type: Number, - }, - country: { - type: String, - trim: true, - }, - phone: { - type: String, - trim: true, - }, - otherPhone: [ - { - type: String, - trim: true, - }, - ], - email: { - type: String, - trim: true, - lowercase: true, - }, - - otherEmail: [ - { - type: String, - trim: true, - lowercase: true, - }, - ], - socialMedia: { - facebook: String, - instagram: String, - twitter: String, - linkedin: String, - tiktok: String, - youtube: String, - snapchat: String, - }, - images: [ - { - id: String, - name: String, - path: String, - description: String, - isPublic: { - type: Boolean, - default: false, - }, - }, - ], - files: [ - { - id: String, - name: String, - path: String, - description: String, - isPublic: { - type: Boolean, - default: false, - }, - }, - ], - notes: String, - category: String, - status: String, - approved: { - type: Boolean, - }, - verified: { - type: Boolean, - }, - tags: [ - { - type: String, - trim: true, - lowercase: true, - }, - ], - created: { - type: Date, - default: Date.now, - }, - updated: { - type: Date, - default: Date.now, - }, -}); -employeeSchema.plugin(require('mongoose-autopopulate')); - -module.exports = mongoose.model('Employee', employeeSchema); diff --git a/backend/src/models/appModels/Expense.js b/backend/src/models/appModels/Expense.js deleted file mode 100644 index e19f863da..000000000 --- a/backend/src/models/appModels/Expense.js +++ /dev/null @@ -1,98 +0,0 @@ -const mongoose = require('mongoose'); - -const schema = new mongoose.Schema({ - removed: { - type: Boolean, - default: false, - }, - - date: { - type: Date, - default: Date.now, - }, - name: { - type: String, - trim: true, - required: true, - }, - description: { - type: String, - }, - ref: { - type: String, - trim: true, - }, - recurring: { - type: String, - enum: ['daily', 'weekly', 'monthly', 'annually', 'quarter'], - }, - supplier: { - type: mongoose.Schema.ObjectId, - autopopulate: true, - }, - expenseCategory: { - type: mongoose.Schema.ObjectId, - ref: 'ExpenseCategory', - autopopulate: true, - required: true, - }, - taxRate: { - type: Number, - }, - subTotal: { - type: Number, - }, - taxTotal: { - type: Number, - }, - total: { - type: Number, - }, - currency: { - type: String, - default: 'NA', - uppercase: true, - required: true, - }, - paymentMode: { - type: mongoose.Schema.ObjectId, - ref: 'PaymentMode', - autopopulate: true, - }, - receipt: String, - images: [ - { - id: String, - name: String, - path: String, - description: String, - isPublic: { - type: Boolean, - default: false, - }, - }, - ], - files: [ - { - id: String, - name: String, - path: String, - description: String, - isPublic: { - type: Boolean, - default: false, - }, - }, - ], - updated: { - type: Date, - default: Date.now, - }, - created: { - type: Date, - default: Date.now, - }, -}); - -schema.plugin(require('mongoose-autopopulate')); -module.exports = mongoose.model('Expense', schema); diff --git a/backend/src/models/appModels/ExpenseCategory.js b/backend/src/models/appModels/ExpenseCategory.js deleted file mode 100644 index 903b7a4de..000000000 --- a/backend/src/models/appModels/ExpenseCategory.js +++ /dev/null @@ -1,39 +0,0 @@ -const mongoose = require('mongoose'); - -const expenseCategorySchema = new mongoose.Schema({ - removed: { - type: Boolean, - default: false, - }, - enabled: { - type: Boolean, - default: true, - }, - - name: { - type: String, - trim: true, - required: true, - }, - description: { - type: String, - trim: true, - required: true, - }, - color: { - type: String, - lowercase: true, - trim: true, - required: true, - }, - created: { - type: Date, - default: Date.now, - }, - updated: { - type: Date, - default: Date.now, - }, -}); - -module.exports = mongoose.model('ExpenseCategory', expenseCategorySchema); diff --git a/backend/src/models/appModels/Lead.js b/backend/src/models/appModels/Lead.js deleted file mode 100644 index d8b16bab5..000000000 --- a/backend/src/models/appModels/Lead.js +++ /dev/null @@ -1,92 +0,0 @@ -const mongoose = require('mongoose'); - -const schema = new mongoose.Schema({ - removed: { - type: Boolean, - default: false, - }, - enabled: { - type: Boolean, - default: true, - }, - - type: { - type: String, - default: 'company', - enum: ['company', 'people'], - required: true, - }, - name: { - type: String, - required: true, - }, - company: { type: mongoose.Schema.ObjectId, ref: 'Company', autopopulate: true }, - people: { type: mongoose.Schema.ObjectId, ref: 'People', autopopulate: true }, - interestedIn: [{ type: mongoose.Schema.ObjectId, ref: 'Product' }], - offer: [{ type: mongoose.Schema.ObjectId, ref: 'Offer' }], - converted: { type: Boolean, default: false }, - createdBy: { type: mongoose.Schema.ObjectId, ref: 'Admin' }, - assigned: { type: mongoose.Schema.ObjectId, ref: 'Admin' }, - subTotal: { - type: Number, - }, - taxTotal: { - type: Number, - }, - total: { - type: Number, - }, - discount: { - type: Number, - }, - images: [ - { - id: String, - name: String, - path: String, - description: String, - isPublic: { - type: Boolean, - default: false, - }, - }, - ], - files: [ - { - id: String, - name: String, - path: String, - description: String, - isPublic: { - type: Boolean, - default: false, - }, - }, - ], - category: String, - status: String, - notes: String, - source: String, - approved: { - type: Boolean, - default: false, - }, - tags: [ - { - type: String, - trim: true, - lowercase: true, - }, - ], - created: { - type: Date, - default: Date.now, - }, - updated: { - type: Date, - default: Date.now, - }, -}); - -schema.plugin(require('mongoose-autopopulate')); -module.exports = mongoose.model('Lead', schema); diff --git a/backend/src/models/appModels/Offer.js b/backend/src/models/appModels/Offer.js deleted file mode 100644 index e4c71a107..000000000 --- a/backend/src/models/appModels/Offer.js +++ /dev/null @@ -1,135 +0,0 @@ -const mongoose = require('mongoose'); - -const offerSchema = new mongoose.Schema({ - removed: { - type: Boolean, - default: false, - }, - - createdBy: { type: mongoose.Schema.ObjectId, ref: 'Admin', required: true }, - converted: { - type: Boolean, - default: false, - }, - number: { - type: Number, - required: true, - }, - year: { - type: Number, - required: true, - }, - content: String, - date: { - type: Date, - required: true, - }, - lead: { - type: mongoose.Schema.ObjectId, - ref: 'Lead', - required: true, - autopopulate: true, - }, - items: [ - { - itemName: { - type: String, - required: true, - }, - description: { - type: String, - }, - quantity: { - type: Number, - required: true, - }, - price: { - type: Number, - required: true, - }, - // taxRate: { - // type: Number, - // default: 0, - // }, - // subTotal: { - // type: Number, - // default: 0, - // }, - // taxTotal: { - // type: Number, - // default: 0, - // }, - total: { - type: Number, - required: true, - }, - }, - ], - currency: { - type: String, - default: 'NA', - uppercase: true, - required: true, - }, - taxRate: { - type: Number, - }, - subTotal: { - type: Number, - }, - subOfferTotal: { - type: Number, - }, - taxTotal: { - type: Number, - }, - total: { - type: Number, - }, - discount: { - type: Number, - default: 0, - }, - notes: { - type: String, - }, - status: { - type: String, - enum: ['draft', 'pending', 'sent', 'accepted', 'declined', 'cancelled', 'on hold'], - default: 'draft', - }, - approved: { - type: Boolean, - default: false, - }, - isExpired: { - type: Boolean, - default: false, - }, - pdf: { - type: String, - }, - files: [ - { - id: String, - name: String, - path: String, - description: String, - isPublic: { - type: Boolean, - default: true, - }, - }, - ], - updated: { - type: Date, - default: Date.now, - }, - created: { - type: Date, - default: Date.now, - }, -}); - -offerSchema.plugin(require('mongoose-autopopulate')); -module.exports = mongoose.model('Offer', offerSchema); diff --git a/backend/src/models/appModels/Order.js b/backend/src/models/appModels/Order.js deleted file mode 100644 index 6b145be9c..000000000 --- a/backend/src/models/appModels/Order.js +++ /dev/null @@ -1,140 +0,0 @@ -const mongoose = require('mongoose'); - -const orderSchema = new mongoose.Schema({ - removed: { - type: Boolean, - default: false, - }, - enabled: { - type: Boolean, - default: true, - }, - - createdBy: { - type: mongoose.Schema.ObjectId, - ref: 'Admin', - }, - - assigned: { - type: mongoose.Schema.ObjectId, - ref: 'Employee', - }, - number: { - type: Number, - required: true, - }, - recurring: { - type: String, - enum: ['daily', 'weekly', 'monthly', 'annually', 'quarter'], - }, - date: { - type: Date, - default: Date.now, - required: true, - }, - client: { - type: mongoose.Schema.ObjectId, - ref: 'Client', - required: true, - autopopulate: true, - }, - invoice: { - type: mongoose.Schema.ObjectId, - ref: 'Ivoince', - autopopulate: true, - }, - items: [ - { - product: { - type: mongoose.Schema.ObjectId, - ref: 'Product', - required: true, - }, - itemName: { - type: String, - required: true, - }, - description: { - type: String, - }, - quantity: { - type: Number, - default: 1, - required: true, - }, - price: { - type: Number, - required: true, - }, - discount: { - type: Number, - default: 0, - }, - // taxRate: { - // type: Number, - // default: 0, - // }, - // subTotal: { - // type: Number, - // default: 0, - // }, - // taxTotal: { - // type: Number, - // default: 0, - // }, - total: { - type: Number, - }, - notes: { - type: String, - }, - }, - ], - shipment: { - type: mongoose.Schema.ObjectId, - ref: 'Shipment', - }, - approved: { - type: Boolean, - default: false, - }, - notes: { - type: String, - }, - fulfillment: { - type: String, - enum: ['pending', 'in review', 'processing', 'packing', 'shipped', 'on hold', 'cancelled'], - default: 'pending', - }, - status: { - type: String, - enum: [ - 'not started', - 'in progress', - 'delayed', - 'completed', - 'delivered', - 'returned', - 'cancelled', - 'on hold', - 'refunded', - ], - default: 'not started', - }, - processingStatus: String, - pdf: { - type: String, - }, - updated: { - type: Date, - default: Date.now, - }, - created: { - type: Date, - default: Date.now, - }, -}); - -orderSchema.plugin(require('mongoose-autopopulate')); - -module.exports = mongoose.model('Order', orderSchema); diff --git a/backend/src/models/appModels/People.js b/backend/src/models/appModels/People.js deleted file mode 100644 index 63136629f..000000000 --- a/backend/src/models/appModels/People.js +++ /dev/null @@ -1,203 +0,0 @@ -const mongoose = require('mongoose'); - -const schema = new mongoose.Schema({ - removed: { - type: Boolean, - default: false, - }, - enabled: { - type: Boolean, - default: true, - }, - - firstname: { - type: String, - trim: true, - required: true, - }, - lastname: { - type: String, - trim: true, - required: true, - }, - isClient: { - type: Boolean, - default: false, - }, - company: { type: mongoose.Schema.ObjectId, ref: 'Company' }, - bio: String, - idCardNumber: { - type: String, - trim: true, - }, - idCardType: { - type: String, - }, - securitySocialNbr: { - type: String, - }, - taxNumber: { - type: String, - }, - birthday: { - type: Date, - }, - birthplace: { - type: String, - }, - gender: { - type: String, - enum: ['male', 'female'], - }, - photo: { - type: String, - }, - bankName: { - type: String, - trim: true, - }, - bankIban: { - type: String, - trim: true, - }, - bankSwift: { - type: String, - trim: true, - }, - bankNumber: { - type: String, - trim: true, - }, - bankRouting: { - type: String, - trim: true, - }, - customField: [ - { - fieldName: { - type: String, - trim: true, - lowercase: true, - }, - fieldType: { - type: String, - trim: true, - lowercase: true, - default: 'string', - }, - fieldValue: {}, - }, - ], - location: { - latitude: Number, - longitude: Number, - }, - address: { - type: String, - }, - city: { - type: String, - }, - State: { - type: String, - }, - postalCode: { - type: Number, - }, - country: { - type: String, - trim: true, - }, - phone: { - type: String, - trim: true, - }, - otherPhone: [ - { - type: String, - trim: true, - }, - ], - email: { - type: String, - trim: true, - lowercase: true, - }, - - otherEmail: [ - { - type: String, - trim: true, - lowercase: true, - }, - ], - socialMedia: { - facebook: String, - instagram: String, - twitter: String, - linkedin: String, - tiktok: String, - youtube: String, - snapchat: String, - }, - website: { - type: String, - trim: true, - lowercase: true, - }, - images: [ - { - id: String, - name: String, - path: String, - description: String, - isPublic: { - type: Boolean, - default: false, - }, - }, - ], - files: [ - { - id: String, - name: String, - path: String, - description: String, - isPublic: { - type: Boolean, - default: false, - }, - }, - ], - notes: String, - category: String, - status: String, - approved: { - type: Boolean, - }, - verified: { - type: Boolean, - }, - tags: [ - { - type: String, - trim: true, - lowercase: true, - }, - ], - created: { - type: Date, - default: Date.now, - }, - updated: { - type: Date, - default: Date.now, - }, - isPublic: { - type: Boolean, - default: false, - }, -}); - -schema.plugin(require('mongoose-autopopulate')); -module.exports = mongoose.model('People', schema); diff --git a/backend/src/models/appModels/Product.js b/backend/src/models/appModels/Product.js deleted file mode 100644 index 8e1f8fb19..000000000 --- a/backend/src/models/appModels/Product.js +++ /dev/null @@ -1,102 +0,0 @@ -const mongoose = require('mongoose'); - -const schema = new mongoose.Schema({ - removed: { - type: Boolean, - default: false, - }, - enabled: { - type: Boolean, - default: true, - }, - - productCategory: { - type: mongoose.Schema.ObjectId, - ref: 'ProductCategory', - required: true, - autopopulate: true, - }, - suppliers: [{ type: mongoose.Schema.ObjectId, ref: 'Supplier' }], - name: { - type: String, - required: true, - }, - description: String, - number: { - type: Number, - }, - title: String, - tags: [String], - headerImage: String, - photo: String, - images: [ - { - id: String, - name: String, - path: String, - description: String, - isPublic: { - type: Boolean, - default: false, - }, - }, - ], - files: [ - { - id: String, - name: String, - path: String, - description: String, - isPublic: { - type: Boolean, - default: false, - }, - }, - ], - priceBeforeTax: { - type: Number, - }, - taxRate: { type: Number, default: 0 }, - price: { - type: Number, - required: true, - }, - currency: { - type: String, - default: 'NA', - uppercase: true, - required: true, - }, - customField: [ - { - fieldName: { - type: String, - trim: true, - lowercase: true, - }, - fieldType: { - type: String, - trim: true, - lowercase: true, - default: 'string', - }, - fieldValue: {}, - }, - ], - created: { - type: Date, - default: Date.now, - }, - updated: { - type: Date, - default: Date.now, - }, - isPublic: { - type: Boolean, - default: true, - }, -}); - -schema.plugin(require('mongoose-autopopulate')); - -module.exports = mongoose.model('Product', schema); diff --git a/backend/src/models/appModels/ProductCategory.js b/backend/src/models/appModels/ProductCategory.js deleted file mode 100644 index 2e26c19b0..000000000 --- a/backend/src/models/appModels/ProductCategory.js +++ /dev/null @@ -1,76 +0,0 @@ -const mongoose = require('mongoose'); - -const productCategorySchema = new mongoose.Schema({ - removed: { - type: Boolean, - default: false, - }, - enabled: { - type: Boolean, - default: true, - }, - - name: { - type: String, - required: true, - }, - description: String, - color: { - type: String, - lowercase: true, - trim: true, - required: true, - }, - hasParentCategory: { - type: Boolean, - default: false, - }, - parentCategory: { - type: mongoose.Schema.ObjectId, - ref: 'ProductCategory', - }, - - title: String, - tags: [String], - icon: String, - headerImage: String, - photo: String, - images: [ - { - id: String, - name: String, - path: String, - description: String, - isPublic: { - type: Boolean, - default: false, - }, - }, - ], - files: [ - { - id: String, - name: String, - path: String, - description: String, - isPublic: { - type: Boolean, - default: false, - }, - }, - ], - created: { - type: Date, - default: Date.now, - }, - updated: { - type: Date, - default: Date.now, - }, - isPublic: { - type: Boolean, - default: true, - }, -}); - -module.exports = mongoose.model('ProductCategory', productCategorySchema); diff --git a/backend/src/models/appModels/Purchase.js b/backend/src/models/appModels/Purchase.js deleted file mode 100644 index bfc8632d1..000000000 --- a/backend/src/models/appModels/Purchase.js +++ /dev/null @@ -1,153 +0,0 @@ -const mongoose = require('mongoose'); - -const purchaseSchema = new mongoose.Schema({ - removed: { - type: Boolean, - default: false, - }, - createdBy: { type: mongoose.Schema.ObjectId, ref: 'Admin', required: true }, - - number: { - type: Number, - required: true, - }, - year: { - type: Number, - required: true, - }, - content: String, - recurring: { - type: String, - enum: ['daily', 'weekly', 'monthly', 'annually', 'quarter'], - }, - date: { - type: Date, - required: true, - }, - expiredDate: { - type: Date, - required: true, - }, - supplier: { - type: mongoose.Schema.ObjectId, - ref: 'Supplier', - required: true, - autopopulate: true, - }, - items: [ - { - product: { - type: mongoose.Schema.ObjectId, - ref: 'Product', - }, - quantity: { - type: Number, - default: 1, - required: true, - }, - price: { - type: Number, - required: true, - }, - taxRate: { - type: Number, - default: 0, - }, - subTotal: { - type: Number, - default: 0, - }, - taxTotal: { - type: Number, - default: 0, - }, - total: { - type: Number, - required: true, - }, - }, - ], - currency: { - type: String, - default: 'NA', - uppercase: true, - required: true, - }, - taxRate: { - type: Number, - default: 0, - }, - subTotal: { - type: Number, - default: 0, - }, - taxTotal: { - type: Number, - default: 0, - }, - total: { - type: Number, - default: 0, - }, - credit: { - type: Number, - default: 0, - }, - discount: { - type: Number, - default: 0, - }, - expense: [ - { - type: mongoose.Schema.ObjectId, - ref: 'Expense', - }, - ], - paymentStatus: { - type: String, - default: 'unpaid', - enum: ['unpaid', 'paid', 'partially'], - }, - isOverdue: { - type: Boolean, - default: false, - }, - approved: { - type: Boolean, - default: false, - }, - notes: { - type: String, - }, - status: { - type: String, - enum: ['draft', 'pending', 'ordred', 'received', 'refunded', 'cancelled', 'on hold'], - default: 'draft', - }, - pdf: { - type: String, - }, - files: [ - { - id: String, - name: String, - path: String, - description: String, - isPublic: { - type: Boolean, - default: true, - }, - }, - ], - updated: { - type: Date, - default: Date.now, - }, - created: { - type: Date, - default: Date.now, - }, -}); - -purchaseSchema.plugin(require('mongoose-autopopulate')); -module.exports = mongoose.model('Purchase', purchaseSchema); diff --git a/backend/src/models/appModels/Shipment.js b/backend/src/models/appModels/Shipment.js deleted file mode 100644 index 6829f18d9..000000000 --- a/backend/src/models/appModels/Shipment.js +++ /dev/null @@ -1,119 +0,0 @@ -const mongoose = require('mongoose'); - -const ShipmentSchema = new mongoose.Schema({ - removed: { - type: Boolean, - default: false, - }, - - createdBy: { - type: mongoose.Schema.ObjectId, - ref: 'Admin', - required: true, - }, - assigned: { - type: mongoose.Schema.ObjectId, - ref: 'Employee', - }, - order: { - type: mongoose.Schema.ObjectId, - ref: 'Order', - }, - carrier: { - type: String, - required: true, - }, - trackingNmber: String, - trackingLink: String, - date: { - type: Date, - required: true, - }, - estimatedDeliveryDate: { - type: Date, - }, - client: { - type: mongoose.Schema.ObjectId, - ref: 'Client', - }, - invoice: { - type: mongoose.Schema.ObjectId, - ref: 'Ivoince', - }, - recipient: { - name: { - type: String, - required: true, - }, - address: { - type: String, - required: true, - }, - city: { - type: String, - required: true, - }, - state: { - type: String, - }, - country: { - type: String, - required: true, - }, - postalCode: { - type: String, - required: true, - }, - phone: { - type: String, - }, - }, - products: [ - { - productId: { - type: mongoose.Schema.ObjectId, - ref: 'Product', - }, - quantity: { - type: String, - required: true, - }, - }, - ], - approved: { - type: Boolean, - default: false, - }, - notes: { - type: String, - }, - status: { - type: String, - enum: [ - 'pending', - 'confirmed', - 'in transit', - 'out for delivery', - 'delivered', - 'returned', - 'failed', - 'cancelled', - ], - default: 'pending', - }, - pdf: { - type: String, - }, - updated: { - type: Date, - default: Date.now, - }, - created: { - type: Date, - default: Date.now, - }, -}); - -ShipmentSchema.plugin(require('mongoose-autopopulate')); - -module.exports = mongoose.model('Shipment', ShipmentSchema); diff --git a/backend/src/models/coreModels/Email.js b/backend/src/models/coreModels/Email.js deleted file mode 100644 index 56a9acac0..000000000 --- a/backend/src/models/coreModels/Email.js +++ /dev/null @@ -1,47 +0,0 @@ -const mongoose = require('mongoose'); - -const emailSchema = new mongoose.Schema({ - removed: { - type: Boolean, - default: false, - }, - enabled: { - type: Boolean, - default: true, - }, - - emailKey: { - type: String, - lowercase: true, - required: true, - }, - emailName: { - type: String, - required: true, - }, - emailVariables: { - type: Array, - }, - emailBody: { - type: String, - required: true, - }, - emailSubject: { - type: String, - required: true, - }, - language: { - type: String, - default: 'us_en', - }, - created: { - type: Date, - default: Date.now, - }, - updated: { - type: Date, - default: Date.now, - }, -}); - -module.exports = mongoose.model('Email', emailSchema); diff --git a/backend/src/routes/appRoutes/appApi.js b/backend/src/routes/appRoutes/appApi.js index 3c2c9d293..9d323c5c5 100644 --- a/backend/src/routes/appRoutes/appApi.js +++ b/backend/src/routes/appRoutes/appApi.js @@ -16,7 +16,7 @@ const routerApp = (entity, controller) => { router.route(`/${entity}/filter`).get(catchErrors(controller['filter'])); router.route(`/${entity}/summary`).get(catchErrors(controller['summary'])); - if (entity === 'invoice' || entity === 'quote' || entity === 'offer' || entity === 'payment') { + if (entity === 'invoice' || entity === 'quote' || entity === 'payment') { router.route(`/${entity}/mail`).post(catchErrors(controller['mail'])); } diff --git a/backend/src/routes/coreRoutes/coreApi.js b/backend/src/routes/coreRoutes/coreApi.js index 38276d418..f60afa2be 100644 --- a/backend/src/routes/coreRoutes/coreApi.js +++ b/backend/src/routes/coreRoutes/coreApi.js @@ -6,7 +6,6 @@ const router = express.Router(); const adminController = require('@/controllers/coreControllers/adminController'); const settingController = require('@/controllers/coreControllers/settingController'); -const emailController = require('@/controllers/coreControllers/emailController'); const { singleStorageUpload } = require('@/middlewares/uploadMiddleware'); @@ -52,14 +51,4 @@ router catchErrors(settingController.updateBySettingKey) ); router.route('/setting/updateManySetting').patch(catchErrors(settingController.updateManySetting)); - -// //____________________________________________ API for Email Templates _________________ -router.route('/email/create').post(catchErrors(emailController.create)); -router.route('/email/read/:id').get(catchErrors(emailController.read)); -router.route('/email/update/:id').patch(catchErrors(emailController.update)); -router.route('/email/search').get(catchErrors(emailController.search)); -router.route('/email/list').get(catchErrors(emailController.list)); -router.route('/email/listAll').get(catchErrors(emailController.listAll)); -router.route('/email/filter').get(catchErrors(emailController.filter)); - module.exports = router; diff --git a/backend/src/routes/coreRoutes/corePublicRouter.js b/backend/src/routes/coreRoutes/corePublicRouter.js index 8b02f7fcb..73e8e3357 100644 --- a/backend/src/routes/coreRoutes/corePublicRouter.js +++ b/backend/src/routes/coreRoutes/corePublicRouter.js @@ -1,19 +1,34 @@ const express = require('express'); - const router = express.Router(); -const path = require('path'); -// Without middleware +const path = require('path'); +const { isPathInside } = require('../../utils/is-path-inside'); router.route('/:subPath/:directory/:file').get(function (req, res) { try { const { subPath, directory, file } = req.params; - const options = { - root: path.join(__dirname, `../../public/${subPath}/${directory}`), - }; - const fileName = file; - return res.sendFile(fileName, options, function (error) { + // Decode each parameter separately + const decodedSubPath = decodeURIComponent(subPath); + const decodedDirectory = decodeURIComponent(directory); + const decodedFile = decodeURIComponent(file); + + // Define the trusted root directory + const rootDir = path.join(__dirname, '../../public'); + + // Safely join the decoded path segments + const relativePath = path.join(decodedSubPath, decodedDirectory, decodedFile); + const absolutePath = path.join(rootDir, relativePath); + + // Check if the resulting path stays inside rootDir + if (!isPathInside(absolutePath, rootDir)) { + return res.status(400).json({ + success: false, + error: 'Invalid filepath', + }); + } + + return res.sendFile(absolutePath, (error) => { if (error) { return res.status(404).json({ success: false, diff --git a/backend/src/setup/defaultSettings/emailSettings.json b/backend/src/setup/defaultSettings/emailSettings.json deleted file mode 100644 index 81fbaff88..000000000 --- a/backend/src/setup/defaultSettings/emailSettings.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "settingCategory": "email_settings", - "settingKey": "email_domain", - "settingValue": "idurarapp.com", - "valueType": "string" - }, - { - "settingCategory": "email_settings", - "settingKey": "email_reply_to", - "settingValue": "reply@idurarapp.com", - "valueType": "string" - }, - { - "settingCategory": "email_settings", - "settingKey": "email_from", - "settingValue": "IDURAR ERP CRM", - "valueType": "string" - } -] diff --git a/backend/src/setup/defaultSettings/inventorySettings.json b/backend/src/setup/defaultSettings/inventorySettings.json deleted file mode 100644 index 2715b79f7..000000000 --- a/backend/src/setup/defaultSettings/inventorySettings.json +++ /dev/null @@ -1,38 +0,0 @@ -[ - { - "settingCategory": "inventory_settings", - "settingKey": "last_order_number", - "settingValue": 0, - "valueType": "number" - }, - { - "settingCategory": "inventory_settings", - "settingKey": "order_number_length", - "settingValue": 13, - "valueType": "number" - }, - { - "settingCategory": "inventory_settings", - "settingKey": "order_number_type", - "settingValue": "date_uniqueid", - "valueType": "string" - }, - { - "settingCategory": "inventory_settings", - "settingKey": "product_number_type", - "settingValue": "barcode", - "valueType": "string" - }, - { - "settingCategory": "inventory_settings", - "settingKey": "last_product_number", - "settingValue": 0, - "valueType": "number" - }, - { - "settingCategory": "inventory_settings", - "settingKey": "generate_product_number", - "settingValue": false, - "valueType": "boolean" - } -] diff --git a/backend/src/setup/defaultSettings/leadSettings.json b/backend/src/setup/defaultSettings/leadSettings.json deleted file mode 100644 index 3026c4845..000000000 --- a/backend/src/setup/defaultSettings/leadSettings.json +++ /dev/null @@ -1,85 +0,0 @@ -[ - { - "settingCategory": "lead_settings", - "settingKey": "lead_type", - "settingValue": ["person", "company"], - "valueType": "array" - }, - { - "settingCategory": "lead_settings", - "settingKey": "lead_source", - "settingValue": [ - "self checking", - "sales lead", - "recomendation", - "facebook", - "instagram", - "tiktok", - "youtube", - "blog", - "linkedin", - "newsletter", - "website", - "twitter" - ], - "valueType": "array" - }, - { - "settingCategory": "lead_settings", - "settingKey": "lead_status", - "settingValue": ["draft", "new", "reached", "waiting", "in negosation", "won", "loose"], - "valueType": "array" - }, - { - "settingCategory": "lead_settings", - "settingKey": "lead_category", - "settingValue": [ - "Corporate", - "person", - "startup", - "small company", - "services business", - "retails", - "cafe & restaurant" - ], - "valueType": "array" - }, - { - "settingCategory": "lead_settings", - "settingKey": "offer_default_lead_type", - "settingValue": "company", - "valueType": "string" - }, - { - "settingCategory": "lead_settings", - "settingKey": "lead_email_imap_server", - "settingValue": "mail.idurarapp.com", - "valueType": "string", - - "isPrivate": true - }, - { - "settingCategory": "lead_settings", - "settingKey": "lead_email_imap_username", - "settingValue": "hello@idurarapp.com", - "valueType": "string", - - "isPrivate": true - }, - { - "settingCategory": "lead_settings", - "settingKey": "lead_email_imap_password", - "settingValue": "password", - "valueType": "string", - - "isPrivate": true - }, - { - "settingCategory": "lead_settings", - "settingKey": "lead_email_imap_port", - "settingValue": 993, - "valueType": "number", - - "isPrivate": true - } -] diff --git a/backend/src/setup/defaultSettings/offerSettings.json b/backend/src/setup/defaultSettings/offerSettings.json deleted file mode 100644 index b1f44f456..000000000 --- a/backend/src/setup/defaultSettings/offerSettings.json +++ /dev/null @@ -1,34 +0,0 @@ -[ - { - "settingCategory": "offer_settings", - "settingKey": "offer_show_product_tax", - "settingValue": false, - "valueType": "boolean" - }, - { - "settingCategory": "offer_settings", - "settingKey": "offer_load_default_client", - "settingValue": false, - "valueType": "boolean" - }, - { - "settingCategory": "offer_settings", - "settingKey": "offer_status", - "settingValue": [ - "draft", - "pending", - "sent", - "negotiation", - "accepted", - "declined", - "cancelled" - ], - "valueType": "array" - }, - { - "settingCategory": "offer_settings", - "settingKey": "offer_pdf_footer", - "settingValue": "Offer was created on a computer and is valid without the signature and seal", - "valueType": "string" - } -] diff --git a/backend/src/setup/emailTemplate/index.json b/backend/src/setup/emailTemplate/index.json deleted file mode 100644 index 9e2219bb1..000000000 --- a/backend/src/setup/emailTemplate/index.json +++ /dev/null @@ -1,51 +0,0 @@ -[ - { - "emailKey": "email_invoice_default", - "emailName":"Invoice Email", - "emailSubject":"Invoice From Idurar", - "emailBody": "

email_invoice_default

", - "emailVariables": ["name","time"] - }, - { - "emailKey": "email_quote_default", - "emailName":"Quote Email", - "emailSubject":"Quote From Idurar", - "emailBody": "

email_quote_default

", - "emailVariables": ["name","time"] - }, - { - "emailKey": "email_offer_default", - "emailName":"Offer Email", - "emailSubject":"Invoice From Idurar", - "emailBody": "

email_offer_default

", - "emailVariables": [] - }, - { - "emailKey": "email_payment_receipt_default", - "emailName":"Payment Email", - "emailSubject":"Payment From Idurar", - "emailBody": "

email_payment_receipt_default

", - "emailVariables": ["name","time"] - }, - { - "emailKey": "email_signup_email_confirm_default", - "emailName":"Signup Confirmation Email", - "emailSubject":"Signup Confirmation From Idurar", - "emailBody": "

email_signup_email_confirm_default

", - "emailVariables": ["name"] - }, - { - "emailKey": "email_reset_password_default", - "emailName":"Password Reset Email", - "emailSubject":"Password Reset From Idurar", - "emailBody": "

email_reset_password_default

", - "emailVariables": ["name"] - }, - { - "emailKey": "welcome_email_default", - "emailName":"Welcome Email", - "emailSubject":"Welcom From Idurar", - "emailBody": "

welcome_email

", - "emailVariables":["name"] - } -] diff --git a/backend/src/setup/reset.js b/backend/src/setup/reset.js index ff5f77f5f..ff9c8a507 100644 --- a/backend/src/setup/reset.js +++ b/backend/src/setup/reset.js @@ -8,9 +8,13 @@ async function deleteData() { const Admin = require('../models/coreModels/Admin'); const AdminPassword = require('../models/coreModels/AdminPassword'); const Setting = require('../models/coreModels/Setting'); + const PaymentMode = require('../models/appModels/PaymentMode'); + const Taxes = require('../models/appModels/Taxes'); await Admin.deleteMany(); await AdminPassword.deleteMany(); + await PaymentMode.deleteMany(); + await Taxes.deleteMany(); console.log('๐Ÿ‘ Admin Deleted. To setup demo admin data, run\n\n\t npm run setup\n\n'); await Setting.deleteMany(); console.log('๐Ÿ‘ Setting Deleted. To setup Setting data, run\n\n\t npm run setup\n\n'); diff --git a/backend/src/utils/is-path-inside.js b/backend/src/utils/is-path-inside.js new file mode 100644 index 000000000..c811051d5 --- /dev/null +++ b/backend/src/utils/is-path-inside.js @@ -0,0 +1,14 @@ +const path = require('path'); + +// is implementation of is-path-inside npm package + +exports.isPathInside = (childPath, parentPath) => { + const relation = path.relative(parentPath, childPath); + + return Boolean( + relation && + relation !== '..' && + !relation.startsWith(`..${path.sep}`) && + relation !== path.resolve(childPath) + ); +}; diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index f77e2e65e..000000000 --- a/docker-compose.yml +++ /dev/null @@ -1,51 +0,0 @@ -version: "3.8" -services: - backend: - build: ./backend - volumes: - - ./backend:/usr/src/app - - /usr/src/app/node_modules - ports: - - "8888:8888" - environment: - - NODE_ENV=development - - DATABASE=mongodb://mongo:27017/local-idurar-erp-crp - - PORT=8888 - - NODE_OPTIONS=--openssl-legacy-provider - - JWT_SECRET=secret_key_1234 - - command: sh -c "npm run setup && npm run dev" - - networks: - - server-mongo - - depends_on: - - mongo - - frontend: - build: ./frontend - volumes: - - ./frontend:/usr/src/app - - /usr/src/app/node_modules - ports: - - "3000:3000" - environment: - - NODE_ENV=development - - REACT_APP_API_URL=http://backend:8888/api - - NODE_OPTIONS=--openssl-legacy-provider - - depends_on: - - backend - - mongo: - image: mongo - volumes: - - mongodb_data:/data/db - networks: - - server-mongo - -volumes: - mongodb_data: - -networks: - server-mongo: diff --git a/frontend/Dockerfile b/frontend/Dockerfile deleted file mode 100644 index 6739192d7..000000000 --- a/frontend/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM node:20.9.0-alpine - -WORKDIR /usr/src/app - -RUN npm install -g npm@10.2.4 - -COPY package*.json ./ -COPY vite.config.js ./ - -COPY . . - -RUN npm install - -EXPOSE 3000 - -CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"] \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index 165630563..0e3fdb85e 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -5,8 +5,11 @@ - IDURAR ERP CRM | Open Code Source - + IDURAR ERP CRM | Free Open Source Accounting Invoice Quote +
diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 806b68ce3..cbb786e61 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,24 +1,24 @@ { "name": "idurar-erp-crm", - "version": "4.0.0-beta.3", + "version": "4.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "idurar-erp-crm", - "version": "4.0.0-beta.3", + "version": "4.1.0", "dependencies": { "@ant-design/icons": "^5.3.0", "@ant-design/pro-layout": "^7.17.19", "@reduxjs/toolkit": "^2.2.1", - "@vitejs/plugin-react": "^4.2.1", + "@vitejs/plugin-react": "^4.3.1", "antd": "^5.14.1", "axios": "^1.6.2", "cross-env": "7.0.3", "currency.js": "2.0.4", "dayjs": "^1.11.10", "just-compare": "^2.3.0", - "react": "^18.2.0", + "react": "^18.3.1", "react-dom": "^18.2.0", "react-quill": "^2.0.0", "react-redux": "^9.1.0", @@ -26,7 +26,7 @@ "redux": "^5.0.1", "reselect": "^5.1.0", "shortid": "^2.2.16", - "vite": "^5.1.4" + "vite": "^5.4.8" }, "devDependencies": { "@types/react": "^18.2.38", @@ -52,12 +52,12 @@ } }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -193,40 +193,40 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.3.tgz", - "integrity": "sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.5.tgz", - "integrity": "sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.5", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.5", - "@babel/parser": "^7.23.5", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.5", - "@babel/types": "^7.23.5", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -242,13 +242,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.5.tgz", - "integrity": "sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", "dependencies": { - "@babel/types": "^7.23.5", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.25.6", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -256,13 +256,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", - "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -270,58 +270,27 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "dependencies": { - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" }, "engines": { "node": ">=6.9.0" @@ -331,89 +300,82 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", - "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.5.tgz", - "integrity": "sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", + "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", "dependencies": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.5", - "@babel/types": "^7.23.5" + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.5.tgz", - "integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "dependencies": { + "@babel/types": "^7.25.6" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -422,11 +384,11 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz", - "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz", + "integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -436,11 +398,11 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz", - "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz", + "integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -461,32 +423,29 @@ } }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.5.tgz", - "integrity": "sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==", - "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.5", - "@babel/types": "^7.23.5", - "debug": "^4.1.0", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -494,12 +453,12 @@ } }, "node_modules/@babel/types": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.5.tgz", - "integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -525,9 +484,9 @@ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], @@ -540,9 +499,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], @@ -555,9 +514,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], @@ -570,9 +529,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], @@ -585,9 +544,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], @@ -600,9 +559,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], @@ -615,9 +574,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], @@ -630,9 +589,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], @@ -645,9 +604,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], @@ -660,9 +619,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], @@ -675,9 +634,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], @@ -690,9 +649,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], @@ -705,9 +664,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], @@ -720,9 +679,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], @@ -735,9 +694,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], @@ -750,9 +709,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], @@ -765,9 +724,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], @@ -780,9 +739,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], @@ -795,9 +754,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], @@ -810,9 +769,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], @@ -825,9 +784,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], @@ -840,9 +799,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], @@ -855,9 +814,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], @@ -974,43 +933,43 @@ "dev": true }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -1195,9 +1154,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", - "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.5.tgz", + "integrity": "sha512-SU5cvamg0Eyu/F+kLeMXS7GoahL+OoizlclVFX3l5Ql6yNlywJJ0OuqTzUx0v+aHhPHEB/56CT06GQrRrGNYww==", "cpu": [ "arm" ], @@ -1207,9 +1166,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", - "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.5.tgz", + "integrity": "sha512-S4pit5BP6E5R5C8S6tgU/drvgjtYW76FBuG6+ibG3tMvlD1h9LHVF9KmlmaUBQ8Obou7hEyS+0w+IR/VtxwNMQ==", "cpu": [ "arm64" ], @@ -1219,9 +1178,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", - "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.5.tgz", + "integrity": "sha512-250ZGg4ipTL0TGvLlfACkIxS9+KLtIbn7BCZjsZj88zSg2Lvu3Xdw6dhAhfe/FjjXPVNCtcSp+WZjVsD3a/Zlw==", "cpu": [ "arm64" ], @@ -1231,9 +1190,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", - "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.5.tgz", + "integrity": "sha512-D8brJEFg5D+QxFcW6jYANu+Rr9SlKtTenmsX5hOSzNYVrK5oLAEMTUgKWYJP+wdKyCdeSwnapLsn+OVRFycuQg==", "cpu": [ "x64" ], @@ -1243,9 +1202,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", - "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.5.tgz", + "integrity": "sha512-PNqXYmdNFyWNg0ma5LdY8wP+eQfdvyaBAojAXgO7/gs0Q/6TQJVXAXe8gwW9URjbS0YAammur0fynYGiWsKlXw==", "cpu": [ "arm" ], @@ -1255,9 +1214,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", - "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.5.tgz", + "integrity": "sha512-kSSCZOKz3HqlrEuwKd9TYv7vxPYD77vHSUvM2y0YaTGnFc8AdI5TTQRrM1yIp3tXCKrSL9A7JLoILjtad5t8pQ==", "cpu": [ "arm" ], @@ -1267,9 +1226,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", - "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.5.tgz", + "integrity": "sha512-oTXQeJHRbOnwRnRffb6bmqmUugz0glXaPyspp4gbQOPVApdpRrY/j7KP3lr7M8kTfQTyrBUzFjj5EuHAhqH4/w==", "cpu": [ "arm64" ], @@ -1279,9 +1238,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", - "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.5.tgz", + "integrity": "sha512-qnOTIIs6tIGFKCHdhYitgC2XQ2X25InIbZFor5wh+mALH84qnFHvc+vmWUpyX97B0hNvwNUL4B+MB8vJvH65Fw==", "cpu": [ "arm64" ], @@ -1291,9 +1250,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", - "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.5.tgz", + "integrity": "sha512-TMYu+DUdNlgBXING13rHSfUc3Ky5nLPbWs4bFnT+R6Vu3OvXkTkixvvBKk8uO4MT5Ab6lC3U7x8S8El2q5o56w==", "cpu": [ "ppc64" ], @@ -1303,9 +1262,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", - "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.5.tgz", + "integrity": "sha512-PTQq1Kz22ZRvuhr3uURH+U/Q/a0pbxJoICGSprNLAoBEkyD3Sh9qP5I0Asn0y0wejXQBbsVMRZRxlbGFD9OK4A==", "cpu": [ "riscv64" ], @@ -1315,9 +1274,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", - "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.5.tgz", + "integrity": "sha512-bR5nCojtpuMss6TDEmf/jnBnzlo+6n1UhgwqUvRoe4VIotC7FG1IKkyJbwsT7JDsF2jxR+NTnuOwiGv0hLyDoQ==", "cpu": [ "s390x" ], @@ -1327,9 +1286,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", - "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.5.tgz", + "integrity": "sha512-N0jPPhHjGShcB9/XXZQWuWBKZQnC1F36Ce3sDqWpujsGjDz/CQtOL9LgTrJ+rJC8MJeesMWrMWVLKKNR/tMOCA==", "cpu": [ "x64" ], @@ -1339,9 +1298,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", - "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.5.tgz", + "integrity": "sha512-uBa2e28ohzNNwjr6Uxm4XyaA1M/8aTgfF2T7UIlElLaeXkgpmIJ2EitVNQxjO9xLLLy60YqAgKn/AqSpCUkE9g==", "cpu": [ "x64" ], @@ -1351,9 +1310,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", - "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.5.tgz", + "integrity": "sha512-RXT8S1HP8AFN/Kr3tg4fuYrNxZ/pZf1HemC5Tsddc6HzgGnJm0+Lh5rAHJkDuW3StI0ynNXukidROMXYl6ew8w==", "cpu": [ "arm64" ], @@ -1363,9 +1322,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", - "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.5.tgz", + "integrity": "sha512-ElTYOh50InL8kzyUD6XsnPit7jYCKrphmddKAe1/Ytt74apOxDq5YEcbsiKs0fR3vff3jEneMM+3I7jbqaMyBg==", "cpu": [ "ia32" ], @@ -1375,9 +1334,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", - "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.5.tgz", + "integrity": "sha512-+lvL/4mQxSV8MukpkKyyvfwhH266COcWlXE/1qxwN08ajovta3459zrjLghYMgDerlzNwLAcFpvU+WWE5y6nAQ==", "cpu": [ "x64" ], @@ -1424,9 +1383,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" }, "node_modules/@types/prop-types": { "version": "15.7.10", @@ -1493,15 +1452,15 @@ "dev": true }, "node_modules/@vitejs/plugin-react": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", - "integrity": "sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz", + "integrity": "sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==", "dependencies": { - "@babel/core": "^7.23.5", - "@babel/plugin-transform-react-jsx-self": "^7.23.3", - "@babel/plugin-transform-react-jsx-source": "^7.23.3", + "@babel/core": "^7.24.5", + "@babel/plugin-transform-react-jsx-self": "^7.24.5", + "@babel/plugin-transform-react-jsx-source": "^7.24.1", "@types/babel__core": "^7.20.5", - "react-refresh": "^0.14.0" + "react-refresh": "^0.14.2" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -1800,9 +1759,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", + "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", "funding": [ { "type": "opencollective", @@ -1818,10 +1777,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001663", + "electron-to-chromium": "^1.5.28", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" @@ -1853,9 +1812,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001563", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001563.tgz", - "integrity": "sha512-na2WUmOxnwIZtwnFI2CZ/3er0wdNzU7hN+cPYz/z2ajHThnkWjNBOpEPP4n+4r2WPM847JaMotaJE3bnfzjyKw==", + "version": "1.0.30001664", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz", + "integrity": "sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g==", "funding": [ { "type": "opencollective", @@ -2089,9 +2048,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.588", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.588.tgz", - "integrity": "sha512-soytjxwbgcCu7nh5Pf4S2/4wa6UIu+A3p03U2yVr53qGxi1/VTR3ENI+p50v+UxqqZAfl48j3z55ud7VHIOr9w==" + "version": "1.5.29", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.29.tgz", + "integrity": "sha512-PF8n2AlIhCKXQ+gTpiJi0VhcHDb69kYX4MtCiivctc2QD3XuNZ/XIOlbGzt7WAjjEev0TtaH6Cu3arZExm5DOw==" }, "node_modules/es-abstract": { "version": "1.22.3", @@ -2209,9 +2168,9 @@ } }, "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" @@ -2220,35 +2179,35 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "engines": { "node": ">=6" } @@ -3546,9 +3505,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==" }, "node_modules/object-assign": { "version": "4.1.1", @@ -3786,14 +3745,14 @@ "integrity": "sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==" }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "funding": [ { "type": "opencollective", @@ -3810,8 +3769,8 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -4515,9 +4474,9 @@ } }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dependencies": { "loose-envify": "^1.1.0" }, @@ -4584,9 +4543,9 @@ } }, "node_modules/react-refresh": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", - "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", "engines": { "node": ">=0.10.0" } @@ -4737,11 +4696,11 @@ } }, "node_modules/rollup": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", - "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.5.tgz", + "integrity": "sha512-WoinX7GeQOFMGznEcWA1WrTQCd/tpEbMkc3nuMs9BT0CPjMdSjPMTVClwWd4pgSQwJdP65SK9mTCNvItlr5o7w==", "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.6" }, "bin": { "rollup": "dist/bin/rollup" @@ -4751,22 +4710,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.18.0", - "@rollup/rollup-android-arm64": "4.18.0", - "@rollup/rollup-darwin-arm64": "4.18.0", - "@rollup/rollup-darwin-x64": "4.18.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", - "@rollup/rollup-linux-arm-musleabihf": "4.18.0", - "@rollup/rollup-linux-arm64-gnu": "4.18.0", - "@rollup/rollup-linux-arm64-musl": "4.18.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", - "@rollup/rollup-linux-riscv64-gnu": "4.18.0", - "@rollup/rollup-linux-s390x-gnu": "4.18.0", - "@rollup/rollup-linux-x64-gnu": "4.18.0", - "@rollup/rollup-linux-x64-musl": "4.18.0", - "@rollup/rollup-win32-arm64-msvc": "4.18.0", - "@rollup/rollup-win32-ia32-msvc": "4.18.0", - "@rollup/rollup-win32-x64-msvc": "4.18.0", + "@rollup/rollup-android-arm-eabi": "4.22.5", + "@rollup/rollup-android-arm64": "4.22.5", + "@rollup/rollup-darwin-arm64": "4.22.5", + "@rollup/rollup-darwin-x64": "4.22.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.5", + "@rollup/rollup-linux-arm-musleabihf": "4.22.5", + "@rollup/rollup-linux-arm64-gnu": "4.22.5", + "@rollup/rollup-linux-arm64-musl": "4.22.5", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.5", + "@rollup/rollup-linux-riscv64-gnu": "4.22.5", + "@rollup/rollup-linux-s390x-gnu": "4.22.5", + "@rollup/rollup-linux-x64-gnu": "4.22.5", + "@rollup/rollup-linux-x64-musl": "4.22.5", + "@rollup/rollup-win32-arm64-msvc": "4.22.5", + "@rollup/rollup-win32-ia32-msvc": "4.22.5", + "@rollup/rollup-win32-x64-msvc": "4.22.5", "fsevents": "~2.3.2" } }, @@ -4927,9 +4886,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "engines": { "node": ">=0.10.0" } @@ -5200,9 +5159,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "funding": [ { "type": "opencollective", @@ -5218,8 +5177,8 @@ } ], "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -5246,13 +5205,13 @@ } }, "node_modules/vite": { - "version": "5.2.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz", - "integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==", + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", + "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", "dependencies": { - "esbuild": "^0.20.1", - "postcss": "^8.4.38", - "rollup": "^4.13.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" @@ -5271,6 +5230,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -5288,6 +5248,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, diff --git a/frontend/package.json b/frontend/package.json index f0a37ad0b..23e979115 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,23 +1,23 @@ { "name": "idurar-erp-crm", - "version": "4.0.0", + "version": "4.1.0", "engines": { - "npm": "10.2.4", - "node": "20.9.0" + "node": "20.9.0", + "npm": "10.2.4" }, "type": "module", "dependencies": { "@ant-design/icons": "^5.3.0", "@ant-design/pro-layout": "^7.17.19", "@reduxjs/toolkit": "^2.2.1", - "@vitejs/plugin-react": "^4.2.1", + "@vitejs/plugin-react": "^4.3.1", "antd": "^5.14.1", "axios": "^1.6.2", "cross-env": "7.0.3", "currency.js": "2.0.4", "dayjs": "^1.11.10", "just-compare": "^2.3.0", - "react": "^18.2.0", + "react": "^18.3.1", "react-dom": "^18.2.0", "react-quill": "^2.0.0", "react-redux": "^9.1.0", @@ -25,7 +25,7 @@ "redux": "^5.0.1", "reselect": "^5.1.0", "shortid": "^2.2.16", - "vite": "^5.1.4" + "vite": "^5.4.8" }, "scripts": { "dev": "vite", diff --git a/frontend/src/apps/ErpApp.jsx b/frontend/src/apps/ErpApp.jsx index a197aee32..ffb97de95 100644 --- a/frontend/src/apps/ErpApp.jsx +++ b/frontend/src/apps/ErpApp.jsx @@ -8,13 +8,12 @@ import { Layout } from 'antd'; import { useAppContext } from '@/context/appContext'; import Navigation from '@/apps/Navigation/NavigationContainer'; -import ExpensesNav from '@/apps/Navigation/ExpensesNav'; + import HeaderContent from '@/apps/Header/HeaderContainer'; import PageLoader from '@/components/PageLoader'; import { settingsAction } from '@/redux/settings/actions'; -import { translateAction } from '@/redux/translate/actions'; import { selectSettings } from '@/redux/settings/selectors'; import AppRouter from '@/router/AppRouter'; @@ -22,14 +21,13 @@ import AppRouter from '@/router/AppRouter'; import useResponsive from '@/hooks/useResponsive'; import storePersist from '@/redux/storePersist'; -import { selectLangDirection } from '@/redux/translate/selectors'; export default function ErpCrmApp() { const { Content } = Layout; - const { state: stateApp, appContextAction } = useAppContext(); - const { app } = appContextAction; - const { isNavMenuClose, currentApp } = stateApp; + // const { state: stateApp, appContextAction } = useAppContext(); + // // const { app } = appContextAction; + // const { isNavMenuClose, currentApp } = stateApp; const { isMobile } = useResponsive(); @@ -39,23 +37,20 @@ export default function ErpCrmApp() { dispatch(settingsAction.list({ entity: 'setting' })); }, []); - const appSettings = useSelector(selectAppSettings); + // const appSettings = useSelector(selectAppSettings); const { isSuccess: settingIsloaded } = useSelector(selectSettings); - useEffect(() => { - const { loadDefaultLang } = storePersist.get('firstVisit'); - if (appSettings.idurar_app_language && !loadDefaultLang) { - dispatch(translateAction.translate(appSettings.idurar_app_language)); - window.localStorage.setItem('firstVisit', JSON.stringify({ loadDefaultLang: true })); - } - }, [appSettings]); - const langDirection = useSelector(selectLangDirection); + // useEffect(() => { + // const { loadDefaultLang } = storePersist.get('firstVisit'); + // if (appSettings.idurar_app_language && !loadDefaultLang) { + // window.localStorage.setItem('firstVisit', JSON.stringify({ loadDefaultLang: true })); + // } + // }, [appSettings]); if (settingIsloaded) return ( - - {/* {currentApp === 'default' ? : } */} + {isMobile ? ( diff --git a/frontend/src/apps/Header/HeaderContainer.jsx b/frontend/src/apps/Header/HeaderContainer.jsx index 305672c72..d60568e6d 100644 --- a/frontend/src/apps/Header/HeaderContainer.jsx +++ b/frontend/src/apps/Header/HeaderContainer.jsx @@ -1,6 +1,6 @@ import { useSelector } from 'react-redux'; import { Link, useNavigate } from 'react-router-dom'; -import { Avatar, Dropdown, Layout } from 'antd'; +import { Avatar, Dropdown, Layout, Badge, Button } from 'antd'; // import Notifications from '@/components/Notification'; @@ -14,8 +14,6 @@ import useLanguage from '@/locale/useLanguage'; import UpgradeButton from './UpgradeButton'; -import { selectLangDirection } from '@/redux/translate/selectors'; - export default function HeaderContent() { const currentAdmin = useSelector(selectCurrentAdmin); const { Header } = Layout; @@ -86,14 +84,13 @@ export default function HeaderContent() { }, ]; - const langDirection = useSelector(selectLangDirection); return (
{ - return ( - <> -

{translate('Do you need help on customize of this app')}

- - - ); - }; return ( - } title={translate('Customize this application')} trigger="click"> - - } - style={{ - color: '#f56a00', - backgroundColor: '#FFF', - float: 'right', - marginTop: '5px', - cursor: 'pointer', - }} - /> - - + + + ); } -// console.log( -// '๐Ÿš€ Welcome to IDURAR ERP CRM! Did you know that we also offer commercial customization services? Contact us at hello@idurarapp.com for more information.' -// ); +console.log( + '๐Ÿš€ Welcome to IDURAR ERP CRM! Did you know that we also offer commercial customization services? Contact us at hello@idurarapp.com for more information.' +); diff --git a/frontend/src/apps/Navigation/AppNav.jsx b/frontend/src/apps/Navigation/AppNav.jsx deleted file mode 100644 index 2688d3324..000000000 --- a/frontend/src/apps/Navigation/AppNav.jsx +++ /dev/null @@ -1,128 +0,0 @@ -import { Link } from 'react-router-dom'; - -import { - SettingOutlined, - CustomerServiceOutlined, - ContainerOutlined, - FileSyncOutlined, - DashboardOutlined, - TagOutlined, - TagsOutlined, - UserOutlined, - CreditCardOutlined, - FileOutlined, - ShopOutlined, - FilterOutlined, - WalletOutlined, -} from '@ant-design/icons'; - -const AppNav = ({ translate }) => [ - { - key: 'dashboard', - icon: , - label: {translate('dashboard')}, - }, - { - key: 'customer', - icon: , - label: {translate('customer')}, - }, - { - key: 'people', - icon: , - label: {translate('people')}, - }, - { - key: 'company', - icon: , - label: {translate('company')}, - }, - { - key: 'lead', - icon: , - label: {translate('lead')}, - }, - { - key: 'offer', - icon: , - label: {translate('Offer Leads')}, - }, - { - key: 'invoice', - icon: , - label: {translate('invoice')}, - }, - { - key: 'quote', - icon: , - label: {translate('quote')}, - }, - { - key: 'payment', - icon: , - label: {translate('payment')}, - }, - { - key: 'expenses', - icon: , - label: {translate('expense')}, - }, - { - key: 'product', - icon: , - label: {translate('product')}, - }, - { - key: 'categoryproduct', - icon: , - label: {translate('product_category')}, - }, - // { - // key: 'employee', - // icon: , - // label: {translate('employee')}, - // }, - - { - label: translate('Settings'), - key: 'settings', - icon: , - children: [ - { - key: 'admin', - // icon: , - label: {translate('Staff')}, - }, - { - key: 'generalSettings', - label: {translate('general_settings')}, - }, - { - key: 'expensesCategory', - label: {translate('expenses_Category')}, - }, - // { - // key: 'emailTemplates', - // label: {translate('email_templates')}, - // }, - { - key: 'paymentMode', - label: {translate('payment_mode')}, - }, - { - key: 'taxes', - label: {translate('taxes')}, - }, - { - key: 'about', - label: {translate('about')}, - }, - // { - // key: 'advancedSettings', - // label: {translate('advanced_settings')}, - // }, - ], - }, -]; - -export default AppNav; diff --git a/frontend/src/apps/Navigation/ExpensesNav.jsx b/frontend/src/apps/Navigation/ExpensesNav.jsx deleted file mode 100644 index 8adeff057..000000000 --- a/frontend/src/apps/Navigation/ExpensesNav.jsx +++ /dev/null @@ -1,167 +0,0 @@ -import { useState, useEffect } from 'react'; -import { Link, useLocation } from 'react-router-dom'; -import { Button, Drawer, Layout, Menu } from 'antd'; - -import { useAppContext } from '@/context/appContext'; - -import useLanguage from '@/locale/useLanguage'; -import logoIcon from '@/style/images/logo-icon.svg'; -import logoText from '@/style/images/logo-text.svg'; -import { useNavigate } from 'react-router-dom'; -import useResponsive from '@/hooks/useResponsive'; - -import { - SettingOutlined, - CustomerServiceOutlined, - ContainerOutlined, - FileSyncOutlined, - DashboardOutlined, - TagOutlined, - TagsOutlined, - UserOutlined, - CreditCardOutlined, - MenuOutlined, - FileOutlined, - ShopOutlined, - FilterOutlined, - WalletOutlined, -} from '@ant-design/icons'; - -const { Sider } = Layout; - -export default function Navigation() { - const { isMobile } = useResponsive(); - - return isMobile ? : ; -} - -function Sidebar({ collapsible, isMobile = false }) { - let location = useLocation(); - - const { state: stateApp, appContextAction } = useAppContext(); - const { isNavMenuClose } = stateApp; - const { navMenu } = appContextAction; - const [showLogoApp, setLogoApp] = useState(isNavMenuClose); - const [currentPath, setCurrentPath] = useState(location.pathname.slice(1)); - - const translate = useLanguage(); - const navigate = useNavigate(); - - const items = [ - { - key: 'expenses', - icon: , - label: {translate('expense')}, - }, - { - key: 'expensesCategory', - icon: , - label: {translate('expenses_Category')}, - }, - ]; - - useEffect(() => { - if (location) - if (currentPath !== location.pathname) { - if (location.pathname === '/') { - setCurrentPath('dashboard'); - } else setCurrentPath(location.pathname.slice(1)); - } - }, [location, currentPath]); - - useEffect(() => { - if (isNavMenuClose) { - setLogoApp(isNavMenuClose); - } - const timer = setTimeout(() => { - if (!isNavMenuClose) { - setLogoApp(isNavMenuClose); - } - }, 200); - return () => clearTimeout(timer); - }, [isNavMenuClose]); - const onCollapse = () => { - navMenu.collapse(); - }; - - return ( - -
navigate('/')} style={{ cursor: 'pointer' }}> - Logo - - {!showLogoApp && ( - Logo - )} -
- - - ); -} - -function MobileSidebar() { - const [visible, setVisible] = useState(false); - const showDrawer = () => { - setVisible(true); - }; - const onClose = () => { - setVisible(false); - }; - return ( - <> - - - - - - ); -} diff --git a/frontend/src/apps/Navigation/NavigationContainer.jsx b/frontend/src/apps/Navigation/NavigationContainer.jsx index fe7a663e5..d90e5b3c9 100644 --- a/frontend/src/apps/Navigation/NavigationContainer.jsx +++ b/frontend/src/apps/Navigation/NavigationContainer.jsx @@ -27,8 +27,6 @@ import { WalletOutlined, ReconciliationOutlined, } from '@ant-design/icons'; -import { useSelector } from 'react-redux'; -import { selectLangDirection } from '@/redux/translate/selectors'; const { Sider } = Layout; @@ -61,26 +59,7 @@ function Sidebar({ collapsible, isMobile = false }) { icon: , label: {translate('customers')}, }, - { - key: 'people', - icon: , - label: {translate('peoples')}, - }, - { - key: 'company', - icon: , - label: {translate('companies')}, - }, - { - key: 'lead', - icon: , - label: {translate('leads')}, - }, - { - key: 'offer', - icon: , - label: {translate('offers')}, - }, + { key: 'invoice', icon: , @@ -89,7 +68,7 @@ function Sidebar({ collapsible, isMobile = false }) { { key: 'quote', icon: , - label: {translate('proforma invoices')}, + label: {translate('quote')}, }, { key: 'payment', @@ -98,49 +77,24 @@ function Sidebar({ collapsible, isMobile = false }) { }, { - key: 'product', - icon: , - label: {translate('products')}, + key: 'paymentMode', + label: {translate('payments_mode')}, + icon: , }, { - key: 'categoryproduct', - icon: , - label: {translate('products_category')}, + key: 'taxes', + label: {translate('taxes')}, + icon: , }, { - key: 'expenses', - icon: , - label: {translate('expenses')}, + key: 'generalSettings', + label: {translate('settings')}, + icon: , }, { - key: 'expensesCategory', + key: 'about', + label: {translate('about')}, icon: , - label: {translate('expenses_Category')}, - }, - - { - label: translate('Settings'), - key: 'settings', - icon: , - children: [ - { - key: 'generalSettings', - label: {translate('settings')}, - }, - - { - key: 'paymentMode', - label: {translate('payments_mode')}, - }, - { - key: 'taxes', - label: {translate('taxes')}, - }, - { - key: 'about', - label: {translate('about')}, - }, - ], }, ]; @@ -168,7 +122,6 @@ function Sidebar({ collapsible, isMobile = false }) { navMenu.collapse(); }; - const langDirection = useSelector(selectLangDirection); return ( @@ -237,7 +187,6 @@ function MobileSidebar() { setVisible(false); }; - const langDirection = useSelector(selectLangDirection); return ( <> { - const dispatch = useDispatch(); - - const { isMobile } = useResponsive(); - const [selectOptions, setOptions] = useState([]); - - const navigate = useNavigate(); - - const asyncList = () => { - return request.listAll({ entity: 'currency', options: { enabled: true } }); - }; - const { result, isLoading: fetchIsLoading, isSuccess } = useFetch(asyncList); - useEffect(() => { - if (isSuccess) { - setOptions(result); - } - }, [isSuccess]); - - const money_format_settings = useSelector(selectMoneyFormat); - - const handleSelectChange = (newValue) => { - if (newValue === 'redirectURL') { - navigate('/settings/currency'); - } - }; - - const optionsList = () => { - const list = []; - - const value = 'redirectURL'; - const label = `+ Add New Currency`; - - list.push(...currencyOptions(selectOptions)); - list.push({ value, label }); - - return list; - }; - - if (money_format_settings.default_currency_code) - return ( - - ); - else { - ; - } -}; - -export default ChooseCurrency; diff --git a/frontend/src/components/DataTable/DataTable.jsx b/frontend/src/components/DataTable/DataTable.jsx index f7860a927..b6f34b43d 100644 --- a/frontend/src/components/DataTable/DataTable.jsx +++ b/frontend/src/components/DataTable/DataTable.jsx @@ -22,7 +22,6 @@ import { useMoney, useDate } from '@/settings'; import { generate as uniqueId } from 'shortid'; import { useCrudContext } from '@/context/crud'; -import { selectLangDirection } from '@/redux/translate/selectors'; function AddNewItem({ config }) { const { crudContextAction } = useCrudContext(); @@ -176,13 +175,11 @@ export default function DataTable({ config, extra = [] }) { }; }, []); - const langDirection=useSelector(selectLangDirection) - return ( <> window.history.back()} - backIcon={langDirection==="rtl"?:} + backIcon={} title={DATATABLE_TITLE} ghost={false} extra={[ @@ -200,7 +197,6 @@ export default function DataTable({ config, extra = [] }) { ]} style={{ padding: '20px 0px', - direction:langDirection }} > diff --git a/frontend/src/components/PageLoader/index.jsx b/frontend/src/components/PageLoader/index.jsx index 1598b0f16..3a95ba150 100644 --- a/frontend/src/components/PageLoader/index.jsx +++ b/frontend/src/components/PageLoader/index.jsx @@ -1,10 +1,13 @@ import React from 'react'; import { Spin } from 'antd'; +import { LoadingOutlined } from '@ant-design/icons'; + const PageLoader = () => { + const antIcon = ; return (
- +
); }; diff --git a/frontend/src/components/PaypalButton/Subscription.jsx b/frontend/src/components/PaypalButton/Subscription.jsx deleted file mode 100644 index 2c05f4a53..000000000 --- a/frontend/src/components/PaypalButton/Subscription.jsx +++ /dev/null @@ -1,38 +0,0 @@ -const PaypalButton = () => { - useEffect(() => { - const script = document.createElement('script'); - script.src = - 'https://www.paypal.com/sdk/js?client-id=AXy1YZNZsMCdiYVhh_jyoYW9_HkylFwgkL75WNGw924gL4jHcW5myCTH5JGOyyMiuZSabMWpovoarBnQ&vault=true&intent=subscription'; - script.async = true; - - script.onload = () => { - paypal - .Buttons({ - style: { - shape: 'rect', - color: 'blue', - layout: 'vertical', - label: 'paypal', - }, - createSubscription: function (data, actions) { - return actions.subscription.create({ - /* Creates the subscription */ - plan_id: 'P-6NV451935K3609258MV3DRUQ', - }); - }, - onApprove: function (data, actions) { - alert(data.subscriptionID); // You can add optional success message for the subscriber here - }, - }) - .render('#paypal-button-container-P-6NV451935K3609258MV3DRUQ'); // Renders the PayPal button - }; - - document.body.appendChild(script); - - return () => { - document.body.removeChild(script); - }; - }, []); - - return
; -}; diff --git a/frontend/src/components/SelectAsync/index.jsx b/frontend/src/components/SelectAsync/index.jsx index d1491d0c5..f97fc15eb 100644 --- a/frontend/src/components/SelectAsync/index.jsx +++ b/frontend/src/components/SelectAsync/index.jsx @@ -37,7 +37,7 @@ const SelectAsync = ({ }; useEffect(() => { if (value !== undefined) { - const val = value[outputValue] ?? value; + const val = value?.[outputValue] ?? value; setCurrentValue(val); onChange(val); } @@ -47,7 +47,7 @@ const SelectAsync = ({ if (newValue === 'redirectURL') { navigate(urlToRedirect); } else { - const val = newValue[outputValue] ?? newValue; + const val = newValue?.[outputValue] ?? newValue; setCurrentValue(newValue); onChange(val); } diff --git a/frontend/src/components/SelectTag/index.jsx b/frontend/src/components/SelectTag/index.jsx index d58b1d168..e983a18e8 100644 --- a/frontend/src/components/SelectTag/index.jsx +++ b/frontend/src/components/SelectTag/index.jsx @@ -1,6 +1,5 @@ import { Select, Tag } from 'antd'; import { generate as uniqueId } from 'shortid'; -import { tagColor } from '@/utils/statusTagColor'; export default function SelectTag({ options, defaultValue }) { return ( @@ -11,19 +10,16 @@ export default function SelectTag({ options, defaultValue }) { }} > {options?.map((value) => { - const option = tagColor(value); if (option) return ( - - {translate(option.label)} - + {translate(option.label)} ); else return ( - {value} + {value} ); })} diff --git a/frontend/src/forms/DynamicForm/index.jsx b/frontend/src/forms/DynamicForm/index.jsx index 81e055420..8cc5b2717 100644 --- a/frontend/src/forms/DynamicForm/index.jsx +++ b/frontend/src/forms/DynamicForm/index.jsx @@ -9,15 +9,12 @@ import SelectAsync from '@/components/SelectAsync'; import { generate as uniqueId } from 'shortid'; import { countryList } from '@/utils/countryList'; -import { selectLangDirection } from '@/redux/translate/selectors'; -import { useSelector } from 'react-redux'; export default function DynamicForm({ fields, isUpdateForm = false }) { const [feedback, setFeedback] = useState(); - const langDirection = useSelector(selectLangDirection); return ( -
+
{Object.keys(fields).map((key) => { let field = fields[key]; diff --git a/frontend/src/forms/LoginForm.jsx b/frontend/src/forms/LoginForm.jsx index 24462d1c9..258d5ae6b 100644 --- a/frontend/src/forms/LoginForm.jsx +++ b/frontend/src/forms/LoginForm.jsx @@ -3,32 +3,26 @@ import { Form, Input, Checkbox } from 'antd'; import { UserOutlined, LockOutlined } from '@ant-design/icons'; import useLanguage from '@/locale/useLanguage'; -import { useSelector } from 'react-redux'; -import { selectLangDirection } from '@/redux/translate/selectors'; export default function LoginForm() { - const langDirection = useSelector(selectLangDirection) - const translate = useLanguage(); return ( -
+
+ > } - placeholder={translate('email')} + placeholder={'admin@demo.com'} type="email" size="large" /> @@ -44,19 +38,18 @@ export default function LoginForm() { > } - placeholder={translate('password')} + placeholder={'admin123'} size="large" /> - + {translate('Remember me')} - + {translate('Forgot password')} -
); diff --git a/frontend/src/forms/RegisterForm.jsx b/frontend/src/forms/RegisterForm.jsx index e86693816..530420f30 100644 --- a/frontend/src/forms/RegisterForm.jsx +++ b/frontend/src/forms/RegisterForm.jsx @@ -50,7 +50,7 @@ export default function RegisterForm({ userLocation }) { > } size="large" /> - {/* } size="large" /> - */} + + {children} diff --git a/frontend/src/locale/Localization.jsx b/frontend/src/locale/Localization.jsx index a629c7a15..ba124dc6f 100644 --- a/frontend/src/locale/Localization.jsx +++ b/frontend/src/locale/Localization.jsx @@ -4,11 +4,10 @@ export default function Localization({ children }) { return ( diff --git a/frontend/src/locale/translation/en_us.js b/frontend/src/locale/translation/en_us.js index 5ff2676fd..8127b6d89 100644 --- a/frontend/src/locale/translation/en_us.js +++ b/frontend/src/locale/translation/en_us.js @@ -448,8 +448,8 @@ const lang = { create_only: 'Create Only', enter_code: 'Enter Code', offers: 'Offers', - proforma_invoices: 'Proforma Invoices', - search:"search" + proforma_invoices: 'quote', + search: 'search', }; export default lang; diff --git a/frontend/src/locale/useLanguage.jsx b/frontend/src/locale/useLanguage.jsx index 9adbf31b7..c632402da 100644 --- a/frontend/src/locale/useLanguage.jsx +++ b/frontend/src/locale/useLanguage.jsx @@ -1,46 +1,41 @@ -import { useSelector } from 'react-redux'; - -import { selectCurrentLang } from '@/redux/translate/selectors'; - -const getLabel = (lang, key) => { +const getLabel = (key) => { try { const lowerCaseKey = key .toLowerCase() .replace(/[^a-zA-Z0-9]/g, '_') .replace(/ /g, '_'); - if (lang[lowerCaseKey]) return lang[lowerCaseKey]; - else { - // convert no found language label key to label + // if (lang[lowerCaseKey]) return lang[lowerCaseKey]; - const remove_underscore_fromKey = key.replace(/_/g, ' ').split(' '); + // convert no found language label key to label - const conversionOfAllFirstCharacterofEachWord = remove_underscore_fromKey.map( - (word) => word[0].toUpperCase() + word.substring(1) - ); + const remove_underscore_fromKey = key.replace(/_/g, ' ').split(' '); - const label = conversionOfAllFirstCharacterofEachWord.join(' '); + const conversionOfAllFirstCharacterofEachWord = remove_underscore_fromKey.map( + (word) => word[0].toUpperCase() + word.substring(1) + ); - const result = window.localStorage.getItem('lang'); - if (!result) { - let list = {}; - list[lowerCaseKey] = label; - window.localStorage.setItem('lang', JSON.stringify(list)); - } else { - let list = { ...JSON.parse(result) }; - list[lowerCaseKey] = label; - window.localStorage.removeItem('lang'); - window.localStorage.setItem('lang', JSON.stringify(list)); - } - // console.error( - // '๐Ÿ‡ฉ๐Ÿ‡ฟ ๐Ÿ‡ง๐Ÿ‡ท ๐Ÿ‡ป๐Ÿ‡ณ ๐Ÿ‡ฎ๐Ÿ‡ฉ ๐Ÿ‡จ๐Ÿ‡ณ Language Label Warning : translate("' + - // lowerCaseKey + - // '") failed to get label for this key : ' + - // lowerCaseKey + - // ' please review your language config file and add this label' - // ); - return label; + const label = conversionOfAllFirstCharacterofEachWord.join(' '); + + const result = window.localStorage.getItem('lang'); + if (!result) { + let list = {}; + list[lowerCaseKey] = label; + window.localStorage.setItem('lang', JSON.stringify(list)); + } else { + let list = { ...JSON.parse(result) }; + list[lowerCaseKey] = label; + window.localStorage.removeItem('lang'); + window.localStorage.setItem('lang', JSON.stringify(list)); } + // console.error( + // '๐Ÿ‡ฉ๐Ÿ‡ฟ ๐Ÿ‡ง๐Ÿ‡ท ๐Ÿ‡ป๐Ÿ‡ณ ๐Ÿ‡ฎ๐Ÿ‡ฉ ๐Ÿ‡จ๐Ÿ‡ณ Language Label Warning : translate("' + + // lowerCaseKey + + // '") failed to get label for this key : ' + + // lowerCaseKey + + // ' please review your language config file and add this label' + // ); + return label; } catch (error) { // console.error( // '๐Ÿšจ error getting this label : translate("' + @@ -54,9 +49,7 @@ const getLabel = (lang, key) => { }; const useLanguage = () => { - const lang = useSelector(selectCurrentLang); - - const translate = (value) => getLabel(lang, value); + const translate = (value) => getLabel(value); return translate; }; diff --git a/frontend/src/modules/AdvancedCrudModule/CreateItem.jsx b/frontend/src/modules/AdvancedCrudModule/CreateItem.jsx deleted file mode 100644 index 84e17003c..000000000 --- a/frontend/src/modules/AdvancedCrudModule/CreateItem.jsx +++ /dev/null @@ -1,95 +0,0 @@ -import { useEffect } from 'react'; - -import { Button, Tag, Form, Divider } from 'antd'; -import { PageHeader } from '@ant-design/pro-layout'; - -import { useSelector, useDispatch } from 'react-redux'; - -import useLanguage from '@/locale/useLanguage'; - -import { settingsAction } from '@/redux/settings/actions'; - -import { adavancedCrud } from '@/redux/adavancedCrud/actions'; -import { selectCreatedItem } from '@/redux/adavancedCrud/selectors'; - -import { generate as uniqueId } from 'shortid'; - -import Loading from '@/components/Loading'; -import { CloseCircleOutlined, PlusOutlined } from '@ant-design/icons'; - -import { useNavigate } from 'react-router-dom'; - -function SaveForm({ form }) { - const translate = useLanguage(); - const handelClick = () => { - form.submit(); - }; - - return ( - - ); -} - -export default function CreateItem({ config, CreateForm }) { - const translate = useLanguage(); - const dispatch = useDispatch(); - const navigate = useNavigate(); - - useEffect(() => { - dispatch(settingsAction.list({ entity: 'setting' })); - }, []); - let { entity } = config; - - const { isLoading, isSuccess, result } = useSelector(selectCreatedItem); - const [form] = Form.useForm(); - - useEffect(() => { - if (isSuccess) { - form.resetFields(); - dispatch(adavancedCrud.resetAction({ actionType: 'create' })); - navigate(`/${entity.toLowerCase()}/read/${result._id}`); - } - return () => {}; - }, [isSuccess]); - - const onSubmit = (fieldsValue) => { - if (fieldsValue) { - dispatch(adavancedCrud.create({ entity, jsonData: fieldsValue })); - } - }; - - return ( - <> - { - navigate(`/${entity.toLowerCase()}`); - }} - title={translate('New')} - ghost={false} - tags={{translate('Draft')}} - // subTitle="This is create page" - extra={[ - , - , - ]} - style={{ - padding: '20px 0px', - }} - > - - -
- - -
- - ); -} diff --git a/frontend/src/modules/AdvancedCrudModule/DataTable.jsx b/frontend/src/modules/AdvancedCrudModule/DataTable.jsx deleted file mode 100644 index 1564b6169..000000000 --- a/frontend/src/modules/AdvancedCrudModule/DataTable.jsx +++ /dev/null @@ -1,194 +0,0 @@ -import { useEffect } from 'react'; -import { - EyeOutlined, - EditOutlined, - DeleteOutlined, - FilePdfOutlined, - RedoOutlined, - PlusOutlined, - EllipsisOutlined, -} from '@ant-design/icons'; -import { Dropdown, Table, Button } from 'antd'; -import { PageHeader } from '@ant-design/pro-layout'; - -import { useSelector, useDispatch } from 'react-redux'; -import useLanguage from '@/locale/useLanguage'; -import { adavancedCrud } from '@/redux/adavancedCrud/actions'; -import { selectListItems } from '@/redux/adavancedCrud/selectors'; -import { useAdavancedCrudContext } from '@/context/adavancedCrud'; -import { generate as uniqueId } from 'shortid'; -import { useNavigate } from 'react-router-dom'; - -import { DOWNLOAD_BASE_URL } from '@/config/serverApiConfig'; - -function AddNewItem({ config }) { - const navigate = useNavigate(); - const { ADD_NEW_ENTITY, entity } = config; - - const handleClick = () => { - navigate(`/${entity.toLowerCase()}/create`); - }; - - return ( - - ); -} - -export default function DataTable({ config, extra = [] }) { - const translate = useLanguage(); - let { entity, dataTableColumns, disableAdd = false } = config; - - const { DATATABLE_TITLE } = config; - - const { result: listResult, isLoading: listIsLoading } = useSelector(selectListItems); - - const { pagination, items: dataSource } = listResult; - - const { adavancedCrudContextAction } = useAdavancedCrudContext(); - const { modal } = adavancedCrudContextAction; - - const items = [ - { - label: translate('Show'), - key: 'read', - icon: , - }, - { - label: translate('Edit'), - key: 'edit', - icon: , - }, - { - label: translate('Download'), - key: 'download', - icon: , - }, - ...extra, - { - type: 'divider', - }, - - { - label: translate('Delete'), - key: 'delete', - icon: , - }, - ]; - - const navigate = useNavigate(); - - const handleRead = (record) => { - dispatch(adavancedCrud.currentItem({ data: record })); - navigate(`/${entity}/read/${record._id}`); - }; - const handleEdit = (record) => { - const data = { ...record }; - dispatch(adavancedCrud.currentAction({ actionType: 'update', data })); - navigate(`/${entity}/update/${record._id}`); - }; - const handleDownload = (record) => { - window.open(`${DOWNLOAD_BASE_URL}${entity}/${entity}-${record._id}.pdf`, '_blank'); - }; - - const handleDelete = (record) => { - dispatch(adavancedCrud.currentAction({ actionType: 'delete', data: record })); - modal.open(); - }; - - const handleRecordPayment = (record) => { - dispatch(adavancedCrud.currentItem({ data: record })); - navigate(`/invoice/pay/${record.invoice._id}`); - }; - - dataTableColumns = [ - ...dataTableColumns, - { - title: '', - key: 'action', - fixed: 'right', - render: (_, record) => ( - { - switch (key) { - case 'read': - handleRead(record); - break; - case 'edit': - handleEdit(record); - break; - case 'download': - handleDownload(record); - break; - case 'delete': - handleDelete(record); - break; - case 'recordPayment': - handleRecordPayment(record); - break; - default: - break; - } - }, - }} - trigger={['click']} - > - e.preventDefault()} - /> - - ), - }, - ]; - - const dispatch = useDispatch(); - - const handelDataTableLoad = (pagination) => { - const options = { page: pagination.current || 1, items: pagination.pageSize || 10 }; - dispatch(adavancedCrud.list({ entity, options })); - }; - - const dispatcher = () => { - dispatch(adavancedCrud.list({ entity })); - }; - - useEffect(() => { - const controller = new AbortController(); - dispatcher(); - return () => { - controller.abort(); - }; - }, []); - - return ( - <> - }> - {translate('Refresh')} - , - !disableAdd && , - ]} - style={{ - padding: '20px 0px', - }} - > - - item._id} - dataSource={dataSource} - pagination={pagination} - loading={listIsLoading} - onChange={handelDataTableLoad} - scroll={{ x: true }} - /> - - ); -} diff --git a/frontend/src/modules/AdvancedCrudModule/DeleteItem.jsx b/frontend/src/modules/AdvancedCrudModule/DeleteItem.jsx deleted file mode 100644 index 0fe3d4d4c..000000000 --- a/frontend/src/modules/AdvancedCrudModule/DeleteItem.jsx +++ /dev/null @@ -1,59 +0,0 @@ -import { useEffect, useState } from 'react'; -import { Modal } from 'antd'; - -import { useDispatch, useSelector } from 'react-redux'; -import { adavancedCrud } from '@/redux/adavancedCrud/actions'; -import { useAdavancedCrudContext } from '@/context/adavancedCrud'; -import { selectDeletedItem } from '@/redux/adavancedCrud/selectors'; -import { valueByString } from '@/utils/helpers'; - -export default function Delete({ config }) { - let { - entity, - deleteModalLabels, - deleteMessage = 'Do you want delete : ', - modalTitle = 'Remove Item', - } = config; - const dispatch = useDispatch(); - const { current, isLoading, isSuccess } = useSelector(selectDeletedItem); - const { state, adavancedCrudContextAction } = useAdavancedCrudContext(); - const { deleteModal } = state; - const { modal } = adavancedCrudContextAction; - const [displayItem, setDisplayItem] = useState(''); - - useEffect(() => { - if (isSuccess) { - modal.close(); - const options = { page: 1, items: 10 }; - dispatch(adavancedCrud.list({ entity, options })); - } - if (current) { - let labels = deleteModalLabels.map((x) => valueByString(current, x)).join(' '); - - setDisplayItem(labels); - } - }, [isSuccess, current]); - - const handleOk = () => { - const id = current._id; - dispatch(adavancedCrud.delete({ entity, id })); - modal.close(); - }; - const handleCancel = () => { - if (!isLoading) modal.close(); - }; - return ( - -

- {deleteMessage} - {displayItem} -

-
- ); -} diff --git a/frontend/src/modules/AdvancedCrudModule/ItemRow.jsx b/frontend/src/modules/AdvancedCrudModule/ItemRow.jsx deleted file mode 100644 index 8671be27c..000000000 --- a/frontend/src/modules/AdvancedCrudModule/ItemRow.jsx +++ /dev/null @@ -1,121 +0,0 @@ -import { useState, useEffect } from 'react'; -import { Form, Input, InputNumber, Row, Col } from 'antd'; - -import { DeleteOutlined } from '@ant-design/icons'; -import { useMoney, useDate } from '@/settings'; -import calculate from '@/utils/calculate'; -import AutoCompleteAsync from '@/components/AutoCompleteAsync'; - -export default function ItemRow({ field, remove, current = null }) { - const [totalState, setTotal] = useState(undefined); - const [price, setPrice] = useState(0); - const [quantity, setQuantity] = useState(0); - - const money = useMoney(); - const updateQt = (value) => { - setQuantity(value); - }; - const updatePrice = (value) => { - setPrice(value); - }; - - useEffect(() => { - if (current) { - // When it accesses the /payment/ endpoint, - // it receives an invoice.item instead of just item - // and breaks the code, but now we can check if items exists, - // and if it doesn't we can access invoice.items. - - const { items, invoice } = current; - - if (invoice) { - const item = invoice[field.fieldKey]; - - if (item) { - setQuantity(item.quantity); - setPrice(item.price); - } - } else { - const item = items[field.fieldKey]; - - if (item) { - setQuantity(item.quantity); - setPrice(item.price); - } - } - } - }, [current]); - - useEffect(() => { - const currentTotal = calculate.multiply(price, quantity); - - setTotal(currentTotal); - }, [price, quantity]); - - return ( - - - - console.log(value)} - > - - - - - - - - - - - - - - - - - - - - - money.amountFormatter({ amount: value })} - /> - - - - -
- remove(field.name)} /> -
- - ); -} diff --git a/frontend/src/modules/AdvancedCrudModule/ReadItem.jsx b/frontend/src/modules/AdvancedCrudModule/ReadItem.jsx deleted file mode 100644 index 389515162..000000000 --- a/frontend/src/modules/AdvancedCrudModule/ReadItem.jsx +++ /dev/null @@ -1,315 +0,0 @@ -import { useState, useEffect } from 'react'; -import { Divider } from 'antd'; - -import { Button, Row, Col, Descriptions, Statistic, Tag } from 'antd'; -import { PageHeader } from '@ant-design/pro-layout'; -import { - EditOutlined, - FilePdfOutlined, - CloseCircleOutlined, - RetweetOutlined, - MailOutlined, -} from '@ant-design/icons'; - -import { useSelector, useDispatch } from 'react-redux'; -import useLanguage from '@/locale/useLanguage'; -import { adavancedCrud } from '@/redux/adavancedCrud/actions'; - -import { generate as uniqueId } from 'shortid'; - -import { selectCurrentItem } from '@/redux/adavancedCrud/selectors'; - -import { DOWNLOAD_BASE_URL } from '@/config/serverApiConfig'; -import { useMoney, useDate } from '@/settings'; -import useMail from '@/hooks/useMail'; -import { useNavigate } from 'react-router-dom'; -import { tagColor } from '@/utils/statusTagColor'; - -const Item = ({ item }) => { - const { moneyFormatter } = useMoney(); - return ( - - -

- {item.itemName} -

-

{item.description}

- - -

- {moneyFormatter({ amount: item.price })} -

- - -

- {item.quantity} -

- - -

- {moneyFormatter({ amount: item.total })} -

- - - - ); -}; - -export default function ReadItem({ config, selectedItem }) { - const translate = useLanguage(); - const { entity, ENTITY_NAME } = config; - const dispatch = useDispatch(); - const navigate = useNavigate(); - - const { moneyFormatter } = useMoney(); - const { send, isLoading: mailInProgress } = useMail({ entity }); - - const { result: currentResult } = useSelector(selectCurrentItem); - - const resetAdavancedCrud = { - status: '', - client: { - name: '', - email: '', - phone: '', - address: '', - }, - subTotal: 0, - taxTotal: 0, - taxRate: 0, - total: 0, - credit: 0, - number: 0, - year: 0, - }; - - const [itemslist, setItemsList] = useState([]); - const [currentAdavancedCrud, setCurrentAdavancedCrud] = useState( - selectedItem ?? resetAdavancedCrud - ); - const [client, setClient] = useState({}); - - useEffect(() => { - if (currentResult) { - const { items, invoice, ...others } = currentResult; - - if (items) { - setItemsList(items); - setCurrentAdavancedCrud(currentResult); - } else if (invoice.items) { - setItemsList(invoice.items); - setCurrentAdavancedCrud({ ...invoice.items, ...others, ...invoice }); - } - } - return () => { - setItemsList([]); - setCurrentAdavancedCrud(resetAdavancedCrud); - }; - }, [currentResult]); - - useEffect(() => { - if (currentAdavancedCrud?.client) { - setClient(currentAdavancedCrud.client[currentAdavancedCrud.client.type]); - } - }, [currentAdavancedCrud]); - - return ( - <> - { - navigate(`/${entity.toLowerCase()}`); - }} - title={`${ENTITY_NAME} # ${currentAdavancedCrud.number}/${currentAdavancedCrud.year || ''}`} - ghost={false} - tags={[ - - {currentAdavancedCrud.status && translate(currentAdavancedCrud.status)} - , - currentAdavancedCrud.paymentStatus && ( - - {currentAdavancedCrud.paymentStatus && translate(currentAdavancedCrud.paymentStatus)} - - ), - ]} - extra={[ - , - , - , - , - - , - ]} - style={{ - padding: '20px 0px', - }} - > - - - - - - - - - - {client.address} - {client.email} - {client.phone} - - - - -

- {translate('Product')} -

- - -

- {translate('Price')} -

- - -

- {translate('Quantity')} -

- - -

- {translate('Total')} -

- - - - {itemslist.map((item) => ( - - ))} -
- -
-

{translate('Sub Total')} :

- - - -

{moneyFormatter({ amount: currentAdavancedCrud.subTotal })}

- - -

- {translate('Tax Total')} ({currentAdavancedCrud.taxRate} %) : -

- - -

{moneyFormatter({ amount: currentAdavancedCrud.taxTotal })}

- - -

{translate('Total')} :

- - -

{moneyFormatter({ amount: currentAdavancedCrud.total })}

- - - - - ); -} diff --git a/frontend/src/modules/AdvancedCrudModule/SearchItem.jsx b/frontend/src/modules/AdvancedCrudModule/SearchItem.jsx deleted file mode 100644 index 04709abae..000000000 --- a/frontend/src/modules/AdvancedCrudModule/SearchItem.jsx +++ /dev/null @@ -1,96 +0,0 @@ -import { useEffect, useState, useRef } from 'react'; - -import { AutoComplete, Input } from 'antd'; -import { SearchOutlined } from '@ant-design/icons'; -import { useSelector, useDispatch } from 'react-redux'; -import { adavancedCrud } from '@/redux/adavancedCrud/actions'; - -import { useAdavancedCrudContext } from '@/context/adavancedCrud'; -import { selectSearchedItems } from '@/redux/adavancedCrud/selectors'; - -import { Empty } from 'antd'; - -export default function Search({ config }) { - let { entity, searchConfig } = config; - - const { displayLabels, searchFields, outputValue = '_id' } = searchConfig; - const dispatch = useDispatch(); - const [value, setValue] = useState(''); - const [options, setOptions] = useState([]); - - const { adavancedCrudContextAction } = useAdavancedCrudContext(); - const { panel, collapsedBox, readBox } = adavancedCrudContextAction; - - const { result, isLoading, isSuccess } = useSelector(selectSearchedItems); - - const isTyping = useRef(false); - - let delayTimer = null; - useEffect(() => { - isLoading && setOptions([{ label: '... Searching' }]); - }, [isLoading]); - const onSearch = (searchText) => { - isTyping.current = true; - - clearTimeout(delayTimer); - delayTimer = setTimeout(function () { - if (isTyping.current && searchText !== '') { - dispatch( - adavancedCrud.search(entity, { - question: searchText, - fields: searchFields, - }) - ); - } - isTyping.current = false; - }, 500); - }; - - const onSelect = (data) => { - const currentItem = result.find((item) => { - return item[outputValue] === data; - }); - - dispatch(adavancedCrud.currentItem({ data: currentItem })); - panel.open(); - collapsedBox.open(); - readBox.open(); - }; - - const onChange = (data) => { - const currentItem = options.find((item) => { - return item.value === data; - }); - const currentValue = currentItem ? currentItem.label : data; - setValue(currentValue); - }; - - useEffect(() => { - let optionResults = []; - - result.map((item) => { - const labels = displayLabels.map((x) => item[x]).join(' '); - optionResults.push({ label: labels, value: item[outputValue] }); - }); - - setOptions(optionResults); - }, [result]); - - return ( - : ''} - allowClear={true} - placeholder="Your Search here" - > - } /> - - ); -} diff --git a/frontend/src/modules/AdvancedCrudModule/UpdateItem.jsx b/frontend/src/modules/AdvancedCrudModule/UpdateItem.jsx deleted file mode 100644 index aaea8b396..000000000 --- a/frontend/src/modules/AdvancedCrudModule/UpdateItem.jsx +++ /dev/null @@ -1,175 +0,0 @@ -import { useState, useEffect } from 'react'; -import { Form, Divider } from 'antd'; -import dayjs from 'dayjs'; -import { Button, Tag } from 'antd'; -import { PageHeader } from '@ant-design/pro-layout'; - -import { useSelector, useDispatch } from 'react-redux'; -import useLanguage from '@/locale/useLanguage'; -import { adavancedCrud } from '@/redux/adavancedCrud/actions'; - -import calculate from '@/utils/calculate'; -import { generate as uniqueId } from 'shortid'; -import { selectUpdatedItem } from '@/redux/adavancedCrud/selectors'; -import Loading from '@/components/Loading'; -import { tagColor } from '@/utils/statusTagColor'; - -import { CloseCircleOutlined, PlusOutlined } from '@ant-design/icons'; -import { useNavigate, useParams } from 'react-router-dom'; -// import { StatusTag } from '@/components/Tag'; - -function SaveForm({ form, translate }) { - const handelClick = () => { - form.submit(); - }; - - return ( - - ); -} - -export default function UpdateItem({ config, UpdateForm }) { - const translate = useLanguage(); - let { entity } = config; - - const dispatch = useDispatch(); - const navigate = useNavigate(); - const { current, isLoading, isSuccess } = useSelector(selectUpdatedItem); - const [form] = Form.useForm(); - const [subTotal, setSubTotal] = useState(0); - - const resetAdavancedCrud = { - status: '', - client: { - name: '', - email: '', - phone: '', - address: '', - }, - subTotal: 0, - taxTotal: 0, - taxRate: 0, - total: 0, - credit: 0, - number: 0, - year: 0, - }; - - const [currentAdavancedCrud, setCurrentAdavancedCrud] = useState(current ?? resetAdavancedCrud); - - const { id } = useParams(); - - const handelValuesChange = (changedValues, values) => { - const items = values['items']; - let subTotal = 0; - - if (items) { - items.map((item) => { - if (item) { - if (item.quantity && item.price) { - let total = calculate.multiply(item['quantity'], item['price']); - //sub total - subTotal = calculate.add(subTotal, total); - } - } - }); - setSubTotal(subTotal); - } - }; - - const onSubmit = (fieldsValue) => { - let dataToUpdate = { ...fieldsValue }; - if (fieldsValue) { - if (fieldsValue.date || fieldsValue.expiredDate) { - dataToUpdate.date = dayjs(fieldsValue.date).format('YYYY-MM-DDTHH:mm:ss.SSSZ'); - dataToUpdate.expiredDate = dayjs(fieldsValue.expiredDate).format( - 'YYYY-MM-DDTHH:mm:ss.SSSZ' - ); - } - if (fieldsValue.items) { - let newList = [...fieldsValue.items]; - newList.map((item) => { - item.total = item.quantity * item.price; - }); - dataToUpdate.items = newList; - } - } - - dispatch(adavancedCrud.update({ entity, id, jsonData: dataToUpdate })); - }; - useEffect(() => { - if (isSuccess) { - form.resetFields(); - setSubTotal(0); - dispatch(adavancedCrud.resetAction({ actionType: 'update' })); - navigate(`/${entity.toLowerCase()}/read/${id}`); - } - }, [isSuccess]); - - useEffect(() => { - if (current) { - setCurrentAdavancedCrud(current); - let formData = { ...current }; - if (formData.date) { - formData.date = dayjs(formData.date); - } - if (formData.expiredDate) { - formData.expiredDate = dayjs(formData.expiredDate); - } - if (!formData.taxRate) { - formData.taxRate = 0; - } - - const { subTotal } = formData; - - form.resetFields(); - form.setFieldsValue(formData); - setSubTotal(subTotal); - } - }, [current]); - - return ( - <> - { - navigate(`/${entity.toLowerCase()}`); - }} - title={translate('update')} - ghost={false} - tags={[ - - {currentAdavancedCrud.status && translate(currentAdavancedCrud.status)} - , - currentAdavancedCrud.paymentStatus && ( - - {currentAdavancedCrud.paymentStatus && translate(currentAdavancedCrud.paymentStatus)} - - ), - ]} - extra={[ - , - , - ]} - style={{ - padding: '20px 0px', - }} - > - - -
- - -
- - ); -} diff --git a/frontend/src/modules/AdvancedCrudModule/index.jsx b/frontend/src/modules/AdvancedCrudModule/index.jsx deleted file mode 100644 index e2e00ee70..000000000 --- a/frontend/src/modules/AdvancedCrudModule/index.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import { useLayoutEffect } from 'react'; - -import DataTable from './DataTable'; - -import Delete from './DeleteItem'; - -import { useDispatch } from 'react-redux'; -import { adavancedCrud } from '@/redux/adavancedCrud/actions'; - -import { useAdavancedCrudContext } from '@/context/adavancedCrud'; - -export default function AdavancedCrudPanel({ config, extra }) { - const dispatch = useDispatch(); - const { state } = useAdavancedCrudContext(); - const { deleteModal } = state; - - const dispatcher = () => { - dispatch(adavancedCrud.resetState()); - }; - - useLayoutEffect(() => { - const controller = new AbortController(); - dispatcher(); - return () => { - controller.abort(); - }; - }, []); - - return ( - <> - - - - ); -} diff --git a/frontend/src/modules/AuthModule/SideContent.jsx b/frontend/src/modules/AuthModule/SideContent.jsx index 0e1596ec3..c1fcca01a 100644 --- a/frontend/src/modules/AuthModule/SideContent.jsx +++ b/frontend/src/modules/AuthModule/SideContent.jsx @@ -2,21 +2,19 @@ import { Space, Layout, Divider, Typography } from 'antd'; import logo from '@/style/images/idurar-crm-erp.svg'; import useLanguage from '@/locale/useLanguage'; import { useSelector } from 'react-redux'; -import { selectLangDirection } from '@/redux/translate/selectors'; const { Content } = Layout; const { Title, Text } = Typography; export default function SideContent() { const translate = useLanguage(); - const langDirection = useSelector(selectLangDirection) return ( -
- {translate('Manage your company with')} : -
-
    -
  • - - {translate('All-in-one tool')} - - {translate('Run and scale your ERP CRM Apps')} - -
  • + + Free Open Source ERP / CRM + + + Accounting / Invoicing / Quote App based on Node.js React.js Ant Design + -
  • - - {translate('Easily add and manage your services')} - {translate('It brings together your invoice clients and leads')} - -
  • -
- -
- {/* Logo1 - Logo2 - Logo3 - Logo4 */} -
+
); diff --git a/frontend/src/modules/DashboardModule/components/CustomerPreviewCard.jsx b/frontend/src/modules/DashboardModule/components/CustomerPreviewCard.jsx index 2b87225ae..9acde89dc 100644 --- a/frontend/src/modules/DashboardModule/components/CustomerPreviewCard.jsx +++ b/frontend/src/modules/DashboardModule/components/CustomerPreviewCard.jsx @@ -1,6 +1,6 @@ -import { Statistic, Progress, Divider, Row, Spin } from "antd"; -import { ArrowUpOutlined, ArrowDownOutlined } from "@ant-design/icons"; -import useLanguage from "@/locale/useLanguage"; +import { Statistic, Progress, Divider, Row, Spin } from 'antd'; +import { ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons'; +import useLanguage from '@/locale/useLanguage'; export default function CustomerPreviewCard({ isLoading = false, @@ -14,12 +14,12 @@ export default function CustomerPreviewCard({
-

- {translate("Customers")} +

+ {translate('Customers')}

{isLoading ? ( @@ -27,23 +27,23 @@ export default function CustomerPreviewCard({ ) : (
-

{translate("New Customer this Month")}

+

{translate('New Customer this Month')}

0 - ? { color: "#3f8600" } + ? { color: '#333' } : activeCustomer < 0 - ? { color: "#cf1322" } - : { color: "#000000" } + ? { color: '#333' } + : { color: '#000000' } } prefix={ activeCustomer > 0 ? ( diff --git a/frontend/src/modules/DashboardModule/components/PreviewCard.jsx b/frontend/src/modules/DashboardModule/components/PreviewCard.jsx index 0429bd46a..d2ca4c90e 100644 --- a/frontend/src/modules/DashboardModule/components/PreviewCard.jsx +++ b/frontend/src/modules/DashboardModule/components/PreviewCard.jsx @@ -71,7 +71,7 @@ const defaultInvoiceStatistics = [ }, ]; -const PreviewState = ({ tag, color, value }) => { +const PreviewState = ({ tag, value }) => { const translate = useLanguage(); return (
@@ -81,8 +81,8 @@ const PreviewState = ({ tag, color, value }) => { percent={value} showInfo={false} strokeColor={{ - '0%': color, - '100%': color, + '0%': '#333', + '100%': '#333', }} />
@@ -124,8 +124,8 @@ export default function PreviewCard({ className="gutter-row" xs={{ span: 24 }} sm={{ span: 24 }} - md={{ span: 8 }} - lg={{ span: 8 }} + md={{ span: 12 }} + lg={{ span: 12 }} >

( - + // sort by colours )) .sort(customSort) diff --git a/frontend/src/modules/DashboardModule/index.jsx b/frontend/src/modules/DashboardModule/index.jsx index 99f1c21a3..2dfbb450e 100644 --- a/frontend/src/modules/DashboardModule/index.jsx +++ b/frontend/src/modules/DashboardModule/index.jsx @@ -8,7 +8,6 @@ import { useMoney } from '@/settings'; import { request } from '@/request'; import useFetch from '@/hooks/useFetch'; import useOnFetch from '@/hooks/useOnFetch'; -import { tagColor } from '@/utils/statusTagColor'; import RecentTable from './components/RecentTable'; @@ -39,8 +38,6 @@ export default function DashboardModule() { const { result: quoteResult, isLoading: quoteLoading, onFetch: fetchQuotesStats } = useOnFetch(); - const { result: offerResult, isLoading: offerLoading, onFetch: fetchOffersStats } = useOnFetch(); - const { result: paymentResult, isLoading: paymentLoading, @@ -57,7 +54,6 @@ export default function DashboardModule() { if (currency) { fetchInvoicesStats(getStatsData({ entity: 'invoice', currency })); fetchQuotesStats(getStatsData({ entity: 'quote', currency })); - fetchOffersStats(getStatsData({ entity: 'offer', currency })); fetchPayemntsStats(getStatsData({ entity: 'payment', currency })); } }, [money_format_settings.default_currency_code]); @@ -89,9 +85,6 @@ export default function DashboardModule() { { title: translate('Status'), dataIndex: 'status', - render: (status) => { - return {translate(status)}; - }, }, ]; @@ -106,13 +99,7 @@ export default function DashboardModule() { result: quoteResult, isLoading: quoteLoading, entity: 'quote', - title: translate('proforma invoices'), - }, - { - result: offerResult, - isLoading: offerLoading, - entity: 'offer', - title: translate('offers'), + title: translate('quote'), }, ]; @@ -143,28 +130,24 @@ export default function DashboardModule() { - - - ); -} diff --git a/frontend/src/modules/EmailModule/ReadEmailModule/components/ReadItem.jsx b/frontend/src/modules/EmailModule/ReadEmailModule/components/ReadItem.jsx deleted file mode 100644 index d076a7a4c..000000000 --- a/frontend/src/modules/EmailModule/ReadEmailModule/components/ReadItem.jsx +++ /dev/null @@ -1,102 +0,0 @@ -import { useState, useEffect } from 'react'; - -import { Divider, Typography, Button } from 'antd'; -import { PageHeader } from '@ant-design/pro-layout'; -import { EditOutlined, CloseCircleOutlined } from '@ant-design/icons'; - -import { useSelector, useDispatch } from 'react-redux'; -import { erp } from '@/redux/erp/actions'; - -import { useErpContext } from '@/context/erp'; -import { generate as uniqueId } from 'shortid'; - -import { selectCurrentItem } from '@/redux/erp/selectors'; - -import { useNavigate } from 'react-router-dom'; -import useLanguage from '@/locale/useLanguage'; - -const { Title, Paragraph } = Typography; - -export default function ReadItem({ config, selectedItem }) { - const translate = useLanguage(); - const { entity, ENTITY_NAME } = config; - const dispatch = useDispatch(); - const { erpContextAction } = useErpContext(); - const navigate = useNavigate(); - - const { result: currentResult } = useSelector(selectCurrentItem); - - const { readPanel, updatePanel } = erpContextAction; - - const resetErp = { - emailName: '', - emailKey: '', - emailSubject: '', - emailBody: '', - emailVariables: [], - _id: '', - }; - - const [currentErp, setCurrentErp] = useState(selectedItem ?? resetErp); - - useEffect(() => { - const controller = new AbortController(); - if (currentResult) { - setCurrentErp(currentResult); - } - return () => controller.abort(); - }, [currentResult]); - - return ( - <> - { - readPanel.close(); - navigate(`/${entity.toLowerCase()}`);//navigate to previous page - }} - title={`${ENTITY_NAME} # ${currentErp?.emailName}`} - ghost={false} - extra={[ - , - - , - ]} - style={{ - padding: '20px 0px', - }} - > - -
- {translate('Subject')} - {currentErp.emailSubject} - {translate('Body')} -
-
- - ); -} diff --git a/frontend/src/modules/EmailModule/ReadEmailModule/index.jsx b/frontend/src/modules/EmailModule/ReadEmailModule/index.jsx deleted file mode 100644 index e4f80954b..000000000 --- a/frontend/src/modules/EmailModule/ReadEmailModule/index.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import NotFound from '@/components/NotFound'; -import { ErpLayout } from '@/layout'; -import ReadItem from './components/ReadItem'; - -import PageLoader from '@/components/PageLoader'; -import { erp } from '@/redux/erp/actions'; -import { selectReadItem } from '@/redux/erp/selectors'; -import { useLayoutEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; - -import { useParams } from 'react-router-dom'; - -export default function ReadEmailModule({ config }) { - const dispatch = useDispatch(); - const { id } = useParams(); - - useLayoutEffect(() => { - dispatch(erp.read({ entity: config.entity, id })); - }, [id]); - - const { result: currentResult, isSuccess, isLoading = true } = useSelector(selectReadItem); - - if (isLoading) { - return ( - - - - ); - } else - return ( - - {isSuccess ? ( - - ) : ( - - )} - - ); -} diff --git a/frontend/src/modules/EmailModule/UpdateEmailModule/componenets/EmailForm.jsx b/frontend/src/modules/EmailModule/UpdateEmailModule/componenets/EmailForm.jsx deleted file mode 100644 index b0a6be681..000000000 --- a/frontend/src/modules/EmailModule/UpdateEmailModule/componenets/EmailForm.jsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useState } from 'react'; -import { Form, Input, Button, Row, Col, Typography, Tag } from 'antd'; -import ReactQuill from 'react-quill'; -import 'react-quill/dist/quill.snow.css'; -import { PlusOutlined } from '@ant-design/icons'; -import useLanguage from '@/locale/useLanguage'; - -const { Paragraph } = Typography; - -export default function EmailForm({ current = null }) { - const translate = useLanguage(); - const [body, setBody] = useState(current?.emailBody); - - const displayLabels = (labels = []) => ( - <> - {labels.map((label, index) => ( - setBody(body)} color="blue"> - {label} - - ))} - - ); - - const setBodyValue = (value) => { - setBody(value); - current.emailBody = value; - }; - - return ( - -

- {translate('Available Variables')} : - {displayLabels(current?.emailVariables)} -
- - - - - - - - - {translate('To write a variable name use the convention')} {`{{variable}}`} e.g. name -{' '} - {`{{name}}`} - - - - - - - - ); -} diff --git a/frontend/src/modules/EmailModule/UpdateEmailModule/index.jsx b/frontend/src/modules/EmailModule/UpdateEmailModule/index.jsx deleted file mode 100644 index c771ac874..000000000 --- a/frontend/src/modules/EmailModule/UpdateEmailModule/index.jsx +++ /dev/null @@ -1,49 +0,0 @@ -import NotFound from '@/components/NotFound'; - -import { ErpLayout } from '@/layout'; -import UpdateItem from '@/modules/ErpPanelModule/UpdateItem'; -import EmailForm from './componenets/EmailForm'; - -import PageLoader from '@/components/PageLoader'; - -import { erp } from '@/redux/erp/actions'; -import { selectReadItem } from '@/redux/erp/selectors'; -import { useLayoutEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; - -import { useParams } from 'react-router-dom'; - -export default function UpdateEmailModule({ config }) { - const dispatch = useDispatch(); - - const { id } = useParams(); - - useLayoutEffect(() => { - dispatch(erp.read({ entity: config.entity, id })); - }, [id]); - - const { result: currentResult, isSuccess, isLoading = true } = useSelector(selectReadItem); - - useLayoutEffect(() => { - if (currentResult) { - dispatch(erp.currentAction({ actionType: 'update', data: currentResult })); - } - }, [currentResult]); - - if (isLoading) { - return ( - - - - ); - } else - return ( - - {isSuccess ? ( - - ) : ( - - )} - - ); -} diff --git a/frontend/src/modules/ErpPanelModule/CreateItem.jsx b/frontend/src/modules/ErpPanelModule/CreateItem.jsx index 5c91c6505..0cb135440 100644 --- a/frontend/src/modules/ErpPanelModule/CreateItem.jsx +++ b/frontend/src/modules/ErpPanelModule/CreateItem.jsx @@ -23,7 +23,6 @@ import { } from '@ant-design/icons'; import { useNavigate } from 'react-router-dom'; -import { selectLangDirection } from '@/redux/translate/selectors'; function SaveForm({ form }) { const translate = useLanguage(); @@ -103,14 +102,14 @@ export default function CreateItem({ config, CreateForm }) { } dispatch(erp.create({ entity, jsonData: fieldsValue })); }; - const langDirection = useSelector(selectLangDirection); + return ( <> { navigate(`/${entity.toLowerCase()}`); }} - backIcon={langDirection === 'rtl' ? : } + backIcon={} title={translate('New')} ghost={false} tags={{translate('Draft')}} diff --git a/frontend/src/modules/ErpPanelModule/DataTable.jsx b/frontend/src/modules/ErpPanelModule/DataTable.jsx index 6daaa59c8..1404ba1e5 100644 --- a/frontend/src/modules/ErpPanelModule/DataTable.jsx +++ b/frontend/src/modules/ErpPanelModule/DataTable.jsx @@ -23,7 +23,6 @@ import { generate as uniqueId } from 'shortid'; import { useNavigate } from 'react-router-dom'; import { DOWNLOAD_BASE_URL } from '@/config/serverApiConfig'; -import { selectLangDirection } from '@/redux/translate/selectors'; function AddNewItem({ config }) { const navigate = useNavigate(); @@ -173,7 +172,6 @@ export default function DataTable({ config, extra = [] }) { const options = { equal: value, filter: searchConfig?.entity }; dispatch(erp.list({ entity, options })); }; - const langDirection=useSelector(selectLangDirection) return ( <> @@ -181,7 +179,7 @@ export default function DataTable({ config, extra = [] }) { title={DATATABLE_TITLE} ghost={true} onBack={() => window.history.back()} - backIcon={langDirection==="rtl"?:} + backIcon={} extra={[ diff --git a/frontend/src/modules/ErpPanelModule/ReadItem.jsx b/frontend/src/modules/ErpPanelModule/ReadItem.jsx index fe9ada0b0..364f3070f 100644 --- a/frontend/src/modules/ErpPanelModule/ReadItem.jsx +++ b/frontend/src/modules/ErpPanelModule/ReadItem.jsx @@ -23,7 +23,6 @@ import { DOWNLOAD_BASE_URL } from '@/config/serverApiConfig'; import { useMoney, useDate } from '@/settings'; import useMail from '@/hooks/useMail'; import { useNavigate } from 'react-router-dom'; -import { tagColor } from '@/utils/statusTagColor'; const Item = ({ item, currentErp }) => { const { moneyFormatter } = useMoney(); @@ -120,7 +119,7 @@ export default function ReadItem({ config, selectedItem }) { useEffect(() => { if (currentErp?.client) { - setClient(currentErp.client[currentErp.client.type]); + setClient(currentErp.client); } }, [currentErp]); @@ -133,13 +132,11 @@ export default function ReadItem({ config, selectedItem }) { title={`${ENTITY_NAME} # ${currentErp.number}/${currentErp.year || ''}`} ghost={false} tags={[ - - {currentErp.status && translate(currentErp.status)} - , + {currentErp.status && translate(currentErp.status)}, currentErp.paymentStatus && ( - + {currentErp.paymentStatus && translate(currentErp.paymentStatus)} - + ), ]} extra={[ diff --git a/frontend/src/modules/ErpPanelModule/UpdateItem.jsx b/frontend/src/modules/ErpPanelModule/UpdateItem.jsx index 7e5ff02e2..580bd444c 100644 --- a/frontend/src/modules/ErpPanelModule/UpdateItem.jsx +++ b/frontend/src/modules/ErpPanelModule/UpdateItem.jsx @@ -12,7 +12,6 @@ import calculate from '@/utils/calculate'; import { generate as uniqueId } from 'shortid'; import { selectUpdatedItem } from '@/redux/erp/selectors'; import Loading from '@/components/Loading'; -import { tagColor } from '@/utils/statusTagColor'; import { CloseCircleOutlined, PlusOutlined } from '@ant-design/icons'; import { useNavigate, useParams } from 'react-router-dom'; @@ -143,13 +142,11 @@ export default function UpdateItem({ config, UpdateForm }) { title={translate('update')} ghost={false} tags={[ - - {currentErp.status && translate(currentErp.status)} - , + {currentErp.status && translate(currentErp.status)}, currentErp.paymentStatus && ( - + {currentErp.paymentStatus && translate(currentErp.paymentStatus)} - + ), ]} extra={[ diff --git a/frontend/src/modules/InvoiceModule/RecordPaymentModule/components/Payment.jsx b/frontend/src/modules/InvoiceModule/RecordPaymentModule/components/Payment.jsx index 4fb0b1fa4..90ba819a5 100644 --- a/frontend/src/modules/InvoiceModule/RecordPaymentModule/components/Payment.jsx +++ b/frontend/src/modules/InvoiceModule/RecordPaymentModule/components/Payment.jsx @@ -10,7 +10,7 @@ import { useMoney } from '@/settings'; import RecordPayment from './RecordPayment'; import useLanguage from '@/locale/useLanguage'; -import { tagColor } from '@/utils/statusTagColor'; + import { useNavigate } from 'react-router-dom'; export default function Payment({ config, currentItem }) { @@ -26,7 +26,7 @@ export default function Payment({ config, currentItem }) { const [client, setClient] = useState({}); useEffect(() => { if (currentErp?.client) { - setClient(currentErp.client[currentErp.client.type]); + setClient(currentErp.client); } }, [currentErp]); @@ -41,10 +41,6 @@ export default function Payment({ config, currentItem }) { return () => controller.abort(); }, [currentItem]); - useEffect(() => { - console.info('itemslist', itemslist); - }, [itemslist]); - return ( <> @@ -61,11 +57,7 @@ export default function Payment({ config, currentItem }) { currentErp.year || '' }`} ghost={false} - tags={ - - {currentErp.paymentStatus && translate(currentErp.paymentStatus)} - - } + tags={{currentErp.paymentStatus && translate(currentErp.paymentStatus)}} // subTitle="This is cuurent erp page" extra={[
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

{translate('Item')}

- -
-

{translate('Description')}

- -
-

{translate('Quantity')}

{' '} - -
-

{translate('Price')}

- -
-

{translate('Total')}

- - - - {(fields, { add, remove }) => ( - <> - {fields.map((field) => ( - - ))} - - - - - )} - - -
- -
- - - - - -

- {translate('Sub Total')} : -

- -
- - - - - - - - - - - - - - - -

- {translate('Total')} : -

- -
- - - - - - ); -} diff --git a/frontend/src/modules/OfferModule/OfferDataTableModule/index.jsx b/frontend/src/modules/OfferModule/OfferDataTableModule/index.jsx deleted file mode 100644 index 26a7628ea..000000000 --- a/frontend/src/modules/OfferModule/OfferDataTableModule/index.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import { ErpLayout } from '@/layout'; -import ErpPanel from '@/modules/ErpPanelModule'; - -export default function OffereDataTableModule({ config }) { - return ( - - - - ); -} diff --git a/frontend/src/modules/OfferModule/ReadOfferModule/ReadOfferItem.jsx b/frontend/src/modules/OfferModule/ReadOfferModule/ReadOfferItem.jsx deleted file mode 100644 index 485f93cff..000000000 --- a/frontend/src/modules/OfferModule/ReadOfferModule/ReadOfferItem.jsx +++ /dev/null @@ -1,318 +0,0 @@ -import { useState, useEffect } from 'react'; - -import { Button, Row, Col, Descriptions, Statistic, Tag, Divider } from 'antd'; -import { PageHeader } from '@ant-design/pro-layout'; -import { - EditOutlined, - FilePdfOutlined, - CloseCircleOutlined, - RetweetOutlined, - MailOutlined, -} from '@ant-design/icons'; - -import { useSelector, useDispatch } from 'react-redux'; -import useLanguage from '@/locale/useLanguage'; -import { erp } from '@/redux/erp/actions'; - -import { generate as uniqueId } from 'shortid'; - -import { selectCurrentItem } from '@/redux/erp/selectors'; - -import { DOWNLOAD_BASE_URL } from '@/config/serverApiConfig'; -import { useMoney, useDate } from '@/settings'; -import useMail from '@/hooks/useMail'; -import { useNavigate } from 'react-router-dom'; -import { tagColor } from '@/utils/statusTagColor'; - -const Item = ({ item, currentErp }) => { - const { moneyFormatter } = useMoney(); - return ( - - -

- {item.itemName} -

-

{item.description}

- -
-

- {moneyFormatter({ amount: item.price, currency_code: currentErp.currency })} -

- -
-

- {item.quantity} -

- -
-

- {moneyFormatter({ amount: item.total, currency_code: currentErp.currency })} -

- - - - ); -}; - -export default function ReadOfferItem({ config, selectedItem }) { - const translate = useLanguage(); - const { entity, ENTITY_NAME } = config; - const dispatch = useDispatch(); - - const { moneyFormatter } = useMoney(); - const { send, isLoading: mailInProgress } = useMail({ entity }); - const navigate = useNavigate(); - const [lead, setLead] = useState({}); - - const { result: currentResult } = useSelector(selectCurrentItem); - - const resetErp = { - status: '', - lead: { - name: '', - email: '', - phone: '', - address: '', - }, - subTotal: 0, - taxTotal: 0, - taxRate: 0, - total: 0, - credit: 0, - number: 0, - year: 0, - }; - - const [itemslist, setItemsList] = useState([]); - const [currentErp, setCurrentErp] = useState(selectedItem ?? resetErp); - - useEffect(() => { - const controller = new AbortController(); - if (currentResult) { - const { items, invoice, ...others } = currentResult; - - // When it accesses the /payment/ endpoint, - // it receives an invoice.item instead of just item - // and breaks the code, but now we can check if items exists, - // and if it doesn't we can access invoice.items and bring - // out the neccessary propery alongside other properties - - if (items) { - setItemsList(items); - setCurrentErp(currentResult); - } else if (invoice.items) { - setItemsList(invoice.items); - setCurrentErp({ ...invoice.items, ...others, ...invoice }); - } - } - return () => controller.abort(); - }, [currentResult]); - - useEffect(() => { - if (currentErp?.lead) { - setLead(currentErp.lead[currentErp.lead.type]); - } - }, [currentErp]); - - return ( - <> - { - navigate(`/${entity.toLowerCase()}`); - }} - title={`${ENTITY_NAME} # ${currentErp.number}/${currentErp.year || ''}`} - ghost={false} - tags={{translate(currentErp.status)}} - // subTitle="This is cuurent erp page" - extra={[ - , - , - , - , - - , - ]} - style={{ - padding: '20px 0px', - }} - > - - - - - - - - - - {lead.address} - {lead.email} - {lead.phone} - - - -
-

- {translate('product')} -

- -
-

- {translate('PRICE')} -

- -
-

- {translate('QUANTITY')} -

- -
-

- {translate('TOTAL')} -

- - - - {itemslist.map((item) => ( - - ))} -
- -
-

{translate('Sub Total')} :

- - -
-

- {moneyFormatter({ amount: currentErp.subTotal, currency_code: currentErp.currency })} -

- -
-

Tax Total ({currentErp.taxRate} %) :

- -
-

- {moneyFormatter({ amount: currentErp.taxTotal, currency_code: currentErp.currency })} -

- -
-

{translate('Total')} :

- -
-

- {moneyFormatter({ amount: currentErp.total, currency_code: currentErp.currency })} -

- - - - - ); -} diff --git a/frontend/src/modules/OfferModule/ReadOfferModule/index.jsx b/frontend/src/modules/OfferModule/ReadOfferModule/index.jsx deleted file mode 100644 index 8604a89b0..000000000 --- a/frontend/src/modules/OfferModule/ReadOfferModule/index.jsx +++ /dev/null @@ -1,40 +0,0 @@ -import NotFound from '@/components/NotFound'; -import { ErpLayout } from '@/layout'; -import ReadOfferItem from './ReadOfferItem'; - -import PageLoader from '@/components/PageLoader'; -import { erp } from '@/redux/erp/actions'; -import useLanguage from '@/locale/useLanguage'; -import { selectReadItem } from '@/redux/erp/selectors'; -import { useLayoutEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { useParams, useNavigate } from 'react-router-dom'; - -export default function ReadOfferModule({ config }) { - const dispatch = useDispatch(); - const { id } = useParams(); - const navigate = useNavigate(); - - useLayoutEffect(() => { - dispatch(erp.read({ entity: config.entity, id })); - }, [id]); - - const { result: currentResult, isSuccess, isLoading = true } = useSelector(selectReadItem); - - if (isLoading) { - return ( - - - - ); - } else - return ( - - {isSuccess ? ( - - ) : ( - - )} - - ); -} diff --git a/frontend/src/modules/OfferModule/UpdateOfferModule/index.jsx b/frontend/src/modules/OfferModule/UpdateOfferModule/index.jsx deleted file mode 100644 index ff48c87c9..000000000 --- a/frontend/src/modules/OfferModule/UpdateOfferModule/index.jsx +++ /dev/null @@ -1,50 +0,0 @@ -import NotFound from '@/components/NotFound'; - -import { ErpLayout } from '@/layout'; -import UpdateItem from '@/modules/ErpPanelModule/UpdateItem'; -import OfferForm from '@/modules/OfferModule/Forms/OfferForm'; - -import PageLoader from '@/components/PageLoader'; - -import { erp } from '@/redux/erp/actions'; -import useLanguage from '@/locale/useLanguage'; -import { selectReadItem } from '@/redux/erp/selectors'; -import { useLayoutEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { useParams, useNavigate } from 'react-router-dom'; - -export default function UpdateOfferModule({ config }) { - const dispatch = useDispatch(); - - const { id } = useParams(); - const navigate = useNavigate(); - - useLayoutEffect(() => { - dispatch(erp.read({ entity: config.entity, id })); - }, [id]); - - const { result: currentResult, isSuccess, isLoading = true } = useSelector(selectReadItem); - - useLayoutEffect(() => { - if (currentResult) { - dispatch(erp.currentAction({ actionType: 'update', data: currentResult })); - } - }, [currentResult]); - - if (isLoading) { - return ( - - - - ); - } else - return ( - - {isSuccess ? ( - - ) : ( - - )} - - ); -} diff --git a/frontend/src/modules/OrderModule/CreateOrderModule/index.jsx b/frontend/src/modules/OrderModule/CreateOrderModule/index.jsx deleted file mode 100644 index 369298dbc..000000000 --- a/frontend/src/modules/OrderModule/CreateOrderModule/index.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import { ErpLayout } from '@/layout'; -import CreateItem from '@/modules/AdvancedCrudModule/CreateItem'; -import OrderForm from '@/modules/OrderModule/Forms/OrderForm'; - -export default function CreateInvoiceModule({ config }) { - return ( - - - - ); -} diff --git a/frontend/src/modules/OrderModule/Forms/InvoiceForm.jsx b/frontend/src/modules/OrderModule/Forms/InvoiceForm.jsx deleted file mode 100644 index 4b4cc663f..000000000 --- a/frontend/src/modules/OrderModule/Forms/InvoiceForm.jsx +++ /dev/null @@ -1,284 +0,0 @@ -import { useState, useEffect, useRef } from 'react'; -import dayjs from 'dayjs'; -import { Form, Input, InputNumber, Button, Select, Divider, Row, Col } from 'antd'; - -import { PlusOutlined } from '@ant-design/icons'; - -import { DatePicker } from 'antd'; - -import AutoCompleteAsync from '@/components/AutoCompleteAsync'; - -import ItemRow from '@/modules/ErpPanelModule/ItemRow'; - -import MoneyInputFormItem from '@/components/MoneyInputFormItem'; -import { selectFinanceSettings } from '@/redux/settings/selectors'; -import { useDate } from '@/settings'; -import useLanguage from '@/locale/useLanguage'; - -import calculate from '@/utils/calculate'; -import { useSelector } from 'react-redux'; -import SelectAsync from '@/components/SelectAsync'; - -export default function InvoiceForm({ subTotal = 0, current = null }) { - const { last_invoice_number } = useSelector(selectFinanceSettings); - - if (!last_invoice_number) { - return <>; - } - - return ; -} - -function LoadInvoiceForm({ subTotal = 0, current = null }) { - const translate = useLanguage(); - const { dateFormat } = useDate(); - const { last_invoice_number } = useSelector(selectFinanceSettings); - const [total, setTotal] = useState(0); - const [taxRate, setTaxRate] = useState(0); - const [taxTotal, setTaxTotal] = useState(0); - const [currentYear, setCurrentYear] = useState(() => new Date().getFullYear()); - const [lastNumber, setLastNumber] = useState(() => last_invoice_number + 1); - - const handelTaxChange = (value) => { - setTaxRate(value / 100); - }; - - useEffect(() => { - if (current) { - const { taxRate = 0, year, number } = current; - setTaxRate(taxRate / 100); - setCurrentYear(year); - setLastNumber(number); - } - }, [current]); - useEffect(() => { - const currentTotal = calculate.add(calculate.multiply(subTotal, taxRate), subTotal); - setTaxTotal(Number.parseFloat(calculate.multiply(subTotal, taxRate))); - setTotal(Number.parseFloat(currentTotal)); - }, [subTotal, taxRate]); - - const addField = useRef(false); - - useEffect(() => { - addField.current.click(); - }, []); - - return ( - <> - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

{translate('Item')}

- -
-

{translate('Description')}

- -
-

{translate('Quantity')}

{' '} - -
-

{translate('Price')}

- -
-

{translate('Total')}

- - - - {(fields, { add, remove }) => ( - <> - {fields.map((field) => ( - - ))} - - - - - )} - - -
- -
- - - - - -

- {translate('Sub Total')} : -

- -
- - - - - - - - - - - - - - - -

- {translate('Total')} : -

- -
- - - - - - ); -} diff --git a/frontend/src/modules/OrderModule/OrderDataTableModule/index.jsx b/frontend/src/modules/OrderModule/OrderDataTableModule/index.jsx deleted file mode 100644 index c0f55e2c8..000000000 --- a/frontend/src/modules/OrderModule/OrderDataTableModule/index.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import { ErpLayout } from '@/layout'; -import AdvancedCrudModule from '@/modules/AdvancedCrudModule'; -import useLanguage from '@/locale/useLanguage'; -import { CreditCardOutlined } from '@ant-design/icons'; - -export default function InvoiceDataTableModule({ config }) { - const translate = useLanguage(); - return ( - - , - // }, - // ]} - > - - ); -} diff --git a/frontend/src/modules/OrderModule/ReadOrderModule/index.jsx b/frontend/src/modules/OrderModule/ReadOrderModule/index.jsx deleted file mode 100644 index a578d9bfd..000000000 --- a/frontend/src/modules/OrderModule/ReadOrderModule/index.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import NotFound from '@/components/NotFound'; -import { ErpLayout } from '@/layout'; -import ReadItem from '@/modules/AdvancedCrudModule/ReadItem'; - -import PageLoader from '@/components/PageLoader'; -import { adavancedCrud } from '@/redux/adavancedCrud/actions'; -import { selectReadItem } from '@/redux/adavancedCrud/selectors'; -import { useLayoutEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; - -import { useParams } from 'react-router-dom'; - -export default function ReadInvoiceModule({ config }) { - const dispatch = useDispatch(); - const { id } = useParams(); - - useLayoutEffect(() => { - dispatch(adavancedCrud.read({ entity: config.entity, id })); - }, [id]); - - const { result: currentResult, isSuccess, isLoading = true } = useSelector(selectReadItem); - - if (isLoading) { - return ( - - - - ); - } else - return ( - - {isSuccess ? ( - - ) : ( - - )} - - ); -} diff --git a/frontend/src/modules/OrderModule/RecordPaymentModule/components/Payment.jsx b/frontend/src/modules/OrderModule/RecordPaymentModule/components/Payment.jsx deleted file mode 100644 index f3789e6ab..000000000 --- a/frontend/src/modules/OrderModule/RecordPaymentModule/components/Payment.jsx +++ /dev/null @@ -1,140 +0,0 @@ -import { useState, useEffect } from 'react'; - -import { Button, Row, Col, Descriptions, Tag, Divider } from 'antd'; -import { PageHeader } from '@ant-design/pro-layout'; -import { FileTextOutlined, CloseCircleOutlined } from '@ant-design/icons'; - -import { generate as uniqueId } from 'shortid'; - -import { useMoney } from '@/settings'; - -import RecordPayment from './RecordPayment'; -import useLanguage from '@/locale/useLanguage'; -import { tagColor } from '@/utils/statusTagColor'; -import { useNavigate } from 'react-router-dom'; - -export default function Payment({ config, currentItem }) { - const translate = useLanguage(); - const { entity, ENTITY_NAME } = config; - - const money = useMoney(); - const navigate = useNavigate(); - - const [itemslist, setItemsList] = useState([]); - const [currentErp, setCurrentErp] = useState(currentItem); - - const [client, setClient] = useState({}); - useEffect(() => { - if (currentErp?.client) { - setClient(currentErp.client[currentErp.client.type]); - } - }, [currentErp]); - - useEffect(() => { - const controller = new AbortController(); - if (currentItem) { - const { items } = currentItem; - - setItemsList(items); - setCurrentErp(currentItem); - } - return () => controller.abort(); - }, [currentItem]); - - useEffect(() => { - console.info('itemslist', itemslist); - }, [itemslist]); - - return ( - <> - - - navigate(`/${entity.toLowerCase()}`)} - title={`Record Payment for ${ENTITY_NAME} # ${currentErp.number}/${ - currentErp.year || '' - }`} - ghost={false} - tags={ - - {currentErp.paymentStatus && translate(currentErp.paymentStatus)} - - } - // subTitle="This is cuurent erp page" - extra={[ - , - , - ]} - style={{ - padding: '20px 0px', - }} - > - - - - - -
- - {client.email} - {client.phone} - - - - {currentErp.paymentStatus && translate(currentErp.paymentStatus)} - - - - {money.amountFormatter({ amount: currentErp.subTotal })} - - - {money.amountFormatter({ amount: currentErp.total })} - - - {money.amountFormatter({ amount: currentErp.discount })} - - - {money.amountFormatter({ amount: currentErp.credit })} - - - - -
- - - - - ); -} diff --git a/frontend/src/modules/OrderModule/RecordPaymentModule/components/RecordPayment.jsx b/frontend/src/modules/OrderModule/RecordPaymentModule/components/RecordPayment.jsx deleted file mode 100644 index 3fa37582c..000000000 --- a/frontend/src/modules/OrderModule/RecordPaymentModule/components/RecordPayment.jsx +++ /dev/null @@ -1,73 +0,0 @@ -import { useState, useEffect } from 'react'; -import { Form, Button } from 'antd'; - -import { useSelector, useDispatch } from 'react-redux'; -import { erp } from '@/redux/erp/actions'; -import { selectRecordPaymentItem } from '@/redux/erp/selectors'; -import useLanguage from '@/locale/useLanguage'; - -import Loading from '@/components/Loading'; - -import PaymentForm from '@/forms/PaymentForm'; -import { useNavigate } from 'react-router-dom'; -import calculate from '@/utils/calculate'; - -export default function RecordPayment({ config }) { - const navigate = useNavigate(); - const translate = useLanguage(); - let { entity } = config; - - const dispatch = useDispatch(); - - const { isLoading, isSuccess, current: currentInvoice } = useSelector(selectRecordPaymentItem); - - const [form] = Form.useForm(); - - const [maxAmount, setMaxAmount] = useState(0); - useEffect(() => { - if (currentInvoice) { - const { credit, total, discount } = currentInvoice; - setMaxAmount(calculate.sub(calculate.sub(total, discount), credit)); - } - }, [currentInvoice]); - useEffect(() => { - if (isSuccess) { - form.resetFields(); - dispatch(erp.resetAction({ actionType: 'recordPayment' })); - dispatch(erp.list({ entity })); - navigate(`/${entity}/`); - } - }, [isSuccess]); - - const onSubmit = (fieldsValue) => { - if (currentInvoice) { - const { _id: invoice } = currentInvoice; - const client = currentInvoice.client && currentInvoice.client._id; - fieldsValue = { - ...fieldsValue, - invoice, - client, - }; - } - - dispatch( - erp.recordPayment({ - entity: 'payment', - jsonData: fieldsValue, - }) - ); - }; - - return ( - -
- - - - - -
- ); -} diff --git a/frontend/src/modules/OrderModule/RecordPaymentModule/index.jsx b/frontend/src/modules/OrderModule/RecordPaymentModule/index.jsx deleted file mode 100644 index 06332c03e..000000000 --- a/frontend/src/modules/OrderModule/RecordPaymentModule/index.jsx +++ /dev/null @@ -1,37 +0,0 @@ -import { ErpLayout } from '@/layout'; - -import PageLoader from '@/components/PageLoader'; -import { erp } from '@/redux/erp/actions'; -import { selectItemById, selectCurrentItem, selectRecordPaymentItem } from '@/redux/erp/selectors'; -import { useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { useParams } from 'react-router-dom'; -import Payment from './components/Payment'; - -export default function RecordPaymentModule({ config }) { - const dispatch = useDispatch(); - const { id } = useParams(); - - let item = useSelector(selectItemById(id)); - - useEffect(() => { - if (item) { - dispatch(erp.currentItem({ data: item })); - } else { - dispatch(erp.read({ entity: config.entity, id })); - } - }, [item, id]); - - const { result: currentResult } = useSelector(selectCurrentItem); - item = currentResult; - - useEffect(() => { - dispatch(erp.currentAction({ actionType: 'recordPayment', data: item })); - }, [item]); - - return ( - - {item ? : } - - ); -} diff --git a/frontend/src/modules/OrderModule/UpdateInvoiceModule/index.jsx b/frontend/src/modules/OrderModule/UpdateInvoiceModule/index.jsx deleted file mode 100644 index c7f1be758..000000000 --- a/frontend/src/modules/OrderModule/UpdateInvoiceModule/index.jsx +++ /dev/null @@ -1,50 +0,0 @@ -import NotFound from '@/components/NotFound'; - -import { ErpLayout } from '@/layout'; -import UpdateItem from '@/modules/ErpPanelModule/UpdateItem'; -import InvoiceForm from '@/modules/InvoiceModule/Forms/InvoiceForm'; - -import PageLoader from '@/components/PageLoader'; - -import { erp } from '@/redux/erp/actions'; - -import { selectReadItem } from '@/redux/erp/selectors'; -import { useLayoutEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { useParams } from 'react-router-dom'; - -export default function UpdateInvoiceModule({ config }) { - const dispatch = useDispatch(); - - const { id } = useParams(); - - useLayoutEffect(() => { - dispatch(erp.read({ entity: config.entity, id })); - }, [id]); - - const { result: currentResult, isSuccess, isLoading = true } = useSelector(selectReadItem); - - useLayoutEffect(() => { - if (currentResult) { - const data = { ...currentResult }; - dispatch(erp.currentAction({ actionType: 'update', data })); - } - }, [currentResult]); - - if (isLoading) { - return ( - - - - ); - } else - return ( - - {isSuccess ? ( - - ) : ( - - )} - - ); -} diff --git a/frontend/src/modules/PaymentModule/ReadPaymentModule/components/ReadItem.jsx b/frontend/src/modules/PaymentModule/ReadPaymentModule/components/ReadItem.jsx index 0e1e43b29..0af86f18f 100644 --- a/frontend/src/modules/PaymentModule/ReadPaymentModule/components/ReadItem.jsx +++ b/frontend/src/modules/PaymentModule/ReadPaymentModule/components/ReadItem.jsx @@ -20,7 +20,7 @@ import { selectCurrentItem } from '@/redux/erp/selectors'; import { DOWNLOAD_BASE_URL } from '@/config/serverApiConfig'; import { useMoney } from '@/settings'; -import { tagColor } from '@/utils/statusTagColor'; + import useMail from '@/hooks/useMail'; import { useNavigate } from 'react-router-dom'; @@ -66,7 +66,7 @@ export default function ReadItem({ config, selectedItem }) { useEffect(() => { if (currentErp?.client) { - setClient(currentErp.client[currentErp.client.type]); + setClient(currentErp.client); } }, [currentErp]); @@ -78,9 +78,7 @@ export default function ReadItem({ config, selectedItem }) { }} title={`${ENTITY_NAME} # ${currentErp.number}/${currentErp.year || ''}`} ghost={false} - tags={ - {currentErp.paymentStatus} - } + tags={{currentErp.paymentStatus}} extra={[
+ - + + - - - +
{formItems.map((item) => { return ( +
{formItems.map((item) => { return ( - - - - - - +
+
+
{title}{description} diff --git a/frontend/src/modules/SettingModule/components/UpdateSettingModule.jsx b/frontend/src/modules/SettingModule/components/UpdateSettingModule.jsx index ea19a003b..a8bbf1d72 100644 --- a/frontend/src/modules/SettingModule/components/UpdateSettingModule.jsx +++ b/frontend/src/modules/SettingModule/components/UpdateSettingModule.jsx @@ -3,8 +3,6 @@ import { Divider } from 'antd'; import { PageHeader } from '@ant-design/pro-layout'; import UpdateSettingForm from './UpdateSettingForm'; -import { useSelector } from 'react-redux'; -import { selectLangDirection } from '@/redux/translate/selectors'; export default function UpdateSettingModule({ config, @@ -12,14 +10,10 @@ export default function UpdateSettingModule({ withUpload = false, uploadSettingKey = null, }) { - - const langDirection=useSelector(selectLangDirection) - return ( <> }> @@ -28,7 +22,6 @@ export default function UpdateSettingModule({ // ]} style={{ padding: '20px 0px', - direction:langDirection }} > diff --git a/frontend/src/pages/AdvancedSettings/index.jsx b/frontend/src/pages/AdvancedSettings/index.jsx deleted file mode 100644 index b0e73d194..000000000 --- a/frontend/src/pages/AdvancedSettings/index.jsx +++ /dev/null @@ -1,102 +0,0 @@ -import useLanguage from '@/locale/useLanguage'; - -import { Switch } from 'antd'; -import { CloseOutlined, CheckOutlined } from '@ant-design/icons'; -import CrudModule from '@/modules/CrudModule/CrudModule'; -import AdvancedSettingsForm from '@/forms/AdvancedSettingsForm'; - -export default function AdvancedSettings() { - const translate = useLanguage(); - const entity = 'setting'; - const searchConfig = { - displayLabels: ['name'], - searchFields: 'name', - outputValue: '_id', - }; - - const deleteModalLabels = ['name']; - - const readColumns = [ - { - title: translate('Setting'), - dataIndex: 'settingKey', - }, - { - title: translate('Value'), - dataIndex: 'settingValue', - }, - { - title: translate('enabled'), - dataIndex: 'enabled', - }, - { - title: translate('Core Setting'), - dataIndex: 'isCoreSetting', - }, - ]; - const dataTableColumns = [ - { - title: translate('Setting'), - dataIndex: 'settingKey', - }, - { - title: translate('Value'), - dataIndex: 'settingValue', - render: (text) => { - return `${text}`; - }, - }, - { - title: translate('enabled'), - dataIndex: 'enabled', - key: 'enabled', - onCell: () => { - return { - props: { - style: { - width: '60px', - }, - }, - }; - }, - render: (_, record) => { - return ( - } - unCheckedChildren={} - /> - ); - }, - }, - ]; - - const Labels = { - PANEL_TITLE: translate('settings'), - DATATABLE_TITLE: translate('settings_list'), - ADD_NEW_ENTITY: translate('add_new_settings'), - ENTITY_NAME: translate('setting'), - - RECORD_ENTITY: translate('record_payment'), - }; - - const configPage = { - entity, - ...Labels, - }; - const config = { - ...configPage, - readColumns, - dataTableColumns, - searchConfig, - deleteModalLabels, - }; - return ( - } - updateForm={} - config={config} - /> - ); -} diff --git a/frontend/src/pages/Company/config.js b/frontend/src/pages/Company/config.js deleted file mode 100644 index ca28b22cc..000000000 --- a/frontend/src/pages/Company/config.js +++ /dev/null @@ -1,173 +0,0 @@ -export const fields = { - name: { - type: 'string', - required: true, - }, - mainContact: { - type: 'search', - renderAsTag: true, - label: 'Contact', - entity: 'people', - redirectLabel: 'Add New Person', - withRedirect: true, - urlToRedirect: '/people', - displayLabels: ['firstname', 'lastname'], - searchFields: 'firstname,lastname', - dataIndex: ['mainContact', 'firstname'], - }, - country: { - type: 'country', - }, - phone: { - type: 'phone', - }, - email: { - type: 'email', - required: true, - }, - website: { - type: 'url', - }, - // legalName: { - // type: 'string', - // required: true, - // }, - // hasParentCompany: { - // type: 'boolean', - // default: false, - // }, - // parentCompany: { type: 'search', entity: 'company' }, - - // people: [{ type: 'search', entity: 'people', mutliple: true }], - - // icon: { - // type: 'image', - // }, - // logo: { - // type: 'image', - // }, - // imageHeader: 'image', - // bankName: { - // type: 'string', - // }, - // bankIban: { - // type: 'string', - // }, - // bankSwift: { - // type: 'string', - // }, - // bankNumber: { - // type: 'string', - // }, - // bankRouting: { - // type: 'string', - // }, - // bankCountry: { - // type: 'string', - // }, - // companyRegNumber: { - // type: 'string', - // }, - // companyTaxNumber: { - // type: 'string', - // }, - // companyTaxId: { - // type: 'string', - // }, - // companyRegId: { - // type: 'string', - // }, - // securitySocialNbr: 'string', - // customField: [ - // { - // fieldName: { - // type: 'string', - - // - // }, - // fieldType: { - // type: 'string', - - // - // default: 'string', - // }, - // fieldValue: {}, - // }, - // ], - // location: { - // latitude: Number, - // longitude: Number, - // }, - // address: { - // type: 'string', - // }, - // city: { - // type: 'string', - // }, - // State: { - // type: 'string', - // }, - // postalCode: { - // type: Number, - // }, - - // otherPhone: [ - // { - // type: 'string', - // }, - // ], - // fax: { - // type: 'string', - // }, - - // otherEmail: [ - // { - // type: 'string', - // }, - // ], - - // socialMedia: { - // facebook: 'string', - // instagram: 'string', - // twitter: 'string', - // linkedin: 'string', - // tiktok: 'string', - // youtube: 'string', - // snapchat: 'string', - // }, - // images: [ - // { - // id: 'string', - // name: 'string', - // path: 'string', - // description: 'string', - // isPublic: { - // type: 'boolean', - // default: false, - // }, - // }, - // ], - // files: [ - // { - // id: 'string', - // name: 'string', - // path: 'string', - // description: 'string', - // isPublic: { - // type: 'boolean', - // default: false, - // }, - // }, - // ], - // category: 'string', - // approved: { - // type: 'boolean', - // default: true, - // }, - // verified: { - // type: 'boolean', - // }, - // notes: { - // type: 'string', - // }, -}; diff --git a/frontend/src/pages/Company/index.jsx b/frontend/src/pages/Company/index.jsx deleted file mode 100644 index e32e3552e..000000000 --- a/frontend/src/pages/Company/index.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import CrudModule from '@/modules/CrudModule/CrudModule'; -import DynamicForm from '@/forms/DynamicForm'; -import { fields } from './config'; - -import useLanguage from '@/locale/useLanguage'; - -export default function Company() { - const translate = useLanguage(); - const entity = 'company'; - const searchConfig = { - displayLabels: ['name'], - searchFields: 'name,phone,eamil', - }; - const deleteModalLabels = ['name']; - - const Labels = { - PANEL_TITLE: translate('company'), - DATATABLE_TITLE: translate('company_list'), - ADD_NEW_ENTITY: translate('add_new_company'), - ENTITY_NAME: translate('company'), - }; - const configPage = { - entity, - ...Labels, - }; - const config = { - ...configPage, - fields, - searchConfig, - deleteModalLabels, - }; - return ( - } - updateForm={} - config={config} - /> - ); -} diff --git a/frontend/src/pages/Customer/config.js b/frontend/src/pages/Customer/config.js index 91e196c78..0e75a84c4 100644 --- a/frontend/src/pages/Customer/config.js +++ b/frontend/src/pages/Customer/config.js @@ -1,55 +1,18 @@ export const fields = { - type: { - type: 'selectWithFeedback', - renderAsTag: true, - options: [ - { value: 'people', label: 'people', color: 'magenta' }, - { value: 'company', label: 'company', color: 'blue' }, - ], - required: true, - hasFeedback: true, - }, name: { type: 'string', - disableForForm: true, }, country: { type: 'country', // color: 'red', - disableForForm: true, + }, + address: { + type: 'string', }, phone: { type: 'phone', - disableForForm: true, }, email: { type: 'email', - disableForForm: true, - }, - people: { - type: 'search', - label: 'people', - entity: 'people', - redirectLabel: 'Add New Person', - withRedirect: true, - urlToRedirect: '/people', - displayLabels: ['firstname', 'lastname'], - searchFields: 'firstname,lastname', - dataIndex: ['people', 'firstname'], - disableForTable: true, - feedback: 'people', - }, - company: { - type: 'search', - label: 'company', - entity: 'company', - redirectLabel: 'Add New Company', - withRedirect: true, - urlToRedirect: '/company', - displayLabels: ['name'], - searchFields: 'name', - dataIndex: ['company', 'name'], - disableForTable: true, - feedback: 'company', }, }; diff --git a/frontend/src/pages/Email/EmailRead.jsx b/frontend/src/pages/Email/EmailRead.jsx deleted file mode 100644 index e714a597a..000000000 --- a/frontend/src/pages/Email/EmailRead.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import useLanguage from '@/locale/useLanguage'; -import ReadEmailModule from '@/modules/EmailModule/ReadEmailModule'; - -export default function EmailRead() { - const entity = 'email'; - const translate = useLanguage(); - - const Labels = { - PANEL_TITLE: translate('email_template'), - DATATABLE_TITLE: translate('email_template_list'), - ADD_NEW_ENTITY: translate('add_new_email_template'), - ENTITY_NAME: translate('email_template'), - }; - - const configPage = { - entity, - create: false, - ...Labels, - }; - return ; -} diff --git a/frontend/src/pages/Email/EmailUpdate.jsx b/frontend/src/pages/Email/EmailUpdate.jsx deleted file mode 100644 index 9a1961c46..000000000 --- a/frontend/src/pages/Email/EmailUpdate.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import useLanguage from '@/locale/useLanguage'; -import UpdateEmailModule from '@/modules/EmailModule/UpdateEmailModule'; - -export default function EmailUpdate() { - const entity = 'email'; - const translate = useLanguage(); - - const Labels = { - PANEL_TITLE: translate('email_template'), - DATATABLE_TITLE: translate('email_template_list'), - ADD_NEW_ENTITY: translate('add_new_email_template'), - ENTITY_NAME: translate('email_template'), - }; - - const configPage = { - entity, - create: false, - ...Labels, - }; - - return ; -} diff --git a/frontend/src/pages/Email/index.jsx b/frontend/src/pages/Email/index.jsx deleted file mode 100644 index cb7b3d525..000000000 --- a/frontend/src/pages/Email/index.jsx +++ /dev/null @@ -1,63 +0,0 @@ -import React from 'react'; -import useLanguage from '@/locale/useLanguage'; -import EmailDataTableModule from '@/modules/EmailModule/EmailDataTableModule'; - -export default function Email() { - const translate = useLanguage(); - const entity = 'email'; - const searchConfig = { - displayLabels: ['name'], - searchFields: 'name', - outputValue: '_id', - }; - - const deleteModalLabels = ['name']; - - const readColumns = [ - { - title: translate('Template'), - dataIndex: 'emailName', - }, - { - title: translate('Subject'), - dataIndex: 'emailSubject', - }, - { - title: translate('email content'), - dataIndex: 'emailBody', - }, - ]; - const dataTableColumns = [ - { - title: translate('Template'), - dataIndex: 'emailName', - key: 'emailName', - }, - { - title: translate('Subject'), - dataIndex: 'emailSubject', - key: 'emailSubject', - }, - ]; - - const Labels = { - PANEL_TITLE: translate('email_template'), - DATATABLE_TITLE: translate('email_template_list'), - ADD_NEW_ENTITY: translate('add_new_email_template'), - ENTITY_NAME: translate('email_template'), - }; - - const configPage = { - entity, - create: false, - ...Labels, - }; - const config = { - ...configPage, - readColumns, - dataTableColumns, - searchConfig, - deleteModalLabels, - }; - return ; -} diff --git a/frontend/src/pages/Employee/index.jsx b/frontend/src/pages/Employee/index.jsx deleted file mode 100644 index 0e63a9fa7..000000000 --- a/frontend/src/pages/Employee/index.jsx +++ /dev/null @@ -1,125 +0,0 @@ -import useLanguage from '@/locale/useLanguage'; -import CrudModule from '@/modules/CrudModule/CrudModule'; -import EmployeeForm from '@/forms/EmployeeForm'; -import dayjs from 'dayjs'; -import { useDate } from '@/settings'; -export default function Employee() { - const translate = useLanguage(); - const { dateFormat } = useDate(); - const entity = 'employee'; - const searchConfig = { - displayLabels: ['name', 'surname'], - searchFields: 'name,surname,birthday', - outputValue: '_id', - }; - - const deleteModalLabels = ['name', 'surname']; - - const dataTableColumns = [ - { - title: translate('first name'), - dataIndex: 'name', - }, - { - title: translate('last name'), - dataIndex: 'surname', - }, - { - title: translate('Birthday'), - dataIndex: 'birthday', - render: (date) => { - return dayjs(date).format(dateFormat); - }, - }, - { - title: translate('Department'), - dataIndex: 'department', - }, - { - title: translate('Position'), - dataIndex: 'position', - }, - { - title: translate('Phone'), - dataIndex: 'phone', - }, - { - title: translate('Email'), - dataIndex: 'email', - }, - ]; - - const readColumns = [ - { - title: translate('first name'), - dataIndex: 'name', - }, - { - title: translate('last name'), - dataIndex: 'surname', - }, - { - title: translate('Birthday'), - dataIndex: 'birthday', - isDate: true, - }, - { - title: translate('birthplace'), - dataIndex: 'birthplace', - }, - { - title: translate('gender'), - dataIndex: 'gender', - }, - { - title: translate('Department'), - dataIndex: 'department', - }, - { - title: translate('Position'), - dataIndex: 'position', - }, - { - title: translate('address'), - dataIndex: 'address', - }, - { - title: translate('state'), - dataIndex: 'state', - }, - { - title: translate('Phone'), - dataIndex: 'phone', - }, - { - title: translate('Email'), - dataIndex: 'email', - }, - ]; - - const Labels = { - PANEL_TITLE: translate('employee'), - DATATABLE_TITLE: translate('employee_list'), - ADD_NEW_ENTITY: translate('add_new_employee'), - ENTITY_NAME: translate('employee'), - }; - - const configPage = { - entity, - ...Labels, - }; - const config = { - ...configPage, - readColumns, - dataTableColumns, - searchConfig, - deleteModalLabels, - }; - return ( - } - updateForm={} - config={config} - /> - ); -} diff --git a/frontend/src/pages/Expense/config.js b/frontend/src/pages/Expense/config.js deleted file mode 100644 index 8d347f183..000000000 --- a/frontend/src/pages/Expense/config.js +++ /dev/null @@ -1,25 +0,0 @@ -export const fields = { - name: { - type: 'string', - required: true, - }, - expenseCategory: { - type: 'async', - label: 'Expense Category', - displayLabels: ['expenseCategory', 'name'], - dataIndex: ['expenseCategory', 'name'], - entity: 'expensecategory', - required: true, - }, - - total: { - type: 'currency', - required: true, - }, - description: { - type: 'textarea', - }, - ref: { - type: 'string', - }, -}; diff --git a/frontend/src/pages/Expense/index.jsx b/frontend/src/pages/Expense/index.jsx deleted file mode 100644 index 3def15334..000000000 --- a/frontend/src/pages/Expense/index.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import CrudModule from '@/modules/CrudModule/CrudModule'; -import DynamicForm from '@/forms/DynamicForm'; -import { fields } from './config'; - -import useLanguage from '@/locale/useLanguage'; - -export default function Expense() { - const translate = useLanguage(); - const entity = 'expense'; - const searchConfig = { - displayLabels: ['name'], - searchFields: 'name', - }; - const deleteModalLabels = ['name']; - - const Labels = { - PANEL_TITLE: translate('Expense'), - DATATABLE_TITLE: translate('Expense_list'), - ADD_NEW_ENTITY: translate('add_new_Expense'), - ENTITY_NAME: translate('Expense'), - }; - const configPage = { - entity, - ...Labels, - }; - const config = { - ...configPage, - fields, - searchConfig, - deleteModalLabels, - }; - return ( - } - updateForm={} - config={config} - /> - ); -} diff --git a/frontend/src/pages/ExpenseCategory/config.js b/frontend/src/pages/ExpenseCategory/config.js deleted file mode 100644 index 597a129d5..000000000 --- a/frontend/src/pages/ExpenseCategory/config.js +++ /dev/null @@ -1,21 +0,0 @@ -import color from '@/utils/color'; - -export const fields = { - name: { - type: 'stringWithColor', - required: true, - }, - description: { - type: 'textarea', - required: true, - }, - color: { - type: 'color', - options: [...color], - required: true, - }, - enabled: { - type: 'boolean', - required: true, - }, -}; diff --git a/frontend/src/pages/ExpenseCategory/index.jsx b/frontend/src/pages/ExpenseCategory/index.jsx deleted file mode 100644 index 5338f540a..000000000 --- a/frontend/src/pages/ExpenseCategory/index.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import CrudModule from '@/modules/CrudModule/CrudModule'; -import DynamicForm from '@/forms/DynamicForm'; -import { fields } from './config'; - -import useLanguage from '@/locale/useLanguage'; - -export default function ExpenseCategory() { - const translate = useLanguage(); - const entity = 'expensecategory'; - const searchConfig = { - displayLabels: ['name'], - searchFields: 'name', - }; - const deleteModalLabels = ['name']; - - const Labels = { - PANEL_TITLE: translate('Expense_Category'), - DATATABLE_TITLE: translate('Expense_Category_list'), - ADD_NEW_ENTITY: translate('add_new_Expense_Category'), - ENTITY_NAME: translate('Expense_Category'), - }; - const configPage = { - entity, - ...Labels, - }; - const config = { - ...configPage, - fields, - searchConfig, - deleteModalLabels, - }; - return ( - } - updateForm={} - config={config} - /> - ); -} diff --git a/frontend/src/pages/Inventory/index.jsx b/frontend/src/pages/Inventory/index.jsx deleted file mode 100644 index 398e0af13..000000000 --- a/frontend/src/pages/Inventory/index.jsx +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; -import CrudModule from '@/modules/CrudModule/CrudModule'; -import InventoryForm from '@/forms/InventoryForm'; // Retaining InventoryForm -import useLanguage from '@/locale/useLanguage'; - -export default function Inventory() { - const translate = useLanguage(); - const entity = 'inventory'; // Updated entity name - const searchConfig = { - displayLabels: ['product'], // Adjusted to search by product - searchFields: 'product', - outputValue: '_id', - }; - const deleteModalLabels = ['product', 'quantity', 'unitPrice']; // Adjusted to display inventory item labels - - const readColumns = [ - { - title: translate('Product'), - dataIndex: 'product', - }, - { - title: translate('Quantity'), - dataIndex: 'quantity', - }, - { - title: translate('Unit Price'), - dataIndex: 'unitPrice', - }, - ]; - - const dataTableColumns = [ - { - title: translate('Product'), - dataIndex: ['product'], - }, - { - title: translate('Quantity'), - dataIndex: ['quantity'], - }, - { - title: translate('Unit Price'), - dataIndex: ['unitPrice'], - }, - ]; - - const Labels = { - PANEL_TITLE: translate('product'), - DATATABLE_TITLE: translate('product_list'), - ADD_NEW_ENTITY: translate('add_new_product'), - ENTITY_NAME: translate('product'), - }; - - const configPage = { - entity, - ...Labels, - }; - const config = { - ...configPage, - readColumns, - dataTableColumns, - searchConfig, - deleteModalLabels, - }; - return ( - } // Retaining InventoryForm - updateForm={} // Retaining InventoryForm - config={config} - /> - ); -} diff --git a/frontend/src/pages/Invoice/index.jsx b/frontend/src/pages/Invoice/index.jsx index f98bde7c9..94e89282a 100644 --- a/frontend/src/pages/Invoice/index.jsx +++ b/frontend/src/pages/Invoice/index.jsx @@ -74,34 +74,10 @@ export default function Invoice() { { title: translate('Status'), dataIndex: 'status', - render: (status) => { - let tagStatus = tagColor(status); - - return ( - - {/* {tagStatus.icon + ' '} */} - {status && translate(tagStatus.label)} - - ); - }, }, { title: translate('Payment'), dataIndex: 'paymentStatus', - render: (paymentStatus) => { - let tagStatus = tagColor(paymentStatus); - - return ( - - {/* {tagStatus.icon + ' '} */} - {paymentStatus && translate(paymentStatus)} - - ); - }, - }, - { - title: translate('Created By'), - dataIndex: ['createdBy', 'name'], }, ]; diff --git a/frontend/src/pages/Lead/config.js b/frontend/src/pages/Lead/config.js deleted file mode 100644 index 058098ec0..000000000 --- a/frontend/src/pages/Lead/config.js +++ /dev/null @@ -1,90 +0,0 @@ -import { selectColor } from '@/utils/color'; -export const fields = { - type: { - type: 'selectWithFeedback', - renderAsTag: true, - options: [ - { value: 'people', label: 'people', color: 'magenta' }, - { value: 'company', label: 'company', color: 'blue' }, - ], - required: true, - hasFeedback: true, - }, - name: { - type: 'string', - disableForForm: true, - }, - status: { - type: 'selectWithTranslation', - renderAsTag: true, - options: [ - { value: 'draft', label: 'draft' }, - { value: 'new', label: 'new', color: 'blue' }, - { value: 'in negociation', label: 'in negociation', color: 'purple' }, - { value: 'won', label: 'won', color: 'green' }, - { value: 'loose', label: 'loose', color: 'red' }, - { value: 'canceled', label: 'canceled', color: selectColor.crimson }, - { value: 'assigned', label: 'assigned', color: selectColor.mediumturquoise }, - { value: 'on hold', label: 'on hold', color: selectColor.burlywood }, - { value: 'waiting', label: 'waiting', color: 'orange' }, - ], - }, - - source: { - type: 'selectWithTranslation', - renderAsTag: true, - options: [ - { value: 'linkedin', label: 'linkedin', color: selectColor.royalblue }, - { value: 'socialmedia', label: 'social_media', color: selectColor.skyblue }, - { value: 'website', label: 'website', color: selectColor.coral }, - { value: 'advertising', label: 'advertising', color: selectColor.darkgreen }, - { value: 'friend', label: 'friend', color: selectColor.firebrick }, - { - value: 'professionals network', - label: 'professionals network', - color: selectColor.mediumvioletred, - }, - - { value: 'customer referral', label: 'customer referral', color: selectColor.violet }, - { value: 'sales', label: 'sales', color: selectColor.deeppink }, - { value: 'other', label: 'other', color: selectColor.darkgray }, - ], - }, - country: { - type: 'country', - color: null, - disableForForm: true, - }, - phone: { - type: 'phone', - disableForForm: true, - }, - email: { - type: 'email', - disableForForm: true, - }, - people: { - type: 'search', - label: 'people', - entity: 'people', - displayLabels: ['firstname', 'lastname'], - searchFields: 'firstname,lastname', - dataIndex: ['people', 'firstname'], - disableForTable: true, - feedback: 'people', - }, - company: { - type: 'search', - label: 'company', - entity: 'company', - displayLabels: ['name'], - searchFields: 'name', - dataIndex: ['company', 'name'], - disableForTable: true, - feedback: 'company', - }, - notes: { - type: 'textarea', - disableForTable: true, - }, -}; diff --git a/frontend/src/pages/Lead/index.jsx b/frontend/src/pages/Lead/index.jsx deleted file mode 100644 index b3b8f6054..000000000 --- a/frontend/src/pages/Lead/index.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import CrudModule from '@/modules/CrudModule/CrudModule'; -import DynamicForm from '@/forms/DynamicForm'; -import { fields } from './config'; - -import useLanguage from '@/locale/useLanguage'; - -export default function Lead() { - const translate = useLanguage(); - const entity = 'lead'; - const searchConfig = { - displayLabels: ['name'], - searchFields: 'name', - }; - const deleteModalLabels = ['name']; - - const Labels = { - PANEL_TITLE: translate('lead'), - DATATABLE_TITLE: translate('lead_list'), - ADD_NEW_ENTITY: translate('add_new_lead'), - ENTITY_NAME: translate('lead'), - }; - const configPage = { - entity, - ...Labels, - }; - const config = { - ...configPage, - fields, - searchConfig, - deleteModalLabels, - }; - return ( - } - updateForm={} - config={config} - /> - ); -} diff --git a/frontend/src/pages/Offer/OfferCreate.jsx b/frontend/src/pages/Offer/OfferCreate.jsx deleted file mode 100644 index 27b95106e..000000000 --- a/frontend/src/pages/Offer/OfferCreate.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import useLanguage from '@/locale/useLanguage'; -import CreateOfferModule from '@/modules/OfferModule/CreateOfferModule'; - -export default function OfferCreate() { - const translate = useLanguage(); - - const entity = 'offer'; - const Labels = { - PANEL_TITLE: translate('Offer Leads'), - DATATABLE_TITLE: translate('offer_list'), - ADD_NEW_ENTITY: translate('add_new_offer'), - ENTITY_NAME: translate('Offer Leads'), - }; - - const configPage = { - entity, - ...Labels, - }; - return ; -} diff --git a/frontend/src/pages/Offer/OfferRead.jsx b/frontend/src/pages/Offer/OfferRead.jsx deleted file mode 100644 index f324b084d..000000000 --- a/frontend/src/pages/Offer/OfferRead.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import useLanguage from '@/locale/useLanguage'; -import ReadOfferModule from '@/modules/OfferModule/ReadOfferModule'; - -export default function OfferRead() { - const translate = useLanguage(); - - const entity = 'offer'; - const Labels = { - PANEL_TITLE: translate('Offer Leads'), - DATATABLE_TITLE: translate('offer_list'), - ADD_NEW_ENTITY: translate('add_new_offer'), - ENTITY_NAME: translate('Offer Leads'), - }; - - const configPage = { - entity, - ...Labels, - }; - return ; -} diff --git a/frontend/src/pages/Offer/OfferUpdate.jsx b/frontend/src/pages/Offer/OfferUpdate.jsx deleted file mode 100644 index f3e2ed2ad..000000000 --- a/frontend/src/pages/Offer/OfferUpdate.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import useLanguage from '@/locale/useLanguage'; -import UpdateOfferModule from '@/modules/OfferModule/UpdateOfferModule'; - -export default function OfferUpdate() { - const translate = useLanguage(); - - const entity = 'offer'; - const Labels = { - PANEL_TITLE: translate('Offer Leads'), - DATATABLE_TITLE: translate('offer_list'), - ADD_NEW_ENTITY: translate('add_new_offer'), - ENTITY_NAME: translate('Offer Leads'), - }; - - const configPage = { - entity, - ...Labels, - }; - return ; -} diff --git a/frontend/src/pages/Offer/index.jsx b/frontend/src/pages/Offer/index.jsx deleted file mode 100644 index 3e988fcbe..000000000 --- a/frontend/src/pages/Offer/index.jsx +++ /dev/null @@ -1,102 +0,0 @@ -import dayjs from 'dayjs'; -import { Tag } from 'antd'; -import { tagColor } from '@/utils/statusTagColor'; - -import OfferDataTableModule from '@/modules/OfferModule/OfferDataTableModule'; -import { useMoney, useDate } from '@/settings'; -import useLanguage from '@/locale/useLanguage'; - -export default function Offer() { - const translate = useLanguage(); - const { moneyFormatter } = useMoney(); - const { dateFormat } = useDate(); - - const searchConfig = { - entity: 'lead', - displayLabels: ['name'], - searchFields: 'name', - }; - const deleteModalLabels = ['number', 'lead.name']; - const dataTableColumns = [ - { - title: translate('Number'), - dataIndex: 'number', - }, - { - title: translate('Company'), - dataIndex: ['lead', 'name'], - }, - { - title: translate('Date'), - dataIndex: 'date', - render: (date) => dayjs(date).format(dateFormat), - }, - { - title: translate('Sub Total'), - dataIndex: 'subTotal', - onCell: () => { - return { - style: { - textAlign: 'right', - whiteSpace: 'nowrap', - direction: 'ltr', - }, - }; - }, - render: (total, record) => moneyFormatter({ amount: total, currency_code: record.currency }), - }, - { - title: translate('Total'), - dataIndex: 'total', - onCell: () => { - return { - style: { - textAlign: 'right', - whiteSpace: 'nowrap', - direction: 'ltr', - }, - }; - }, - render: (total, record) => moneyFormatter({ amount: total, currency_code: record.currency }), - }, - - { - title: translate('Note'), - dataIndex: 'notes', - }, - { - title: translate('Status'), - dataIndex: 'status', - render: (status) => { - let tagStatus = tagColor(status); - - return ( - - {/* {tagStatus.icon + ' '} */} - {status && translate(tagStatus.label)} - - ); - }, - }, - ]; - - const entity = 'offer'; - const Labels = { - PANEL_TITLE: translate('Offer Leads'), - DATATABLE_TITLE: translate('offer_list'), - ADD_NEW_ENTITY: translate('add_new_offer'), - ENTITY_NAME: translate('Offer Leads'), - }; - - const configPage = { - entity, - ...Labels, - }; - const config = { - ...configPage, - dataTableColumns, - searchConfig, - deleteModalLabels, - }; - return ; -} diff --git a/frontend/src/pages/Order/index.jsx b/frontend/src/pages/Order/index.jsx deleted file mode 100644 index 485fccd2c..000000000 --- a/frontend/src/pages/Order/index.jsx +++ /dev/null @@ -1,97 +0,0 @@ -import React from 'react'; - -import CrudModule from '@/modules/CrudModule/CrudModule'; -import OrderForm from '@/forms/OrderForm'; // Ensure to create this form -import useLanguage from '@/locale/useLanguage'; - -export default function Order() { - const translate = useLanguage(); - const entity = 'order'; - const searchConfig = { - displayLabels: ['orderId', 'status'], - searchFields: 'orderId,status', - outputValue: '_id', - }; - - const deleteModalLabels = ['orderId']; - - const readColumns = [ - { - title: translate('Order ID'), - dataIndex: 'orderId', - }, - { - title: translate('Product'), - dataIndex: 'products', - }, - { - title: translate('Quantity'), - dataIndex: 'quantity', - }, - { - title: translate('Price'), - dataIndex: 'price', - }, - { - title: translate('Status'), - dataIndex: 'status', - }, - { - title: translate('Note'), - dataIndex: 'notes', - }, - ]; - const dataTableColumns = [ - { - title: translate('Order ID'), - dataIndex: 'orderId', - }, - { - title: translate('Product'), - dataIndex: 'products', - }, - { - title: translate('Quantity'), - dataIndex: 'quantity', - }, - { - title: translate('Price'), - dataIndex: 'price', - }, - - { - title: translate('Status'), - dataIndex: 'status', - }, - { - title: translate('Note'), - dataIndex: 'notes', - }, - ]; - - const Labels = { - PANEL_TITLE: translate('order'), - DATATABLE_TITLE: translate('order_list'), - ADD_NEW_ENTITY: translate('add_new_order'), - ENTITY_NAME: translate('order'), - }; - - const configPage = { - entity, - ...Labels, - }; - const config = { - ...configPage, - dataTableColumns, - readColumns, - searchConfig, - deleteModalLabels, - }; - return ( - } - updateForm={} - config={config} - /> - ); -} diff --git a/frontend/src/pages/People/config.js b/frontend/src/pages/People/config.js deleted file mode 100644 index 157659dbc..000000000 --- a/frontend/src/pages/People/config.js +++ /dev/null @@ -1,94 +0,0 @@ -export const fields = { - firstname: { - type: 'string', - required: true, - }, - lastname: { - type: 'string', - required: true, - }, - company: { - type: 'search', - entity: 'company', - renderAsTag: true, - redirectLabel: 'Add New Company', - withRedirect: true, - urlToRedirect: '/company', - displayLabels: ['name'], - searchFields: 'name', - dataIndex: ['company', 'name'], - }, - country: { - type: 'country', - }, - phone: { - type: 'phone', - }, - email: { - type: 'email', - }, - // bio: { - // type: 'string', - // }, - // idCardNumber: { - // type: 'string', - // }, - // idCardType: { - // type: 'string', - // }, - // securitySocialNbr: { - // type: 'string', - // }, - // taxNumber: { - // type: 'string', - // }, - // birthday: { - // type: 'date', - // }, - // birthplace: { - // type: 'string', - // }, - // gender: { - // type: 'select', - // options: [ - // { - // value: 'male', - // label: 'Male', - // }, - // { - // value: 'female', - // label: 'Female', - // }, - // ], - // }, - // bankName: { - // type: 'string', - // }, - // bankIban: { - // type: 'string', - // }, - // bankSwift: { - // type: 'string', - // }, - // bankNumber: { - // type: 'string', - // }, - // bankRouting: { - // type: 'string', - // }, - // address: { - // type: 'string', - // }, - // city: { - // type: 'string', - // }, - // State: { - // type: 'string', - // }, - // postalCode: { - // type: 'number', - // }, - // website: { - // type: 'string', - // }, -}; diff --git a/frontend/src/pages/People/index.jsx b/frontend/src/pages/People/index.jsx deleted file mode 100644 index b44e2598e..000000000 --- a/frontend/src/pages/People/index.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import CrudModule from '@/modules/CrudModule/CrudModule'; -import DynamicForm from '@/forms/DynamicForm'; -import { fields } from './config'; - -import useLanguage from '@/locale/useLanguage'; - -export default function People() { - const translate = useLanguage(); - const entity = 'people'; - const searchConfig = { - displayLabels: ['firstname', 'lastname'], - searchFields: 'firstname,lastname,email', - }; - const deleteModalLabels = ['firstname', 'lastname']; - - const Labels = { - PANEL_TITLE: translate('person'), - DATATABLE_TITLE: translate('people_list'), - ADD_NEW_ENTITY: translate('add_new_person'), - ENTITY_NAME: translate('person'), - }; - const configPage = { - entity, - ...Labels, - }; - const config = { - ...configPage, - fields, - searchConfig, - deleteModalLabels, - }; - return ( - } - updateForm={} - config={config} - /> - ); -} diff --git a/frontend/src/pages/Product/config.js b/frontend/src/pages/Product/config.js deleted file mode 100644 index c9f5c32c2..000000000 --- a/frontend/src/pages/Product/config.js +++ /dev/null @@ -1,25 +0,0 @@ -export const fields = { - name: { - type: 'string', - required: true, - }, - productCategory: { - type: 'async', - label: 'product Category', - displayLabels: ['productCategory', 'name'], - dataIndex: ['productCategory', 'name'], - entity: 'productcategory', - required: true, - }, - - price: { - type: 'currency', - required: true, - }, - description: { - type: 'textarea', - }, - ref: { - type: 'string', - }, -}; diff --git a/frontend/src/pages/Product/index.jsx b/frontend/src/pages/Product/index.jsx deleted file mode 100644 index 9c8c01070..000000000 --- a/frontend/src/pages/Product/index.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import CrudModule from '@/modules/CrudModule/CrudModule'; -import DynamicForm from '@/forms/DynamicForm'; -import { fields } from './config'; - -import useLanguage from '@/locale/useLanguage'; - -export default function Product() { - const translate = useLanguage(); - const entity = 'product'; - const searchConfig = { - displayLabels: ['name'], - searchFields: 'name', - }; - const deleteModalLabels = ['name']; - - const Labels = { - PANEL_TITLE: translate('Product'), - DATATABLE_TITLE: translate('Product_list'), - ADD_NEW_ENTITY: translate('add_new_Product'), - ENTITY_NAME: translate('Product'), - }; - const configPage = { - entity, - ...Labels, - }; - const config = { - ...configPage, - fields, - searchConfig, - deleteModalLabels, - }; - return ( - } - updateForm={} - config={config} - /> - ); -} diff --git a/frontend/src/pages/ProductCategory/config.js b/frontend/src/pages/ProductCategory/config.js deleted file mode 100644 index 597a129d5..000000000 --- a/frontend/src/pages/ProductCategory/config.js +++ /dev/null @@ -1,21 +0,0 @@ -import color from '@/utils/color'; - -export const fields = { - name: { - type: 'stringWithColor', - required: true, - }, - description: { - type: 'textarea', - required: true, - }, - color: { - type: 'color', - options: [...color], - required: true, - }, - enabled: { - type: 'boolean', - required: true, - }, -}; diff --git a/frontend/src/pages/ProductCategory/index.jsx b/frontend/src/pages/ProductCategory/index.jsx deleted file mode 100644 index 2c841ba7e..000000000 --- a/frontend/src/pages/ProductCategory/index.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import CrudModule from '@/modules/CrudModule/CrudModule'; -import DynamicForm from '@/forms/DynamicForm'; -import { fields } from './config'; - -import useLanguage from '@/locale/useLanguage'; - -export default function ProductCategory() { - const translate = useLanguage(); - const entity = 'productcategory'; - const searchConfig = { - displayLabels: ['name'], - searchFields: 'name', - }; - const deleteModalLabels = ['name']; - - const Labels = { - PANEL_TITLE: translate('Product_Category'), - DATATABLE_TITLE: translate('Product_Category_list'), - ADD_NEW_ENTITY: translate('add_new_Product_Category'), - ENTITY_NAME: translate('Product_Category'), - }; - const configPage = { - entity, - ...Labels, - }; - const config = { - ...configPage, - fields, - searchConfig, - deleteModalLabels, - }; - return ( - } - updateForm={} - config={config} - /> - ); -} diff --git a/frontend/src/pages/Quote/index.jsx b/frontend/src/pages/Quote/index.jsx index e57981302..ec668f817 100644 --- a/frontend/src/pages/Quote/index.jsx +++ b/frontend/src/pages/Quote/index.jsx @@ -72,16 +72,6 @@ export default function Quote() { { title: translate('Status'), dataIndex: 'status', - render: (status) => { - let tagStatus = tagColor(status); - - return ( - - {/* {tagStatus.icon + ' '} */} - {status && translate(tagStatus.label)} - - ); - }, }, ]; diff --git a/frontend/src/redux/rootReducer.js b/frontend/src/redux/rootReducer.js index 04c47da73..8ba7ee88b 100644 --- a/frontend/src/redux/rootReducer.js +++ b/frontend/src/redux/rootReducer.js @@ -5,7 +5,6 @@ import { reducer as crudReducer } from './crud'; import { reducer as erpReducer } from './erp'; import { reducer as adavancedCrudReducer } from './adavancedCrud'; import { reducer as settingsReducer } from './settings'; -import { reducer as translateReducer } from './translate'; // Combine all reducers. @@ -15,7 +14,6 @@ const rootReducer = combineReducers({ erp: erpReducer, adavancedCrud: adavancedCrudReducer, settings: settingsReducer, - translate: translateReducer, }); export default rootReducer; diff --git a/frontend/src/redux/store.js b/frontend/src/redux/store.js index 0fd4826d2..2f88cb805 100644 --- a/frontend/src/redux/store.js +++ b/frontend/src/redux/store.js @@ -3,20 +3,9 @@ import { configureStore } from '@reduxjs/toolkit'; import lang from '@/locale/translation/en_us'; import rootReducer from './rootReducer'; -import storePersist, { localStorageHealthCheck } from './storePersist'; +import storePersist from './storePersist'; -localStorageHealthCheck(); - -const LANG_INITIAL_STATE = { - result: lang, - langCode: 'en_us', - isLoading: false, - isSuccess: false, -}; - -const lang_state = storePersist.get('translate') - ? storePersist.get('translate') - : LANG_INITIAL_STATE; +// localStorageHealthCheck(); const AUTH_INITIAL_STATE = { current: {}, @@ -27,7 +16,7 @@ const AUTH_INITIAL_STATE = { const auth_state = storePersist.get('auth') ? storePersist.get('auth') : AUTH_INITIAL_STATE; -const initialState = { translate: lang_state, auth: auth_state }; +const initialState = { auth: auth_state }; const store = configureStore({ reducer: rootReducer, @@ -35,8 +24,8 @@ const store = configureStore({ devTools: import.meta.env.PROD === false, // Enable Redux DevTools in development mode }); -// console.log( -// '๐Ÿš€ Welcome to IDURAR ERP CRM! Did you know that we also offer commercial customization services? Contact us at hello@idurarapp.com for more information.' -// ); +console.log( + '๐Ÿš€ Welcome to IDURAR ERP CRM! Did you know that we also offer commercial customization services? Contact us at hello@idurarapp.com for more information.' +); export default store; diff --git a/frontend/src/redux/translate/actions.js b/frontend/src/redux/translate/actions.js deleted file mode 100644 index 0c5db970d..000000000 --- a/frontend/src/redux/translate/actions.js +++ /dev/null @@ -1,41 +0,0 @@ -import * as actionTypes from './types'; - -import translation from '@/locale/translation/translation'; - -export const translateAction = { - resetState: () => (dispatch) => { - dispatch({ - type: actionTypes.RESET_STATE, - }); - }, - translate: - (value = 'en_us') => - async (dispatch) => { - dispatch({ - type: actionTypes.REQUEST_LOADING, - }); - - let data = translation.en_us; - if (data) { - const LANG_STATE = { - result: data, - isRtl: isRtl, - langDirection: 'ltr', - langCode: value, - isLoading: false, - isSuccess: false, - }; - window.localStorage.setItem('translate', JSON.stringify(LANG_STATE)); - dispatch({ - type: actionTypes.REQUEST_SUCCESS, - payload: data, - langCode: value, - isRtl: isRtl, - }); - } else { - dispatch({ - type: actionTypes.REQUEST_FAILED, - }); - } - }, -}; diff --git a/frontend/src/redux/translate/index.js b/frontend/src/redux/translate/index.js deleted file mode 100644 index cbc56ade5..000000000 --- a/frontend/src/redux/translate/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default as reducer } from './reducer'; diff --git a/frontend/src/redux/translate/reducer.js b/frontend/src/redux/translate/reducer.js deleted file mode 100644 index cb3ed7b2a..000000000 --- a/frontend/src/redux/translate/reducer.js +++ /dev/null @@ -1,47 +0,0 @@ -import * as actionTypes from './types'; -import en_us from '@/locale/translation/en_us'; -import storePersist from '../storePersist'; - -const LANG_INITIAL_STATE = { - result: en_us, - langCode: 'en_us', - langDirection: 'ltr', - isLoading: false, - isSuccess: false, -}; - -const INITIAL_STATE = storePersist.get('translate') - ? storePersist.get('translate') - : LANG_INITIAL_STATE; - -const translateReducer = (state = INITIAL_STATE, action) => { - const { payload = null, langCode, isRtl = false } = action; - switch (action.type) { - case actionTypes.RESET_STATE: - return INITIAL_STATE; - case actionTypes.REQUEST_LOADING: - return { - ...state, - isLoading: true, - }; - case actionTypes.REQUEST_FAILED: - return { - ...state, - isLoading: false, - isSuccess: false, - }; - - case actionTypes.REQUEST_SUCCESS: - return { - result: payload, - langCode: langCode.toLowerCase(), - langDirection: isRtl ? 'rtl' : 'ltr', - isLoading: false, - isSuccess: true, - }; - default: - return state; - } -}; - -export default translateReducer; diff --git a/frontend/src/redux/translate/selectors.js b/frontend/src/redux/translate/selectors.js deleted file mode 100644 index 8ac59883c..000000000 --- a/frontend/src/redux/translate/selectors.js +++ /dev/null @@ -1,10 +0,0 @@ -import { createSelector } from 'reselect'; - -export const selectLangState = (state) => state.translate; - -export const selectCurrentLang = createSelector([selectLangState], (translate) => translate.result); -export const selectLangCode = createSelector([selectLangState], (translate) => translate.langCode); -export const selectLangDirection = createSelector( - [selectLangState], - (translate) => translate.langDirection -); diff --git a/frontend/src/redux/translate/types.js b/frontend/src/redux/translate/types.js deleted file mode 100644 index 15bcf1785..000000000 --- a/frontend/src/redux/translate/types.js +++ /dev/null @@ -1,5 +0,0 @@ -export const RESET_STATE = 'TRANSLATE_RESET_STATE'; - -export const REQUEST_LOADING = 'TRANSLATE_REQUEST_LOADING'; -export const REQUEST_SUCCESS = 'TRANSLATE_REQUEST_SUCCESS'; -export const REQUEST_FAILED = 'TRANSLATE_REQUEST_FAILED'; diff --git a/frontend/src/request/errorHandler.js b/frontend/src/request/errorHandler.js index 8207317b7..a580a0eca 100644 --- a/frontend/src/request/errorHandler.js +++ b/frontend/src/request/errorHandler.js @@ -27,10 +27,10 @@ const errorHandler = (error) => { maxCount: 1, }); // Code to execute when there is no internet connection - notification.error({ - message: 'Problem connecting to server', - description: 'Cannot connect to the server, Try again later', - }); + // notification.error({ + // message: 'Problem connecting to server', + // description: 'Cannot connect to the server, Try again later', + // }); return { success: false, result: null, @@ -53,7 +53,7 @@ const errorHandler = (error) => { const message = response.data && response.data.message; const errorText = message || codeMessage[response.status]; - const { status } = response; + const { status, error } = response; notification.config({ duration: 20, maxCount: 2, @@ -62,7 +62,12 @@ const errorHandler = (error) => { message: `Request error ${status}`, description: errorText, }); - return response.data; + + if (response?.data?.error?.name === 'JsonWebTokenError') { + window.localStorage.removeItem('auth'); + window.localStorage.removeItem('isLogout'); + window.location.href = '/logout'; + } else return response.data; } else { notification.config({ duration: 15, diff --git a/frontend/src/request/request.js b/frontend/src/request/request.js index febf19028..2679ade73 100644 --- a/frontend/src/request/request.js +++ b/frontend/src/request/request.js @@ -3,13 +3,31 @@ import { API_BASE_URL } from '@/config/serverApiConfig'; import errorHandler from './errorHandler'; import successHandler from './successHandler'; +import storePersist from '@/redux/storePersist'; -axios.defaults.baseURL = API_BASE_URL; -axios.defaults.withCredentials = true; +function findKeyByPrefix(object, prefix) { + for (var property in object) { + if (object.hasOwnProperty(property) && property.toString().startsWith(prefix)) { + return property; + } + } +} + +function includeToken() { + axios.defaults.baseURL = API_BASE_URL; + + axios.defaults.withCredentials = true; + const auth = storePersist.get('auth'); + + if (auth) { + axios.defaults.headers.common['Authorization'] = `Bearer ${auth.current.token}`; + } +} const request = { create: async ({ entity, jsonData }) => { try { + includeToken(); const response = await axios.post(entity + '/create', jsonData); successHandler(response, { notifyOnSuccess: true, @@ -22,6 +40,7 @@ const request = { }, createAndUpload: async ({ entity, jsonData }) => { try { + includeToken(); const response = await axios.post(entity + '/create', jsonData, { headers: { 'Content-Type': 'multipart/form-data', @@ -38,6 +57,7 @@ const request = { }, read: async ({ entity, id }) => { try { + includeToken(); const response = await axios.get(entity + '/read/' + id); successHandler(response, { notifyOnSuccess: false, @@ -50,6 +70,7 @@ const request = { }, update: async ({ entity, id, jsonData }) => { try { + includeToken(); const response = await axios.patch(entity + '/update/' + id, jsonData); successHandler(response, { notifyOnSuccess: true, @@ -62,6 +83,7 @@ const request = { }, updateAndUpload: async ({ entity, id, jsonData }) => { try { + includeToken(); const response = await axios.patch(entity + '/update/' + id, jsonData, { headers: { 'Content-Type': 'multipart/form-data', @@ -79,6 +101,7 @@ const request = { delete: async ({ entity, id }) => { try { + includeToken(); const response = await axios.delete(entity + '/delete/' + id); successHandler(response, { notifyOnSuccess: true, @@ -92,6 +115,7 @@ const request = { filter: async ({ entity, options = {} }) => { try { + includeToken(); let filter = options.filter ? 'filter=' + options.filter : ''; let equal = options.equal ? '&equal=' + options.equal : ''; let query = `?${filter}${equal}`; @@ -109,6 +133,7 @@ const request = { search: async ({ entity, options = {} }) => { try { + includeToken(); let query = '?'; for (var key in options) { query += key + '=' + options[key] + '&'; @@ -129,6 +154,7 @@ const request = { list: async ({ entity, options = {} }) => { try { + includeToken(); let query = '?'; for (var key in options) { query += key + '=' + options[key] + '&'; @@ -148,6 +174,7 @@ const request = { }, listAll: async ({ entity, options = {} }) => { try { + includeToken(); let query = '?'; for (var key in options) { query += key + '=' + options[key] + '&'; @@ -168,6 +195,7 @@ const request = { post: async ({ entity, jsonData }) => { try { + includeToken(); const response = await axios.post(entity, jsonData); return response.data; @@ -177,6 +205,7 @@ const request = { }, get: async ({ entity }) => { try { + includeToken(); const response = await axios.get(entity); return response.data; } catch (error) { @@ -185,6 +214,7 @@ const request = { }, patch: async ({ entity, jsonData }) => { try { + includeToken(); const response = await axios.patch(entity, jsonData); successHandler(response, { notifyOnSuccess: true, @@ -198,6 +228,7 @@ const request = { upload: async ({ entity, id, jsonData }) => { try { + includeToken(); const response = await axios.patch(entity + '/upload/' + id, jsonData, { headers: { 'Content-Type': 'multipart/form-data', @@ -221,6 +252,7 @@ const request = { summary: async ({ entity, options = {} }) => { try { + includeToken(); let query = '?'; for (var key in options) { query += key + '=' + options[key] + '&'; @@ -241,6 +273,7 @@ const request = { mail: async ({ entity, jsonData }) => { try { + includeToken(); const response = await axios.post(entity + '/mail/', jsonData); successHandler(response, { notifyOnSuccess: true, @@ -254,6 +287,7 @@ const request = { convert: async ({ entity, id }) => { try { + includeToken(); const response = await axios.get(`${entity}/convert/${id}`); successHandler(response, { notifyOnSuccess: true, diff --git a/frontend/src/router/routes.jsx b/frontend/src/router/routes.jsx index 80fd1dd0e..156b89c51 100644 --- a/frontend/src/router/routes.jsx +++ b/frontend/src/router/routes.jsx @@ -24,21 +24,8 @@ const PaymentUpdate = lazy(() => import('@/pages/Payment/PaymentUpdate')); const Settings = lazy(() => import('@/pages/Settings/Settings')); const PaymentMode = lazy(() => import('@/pages/PaymentMode')); const Taxes = lazy(() => import('@/pages/Taxes')); -const AdvancedSettings = lazy(() => import('@/pages/AdvancedSettings')); -const Profile = lazy(() => import('@/pages/Profile')); -const Lead = lazy(() => import('@/pages/Lead/index')); -const Offer = lazy(() => import('@/pages/Offer/index')); -const OfferCreate = lazy(() => import('@/pages/Offer/OfferCreate')); -const OfferRead = lazy(() => import('@/pages/Offer/OfferRead')); -const OfferUpdate = lazy(() => import('@/pages/Offer/OfferUpdate')); - -const ExpenseCategory = lazy(() => import('@/pages/ExpenseCategory')); -const Expense = lazy(() => import('@/pages/Expense')); -const ProductCategory = lazy(() => import('@/pages/ProductCategory')); -const Product = lazy(() => import('@/pages/Product')); -const People = lazy(() => import('@/pages/People')); -const Company = lazy(() => import('@/pages/Company')); +const Profile = lazy(() => import('@/pages/Profile')); const About = lazy(() => import('@/pages/About')); @@ -49,14 +36,6 @@ let routes = { path: '/login', element: , }, - { - path: '/verify/*', - element: , - }, - { - path: '/resetpassword/*', - element: , - }, { path: '/logout', element: , @@ -73,22 +52,6 @@ let routes = { path: '/customer', element: , }, - { - path: '/people', - element: , - }, - { - path: '/company', - element: , - }, - { - path: '/product', - element: , - }, - { - path: '/category/product', - element: , - }, { path: '/invoice', @@ -156,42 +119,10 @@ let routes = { element: , }, - { - path: '/settings/advanced', - element: , - }, { path: '/profile', element: , }, - { - path: '/lead', - element: , - }, - { - path: '/offer', - element: , - }, - { - path: '/offer/create', - element: , - }, - { - path: '/offer/read/:id', - element: , - }, - { - path: '/offer/update/:id', - element: , - }, - { - path: '/expenses', - element: , - }, - { - path: 'category/expenses', - element: , - }, { path: '*', element: , diff --git a/frontend/src/style/partials/collapseBox.css b/frontend/src/style/partials/collapseBox.css index c3e6fc994..c4dc5eab2 100644 --- a/frontend/src/style/partials/collapseBox.css +++ b/frontend/src/style/partials/collapseBox.css @@ -10,7 +10,7 @@ font-size: 14px; text-transform: uppercase; cursor: pointer; - background-color: #f9fafc; + background-color: #ffffff; border-top: 1px solid #edf0f5; border-bottom: 1px solid #edf0f5; } diff --git a/frontend/src/style/partials/core.css b/frontend/src/style/partials/core.css index 600f0ef9a..de1cd89f5 100644 --- a/frontend/src/style/partials/core.css +++ b/frontend/src/style/partials/core.css @@ -13,17 +13,16 @@ } .whiteBox { background: #fff; - border-radius: 6px; width: 100%; min-height: 100px; overflow: hidden; transition: all 0.3s ease-in-out; } .shadow { - box-shadow: 0px 0px 20px 3px rgba(150, 190, 238, 0.15); + border: 1px solid #e0e0e0; } .shadow:hover { - box-shadow: 0px 0px 30px 8px rgba(150, 190, 238, 0.25); + border: 1px solid #bdbdbd; } .line { border-top: 1px solid #edf0f5; diff --git a/frontend/src/style/partials/customAntd.css b/frontend/src/style/partials/customAntd.css index 00110ff2b..7e4837e9d 100644 --- a/frontend/src/style/partials/customAntd.css +++ b/frontend/src/style/partials/customAntd.css @@ -1,5 +1,5 @@ .ant-layout { - background: #f9fafc !important; + background: #ffffff !important; } .site-layout .site-layout-background { background: #fff; @@ -7,31 +7,6 @@ [data-theme='dark'] .site-layout .site-layout-background { background: #141414; } -.ant-layout .ant-layout-sider-light .ant-layout-sider-trigger { - /* background: none !important; */ - border-radius: 6px !important; - border: none !important; - margin-top: -10px !important; -} -.ant-layout-sider-trigger { - background: #fff; - border-top: 1px solid #edf0f5; - border-right: 1px solid #edf0f5; - color: #4f5d75; -} -.ant-layout-sider-zero-width-trigger { - top: 5px; - right: 10px; - color: #001529; - background: none; - font-size: 20px; -} - -.ant-menu-inline, -.ant-menu-vertical, -.ant-menu-vertical-left { - border: none !important; -} .headerIcon .ant-dropdown-menu { border-radius: 6px; diff --git a/frontend/src/style/partials/navigation.css b/frontend/src/style/partials/navigation.css index 68596d98d..d7f613c66 100644 --- a/frontend/src/style/partials/navigation.css +++ b/frontend/src/style/partials/navigation.css @@ -31,9 +31,9 @@ padding: 12px 0px !important; } -.ant-btn.mobile-sidebar-btn { +/* .ant-btn.mobile-sidebar-btn { display: none; -} +} */ .tabsNavigation span { background-color: transparent; @@ -53,11 +53,11 @@ height: 100%; } - .ant-btn.mobile-sidebar-btn { + /* .ant-btn.mobile-sidebar-btn { display: block; position: absolute; top: 21px; - } + } */ .mobile-sidebar-wraper { display: block; diff --git a/frontend/src/style/partials/rest.css b/frontend/src/style/partials/rest.css index a80015352..a5ef192d7 100644 --- a/frontend/src/style/partials/rest.css +++ b/frontend/src/style/partials/rest.css @@ -17,5 +17,5 @@ body { margin: 0; padding: 0; - background: #f9fafc !important; + background: #ffffff !important; } diff --git a/frontend/src/utils/countryList.js b/frontend/src/utils/countryList.js index 17667046f..95631e626 100644 --- a/frontend/src/utils/countryList.js +++ b/frontend/src/utils/countryList.js @@ -1,39 +1,32 @@ export const countryList = [ { - icon: '๐Ÿ‡ฆ๐Ÿ‡ซ', label: 'Afghanistan', value: 'AF', timeZone: ['Asia/Kabul'], }, { - icon: '๐Ÿ‡ฆ๐Ÿ‡ฑ', label: 'Albania', value: 'AL', timeZone: ['Europe/Tirane'], }, { - icon: '๐Ÿ‡ฉ๐Ÿ‡ฟ', label: 'Algeria', value: 'DZ', timeZone: ['Africa/Algiers'], }, { - icon: '๐Ÿ‡ฆ๐Ÿ‡ฉ', label: 'Andorra', value: 'AD', }, { - icon: '๐Ÿ‡ฆ๐Ÿ‡ด', label: 'Angola', value: 'AO', }, { - icon: '๐Ÿ‡ฆ๐Ÿ‡ฎ', label: 'Anguilla', value: 'AI', }, { - icon: '๐Ÿ‡ฆ๐Ÿ‡ท', label: 'Argentina', value: 'AR', timeZone: [ @@ -52,18 +45,15 @@ export const countryList = [ ], }, { - icon: '๐Ÿ‡ฆ๐Ÿ‡ฒ', label: 'Armenia', value: 'AM', timeZone: ['Asia/Yerevan'], }, { - icon: '๐Ÿ‡ฆ๐Ÿ‡ผ', label: 'Aruba', value: 'AW', }, { - icon: '๐Ÿ‡ฆ๐Ÿ‡บ', label: 'Australia', value: 'AU', timeZone: [ @@ -83,93 +73,77 @@ export const countryList = [ ], }, { - icon: '๐Ÿ‡ฆ๐Ÿ‡น', label: 'Austria', value: 'AT', timeZone: ['Europe/Vienna'], }, { - icon: '๐Ÿ‡ฆ๐Ÿ‡ฟ', label: 'Azerbaijan', value: 'AZ', timeZone: ['Asia/Baku'], }, { - icon: '๐Ÿ‡ง๐Ÿ‡ธ', label: 'Bahamas', value: 'BS', }, { - icon: '๐Ÿ‡ง๐Ÿ‡ญ', label: 'Bahrain', value: 'BH', timeZone: ['Asia/Bahrain'], }, { - icon: '๐Ÿ‡ง๐Ÿ‡ฉ', label: 'Bangladesh', value: 'BD', timeZone: ['Asia/Dhaka'], }, { - icon: '๐Ÿ‡ง๐Ÿ‡ง', label: 'Barbados', value: 'BB', }, { - icon: '๐Ÿ‡ง๐Ÿ‡พ', label: 'Belarus', value: 'BY', timeZone: ['Europe/Minsk'], }, { - icon: '๐Ÿ‡ง๐Ÿ‡ช', label: 'Belgium', value: 'BE', timeZone: ['Europe/Brussels'], }, { - icon: '๐Ÿ‡ง๐Ÿ‡ฟ', label: 'Belize', value: 'BZ', timeZone: ['America/Belize'], }, { - icon: '๐Ÿ‡ง๐Ÿ‡ฏ', label: 'Benin', value: 'BJ', }, { - icon: '๐Ÿ‡ง๐Ÿ‡ฒ', label: 'Bermuda', value: 'BM', }, { - icon: '๐Ÿ‡ง๐Ÿ‡น', label: 'Bhutan', value: 'BT', timeZone: ['Asia/Thimphu'], }, { - icon: '๐Ÿ‡ง๐Ÿ‡ด', label: 'Bolivia', value: 'BO', timeZone: ['America/La_Paz'], }, { - icon: '๐Ÿ‡ง๐Ÿ‡ฆ', label: 'Bosnia and Herzegovina', value: 'BA', timeZone: ['Europe/Sarajevo'], }, { - icon: '๐Ÿ‡ง๐Ÿ‡ผ', label: 'Botswana', value: 'BW', timeZone: ['Africa/Gaborone'], }, { - icon: '๐Ÿ‡ง๐Ÿ‡ท', label: 'Brazil', value: 'BR', timeZone: [ @@ -192,46 +166,38 @@ export const countryList = [ ], }, { - icon: '๐Ÿ‡ง๐Ÿ‡ณ', label: 'Brunei Darussalam', value: 'BN', timeZone: ['Asia/Brunei'], }, { - icon: '๐Ÿ‡ง๐Ÿ‡ฌ', label: 'Bulgaria', value: 'BG', timeZone: ['Europe/Sofia'], }, { - icon: '๐Ÿ‡ง๐Ÿ‡ซ', label: 'Burkina Faso', value: 'BF', }, { - icon: '๐Ÿ‡ง๐Ÿ‡ฎ', label: 'Burundi', value: 'BI', }, { - icon: '๐Ÿ‡จ๐Ÿ‡ป', label: 'Cabo Verde', value: 'CV', }, { - icon: '๐Ÿ‡ฐ๐Ÿ‡ญ', label: 'Cambodia', value: 'KH', timeZone: ['Asia/Phnom_Penh'], }, { - icon: '๐Ÿ‡จ๐Ÿ‡ฒ', label: 'Cameroon', value: 'CM', timeZone: ['Africa/Douala'], }, { - icon: '๐Ÿ‡จ๐Ÿ‡ฆ', label: 'Canada', value: 'CA', timeZone: [ @@ -266,467 +232,385 @@ export const countryList = [ ], }, { - icon: '๐Ÿ‡จ๐Ÿ‡ซ', label: 'Central African Republic', value: 'CF', }, { - icon: '๐Ÿ‡น๐Ÿ‡ฉ', label: 'Chad', value: 'TD', }, { - icon: '๐Ÿ‡จ๐Ÿ‡ฑ', label: 'Chile', value: 'CL', timeZone: ['America/Santiago', 'Pacific/Easter'], }, { - icon: '๐Ÿ‡จ๐Ÿ‡ณ', label: 'China', value: 'CN', timeZone: ['Asia/Shanghai', 'Asia/Urumqi'], }, { - icon: '๐Ÿ‡จ๐Ÿ‡ด', label: 'Colombia', value: 'CO', timeZone: ['America/Bogota'], }, { - icon: '๐Ÿ‡ฐ๐Ÿ‡ฒ', label: 'Comoros', value: 'KM', }, { - icon: '๐Ÿ‡จ๐Ÿ‡ฉ', label: 'Congo', value: 'CD', timeZone: ['Africa/Kinshasa', 'Africa/Lubumbashi'], }, { - icon: '๐Ÿ‡จ๐Ÿ‡ฌ', label: 'Congo', value: 'CG', }, { - icon: '๐Ÿ‡จ๐Ÿ‡ท', label: 'Costa Rica', value: 'CR', timeZone: ['America/Costa_Rica'], }, { - icon: '๐Ÿ‡ญ๐Ÿ‡ท', label: 'Croatia', value: 'HR', timeZone: ['Europe/Zagreb'], }, { - icon: '๐Ÿ‡จ๐Ÿ‡บ', label: 'Cuba', value: 'CU', timeZone: ['America/Havana'], }, { - icon: '๐Ÿ‡จ๐Ÿ‡พ', label: 'Cyprus', value: 'CY', }, { - icon: '๐Ÿ‡จ๐Ÿ‡ฟ', label: 'Czechia', value: 'CZ', timeZone: ['Europe/Prague'], }, { - icon: '๐Ÿ‡จ๐Ÿ‡ฎ', label: 'Cote d Ivoire', value: 'CI', timeZone: ['Africa/Abidjan'], }, { - icon: '๐Ÿ‡ฉ๐Ÿ‡ฐ', label: 'Denmark', value: 'DK', timeZone: ['Europe/Copenhagen'], }, { - icon: '๐Ÿ‡ฉ๐Ÿ‡ฏ', label: 'Djibouti', value: 'DJ', timeZone: ['Africa/Djibouti'], }, { - icon: '๐Ÿ‡ฉ๐Ÿ‡ฒ', label: 'Dominica', value: 'DM', }, { - icon: '๐Ÿ‡ฉ๐Ÿ‡ด', label: 'Dominican Republic', value: 'DO', timeZone: ['America/Santo_Domingo'], }, { - icon: '๐Ÿ‡ช๐Ÿ‡จ', label: 'Ecuador', value: 'EC', timeZone: ['America/Guayaquil', 'Pacific/Galapagos'], }, { - icon: '๐Ÿ‡ช๐Ÿ‡ฌ', label: 'Egypt', value: 'EG', timeZone: ['Africa/Cairo'], }, { - icon: '๐Ÿ‡ธ๐Ÿ‡ป', label: 'El Salvador', value: 'SV', timeZone: ['America/El_Salvador'], }, { - icon: '๐Ÿ‡ช๐Ÿ‡ท', label: 'Eritrea', value: 'ER', timeZone: ['Africa/Asmara'], }, { - icon: '๐Ÿ‡ช๐Ÿ‡ช', label: 'Estonia', value: 'EE', timeZone: ['Europe/Tallinn'], }, { - icon: '๐Ÿ‡ธ๐Ÿ‡ฟ', label: 'Eswatini', value: 'SZ', }, { - icon: '๐Ÿ‡ช๐Ÿ‡น', label: 'Ethiopia', value: 'ET', timeZone: ['Africa/Addis_Ababa'], }, { - icon: '๐Ÿ‡ซ๐Ÿ‡ฏ', label: 'Fiji', value: 'FJ', }, { - icon: '๐Ÿ‡ซ๐Ÿ‡ฎ', label: 'Finland', value: 'FI', timeZone: ['Europe/Helsinki'], }, { - icon: '๐Ÿ‡ซ๐Ÿ‡ท', label: 'France', value: 'FR', timeZone: ['Europe/Paris'], }, { - icon: '๐Ÿ‡ฌ๐Ÿ‡ฆ', label: 'Gabon', value: 'GA', }, { - icon: '๐Ÿ‡ฌ๐Ÿ‡ฒ', label: 'Gambia', value: 'GM', }, { - icon: '๐Ÿ‡ฌ๐Ÿ‡ช', label: 'Georgia', value: 'GE', timeZone: ['Asia/Tbilisi'], }, { - icon: '๐Ÿ‡ฉ๐Ÿ‡ช', label: 'Germany', value: 'DE', timeZone: ['Europe/Berlin', 'Europe/Busingen'], }, { - icon: '๐Ÿ‡ฌ๐Ÿ‡ญ', label: 'Ghana', value: 'GH', }, { - icon: '๐Ÿ‡ฌ๐Ÿ‡ฎ', label: 'Gibraltar', value: 'GI', }, { - icon: '๐Ÿ‡ฌ๐Ÿ‡ท', label: 'Greece', value: 'GR', timeZone: ['Europe/Athens'], }, { - icon: '๐Ÿ‡ฌ๐Ÿ‡ฑ', label: 'Greenland', value: 'GL', timeZone: ['America/Godthab', 'America/Danmarkshavn', 'America/Scoresbysund', 'America/Thule'], }, { - icon: '๐Ÿ‡ฌ๐Ÿ‡ต', label: 'Guadeloupe', value: 'GP', }, { - icon: '๐Ÿ‡ฌ๐Ÿ‡บ', label: 'Guam', value: 'GU', }, { - icon: '๐Ÿ‡ฌ๐Ÿ‡น', label: 'Guatemala', value: 'GT', timeZone: ['America/Guatemala'], }, { - icon: '๐Ÿ‡ฌ๐Ÿ‡ฌ', label: 'Guernsey', value: 'GG', }, { - icon: '๐Ÿ‡ฌ๐Ÿ‡ณ', label: 'Guinea', value: 'GN', }, { - icon: '๐Ÿ‡ฌ๐Ÿ‡ผ', label: 'Guinea-Bissau', value: 'GW', }, { - icon: '๐Ÿ‡ฌ๐Ÿ‡พ', label: 'Guyana', value: 'GY', }, { - icon: '๐Ÿ‡ญ๐Ÿ‡น', label: 'Haiti', value: 'HT', timeZone: ['America/Port-au-Prince'], }, { - icon: '๐Ÿ‡ญ๐Ÿ‡ณ', label: 'Honduras', value: 'HN', timeZone: ['America/Tegucigalpa'], }, { - icon: '๐Ÿ‡ญ๐Ÿ‡ฐ', label: 'Hong Kong', value: 'HK', timeZone: ['Asia/Hong_Kong'], }, { - icon: '๐Ÿ‡ญ๐Ÿ‡บ', label: 'Hungary', value: 'HU', timeZone: ['Europe/Budapest'], }, { - icon: '๐Ÿ‡ฎ๐Ÿ‡ธ', label: 'Iceland', value: 'IS', timeZone: ['Atlantic/Reykjavik'], }, { - icon: '๐Ÿ‡ฎ๐Ÿ‡ณ', value: 'IN', label: 'India', timeZone: ['Asia/Kolkata'], }, { - icon: '๐Ÿ‡ฎ๐Ÿ‡ฉ', value: 'ID', label: 'Indonesia', timeZone: ['Asia/Jakarta', 'Asia/Pontianak', 'Asia/Makassar', 'Asia/Jayapura'], }, { - icon: '๐Ÿ‡ฎ๐Ÿ‡ท', value: 'IR', label: 'Iran', timeZone: ['Asia/Tehran'], }, { - icon: '๐Ÿ‡ฎ๐Ÿ‡ถ', value: 'IQ', label: 'Iraq', timeZone: ['Asia/Baghdad'], }, { - icon: '๐Ÿ‡ฎ๐Ÿ‡ช', value: 'IE', label: 'Ireland', timeZone: ['Europe/Dublin'], }, { - icon: '๐Ÿ‡ฎ๐Ÿ‡ฑ', value: 'IL', label: 'Israel', timeZone: ['Asia/Jerusalem'], }, { - icon: '๐Ÿ‡ฎ๐Ÿ‡น', value: 'IT', label: 'Italy', timeZone: ['Europe/Rome'], }, { - icon: '๐Ÿ‡ฏ๐Ÿ‡ฒ', value: 'JM', label: 'Jamaica', timeZone: ['America/Jamaica'], }, { - icon: '๐Ÿ‡ฏ๐Ÿ‡ต', value: 'JP', label: 'Japan', timeZone: ['Asia/Tokyo'], }, { - icon: '๐Ÿ‡ฏ๐Ÿ‡ด', value: 'JO', label: 'Jordan', timeZone: ['Asia/Amman'], }, { - icon: '๐Ÿ‡ฐ๐Ÿ‡ฟ', value: 'KZ', label: 'Kazakhstan', timeZone: ['Asia/Almaty', 'Asia/Qyzylorda', 'Asia/Aqtobe', 'Asia/Aqtau', 'Asia/Oral'], }, { - icon: '๐Ÿ‡ฐ๐Ÿ‡ช', value: 'KE', label: 'Kenya', timeZone: ['Africa/Nairobi'], }, { - icon: '๐Ÿ‡ฐ๐Ÿ‡ต', value: 'KP', label: 'Korea', }, { - icon: '๐Ÿ‡ฐ๐Ÿ‡ท', value: 'KR', label: 'Korea', timeZone: ['Asia/Seoul'], }, { - icon: '๐Ÿ‡ฐ๐Ÿ‡ผ', value: 'KW', label: 'Kuwait', timeZone: ['Asia/Kuwait'], }, { - icon: '๐Ÿ‡ฐ๐Ÿ‡ฌ', value: 'KG', label: 'Kyrgyzstan', timeZone: ['Asia/Bishkek'], }, { - icon: '๐Ÿ‡ฑ๐Ÿ‡ป', value: 'LV', label: 'Latvia', timeZone: ['Europe/Riga'], }, { - icon: '๐Ÿ‡ฑ๐Ÿ‡ง', value: 'LB', label: 'Lebanon', timeZone: ['Asia/Beirut'], }, { - icon: '๐Ÿ‡ฑ๐Ÿ‡ธ', value: 'LS', label: 'Lesotho', }, { - icon: '๐Ÿ‡ฑ๐Ÿ‡ท', value: 'LR', label: 'Liberia', }, { - icon: '๐Ÿ‡ฑ๐Ÿ‡พ', value: 'LY', label: 'Libya', timeZone: ['Africa/Tripoli'], }, { - icon: '๐Ÿ‡ฑ๐Ÿ‡ฎ', value: 'LI', label: 'Liechtenstein', timeZone: ['Europe/Vaduz'], }, { - icon: '๐Ÿ‡ฑ๐Ÿ‡น', value: 'LT', label: 'Lithuania', timeZone: ['Europe/Vilnius'], }, { - icon: '๐Ÿ‡ฑ๐Ÿ‡บ', value: 'LU', label: 'Luxembourg', timeZone: ['Europe/Luxembourg'], }, { - icon: '๐Ÿ‡ฒ๐Ÿ‡ฌ', value: 'MG', label: 'Madagascar', }, { - icon: '๐Ÿ‡ฒ๐Ÿ‡ผ', value: 'MW', label: 'Malawi', }, { - icon: '๐Ÿ‡ฒ๐Ÿ‡พ', value: 'MY', label: 'Malaysia', timeZone: ['Asia/Kuala_Lumpur', 'Asia/Kuching'], }, { - icon: '๐Ÿ‡ฒ๐Ÿ‡ป', value: 'MV', label: 'Maldives', timeZone: ['Indian/Maldives'], }, { - icon: '๐Ÿ‡ฒ๐Ÿ‡ฑ', value: 'ML', label: 'Mali', timeZone: ['Africa/Bamako'], }, { - icon: '๐Ÿ‡ฒ๐Ÿ‡น', value: 'MT', label: 'Malta', timeZone: ['Europe/Malta'], }, { - icon: '๐Ÿ‡ฒ๐Ÿ‡ถ', value: 'MQ', label: 'Martinique', }, { - icon: '๐Ÿ‡ฒ๐Ÿ‡ท', value: 'MR', label: 'Mauritania', }, { - icon: '๐Ÿ‡ฒ๐Ÿ‡บ', value: 'MU', label: 'Mauritius', }, { - icon: '๐Ÿ‡ฒ๐Ÿ‡ฝ', value: 'MX', label: 'Mexico', timeZone: [ @@ -744,186 +628,154 @@ export const countryList = [ ], }, { - icon: '๐Ÿ‡ฒ๐Ÿ‡ฉ', value: 'MD', label: 'Moldova', timeZone: ['Europe/Chisinau'], }, { - icon: '๐Ÿ‡ฒ๐Ÿ‡จ', value: 'MC', label: 'Monaco', timeZone: ['Europe/Monaco'], }, { - icon: '๐Ÿ‡ฒ๐Ÿ‡ณ', value: 'MN', label: 'Mongolia', timeZone: ['Asia/Ulaanbaatar', 'Asia/Hovd', 'Asia/Choibalsan'], }, { - icon: '๐Ÿ‡ฒ๐Ÿ‡ช', value: 'ME', label: 'Montenegro', timeZone: ['Europe/Podgorica'], }, { - icon: '๐Ÿ‡ฒ๐Ÿ‡ธ', value: 'MS', label: 'Montserrat', }, { - icon: '๐Ÿ‡ฒ๐Ÿ‡ฆ', value: 'MA', label: 'Morocco', timeZone: ['Africa/Casablanca'], }, { - icon: '๐Ÿ‡ฒ๐Ÿ‡ฟ', value: 'MZ', label: 'Mozambique', }, { - icon: '๐Ÿ‡ฒ๐Ÿ‡ฒ', value: 'MM', label: 'Myanmar', timeZone: ['Asia/Rangoon'], }, { - icon: '๐Ÿ‡ณ๐Ÿ‡ฆ', value: 'NA', label: 'Namibia', }, { - icon: '๐Ÿ‡ณ๐Ÿ‡ต', value: 'NP', label: 'Nepal', timeZone: ['Asia/Kathmandu'], }, { - icon: '๐Ÿ‡ณ๐Ÿ‡ฑ', value: 'NL', label: 'Netherlands', timeZone: ['Europe/Amsterdam'], }, { - icon: '๐Ÿ‡ณ๐Ÿ‡จ', value: 'NC', label: 'New Caledonia', }, { - icon: '๐Ÿ‡ณ๐Ÿ‡ฟ', value: 'NZ', label: 'New Zealand', timeZone: ['Pacific/Auckland', 'Pacific/Chatham'], }, { - icon: '๐Ÿ‡ณ๐Ÿ‡ฎ', value: 'NI', label: 'Nicaragua', timeZone: ['America/Managua'], }, { - icon: '๐Ÿ‡ณ๐Ÿ‡ช', value: 'NE', label: 'Niger', }, { - icon: '๐Ÿ‡ณ๐Ÿ‡ฌ', value: 'NG', label: 'Nigeria', timeZone: ['Africa/Lagos'], }, { - icon: '๐Ÿ‡ณ๐Ÿ‡ด', value: 'NO', label: 'Norway', timeZone: ['Europe/Oslo'], }, { - icon: '๐Ÿ‡ด๐Ÿ‡ฒ', value: 'OM', label: 'Oman', timeZone: ['Asia/Muscat'], }, { - icon: '๐Ÿ‡ต๐Ÿ‡ฐ', value: 'PK', label: 'Pakistan', timeZone: ['Asia/Karachi'], }, { - icon: '๐Ÿ‡ต๐Ÿ‡ธ', value: 'PS', label: 'Palestine', }, { - icon: '๐Ÿ‡ต๐Ÿ‡ฆ', value: 'PA', label: 'Panama', timeZone: ['America/Panama'], }, { - icon: '๐Ÿ‡ต๐Ÿ‡ฌ', value: 'PG', label: 'Papua New Guinea', }, { - icon: '๐Ÿ‡ต๐Ÿ‡พ', value: 'PY', label: 'Paraguay', timeZone: ['America/Asuncion'], }, { - icon: '๐Ÿ‡ต๐Ÿ‡ช', value: 'PE', label: 'Peru', timeZone: ['America/Lima'], }, { - icon: '๐Ÿ‡ต๐Ÿ‡ญ', value: 'PH', label: 'Philippines', timeZone: ['Asia/Manila'], }, { - icon: '๐Ÿ‡ต๐Ÿ‡ฑ', value: 'PL', label: 'Poland', timeZone: ['Europe/Warsaw'], }, { - icon: '๐Ÿ‡ต๐Ÿ‡น', value: 'PT', label: 'Portugal', timeZone: ['Europe/Lisbon', 'Atlantic/Madeira', 'Atlantic/Azores'], }, { - icon: '๐Ÿ‡ต๐Ÿ‡ท', value: 'PR', label: 'Puerto Rico', timeZone: ['America/Puerto_Rico'], }, { - icon: '๐Ÿ‡ถ๐Ÿ‡ฆ', value: 'QA', label: 'Qatar', timeZone: ['Asia/Qatar'], }, { - icon: '๐Ÿ‡ฒ๐Ÿ‡ฐ', value: 'MK', label: 'Macedonia', timeZone: ['Europe/Skopje'], }, { - icon: '๐Ÿ‡ท๐Ÿ‡ด', value: 'RO', label: 'Romania', timeZone: ['Europe/Bucharest'], }, { - icon: '๐Ÿ‡ท๐Ÿ‡บ', value: 'RU', label: 'Russia', timeZone: [ @@ -954,156 +806,129 @@ export const countryList = [ ], }, { - icon: '๐Ÿ‡ท๐Ÿ‡ผ', value: 'RW', label: 'Rwanda', timeZone: ['Africa/Kigali'], }, { - icon: '๐Ÿ‡ท๐Ÿ‡ช', value: 'RE', label: 'Rรฉunion', timeZone: ['Indian/Reunion'], }, { - icon: '๐Ÿ‡ธ๐Ÿ‡ฆ', value: 'SA', label: 'Saudi Arabia', timeZone: ['Asia/Riyadh'], }, { - icon: '๐Ÿ‡ธ๐Ÿ‡ณ', value: 'SN', label: 'Senegal', timeZone: ['Africa/Dakar'], }, { - icon: '๐Ÿ‡ท๐Ÿ‡ธ', value: 'RS', label: 'Serbia', timeZone: ['Europe/Belgrade'], }, { - icon: '๐Ÿ‡ธ๐Ÿ‡ฌ', value: 'SG', label: 'Singapore', timeZone: ['Asia/Singapore'], }, { - icon: '๐Ÿ‡ธ๐Ÿ‡ฐ', value: 'SK', label: 'Slovakia', timeZone: ['Europe/Bratislava'], }, { - icon: '๐Ÿ‡ธ๐Ÿ‡ฎ', value: 'SI', label: 'Slovenia', timeZone: ['Europe/Ljubljana'], }, { - icon: '๐Ÿ‡ธ๐Ÿ‡ด', value: 'SO', label: 'Somalia', timeZone: ['Africa/Mogadishu'], }, { - icon: '๐Ÿ‡ฟ๐Ÿ‡ฆ', value: 'ZA', label: 'South Africa', timeZone: ['Africa/Johannesburg'], }, { - icon: '๐Ÿ‡ธ๐Ÿ‡ธ', value: 'SS', label: 'South Sudan', }, { - icon: '๐Ÿ‡ช๐Ÿ‡ธ', value: 'ES', label: 'Spain', timeZone: ['Europe/Madrid', 'Africa/Ceuta', 'Atlantic/Canary'], }, { - icon: '๐Ÿ‡ฑ๐Ÿ‡ฐ', value: 'LK', label: 'Sri Lanka', timeZone: ['Asia/Colombo'], }, { - icon: '๐Ÿ‡ธ๐Ÿ‡ฉ', value: 'SD', label: 'Sudan', }, { - icon: '๐Ÿ‡ธ๐Ÿ‡ท', value: 'SR', label: 'Suriname', }, { - icon: '๐Ÿ‡ธ๐Ÿ‡ช', value: 'SE', label: 'Sweden', timeZone: ['Europe/Stockholm'], }, { - icon: '๐Ÿ‡จ๐Ÿ‡ญ', value: 'CH', label: 'Switzerland', timeZone: ['Europe/Zurich'], }, { - icon: '๐Ÿ‡ธ๐Ÿ‡พ', value: 'SY', label: 'Syria', timeZone: ['Asia/Damascus'], }, { - icon: '๐Ÿ‡น๐Ÿ‡ผ', value: 'TW', label: 'Taiwan', timeZone: ['Asia/Taipei'], }, { - icon: '๐Ÿ‡น๐Ÿ‡ฏ', value: 'TJ', label: 'Tajikistan', timeZone: ['Asia/Dushanbe'], }, { - icon: '๐Ÿ‡น๐Ÿ‡ฟ', value: 'TZ', label: 'Tanzania', }, { - icon: '๐Ÿ‡น๐Ÿ‡ญ', value: 'TH', label: 'Thailand', timeZone: ['Asia/Bangkok'], }, { - icon: '๐Ÿ‡น๐Ÿ‡ฑ', value: 'TL', label: 'Timor-Leste', }, { - icon: '๐Ÿ‡น๐Ÿ‡ฌ', value: 'TG', label: 'Togo', }, { - icon: '๐Ÿ‡น๐Ÿ‡ด', value: 'TO', label: 'Tonga', }, { - icon: '๐Ÿ‡น๐Ÿ‡ณ', value: 'TN', label: 'Tunisia', timeZone: ['Africa/Tunis'], }, { - icon: '๐Ÿ‡น๐Ÿ‡ท', value: 'TR', label: 'Turkey', timeZone: ['Europe/Istanbul'], @@ -1111,36 +936,35 @@ export const countryList = [ { value: 'TM', label: 'Turkmenistan', - icon: '๐Ÿ‡น๐Ÿ‡ฒ', + timeZone: ['Asia/Ashgabat'], }, { value: 'UG', label: 'Uganda', - icon: '๐Ÿ‡บ๐Ÿ‡ฌ', }, { value: 'UA', label: 'Ukraine', - icon: '๐Ÿ‡บ๐Ÿ‡ฆ', + timeZone: ['Europe/Kiev', 'Europe/Uzhgorod', 'Europe/Zaporozhye'], }, { value: 'AE', label: 'United Arab Emirates', - icon: '๐Ÿ‡ฆ๐Ÿ‡ช', + timeZone: ['Asia/Dubai'], }, { value: 'GB', label: 'United Kingdom', - icon: '๐Ÿ‡ฌ๐Ÿ‡ง', + timeZone: ['Europe/London'], }, { value: 'US', label: 'United States', - icon: '๐Ÿ‡บ๐Ÿ‡ธ', + timeZone: [ 'America/New_York', 'America/Detroit', @@ -1176,42 +1000,41 @@ export const countryList = [ { value: 'UY', label: 'Uruguay', - icon: '๐Ÿ‡บ๐Ÿ‡พ', + timeZone: ['America/Montevideo'], }, { value: 'UZ', label: 'Uzbekistan', - icon: '๐Ÿ‡บ๐Ÿ‡ฟ', + timeZone: ['Asia/Samarkand', 'Asia/Tashkent'], }, { value: 'VE', label: 'Venezuela', - icon: '๐Ÿ‡ป๐Ÿ‡ช', + timeZone: ['America/Caracas'], }, { value: 'VN', label: 'Vietnam', - icon: '๐Ÿ‡ป๐Ÿ‡ณ', + timeZone: ['Asia/Ho_Chi_Minh'], }, { value: 'YE', label: 'Yemen', - icon: '๐Ÿ‡พ๐Ÿ‡ช', + timeZone: ['Asia/Aden'], }, { value: 'ZM', label: 'Zambia', - icon: '๐Ÿ‡ฟ๐Ÿ‡ฒ', }, { value: 'ZW', label: 'Zimbabwe', - icon: '๐Ÿ‡ฟ๐Ÿ‡ผ', + timeZone: ['Africa/Harare'], }, ];