diff --git a/assets/styles/modes.css b/assets/styles/modes.css index 33343ca..383815b 100644 --- a/assets/styles/modes.css +++ b/assets/styles/modes.css @@ -306,19 +306,211 @@ /* Enhanced mode styles (for future use) */ [data-website-mode="enhanced"] .btn { - background: linear-gradient(45deg, var(--bs-primary), var(--bs-secondary)); - border: 2px solid var(--bs-primary); - transition: all 0.3s ease; + background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan), var(--bs-yellow), var(--bs-green), var(--bs-orange), var(--bs-red)); + background-size: 400% 400%; + animation: rainbowGradient 1s ease infinite; + border: 4px solid var(--bs-white); + font-weight: bold; + text-shadow: 2px 2px 4px rgba(0,0,0,0.5); + position: relative; + overflow: hidden; + transition: all 0.2s ease; } [data-website-mode="enhanced"] .btn:hover { - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0,0,0,0.2); + animation: rainbowGradient 0.5s ease infinite; + box-shadow: 0 0 30px var(--bs-pink), 0 0 60px var(--bs-purple); +} + +[data-website-mode="enhanced"] .btn::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: linear-gradient(45deg, transparent, rgba(255,255,255,0.5), transparent); + transform: rotate(45deg); + animation: spin 0.5s linear infinite; } [data-website-mode="enhanced"] .navbar { - background: linear-gradient(90deg, var(--bs-primary), var(--bs-secondary)); - box-shadow: 0 2px 8px rgba(0,0,0,0.1); + background: linear-gradient(90deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan), var(--bs-yellow), var(--bs-green), var(--bs-orange), var(--bs-red)); + background-size: 200% 200%; + animation: rainbowGradient 2s ease infinite; + box-shadow: 0 0 50px rgba(255, 105, 180, 0.9); + height: auto !important; + min-height: 56px; +} + +[data-website-mode="enhanced"] .navbar-brand { + animation: rainbowText 0.8s infinite; + font-size: 1.8em; + text-shadow: 3px 3px 6px rgba(0,0,0,0.5); + position: relative; + overflow: hidden; +} + +[data-website-mode="enhanced"] .navbar-brand::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: linear-gradient(45deg, transparent, rgba(255,255,255,0.3), transparent); + transform: rotate(45deg); + animation: spin 2s linear infinite; +} + +[data-website-mode="enhanced"] .navbar-nav .nav-link { + animation: rainbowText 1.2s infinite; + font-weight: bold; + text-shadow: 2px 2px 4px rgba(0,0,0,0.5); + border: 2px solid transparent; + border-radius: 8px; + padding: 8px 16px; + margin: 0 4px; + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} + +[data-website-mode="enhanced"] .navbar-nav .nav-link::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent); + animation: shine 1.5s ease-in-out infinite; +} + +[data-website-mode="enhanced"] .navbar-nav .nav-link:hover { + background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple)); + border-color: var(--bs-white); + box-shadow: 0 0 20px var(--bs-pink); +} + +[data-website-mode="enhanced"] .navbar-nav .nav-link.active { + background: linear-gradient(45deg, var(--bs-yellow), var(--bs-orange)); + border-color: var(--bs-white); + box-shadow: 0 0 25px var(--bs-yellow); +} + +[data-website-mode="enhanced"] .navbar-text { + animation: rainbowText 1.5s infinite; + font-weight: bold; + text-shadow: 2px 2px 4px rgba(0,0,0,0.5); + border: 2px solid var(--bs-white); + border-radius: 8px; + padding: 6px 12px; + background: linear-gradient(45deg, var(--bs-cyan), var(--bs-blue)); + box-shadow: 0 0 15px var(--bs-cyan); +} + +[data-website-mode="enhanced"] .navbar-toggler { + border: 3px solid var(--bs-white); + background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple)); + animation: rainbowGradient 0.6s ease infinite; + box-shadow: 0 0 20px var(--bs-pink); +} + +[data-website-mode="enhanced"] .navbar-toggler:focus { + box-shadow: 0 0 30px var(--bs-pink), 0 0 0 0.2rem rgba(255, 105, 180, 0.5); +} + +[data-website-mode="enhanced"] .navbar-toggler-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(255, 255, 255, 1)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); + animation: spin 1s linear infinite; +} + +[data-website-mode="enhanced"] .dropdown-menu { + background: linear-gradient(135deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan)); + border: 3px solid var(--bs-white); + box-shadow: 0 0 30px rgba(255,105,180,0.8); + animation: rainbowGradient 2s ease infinite; +} + +[data-website-mode="enhanced"] .dropdown-item { + animation: rainbowText 1.8s infinite; + font-weight: bold; + text-shadow: 1px 1px 2px rgba(0,0,0,0.5); + border-bottom: 1px solid rgba(255,255,255,0.3); + transition: all 0.3s ease; +} + +[data-website-mode="enhanced"] .dropdown-item:hover { + background: linear-gradient(45deg, var(--bs-yellow), var(--bs-orange)); + color: var(--bs-white); + box-shadow: 0 0 15px var(--bs-yellow); +} + +[data-website-mode="enhanced"] .navbar-collapse { + background: linear-gradient(135deg, rgba(255,105,180,0.1), rgba(138,43,226,0.1)); + border-radius: 8px; + margin-top: 8px; + padding: 8px; + border: 2px solid var(--bs-pink); +} + +[data-website-mode="enhanced"] h1, [data-website-mode="enhanced"] h2, [data-website-mode="enhanced"] h3 { + animation: rainbowText 1.5s infinite; + text-shadow: 2px 2px 4px rgba(0,0,0,0.3); +} + +[data-website-mode="enhanced"] .table { + background: linear-gradient(135deg, rgba(255,105,180,0.2), rgba(138,43,226,0.2), rgba(0,255,255,0.2)); + animation: rainbowGradient 3s ease infinite; + border: 3px solid var(--bs-pink); + box-shadow: 0 0 30px rgba(255,105,180,0.5); +} + +[data-website-mode="enhanced"] .table th { + background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple)); + color: var(--bs-white); + animation: rainbowGradient 0.8s ease infinite; + text-shadow: 1px 1px 2px rgba(0,0,0,0.5); + font-size: 1.1em; +} + +[data-website-mode="enhanced"] .form-control { + border: 3px solid var(--bs-pink); + box-shadow: 0 0 15px var(--bs-pink); +} + +[data-website-mode="enhanced"] .alert { + animation: rainbowGradient 0.6s ease infinite; + border: 4px solid var(--bs-white); + font-weight: bold; + font-size: 1.1em; +} + +[data-website-mode="enhanced"] .card { + background: linear-gradient(45deg, rgba(255,105,180,0.2), rgba(138,43,226,0.2)); + border: 3px solid var(--bs-purple); + box-shadow: 0 0 35px rgba(138,43,226,0.6); +} + +[data-website-mode="enhanced"] .modal-content { + background: linear-gradient(135deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan)); + border: 4px solid var(--bs-white); + box-shadow: 0 0 50px rgba(255,105,180,0.8); + animation: rainbowGradient 2s ease infinite; +} + +[data-website-mode="enhanced"] .modal-header { + background: linear-gradient(90deg, var(--bs-yellow), var(--bs-orange)); + animation: rainbowGradient 0.8s ease infinite; + font-size: 1.2em; +} + +[data-website-mode="enhanced"] .number-input-wrapper { +} + +[data-website-mode="enhanced"] .number-input-wrapper .btn { + animation: rainbowGradient 0.3s ease infinite; } /* Emoji Footprint Animation */ diff --git a/composer.json b/composer.json index 8e10aae..77a6ce6 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,7 @@ "doctrine/doctrine-bundle": "^2.14", "doctrine/doctrine-migrations-bundle": "^3.4", "doctrine/orm": "^3.3", + "runtime/frankenphp-symfony": "^0.2.0", "symfony/asset": "7.3.*", "symfony/asset-mapper": "7.3.*", "symfony/console": "7.3.*", diff --git a/composer.lock b/composer.lock index 6218895..50db31c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c4121f65f6a9cee00e98a29d74d53e2a", + "content-hash": "0171698e06036913d5ba729e28df03c7", "packages": [ { "name": "composer/semver", @@ -1562,6 +1562,58 @@ }, "time": "2024-09-11T13:17:53+00:00" }, + { + "name": "runtime/frankenphp-symfony", + "version": "0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-runtime/frankenphp-symfony.git", + "reference": "56822c3631d9522a3136a4c33082d006bdfe4bad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-runtime/frankenphp-symfony/zipball/56822c3631d9522a3136a4c33082d006bdfe4bad", + "reference": "56822c3631d9522a3136a4c33082d006bdfe4bad", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0", + "symfony/runtime": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Runtime\\FrankenPhpSymfony\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "kevin@dunglas.dev" + } + ], + "description": "FrankenPHP runtime for Symfony", + "support": { + "issues": "https://github.com/php-runtime/frankenphp-symfony/issues", + "source": "https://github.com/php-runtime/frankenphp-symfony/tree/0.2.0" + }, + "funding": [ + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2023-12-12T12:06:11+00:00" + }, { "name": "symfony/asset", "version": "v7.3.0", diff --git a/src/Controller/DrinkTypeBulkController.php b/src/Controller/DrinkTypeBulkController.php index 855d460..d1ebcdb 100644 --- a/src/Controller/DrinkTypeBulkController.php +++ b/src/Controller/DrinkTypeBulkController.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Controller; use App\Form\BulkEditDrinkTypeStockForm; +use App\Form\BulkEditDrinkTypeWantedStockForm; use App\Repository\DrinkTypeRepository; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -38,7 +39,7 @@ final class DrinkTypeBulkController extends AbstractController $entityManager->flush(); - $this->addFlash('success', 'Wanted stock levels updated successfully!'); + $this->addFlash('success', 'Current stock levels updated successfully!'); return $this->redirectToRoute('app_drink_type_bulk_edit_stock'); } @@ -48,4 +49,38 @@ final class DrinkTypeBulkController extends AbstractController 'drinkTypes' => $drinkTypes, ]); } + + #[Route('/bulk-edit-wanted-stock', name: 'app_drink_type_bulk_edit_wanted_stock')] + public function bulkEditWantedStock( + Request $request, + DrinkTypeRepository $drinkTypeRepository, + EntityManagerInterface $entityManager + ): Response { + $drinkTypes = $drinkTypeRepository->findAll(); + + $form = $this->createForm(BulkEditDrinkTypeWantedStockForm::class, [ + 'drinkTypes' => $drinkTypes, + ]); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $data = $form->getData(); + + foreach ($data['drinkTypes'] as $drinkType) { + $entityManager->persist($drinkType); + } + + $entityManager->flush(); + + $this->addFlash('success', 'Wanted stock levels updated successfully!'); + + return $this->redirectToRoute('app_drink_type_bulk_edit_wanted_stock'); + } + + return $this->render('drink_type/bulk_edit_wanted_stock.html.twig', [ + 'form' => $form->createView(), + 'drinkTypes' => $drinkTypes, + ]); + } } diff --git a/src/Form/BulkEditDrinkTypeStockForm.php b/src/Form/BulkEditDrinkTypeStockForm.php index 6d3962f..1a21016 100644 --- a/src/Form/BulkEditDrinkTypeStockForm.php +++ b/src/Form/BulkEditDrinkTypeStockForm.php @@ -25,7 +25,7 @@ class BulkEditDrinkTypeStockForm extends AbstractType 'by_reference' => false, ]) ->add('save', SubmitType::class, [ - 'label' => 'Update Wanted Stock Levels', + 'label' => 'Update Current Stock Levels', 'attr' => [ 'class' => 'btn btn-primary', ], diff --git a/src/Form/BulkEditDrinkTypeWantedStockForm.php b/src/Form/BulkEditDrinkTypeWantedStockForm.php new file mode 100644 index 0000000..b692bad --- /dev/null +++ b/src/Form/BulkEditDrinkTypeWantedStockForm.php @@ -0,0 +1,41 @@ +add('drinkTypes', CollectionType::class, [ + 'entry_type' => DrinkTypeWantedStockEditType::class, + 'entry_options' => [ + 'label' => false, + ], + 'allow_add' => false, + 'allow_delete' => false, + 'by_reference' => false, + ]) + ->add('save', SubmitType::class, [ + 'label' => 'Update Wanted Stock Levels', + 'attr' => [ + 'class' => 'btn btn-primary', + ], + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => null, + ]); + } +} diff --git a/src/Form/DrinkTypeStockEditType.php b/src/Form/DrinkTypeStockEditType.php index 21489c4..0b78796 100644 --- a/src/Form/DrinkTypeStockEditType.php +++ b/src/Form/DrinkTypeStockEditType.php @@ -11,6 +11,8 @@ use Symfony\Component\Form\Extension\Core\Type\NumberType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraints\GreaterThanOrEqual; +use Symfony\Component\Validator\Constraints\NotBlank; class DrinkTypeStockEditType extends AbstractType { @@ -26,13 +28,22 @@ class DrinkTypeStockEditType extends AbstractType 'readonly' => true, ], ]) - ->add('wantedStock', NumberType::class, [ - 'label' => 'Wanted Stock', + ->add('currentStock', NumberType::class, [ + 'label' => 'Current Stock', 'attr' => [ 'min' => 0, 'step' => 1, 'class' => 'form-control', ], + 'constraints' => [ + new NotBlank([ + 'message' => 'Current stock cannot be blank', + ]), + new GreaterThanOrEqual([ + 'value' => 0, + 'message' => 'Current stock must not be negative', + ]), + ], ]); } diff --git a/src/Form/DrinkTypeWantedStockEditType.php b/src/Form/DrinkTypeWantedStockEditType.php new file mode 100644 index 0000000..d3c7dfc --- /dev/null +++ b/src/Form/DrinkTypeWantedStockEditType.php @@ -0,0 +1,56 @@ +add('id', HiddenType::class, [ + 'mapped' => false, + ]) + ->add('name', TextType::class, [ + 'disabled' => true, + 'attr' => [ + 'readonly' => true, + ], + ]) + ->add('wantedStock', NumberType::class, [ + 'label' => 'Wanted Stock', + 'attr' => [ + 'min' => 0, + 'step' => 1, + 'class' => 'form-control', + ], + 'constraints' => [ + new NotBlank([ + 'message' => 'Wanted stock cannot be blank', + ]), + new GreaterThanOrEqual([ + 'value' => 0, + 'message' => 'Wanted stock must not be negative', + ]), + ], + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => DrinkType::class, + ]); + } +} diff --git a/src/Service/DrinkType/FilterLowStockDrinks.php b/src/Service/DrinkType/FilterLowStockDrinks.php new file mode 100644 index 0000000..d52da3f --- /dev/null +++ b/src/Service/DrinkType/FilterLowStockDrinks.php @@ -0,0 +1,24 @@ + + $drinkType->getCurrentStock() < ($drinkType->getWantedStock() * $this->lowStockMultiplier->getValue()) + ); + } + +} diff --git a/templates/drink_type/bulk_edit_stock.html.twig b/templates/drink_type/bulk_edit_stock.html.twig index 01454f9..f63a43c 100644 --- a/templates/drink_type/bulk_edit_stock.html.twig +++ b/templates/drink_type/bulk_edit_stock.html.twig @@ -1,10 +1,10 @@ {% extends 'base.html.twig' %} -{% block title %}Bulk Edit Drink Type Wanted Stock{% endblock %} +{% block title %}Bulk Edit Drink Type Current Stock{% endblock %} {% block body %}
Drink Type | +Wanted Stock | +
---|---|
+ {{ form_widget(drinkTypeForm.id) }} + {{ form_widget(drinkTypeForm.name) }} + | ++ {{ form_widget(drinkTypeForm.wantedStock) }} + {{ form_errors(drinkTypeForm.wantedStock) }} + | +