421 Commits

Author SHA1 Message Date
Valentin Lab
f6eb686fcd fix: display « tarif appliqué » on checkout page
The ``basketTotal`` partial expects ``$sale_channel`` as a top-level
view variable.  The cart page passed it correctly, but the checkout
page and the AJAX basket refresh only passed it nested inside
``$basket``.
2026-02-09 10:00:39 +01:00
Valentin Lab
2771a09a90 chg: use rich address partial in checkout with add/delete support
Replaces the simple address radio list in the checkout page with the
richer ``Shop.Customers.partials.addresses`` partial already used on
the profile edit page.  Customers can now choose among existing
addresses, add a new one on-the-fly, or delete an address directly
from the checkout flow.
2026-02-09 09:51:39 +01:00
Valentin Lab
4f3ab05757 fix: use absolute URL for logo in all mail templates
The logo ``src`` was ``/storage/photos/shares/logo.png`` — a relative
path to a file that doesn't exist.  Email clients cannot resolve
relative URLs.  Replaces with the absolute URL to the actual logo at
``https://boutique.jardinenvie.com/img/logo.png`` across all 4 mail
templates.
2026-02-09 09:37:49 +01:00
Valentin Lab
bf8e948ff3 new: display dynamic password rules checklist on registration and profile edit
Each rule (length, lowercase, uppercase, number, special character)
shows a live check/cross icon as the user types.  Also aligns
``handlePasswordChange`` server-side validation with the boilerplate
``Password`` rule (was only enforcing min 8 chars).
2026-02-09 09:29:29 +01:00
Valentin Lab
3d4496b253 fix: make phone number mandatory on registration and profile edit 2026-02-09 09:16:37 +01:00
Valentin Lab
b763915211 fix: enable scrolling in mobile navigation menu
The mobile category menu (``#navbarContentMobile``) could not scroll
when its content exceeded the viewport height, because no overflow
or height constraint was set on the collapse container.

Add ``max-height: calc(100vh - 60px)``, ``overflow-y: auto`` and
``-webkit-overflow-scrolling: touch`` to allow touch-scrolling
through the full category list on mobile devices.
2026-02-09 09:12:07 +01:00
Valentin Lab
1bf920c123 fix: correct PDF invoice address separator and translate label
- Replace ``<br>`` with ``\n`` in ``InvoicePDF::makeAddress()`` so
  dompdf renders line breaks instead of showing raw HTML tags.
- Translate ``order number`` to ``Numéro de commande`` in the custom
  fields passed to the invoice builder.

The amount-in-words was already in English because the container
lacked French ICU data (``icu-data-full``); that was fixed at
runtime, not in code.
2026-02-09 08:53:38 +01:00
Valentin Lab
ed3909782b fix: implement password change for shop customers
The password change form on the profile page (``Mes coordonnées``)
was scaffolded but never wired to any backend logic. The fields
``current-password``, ``new-password`` and ``new-password_confirmation``
were silently ignored by ``Customers::storeFull()``.

- Add ``handlePasswordChange()`` in ``CustomerController`` that
  validates current password, confirmation match, and 8-char minimum
  before hashing and saving.
- Remove ``required`` attribute from password fields so the form can
  submit for profile-only updates without filling password fields.
- Strip password fields from request data before passing to
  ``storeFull()`` to avoid Eloquent mass-assignment noise.
2026-02-09 08:36:29 +01:00
Valentin Lab
4fbbe991d9 fix: order confirmation email shows wrong payment method and incomplete address
The email template had "Carte de crédit" hardcoded regardless of the
actual payment method. The address blocks were also missing the
``address2`` and ``name`` fields.

- Add ``mode_paiement``, ``livraison_nom``, ``facturation_nom``,
  ``livraison_adresse2``, ``facturation_adresse2`` to
  ``ConfirmationCommande`` Mailable
- Migration to replace hardcoded payment label with
  ``{{mode_paiement}}`` and add ``address2`` fields in DB template
- Migration to add ``name`` fields before each address block
2026-02-09 07:23:50 +01:00
Valentin Lab
41d3294f74 chg: show "en attente de règlement" for check/wire orders in customer order list
When an order has status 0 ("En attente") and payment type is check
or wire transfer, the customer-facing order list now displays
"En attente de règlement" instead of the generic "En attente".
2026-02-09 06:53:55 +01:00
Valentin Lab
9c1f3dfed2 new: show payment-specific confirmation for check/wire orders
Display a tailored confirmation message when the customer pays by
check or wire transfer, including a warning about the 30-day
cancellation policy. The payment type is passed as a query parameter
so the message survives page reloads.

- Add ``getOrderConfirmedByCheckContent()`` and
  ``getOrderConfirmedByWireContent()`` to ``Contents`` repository
- Flash ``payment_type`` through redirect query parameter
- Add migration inserting content rows (id 10, 11)
- Update confirmed view with green checkmark and warning icon
2026-02-09 06:47:18 +01:00
Valentin Lab
e774113110 fix: prevent error 500 on search with no results
``getArticlesToSell()`` returned ``false`` when no articles matched,
causing ``collect(false)`` to produce ``[false]``. The view then
iterated over that single-element array and tried to access array
offsets on a boolean value.
2026-02-09 05:00:27 +01:00
Valentin Lab
1f4177cdb3 fix: missing translation for article's deletion confirmation modal 2025-12-14 21:43:46 +01:00
Valentin Lab
66c035ef9a new: add `duplicate` button for articles 2025-12-14 21:25:42 +01:00
Valentin Lab
fefd6209ac new: add tooltip to the existing links towards "offre" and "tarif" 2025-12-13 22:26:40 +01:00
Valentin Lab
f5ec254c0e new: add a direct link toward article's admin edit form from the public article page 2025-12-13 22:18:42 +01:00
Valentin Lab
a43e82f3d9 new: remove the "previsualisation" side pane from the "offre" admin edit form 2025-12-13 22:07:16 +01:00
Valentin Lab
65460fd9f1 new: add a link to public article page from article and offres admin edit form 2025-12-13 22:04:56 +01:00
Valentin Lab
d9ae84310d fix: avoid sharing route to JSON data to offers
No paths leads there, and there are no reason to keep this route.
2025-12-13 22:02:46 +01:00
Valentin Lab
2fc091d754 chg: make the herited info pane closed by default in admin edit form for articles 2025-12-13 21:46:45 +01:00
Valentin Lab
7887e2d532 new: make all forms have a cancel/save button on the top also 2025-12-13 21:43:40 +01:00
Valentin Lab
f92e175731 fix: prevent error 500 upon displaying empty 'Rayons' 2025-12-13 21:43:40 +01:00
Valentin Lab
6bb910bb54 fix: make 'Rayons' title adaptable to screen width 2025-12-13 21:43:40 +01:00
Valentin Lab
1db3725fb2 new: make the menu visible on mobile 2025-12-13 21:43:40 +01:00
Valentin Lab
ebdf0c0d8e fix: repair tinymce implementation 2025-12-13 20:10:19 +01:00
Valentin Lab
22ebcb102f fix: prevent error message about missing css 2025-12-13 20:09:56 +01:00
Valentin Lab
cc3d4d3e32 chg: put article description after variety description on product public page 2025-12-13 18:21:37 +01:00
Valentin Lab
ef1964d472 fix: prevent error 500 on article pages 2025-11-03 11:35:16 +01:00
Valentin Lab
abb32e32b9 fix: add "Bientôt disponible" box on public product page without prices 2025-11-03 11:27:47 +01:00
Valentin Lab
8c29459489 new: make the debug info available to all backoffice users with helpful links 2025-11-03 11:23:58 +01:00
Valentin Lab
accb052f5c fix: repair price appearance on all articles 2025-11-03 11:23:24 +01:00
Valentin Lab
d5f095b5e5 fix: remove non-visible article from research results 2025-11-03 09:20:53 +01:00
Valentin Lab
fd628f3f95 fix: prevent error 500 on creation of new backoffice user 2025-11-03 09:09:36 +01:00
Valentin Lab
a10f0b35d9 fix: focus invalid field on error in article form 2025-10-15 14:49:41 +02:00
Valentin Lab
858421a9eb fix: prevent broken link upon thumbnail in variety list when having uploaded a PNG file 2025-10-15 14:48:51 +02:00
Valentin Lab
158bc4fd57 fix: provide correct temporary directory outside of `vendor/` 2025-10-15 14:08:16 +02:00
Valentin Lab
b7e3eefed6 new: allow to delete seuil lines in price-list's pice modal 2025-10-15 13:17:54 +02:00
Valentin Lab
67e4346c68 fix: pkg: do not create bogus `{cache,views,sessions}` directory in prod export 2025-10-15 12:46:01 +02:00
Valentin Lab
9ce62e82e5 fix: allow saving list-price's price seuil if seuil is unset or 0 2025-10-15 12:22:36 +02:00
Valentin Lab
7e93219774 fix: allow to re-use a deleted ref in articles 2025-10-15 12:05:16 +02:00
Valentin Lab
29f46b7287 fix: enable saving in price-list's price edit modal 2025-10-15 11:57:38 +02:00
Valentin Lab
1f02c932a0 fix: make varieties creation form avoid error 500 on save 2025-10-15 11:57:38 +02:00
Valentin Lab
7d8bd8c372 fix: make form submit apply modification on existing article 2025-10-15 11:57:38 +02:00
Valentin Lab
f4bd4ddf24 fix: prevent err 500 upon species edit form opening 2025-10-15 11:57:28 +02:00
Valentin Lab
1f7098d55b fix: repair link made by `asset() in blade template when working on http`
The forcing is useless, we are forcing links through many other
ways. I need to test aspects of deployments on my laptop to mimic
production deployment without this hassle.
2025-10-11 05:32:01 +02:00
Valentin Lab
1867e75177 new: doc: added `AGENTS.md` and small addition of a french paragraph 2025-10-10 08:20:13 +02:00
Valentin Lab
d502882052 fix: add delivery cost on load if delivery is selected 2025-10-05 12:38:19 +02:00
Valentin Lab
a5b2196b32 fix: make the selected channel apply changes to product each time 2025-10-05 12:33:08 +02:00
Valentin Lab
cc8dfa29b4 fix: display only delivery types that have a price and auto-select first 2025-10-05 10:09:03 +02:00
Valentin Lab
62bce92d6d fix: make delivery option on checkout stick to the current sale channel 2025-10-05 09:56:33 +02:00
Valentin Lab
8d130b9741 new: add channel management 2025-10-05 09:39:27 +02:00
Valentin Lab
2d7436a12b fix: make sale channel description field editable 2025-10-05 05:26:20 +02:00
Valentin Lab
f25a62ed26 new: make admin delivery edition can toggle off public and active states 2025-10-05 03:32:08 +02:00
Valentin Lab
36764f2647 fix: make save button avoid error 500 in delivery method admin page 2025-10-05 03:28:03 +02:00
Valentin Lab
e37cad6699 new: make the eye icon work to see an invoice in admin customer view 2025-10-04 15:37:28 +02:00
Valentin Lab
ae7f8ed2c9 fix: remove 404 about javascript file in admin console 2025-10-04 14:39:07 +02:00
Valentin Lab
a3a86f4b2f new: keep cart when login in 2025-10-04 14:13:48 +02:00
Valentin Lab
9c081574c8 new: make click in choices of search box load the page of the product 2025-10-04 13:54:21 +02:00
Valentin Lab
11edccad02 fix: make invoices creation resistant to missing address if this still happens 2025-10-04 12:55:11 +02:00
Valentin Lab
7c796802be new: make invoice still keep the old addresses when their address gets deleted in profile 2025-10-04 12:39:13 +02:00
Valentin Lab
5cc43bc889 fix: make the button to add an address unusable when the address form is open 2025-10-04 12:19:24 +02:00
Valentin Lab
f094411f10 new: add persistence of default address selection 2025-10-04 11:59:57 +02:00
Valentin Lab
ccc477f291 new: display the default address checkbox on profile load 2025-10-04 11:59:57 +02:00
Valentin Lab
7217d945a3 fix: make the address appear when added 2025-10-04 11:59:57 +02:00
Valentin Lab
9185269874 fix: prevent deleting last address for each kind 2025-10-04 11:59:57 +02:00
Valentin Lab
e42e3b4c0d fix: prevent 404 when deleting an adress 2025-10-04 11:06:43 +02:00
Valentin Lab
a7ae946797 fix: prevent error 500 on profile edition 2025-10-04 10:51:41 +02:00
Valentin Lab
7a189abf0b fix: move `build directory to resources/shop` 2025-10-04 10:13:38 +02:00
Valentin Lab
34fc1c33bf fix: repair favicon links and provide one from https://www.jardinenvie.com 2025-10-04 09:41:15 +02:00
Valentin Lab
61e34b4f4e fix: finalize payments and clear cart after Paybox success
This captures the Paybox verification flow, duplicate-payment guard, and cart cleanup.
2025-10-04 09:17:53 +02:00
Valentin Lab
7fe2770d45 fix: do not call debugbar if not available (when in prod) 2025-09-29 11:32:19 +02:00
Valentin Lab
0479ae25f8 new: add docker build reproducible code 2025-09-29 11:22:02 +02:00
Valentin Lab
0a45b0c71f fix: pkg: repair grunt 2025-09-25 13:58:09 +02:00
ludo
1cc6cc879e fix: fix 2025-04-21 10:57:31 +02:00
ludo
adbba79bd2 fixes 2025-03-18 13:06:02 +01:00
ludo
592402a6c1 fixes 2025-02-15 12:12:42 +01:00
ludo
d1cc62c9b1 remove browsershot 2025-01-03 16:05:05 +01:00
ludo
a76d81c437 fix contents 2025-01-03 16:04:19 +01:00
ludo
2e71f17856 fix calculation on indirect articles by tags 2025-01-03 14:45:23 +01:00
ludo
df78126b12 refactor scopes 2025-01-03 14:09:22 +01:00
ludo
befaa40b48 fixes 2025-01-03 03:46:45 +01:00
ludo
b3fbfc38e7 fixes 2024-06-17 21:56:42 +02:00
ludo
ee60bac538 fixes on addresses 2024-04-29 22:01:45 +02:00
ludo
c63bb762ed fix on producter 2024-03-11 19:31:22 +01:00
ludo
44bfe7d09c fix on tags 2024-03-11 19:02:57 +01:00
ludo
5db7438c27 fixes on auth customers 2024-03-11 18:48:25 +01:00
ludo
2227242704 fix route 2024-03-11 18:38:23 +01:00
ludo
7b7295aed1 fixes 2024-03-03 22:52:00 +01:00
ludo
26ca3eb3ca Core classes removed by error 2024-03-03 14:53:54 +01:00
ludo
139aeb8074 fixes 2024-03-03 14:44:35 +01:00
ludo
cc411cba68 minor fixes 2024-02-23 08:35:41 +01:00
ludo
fb6da523fa cleaning day 2024-02-22 21:28:33 +01:00
ludo
64a218afc2 coding style 2024-02-22 19:35:51 +01:00
ludo
722ea43bc2 add monitoring 2024-02-22 19:33:56 +01:00
ludo
869b148e20 add seo 2024-02-19 23:51:32 +01:00
ludo
15a6621a56 add shop cart storage 2024-02-07 21:54:58 +01:00
ludo
601b758179 fixes 2024-02-07 21:11:17 +01:00
ludo
e559c785c2 add health route 2024-02-07 21:07:19 +01:00
ludo
329643ce3b add health route 2024-02-07 21:05:48 +01:00
ludo
7bb38071ef add health 2024-02-07 21:01:02 +01:00
ludo
ee9979f547 fix 2024-02-07 20:43:58 +01:00
ludo
c4fda18356 move autocomplete for customer 2024-02-07 20:41:04 +01:00
ludo
8c6e10fb3b force https on docker 2024-02-07 20:31:51 +01:00
ludo
5a400aaedd add restore backup 2024-02-05 22:36:45 +01:00
ludo
23fb8a79ac fix on worker with php 8.2 2024-02-05 22:04:17 +01:00
ludo
67e439f420 fix ports 2024-02-05 21:48:07 +01:00
ludo
df377c4f3f fix on docker 2024-02-05 21:38:40 +01:00
ludo
69264dcf80 fix on docker 2024-02-05 21:34:29 +01:00
ludo
308b6cb349 add watermark on zoom, update display of article nature on shelve (change icon to text 2024-02-04 22:09:53 +01:00
ludo
4c6f9b3b61 add datatbles for invoices, add pdf icon, refactor icons components, add autocomplete on search, adapt searching to meilisearch 2024-02-04 02:51:38 +01:00
ludo
067532b6fc add new search engine 2024-01-31 23:45:58 +01:00
ludo
8eb3104b2a add metrics 2024-01-30 23:24:00 +01:00
ludo
1bc9bf9fe9 add migration to catrt storage, update SCOUT 2024-01-29 23:45:55 +01:00
ludo
53ad10eefa add new metrics, graph metrics, prepare basket to storage 2024-01-29 23:44:49 +01:00
ludo
9fcc81f4d9 add graphs for stats 2024-01-29 22:39:57 +01:00
ludo
75107285e7 Merge branch 'master' of https://gitlab.huma.net/ludo/opensem 2024-01-28 20:28:39 +01:00
ludo
502b71617a fix articles datatables, enhance statistics 2024-01-28 19:56:13 +01:00
Ludovic CANDELLIER
ca474ddadb restrict ports to be compatible with legacy version 2024-01-23 00:01:15 +01:00
Ludovic CANDELLIER
fbe9633651 add certbot 2024-01-22 23:05:39 +01:00
Ludovic CANDELLIER
1677ec6b03 add slave1 2024-01-22 23:04:23 +01:00
Ludovic CANDELLIER
1533c18c54 remove highcharts 2024-01-22 23:01:33 +01:00
Ludovic CANDELLIER
fc5a3186bf remove old models 2024-01-22 22:52:03 +01:00
ludo
0bb75125a7 update 2024-01-22 22:50:56 +01:00
ludo
af5fc8d0ee Merge branch 'master' of https://gitlab.huma.net/ludo/opensem 2024-01-22 22:47:39 +01:00
ludo
17e322cd88 update migrations 2024-01-22 22:47:20 +01:00
Ludovic CANDELLIER
8ceab7e9f5 update docker 2024-01-22 22:14:36 +01:00
ludo
116f289285 refactoring on Articles, minor fixes 2024-01-21 11:42:42 +01:00
ludo
560ef61c9f render invoice in pdf 2024-01-07 23:16:29 +01:00
ludo
4df8628a3e fixing styles 2024-01-07 20:42:37 +01:00
ludo
5144c1f7fd fix devops error 2024-01-05 01:30:46 +01:00
ludo
90b0af5b2d coding styles 2024-01-04 15:54:18 +01:00
ludo
03027cde01 fix on payment by cb 2024-01-04 15:43:02 +01:00
ludo
8a463e7b9e update payments and vat mentions 2023-12-21 23:04:42 +01:00
ludo
643c26d549 fix parameters for invoices 2023-12-21 16:57:40 +01:00
ludo
ee64ae0be7 fix greedy replace 2023-12-12 21:40:24 +01:00
ludo
e0d8106078 minor fixes 2023-12-11 21:09:48 +01:00
ludo
df65516b36 adjust deliveries by customer 2023-12-11 21:07:49 +01:00
ludo
25b78f3380 change homepages to contents, add new methods to deliveries and sale_channels by customer 2023-12-09 23:55:50 +01:00
ludo
2a429e4163 minor fixes 2023-12-09 21:02:28 +01:00
ludo
b5da5fc881 enhance invoice display 2023-12-03 02:20:41 +01:00
ludo
ec509df665 better management of shipping and basket summary display 2023-12-03 00:40:47 +01:00
ludo
4bcfc7bc6d add basket on merchandise form 2023-11-25 20:42:15 +01:00
ludo
9949ae95cf update 2023-11-25 20:23:21 +01:00
ludo
82b864768e fix invoice payment 2023-11-25 19:33:41 +01:00
ludo
34f0b2796f add payments by invoice 2023-11-25 16:21:35 +01:00
ludo
731c31a58c fix on merchandises with validator 2023-11-25 16:21:02 +01:00
ludo
9b18531c83 try to refresh total with recalculation by delivery and delivery_type 2023-11-16 00:00:05 +01:00
ludo
2ebdc5f16b add shipping to order, methods to calculate, little refactoring 2023-11-15 23:20:42 +01:00
ludo
04df068931 fixes 2023-11-14 00:25:58 +01:00
ludo
86b8156e38 little refactoring 2023-11-14 00:11:31 +01:00
ludo
b86b043604 add validator, optimizations 2023-11-13 23:03:12 +01:00
ludo
23ac0cedad fix on weight 2023-11-13 00:40:41 +01:00
ludo
9f90f983ab Fix on addresses 2023-11-13 00:02:21 +01:00
ludo
4ce3d528dd fix on product null 2023-11-08 17:07:53 +01:00
ludo
e42ac75ff7 fix if no icon 2023-11-08 16:55:26 +01:00
ludo
746cf661ce fix basket 2023-10-31 17:05:41 +01:00
ludo
a9432bd3c1 add filter not collapsed if filter is on 2023-10-17 17:47:25 +02:00
ludo
f3b9db1a6f fix on customer auth, fix filters on shelves, refactor for article_nature, add slug 2023-10-17 17:20:30 +02:00
Ludovic CANDELLIER
50d5d6944d Supprimer le fichier .env du répertoire docker 2023-09-13 23:10:04 +02:00
Ludovic CANDELLIER
bc7880b242 add docker version 2023-09-13 23:04:55 +02:00
Ludovic CANDELLIER
5f215cef81 coding style 2023-09-13 22:53:37 +02:00
Ludovic CANDELLIER
da48f41ec0 minor fix on updating status of order, upgrade datatables 2023-09-13 22:17:49 +02:00
Ludovic CANDELLIER
eda2bbf1db adapt display of article_natures 2023-09-12 23:39:28 +02:00
Ludovic CANDELLIER
5e161745bb adapt display of article natures by disponibility of offers in shelve 2023-09-12 23:27:56 +02:00
Ludovic CANDELLIER
a29faabbf2 add methods to get icon on article natures 2023-09-12 23:00:36 +02:00
Ludovic CANDELLIER
470560efb6 refactor datatables admin 2023-08-29 23:31:15 +02:00
Ludovic CANDELLIER
b1d16a7871 refactoring of admin datatables 2023-08-29 22:56:37 +02:00
Ludovic CANDELLIER
496274b4f4 fix orders datatables on profile, fix deliveries for profile (public & active) 2023-08-29 22:15:37 +02:00
Ludovic CANDELLIER
7addea00a2 fixes on login 2023-08-28 22:58:11 +02:00
Ludovic CANDELLIER
f721422abc fixes 2023-08-28 21:48:04 +02:00
Ludovic CANDELLIER
1cba52bb6d fixes, add weight 2023-08-01 21:55:17 +02:00
Ludovic CANDELLIER
902604b9cd fixes 2023-07-16 18:09:40 +02:00
Ludovic CANDELLIER
b8c31f6049 fix shipping 2023-07-16 17:54:44 +02:00
Ludovic CANDELLIER
1675745e2a fixes on bad pint 2023-07-16 15:07:15 +02:00
Ludovic CANDELLIER
0879b0abf0 add shipping rules 2023-07-16 14:45:42 +02:00
Ludovic CANDELLIER
72a7b270f9 add mail tracker 2023-07-04 23:33:13 +02:00
Ludovic CANDELLIER
3d16580bc8 add multiple addresses on customer edition 2023-07-04 23:32:41 +02:00
Ludovic CANDELLIER
d6ab6c73e2 rollback to retrieve buttons 2023-07-04 19:24:31 +02:00
Ludovic CANDELLIER
9f9b7173d7 fixes on mail templates, change order edit layout, add DeliveryTypes, DeliveryTypeCalculations & DeliveryPackages 2023-05-24 23:30:29 +02:00
Ludovic CANDELLIER
c677dbd5fa add package to date scopes & browsershot 2023-05-09 21:51:48 +02:00
Ludovic CANDELLIER
313525a25b finish implementing mails 2023-04-17 00:27:03 +02:00
Ludovic CANDELLIER
24e518fffe add methods to detect distinct product type et article nature on shelve 2023-04-01 22:01:15 +02:00
Ludovic CANDELLIER
7d1a34a12e simplify variables names for templates, refactor to be multi-model 2023-03-28 00:17:04 +02:00
Ludovic CANDELLIER
938d6a9cbd Enhance modal 2023-03-27 23:12:57 +02:00
Ludovic CANDELLIER
0a6b90b434 fix on article based on old merchandise 2023-03-27 21:05:37 +02:00
Ludovic CANDELLIER
20b3521c72 add basket on rows, uniformize baskets 2023-03-21 23:16:47 +01:00
Ludovic CANDELLIER
06c68dd223 fix recalculation on basket, fix quick add on basket 2023-03-21 22:41:48 +01:00
Ludovic CANDELLIER
4f9f9b296d cosmetic fixes, enhance profile, fix mails, ... 2023-03-14 23:33:14 +01:00
Ludovic CANDELLIER
7454411d27 fix active hierarchy in menu and megamenu 2023-03-14 21:46:57 +01:00
Ludovic CANDELLIER
4e69399309 add datatables on orders 2023-02-28 08:42:53 +01:00
Ludovic CANDELLIER
3745abc90b remove buggy package for logging mail / incompatible with laravel 9 2023-02-27 23:16:45 +01:00
Ludovic CANDELLIER
0828ac3377 quick add to basket on shelves 2023-02-27 23:16:15 +01:00
Ludovic CANDELLIER
b3d16a06b0 adapt to laravel 9 2023-02-27 23:15:16 +01:00
Ludovic CANDELLIER
753be00a1e fix modal 2023-02-27 22:02:19 +01:00
Ludovic CANDELLIER
a63618a753 fixe menu 2023-02-27 21:45:28 +01:00
Ludovic CANDELLIER
3287caac54 fix typo 2023-02-17 22:27:07 +01:00
Ludovic CANDELLIER
312f1f4e3d fix typo on Admin 2023-02-17 22:22:04 +01:00
Ludovic CANDELLIER
39e3407ea1 fix on sale_channel get_default 2023-02-17 22:08:13 +01:00
Ludovic CANDELLIER
03a52d504b fix on sale_channel get_default 2023-02-17 22:07:43 +01:00
Ludovic CANDELLIER
c1d7f3fe10 fix if default sale channel empty 2023-02-17 21:47:36 +01:00
Ludovic CANDELLIER
8e571de523 Fix on invoices, add delivery reference, wip on dashboard concurrency requests designed on template 2023-02-17 00:05:03 +01:00
Ludovic CANDELLIER
878ec7a8f2 remove debug mode 2023-02-14 00:30:30 +01:00
Ludovic CANDELLIER
06107cb8fc Add overlay on css, adapt shelves, fix bienvenue mail on laravel 9 methods 2023-02-14 00:20:00 +01:00
Ludovic CANDELLIER
10cebd0955 upgrade to version 9 2023-02-13 23:47:01 +01:00
Ludovic CANDELLIER
0ecc7c73c7 enhance components, add mailtemplate, add traits for translations, for stats 2023-02-13 22:52:39 +01:00
Ludovic CANDELLIER
f2f4788ce1 change icons, css, add routing to merchandise, add mail templater, fixes 2023-02-12 23:34:48 +01:00
Ludovic CANDELLIER
8313e25f2e fix megamenu on hover for yellow background 2023-02-12 00:11:25 +01:00
Ludovic CANDELLIER
cafd0a49e7 change registration or connection in order page, change filter on shelve page, add new api to get article_nature by product_type, css fixes 2023-02-10 23:11:48 +01:00
Ludovic CANDELLIER
405effe43e fix on empty images on merchandise 2023-02-09 23:49:17 +01:00
Ludovic CANDELLIER
4914e0c9c9 add cookie consent, change search for product_type empty 2023-02-07 23:25:59 +01:00
Ludovic CANDELLIER
909336bb8b fix debug mode 2023-02-06 23:52:20 +01:00
Ludovic CANDELLIER
60682f2295 remove order alphabetically because is against reordering tree 2023-02-06 23:48:50 +01:00
Ludovic CANDELLIER
2fc88c6163 fix col, because bug on 2 lines for long menu 2023-02-05 23:22:44 +01:00
Ludovic CANDELLIER
6c60d9a148 fix 2023-02-05 23:16:00 +01:00
Ludovic CANDELLIER
cb488383e0 fix css and html structure 2023-02-05 22:57:47 +01:00
Ludovic CANDELLIER
32291dc44a enhance css 2023-02-05 21:40:05 +01:00
Ludovic CANDELLIER
0123885e03 enhance addresses 2023-01-01 22:45:27 +01:00
Ludovic CANDELLIER
72d989f692 fix on login 2023-01-01 21:24:40 +01:00
Ludovic CANDELLIER
66e0197b50 fix 2022-12-29 19:23:28 +01:00
Ludovic CANDELLIER
bfd30b668e fixes 2022-12-29 17:13:43 +01:00
Ludovic CANDELLIER
28c200fd9f fix design, add addresses 2022-12-29 16:16:09 +01:00
Ludovic CANDELLIER
7819a8e11b fix 2022-12-22 18:17:50 +01:00
Ludovic CANDELLIER
b6821d52a7 fix 2022-12-22 18:16:25 +01:00
Ludovic CANDELLIER
b4d8bab385 fix 2022-12-22 12:10:44 +01:00
Ludovic CANDELLIER
d72dfa5b6b fix description on shelves 2022-12-22 01:17:01 +01:00
Ludovic CANDELLIER
35fcc992ae fix cache css 2022-12-22 01:12:40 +01:00
Ludovic CANDELLIER
0feebca7e0 fixes 2022-12-22 01:09:11 +01:00
Ludovic CANDELLIER
5151f393be fix layout 2022-12-21 18:33:15 +01:00
Ludovic CANDELLIER
73ed46bc28 remove dump 2022-11-24 17:51:37 +01:00
Ludovic CANDELLIER
533b63f8fb fix routes 2022-11-24 17:48:08 +01:00
Ludovic CANDELLIER
966e687509 fix on better routes 2022-11-24 17:41:57 +01:00
Ludovic CANDELLIER
ee1511962b fix 2022-11-24 16:07:40 +01:00
Ludovic CANDELLIER
d62cad1725 fixes on invoices relations and revisions 2022-11-20 00:21:38 +01:00
Ludovic CANDELLIER
0cf5569a4c Merge branch 'master' of https://gitlab.huma.net/ludo/opensem 2022-11-19 23:43:39 +01:00
Ludovic CANDELLIER
73763bb146 fix editing orders 2022-11-19 23:43:12 +01:00
Ludovic CANDELLIER
caee665758 reorganize 2022-11-11 13:24:24 +01:00
Ludovic CANDELLIER
7df2421373 restart 2022-11-11 13:05:40 +01:00
Ludovic CANDELLIER
f89acd9399 [WIP] Working on orders & invoices 2022-08-19 22:04:44 +02:00
Ludovic CANDELLIER
c22b10dd10 [WIP] Finish the order process 2022-08-18 18:20:44 +02:00
Ludo
5cd48c03f9 Add new file 2022-08-02 14:21:10 +00:00
Ludovic CANDELLIER
d423fce4f5 fixes 2022-07-04 00:35:43 +02:00
Ludovic CANDELLIER
573e98a2ce [WIP] Order process with interactive methods 2022-07-03 23:36:33 +02:00
Ludovic CANDELLIER
06cfb92757 [WIP] Order process 2022-07-03 22:38:08 +02:00
Ludovic CANDELLIER
bcb3e15f33 fix on array 2022-07-03 10:48:42 +02:00
Ludovic CANDELLIER
b392b426d5 fix 2022-07-03 09:31:45 +02:00
Ludovic CANDELLIER
e435752484 begin order form with registration 2022-06-26 23:33:39 +02:00
Ludovic CANDELLIER
c2fd71e3d1 fix selector on filters 2022-06-22 22:40:04 +02:00
Ludovic CANDELLIER
e9ab7173f8 Refactor article getter for descriptions & tags, minor fixes on tags 2022-06-22 22:28:18 +02:00
Ludovic CANDELLIER
6b1cc0f045 change for description by level of data 2022-06-16 22:59:26 +02:00
Ludovic CANDELLIER
123b951538 missing file 2022-06-14 22:29:18 +02:00
Ludovic CANDELLIER
9710a7017a fixes 2022-06-14 22:24:24 +02:00
Ludovic CANDELLIER
32044118f3 new routes 2022-06-13 23:30:06 +02:00
Ludovic CANDELLIER
b37321daf8 [WIP] begin of new display for article, shelves 2022-06-13 23:29:05 +02:00
Ludovic CANDELLIER
e31978b1e3 change display on categories 2022-05-31 23:21:04 +02:00
Ludovic CANDELLIER
5b74c93b2e Works for friday & saturday 2022-05-29 00:46:04 +02:00
Ludovic CANDELLIER
352b109e87 Add new component, add flags on filter 2022-05-09 23:14:50 +02:00
Ludovic CANDELLIER
a70e8c39cf modify filter calculation 2022-05-09 22:33:18 +02:00
Ludovic CANDELLIER
439a339027 fixes on merchandise 2022-05-02 08:34:40 +02:00
Ludovic CANDELLIER
2ee339a022 fix on empty picture 2022-04-25 23:43:25 +02:00
Ludovic CANDELLIER
84063d2f72 Fix on merchandises 2022-04-25 23:31:24 +02:00
Ludovic CANDELLIER
61a52ef330 Fixes on available offers in category childrens for building menu 2022-04-25 22:36:43 +02:00
Ludovic CANDELLIER
c9bf18d87d fix on shelve with available offers 2022-04-25 21:59:53 +02:00
Ludovic CANDELLIER
328d791b87 fix on empty shelve 2022-04-25 20:46:18 +02:00
Ludovic CANDELLIER
e22a541342 fix 2022-04-25 20:29:50 +02:00
Ludovic CANDELLIER
d8bf91da54 Add plus on products 2022-04-25 20:02:28 +02:00
Ludovic CANDELLIER
5747b93952 Add producers 2022-04-25 11:07:02 +02:00
Ludovic CANDELLIER
570374bab7 Add new data in getBasket context 2022-04-24 23:49:28 +02:00
Ludovic CANDELLIER
416c724ad1 Add new data in getBasket context 2022-04-24 23:20:52 +02:00
Ludovic CANDELLIER
5d68e8787a 'fixes' 2022-04-24 22:07:31 +02:00
Ludovic CANDELLIER
e4672a42d7 fixes 2022-04-22 02:32:53 +02:00
Ludovic CANDELLIER
94234218d6 Filters collapsed, customer auth and register, fix on basket recalculation 2022-04-20 00:16:16 +02:00
Ludovic CANDELLIER
a12dd0c653 fix on basket 2022-04-17 00:16:36 +02:00
Ludovic CANDELLIER
fe1e14d2c0 Multi-images component, refactoring medias functions 2022-04-16 19:33:17 +02:00
Ludovic CANDELLIER
1dc815bf39 Add count function for images herited 2022-04-16 13:58:09 +02:00
Ludovic CANDELLIER
2d111605f2 Add management of merchandises, enhance imageable trait 2022-04-16 11:40:19 +02:00
Ludovic CANDELLIER
68a13b7a58 Build form for merchandise 2022-04-14 23:41:58 +02:00
Ludovic CANDELLIER
c2ef0c7b35 Add merchandise, fix articletosell with src for images 2022-04-14 23:20:09 +02:00
Ludovic CANDELLIER
9c2b9cf02e wip 3d 2022-04-13 23:49:48 +02:00
Ludovic CANDELLIER
6e133246cf enhance add to basket 2022-04-01 00:11:15 +02:00
Ludovic CANDELLIER
eff2cb21c7 Add homepage 2022-03-30 22:23:57 +02:00
Ludovic CANDELLIER
c50bd2aead fix 2022-03-30 18:02:19 +02:00
Ludovic CANDELLIER
dd0dddb1ff fix 2022-03-30 18:01:01 +02:00
Ludovic CANDELLIER
34d273e510 Fix slider 2022-03-30 17:54:50 +02:00
Ludovic CANDELLIER
f1f1a8bc70 fix 2022-03-30 16:43:10 +02:00
Ludovic CANDELLIER
c1a0d449be fix 2022-03-30 16:42:46 +02:00
Ludovic CANDELLIER
957d033e2d fix 2022-03-30 16:38:49 +02:00
Ludovic CANDELLIER
dde59a0c90 Fixes on grouping 2022-03-30 16:34:08 +02:00
Ludovic CANDELLIER
1dced19068 Fix 2022-03-30 16:03:26 +02:00
Ludovic CANDELLIER
1c5db3c654 fix 2022-03-30 16:01:12 +02:00
Ludovic CANDELLIER
c8cd3e4fa6 fix 2022-03-30 15:59:31 +02:00
Ludovic CANDELLIER
e312572bcc Fix on tarif with sale_channel 2022-03-30 15:46:10 +02:00
Ludovic CANDELLIER
c4bb4fdd59 Try to fix price_lists by sale_channel 2022-03-30 00:36:58 +02:00
Ludovic CANDELLIER
ff18a0f5bf fix empty 2022-03-24 15:07:55 +01:00
Ludovic CANDELLIER
573e4dc6cb fix empty 2022-03-24 15:06:51 +01:00
Ludovic CANDELLIER
2a98b24bc1 Add calculations on basket 2022-03-24 14:57:39 +01:00
Ludovic CANDELLIER
c357ea932a Add toggle for homepage 2022-03-24 10:08:23 +01:00
Ludovic CANDELLIER
c65056531c Manage homepage by article, modify article template, enhance basket (add selector) 2022-03-24 00:48:26 +01:00
Ludovic CANDELLIER
ddc5f2664c Add variations, slider, fix cart ... 2022-03-21 21:52:12 +01:00
Ludovic CANDELLIER
0eaa11b2a9 Fix on default sale channel 2022-03-07 22:58:40 +01:00
Ludovic CANDELLIER
aa50f908ba inherited description 2022-03-07 22:50:33 +01:00
Ludovic CANDELLIER
719f89cc50 Fix name with spaces 2022-03-07 22:19:57 +01:00
Ludovic CANDELLIER
14931bc5e6 Add method to get image with parent for article 2022-03-07 22:10:59 +01:00
Ludovic CANDELLIER
30876ba67d add constaint on stock 2022-03-07 20:17:50 +01:00
Ludovic CANDELLIER
5794cbb045 Fixes on articles by sale_channel 2022-02-22 23:03:29 +01:00
Ludovic CANDELLIER
27893eaa7e fix empty 2022-02-22 22:46:10 +01:00
Ludovic CANDELLIER
53d1307837 fix 2022-02-22 22:41:50 +01:00
Ludovic CANDELLIER
f8c686caa3 Add prices and filtering by sale_channel with default 2022-02-22 22:32:46 +01:00
Ludovic CANDELLIER
3c3481b39d Add new search of articles 2022-02-21 09:09:36 +01:00
Ludovic CANDELLIER
c75f580ad2 Add price taxed 2022-02-20 21:59:19 +01:00
Ludovic CANDELLIER
ea53cb4c8a change construction of articles/offers 2022-02-20 21:38:21 +01:00
Ludovic CANDELLIER
4b2c431ee9 fix 2022-02-18 09:48:02 +01:00
Ludovic CANDELLIER
655f502279 Fix visible 2022-02-18 09:46:20 +01:00
Ludovic CANDELLIER
6556127cc8 Fix save 2022-02-17 16:27:44 +01:00
Ludovic CANDELLIER
b3a2ad31b2 fix empty 2022-02-17 12:42:46 +01:00
Ludovic CANDELLIER
7d6c7ca36d fix empty 2022-02-17 12:30:17 +01:00
Ludovic CANDELLIER
5701985734 active visible on shelves 2022-02-17 12:27:20 +01:00
Ludovic CANDELLIER
9e064bcd74 fix 2022-02-17 12:14:22 +01:00
Ludovic CANDELLIER
f0386269e6 fix on new tariff_unities 2022-02-17 12:03:01 +01:00
Ludovic CANDELLIER
90d683f7ed Fix old methods 2022-02-17 11:52:19 +01:00
Ludovic CANDELLIER
7723b475ac change old route 2022-02-17 11:44:47 +01:00
Ludovic CANDELLIER
d8ce8f5259 Fix save 2022-02-17 11:42:11 +01:00
Ludovic CANDELLIER
48359525bf Upgrade package category and dependencies for php8.0 2022-02-17 11:38:19 +01:00
Ludovic CANDELLIER
0399d90ca7 fix unsetted 2022-02-17 09:44:30 +01:00
Ludovic CANDELLIER
32362d74dc Fix 2022-02-16 09:42:08 +01:00
Ludovic CANDELLIER
b90d3ba3f2 Fix categories & varieties 2022-02-16 09:17:51 +01:00
Ludovic CANDELLIER
642fd52d36 fix name 2022-02-15 13:55:28 +01:00
Ludovic CANDELLIER
a6a4b9e59a change template 2022-02-15 13:52:03 +01:00
Ludovic CANDELLIER
3c00452219 Fix 2022-02-15 13:19:30 +01:00
Ludovic CANDELLIER
2e14e494a1 fix search 2022-02-09 09:25:48 +01:00
Ludovic CANDELLIER
b4856266c8 Add method to get offers by articles with siblings, enhance display 2022-01-30 22:48:04 +01:00
Ludovic CANDELLIER
5e5f12ddb2 Fix bug on select2 in modal filters, add filters by tags and shelves on articles 2022-01-30 15:04:08 +01:00
Ludovic CANDELLIER
5799eb36fc fix roles 2022-01-30 00:30:21 +01:00
Ludovic CANDELLIER
b4057c28d0 fix 2022-01-25 23:32:39 +01:00
Ludovic CANDELLIER
45c7385046 fix on image 2022-01-25 23:27:55 +01:00
Ludovic CANDELLIER
fb047aa036 Add no visual 2022-01-25 22:59:17 +01:00
Ludovic CANDELLIER
ed1d87a7d1 Fixes on tag_Groups and variations, add migrations 2022-01-25 22:25:18 +01:00
Ludovic CANDELLIER
9b6bac5545 fix on empty articles 2022-01-24 00:47:45 +01:00
Ludovic CANDELLIER
1fb9319bac Fix on home 2022-01-24 00:32:46 +01:00
Ludovic CANDELLIER
30666e2931 better integration of filters 2022-01-24 00:31:23 +01:00
Ludovic CANDELLIER
3e26bf368b Add toggle by rows/by cards 2022-01-23 23:16:56 +01:00
Ludovic CANDELLIER
52019357ba Add display of shelve 2022-01-23 22:49:23 +01:00
Ludovic CANDELLIER
81b6c87d59 fixes 2022-01-23 21:48:37 +01:00
Ludovic CANDELLIER
fe759565a8 Add filter by sale_channel, add method to get prices by offer, sale_channel and quantity 2022-01-23 21:37:54 +01:00
Ludovic CANDELLIER
94a162deb7 fix 2022-01-23 09:02:43 +01:00
Ludovic CANDELLIER
f4aecc9130 fixes on tags with slug 2022-01-22 22:05:18 +01:00
Ludovic CANDELLIER
f35650b234 Add tariff unities management 2022-01-22 19:26:35 +01:00
Ludovic CANDELLIER
6f04a8e7b7 Fixes size of description on article, fix save form on Families and genres 2022-01-22 17:53:24 +01:00
Ludovic CANDELLIER
2912dc6794 Display filters, and fix css for article 2022-01-22 13:12:43 +01:00
Ludovic CANDELLIER
6ff65eb927 fix 2022-01-19 22:43:32 +01:00
Ludovic CANDELLIER
7ae2c4b07c Add parameters to display by rows 2022-01-18 23:46:06 +01:00
Ludovic CANDELLIER
cefe956bc4 Add display articles by rows, and display article in full mode 2022-01-18 23:39:27 +01:00
Ludovic CANDELLIER
3641bd7d68 Fix on data for article, problem with id on polymorphic 2022-01-18 00:08:04 +01:00
Ludovic CANDELLIER
050fd76122 Add deep relations 2022-01-14 00:03:21 +01:00
Ludovic CANDELLIER
95ca3c6404 invert query from offers->articles to articles->offers 2022-01-05 22:05:30 +01:00
Ludovic CANDELLIER
a3c6fc6ebe [WIP] Add thumb on offers, refactor categories, try to fix counter on relations polymorphic with eage loader, bad pattern ! 2021-12-17 00:30:07 +01:00
Ludovic CANDELLIER
2be07ce72c fixes 2021-11-24 23:04:13 +01:00
Ludovic CANDELLIER
2f3da7d700 Manage address and deliveries 2021-11-24 20:57:12 +01:00
Ludovic CANDELLIER
b0b1164881 Fix 2021-11-24 16:15:56 +01:00
Ludovic CANDELLIER
6439d2d4ad fix on server 2021-11-24 15:56:02 +01:00
Ludovic CANDELLIER
6f0506a71e Fix on php 7.4 2021-11-24 15:53:17 +01:00
Ludovic CANDELLIER
5b84ff74e3 Fixes for deliveries vs sale_channels 2021-11-23 23:37:47 +01:00
Ludovic CANDELLIER
323330b1a1 fixes 2021-11-07 23:41:17 +01:00
Ludovic CANDELLIER
63c6671c97 fixes 2021-11-07 19:58:38 +01:00
Ludovic CANDELLIER
c7c8e18cbc Fixes 2021-11-07 17:16:35 +01:00
Ludovic CANDELLIER
46b751c361 change last_nulls on mysql , is compatible with mariadb ? 2021-11-04 16:59:11 +01:00
Ludovic CANDELLIER
4761656405 Fix 2021-11-04 16:37:42 +01:00
Ludovic CANDELLIER
eb0c9444bc Add filters 2021-11-01 23:42:53 +01:00
Ludovic CANDELLIER
e8d503b65d Minor fixes on traits 2021-11-01 18:50:17 +01:00
Ludovic CANDELLIER
900da34b57 Add thumbs views in datatables with traits 2021-11-01 18:37:25 +01:00
Ludovic CANDELLIER
ae20643879 add offers count, & minor fixes code standards 2021-11-01 16:26:31 +01:00
Ludovic CANDELLIER
8aaab4345f Minor fixes, coding standards 2021-11-01 00:50:10 +01:00
Ludovic CANDELLIER
e356b3fcda Refactoring, change menu, add many features 2021-10-30 02:22:51 +02:00
Ludovic CANDELLIER
fae7b7897f Fix 2021-10-26 21:51:47 +02:00
Ludovic CANDELLIER
e040837ce6 Fix 2021-10-26 21:47:00 +02:00
Ludovic CANDELLIER
86f6ee9a13 Synchro back-office, fix on tariffs 2021-10-26 21:41:46 +02:00
Ludovic CANDELLIER
c150be2c3e refactor, better class namespace intergration 2021-10-04 14:09:51 +02:00
Ludovic CANDELLIER
a7f661ab10 fixes 2021-10-04 13:49:45 +02:00
Ludovic CANDELLIER
9d21f28d9e downgrade for incompatibility with depedencies 2021-09-22 22:15:38 +02:00
Ludovic CANDELLIER
95997a4a0a Saving for offers 2021-09-22 22:14:39 +02:00
Ludovic CANDELLIER
2195ca122c fix 2021-09-22 21:03:42 +02:00
Ludovic CANDELLIER
85465f67c6 Fix tags with group name 2021-09-22 21:03:19 +02:00
Ludovic CANDELLIER
c347b7fe82 Remove duplicate migrations 2021-09-22 21:02:45 +02:00
Ludovic CANDELLIER
e98266e556 Upgrade boilerplate 2021-09-22 21:01:43 +02:00
Ludovic CANDELLIER
ffb9f81353 Add relations in tables, add saving states for datatables, minor fixes 2021-09-14 23:14:03 +02:00
Ludovic CANDELLIER
1dcc3e34a9 [WIP] Tentative d'ajout des tarifs dans le tableau 2021-09-09 00:30:36 +02:00
Ludovic CANDELLIER
9cf96b7d4e fixes 2021-09-09 00:03:24 +02:00
Ludovic CANDELLIER
50d445bb3b fix filter on prices by tariff 2021-09-01 10:25:59 +02:00
Ludovic CANDELLIER
b20c32d722 add description on variations 2021-09-01 09:10:58 +02:00
Ludovic CANDELLIER
4614ea57cf fix adding price for count(prices) > 3 2021-08-31 23:33:10 +02:00
Ludovic CANDELLIER
73cfe5a42e Fix on refreshing description & images from products 2021-08-31 23:12:18 +02:00
Ludovic CANDELLIER
144532acbf Add refreshing for inherited data 2021-08-30 22:59:50 +02:00
Ludovic CANDELLIER
8d3ccbf148 Enhance categories, add tags, parent 2021-08-26 17:45:37 +02:00
Ludovic CANDELLIER
e407934e2a Fix menu catalogue, fix path for price modal 2021-08-26 15:59:56 +02:00
Ludovic CANDELLIER
5ddcebc303 Fix variation 2021-08-26 15:53:41 +02:00
Ludovic CANDELLIER
04685cc7dc Fix translation yet forced on tags 2021-08-26 13:42:58 +02:00
Ludovic CANDELLIER
32c532d49b Fixes on tag updating 2021-08-26 09:54:37 +02:00
Ludovic CANDELLIER
67f490b2fe Fixes 2021-08-24 23:41:10 +02:00
Ludovic CANDELLIER
82afe63c60 Fix translation 2021-08-24 22:50:18 +02:00
Ludovic CANDELLIER
a84955412a Fix translations typo case 2021-08-24 22:40:22 +02:00
Ludovic CANDELLIER
f32ac13f1e Fix tree 2021-08-24 22:26:37 +02:00
Ludovic CANDELLIER
8d51ced269 Fix relationship 2021-08-24 20:42:51 +02:00
Ludovic CANDELLIER
967af93f8c fix segregation of article 2021-08-24 20:28:57 +02:00
Ludovic CANDELLIER
c3f66af009 Fix admin path 2021-08-24 19:14:29 +02:00
Ludovic CANDELLIER
46316ac974 Fix css 2021-08-24 19:03:52 +02:00
Ludovic CANDELLIER
7d1b2f1273 Fix package eloquent-macro unavailable for now 2021-08-24 16:40:31 +02:00
Ludovic CANDELLIER
5d99f9a09a remove old files 2021-08-24 16:27:05 +02:00
Ludovic CANDELLIER
24fffce7a1 Fix on preview mode 2021-08-23 23:56:46 +02:00
Ludovic CANDELLIER
81fbec892c comments 2021-08-21 19:48:21 +02:00
Ludovic CANDELLIER
9a0601d473 Rename Admin views directory, add some functions on models 2021-07-27 22:12:58 +02:00
Ludovic CANDELLIER
daeece59c9 Fixes on adding price & errors 2021-07-27 17:33:18 +02:00
Ludovic CANDELLIER
b879f11c99 Add new version in repository 2021-07-25 23:19:27 +02:00
Ludovic CANDELLIER
f75632b054 MCD 2021-06-05 18:00:10 +02:00
Ludovic CANDELLIER
b50f50ea62 [WIP] Refactor project 2021-05-21 00:21:05 +02:00
Ludovic CANDELLIER
4ce0fa942d v1 2021-05-07 00:14:27 +02:00
Ludovic CANDELLIER
fd2e87aa07 Fixes 2021-04-19 21:45:17 +02:00
Ludovic CANDELLIER
ebea9844dd Fixes on article preview 2021-04-16 00:04:00 +02:00
Ludovic CANDELLIER
096351ae4e Fixes on widget uploder 2021-04-15 23:58:51 +02:00
Ludovic CANDELLIER
f5ca57fdf2 Add preview from father, add new features 2021-04-11 00:36:41 +02:00
Ludovic CANDELLIER
f781158e36 Fix typo 2021-04-08 16:59:06 +02:00
Ludovic CANDELLIER
7a9f20acb9 Fix Typo 2021-04-08 16:57:50 +02:00
Ludovic CANDELLIER
098a46f3a0 Fix 2021-04-08 16:18:21 +02:00
73 changed files with 1369 additions and 2434 deletions

View File

@@ -43,6 +43,7 @@ COPY . /app
WORKDIR /app
RUN mkdir -p /app/bootstrap/cache \
/app/storage/media-library/temp \
/app/storage/framework/cache \
/app/storage/framework/views \
/app/storage/framework/sessions \
@@ -56,6 +57,7 @@ RUN chmod +x artisan
RUN ./artisan vendor:publish --tag=public --force ## creates public/vendor/jsvalidation
RUN ./artisan vendor:publish --tag=boilerplate-public --force --ansi ## creates public/vendor/boilerplate
RUN ./artisan vendor:publish --tag=datatables-buttons --force --ansi ## creates public/vendor/datatables/buttons
RUN ./artisan vendor:publish --tag=lfm_public --force --ansi
## XXXvlab: 2025-09-25 these migration files are breaking first
## install, but we had to resolve to not install from scratch and use
@@ -84,8 +86,6 @@ RUN apk add --no-cache xz
# bring PHP app with vendor
COPY --from=phpdeps /app /app
# ensure required runtime dirs exist (empty is fine)
RUN mkdir -p storage/framework/{cache,views,sessions} bootstrap/cache
# create artifact (use tar + xz so we don't depend on GNU tar -J)
RUN mkdir -p /out \
&& tar -C /app -cf /out/app.tar \

View File

@@ -47,6 +47,10 @@ class CustomerOrdersDataTable extends DataTable
{
$datatables
->editColumn('status', function (Order $order) {
if ($order->status == 0 && in_array($order->payment_type, [2, 3])) {
return 'En attente de règlement';
}
return Orders::getStatus($order->status);
})
->editColumn('created_at', function (Order $order) {

View File

@@ -3,7 +3,6 @@
namespace App\Http\Controllers\Admin\Botanic;
use App\Datatables\Botanic\SpeciesDataTable;
use App\Repositories\Botanic\Genres;
use App\Repositories\Botanic\Species;
use Illuminate\Http\Request;
@@ -21,7 +20,7 @@ class SpecieController extends Controller
public function create()
{
$data = Genres::init();
$data = Species::init();
return view('Admin.Botanic.Species.create', $data);
}
@@ -36,7 +35,7 @@ class SpecieController extends Controller
public function edit($id)
{
$data = Genres::init();
$data = Species::init();
$data['specie'] = Species::getFull($id);
return view('Admin.Botanic.Species.edit', $data);

View File

@@ -63,6 +63,17 @@ class ArticleController extends Controller
return view('Admin.Shop.Articles.edit', $data);
}
public function duplicate($id)
{
$data = Articles::getFull($id);
// Prepare for creation: blank id/slug, tweak name to indicate copy
$data['article']['id'] = null;
$data['article']['slug'] = null;
$data['article']['name'] = ($data['article']['name'] ?? '').' (copie)';
return view('Admin.Shop.Articles.create', $data);
}
public function destroy($id)
{
return Articles::destroy($id);

View File

@@ -71,7 +71,10 @@ class PriceListValueController extends Controller
public function addPrice($index)
{
$data['index'] = $index;
$data = [
'index' => $index,
'taxes' => Taxes::getOptions(),
];
return view('Admin.Shop.PriceListValues.partials.row_price', $data);
}

View File

@@ -68,8 +68,10 @@ class BasketController extends Controller
public function getBasketTotal($deliveryId = false, $deliveryTypeId = false)
{
$basket = Baskets::getBasketTotal($deliveryId, $deliveryTypeId);
$data = [
'basket' => Baskets::getBasketTotal($deliveryId, $deliveryTypeId),
'basket' => $basket,
'sale_channel' => $basket['sale_channel'] ?? null,
];
return view('Shop.Baskets.partials.basketTotal', $data);

View File

@@ -8,6 +8,7 @@ use App\Repositories\Shop\CustomerAddresses;
use App\Repositories\Shop\Customers;
use App\Repositories\Shop\Offers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Validator;
@@ -117,9 +118,82 @@ class CustomerController extends Controller
public function store(Request $request)
{
$data = $request->all();
$validator = Validator::make($data, [
'phone' => 'required|max:30',
], [
'phone.required' => __('Le numéro de téléphone est obligatoire.'),
]);
if ($validator->fails()) {
return redirect()->route('Shop.Customers.edit')
->withInput()
->withErrors($validator->errors(), 'registration');
}
$passwordError = $this->handlePasswordChange($request);
if ($passwordError) {
return redirect()->route('Shop.Customers.edit')
->with('growl', [$passwordError, 'danger']);
}
unset($data['current-password'], $data['new-password'], $data['new-password_confirmation']);
$customer = Customers::storeFull($data);
return redirect()->route('Shop.Customers.edit');
$growl = $request->filled('new-password')
? [__('Profil et mot de passe mis à jour.'), 'success']
: [__('Profil mis à jour.'), 'success'];
return redirect()->route('Shop.Customers.edit')->with('growl', $growl);
}
protected function handlePasswordChange(Request $request)
{
if (! $request->filled('new-password')) {
return null;
}
$customer = Customers::get(Customers::getId());
if (! $customer) {
return __('Impossible de modifier le mot de passe.');
}
if (! Hash::check($request->input('current-password'), $customer->password)) {
return __('Le mot de passe actuel est incorrect.');
}
if ($request->input('new-password') !== $request->input('new-password_confirmation')) {
return __('Les mots de passe ne correspondent pas.');
}
$newPassword = $request->input('new-password');
if (strlen($newPassword) < 8) {
return __('Le mot de passe doit contenir au moins 8 caractères.');
}
if (! preg_match('/[a-z]/', $newPassword)) {
return __('Le mot de passe doit contenir au moins une lettre minuscule.');
}
if (! preg_match('/[A-Z]/', $newPassword)) {
return __('Le mot de passe doit contenir au moins une lettre majuscule.');
}
if (! preg_match('/[0-9]/', $newPassword)) {
return __('Le mot de passe doit contenir au moins un chiffre.');
}
if (! preg_match('/[^A-Za-z0-9]/', $newPassword)) {
return __('Le mot de passe doit contenir au moins un caractère spécial.');
}
$customer->password = Hash::make($request->input('new-password'));
$customer->save();
return null;
}
public function storeAddress(Request $request)
@@ -171,6 +245,7 @@ class CustomerController extends Controller
$html = view('Shop.Customers.partials.address_item', [
'address' => $address->toArray(),
'prefix' => $prefix,
'inputName' => $request->input('input_name'),
'with_name' => true,
'selected' => $address->id,
])->render();

View File

@@ -9,6 +9,7 @@ use App\Repositories\Core\User\ShopCart;
use App\Repositories\Shop\Baskets;
use App\Repositories\Shop\Contents;
use App\Repositories\Shop\Customers;
use App\Repositories\Shop\CustomerAddresses;
use App\Repositories\Shop\Deliveries;
use App\Repositories\Shop\DeliveryTypes;
use App\Repositories\Shop\OrderMails;
@@ -57,8 +58,21 @@ class OrderController extends Controller
$deliveries = $deliveries ? $deliveries->values() : collect();
$customerData = $customer ? $customer->toArray() : false;
if ($customerData && $defaultSaleChannelId) {
$customerData['default_sale_channel_id'] = $defaultSaleChannelId;
if ($customerData) {
$customerData['delivery_address_id'] = optional(CustomerAddresses::getDeliveryAddress($customerId))->id;
$customerData['invoice_address_id'] = optional(CustomerAddresses::getInvoiceAddress($customerId))->id;
if (! $customerData['delivery_address_id'] && ! empty($customerData['delivery_addresses'])) {
$customerData['delivery_address_id'] = $customerData['delivery_addresses'][0]['id'] ?? null;
}
if (! $customerData['invoice_address_id'] && ! empty($customerData['invoice_addresses'])) {
$customerData['invoice_address_id'] = $customerData['invoice_addresses'][0]['id'] ?? null;
}
if ($defaultSaleChannelId) {
$customerData['default_sale_channel_id'] = $defaultSaleChannelId;
}
}
$data = [
@@ -88,7 +102,9 @@ class OrderController extends Controller
}
OrderMails::sendOrderConfirmed($order->id);
return redirect()->route('Shop.Orders.confirmed');
return redirect()->route('Shop.Orders.confirmed', [
'payment_type' => $data['payment_type'],
]);
}
return view('Shop.Orders.order');
@@ -97,9 +113,18 @@ class OrderController extends Controller
public function confirmed()
{
ShopCart::clear();
$paymentType = request('payment_type');
$content = Contents::getOrderConfirmedContent();
$paymentLabel = match ($paymentType) {
'2' => 'chèque',
'3' => 'virement',
default => null,
};
return view('Shop.Orders.confirmed', ['content' => $content]);
return view('Shop.Orders.confirmed', [
'content' => $content,
'payment_label' => $paymentLabel,
]);
}
public function getPdf($uuid)

View File

@@ -3,6 +3,7 @@
namespace App\Http\Requests\Admin\Shop;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class StoreArticlePost extends FormRequest
{
@@ -13,8 +14,13 @@ class StoreArticlePost extends FormRequest
public function rules()
{
$articleId = $this->input('id');
return [
'ref' => 'required|unique:shop_articles',
'ref' => [
'required',
Rule::unique('shop_articles', 'ref')->ignore($articleId)->whereNull('deleted_at'),
],
'product_type' => 'required',
'product_id' => 'required',
'article_nature_id' => 'required',

View File

@@ -20,6 +20,7 @@ class RegisterCustomer extends FormRequest
'last_name' => 'required|max:255',
'first_name' => 'required|max:255',
'email' => 'required|email|max:255|unique:shop_customers,email,NULL,id,deleted_at,NULL',
'phone' => 'required|max:30',
'password' => ['required', 'confirmed', new Password()],
];
}

View File

@@ -3,6 +3,7 @@
namespace App\Mail;
use App\Models\Core\Mail\MailTemplate;
use App\Repositories\Shop\Orders;
use App\Repositories\Shop\Traits\MailCustomers;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
@@ -32,29 +33,44 @@ class ConfirmationCommande extends TemplateMailable
public $facturation_ville;
public $livraison_nom;
public $livraison_adresse;
public $livraison_adresse2;
public $livraison_cp;
public $livraison_ville;
public $facturation_nom;
public $facturation_adresse2;
public $mode_paiement;
protected static $templateModelClass = MailTemplate::class;
public function __construct($order)
{
$facturation_address = $order->invoice->address;
$facturation_address = $order->invoice->address;
$delivery_address = $order->delivery_address;
$this->prenom = $order->customer->first_name;
$this->nom = $order->customer->last_name;
$this->facturation_nom = $facturation_address->name;
$this->facturation_adresse = $facturation_address->address;
$this->facturation_adresse2 = $facturation_address->address2;
$this->facturation_cp = $facturation_address->zipcode;
$this->facturation_ville = $facturation_address->city;
$this->livraison_nom = $delivery_address->name;
$this->livraison_adresse = $delivery_address->address;
$this->livraison_adresse2 = $delivery_address->address2;
$this->livraison_cp = $delivery_address->zipcode;
$this->livraison_ville = $delivery_address->city;
$this->societe = $order->customer->company;
$this->email = $order->customer->email;
$this->numero_commande = $order->ref;
$this->date_commande = $order->created_at;
$this->mode_paiement = Orders::getPaymentType($order->payment_type);
}
}

View File

@@ -11,9 +11,6 @@ class AppServiceProvider extends ServiceProvider
{
public function boot()
{
if (config('app.env') === 'production') {
\URL::forceScheme('https');
}
Schema::defaultStringLength(191);
View::composer('Shop.layout.layout', LayoutComposer::class);
}

View File

@@ -65,6 +65,9 @@ class Varieties
{
$images = $data['images'] ?? false;
$tags = $data['tags'] ?? false;
if (! array_key_exists('plus', $data) || $data['plus'] === null) {
$data['plus'] = '';
}
unset($data['images']);
unset($data['tags']);
$variety = self::store($data);

View File

@@ -2,6 +2,8 @@
namespace App\Repositories\Core;
use Spatie\MediaLibrary\MediaCollections\Models\Media as MediaModel;
class Medias
{
public static function getImage($model, $conversion = 'normal', $collection = 'images')
@@ -79,13 +81,9 @@ class Medias
public static function getImageSrc($image)
{
if (! $image) {
return null;
}
$id = $image['id'];
$filename = self::getFilename($image);
$media = self::resolveMedia($image);
return "/storage/{$id}/{$filename}";
return $media ? $media->getUrl() : null;
}
public static function getThumbSrc($image)
@@ -110,6 +108,12 @@ class Medias
public static function getSrcByType($image, $type)
{
$media = self::resolveMedia($image);
if ($media) {
return $type ? $media->getUrl($type) : $media->getUrl();
}
return $image ? '/storage/'.$image['id'].'/conversions/'.self::getFilename($image, $type) : false;
}
@@ -124,4 +128,48 @@ class Medias
{
return str_replace(['#', '/', '\\', ' '], '-', $name);
}
protected static function resolveMedia($image): ?MediaModel
{
if ($image instanceof MediaModel) {
return $image;
}
if (is_null($image)) {
return null;
}
if (is_array($image)) {
return self::hydrateMedia($image);
}
if (is_object($image)) {
if ($image instanceof \ArrayAccess) {
return self::hydrateMedia((array) $image);
}
$array = method_exists($image, 'toArray') ? $image->toArray() : (array) $image;
return self::hydrateMedia($array);
}
$id = data_get($image, 'id');
return $id ? MediaModel::query()->withoutGlobalScopes()->find($id) : null;
}
protected static function hydrateMedia(array $attributes): ?MediaModel
{
$id = data_get($attributes, 'id');
if (! $id) {
return null;
}
$media = new MediaModel();
$media->forceFill($attributes);
$media->exists = true;
return $media;
}
}

View File

@@ -31,7 +31,11 @@ class ArticleImages
public static function getFullImagesByArticle($article)
{
$images = count($article->images) ? $article->images : collect([]);
if (! $article) {
return collect([]);
}
$images = count($article->images ?? []) ? $article->images : collect([]);
switch ($article->product_type) {
case 'App\Models\Botanic\Variety':
$variety = $article->product ?? false;

View File

@@ -27,21 +27,34 @@ class ArticleTags
switch ($article->product_type) {
case 'App\Models\Botanic\Variety':
$data += $article->product->tags->toArray();
if ($article->product->specie ?? false) {
$data += $article->product->specie->tags->toArray();
$variety = $article->product;
if ($variety && $variety->tags) {
$data = array_merge($data, $variety->tags->toArray());
}
if ($variety && $variety->specie && $variety->specie->tags) {
$data = array_merge($data, $variety->specie->tags->toArray());
}
break;
case 'App\Models\Botanic\Specie':
$data += $article->product->tags->toArray();
$specie = $article->product;
if ($specie && $specie->tags) {
$data = array_merge($data, $specie->tags->toArray());
}
break;
case 'App\Models\Shop\Merchandise':
$data += $article->product->tags->toArray();
$data += $article->product->producer->tags->toArray();
$merchandise = $article->product;
if ($merchandise && $merchandise->tags) {
$data = array_merge($data, $merchandise->tags->toArray());
}
if ($merchandise && $merchandise->producer && $merchandise->producer->tags) {
$data = array_merge($data, $merchandise->producer->tags->toArray());
}
break;
default:
}
$data += $article->tags->toArray();
if ($article->tags) {
$data = array_merge($data, $article->tags->toArray());
}
foreach ($data as $tag) {
if (! isset($tags[$tag['group']][$tag['name']])) {

View File

@@ -19,7 +19,7 @@ class Articles
public static function autocomplete($str)
{
$data = Article::byAutocomplete($str)->orderBy('name')->limit(20)->pluck('name', 'id');
$data = Article::byAutocomplete($str)->visible()->orderBy('name')->limit(20)->pluck('name', 'id');
$export = [];
foreach ($data as $key => $name) {
$export[] = ['value' => $key, 'text' => $name];
@@ -135,8 +135,11 @@ class Articles
$data['specie'] = $article->product ? $article->product->description : '';
break;
case 'App\Models\Shop\Merchandise':
$data['merchandise'] = $article->product ? $article->product->description : '';
$data['producer'] = $article->product->producer->description;
$merchandise = $article->product;
$data['merchandise'] = $merchandise ? ($merchandise->description ?? '') : '';
if ($merchandise && $merchandise->producer) {
$data['producer'] = $merchandise->producer->description ?? '';
}
break;
default:
}
@@ -175,10 +178,18 @@ class Articles
$articles = self::getArticlesWithOffers($options);
$searchOrder = $options['ids'] ?? false ? array_flip($options['ids']->toArray()) : false;
foreach ($articles as $article) {
// Skip articles without an offer/tariff/price list for the resolved sale channel
if (!isset($article->offers[0]) || ! $article->offers[0]->tariff) {
continue;
}
$price_lists = $article->offers[0]->tariff->price_lists->toArray();
if (! count($price_lists)) {
continue;
}
if (empty($price_lists[0]['price_list_values'][0] ?? null)) {
continue;
}
if (! is_array($data[$article->name] ?? false)) {
$data[$article->name] = self::getDataForSale($article);
if ($searchOrder) {
@@ -193,7 +204,7 @@ class Articles
ksort($data);
}
return $data ?? false;
return $data ?? [];
}
public static function getDataForSale($article)

View File

@@ -41,6 +41,16 @@ class Contents
return self::get(5)->text ?? 'Votre commande a été confirmée';
}
public static function getOrderConfirmedByCheckContent()
{
return self::get(10)->text ?? 'Votre commande a bien été enregistrée, elle vous sera expédiée dès réception de votre chèque.';
}
public static function getOrderConfirmedByWireContent()
{
return self::get(11)->text ?? 'Votre commande a bien été enregistrée, elle vous sera expédiée dès réception de votre virement.';
}
public static function getPayboxConfirmedContent()
{
return self::get(6)->text ?? 'Merci pour votre règlement. Votre commande sera traitée sous peu.';

View File

@@ -19,7 +19,7 @@ class InvoicePDF
$invoice = Invoices::getFull($id);
$customFields = [];
if ($orderRef = optional($invoice->order)->ref) {
$customFields['order number'] = $orderRef;
$customFields['Numéro de commande'] = $orderRef;
}
$customer = new Party([
@@ -61,7 +61,7 @@ class InvoicePDF
trim(($address->zipcode ?? '').' '.($address->city ?? '')),
]);
return implode('<br>', $lines);
return implode("\n", $lines);
}
public static function makeItems($details)

View File

@@ -3,6 +3,7 @@
namespace App\Repositories\Shop;
use App\Models\Shop\Offer;
use App\Models\Shop\PriceList;
use App\Models\Shop\PriceListValue;
use App\Models\Shop\SaleChannel;
use App\Traits\Model\Basic;
@@ -185,20 +186,44 @@ class Offers
->with([
'article',
'tariff:id,status_id',
'variation',
])
->get();
return $channels->map(function ($channel) use ($offers) {
$priceValue = null;
$candidateOffer = null;
$allOffersForChannel = [];
foreach ($offers as $offer) {
$priceCandidate = self::getPrice($offer->id, 1, $channel->id);
if ($priceCandidate && (float) $priceCandidate->price_taxed > 0) {
$priceValue = $priceCandidate;
$candidateOffer = $offer;
break;
// Get price list name
$priceListName = null;
if ($priceCandidate) {
$priceListModel = PriceList::find($priceCandidate->price_list_id);
$priceListName = $priceListModel ? $priceListModel->name : null;
}
// Collect all offers with their details
$allOffersForChannel[] = [
'id' => $offer->id,
'variation_name' => $offer->variation ? $offer->variation->name : null,
'stock_current' => (int) $offer->stock_current,
'status_id' => (int) $offer->status_id,
'is_active' => (int) $offer->status_id === 1,
'tariff_id' => $offer->tariff_id ? (int) $offer->tariff_id : null,
'price_taxed' => (float) $priceCandidate->price_taxed,
'quantity' => (int) $priceCandidate->quantity,
'price_list_name' => $priceListName,
];
// Keep first valid offer as the main candidate
if (!$candidateOffer) {
$priceValue = $priceCandidate;
$candidateOffer = $offer;
}
}
}
@@ -209,6 +234,7 @@ class Offers
$offerHasStock = $candidateOffer && $candidateOffer->stock_current !== null
? (float) $candidateOffer->stock_current > 0
: null;
$offerTariffId = $candidateOffer && $candidateOffer->tariff_id ? (int) $candidateOffer->tariff_id : null;
return [
'id' => $channel->id,
@@ -221,6 +247,8 @@ class Offers
'offer_stock_current' => $offerStock,
'offer_has_stock' => $offerHasStock,
'tariff_status_id' => $offerTariffStatus,
'tariff_id' => $offerTariffId,
'all_offers' => $allOffersForChannel,
];
})->toArray();
}

View File

@@ -46,12 +46,34 @@ class PriceListValues
{
foreach ($values as $value) {
$value['price_list_id'] = $price_list_id;
if ($value['price']) {
if (self::hasPrice($value)) {
self::store($value);
}
}
}
public static function purgeRemovedValues($price_list_id, array $ids)
{
if (! count($ids)) {
return;
}
PriceListValue::byPriceList($price_list_id)
->whereIn('id', $ids)
->delete();
}
protected static function hasPrice($value): bool
{
if (! array_key_exists('price', $value)) {
return false;
}
$price = $value['price'];
return $price !== null && $price !== '';
}
public static function getModel()
{
return PriceListValue::query();

View File

@@ -17,7 +17,7 @@ class PriceLists
'taxes' => Taxes::getOptions(),
'price_list' => [
'tariff_id' => $tariffId,
'price_list_values' => array_fill(0, 3, ''),
'price_list_values' => [],
],
];
}
@@ -50,9 +50,8 @@ class PriceLists
public static function edit($id)
{
$price_list = self::getFull($id)->toArray();
$n = count($price_list['price_list_values']);
if ($n <= 3) {
$price_list['price_list_values'] += array_fill($n, 3 - $n, '');
if (count($price_list['price_list_values']) === 0) {
$price_list['price_list_values'][] = [];
}
return $price_list;
@@ -71,9 +70,14 @@ class PriceLists
public static function store($data)
{
$id = $data['id'] ?? false;
$price_list_values = $data['price_list_values'] ?? false;
$price_list_values = $data['price_list_values'] ?? [];
$deleted_values = array_map('intval', array_filter($data['deleted_price_list_value_ids'] ?? [], function ($value) {
return $value !== null && $value !== '';
}));
unset($data['price_list_values']);
unset($data['deleted_price_list_value_ids']);
$price_list = $id ? self::update($data) : self::create($data);
PriceListValues::purgeRemovedValues($price_list->id, $deleted_values);
PriceListValues::storePrices($price_list->id, $price_list_values);
return $price_list;

View File

@@ -49,7 +49,7 @@ class SaleChannels
}
}
return self::getByCode('EXP');
return self::getByCode('POSTE');
}
public static function getByCode($code)

View File

@@ -8,8 +8,14 @@ class Searches
{
public static function search($options)
{
// Get article IDs from Scout search
$searchResults = Article::search($options['search_name'])->get()->pluck('id');
// Filter to only include visible articles
$visibleArticleIds = Article::whereIn('id', $searchResults)->visible()->pluck('id');
return collect(Articles::getArticlesToSell([
'ids' => Article::search($options['search_name'])->get()->pluck('id'),
'ids' => $visibleArticleIds,
]))->sortBy('searchOrder')->toArray();
}

View File

@@ -1,23 +1,35 @@
<?php
return [
// --- The default avatar size
'size' => 80,
// Default configuration group for Gravatar
'default' => [
// --- The default avatar size
'size' => 80,
// --- The default avatar to display if we have no results
// (bool) false
// (string) 404
// (string) mm: (mystery-man) a simple, cartoon-style silhouetted outline of a person (does not vary by email hash).
// (string) identicon: a geometric pattern based on an email hash.
// (string) monsterid: a generated 'monster' with different colors, faces, etc.
// (string) wavatar: generated faces with differing features and backgrounds.
// (string) retro: awesome generated, 8-bit arcade-style pixelated faces.
'default' => 'identicon',
// --- The default avatar to display if we have no results
// (bool) false
// (string) 404
// (string) mm: (mystery-man) a simple, cartoon-style silhouetted outline of a person (does not vary by email hash).
// (string) identicon: a geometric pattern based on an email hash.
// (string) monsterid: a generated 'monster' with different colors, faces, etc.
// (string) wavatar: generated faces with differing features and backgrounds.
// (string) retro: awesome generated, 8-bit arcade-style pixelated faces.
'fallback' => 'identicon',
// --- Set the type of avatars we allow to show
// - g: suitable for display on all websites with any audience type.
// - pg: may contain rude gestures, provocatively dressed individuals, the lesser swear words, or mild violence.
// - r: may contain such things as harsh profanity, intense violence, nudity, or hard drug use.
// - x: may contain hardcore sexual imagery or extremely disturbing violence.
'maxRating' => 'g',
// --- Whether to use HTTPS protocol
'secure' => false,
// --- Set the type of avatars we allow to show
// - g: suitable for display on all websites with any audience type.
// - pg: may contain rude gestures, provocatively dressed individuals, the lesser swear words, or mild violence.
// - r: may contain such things as harsh profanity, intense violence, nudity, or hard drug use.
// - x: may contain hardcore sexual imagery or extremely disturbing violence.
'maximumRating' => 'g',
// --- Force default image to always display
'forceDefault' => false,
// --- Optional file extension appended to URL
'forceExtension' => 'jpg',
]
];

View File

@@ -126,7 +126,7 @@ return [
* The path where to store temporary files while performing image conversions.
* If set to null, storage_path('media-library/temp') will be used.
*/
'temporary_directory_path' => null,
'temporary_directory_path' => env('MEDIA_LIBRARY_TEMP_PATH', storage_path('media-library/temp')),
/*
* The engine that should perform the image conversions.

View File

@@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('shop_articles', function (Blueprint $table) {
$table->dropUnique('ref');
$table->unique(['ref', 'deleted_at'], 'shop_articles_ref_deleted_at_unique');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('shop_articles', function (Blueprint $table) {
$table->dropUnique('shop_articles_ref_deleted_at_unique');
$table->unique('ref');
});
}
};

View File

@@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
DB::table('shop_contents')->insert([
[
'id' => 10,
'text' => '<p>Votre commande a bien été enregistrée, elle vous sera expédiée dès réception de votre chèque.</p><p class="mt-3 text-warning"><i class="fa fa-exclamation-triangle mr-1"></i> Sans réception de votre paiement au bout de 30 jours, votre commande sera annulée.</p>',
'created_at' => now(),
'updated_at' => now(),
],
[
'id' => 11,
'text' => '<p>Votre commande a bien été enregistrée, elle vous sera expédiée dès réception de votre virement.</p><p class="mt-3 text-warning"><i class="fa fa-exclamation-triangle mr-1"></i> Sans réception de votre paiement au bout de 30 jours, votre commande sera annulée.</p>',
'created_at' => now(),
'updated_at' => now(),
],
]);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
DB::table('shop_contents')->whereIn('id', [10, 11])->delete();
}
};

View File

@@ -0,0 +1,87 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
$this->transformTemplate(function ($html) {
// Replace hardcoded "Carte de crédit" with the template variable
$html = str_replace(
'Carte de cr&eacute;dit',
'{{mode_paiement}}',
$html
);
// Add address2 to delivery address
$html = str_replace(
'{{livraison_adresse}}<br />{{livraison_cp}} {{livraison_ville}}',
'{{livraison_adresse}}{{#livraison_adresse2}}<br />{{livraison_adresse2}}{{/livraison_adresse2}}<br />{{livraison_cp}} {{livraison_ville}}',
$html
);
// Add address2 to billing address
$html = str_replace(
'{{facturation_adresse}}<br />{{facturation_cp}} {{facturation_ville}}',
'{{facturation_adresse}}{{#facturation_adresse2}}<br />{{facturation_adresse2}}{{/facturation_adresse2}}<br />{{facturation_cp}} {{facturation_ville}}',
$html
);
return $html;
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
$this->transformTemplate(function ($html) {
$html = str_replace(
'{{mode_paiement}}',
'Carte de cr&eacute;dit',
$html
);
$html = str_replace(
'{{livraison_adresse}}{{#livraison_adresse2}}<br />{{livraison_adresse2}}{{/livraison_adresse2}}<br />{{livraison_cp}} {{livraison_ville}}',
'{{livraison_adresse}}<br />{{livraison_cp}} {{livraison_ville}}',
$html
);
$html = str_replace(
'{{facturation_adresse}}{{#facturation_adresse2}}<br />{{facturation_adresse2}}{{/facturation_adresse2}}<br />{{facturation_cp}} {{facturation_ville}}',
'{{facturation_adresse}}<br />{{facturation_cp}} {{facturation_ville}}',
$html
);
return $html;
});
}
private function transformTemplate(callable $transform): void
{
$template = DB::table('mail_templates')
->where('mailable', 'App\\Mail\\ConfirmationCommande')
->first();
if (! $template) {
return;
}
$translations = json_decode($template->html_template, true);
foreach ($translations as $lang => $html) {
$translations[$lang] = $transform($html);
}
DB::table('mail_templates')
->where('id', $template->id)
->update(['html_template' => json_encode($translations, JSON_UNESCAPED_UNICODE)]);
}
};

View File

@@ -0,0 +1,74 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
$this->transformTemplate(function ($html) {
// Add name before delivery address
$html = str_replace(
'{{livraison_adresse}}',
'{{#livraison_nom}}{{livraison_nom}}<br />{{/livraison_nom}}{{livraison_adresse}}',
$html
);
// Add name before billing address
$html = str_replace(
'{{facturation_adresse}}',
'{{#facturation_nom}}{{facturation_nom}}<br />{{/facturation_nom}}{{facturation_adresse}}',
$html
);
return $html;
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
$this->transformTemplate(function ($html) {
$html = str_replace(
'{{#livraison_nom}}{{livraison_nom}}<br />{{/livraison_nom}}{{livraison_adresse}}',
'{{livraison_adresse}}',
$html
);
$html = str_replace(
'{{#facturation_nom}}{{facturation_nom}}<br />{{/facturation_nom}}{{facturation_adresse}}',
'{{facturation_adresse}}',
$html
);
return $html;
});
}
private function transformTemplate(callable $transform): void
{
$template = DB::table('mail_templates')
->where('mailable', 'App\\Mail\\ConfirmationCommande')
->first();
if (! $template) {
return;
}
$translations = json_decode($template->html_template, true);
foreach ($translations as $lang => $html) {
$translations[$lang] = $transform($html);
}
DB::table('mail_templates')
->where('id', $template->id)
->update(['html_template' => json_encode($translations, JSON_UNESCAPED_UNICODE)]);
}
};

View File

@@ -0,0 +1,56 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
private $oldSrc = '/storage/photos/shares/logo.png';
private $newSrc = 'https://boutique.jardinenvie.com/img/logo.png';
/**
* Run the migrations.
*/
public function up(): void
{
$this->replaceLogoInAllTemplates($this->oldSrc, $this->newSrc);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
$this->replaceLogoInAllTemplates($this->newSrc, $this->oldSrc);
}
private function replaceLogoInAllTemplates(string $from, string $to): void
{
$templates = DB::table('mail_templates')->get();
foreach ($templates as $template) {
$translations = json_decode($template->html_template, true);
if (! $translations) {
continue;
}
$changed = false;
foreach ($translations as $lang => $html) {
$updated = str_replace($from, $to, $html);
if ($updated !== $html) {
$translations[$lang] = $updated;
$changed = true;
}
}
if ($changed) {
DB::table('mail_templates')
->where('id', $template->id)
->update(['html_template' => json_encode($translations, JSON_UNESCAPED_UNICODE)]);
}
}
}
};

2175
dump.sql

File diff suppressed because one or more lines are too long

9
resources/lang/en/fg.php Normal file
View File

@@ -0,0 +1,9 @@
<?php
return [
'confirmdelete' => 'Do you confirm the deletion?',
'deletesuccess' => 'Deleted successfully.',
'mail_the_selection' => 'Mail the selection',
'mail_the_complete_list' => 'Mail the complete list',
];

9
resources/lang/fr/fg.php Normal file
View File

@@ -0,0 +1,9 @@
<?php
return [
'confirmdelete' => 'Confirmez-vous la suppression ?',
'deletesuccess' => 'La suppression a été effectuée.',
'mail_the_selection' => 'Envoyer la sélection',
'mail_the_complete_list' => 'Envoyer toute la liste',
];

View File

@@ -112,3 +112,33 @@ body {
.bg-darker {
background-color: rgba(0,0,0,0.05)!important;
}
/* Header action buttons aligned with page title */
.content-header .form-buttons {
margin-left: 12px;
}
.content-header .form-buttons .btn {
height: 32px;
display: inline-flex;
align-items: center;
padding-top: 4px;
padding-bottom: 4px;
line-height: 1.1;
}
@media (max-width: 575.98px) {
.content-header .form-buttons {
margin-left: 0;
margin-top: 8px;
}
.content-header .form-buttons .btn {
height: 28px;
padding-top: 2px;
padding-bottom: 2px;
padding-left: 8px;
padding-right: 8px;
font-size: 0.75rem;
}
}

View File

@@ -311,6 +311,52 @@ div.megamenu ul.megamenu li.megamenu.level1
}
.category-title {
font-size: 2em;
}
.category-description {
font-size: 1.2em;
}
.breadcrumb-title {
font-size: 1.6em;
}
.breadcrumb-current {
font-size: 1em;
}
@media (max-width: 767.98px){
.category-title {
font-size: 1.5em;
}
.category-description {
font-size: 1.05em;
}
.breadcrumb-title {
font-size: 1.4em;
}
.breadcrumb-current {
font-size: 0.95em;
}
}
@media (max-width: 575.98px){
.category-title {
font-size: 1.35em;
}
.category-description {
font-size: 0.95em;
}
.breadcrumb-title {
font-size: 1.2em;
}
.breadcrumb-current {
font-size: 0.9em;
}
}
@font-face {
font-family: 'noto_sanscondensed';
src: url('/fonts/notosans-condensed/notosans-condensed-webfont.eot');
@@ -348,4 +394,45 @@ div.megamenu ul.megamenu li.megamenu.level1
.dropdown-menu > li:hover > .submenu{
display: block;
}
}
}
@media (max-width: 991.98px){
#navbarContentMobile {
max-height: calc(100vh - 60px);
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
#navbarContentMobile .navbar-nav {
flex-direction: column;
}
#navbarContentMobile .navbar-nav .col {
flex: 0 0 100%;
max-width: 100%;
}
#navbarContentMobile .dropdown-menu {
display: block;
position: static;
float: none;
box-shadow: none;
background: transparent;
}
#navbarContentMobile .dropdown-toggle::after {
display: none;
}
#navbarContentMobile .dropdown-menu .container,
#navbarContentMobile .dropdown-menu .row {
margin: 0;
}
#navbarContentMobile .dropdown-menu .shadow {
box-shadow: none !important;
}
.category-card .card-body {
font-size: 0.75rem;
}
/* Supprimer les grandes marges du container en affichage mobile/tablette */
.container {
max-width: 100%;
}
}

View File

@@ -32,12 +32,14 @@ $(document).on('click', '.dropdown-menu', function (e) {
// make it as accordion for smaller screens
if ($(window).width() < 992) {
$('.dropdown-menu a').click(function(e) {
e.preventDefault();
if ($(this).next('.submenu').length) {
$(this).next('.submenu').toggle();
var $submenu = $(this).next('.submenu');
if ($submenu.length) {
e.preventDefault();
$submenu.toggle();
}
$('.dropdown').on('hide.bs.dropdown', function () {
$(this).find('.submenu').hide();
});
});
$('.dropdown').on('hide.bs.dropdown', function () {
$(this).find('.submenu').hide();
});
}

View File

@@ -5,5 +5,7 @@
])
@section('content')
@include('Admin.Shop.Articles.form')
@include('Admin.Shop.Articles.form', [
'cancel_url' => route('Admin.Shop.Articles.index'),
])
@endsection

View File

@@ -5,5 +5,13 @@
])
@section('content')
@include('Admin.Shop.Articles.form')
@php
$duplicateUrl = \Route::has('Admin.Shop.Articles.duplicate')
? route('Admin.Shop.Articles.duplicate', $article['id'] ?? null)
: null;
@endphp
@include('Admin.Shop.Articles.form', [
'duplicate_url' => $duplicateUrl,
'cancel_url' => route('Admin.Shop.Articles.index'),
])
@endsection

View File

@@ -5,10 +5,29 @@
'files' => true,
]) }}
<input type="hidden" name="id" id="id" value="{{ $article['id'] ?? null }}">
@php
$articlePublicUrl = null;
if (!empty($article['slug'] ?? null)) {
$articlePublicUrl = route('Shop.Articles.slug', ['slug' => $article['slug']]);
} elseif (!empty($article['id'] ?? null)) {
$articlePublicUrl = route('Shop.Articles.show', ['id' => $article['id']]);
}
@endphp
@if ($articlePublicUrl)
<div class="d-flex justify-content-end mb-3">
<a href="{{ $articlePublicUrl }}" class="btn btn-outline-primary" target="_blank" rel="noopener">
Voir la page publique
<i class="fa fa-external-link"></i>
</a>
</div>
@endif
@include('Admin.Shop.Articles.partials.characteristics')
{{ Form::close() }}
<x-save />
<x-save :cancel-url="$cancel_url ?? null" :duplicate-url="$duplicate_url ?? null" />
@include('load.form.appender')
@include('load.form.editor')

View File

@@ -2,7 +2,7 @@
@component('components.layout.box-collapse', [
'id' => 'product_description_box',
'title' => 'Informations héritées',
'collapsed' => $collapsed ?? false,
'collapsed' => $collapsed ?? true,
])
@foreach ($article['inherited'] as $inherited)
@component('components.card', ['title' => $inherited['name'], 'class' => 'mb-3'])

View File

@@ -1,8 +1,17 @@
{{ Form::open(['route' => 'Admin.Shop.Offers.store', 'id' => 'offer-form', 'autocomplete' => 'off']) }}
<input type="hidden" name="id" value="{{ $offer['id'] ?? false }}">
@if (($offer['id'] ?? false) && ($offer['article_id'] ?? false))
<div class="d-flex justify-content-end mb-3">
<a href="{{ route('Shop.Articles.show', ['id' => $offer['article_id']]) }}" class="btn btn-outline-primary" target="_blank" rel="noopener">
Voir la page publique de l'article
<i class="fa fa-external-link"></i>
</a>
</div>
@endif
<div class="row mb-3">
<div class="col-8">
<div class="col-12">
<div class="row mb-3">
<div class="col-12">
@include('components.form.select', [
@@ -96,13 +105,6 @@
</div>
@endcomponent
</div>
<div class="col-4">
@component('components.card', ['title' => 'Previsualisation'])
<div id="preview-article"></div>
<div id="preview-variation"></div>
<div id="preview-tariff"></div>
@endcomponent
</div>
</div>
@@ -117,59 +119,8 @@
{!! JsValidator::formRequest('App\Http\Requests\Admin\Shop\StoreOfferPost', '#offer-form') !!}
<script>
function handleArticle() {
$('.select_article').change(function() {
previewArticle($(this).val());
})
}
function previewArticle(id) {
var url = '{{ route('Admin.Shop.Offers.previewArticle') }}/' + id;
$('#preview-article').load(url, function() {
initChevron();
});
}
function handleVariation() {
$('.select_variation').change(function() {
previewVariation($(this).val());
})
}
function previewVariation(id) {
var url = '{{ route('Admin.Shop.Offers.previewVariation') }}/' + id;
$('#preview-variation').load(url, function() {
initChevron();
});
}
function handleTariff() {
$('.select_tariffs').change(function() {
previewTariff($(this).val());
})
}
function previewTariff(id) {
var url = '{{ route('Admin.Shop.Offers.previewTariff') }}/' + id;
$('#preview-tariff').load(url, function() {
initChevron();
});
}
function initPreview() {
previewArticle("{{ $offer['article_id'] ?? null }}");
previewVariation("{{ $offer['variation_id'] ?? null }}");
previewTariff("{{ $offer['tariff_id'] ?? null }}");
}
handleArticle();
handleVariation();
handleTariff();
initChevron();
initSaveForm('#offer-form');
initSelect2();
@if ($offer['id'] ?? false)
initPreview();
@endif
</script>
@endpush

View File

@@ -15,4 +15,9 @@
<td>
@include('components.form.inputs.money', ['name' => 'price_list_values[' . $index . '][price_taxed]', 'value' => $price_list_value['price_taxed'] ?? null, 'required' => true, 'class' => 'price_taxed'])
</td>
</tr>
<td class="text-center align-middle">
<button type="button" class="btn btn-outline-danger btn-xs remove-price" title="{{ __('Supprimer') }}">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>

View File

@@ -30,12 +30,18 @@
<th>Unit. HT</th>
<th>TVA</th>
<th>Unit. TTC</th>
<th class="text-center" style="width: 60px;">Actions</th>
</tr>
</thead>
<tbody>
@foreach ($price_list['price_list_values'] as $price_list_value)
@include('Admin.Shop.PriceListValues.partials.row_price', ['index' => $loop->index])
@php($priceListValues = $price_list['price_list_values'] ?? [])
@php($nextIndex = count($priceListValues))
@foreach ($priceListValues as $index => $price_list_value)
@include('Admin.Shop.PriceListValues.partials.row_price', ['index' => $index, 'price_list_value' => $price_list_value])
@endforeach
@include('Admin.Shop.PriceListValues.partials.row_price', ['index' => $nextIndex, 'price_list_value' => []])
</tbody>
<tfoot>
<tr>
@@ -49,21 +55,30 @@
</tfoot>
</table>
@endcomponent
<div id="deleted-price-list-values"></div>
</form>
<script>
var priceRowIndex = 0;
var lastRemovedIndex = null;
function handleAddPrice() {
$('#add_price').click( function () {
var index = $('#prices-table tbody tr').length;
$.get("{{ route('Admin.Shop.PriceListValues.addPrice') }}/" + index, function(data) {
$("#prices-table").append(data);
})
handlePrices();
addEmptyPriceRow();
})
}
function addEmptyPriceRow() {
var index = nextPriceRowIndex();
$.get("{{ route('Admin.Shop.PriceListValues.addPrice') }}/" + index, function(data) {
$("#prices-table tbody").append(data);
handlePrices();
handleRemovePrice();
lastRemovedIndex = null;
});
}
function handle_prices() {
$('.price').change(function() {
$col_tax = $(this).parent().parent().find('.tax');
@@ -101,9 +116,82 @@
handle_prices_taxed();
}
function handleRemovePrice() {
$('#prices-table').off('click', '.remove-price').on('click', '.remove-price', function() {
var $row = $(this).closest('tr');
var idx = extractRowIndex($row);
var id = $row.find('input[name$="[id]"]').val();
if (id) {
registerDeletedPrice(id);
}
$row.remove();
lastRemovedIndex = idx;
ensureAtLeastOneRow();
});
}
function registerDeletedPrice(id) {
var $container = $('#deleted-price-list-values');
if ($container.find('input[value="' + id + '"]').length === 0) {
$container.append('<input type="hidden" name="deleted_price_list_value_ids[]" value="' + id + '">');
}
}
function ensureAtLeastOneRow() {
if ($('#prices-table tbody tr').length === 0) {
if (lastRemovedIndex !== null) {
$.get("{{ route('Admin.Shop.PriceListValues.addPrice') }}/" + lastRemovedIndex, function(data) {
$("#prices-table tbody").append(data);
handlePrices();
handleRemovePrice();
});
} else {
addEmptyPriceRow();
}
}
}
function computeStartingIndex() {
var maxIndex = -1;
$('#prices-table tbody tr').each(function() {
var $idInput = $(this).find('input[name$="[id]"]');
var name = $idInput.attr('name');
if (name) {
var matches = name.match(/price_list_values\[(\d+)\]\[id\]/);
if (matches && matches[1]) {
var idx = parseInt(matches[1], 10);
if (!isNaN(idx) && idx > maxIndex) {
maxIndex = idx;
}
}
}
});
return maxIndex + 1;
}
function nextPriceRowIndex() {
return priceRowIndex++;
}
function extractRowIndex($row) {
var $idInput = $row.find('input[name$="[id]"]');
var name = $idInput.attr('name');
if (!name) {
return priceRowIndex;
}
var matches = name.match(/price_list_values\[(\d+)\]\[id\]/);
return matches && matches[1] ? parseInt(matches[1], 10) : priceRowIndex;
}
$(function() {
priceRowIndex = computeStartingIndex();
handleAddPrice();
handlePrices();
handleRemovePrice();
ensureAtLeastOneRow();
});
</script>

View File

@@ -1,37 +1,59 @@
@if ($article['offers']['semences'] ?? false)
@include('Shop.Articles.partials.addBasket', [
'data' => $article['offers']['semences'],
'title' => 'Semences',
'model' => 'semences',
'bgClass' => 'bg-green-light',
])
@endif
@php
// Check if article is not visible OR has no offers at all
$hasNoOffers = empty($article['offers']['semences'] ?? false)
&& empty($article['offers']['plants'] ?? false)
&& empty($article['offers']['legumes'] ?? false)
&& empty($article['offers']['marchandise'] ?? false);
$shouldShowComingSoon = !($article['visible'] ?? true) || $hasNoOffers;
@endphp
@if ($article['offers']['plants'] ?? false)
@include('Shop.Articles.partials.addBasket', [
'data' => $article['offers']['plants'],
'title' => 'Plants',
'model' => 'plants',
'bgClass' => 'bg-green-light',
])
@endif
@if ($shouldShowComingSoon)
{{-- Display "Coming Soon" box when article is not visible or has no offers --}}
<div class="card border-info">
<div class="card-body text-center p-4">
<h4 class="text-info mb-3">
<i class="fas fa-clock"></i>
</h4>
<h5 class="card-title mb-0">Bientôt disponible</h5>
</div>
</div>
@else
{{-- Display normal offers for visible articles with available offers --}}
@if ($article['offers']['semences'] ?? false)
@include('Shop.Articles.partials.addBasket', [
'data' => $article['offers']['semences'],
'title' => 'Semences',
'model' => 'semences',
'bgClass' => 'bg-green-light',
])
@endif
@if ($article['offers']['legumes'] ?? false)
@include('Shop.Articles.partials.addBasket', [
'data' => $article['offers']['legumes'],
'title' => 'Légumes',
'model' => 'legumes',
'bgClass' => 'bg-green-light',
])
@endif
@if ($article['offers']['plants'] ?? false)
@include('Shop.Articles.partials.addBasket', [
'data' => $article['offers']['plants'],
'title' => 'Plants',
'model' => 'plants',
'bgClass' => 'bg-green-light',
])
@endif
@if ($article['offers']['marchandise'] ?? false)
@include('Shop.Articles.partials.addBasket', [
'data' => $article['offers']['marchandise'],
'title' => 'Marchandises',
'model' => 'marchandise',
'bgClass' => 'bg-green-light',
])
@if ($article['offers']['legumes'] ?? false)
@include('Shop.Articles.partials.addBasket', [
'data' => $article['offers']['legumes'],
'title' => 'Légumes',
'model' => 'legumes',
'bgClass' => 'bg-green-light',
])
@endif
@if ($article['offers']['marchandise'] ?? false)
@include('Shop.Articles.partials.addBasket', [
'data' => $article['offers']['marchandise'],
'title' => 'Marchandises',
'model' => 'marchandise',
'bgClass' => 'bg-green-light',
])
@endif
@endif
@include('load.basket')

View File

@@ -18,9 +18,9 @@
</div>
</div>
<div class="col-lg-5 col-xs-12 text-justify">
{!! $article['description']['variety'] ?? null !!}
{!! $article['description']['semences'] ?? null !!}
{!! $article['description']['plants'] ?? null !!}
{!! $article['description']['variety'] ?? null !!}
{!! $article['description']['merchandise'] ?? null !!}
@if ($article['description']['plus'] ?? false)
@@ -48,9 +48,18 @@
</div>
<div class="col-lg-3 col-xs-12">
@if (config('app.debug') && !empty($article['available_sale_channels']))
<div class="alert alert-info p-2 mb-3">
<strong class="d-block">Offres :</strong>
@if (auth('web')->check() && !empty($article['available_sale_channels']))
<div id="article-admin-offers" class="alert alert-info p-2 mb-3">
<div class="d-flex justify-content-between align-items-center">
<strong class="d-block mb-0">Offres :</strong>
<a href="{{ route('Admin.Shop.Articles.edit', $article['id']) }}" class="text-dark d-inline-flex align-items-center gap-1" style="font-size: 0.95rem;" title="Ouvrir la fiche article en admin" target="_blank" rel="noopener">
<svg aria-hidden="true" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 20h9" />
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4Z" />
</svg>
<span class="sr-only">Éditer l'article</span>
</a>
</div>
<ul class="list-unstyled mb-0 small">
@php
$currentSaleChannelId = $article['current_sale_channel']['id'] ?? null;
@@ -82,16 +91,61 @@
</span>
</span>
@if ($priceTaxed !== null)
<span class="ml-2 text-nowrap text-right {{ $nameClass }}">
{{ number_format($priceTaxed, 2, ',', ' ') }} TTC
@if (! empty($quantity))
<span class="d-block text-muted" style="font-size: 0.85em;">Qté min. {{ $quantity }}</span>
@endif
</span>
@php
$tariffId = $channel['tariff_id'] ?? null;
@endphp
@if ($tariffId)
<a href="{{ route('Admin.Shop.Tariffs.edit', $tariffId) }}" target="_blank" rel="noopener" title="Ouvrir le tarif" class="ml-2 text-nowrap text-right {{ $nameClass }} text-decoration-none text-reset d-inline-block admin-link-group admin-price-link">
{{ number_format($priceTaxed, 2, ',', ' ') }} € TTC
@if (! empty($quantity))
<span class="d-block text-muted" style="font-size: 0.85em;">Qté min. {{ $quantity }}</span>
@endif
</a>
@else
<span class="ml-2 text-nowrap text-right {{ $nameClass }}">
{{ number_format($priceTaxed, 2, ',', ' ') }} € TTC
@if (! empty($quantity))
<span class="d-block text-muted" style="font-size: 0.85em;">Qté min. {{ $quantity }}</span>
@endif
</span>
@endif
@else
<span class="ml-2 text-muted"></span>
@endif
</div>
@if (!empty($channel['all_offers']))
<ul class="list-unstyled mb-0 mt-1" style="padding-left: 0.75em;">
@foreach ($channel['all_offers'] as $offer)
@php
$isSelectedOffer = $offer['id'] === $channel['offer_id'];
$offerClass = $offer['is_active'] ? 'text-dark' : 'text-muted';
$stockClass = $offer['stock_current'] > 0 ? 'text-success' : 'text-danger';
@endphp
<li class="small {{ $offerClass }}" style="font-size: 0.85em;">
<a href="{{ route('Admin.Shop.Offers.edit', $offer['id']) }}" target="_blank" rel="noopener" title="Ouvrir l'offre" class="text-decoration-none {{ $offerClass }} admin-link-group admin-offer-link">
<div class="d-flex justify-content-between align-items-start">
<div>
<span style="opacity: 0.5;">{{ $isSelectedOffer ? '▸' : '○' }}</span>
@if ($offer['variation_name'])
{{ $offer['variation_name'] }}
@endif
- Stock: <strong class="{{ $stockClass }}">{{ $offer['stock_current'] }}</strong>
@if (!$offer['is_active'])
<span class="text-muted">(inactive)</span>
@endif
</div>
<div class="text-right text-nowrap ml-2">
{{ number_format($offer['price_taxed'], 2, ',', ' ') }} €
@if ($offer['quantity'] > 1)
<span class="text-muted">(min {{ $offer['quantity'] }})</span>
@endif
</div>
</div>
</a>
</li>
@endforeach
</ul>
@endif
</li>
@endforeach
</ul>
@@ -103,3 +157,67 @@
@endsection
@include('load.layout.modal')
@if (auth('web')->check() && !empty($article['available_sale_channels']))
@push('styles')
<style>
#article-admin-offers .admin-link-group {
transition: background-color 0.15s ease;
border-radius: 3px;
}
#article-admin-offers .admin-price-link {
display: inline-block;
padding: 2px 4px;
margin: -2px -4px;
}
#article-admin-offers .admin-offer-link {
display: block;
padding: 2px 4px;
margin: -2px -4px;
}
#article-admin-offers .admin-link-group:hover,
#article-admin-offers .admin-link-group:focus,
#article-admin-offers .admin-link-group.linked-hover {
background-color: rgba(0, 123, 255, 0.1);
text-decoration: none;
}
</style>
@endpush
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
const container = document.getElementById('article-admin-offers');
if (!container) {
return;
}
const links = Array.from(container.querySelectorAll('a.admin-link-group[href]'));
const grouped = new Map();
links.forEach((link) => {
const href = link.getAttribute('href');
if (!grouped.has(href)) {
grouped.set(href, []);
}
grouped.get(href).push(link);
});
grouped.forEach((group) => {
group.forEach((link) => {
const addHighlight = () => group.forEach((item) => item.classList.add('linked-hover'));
const removeHighlight = () => group.forEach((item) => item.classList.remove('linked-hover'));
link.addEventListener('mouseenter', addHighlight);
link.addEventListener('mouseleave', removeHighlight);
link.addEventListener('focus', addHighlight);
link.addEventListener('blur', removeHighlight);
});
});
});
</script>
@endpush
@endif

View File

@@ -1,7 +1,7 @@
<div class="row mt-3 address-row" data-address-id="{{ $address['id'] }}">
<div class="col-1">
@php
$inputName = isset($prefix) && $prefix ? $prefix.'[address_id]' : 'address_id';
$inputName = $inputName ?? (isset($prefix) && $prefix ? $prefix.'[address_id]' : 'address_id');
$currentValue = $selected ?? null;
@endphp
<x-form.radios.icheck name="{{ $inputName }}" val="{{ $address['id'] }}"

View File

@@ -3,6 +3,7 @@
@include('Shop.Customers.partials.address_item', [
'address' => $address,
'prefix' => $prefix ?? null,
'inputName' => $inputName ?? null,
'with_name' => $with_name ?? false,
'selected' => $selected ?? null,
])
@@ -44,6 +45,7 @@
<script>
(function() {
var prefix = '{{ $prefix }}';
var inputName = '{{ $inputName ?? '' }}';
var $formContainer = $('#add_address_container_{{ $prefix }}');
var $list = $('#addresses_list_{{ $prefix }}');
var storeUrl = '{{ route('Shop.Customers.address.store') }}';
@@ -69,7 +71,7 @@
$.ajax({
url: storeUrl,
method: 'POST',
data: data + '&prefix=' + prefix,
data: data + '&prefix=' + prefix + (inputName ? '&input_name=' + inputName : ''),
success: function(response) {
if (response.html) {
$list.append(response.html);

View File

@@ -57,6 +57,7 @@
'name' => 'phone',
'value' => $customer['phone'] ?? (old('phone') ?? ''),
'label' => 'Téléphone',
'required' => true,
])
</div>
</div>

View File

@@ -4,8 +4,20 @@
@section('content')
<div class="row">
<div class="col-12">
{!! $content !!}
<div class="col-12 text-center py-5">
<i class="fa fa-check-circle text-success" style="font-size: 5rem;"></i>
<div class="mt-4" style="font-size: 1.2rem;">
{!! $content !!}
</div>
@if($payment_label ?? false)
<div class="mt-3" style="font-size: 1.1rem;">
Votre commande a bien été enregistrée, elle vous sera expédiée dès réception de votre {{ $payment_label }}.
</div>
<div class="mt-3" style="font-size: 1.1rem;">
<i class="fa fa-exclamation-triangle text-warning mr-1"></i>
Sans réception de votre paiement au bout de 30 jours, votre commande sera annulée.
</div>
@endif
</div>
</div>
@endsection

View File

@@ -40,7 +40,10 @@
<div class="col-sm-12 col-lg-4">
<x-card class='shadow'>
<div id="basketTotal">
@include('Shop.Baskets.partials.basketTotal', ['basket' => $basket])
@include('Shop.Baskets.partials.basketTotal', [
'basket' => $basket,
'sale_channel' => $basket['sale_channel'] ?? null,
])
</div>
</x-card>
</div>

View File

@@ -1,9 +1,11 @@
<div id="registred">
<x-layout.collapse id="invoice_addresses" title="Adresse de facturation" class="rounded-lg mb-3" uncollapsed=true>
@include('Shop.Orders.partials.addresses', [
'addresses' => $customer['invoice_addresses'] ?? false,
'prefix' => 'invoice',
'name' => 'invoice[invoice_address_id]',
@include('Shop.Customers.partials.addresses', [
'addresses' => $customer['invoice_addresses'] ?? [],
'prefix' => 'invoices',
'inputName' => 'invoice[invoice_address_id]',
'with_name' => true,
'selected' => $customer['invoice_address_id'] ?? null,
])
</x-layout.collapse>
@@ -13,10 +15,12 @@
<x-layout.collapse id="delivery_addresses" title="Adresse de livraison" class="rounded-lg mb-3 d-none"
uncollapsed=true>
@include('Shop.Orders.partials.addresses', [
'addresses' => $customer['delivery_addresses'] ?? false,
'prefix' => 'delivery',
'name' => 'delivery_address_id',
@include('Shop.Customers.partials.addresses', [
'addresses' => $customer['delivery_addresses'] ?? [],
'prefix' => 'deliveries',
'inputName' => 'delivery_address_id',
'with_name' => true,
'selected' => $customer['delivery_address_id'] ?? null,
])
@include('Shop.Orders.partials.shipping')
</x-layout.collapse>

View File

@@ -1,8 +1,6 @@
<h1 style="font-size: 1.5em;">
<h1 class="breadcrumb-title">
@foreach($breadcrumb ?? [] as $parent)
<a href="{{ route('Shop.Categories.show', ['id' => $parent['id']]) }}" style="text-decoration: none; color: inherit;">{{ $parent['name'] }}</a> /
<a href="{{ route('Shop.Categories.show', ['id' => $parent['id']]) }}" class="breadcrumb-link">{{ $parent['name'] }}</a> /
@endforeach
<span style="font-size: 1.4em;">
{{ $category['name'] }}
</span>
<span class="breadcrumb-current">{{ $category['name'] }}</span>
</h1>

View File

@@ -1,7 +1,7 @@
<div class="row">
<div class="col-8">
<h1 style="font-size: 2em;">{{ $category['name'] }}</h1>
<h3 style="font-size: 1.2em;">{!! $category['description'] !!}</h3>
<h1 class="category-title">{{ $category['name'] }}</h1>
<h3 class="category-description">{!! $category['description'] !!}</h3>
</div>
<div class="col-4">
@include('Shop.layout.partials.category_add')
@@ -12,4 +12,4 @@
<div class="col-12">
@include('Shop.layout.partials.category_articles')
</div>
</div>
</div>

View File

@@ -1,7 +1,7 @@
<div class="row">
<div class="row mx-n1">
@if ($articles ?? false)
@foreach ($articles as $product_name => $article)
<div class="col-lg-3 col-xs-12 mb-3">
<div class="category-card col-6 col-md-4 col-lg-3 mb-2 px-1">
@include('Shop.Articles.partials.article')
</div>
@endforeach
@@ -46,4 +46,3 @@
});
</script>
@endpush

View File

@@ -0,0 +1,62 @@
@php
$passwordInputId = $passwordInputId ?? 'password';
@endphp
<ul class="list-unstyled small mt-1 mb-0 password-rules" data-input="#{{ $passwordInputId }}" style="display: none;">
<li data-rule="length"><i class="fa fa-fw fa-times"></i> Au moins 8 caractères</li>
<li data-rule="lowercase"><i class="fa fa-fw fa-times"></i> Au moins une lettre minuscule</li>
<li data-rule="uppercase"><i class="fa fa-fw fa-times"></i> Au moins une lettre majuscule</li>
<li data-rule="number"><i class="fa fa-fw fa-times"></i> Au moins un chiffre</li>
<li data-rule="special"><i class="fa fa-fw fa-times"></i> Au moins un caractère spécial</li>
</ul>
@once
@push('css')
<style>
.password-rules li {
color: #dc3545;
transition: all 0.3s;
}
.password-rules li.valid {
display: none;
}
</style>
@endpush
@push('js')
<script>
$(function() {
$('.password-rules').each(function() {
var $rules = $(this);
var inputSelector = $rules.data('input');
var $input = $(inputSelector);
if (!$input.length) return;
var checks = {
length: function(v) { return v.length >= 8; },
lowercase: function(v) { return /[a-z]/.test(v); },
uppercase: function(v) { return /[A-Z]/.test(v); },
number: function(v) { return /[0-9]/.test(v); },
special: function(v) { return /[^A-Za-z0-9]/.test(v); }
};
$input.on('input keyup', function() {
var val = $(this).val();
if (val.length === 0) {
$rules.hide();
return;
}
$rules.find('li').each(function() {
var rule = $(this).data('rule');
if (checks[rule]) {
$(this).toggleClass('valid', checks[rule](val));
}
});
$rules.show();
});
});
});
</script>
@endpush
@endonce

View File

@@ -11,10 +11,12 @@
<label>Mot de passe *</label>
{{ Form::password('password', [
'class' => 'form-control',
'id' => 'password',
'placeholder' => __('boilerplate::auth.fields.password'),
'required',
]) }}
{!! $errors->registration->first('password', '<p class="text-danger"><strong>:message</strong></p>') !!}
@include('Shop.auth.partials.password_rules', ['passwordInputId' => 'password'])
</div>
</div>
<div class="col-6">

View File

@@ -13,7 +13,8 @@
<label for="new-password" class="col-md-6 control-label text-right">Nouveau mot de passe</label>
<div class="col-md-6">
<input id="new-password" type="password" class="form-control" name="new-password" required>
<input id="new-password" type="password" class="form-control" name="new-password">
@include('Shop.auth.partials.password_rules', ['passwordInputId' => 'new-password'])
</div>
</div>
@@ -21,6 +22,6 @@
<label for="new-password-confirm" class="col-md-6 control-label text-right">Confirmez votre mot de passe</label>
<div class="col-md-6">
<input id="new-password-confirm" type="password" class="form-control" name="new-password_confirmation" required>
<input id="new-password-confirm" type="password" class="form-control" name="new-password_confirmation">
</div>
</div>

View File

@@ -1,18 +1,28 @@
<div class="row bg-light">
<div class="row bg-light align-items-center">
<div class="col-sm-12 col-lg-5">
<div class="col-6 col-lg-5 d-flex align-items-center">
<a href="/"><img src="/img/logo.png" height="52" alt="Jardin'Envie"></a>
<span class="green ml-3">Variétés Paysannes de la Semence à l'Assiette</span>
<span class="green ml-3 d-none d-md-inline">Variétés Paysannes de la Semence à l'Assiette</span>
</div>
<div class="col-sm-12 col-lg-4 pt-2">
<div class="col-12 col-lg-4 pt-2 order-3 order-lg-2">
@include('Shop.layout.partials.search')
</div>
<div class="col-sm-12 col-lg-3 pt-2 text-right">
<div class="col-6 col-lg-3 pt-2 text-right order-2 order-lg-3 d-flex justify-content-end align-items-center">
@include('Shop.layout.partials.header-catalog')
@include('Shop.layout.partials.header-profile')
@include('Shop.layout.partials.header-basket')
</div>
</div>
<div class="row d-lg-none bg-green-dark">
<div class="col-12 p-0">
<div class="collapse" id="navbarContentMobile">
<nav class="navbar navbar-dark p-0">
@include('Shop.layout.partials.sections-menu-list')
</nav>
</div>
</div>
</div>

View File

@@ -10,6 +10,13 @@
<div class="input-group-append">
<span class="input-group-text"><i class="btn btn-sm fa fa-search"></i></span>
</div>
<div class="input-group-append d-lg-none">
<button class="navbar-toggler navbar-light" type="button" data-toggle="collapse"
data-target="#navbarContentMobile" aria-controls="navbarContentMobile" aria-expanded="false"
aria-label="Menu catégories">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</div>
</form>

View File

@@ -0,0 +1,19 @@
<ul class="navbar-nav w-100">
@foreach ($categories as $menu)
<li class="nav-item dropdown megamenu p-2 col
@if (in_array($menu['id'], [$category['id'] ?? false, $category['parent_id'] ?? false, $category['parent']['parent_id'] ?? false])) active @endif">
@if ($menu['children'] ?? false)
<a id="megamenu_{{ $menu['id'] }}" href="{{ route('Shop.Categories.show', ['id' => $menu['id']]) }}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="nav-link dropdown-toggle text-uppercase">
{{ $menu['name'] }}
</a>
<div aria-labelledby="megamenu_{{ $menu['id'] }}" class="dropdown-menu border-0 p-0 m-0">
@include('Shop.layout.partials.megamenu')
</div>
@else
<a href="{{ route('Shop.Categories.show', ['id' => $menu['id']]) }}" class="nav-link text-uppercase text-white">
{{ $menu['name'] }}
</a>
@endif
</li>
@endforeach
</ul>

View File

@@ -1,27 +1,9 @@
<div class="row mb-3 bg-green-dark">
<div class="row mb-3 bg-green-dark d-none d-lg-block">
<div class="col-12 pl-0 pr-0">
<nav class="navbar navbar-expand-lg p-0">
<div class="collapse navbar-collapse" id="navbarContent">
<ul class="navbar-nav w-100">
@foreach ($categories as $menu)
<li class="nav-item dropdown megamenu p-2 col
@if (in_array($menu['id'], [$category['id'] ?? false, $category['parent_id'] ?? false, $category['parent']['parent_id'] ?? false])) active @endif">
@if ($menu['children'] ?? false)
<a id="megamenu_{{ $menu['id'] }}" href="{{ route('Shop.Categories.show', ['id' => $menu['id']]) }}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="nav-link dropdown-toggle text-uppercase">
{{ $menu['name'] }}
</a>
<div aria-labelledby="megamenu_{{ $menu['id'] }}" class="dropdown-menu border-0 p-0 m-0">
@include('Shop.layout.partials.megamenu')
</div>
@else
<a href="{{ route('Shop.Categories.show', ['id' => $menu['id']]) }}" class="nav-link text-uppercase text-white">
{{ $menu['name'] }}
</a>
@endif
</li>
@endforeach
</ul>
<nav class="navbar navbar-expand-lg navbar-dark p-0">
<div class="navbar-collapse show" id="navbarContent">
@include('Shop.layout.partials.sections-menu-list')
</div>
</nav>
</div>
</div>
</div>

View File

@@ -15,7 +15,20 @@
@isset($trigger) data-trigger="{{ $trigger }}" @endisset
@isset($container) data-container="{{ $container }}" @endisset
@isset($html) data-html="true" @endisset
@isset($metadata) {{ $metadata }} @endisset>
@isset($metadata) {{ $metadata }} @endisset
@isset($attr)
@if (is_array($attr))
@foreach ($attr as $key => $value)
@if ($value === true)
{{ $key }}
@elseif ($value !== false && $value !== null)
{{ $key }}="{{ $value }}"
@endif
@endforeach
@else
{{ $attr }}
@endif
@endisset>
<i class="fa fa-fw {{ $icon ?? '' }}"></i>
{{ $txt ?? '' }}
</button>

View File

@@ -0,0 +1,6 @@
@include('components.form.button', [
'class' => 'btn-info duplicate ' . ($class ?? ''),
'icon' => 'fa-copy',
'txt' => __('Dupliquer'),
'attr' => ['data-url' => $duplicate_url ?? $duplicateUrl ?? null],
])

View File

@@ -1,7 +1,33 @@
@php
$cancelUrl = $cancel_url ?? $cancelUrl ?? null;
$duplicateUrl = $duplicate_url ?? $duplicateUrl ?? null;
@endphp
@push('header-actions')
<div class="form-buttons d-flex align-items-center ml-3">
@include('components.form.buttons.button-cancel', [
'class' => 'btn-sm mr-2',
'url' => $cancelUrl,
])
@if($duplicateUrl)
@include('components.form.buttons.button-duplicate', [
'class' => 'btn-sm mr-2',
'duplicate_url' => $duplicateUrl,
])
@endif
@include('components.form.buttons.button-save', [
'class' => 'btn-sm',
])
</div>
@endpush
<div class="row pt-0 pb-3">
<div class="col-12">
<div class="text-right form-buttons">
@include('components.form.buttons.button-cancel')
@include('components.form.buttons.button-cancel', ['url' => $cancelUrl])
@if($duplicateUrl)
@include('components.form.buttons.button-duplicate', ['duplicate_url' => $duplicateUrl])
@endif
@include('components.form.buttons.button-save')
</div>
</div>

View File

@@ -1,15 +1,18 @@
<div class="content-header pt-2 pb-1">
<div class="container-fluid">
<div class="row mb-2 align-items-end">
<div class="col-sm-6">
<h1 class="m-0 text-dark">
{{ $title ?? null}}
@isset($subtitle)
<small class="font-weight-light ml-1 text-md">{{ $subtitle }}</small>
@endisset
</h1>
<div class="row mb-2 align-items-center">
<div class="col-sm-8">
<div class="d-flex align-items-center flex-wrap">
<h1 class="m-0 text-dark d-flex align-items-center flex-grow-1">
{{ $title ?? null}}
@isset($subtitle)
<small class="font-weight-light ml-1 text-md">{{ $subtitle }}</small>
@endisset
</h1>
@stack('header-actions')
</div>
</div>
<div class="col-sm-6">
<div class="col-sm-4">
<ol class="breadcrumb float-sm-right text-sm">
<li class="breadcrumb-item">
<a href="{{ route('boilerplate.dashboard') }}">

View File

@@ -94,6 +94,18 @@
@stack('scripts')
@stack('js')
<script>
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.form-buttons .duplicate').forEach(function(btn) {
btn.addEventListener('click', function() {
var url = this.dataset.url || this.getAttribute('data-url');
if (url) {
window.location = url;
}
});
});
});
</script>
</body>

View File

@@ -39,7 +39,7 @@
''); // Empty tags
},
skin: "oxide",
content_css: 'oxide',
content_css: 'default',
language: '{{ App::getLocale() }}',
file_picker_callback: function(callback, value, meta) {
var x = window.innerWidth || document.documentElement.clientWidth || document.getElementsByTagName(

View File

@@ -9,14 +9,16 @@
var selector = (typeof(sel) == 'undefined') ? '.save' : sel;
$(selector).off().click(function(e) {
if (typeof initValidator === 'function') {
console.log('click');
e.preventDefault();
if ($(form).valid()) {
$(this).prop("disabled", true);
$(this).html($(this).data('loading-text'));
$(form).submit();
} else {
console.log('erreur');
const $firstInvalid = $(form).find(':invalid, .error').first();
if ($firstInvalid.length) {
$firstInvalid.focus();
}
}
} else {
$(form).submit();

View File

@@ -75,12 +75,12 @@
if (typeof(tinyMCE) != 'undefined') {
tinyMCE.triggerSave();
}
$('form ' + form_id).submit();
getModalForm(form_id).trigger('submit');
status = 1;
}
function handlePostModal(form_id, url_save, callback) {
$('form ' + form_id).submit(function(e) {
getModalForm(form_id).off('submit').on('submit', function(e) {
e.preventDefault();
var formData = new FormData(this);
$.ajax({
@@ -98,8 +98,17 @@
});
});
}
function getModalForm(form_id) {
var $form = $(form_id);
if (! $form.length) {
$form = $('form' + form_id);
}
return $form;
}
</script>
@endpush
@php(define('LOAD_MODAL', true))
@endif
@endif

View File

@@ -14,4 +14,5 @@ Route::prefix('Articles')->name('Articles.')->group(function () {
Route::get('getProductImages/{product_id?}/{model?}', 'ArticleController@getProductImages')->name('getProductImages');
Route::post('toggleVisible', 'ArticleController@toggleVisible')->name('toggleVisible');
Route::post('toggleHomepage', 'ArticleController@toggleHomepage')->name('toggleHomepage');
Route::get('duplicate/{id}', 'ArticleController@duplicate')->name('duplicate');
});

View File

@@ -1,8 +1,8 @@
<?php
Route::middleware('auth')->prefix('Admin')->namespace('Admin')->name('Admin.')->group(function () {
Route::get('{period?}', 'HomeController@index')->name('home');
include __DIR__.'/Botanic/route.php';
include __DIR__.'/Core/route.php';
include __DIR__.'/Shop/route.php';
Route::get('{period?}', 'HomeController@index')->name('home');
});

View File

@@ -1,5 +1,8 @@
<?php
Route::prefix('Offres')->name('Offers.')->group(function () {
Route::get('show/{id}', 'OfferController@show')->name('show');
// Public offer pages are not exposed; keep the route returning 404 to avoid leaking data.
Route::get('show/{id}', function () {
abort(404);
})->name('show');
});