fix(admin): products canDeactivate guard, unblock npm, drop dead deps
RC-01 Phase 1 mechanical fixes (verified against current repo state, not blindly reapplied from TODO.md): - admin/products create/edit/duplicate now protected by an unsaved- changes guard (adminProductDirtyGuard), mirroring the existing categories pattern. AdminProductsFacade had zero dirty-tracking before this - added a dirty signal, set true on updateDraft(), cleared on load/create/successful save. Added confirmLeaveUnsaved to the adminProducts i18n section (en/ru/hy) - categories already had its own copy of this key, products didn't. - barry-cache bumped ^0.1.0 -> ^0.9.3 (the pinned range no longer resolved on the registry - ETARGET - which had been silently blocking every npm install/uninstall all cycle). - Removed primeng/primeicons (npm uninstall, now unblocked) - the only consumer (items-carousel) was already deleted in RC PERF-01. - Removed core/search/services/search-history.service.ts, a dead 1-line re-export with zero importers (verified: the real implementation is features/search/services/search-history.service.ts, used by search.facade.ts). Left core/search/models/* alone - those ARE live, imported by catalog components. Verified before touching: HeaderConfig.showProfile toggle is already removed from the header-section editor template (TODO.md was stale on this one) - no change needed, will correct the tracking doc separately. tsc --noEmit clean, npm run build green (bundle unchanged, primeng was already tree-shaken out, this just removes the dead dependency declaration itself). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
177
package-lock.json
generated
177
package-lock.json
generated
@@ -18,8 +18,6 @@
|
|||||||
"@angular/router": "21.1.5",
|
"@angular/router": "21.1.5",
|
||||||
"@angular/service-worker": "21.1.5",
|
"@angular/service-worker": "21.1.5",
|
||||||
"@lucide/angular": "^1.25.0",
|
"@lucide/angular": "^1.25.0",
|
||||||
"primeicons": "^7.0.0",
|
|
||||||
"primeng": "^21.0.3",
|
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.8.0",
|
"tslib": "^2.8.0",
|
||||||
"zone.js": "~0.16.0"
|
"zone.js": "~0.16.0"
|
||||||
@@ -29,6 +27,7 @@
|
|||||||
"@angular/cli": "21.1.5",
|
"@angular/cli": "21.1.5",
|
||||||
"@angular/compiler-cli": "21.1.5",
|
"@angular/compiler-cli": "21.1.5",
|
||||||
"@types/jasmine": "~5.1.0",
|
"@types/jasmine": "~5.1.0",
|
||||||
|
"barry-cache": "^0.9.3",
|
||||||
"jasmine-core": "~5.5.0",
|
"jasmine-core": "~5.5.0",
|
||||||
"karma": "~6.4.0",
|
"karma": "~6.4.0",
|
||||||
"karma-chrome-launcher": "~3.2.0",
|
"karma-chrome-launcher": "~3.2.0",
|
||||||
@@ -3085,48 +3084,6 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"node_modules/@primeuix/motion": {
|
|
||||||
"version": "0.0.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/@primeuix/motion/-/motion-0.0.10.tgz",
|
|
||||||
"integrity": "sha512-PsZwOPq79Scp7/ionshRcQ5xKVf9+zuLcyY5mf6onK8chHT5C9JGphmcIZ4CzcqxuGEpsm8AIbTGy+zS3RtzLA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@primeuix/utils": "^0.6.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.11.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@primeuix/styled": {
|
|
||||||
"version": "0.7.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@primeuix/styled/-/styled-0.7.4.tgz",
|
|
||||||
"integrity": "sha512-QSO/NpOQg8e9BONWRBx9y8VGMCMYz0J/uKfNJEya/RGEu7ARx0oYW0ugI1N3/KB1AAvyGxzKBzGImbwg0KUiOQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@primeuix/utils": "^0.6.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.11.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@primeuix/styles": {
|
|
||||||
"version": "2.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@primeuix/styles/-/styles-2.0.2.tgz",
|
|
||||||
"integrity": "sha512-LNtkJsTonNHF5ag+9s3+zQzm00+LRmffw68QRIHy6S/dam1JpdrrAnUzNYlWbaY7aE2EkZvQmx7Np7+PyHn+ow==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@primeuix/styled": "^0.7.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@primeuix/utils": {
|
|
||||||
"version": "0.6.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@primeuix/utils/-/utils-0.6.3.tgz",
|
|
||||||
"integrity": "sha512-/SLNQSKQ73WbBIsflKVqbpVjCfFYvQO3Sf1LMheXyxh8JqxO4M63dzP56wwm9OPGuCQ6MYOd2AHgZXz+g7PZcg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.11.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@rolldown/binding-android-arm64": {
|
"node_modules/@rolldown/binding-android-arm64": {
|
||||||
"version": "1.0.0-beta.58",
|
"version": "1.0.0-beta.58",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.58.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.58.tgz",
|
||||||
@@ -4091,6 +4048,21 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/barry-cache": {
|
||||||
|
"version": "0.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/barry-cache/-/barry-cache-0.9.3.tgz",
|
||||||
|
"integrity": "sha512-HQmOPyifjXnV/99zOQ7oYsxOjOu4zwj1djC6BgkDvNEVMZftwXDYHEFQyaNXzbl6Zhk7VfxT6YXqBDkWkGPRxw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"serve-handler": "^6.1.7"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"barry-cache": "dist/cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/base64id": {
|
"node_modules/base64id": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
||||||
@@ -6936,9 +6908,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -7678,6 +7650,13 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/path-is-inside": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "(WTFPL OR MIT)"
|
||||||
|
},
|
||||||
"node_modules/path-key": {
|
"node_modules/path-key": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||||
@@ -7812,34 +7791,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/primeicons": {
|
|
||||||
"version": "7.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/primeicons/-/primeicons-7.0.0.tgz",
|
|
||||||
"integrity": "sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/primeng": {
|
|
||||||
"version": "21.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/primeng/-/primeng-21.0.3.tgz",
|
|
||||||
"integrity": "sha512-B5jrDU2vXZ5+ZNtOIxH2pLuLr8C/pwTsvEkppdq9ehgzwRj0eGbh2eoy8EbH8mWkcSXUuH7xneWi9veIrnwZ+Q==",
|
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
|
||||||
"dependencies": {
|
|
||||||
"@primeuix/motion": "^0.0.10",
|
|
||||||
"@primeuix/styled": "^0.7.4",
|
|
||||||
"@primeuix/styles": "^2.0.2",
|
|
||||||
"@primeuix/utils": "^0.6.3",
|
|
||||||
"tslib": "^2.3.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@angular/cdk": "^21.0.0",
|
|
||||||
"@angular/common": "^21.0.0",
|
|
||||||
"@angular/core": "^21.0.7",
|
|
||||||
"@angular/forms": "^21.0.0",
|
|
||||||
"@angular/platform-browser": "^21.0.0",
|
|
||||||
"@angular/router": "^21.0.0",
|
|
||||||
"rxjs": "^6.0.0 || ^7.8.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/proc-log": {
|
"node_modules/proc-log": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz",
|
||||||
@@ -8276,6 +8227,82 @@
|
|||||||
"url": "https://opencollective.com/express"
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/serve-handler": {
|
||||||
|
"version": "6.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.7.tgz",
|
||||||
|
"integrity": "sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bytes": "3.0.0",
|
||||||
|
"content-disposition": "0.5.2",
|
||||||
|
"mime-types": "2.1.18",
|
||||||
|
"minimatch": "3.1.5",
|
||||||
|
"path-is-inside": "1.0.2",
|
||||||
|
"path-to-regexp": "3.3.0",
|
||||||
|
"range-parser": "1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/serve-handler/node_modules/bytes": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/serve-handler/node_modules/content-disposition": {
|
||||||
|
"version": "0.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
|
||||||
|
"integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/serve-handler/node_modules/mime-db": {
|
||||||
|
"version": "1.33.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
|
||||||
|
"integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/serve-handler/node_modules/mime-types": {
|
||||||
|
"version": "2.1.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
|
||||||
|
"integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "~1.33.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/serve-handler/node_modules/path-to-regexp": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz",
|
||||||
|
"integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/serve-handler/node_modules/range-parser": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/serve-static": {
|
"node_modules/serve-static": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
|
||||||
|
|||||||
@@ -31,8 +31,6 @@
|
|||||||
"@angular/router": "21.1.5",
|
"@angular/router": "21.1.5",
|
||||||
"@angular/service-worker": "21.1.5",
|
"@angular/service-worker": "21.1.5",
|
||||||
"@lucide/angular": "^1.25.0",
|
"@lucide/angular": "^1.25.0",
|
||||||
"primeicons": "^7.0.0",
|
|
||||||
"primeng": "^21.0.3",
|
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.8.0",
|
"tslib": "^2.8.0",
|
||||||
"zone.js": "~0.16.0"
|
"zone.js": "~0.16.0"
|
||||||
@@ -42,12 +40,12 @@
|
|||||||
"@angular/cli": "21.1.5",
|
"@angular/cli": "21.1.5",
|
||||||
"@angular/compiler-cli": "21.1.5",
|
"@angular/compiler-cli": "21.1.5",
|
||||||
"@types/jasmine": "~5.1.0",
|
"@types/jasmine": "~5.1.0",
|
||||||
|
"barry-cache": "^0.9.3",
|
||||||
"jasmine-core": "~5.5.0",
|
"jasmine-core": "~5.5.0",
|
||||||
"karma": "~6.4.0",
|
"karma": "~6.4.0",
|
||||||
"karma-chrome-launcher": "~3.2.0",
|
"karma-chrome-launcher": "~3.2.0",
|
||||||
"karma-jasmine": "~5.1.0",
|
"karma-jasmine": "~5.1.0",
|
||||||
"karma-jasmine-html-reporter": "~2.1.0",
|
"karma-jasmine-html-reporter": "~2.1.0",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3"
|
||||||
"barry-cache": "^0.1.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { projectEditorDirtyGuard } from './features/project-editor/guards/projec
|
|||||||
import { adminAuthGuard } from './core/admin-auth/admin-auth.guard';
|
import { adminAuthGuard } from './core/admin-auth/admin-auth.guard';
|
||||||
import { authRoutes } from './core/auth/auth.routes';
|
import { authRoutes } from './core/auth/auth.routes';
|
||||||
import { adminCategoryDirtyGuard } from './features/admin/categories/guards/admin-category-dirty.guard';
|
import { adminCategoryDirtyGuard } from './features/admin/categories/guards/admin-category-dirty.guard';
|
||||||
|
import { adminProductDirtyGuard } from './features/admin/products/guards/admin-product-dirty.guard';
|
||||||
import { AdminLayoutComponent } from './features/admin/shell/admin-layout.component';
|
import { AdminLayoutComponent } from './features/admin/shell/admin-layout.component';
|
||||||
import { environment } from '../environments/environment';
|
import { environment } from '../environments/environment';
|
||||||
|
|
||||||
@@ -82,6 +83,7 @@ const coreRoutes: Routes = [
|
|||||||
{
|
{
|
||||||
path: 'products/create',
|
path: 'products/create',
|
||||||
loadComponent: () => import('./features/admin/products/pages/admin-product-editor-page.component').then(m => m.AdminProductEditorPageComponent),
|
loadComponent: () => import('./features/admin/products/pages/admin-product-editor-page.component').then(m => m.AdminProductEditorPageComponent),
|
||||||
|
canDeactivate: [adminProductDirtyGuard],
|
||||||
data: {
|
data: {
|
||||||
titleKey: 'adminShell.pages.productCreate.title',
|
titleKey: 'adminShell.pages.productCreate.title',
|
||||||
descriptionKey: 'adminShell.pages.productCreate.description',
|
descriptionKey: 'adminShell.pages.productCreate.description',
|
||||||
@@ -91,6 +93,7 @@ const coreRoutes: Routes = [
|
|||||||
{
|
{
|
||||||
path: 'products/:id/edit',
|
path: 'products/:id/edit',
|
||||||
loadComponent: () => import('./features/admin/products/pages/admin-product-editor-page.component').then(m => m.AdminProductEditorPageComponent),
|
loadComponent: () => import('./features/admin/products/pages/admin-product-editor-page.component').then(m => m.AdminProductEditorPageComponent),
|
||||||
|
canDeactivate: [adminProductDirtyGuard],
|
||||||
data: {
|
data: {
|
||||||
titleKey: 'adminShell.pages.productEdit.title',
|
titleKey: 'adminShell.pages.productEdit.title',
|
||||||
descriptionKey: 'adminShell.pages.productEdit.description',
|
descriptionKey: 'adminShell.pages.productEdit.description',
|
||||||
@@ -100,6 +103,7 @@ const coreRoutes: Routes = [
|
|||||||
{
|
{
|
||||||
path: 'products/:id/duplicate',
|
path: 'products/:id/duplicate',
|
||||||
loadComponent: () => import('./features/admin/products/pages/admin-product-editor-page.component').then(m => m.AdminProductEditorPageComponent),
|
loadComponent: () => import('./features/admin/products/pages/admin-product-editor-page.component').then(m => m.AdminProductEditorPageComponent),
|
||||||
|
canDeactivate: [adminProductDirtyGuard],
|
||||||
data: {
|
data: {
|
||||||
titleKey: 'adminShell.pages.productDuplicate.title',
|
titleKey: 'adminShell.pages.productDuplicate.title',
|
||||||
descriptionKey: 'adminShell.pages.productDuplicate.description',
|
descriptionKey: 'adminShell.pages.productDuplicate.description',
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export { SearchHistoryService } from '../../../features/search/services/search-history.service';
|
|
||||||
@@ -93,6 +93,7 @@ export class AdminProductsFacade {
|
|||||||
readonly loading = signal(false);
|
readonly loading = signal(false);
|
||||||
readonly selectedIds = signal<string[]>([]);
|
readonly selectedIds = signal<string[]>([]);
|
||||||
readonly draft = signal<AdminProduct | null>(null);
|
readonly draft = signal<AdminProduct | null>(null);
|
||||||
|
readonly dirty = signal(false);
|
||||||
readonly editorMode = signal<AdminProductEditorMode>('create');
|
readonly editorMode = signal<AdminProductEditorMode>('create');
|
||||||
|
|
||||||
readonly hasSelection = computed(() => this.selectedIds().length > 0);
|
readonly hasSelection = computed(() => this.selectedIds().length > 0);
|
||||||
@@ -283,20 +284,22 @@ export class AdminProductsFacade {
|
|||||||
startCreate(): void {
|
startCreate(): void {
|
||||||
this.editorMode.set('create');
|
this.editorMode.set('create');
|
||||||
this.draft.set(this.formFactory.createEmpty());
|
this.draft.set(this.formFactory.createEmpty());
|
||||||
|
this.dirty.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadForEdit(id: string, mode: AdminProductEditorMode = 'edit'): void {
|
loadForEdit(id: string, mode: AdminProductEditorMode = 'edit'): void {
|
||||||
this.editorMode.set(mode);
|
this.editorMode.set(mode);
|
||||||
if (mode === 'duplicate') {
|
if (mode === 'duplicate') {
|
||||||
this.gateway.duplicateProduct(id).pipe(take(1)).subscribe({ next: product => this.draft.set(product) });
|
this.gateway.duplicateProduct(id).pipe(take(1)).subscribe({ next: product => { this.draft.set(product); this.dirty.set(false); } });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.gateway.loadProduct(id).pipe(take(1)).subscribe({ next: product => this.draft.set(product ? { ...product } : null) });
|
this.gateway.loadProduct(id).pipe(take(1)).subscribe({ next: product => { this.draft.set(product ? { ...product } : null); this.dirty.set(false); } });
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDraft(patch: Partial<AdminProduct>): void {
|
updateDraft(patch: Partial<AdminProduct>): void {
|
||||||
this.draft.update(current => current ? ({ ...current, ...patch, updatedAt: new Date().toISOString() }) : current);
|
this.draft.update(current => current ? ({ ...current, ...patch, updatedAt: new Date().toISOString() }) : current);
|
||||||
|
this.dirty.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
saveDraft(): void {
|
saveDraft(): void {
|
||||||
@@ -307,7 +310,7 @@ export class AdminProductsFacade {
|
|||||||
? this.gateway.createProduct(draft)
|
? this.gateway.createProduct(draft)
|
||||||
: this.gateway.updateProduct(draft);
|
: this.gateway.updateProduct(draft);
|
||||||
|
|
||||||
request.pipe(take(1)).subscribe({ next: () => this.loadList() });
|
request.pipe(take(1)).subscribe({ next: () => { this.dirty.set(false); this.loadList(); } });
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteOne(id: string): void {
|
deleteOne(id: string): void {
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { inject } from '@angular/core';
|
||||||
|
import { CanDeactivateFn } from '@angular/router';
|
||||||
|
import { AdminProductsFacade } from '../facade/admin-products.facade';
|
||||||
|
import { AdminProductEditorPageComponent } from '../pages/admin-product-editor-page.component';
|
||||||
|
import { TranslateService } from '../../../../i18n/translate.service';
|
||||||
|
|
||||||
|
export const adminProductDirtyGuard: CanDeactivateFn<AdminProductEditorPageComponent> = () => {
|
||||||
|
const facade = inject(AdminProductsFacade);
|
||||||
|
if (!facade.dirty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const translate = inject(TranslateService);
|
||||||
|
return window.confirm(translate.t('adminProducts.confirmLeaveUnsaved'));
|
||||||
|
};
|
||||||
@@ -1543,6 +1543,7 @@ export const en: Translations = {
|
|||||||
recommendNone: 'Nice work — your category structure is in good shape.',
|
recommendNone: 'Nice work — your category structure is in good shape.',
|
||||||
},
|
},
|
||||||
adminProducts: {
|
adminProducts: {
|
||||||
|
confirmLeaveUnsaved: 'You have unsaved changes. Leave without saving?',
|
||||||
emptyTitle: 'No products found',
|
emptyTitle: 'No products found',
|
||||||
emptyDescription: 'Try adjusting your filters, or create a new product.',
|
emptyDescription: 'Try adjusting your filters, or create a new product.',
|
||||||
emptyGuide: 'Good products have a clear title, at least one photo, a price, and a short description — that\'s enough to publish. You can always add more detail later.',
|
emptyGuide: 'Good products have a clear title, at least one photo, a price, and a short description — that\'s enough to publish. You can always add more detail later.',
|
||||||
|
|||||||
@@ -1538,6 +1538,7 @@ export const hy: Translations = {
|
|||||||
recommendNone: 'Հիանալի է․ կատեգորիաների կառուցվածքը լավ վիճակում է։',
|
recommendNone: 'Հիանալի է․ կատեգորիաների կառուցվածքը լավ վիճակում է։',
|
||||||
},
|
},
|
||||||
adminProducts: {
|
adminProducts: {
|
||||||
|
confirmLeaveUnsaved: 'Դուք ունեք չպահպանված փոփոխություններ։ Դո՞ւրս գալ առանց պահպանելու։',
|
||||||
emptyTitle: 'Ապրանքներ չեն գտնվել',
|
emptyTitle: 'Ապրանքներ չեն գտնվել',
|
||||||
emptyDescription: 'Փոխեք ֆիլտրերը կամ ստեղծեք նոր ապրանք։',
|
emptyDescription: 'Փոխեք ֆիլտրերը կամ ստեղծեք նոր ապրանք։',
|
||||||
emptyGuide: 'Լավ ապրանքին բավական է հստակ վերնագիր, առնվազն մեկ լուսանկար, գին և կարճ նկարագրություն՝ հրապարակելու համար։ Մնացածը կարող եք ավելացնել հետո։',
|
emptyGuide: 'Լավ ապրանքին բավական է հստակ վերնագիր, առնվազն մեկ լուսանկար, գին և կարճ նկարագրություն՝ հրապարակելու համար։ Մնացածը կարող եք ավելացնել հետո։',
|
||||||
|
|||||||
@@ -1538,6 +1538,7 @@ export const ru: Translations = {
|
|||||||
recommendNone: 'Отлично — структура категорий в хорошем состоянии.',
|
recommendNone: 'Отлично — структура категорий в хорошем состоянии.',
|
||||||
},
|
},
|
||||||
adminProducts: {
|
adminProducts: {
|
||||||
|
confirmLeaveUnsaved: 'У вас есть несохранённые изменения. Выйти без сохранения?',
|
||||||
emptyTitle: 'Товары не найдены',
|
emptyTitle: 'Товары не найдены',
|
||||||
emptyDescription: 'Измените фильтры или создайте новый товар.',
|
emptyDescription: 'Измените фильтры или создайте новый товар.',
|
||||||
emptyGuide: 'Хорошему товару нужны понятное название, минимум одно фото, цена и короткое описание — этого достаточно для публикации. Остальное можно добавить позже.',
|
emptyGuide: 'Хорошему товару нужны понятное название, минимум одно фото, цена и короткое описание — этого достаточно для публикации. Остальное можно добавить позже.',
|
||||||
|
|||||||
@@ -1551,6 +1551,7 @@ export interface Translations {
|
|||||||
recommendNone: string;
|
recommendNone: string;
|
||||||
};
|
};
|
||||||
adminProducts: {
|
adminProducts: {
|
||||||
|
confirmLeaveUnsaved: string;
|
||||||
emptyTitle: string;
|
emptyTitle: string;
|
||||||
emptyDescription: string;
|
emptyDescription: string;
|
||||||
emptyGuide: string;
|
emptyGuide: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user