-
Notifications
You must be signed in to change notification settings - Fork 82
Shopping list #2969
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Shopping list #2969
Changes from 146 commits
d9394c8
4da1c78
674bcfb
eb16b86
08d4f86
ab19ccd
6fd2514
16bf9ab
3bf4298
fe36e0f
1dd082e
10542d7
05b9dd7
b19fa53
86f8124
7228fb6
b1c108e
15a2ef5
093ba65
d41dc22
63d1bfb
bc6b1b0
edf0209
a84fcc1
cf8c65c
e0f7c8c
d9666e1
4fb06ec
89a0b62
d6deae0
bba2f96
5dfbc6c
fffc6e8
eb654b0
ff8de38
545950b
99e2d18
edebada
291e9f7
a33b54b
794dd19
831fa17
bf1daae
7c5afb6
248bc55
6b6a21f
8e2882e
dafdc7c
b39d9e3
5ded619
57da608
e05f97d
e98cb33
59dfd31
2be095b
9e0c4c6
30ca955
0bde436
15eaf25
237335c
b4af677
f2238c2
cdec608
802c4d6
c62dcdf
3618acf
3163d60
c239c6f
05fbb13
a64d925
111dee3
0cc35ac
9bed077
00e5ae9
d5b814e
b93b95f
d23007f
d6cc03a
3698b30
442db3a
feaf270
344b833
f497c6c
ba6c88f
1193848
efc3c70
2a1751c
6e70de2
939f0c5
64df157
5feb876
550e0f7
5b95c87
890e4c8
7c82d70
7e70fdc
37b314e
c070971
e889095
11e0ceb
5682d37
ecc4982
d214221
a144b10
0a459bc
36f524d
7b71b5b
e45e597
06f40ad
947cd10
5c4230e
56f3ac9
2703f3b
7d4a168
11a3d80
68d266a
1c8fe53
6c5650f
e20e2f2
4fb4b0e
8cb93d6
e3f9d07
4824806
2131e64
1992e37
d4ed7a2
35876f1
c22c7a1
0ea9e6d
205417f
340848c
22e061f
13dabf5
611bde2
62acde4
54d16ef
49d62b5
4123b78
f91361c
7c19147
ff021d1
8eac32c
fe70314
1112ed7
0c47ea7
33932ed
45f65b0
14adf78
4661503
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| // Shopping list service | ||
| import ShoppingList from '@ibexa-shopping-list/src/bundle/Resources/public/js/component/shopping.list'; | ||
| // The Add to shopping list interaction | ||
| import { AddToShoppingList } from '@ibexa-shopping-list/src/bundle/Resources/public/js/component/add.to.shopping.list'; | ||
| // List of all user's shopping lists | ||
| import { ShoppingListsList } from '@ibexa-shopping-list/src/bundle/Resources/public/js/component/shopping.lists.list'; | ||
|
|
||
| (function (global: Window, doc: Document) { | ||
| const shoppingList = new ShoppingList(); | ||
| shoppingList.init(); // Fetch user's shopping lists | ||
|
|
||
| const addToShoppingListsNodes = doc.querySelectorAll<HTMLDivElement>('.ibexa-sl-add-to-shopping-list'); | ||
| addToShoppingListsNodes.forEach((addToShoppingListNode) => { | ||
| const addToShoppingList = new AddToShoppingList({ node: addToShoppingListNode, ListClass: ShoppingListsList }); | ||
|
|
||
| addToShoppingList.init(); | ||
| }); | ||
| })(window, window.document); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| ibexa: | ||
| system: | ||
| default: | ||
| content_view: | ||
| full: | ||
| product: | ||
| controller: 'App\Controller\ProductViewController::viewAction' | ||
| template: '@ibexadesign/full/product.html.twig' | ||
| match: | ||
| '@Ibexa\Contracts\ProductCatalog\ViewMatcher\ProductBased\IsProduct': true |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| <?php declare(strict_types=1); | ||
|
|
||
| namespace App\Controller; | ||
|
|
||
| use Ibexa\Contracts\Core\Repository\Iterator\BatchIterator; | ||
| use Ibexa\Contracts\ProductCatalog\Iterator\BatchIteratorAdapter\ProductVariantFetchAdapter; | ||
| use Ibexa\Contracts\ProductCatalog\Local\LocalProductServiceInterface; | ||
| use Ibexa\Contracts\ProductCatalog\Values\Product\ProductVariantQuery; | ||
| use Ibexa\Core\MVC\Symfony\View\ContentView; | ||
| use Ibexa\Core\MVC\Symfony\View\View; | ||
| use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | ||
| use Symfony\Component\HttpFoundation\Request; | ||
|
|
||
| class ProductViewController extends AbstractController | ||
| { | ||
| public function __construct(private readonly LocalProductServiceInterface $productService) | ||
| { | ||
| } | ||
|
|
||
| public function viewAction(Request $request, ContentView $view): View | ||
| { | ||
| $product = $this->productService->getProductFromContent($view->getContent()); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can it be written in a way that support remote PIM, so PIM where products are not content-based? |
||
| if ($product->isBaseProduct()) { | ||
| $view->addParameters([ | ||
| 'variants' => new BatchIterator(new ProductVariantFetchAdapter( | ||
| $this->productService, | ||
| $product, | ||
| new ProductVariantQuery(), | ||
| )), | ||
| ]); | ||
| } | ||
|
|
||
| return $view; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| {% extends '@ibexadesign/pagelayout.html.twig' %} | ||
|
|
||
| {% set product = content|ibexa_get_product %} | ||
|
|
||
| {% block meta %} | ||
| {% set token = csrf_token ?? csrf_token(ibexa_get_rest_csrf_token_intention()) %} | ||
| <meta name="CSRF-Token" content="{{ token }}"/> | ||
| <meta name="SiteAccess" content="{{ app.request.get('siteaccess').name }}"/> | ||
| {% endblock %} | ||
|
|
||
| {% block content %} | ||
| <span>{{ ibexa_content_name(content) }}</span> | ||
| <code>{{ product.code }}</code> | ||
| {% if not product.isBaseProduct() and can_view_shopping_list and can_edit_shopping_list %} | ||
| {% set component %} | ||
| {% include '@ibexadesign/shopping_list/component/add_to_shopping_list/add_to_shopping_list.html.twig' with { | ||
| product_code: product.code, | ||
| } %} | ||
| {% endset %} | ||
| {{ _self.add_to_shopping_list(product, component) }} | ||
| {% endif %} | ||
|
|
||
| {% if product.isBaseProduct() %} | ||
| <ul> | ||
| {% for variant in variants %} | ||
| <li> | ||
| <span>{{ variant.name }}</span> | ||
| <code>{{ variant.code }}</code> | ||
| {% if can_view_shopping_list and can_edit_shopping_list %} | ||
| {% set component %} | ||
| {% include '@ibexadesign/shopping_list/component/add_to_shopping_list/add_to_shopping_list.html.twig' with { | ||
| product_code: variant.code, | ||
| } %} | ||
| {% endset %} | ||
| {{ _self.add_to_shopping_list(variant, component) }} | ||
| {% endif %} | ||
| </li> | ||
| {% endfor %} | ||
| </ul> | ||
| {% endif %} | ||
| {% endblock %} | ||
|
|
||
| {% block javascripts %} | ||
| {{ encore_entry_script_tags('add-to-shopping-list-js') }} | ||
| {% endblock %} | ||
|
|
||
| {% macro add_to_shopping_list(product, component) %} | ||
| {% set widget_id = 'add-to-shopping-list-' ~ product.code|slug %} | ||
| <div style="display: inline-block;"> | ||
| <button onclick="(function(){let e=document.getElementById('{{ widget_id }}'); e.style.display=('none'===window.getComputedStyle(e).display)?'block':'none';})()"> | ||
| Add to shopping list | ||
| </button> | ||
| <div id="{{ widget_id }}" style="display: none; position: absolute; background: #fff;"> | ||
| {{ component }} | ||
| </div> | ||
| </div> | ||
| {% endmacro %} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| const fs = require('fs'); | ||
| const path = require('path'); | ||
| const Encore = require('@symfony/webpack-encore'); | ||
| const getWebpackConfigs = require('@ibexa/frontend-config/webpack-config/get-configs'); | ||
| const customConfigsPaths = require('./var/encore/ibexa.webpack.custom.config.js'); | ||
|
|
||
| const customConfigs = getWebpackConfigs(Encore, customConfigsPaths); | ||
| const isReactBlockPathCreated = fs.existsSync('./assets/page-builder/react/blocks'); | ||
|
|
||
| Encore.reset(); | ||
| Encore | ||
| .setOutputPath('public/build/') | ||
| .setPublicPath('/build') | ||
| .enableSassLoader() | ||
| .enableReactPreset((options) => { | ||
| options.runtime = 'classic'; | ||
| }) | ||
| .enableSingleRuntimeChunk() | ||
| .copyFiles({ | ||
| from: './assets/images', | ||
| to: 'images/[path][name].[ext]', | ||
| pattern: /\.(png|svg)$/, | ||
| }) | ||
| .configureBabelPresetEnv((config) => { | ||
| config.useBuiltIns = 'usage'; | ||
| config.corejs = 3; | ||
| }); | ||
|
|
||
| // Welcome page stylesheets | ||
| Encore.addEntry('welcome-page-css', [ | ||
| path.resolve(__dirname, './assets/scss/welcome-page.scss'), | ||
| ]); | ||
|
|
||
| // Welcome page javascripts | ||
| Encore.addEntry('welcome-page-js', [ | ||
| path.resolve(__dirname, './assets/js/welcome.page.js'), | ||
| ]); | ||
|
|
||
| if (isReactBlockPathCreated) { | ||
| // React Blocks javascript | ||
| Encore.addEntry('react-blocks-js', './assets/js/react.blocks.js'); | ||
| } | ||
|
|
||
| //Encore.addEntry('app', './assets/app.js'); | ||
|
Check warning on line 44 in code_samples/shopping_list/add_to_shopping_list/webpack.config.js
|
||
|
|
||
| Encore | ||
| .enableTypeScriptLoader() | ||
| .addAliases({ | ||
| '@ibexa-shopping-list': path.resolve('./vendor/ibexa/shopping-list'), | ||
| '@ibexa-admin-ui': path.resolve('./vendor/ibexa/admin-ui'), // @ibexa-admin-ui/…/text.helper dependency | ||
| }) | ||
| .addEntry('add-to-shopping-list-js', [ | ||
| path.resolve(__dirname, './assets/js/add-to-shopping-list.ts'), | ||
| ]) | ||
| ; | ||
|
|
||
| const projectConfig = Encore.getWebpackConfig(); | ||
|
|
||
| projectConfig.name = 'app'; | ||
|
|
||
| module.exports = [...customConfigs, projectConfig]; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| CREATE TABLE ibexa_shopping_list ( | ||
| id INT AUTO_INCREMENT NOT NULL, | ||
| owner_id INT NOT NULL, | ||
| identifier CHAR(36) NOT NULL COMMENT '(DC2Type:guid)', | ||
| name VARCHAR(190) DEFAULT NULL, | ||
| created_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', | ||
| updated_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', | ||
| is_default TINYINT(1) DEFAULT 0 NOT NULL, | ||
| UNIQUE INDEX ibexa_shopping_list_identifier_idx (identifier), | ||
| INDEX ibexa_shopping_list_owner_idx (owner_id), | ||
| INDEX ibexa_shopping_list_default_idx (is_default), | ||
| PRIMARY KEY(id) | ||
| ) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_520_ci` ENGINE = InnoDB; | ||
| CREATE TABLE ibexa_shopping_list_entry ( | ||
| id INT AUTO_INCREMENT NOT NULL, | ||
| shopping_list_id INT NOT NULL, | ||
| product_code VARCHAR(64) NOT NULL, | ||
| identifier CHAR(36) NOT NULL COMMENT '(DC2Type:guid)', | ||
| added_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', | ||
| UNIQUE INDEX ibexa_shopping_list_entry_identifier_idx (identifier), | ||
| INDEX ibexa_shopping_list_entry_list_idx (shopping_list_id), | ||
| INDEX ibexa_shopping_list_entry_product_idx (product_code), | ||
| INDEX ibexa_shopping_list_entry_added_at_idx (added_at), | ||
| UNIQUE INDEX ibexa_shopping_list_entry_unique (shopping_list_id, product_code), | ||
| PRIMARY KEY(id) | ||
| ) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_520_ci` ENGINE = InnoDB; | ||
| ALTER TABLE ibexa_shopping_list | ||
| ADD CONSTRAINT ibexa_shopping_list_owner_fk FOREIGN KEY (owner_id) REFERENCES ibexa_user (contentobject_id) ON UPDATE CASCADE ON DELETE CASCADE; | ||
| ALTER TABLE ibexa_shopping_list_entry | ||
| ADD CONSTRAINT ibexa_shopping_list_entry_list_fk FOREIGN KEY (shopping_list_id) REFERENCES ibexa_shopping_list (id) ON UPDATE CASCADE ON DELETE CASCADE; | ||
| ALTER TABLE ibexa_shopping_list_entry | ||
| ADD CONSTRAINT ibexa_shopping_list_entry_product_fk FOREIGN KEY (product_code) REFERENCES ibexa_product (code) ON UPDATE CASCADE ON DELETE CASCADE; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| CREATE TABLE ibexa_shopping_list ( | ||
| id SERIAL NOT NULL, | ||
| owner_id INT NOT NULL, | ||
| identifier UUID NOT NULL, | ||
| name VARCHAR(190) DEFAULT NULL, | ||
| created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, | ||
| updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, | ||
| is_default BOOLEAN DEFAULT false NOT NULL, | ||
| PRIMARY KEY (id) | ||
| ); | ||
| CREATE UNIQUE INDEX ibexa_shopping_list_identifier_idx ON ibexa_shopping_list (identifier); | ||
| CREATE INDEX ibexa_shopping_list_owner_idx ON ibexa_shopping_list (owner_id); | ||
| CREATE INDEX ibexa_shopping_list_default_idx ON ibexa_shopping_list (is_default); | ||
| COMMENT ON COLUMN ibexa_shopping_list.created_at IS '(DC2Type:datetime_immutable)'; | ||
| COMMENT ON COLUMN ibexa_shopping_list.updated_at IS '(DC2Type:datetime_immutable)'; | ||
| CREATE TABLE ibexa_shopping_list_entry ( | ||
| id SERIAL NOT NULL, | ||
| shopping_list_id INT NOT NULL, | ||
| product_code VARCHAR(64) NOT NULL, | ||
| identifier UUID NOT NULL, | ||
| added_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, | ||
| PRIMARY KEY (id) | ||
| ); | ||
| CREATE UNIQUE INDEX ibexa_shopping_list_entry_identifier_idx ON ibexa_shopping_list_entry (identifier); | ||
| CREATE INDEX ibexa_shopping_list_entry_list_idx ON ibexa_shopping_list_entry (shopping_list_id); | ||
| CREATE INDEX ibexa_shopping_list_entry_product_idx ON ibexa_shopping_list_entry (product_code); | ||
| CREATE INDEX ibexa_shopping_list_entry_added_at_idx ON ibexa_shopping_list_entry (added_at); | ||
| CREATE UNIQUE INDEX ibexa_shopping_list_entry_unique ON ibexa_shopping_list_entry (shopping_list_id, product_code); | ||
| COMMENT ON COLUMN ibexa_shopping_list_entry.added_at IS '(DC2Type:datetime_immutable)'; | ||
| ALTER TABLE ibexa_shopping_list | ||
| ADD CONSTRAINT ibexa_shopping_list_owner_fk FOREIGN KEY (owner_id) REFERENCES ibexa_user (contentobject_id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE; | ||
| ALTER TABLE ibexa_shopping_list_entry | ||
| ADD CONSTRAINT ibexa_shopping_list_entry_list_fk FOREIGN KEY (shopping_list_id) REFERENCES ibexa_shopping_list (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE; | ||
| ALTER TABLE ibexa_shopping_list_entry | ||
| ADD CONSTRAINT ibexa_shopping_list_entry_product_fk FOREIGN KEY (product_code) REFERENCES ibexa_product (code) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| - type: reference | ||
| mode: load | ||
| filename: references/customer_group_references.yml | ||
|
|
||
| - type: role | ||
| mode: create | ||
| metadata: | ||
| identifier: Shopping List User | ||
| policies: | ||
| - module: shopping_list | ||
| function: create | ||
| limitations: | ||
| - identifier: ShoppingListOwner | ||
| values: [self] | ||
| - module: shopping_list | ||
| function: view | ||
| limitations: | ||
| - identifier: ShoppingListOwner | ||
| values: [self] | ||
| - module: shopping_list | ||
| function: edit | ||
| limitations: | ||
| - identifier: ShoppingListOwner | ||
| values: [self] | ||
| - module: shopping_list | ||
| function: delete | ||
| limitations: | ||
| - identifier: ShoppingListOwner | ||
| values: [self] | ||
| actions: | ||
| - action: assign_role_to_user_group | ||
| value: | ||
| id: 'reference:ref__checkout__customers_user_group__content_id' |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| <?php declare(strict_types=1); | ||
|
|
||
| namespace App\Command; | ||
|
|
||
| use Ibexa\Contracts\Core\Repository\PermissionResolver; | ||
| use Ibexa\Contracts\Core\Repository\UserService; | ||
| use Ibexa\Contracts\ShoppingList\ShoppingListServiceInterface; | ||
| use Ibexa\Contracts\ShoppingList\Value\EntryAddStruct; | ||
| use Ibexa\Contracts\ShoppingList\Value\ShoppingListCreateStruct; | ||
| use Ibexa\Contracts\ShoppingList\Value\ShoppingListInterface; | ||
| use Symfony\Component\Console\Attribute\AsCommand; | ||
| use Symfony\Component\Console\Command\Command; | ||
| use Symfony\Component\Console\Input\InputInterface; | ||
| use Symfony\Component\Console\Output\OutputInterface; | ||
|
|
||
| #[AsCommand(name: 'app:shopping-list:filter', description: 'Test Shopping List duplicate entry filtering before addition')] | ||
| class ShoppingListFilterCommand extends Command | ||
| { | ||
| public function __construct( | ||
| private readonly UserService $userService, | ||
| private readonly PermissionResolver $permissionResolver, | ||
| private readonly ShoppingListServiceInterface $shoppingListService | ||
| ) { | ||
| parent::__construct(); | ||
| } | ||
|
|
||
| protected function execute(InputInterface $input, OutputInterface $output): int | ||
| { | ||
| $login = 'admin'; | ||
| $desiredProductCodes = ['TODO', 'TODO']; | ||
| $name = 'shopping-list-test-filtering'; | ||
|
|
||
| $user = $this->userService->loadUserByLogin($login); | ||
| $this->permissionResolver->setCurrentUserReference($user); | ||
|
|
||
| $list = $this->shoppingListService->createShoppingList(new ShoppingListCreateStruct($name)); | ||
|
|
||
| $list = $this->shoppingListService->addEntries($list, [new EntryAddStruct($desiredProductCodes[1])]); | ||
|
|
||
| $filteredProductCodes = array_filter( | ||
| $desiredProductCodes, | ||
| static fn ($productCode): bool => !$list->getEntries()->hasEntryWithProductCode($productCode) | ||
| ); | ||
| $list = $this->shoppingListService->addEntries( | ||
| $list, | ||
| array_map( | ||
| static fn ($productCode): EntryAddStruct => new EntryAddStruct($productCode), | ||
| $filteredProductCodes | ||
| ) | ||
| ); | ||
|
|
||
| $this->displayList($output, $list); | ||
|
|
||
| $this->shoppingListService->deleteShoppingList($list); | ||
|
|
||
| return Command::SUCCESS; | ||
| } | ||
|
|
||
| private function displayList(OutputInterface $output, ShoppingListInterface $list): void | ||
| { | ||
| $output->writeln("{$list->getOwner()->getName()} ({$list->getOwner()->getLogin()})"); | ||
| $output->writeln("{$list->getName()} ({$list->getIdentifier()})" . ($list->isDefault() ? ' [default]' : '')); | ||
| $entries = $list->getEntries(); | ||
| $output->writeln(count($entries) . (count($entries) > 1 ? ' entries' : ' entry')); | ||
| foreach ($entries as $entry) { | ||
| $output->writeln("- <info>{$entry->getProduct()->getName()} ({$entry->getProduct()->getCode()})</info>"); | ||
| } | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.