mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
Compare commits
1 Commits
hotfix-538
...
nx-azure-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13616adaac |
@@ -2,3 +2,6 @@ last 1 Chrome version
|
||||
last 1 Firefox version
|
||||
last 2 Edge major versions
|
||||
last 2 iOS major versions
|
||||
safari > 11
|
||||
Firefox ESR
|
||||
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
|
||||
@@ -7,7 +7,6 @@ indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
end_of_line = crlf
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
|
||||
100
.github/commit-instructions.md
vendored
100
.github/commit-instructions.md
vendored
@@ -1,86 +1,36 @@
|
||||
# Commit Message Instructions (Conventional Commits)
|
||||
|
||||
Commit messages should follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/). This provides a standardized format for commit messages, making it easier to understand changes, automate changelog generation, and trigger build/publish processes.
|
||||
# Commit Message Instructions
|
||||
|
||||
## Format
|
||||
|
||||
The commit message structure is as follows:
|
||||
Each commit message should follow this structure:
|
||||
|
||||
1. **Short Summary**: A brief summary of the changes (max 72 characters).
|
||||
2. **List of Changes**: A detailed list of changes with icons to indicate the type of change.
|
||||
|
||||
---
|
||||
|
||||
### Example
|
||||
|
||||
```
|
||||
<type>[optional scope]: <description>
|
||||
Added a new module to handle user authentication, including login and registration.
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer(s)]
|
||||
- ✨ **Feature**: Implemented user login functionality
|
||||
- 🐛 **Fix**: Resolved session timeout issue
|
||||
- 🛠️ **Refactor**: Improved error handling in auth service
|
||||
- 🧪 **Test**: Added unit tests for login component
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Components
|
||||
## Icons for Change Types
|
||||
|
||||
1. **Type**: Indicates the kind of change introduced by the commit. Must be one of the allowed types (see below).
|
||||
2. **Scope (Optional)**: A noun describing the section of the codebase affected by the change (e.g., `auth`, `ui`, `build`). Enclosed in parentheses.
|
||||
3. **Description**: A concise summary of the change in the imperative, present tense (e.g., "add login feature", not "added login feature" or "adds login feature"). Starts with a lowercase letter and should not end with a period. Max 72 characters recommended for the entire header line (`<type>[optional scope]: <description>`).
|
||||
4. **Body (Optional)**: A more detailed explanation of the changes. Use the imperative, present tense. Explain the _what_ and _why_ vs. _how_. Separate from the description by a blank line. Wrap lines at 72 characters.
|
||||
5. **Footer(s) (Optional)**: Contains additional metadata. Common footers include:
|
||||
- `BREAKING CHANGE:` followed by a description of the breaking change. A `!` can also be appended to the type/scope (`feat!:`) to indicate a breaking change.
|
||||
- Issue references (e.g., `Refs: #123`, `Closes: #456`). Separate from the body by a blank line.
|
||||
|
||||
---
|
||||
|
||||
### Allowed Types
|
||||
|
||||
- **feat**: A new feature for the user.
|
||||
- **fix**: A bug fix for the user.
|
||||
- **build**: Changes that affect the build system or external dependencies (e.g., gulp, broccoli, npm).
|
||||
- **chore**: Other changes that don't modify src or test files (e.g., updating dependencies, build tasks).
|
||||
- **ci**: Changes to CI configuration files and scripts (e.g., Travis, Circle, BrowserStack, SauceLabs).
|
||||
- **docs**: Documentation only changes.
|
||||
- **perf**: A code change that improves performance.
|
||||
- **refactor**: A code change that neither fixes a bug nor adds a feature.
|
||||
- **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc).
|
||||
- **test**: Adding missing tests or correcting existing tests.
|
||||
|
||||
---
|
||||
|
||||
### Examples
|
||||
|
||||
**Commit with description only:**
|
||||
|
||||
```
|
||||
fix: correct minor typos in code
|
||||
```
|
||||
|
||||
**Commit with scope:**
|
||||
|
||||
```
|
||||
feat(lang): add polish language
|
||||
```
|
||||
|
||||
**Commit with body and breaking change footer:**
|
||||
|
||||
```
|
||||
refactor: drop support for Node 6
|
||||
|
||||
The new implementation relies on async/await and other features
|
||||
introduced in Node 8+.
|
||||
|
||||
BREAKING CHANGE: refactor to use JavaScript features not available in Node 6.
|
||||
```
|
||||
|
||||
**Commit with scope, body, and issue footer:**
|
||||
|
||||
```
|
||||
docs(readme): improve installation instructions
|
||||
|
||||
Provide clearer steps for setting up the development environment.
|
||||
Add links to prerequisite tools.
|
||||
|
||||
Closes: #12
|
||||
```
|
||||
|
||||
**Commit with `!` for breaking change:**
|
||||
|
||||
```
|
||||
feat(api)!: send an email to the customer when a product is shipped
|
||||
```
|
||||
- ✨ **Feature**: New features or functionality
|
||||
- 🐛 **Fix**: Bug fixes
|
||||
- 🛠️ **Refactor**: Code improvements without changing functionality
|
||||
- 🧪 **Test**: Adding or updating tests
|
||||
- 📚 **Docs**: Documentation updates
|
||||
- 🗑️ **Chore**: Maintenance tasks (e.g., dependency updates)
|
||||
- 🚀 **Performance**: Performance improvements
|
||||
- 🎨 **Style**: Code style changes (e.g., formatting)
|
||||
- 🔒 **Security**: Security-related changes
|
||||
- ⚙️ **Config**: Configuration changes
|
||||
|
||||
22
.github/copilot-instructions.md
vendored
22
.github/copilot-instructions.md
vendored
@@ -1,23 +1,17 @@
|
||||
# Mentor Instructions
|
||||
# Spark Instructions
|
||||
|
||||
## Introduction
|
||||
|
||||
You are Mentor, an AI assistant focused on ensuring code quality, strict adherence to best practices, and development efficiency. **Your core function is to enforce the coding standards and guidelines established in this workspace.** Your goal is to help me produce professional, maintainable, and high-performing code.
|
||||
|
||||
**Always get the latest official documentation for Angular, Nx, or any related technology before implementing or when answering questions or providing feedback. Use Context7:**
|
||||
You are Spark, a mentor designed to help me with coding, preview my work, and assist me in improving by pointing out areas for enhancement.
|
||||
|
||||
## Tone and Personality
|
||||
|
||||
Maintain a professional, objective, and direct tone consistently:
|
||||
|
||||
- **Guideline Enforcement & Error Correction:** When code deviates from guidelines or contains errors, provide precise, technical feedback. Clearly state the issue, cite the relevant guideline or principle, and explain the required correction for optimal, maintainable results.
|
||||
- **Technical Consultation:** In discussions about architecture, best practices, or complex coding inquiries, remain formal and analytical. Provide clear, well-reasoned explanations and recommendations grounded in industry standards and the project's specific guidelines.
|
||||
You are a mentor with a dual approach: when I make a mistake or my work needs improvement, you adopt a strict and technical tone to clearly explain what’s wrong and how to fix it. In all other cases, you are casual and friendly, like a supportive coding buddy, keeping the vibe light and encouraging.
|
||||
|
||||
## Behavioral Guidelines
|
||||
|
||||
- **Actionable Feedback:** Prioritize constructive, actionable feedback aimed at improving code quality, maintainability, and adherence to standards. Avoid rewriting code; focus on explaining the necessary changes and their rationale based on guidelines.
|
||||
- **Strict Guideline Adherence:** Base _all_ feedback, suggestions, and explanations rigorously on the guidelines documented within this workspace. Cite specific rules and principles consistently.
|
||||
- **Demand Clarity:** If a query or code snippet lacks sufficient detail for a thorough, professional analysis, request clarification.
|
||||
- **Professional Framing:** Frame all feedback objectively, focusing on the technical aspects and the importance of meeting project standards for long-term success.
|
||||
- **Context-Specific Expertise:** Provide specific, context-aware advice tailored to the code or problem, always within the framework of our established guidelines.
|
||||
- **Enforce Standards:** Actively enforce project preferences for Type safety, Clean Code principles, and thorough documentation, as mandated by the workspace guidelines.
|
||||
- Focus on constructive feedback; avoid simply rewriting my code unless I ask for it.
|
||||
- If my question or code is unclear, ask me for clarification or more details.
|
||||
- Do not discourage me; always frame suggestions as opportunities for growth.
|
||||
- Avoid giving generic answers—tailor your advice to my specific code or problem.
|
||||
- Keep my preferences in mind: prioritize Type safety, follow Clean Code principles and emphasize good documentation.
|
||||
|
||||
5
.github/review-instructions.md
vendored
5
.github/review-instructions.md
vendored
@@ -36,9 +36,8 @@ When conducting a code review, follow these steps to ensure a thorough and const
|
||||
|
||||
## Additional Informations
|
||||
|
||||
- Missing tests and JSDocs are minor issues
|
||||
- Missing unit test are minor issues
|
||||
- Missing End-to-End (E2E) Testing Attributes (`data-what`, `data-which`) are warnings
|
||||
- Treat missing tests and JSDocs as warnings
|
||||
- Tread missing unit test as warnings
|
||||
|
||||
### Review Template
|
||||
|
||||
|
||||
44
.github/testing-instructions.md
vendored
44
.github/testing-instructions.md
vendored
@@ -1,14 +1,10 @@
|
||||
# Testing Instructions
|
||||
|
||||
## Framework and Tools
|
||||
|
||||
- **Vitest** is the recommended testing framework.
|
||||
[Vitest Documentation (latest)](https://context7.com/vitest-dev/vitest/llms.txt?topic=getting+started)
|
||||
- **Jest** and **Spectator** are **deprecated**.
|
||||
Do not use them for new tests. Existing tests should be migrated to Vitest where possible.
|
||||
- Use **Jest** as the testing framework.
|
||||
- For unit tests, utilize **Spectator** to simplify Angular component testing.
|
||||
|
||||
## Guidelines
|
||||
|
||||
1. **Error Case Testing**: Ensure all edge cases and error scenarios are thoroughly tested.
|
||||
2. **Arrange-Act-Assert Pattern**: Follow the Arrange-Act-Assert pattern for structuring your tests:
|
||||
- **Arrange**: Set up the testing environment and initialize required variables.
|
||||
@@ -16,40 +12,35 @@
|
||||
- **Assert**: Verify the expected outcomes.
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Write clear and descriptive test names.
|
||||
- Ensure tests are isolated and do not depend on each other.
|
||||
- Mock external dependencies to avoid side effects.
|
||||
- Aim for high code coverage without compromising test quality.
|
||||
|
||||
## Example Test Structure
|
||||
|
||||
```typescript
|
||||
// Example using Vitest (Jest and Spectator are deprecated)
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { render } from '@testing-library/angular';
|
||||
// Example using Jest and Spectator
|
||||
import { createComponentFactory, Spectator } from '@ngneat/spectator';
|
||||
import { MyComponent } from './my-component.component';
|
||||
|
||||
describe('MyComponent', () => {
|
||||
let component: MyComponent;
|
||||
let spectator: Spectator<MyComponent>;
|
||||
const createComponent = createComponentFactory(MyComponent);
|
||||
|
||||
beforeEach(async () => {
|
||||
const { fixture } = await render(MyComponent);
|
||||
component = fixture.componentInstance;
|
||||
beforeEach(() => {
|
||||
spectator = createComponent();
|
||||
});
|
||||
|
||||
it('should display the correct title', async () => {
|
||||
it('should display the correct title', () => {
|
||||
// Arrange
|
||||
const expectedTitle = 'Hello World';
|
||||
|
||||
// Act
|
||||
component.title = expectedTitle;
|
||||
// If using Angular, trigger change detection:
|
||||
// fixture.detectChanges();
|
||||
spectator.component.title = expectedTitle;
|
||||
spectator.detectChanges();
|
||||
|
||||
// Assert
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
expect(compiled.querySelector('h1')?.textContent).toBe(expectedTitle);
|
||||
expect(spectator.query('h1')).toHaveText(expectedTitle);
|
||||
});
|
||||
|
||||
it('should handle error cases gracefully', () => {
|
||||
@@ -57,17 +48,14 @@ describe('MyComponent', () => {
|
||||
const invalidInput = null;
|
||||
|
||||
// Act
|
||||
component.input = invalidInput;
|
||||
spectator.component.input = invalidInput;
|
||||
|
||||
// Assert
|
||||
expect(() => component.processInput()).toThrowError('Invalid input');
|
||||
expect(() => spectator.component.processInput()).toThrowError('Invalid input');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Vitest Documentation (latest)](https://context7.com/vitest-dev/vitest/llms.txt?topic=getting+started)
|
||||
- [Vitest Official Guide](https://vitest.dev/guide/)
|
||||
- [Testing Library for Angular](https://testing-library.com/docs/angular-testing-library/intro/)
|
||||
- **Jest** and **Spectator** documentation are deprecated
|
||||
- [Jest Documentation](https://jestjs.io/docs/getting-started)
|
||||
- [Spectator Documentation](https://ngneat.github.io/spectator/)
|
||||
140
.gitignore
vendored
140
.gitignore
vendored
@@ -1,77 +1,63 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
.matomo
|
||||
junit.xml
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
|
||||
/
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# profiling files
|
||||
chrome-profiler-events.json
|
||||
speed-measure-plugin.json
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# misc
|
||||
/.angular/cache
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/testresults
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
yarn.lock
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
libs/swagger/src/lib/*
|
||||
*storybook.log
|
||||
|
||||
|
||||
.nx/cache
|
||||
.nx/workspace-data
|
||||
.angular
|
||||
.claude
|
||||
|
||||
|
||||
storybook-static
|
||||
|
||||
.cursor\rules\nx-rules.mdc
|
||||
.github\instructions\nx.instructions.md
|
||||
.cursor/rules/nx-rules.mdc
|
||||
.github/instructions/nx.instructions.md
|
||||
|
||||
vite.config.*.timestamp*
|
||||
vitest.config.*.timestamp*
|
||||
|
||||
.mcp.json
|
||||
.memory.json
|
||||
|
||||
nx.instructions.md
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
.matomo
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
|
||||
/
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# profiling files
|
||||
chrome-profiler-events.json
|
||||
speed-measure-plugin.json
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
.prettierrc
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# misc
|
||||
/.angular/cache
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/testresults
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
yarn.lock
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
libs/swagger/src/lib/*
|
||||
*storybook.log
|
||||
|
||||
|
||||
.nx/cache
|
||||
.nx/workspace-data
|
||||
.angular
|
||||
|
||||
|
||||
storybook-static
|
||||
1
.husky/pre-push
Normal file
1
.husky/pre-push
Normal file
@@ -0,0 +1 @@
|
||||
npm run ci
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"*.ts": "npx eslint --fix --config eslint.config.js",
|
||||
"*.tsx": "npx eslint --fix --config eslint.config.js",
|
||||
"*.js": "npx eslint --fix --config eslint.config.js",
|
||||
"*.jsx": "npx eslint --fix --config eslint.config.js",
|
||||
"*.html": "npx eslint --fix --config eslint.config.js"
|
||||
}
|
||||
{
|
||||
"*.ts": "npx eslint --fix --config eslint.config.mjs",
|
||||
"*.tsx": "npx eslint --fix --config eslint.config.mjs",
|
||||
"*.js": "npx eslint --fix --config eslint.config.mjs",
|
||||
"*.jsx": "npx eslint --fix --config eslint.config.mjs",
|
||||
"*.html": "npx eslint --fix --config eslint.config.mjs"
|
||||
}
|
||||
|
||||
37
.prettierrc
37
.prettierrc
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"semi": true,
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 2,
|
||||
"bracketSpacing": true,
|
||||
"printWidth": 80,
|
||||
"endOfLine": "auto",
|
||||
"arrowParens": "always",
|
||||
"quoteProps": "consistent",
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.html",
|
||||
"options": {
|
||||
"parser": "html"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "*.component.html",
|
||||
"options": {
|
||||
"parser": "angular"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "*.scss",
|
||||
"options": {
|
||||
"singleQuote": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "*.json",
|
||||
"options": {
|
||||
"printWidth": 80
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@@ -5,7 +5,6 @@
|
||||
"angular.ng-template",
|
||||
"nrwl.angular-console",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"firsttris.vscode-jest-runner",
|
||||
"editorconfig.editorconfig"
|
||||
"firsttris.vscode-jest-runner"
|
||||
]
|
||||
}
|
||||
|
||||
19
.vscode/settings.json
vendored
19
.vscode/settings.json
vendored
@@ -19,10 +19,7 @@
|
||||
"exportall.config.folderListener": [
|
||||
"/libs/oms/data-access/src/lib/models",
|
||||
"/libs/oms/data-access/src/lib/schemas",
|
||||
"/libs/catalogue/data-access/src/lib/models",
|
||||
"/libs/common/data-access/src/lib/models",
|
||||
"/libs/common/data-access/src/lib/error",
|
||||
"/libs/oms/data-access/src/lib/errors/return-process"
|
||||
"/libs/catalogue/data-access/src/lib/models"
|
||||
],
|
||||
"github.copilot.chat.commitMessageGeneration.instructions": [
|
||||
{
|
||||
@@ -31,7 +28,13 @@
|
||||
],
|
||||
"github.copilot.chat.codeGeneration.instructions": [
|
||||
{
|
||||
"file": ".vscode/llms/angular.txt"
|
||||
"file": ".github/copilot-instructions.md"
|
||||
},
|
||||
{
|
||||
"file": ".github/review-instructions.md"
|
||||
},
|
||||
{
|
||||
"file": ".github/testing-instructions.md"
|
||||
},
|
||||
{
|
||||
"file": "docs/tech-stack.md"
|
||||
@@ -50,6 +53,9 @@
|
||||
}
|
||||
],
|
||||
"github.copilot.chat.testGeneration.instructions": [
|
||||
{
|
||||
"file": ".github/copilot-instructions.md"
|
||||
},
|
||||
{
|
||||
"file": ".github/testing-instructions.md"
|
||||
},
|
||||
@@ -86,7 +92,4 @@
|
||||
"file": "docs/guidelines/testing.md"
|
||||
}
|
||||
],
|
||||
"nxConsole.generateAiAgentRules": true,
|
||||
"chat.mcp.enabled": true,
|
||||
"chat.mcp.discovery.enabled": true
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ WORKDIR /app
|
||||
COPY . .
|
||||
RUN umask 0022
|
||||
RUN npm version ${SEMVERSION}
|
||||
RUN npm install --foreground-scripts
|
||||
RUN npm install --always-auth=false
|
||||
RUN if [ "${IS_PRODUCTION}" = "true" ] ; then npm run-script build-prod ; else npm run-script build ; fi
|
||||
|
||||
# stage final
|
||||
|
||||
@@ -2,11 +2,8 @@ import type { StorybookConfig } from '@storybook/angular';
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ['../stories/**/*.@(mdx|stories.@(js|jsx|ts|tsx))'],
|
||||
addons: [
|
||||
'@storybook/addon-essentials',
|
||||
'@storybook/addon-interactions',
|
||||
'@storybook/addon-docs',
|
||||
],
|
||||
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
|
||||
staticDirs: ['../src/assets'],
|
||||
previewHead: (head) => `
|
||||
${head}
|
||||
<link href="/assets/fonts/fonts.css" rel="stylesheet" />
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
const nx = require('@nx/eslint-plugin');
|
||||
const baseConfig = require('../../eslint.config.js');
|
||||
|
||||
module.exports = [
|
||||
...baseConfig,
|
||||
...nx.configs['flat/angular'],
|
||||
...nx.configs['flat/angular-template'],
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
rules: {
|
||||
'@angular-eslint/directive-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'attribute',
|
||||
prefix: 'app',
|
||||
style: 'camelCase',
|
||||
},
|
||||
],
|
||||
'@angular-eslint/component-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'element',
|
||||
prefix: 'app',
|
||||
style: 'kebab-case',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-expressions': 'warn',
|
||||
'prefer-const': 'warn',
|
||||
'@angular-eslint/contextual-lifecycle': 'warn',
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'@angular-eslint/no-empty-lifecycle-method': 'warn',
|
||||
'@typescript-eslint/no-inferrable-types': 'warn',
|
||||
'@angular-eslint/component-selector': 'warn',
|
||||
'@angular-eslint/prefer-standalone': 'warn',
|
||||
'@typescript-eslint/no-inferrable-types': 'warn',
|
||||
'no-empty-function': 'warn',
|
||||
'@typescript-eslint/no-empty-function': 'warn',
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
'@angular-eslint/directive-selector': 'warn',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.html'],
|
||||
// Override or add rules here
|
||||
rules: {
|
||||
'@angular-eslint/template/elements-content': 'warn',
|
||||
'@angular-eslint/template/no-autofocus': 'warn',
|
||||
},
|
||||
},
|
||||
];
|
||||
import nx from '@nx/eslint-plugin';
|
||||
import baseConfig from '../../eslint.config.mjs';
|
||||
|
||||
export default [
|
||||
...baseConfig,
|
||||
...nx.configs['flat/angular'],
|
||||
...nx.configs['flat/angular-template'],
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
rules: {
|
||||
'@angular-eslint/directive-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'attribute',
|
||||
prefix: 'app',
|
||||
style: 'camelCase',
|
||||
},
|
||||
],
|
||||
'@angular-eslint/component-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'element',
|
||||
prefix: 'app',
|
||||
style: 'kebab-case',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-expressions': 'warn',
|
||||
'prefer-const': 'warn',
|
||||
'@angular-eslint/contextual-lifecycle': 'warn',
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'@angular-eslint/no-empty-lifecycle-method': 'warn',
|
||||
'@typescript-eslint/no-inferrable-types': 'warn',
|
||||
'@angular-eslint/component-selector': 'warn',
|
||||
'@angular-eslint/prefer-standalone': 'warn',
|
||||
'@typescript-eslint/no-inferrable-types': 'warn',
|
||||
'no-empty-function': 'warn',
|
||||
'@typescript-eslint/no-empty-function': 'warn',
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
'@angular-eslint/directive-selector': 'warn',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.html'],
|
||||
// Override or add rules here
|
||||
rules: {
|
||||
'@angular-eslint/template/elements-content': 'warn',
|
||||
'@angular-eslint/template/no-autofocus': 'warn',
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -38,7 +38,7 @@
|
||||
],
|
||||
"styles": [
|
||||
"@angular/cdk/overlay-prebuilt.css",
|
||||
"apps/isa-app/src/tailwind.scss",
|
||||
"apps/isa-app/src/ui.scss",
|
||||
"apps/isa-app/src/styles.scss"
|
||||
],
|
||||
"scripts": []
|
||||
@@ -84,8 +84,7 @@
|
||||
"buildTarget": "isa-app:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development",
|
||||
"continuous": true
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"executor": "@angular-devkit/build-angular:extract-i18n",
|
||||
@@ -118,19 +117,7 @@
|
||||
"configDir": "apps/isa-app/.storybook",
|
||||
"browserTarget": "isa-app:build",
|
||||
"compodoc": false,
|
||||
"open": false,
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "apps/isa-app/src/assets",
|
||||
"output": "/assets"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"@angular/cdk/overlay-prebuilt.css",
|
||||
"apps/isa-app/src/tailwind.scss",
|
||||
"apps/isa-app/src/styles.scss"
|
||||
]
|
||||
"styles": ["apps/isa-app/src/ui.scss", "apps/isa-app/src/styles.scss"]
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
@@ -145,18 +132,33 @@
|
||||
"outputDir": "dist/storybook/isa-app",
|
||||
"configDir": "apps/isa-app/.storybook",
|
||||
"browserTarget": "isa-app:build",
|
||||
"compodoc": false,
|
||||
"styles": [
|
||||
"@angular/cdk/overlay-prebuilt.css",
|
||||
"apps/isa-app/src/tailwind.scss",
|
||||
"apps/isa-app/src/styles.scss"
|
||||
]
|
||||
"compodoc": false
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"quiet": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"test-storybook": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"command": "test-storybook -c apps/isa-app/.storybook --url=http://localhost:4400"
|
||||
}
|
||||
},
|
||||
"static-storybook": {
|
||||
"executor": "@nx/web:file-server",
|
||||
"dependsOn": ["build-storybook"],
|
||||
"options": {
|
||||
"buildTarget": "isa-app:build-storybook",
|
||||
"staticFilePath": "dist/storybook/isa-app",
|
||||
"spa": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"buildTarget": "isa-app:build-storybook:ci"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import {
|
||||
PromptModalData,
|
||||
UiModalService,
|
||||
UiPromptModalComponent,
|
||||
} from '@ui/modal';
|
||||
import { PromptModalData, UiModalService, UiPromptModalComponent } from '@ui/modal';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ScanAdapter } from './scan-adapter';
|
||||
import { Config } from '@core/config';
|
||||
@@ -18,14 +14,9 @@ export class DevScanAdapter implements ScanAdapter {
|
||||
private _config = inject(Config);
|
||||
|
||||
async init(): Promise<boolean> {
|
||||
const enabled = localStorage.getItem('dev_scan_adapter_enabled') === 'true';
|
||||
if (enabled) {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(coerceBooleanProperty(this._config.get('dev-scanner')));
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(coerceBooleanProperty(this._config.get('dev-scanner')));
|
||||
});
|
||||
}
|
||||
|
||||
scan(): Observable<string> {
|
||||
@@ -34,8 +25,7 @@ export class DevScanAdapter implements ScanAdapter {
|
||||
content: UiPromptModalComponent,
|
||||
title: 'Scannen',
|
||||
data: {
|
||||
message:
|
||||
'Diese Eingabemaske dient nur zu Entwicklungs und Testzwecken.',
|
||||
message: 'Diese Eingabemaske dient nur zu Entwicklungs und Testzwecken.',
|
||||
placeholder: 'Scan Code',
|
||||
confirmText: 'weiter',
|
||||
cancelText: 'abbrechen',
|
||||
|
||||
@@ -27,10 +27,7 @@ export class ScanditScanAdapter implements ScanAdapter {
|
||||
) {}
|
||||
|
||||
async init(): Promise<boolean> {
|
||||
const enabled =
|
||||
localStorage.getItem('scandit_scan_adapter_enabled') === 'true';
|
||||
|
||||
if (enabled || this._environmentService.isTablet()) {
|
||||
if (this._environmentService.isTablet()) {
|
||||
try {
|
||||
await configure({
|
||||
licenseKey: this._config.get('licence.scandit'),
|
||||
@@ -91,11 +88,7 @@ export class ScanditScanAdapter implements ScanAdapter {
|
||||
|
||||
createOverlay() {
|
||||
const overlay = this._overlay.create({
|
||||
positionStrategy: this._overlay
|
||||
.position()
|
||||
.global()
|
||||
.centerHorizontally()
|
||||
.centerVertically(),
|
||||
positionStrategy: this._overlay.position().global().centerHorizontally().centerVertically(),
|
||||
hasBackdrop: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,231 +1,184 @@
|
||||
import { isDevMode, NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import {
|
||||
CanActivateCartGuard,
|
||||
CanActivateCartWithProcessIdGuard,
|
||||
CanActivateCustomerGuard,
|
||||
CanActivateCustomerOrdersGuard,
|
||||
CanActivateCustomerOrdersWithProcessIdGuard,
|
||||
CanActivateCustomerWithProcessIdGuard,
|
||||
CanActivateGoodsInGuard,
|
||||
CanActivateProductGuard,
|
||||
CanActivateProductWithProcessIdGuard,
|
||||
CanActivateRemissionGuard,
|
||||
CanActivateTaskCalendarGuard,
|
||||
IsAuthenticatedGuard,
|
||||
} from './guards';
|
||||
import { CanActivateAssortmentGuard } from './guards/can-activate-assortment.guard';
|
||||
import { CanActivatePackageInspectionGuard } from './guards/can-activate-package-inspection.guard';
|
||||
import { MainComponent } from './main.component';
|
||||
import { PreviewComponent } from './preview';
|
||||
import {
|
||||
BranchSectionResolver,
|
||||
CustomerSectionResolver,
|
||||
ProcessIdResolver,
|
||||
} from './resolvers';
|
||||
import { TokenLoginComponent, TokenLoginModule } from './token-login';
|
||||
import { ProcessIdGuard } from './guards/process-id.guard';
|
||||
import {
|
||||
ActivateProcessIdGuard,
|
||||
ActivateProcessIdWithConfigKeyGuard,
|
||||
} from './guards/activate-process-id.guard';
|
||||
import { MatomoRouteData } from 'ngx-matomo-client';
|
||||
import { tabResolverFn } from '@isa/core/tabs';
|
||||
import { provideScrollPositionRestoration } from '@isa/utils/scroll-position';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', redirectTo: 'kunde/dashboard', pathMatch: 'full' },
|
||||
{
|
||||
path: 'login',
|
||||
children: [
|
||||
{ path: ':token', component: TokenLoginComponent },
|
||||
{ path: '**', redirectTo: 'kunde', pathMatch: 'full' },
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
canActivate: [IsAuthenticatedGuard],
|
||||
children: [
|
||||
{
|
||||
path: 'kunde',
|
||||
component: MainComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'dashboard',
|
||||
loadChildren: () =>
|
||||
import('@page/dashboard').then((m) => m.DashboardModule),
|
||||
data: {
|
||||
matomo: {
|
||||
title: 'Dashboard',
|
||||
} as MatomoRouteData,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'product',
|
||||
loadChildren: () =>
|
||||
import('@page/catalog').then((m) => m.PageCatalogModule),
|
||||
canActivate: [CanActivateProductGuard],
|
||||
},
|
||||
{
|
||||
path: ':processId/product',
|
||||
loadChildren: () =>
|
||||
import('@page/catalog').then((m) => m.PageCatalogModule),
|
||||
canActivate: [CanActivateProductWithProcessIdGuard],
|
||||
resolve: { processId: ProcessIdResolver },
|
||||
},
|
||||
{
|
||||
path: 'order',
|
||||
loadChildren: () =>
|
||||
import('@page/customer-order').then((m) => m.CustomerOrderModule),
|
||||
canActivate: [CanActivateCustomerOrdersGuard],
|
||||
},
|
||||
{
|
||||
path: ':processId/order',
|
||||
loadChildren: () =>
|
||||
import('@page/customer-order').then((m) => m.CustomerOrderModule),
|
||||
canActivate: [CanActivateCustomerOrdersWithProcessIdGuard],
|
||||
resolve: { processId: ProcessIdResolver },
|
||||
},
|
||||
{
|
||||
path: 'customer',
|
||||
loadChildren: () =>
|
||||
import('@page/customer').then((m) => m.CustomerModule),
|
||||
canActivate: [CanActivateCustomerGuard],
|
||||
},
|
||||
{
|
||||
path: ':processId/customer',
|
||||
loadChildren: () =>
|
||||
import('@page/customer').then((m) => m.CustomerModule),
|
||||
canActivate: [CanActivateCustomerWithProcessIdGuard],
|
||||
resolve: { processId: ProcessIdResolver },
|
||||
},
|
||||
{
|
||||
path: 'cart',
|
||||
loadChildren: () =>
|
||||
import('@page/checkout').then((m) => m.PageCheckoutModule),
|
||||
canActivate: [CanActivateCartGuard],
|
||||
},
|
||||
{
|
||||
path: ':processId/cart',
|
||||
loadChildren: () =>
|
||||
import('@page/checkout').then((m) => m.PageCheckoutModule),
|
||||
canActivate: [CanActivateCartWithProcessIdGuard],
|
||||
},
|
||||
{
|
||||
path: 'pickup-shelf',
|
||||
canActivate: [ProcessIdGuard],
|
||||
// NOTE: This is a workaround for the canActivate guard not being called
|
||||
loadChildren: () =>
|
||||
import('@page/pickup-shelf').then((m) => m.PickupShelfOutModule),
|
||||
},
|
||||
{
|
||||
path: ':processId/pickup-shelf',
|
||||
canActivate: [ActivateProcessIdGuard],
|
||||
loadChildren: () =>
|
||||
import('@page/pickup-shelf').then((m) => m.PickupShelfOutModule),
|
||||
},
|
||||
{ path: '**', redirectTo: 'dashboard', pathMatch: 'full' },
|
||||
],
|
||||
resolve: { section: CustomerSectionResolver },
|
||||
},
|
||||
{
|
||||
path: 'filiale',
|
||||
component: MainComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'task-calendar',
|
||||
loadChildren: () =>
|
||||
import('@page/task-calendar').then(
|
||||
(m) => m.PageTaskCalendarModule,
|
||||
),
|
||||
canActivate: [CanActivateTaskCalendarGuard],
|
||||
},
|
||||
{
|
||||
path: 'pickup-shelf',
|
||||
canActivate: [ActivateProcessIdWithConfigKeyGuard('pickupShelf')],
|
||||
// NOTE: This is a workaround for the canActivate guard not being called
|
||||
loadChildren: () =>
|
||||
import('@page/pickup-shelf').then((m) => m.PickupShelfInModule),
|
||||
},
|
||||
{
|
||||
path: 'goods/in',
|
||||
loadChildren: () =>
|
||||
import('@page/goods-in').then((m) => m.GoodsInModule),
|
||||
canActivate: [CanActivateGoodsInGuard],
|
||||
},
|
||||
// {
|
||||
// path: 'remission',
|
||||
// loadChildren: () =>
|
||||
// import('@page/remission').then((m) => m.PageRemissionModule),
|
||||
// canActivate: [CanActivateRemissionGuard],
|
||||
// },
|
||||
{
|
||||
path: 'package-inspection',
|
||||
loadChildren: () =>
|
||||
import('@page/package-inspection').then(
|
||||
(m) => m.PackageInspectionModule,
|
||||
),
|
||||
canActivate: [CanActivatePackageInspectionGuard],
|
||||
},
|
||||
{
|
||||
path: 'assortment',
|
||||
loadChildren: () =>
|
||||
import('@page/assortment').then((m) => m.AssortmentModule),
|
||||
canActivate: [CanActivateAssortmentGuard],
|
||||
},
|
||||
{ path: '**', redirectTo: 'task-calendar', pathMatch: 'full' },
|
||||
],
|
||||
resolve: { section: BranchSectionResolver },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: ':tabId',
|
||||
component: MainComponent,
|
||||
resolve: { process: tabResolverFn, tab: tabResolverFn },
|
||||
canActivate: [IsAuthenticatedGuard],
|
||||
children: [
|
||||
{
|
||||
path: 'return',
|
||||
loadChildren: () =>
|
||||
import('@isa/oms/feature/return-search').then((m) => m.routes),
|
||||
},
|
||||
{
|
||||
path: 'remission',
|
||||
children: [
|
||||
{
|
||||
path: 'return-receipt',
|
||||
loadChildren: () =>
|
||||
import(
|
||||
'@isa/remission/feature/remission-return-receipt-list'
|
||||
).then((m) => m.routes),
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
loadChildren: () =>
|
||||
import('@isa/remission/feature/remission-list').then(
|
||||
(m) => m.routes,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
if (isDevMode()) {
|
||||
routes.unshift({
|
||||
path: 'preview',
|
||||
component: PreviewComponent,
|
||||
});
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forRoot(routes, { bindToComponentInputs: true }),
|
||||
TokenLoginModule,
|
||||
],
|
||||
exports: [RouterModule],
|
||||
providers: [provideScrollPositionRestoration()],
|
||||
})
|
||||
export class AppRoutingModule {}
|
||||
import { isDevMode, NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import {
|
||||
CanActivateCartGuard,
|
||||
CanActivateCartWithProcessIdGuard,
|
||||
CanActivateCustomerGuard,
|
||||
CanActivateCustomerOrdersGuard,
|
||||
CanActivateCustomerOrdersWithProcessIdGuard,
|
||||
CanActivateCustomerWithProcessIdGuard,
|
||||
CanActivateGoodsInGuard,
|
||||
CanActivateProductGuard,
|
||||
CanActivateProductWithProcessIdGuard,
|
||||
CanActivateRemissionGuard,
|
||||
CanActivateTaskCalendarGuard,
|
||||
IsAuthenticatedGuard,
|
||||
} from './guards';
|
||||
import { CanActivateAssortmentGuard } from './guards/can-activate-assortment.guard';
|
||||
import { CanActivatePackageInspectionGuard } from './guards/can-activate-package-inspection.guard';
|
||||
import { MainComponent } from './main.component';
|
||||
import { PreviewComponent } from './preview';
|
||||
import { BranchSectionResolver, CustomerSectionResolver, ProcessIdResolver } from './resolvers';
|
||||
import { TokenLoginComponent, TokenLoginModule } from './token-login';
|
||||
import { ProcessIdGuard } from './guards/process-id.guard';
|
||||
import {
|
||||
ActivateProcessIdGuard,
|
||||
ActivateProcessIdWithConfigKeyGuard,
|
||||
} from './guards/activate-process-id.guard';
|
||||
import { MatomoRouteData } from 'ngx-matomo-client';
|
||||
import { processResolverFn } from '@isa/core/process';
|
||||
import { provideScrollPositionRestoration } from '@isa/utils/scroll-position';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', redirectTo: 'kunde/dashboard', pathMatch: 'full' },
|
||||
{
|
||||
path: 'login',
|
||||
children: [
|
||||
{ path: ':token', component: TokenLoginComponent },
|
||||
{ path: '**', redirectTo: 'kunde', pathMatch: 'full' },
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
canActivate: [IsAuthenticatedGuard],
|
||||
children: [
|
||||
{
|
||||
path: 'kunde',
|
||||
component: MainComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'dashboard',
|
||||
loadChildren: () => import('@page/dashboard').then((m) => m.DashboardModule),
|
||||
data: {
|
||||
matomo: {
|
||||
title: 'Dashboard',
|
||||
} as MatomoRouteData,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'product',
|
||||
loadChildren: () => import('@page/catalog').then((m) => m.PageCatalogModule),
|
||||
canActivate: [CanActivateProductGuard],
|
||||
},
|
||||
{
|
||||
path: ':processId/product',
|
||||
loadChildren: () => import('@page/catalog').then((m) => m.PageCatalogModule),
|
||||
canActivate: [CanActivateProductWithProcessIdGuard],
|
||||
resolve: { processId: ProcessIdResolver },
|
||||
},
|
||||
{
|
||||
path: 'order',
|
||||
loadChildren: () => import('@page/customer-order').then((m) => m.CustomerOrderModule),
|
||||
canActivate: [CanActivateCustomerOrdersGuard],
|
||||
},
|
||||
{
|
||||
path: ':processId/order',
|
||||
loadChildren: () => import('@page/customer-order').then((m) => m.CustomerOrderModule),
|
||||
canActivate: [CanActivateCustomerOrdersWithProcessIdGuard],
|
||||
resolve: { processId: ProcessIdResolver },
|
||||
},
|
||||
{
|
||||
path: 'customer',
|
||||
loadChildren: () => import('@page/customer').then((m) => m.CustomerModule),
|
||||
canActivate: [CanActivateCustomerGuard],
|
||||
},
|
||||
{
|
||||
path: ':processId/customer',
|
||||
loadChildren: () => import('@page/customer').then((m) => m.CustomerModule),
|
||||
canActivate: [CanActivateCustomerWithProcessIdGuard],
|
||||
resolve: { processId: ProcessIdResolver },
|
||||
},
|
||||
{
|
||||
path: 'cart',
|
||||
loadChildren: () => import('@page/checkout').then((m) => m.PageCheckoutModule),
|
||||
canActivate: [CanActivateCartGuard],
|
||||
},
|
||||
{
|
||||
path: ':processId/cart',
|
||||
loadChildren: () => import('@page/checkout').then((m) => m.PageCheckoutModule),
|
||||
canActivate: [CanActivateCartWithProcessIdGuard],
|
||||
},
|
||||
{
|
||||
path: 'pickup-shelf',
|
||||
canActivate: [ProcessIdGuard],
|
||||
// NOTE: This is a workaround for the canActivate guard not being called
|
||||
loadChildren: () => import('@page/pickup-shelf').then((m) => m.PickupShelfOutModule),
|
||||
},
|
||||
{
|
||||
path: ':processId/pickup-shelf',
|
||||
canActivate: [ActivateProcessIdGuard],
|
||||
loadChildren: () => import('@page/pickup-shelf').then((m) => m.PickupShelfOutModule),
|
||||
},
|
||||
{ path: '**', redirectTo: 'dashboard', pathMatch: 'full' },
|
||||
],
|
||||
resolve: { section: CustomerSectionResolver },
|
||||
},
|
||||
{
|
||||
path: 'filiale',
|
||||
component: MainComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'task-calendar',
|
||||
loadChildren: () => import('@page/task-calendar').then((m) => m.PageTaskCalendarModule),
|
||||
canActivate: [CanActivateTaskCalendarGuard],
|
||||
},
|
||||
{
|
||||
path: 'pickup-shelf',
|
||||
canActivate: [ActivateProcessIdWithConfigKeyGuard('pickupShelf')],
|
||||
// NOTE: This is a workaround for the canActivate guard not being called
|
||||
loadChildren: () => import('@page/pickup-shelf').then((m) => m.PickupShelfInModule),
|
||||
},
|
||||
{
|
||||
path: 'goods/in',
|
||||
loadChildren: () => import('@page/goods-in').then((m) => m.GoodsInModule),
|
||||
canActivate: [CanActivateGoodsInGuard],
|
||||
},
|
||||
{
|
||||
path: 'remission',
|
||||
loadChildren: () => import('@page/remission').then((m) => m.PageRemissionModule),
|
||||
canActivate: [CanActivateRemissionGuard],
|
||||
},
|
||||
{
|
||||
path: 'package-inspection',
|
||||
loadChildren: () =>
|
||||
import('@page/package-inspection').then((m) => m.PackageInspectionModule),
|
||||
canActivate: [CanActivatePackageInspectionGuard],
|
||||
},
|
||||
{
|
||||
path: 'assortment',
|
||||
loadChildren: () => import('@page/assortment').then((m) => m.AssortmentModule),
|
||||
canActivate: [CanActivateAssortmentGuard],
|
||||
},
|
||||
{ path: '**', redirectTo: 'task-calendar', pathMatch: 'full' },
|
||||
],
|
||||
resolve: { section: BranchSectionResolver },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: ':processId',
|
||||
component: MainComponent,
|
||||
resolve: { process: processResolverFn },
|
||||
canActivate: [IsAuthenticatedGuard],
|
||||
children: [
|
||||
{
|
||||
path: 'return',
|
||||
loadChildren: () => import('@isa/oms/feature/return-search').then((m) => m.routes),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
if (isDevMode()) {
|
||||
routes.unshift({
|
||||
path: 'preview',
|
||||
component: PreviewComponent,
|
||||
});
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes), TokenLoginModule],
|
||||
exports: [RouterModule],
|
||||
providers: [provideScrollPositionRestoration()],
|
||||
})
|
||||
export class AppRoutingModule {}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import {
|
||||
Component,
|
||||
effect,
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
Renderer2,
|
||||
signal,
|
||||
untracked,
|
||||
DOCUMENT
|
||||
} from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { SwUpdate } from '@angular/service-worker';
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
withInterceptorsFromDi,
|
||||
} from '@angular/common/http';
|
||||
import {
|
||||
DEFAULT_CURRENCY_CODE,
|
||||
ErrorHandler,
|
||||
Injector,
|
||||
LOCALE_ID,
|
||||
@@ -229,10 +228,6 @@ export function _notificationsHubOptionsFactory(
|
||||
withRouteData(),
|
||||
),
|
||||
provideLogging(withLogLevel(LogLevel.Debug), withSink(ConsoleLogSink)),
|
||||
{
|
||||
provide: DEFAULT_CURRENCY_CODE,
|
||||
useValue: 'EUR',
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@@ -1,22 +1,12 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
} from "@angular/router";
|
||||
import { ApplicationProcess, ApplicationService } from "@core/application";
|
||||
import { DomainCheckoutService } from "@domain/checkout";
|
||||
import { logger } from "@isa/core/logging";
|
||||
import { CustomerSearchNavigation } from "@shared/services/navigation";
|
||||
import { first } from "rxjs/operators";
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { ApplicationProcess, ApplicationService } from '@core/application';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { CustomerSearchNavigation } from '@shared/services/navigation';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: "root" })
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CanActivateCustomerGuard {
|
||||
#logger = logger(() => ({
|
||||
context: "CanActivateCustomerGuard",
|
||||
tags: ["guard", "customer", "navigation"],
|
||||
}));
|
||||
|
||||
constructor(
|
||||
private readonly _applicationService: ApplicationService,
|
||||
private readonly _checkoutService: DomainCheckoutService,
|
||||
@@ -24,77 +14,36 @@ export class CanActivateCustomerGuard {
|
||||
private readonly _navigation: CustomerSearchNavigation,
|
||||
) {}
|
||||
|
||||
async canActivate(
|
||||
route: ActivatedRouteSnapshot,
|
||||
{ url }: RouterStateSnapshot,
|
||||
) {
|
||||
if (url.startsWith("/kunde/customer/search/")) {
|
||||
const processId = Date.now(); // Generate a new process ID
|
||||
// Extract parts before and after the pattern
|
||||
const parts = url.split("/kunde/customer/");
|
||||
if (parts.length === 2) {
|
||||
const prefix = parts[0] + "/kunde/";
|
||||
const suffix = "customer/" + parts[1];
|
||||
|
||||
// Construct the new URL with process ID inserted
|
||||
const newUrl = `${prefix}${processId}/${suffix}`;
|
||||
|
||||
this.#logger.info("Redirecting to URL with process ID", () => ({
|
||||
originalUrl: url,
|
||||
newUrl,
|
||||
processId,
|
||||
}));
|
||||
|
||||
// Navigate to the new URL and prevent original navigation
|
||||
this._router.navigateByUrl(newUrl);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const processes = await this._applicationService
|
||||
.getProcesses$("customer")
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
const lastActivatedProcessId = (
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
|
||||
let lastActivatedProcessId = (
|
||||
await this._applicationService
|
||||
.getLastActivatedProcessWithSectionAndType$("customer", "cart")
|
||||
.getLastActivatedProcessWithSectionAndType$('customer', 'cart')
|
||||
.pipe(first())
|
||||
.toPromise()
|
||||
)?.id;
|
||||
|
||||
const lastActivatedCartCheckoutProcessId = (
|
||||
await this._applicationService
|
||||
.getLastActivatedProcessWithSectionAndType$("customer", "cart-checkout")
|
||||
.getLastActivatedProcessWithSectionAndType$('customer', 'cart-checkout')
|
||||
.pipe(first())
|
||||
.toPromise()
|
||||
)?.id;
|
||||
|
||||
const lastActivatedGoodsOutProcessId = (
|
||||
await this._applicationService
|
||||
.getLastActivatedProcessWithSectionAndType$("customer", "goods-out")
|
||||
.getLastActivatedProcessWithSectionAndType$('customer', 'goods-out')
|
||||
.pipe(first())
|
||||
.toPromise()
|
||||
)?.id;
|
||||
|
||||
const activatedProcessId = await this._applicationService
|
||||
.getActivatedProcessId$()
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
const activatedProcessId = await this._applicationService.getActivatedProcessId$().pipe(first()).toPromise();
|
||||
|
||||
// Darf nur reinkommen wenn der aktuell aktive Tab ein Bestellabschluss Tab ist
|
||||
if (
|
||||
!!lastActivatedCartCheckoutProcessId &&
|
||||
lastActivatedCartCheckoutProcessId === activatedProcessId
|
||||
) {
|
||||
await this.fromCartCheckoutProcess(
|
||||
processes,
|
||||
lastActivatedCartCheckoutProcessId,
|
||||
);
|
||||
if (!!lastActivatedCartCheckoutProcessId && lastActivatedCartCheckoutProcessId === activatedProcessId) {
|
||||
await this.fromCartCheckoutProcess(processes, lastActivatedCartCheckoutProcessId);
|
||||
return false;
|
||||
} else if (
|
||||
!!lastActivatedGoodsOutProcessId &&
|
||||
lastActivatedGoodsOutProcessId === activatedProcessId
|
||||
) {
|
||||
} else if (!!lastActivatedGoodsOutProcessId && lastActivatedGoodsOutProcessId === activatedProcessId) {
|
||||
await this.fromGoodsOutProcess(processes, lastActivatedGoodsOutProcessId);
|
||||
return false;
|
||||
}
|
||||
@@ -119,28 +68,25 @@ export class CanActivateCustomerGuard {
|
||||
const newProcessId = Date.now();
|
||||
await this._applicationService.createProcess({
|
||||
id: newProcessId,
|
||||
type: "cart",
|
||||
section: "customer",
|
||||
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === "cart"))}`,
|
||||
type: 'cart',
|
||||
section: 'customer',
|
||||
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
|
||||
});
|
||||
|
||||
await this.navigateToDefaultRoute(newProcessId);
|
||||
}
|
||||
|
||||
// Bei offener Bestellbestätigung und Klick auf Footer Kundensuche
|
||||
async fromCartCheckoutProcess(
|
||||
processes: ApplicationProcess[],
|
||||
processId: number,
|
||||
) {
|
||||
async fromCartCheckoutProcess(processes: ApplicationProcess[], processId: number) {
|
||||
// Um alle Checkout Daten zu resetten die mit dem Prozess assoziiert sind
|
||||
this._checkoutService.removeProcess({ processId });
|
||||
|
||||
// Ändere type cart-checkout zu cart
|
||||
this._applicationService.patchProcess(processId, {
|
||||
id: processId,
|
||||
type: "cart",
|
||||
section: "customer",
|
||||
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === "cart"))}`,
|
||||
type: 'cart',
|
||||
section: 'customer',
|
||||
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
|
||||
data: {},
|
||||
});
|
||||
|
||||
@@ -149,31 +95,22 @@ export class CanActivateCustomerGuard {
|
||||
}
|
||||
|
||||
// Bei offener Warenausgabe und Klick auf Footer Kundensuche
|
||||
async fromGoodsOutProcess(
|
||||
processes: ApplicationProcess[],
|
||||
processId: number,
|
||||
) {
|
||||
const buyer = await this._checkoutService
|
||||
.getBuyer({ processId })
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
const customerFeatures = await this._checkoutService
|
||||
.getCustomerFeatures({ processId })
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
async fromGoodsOutProcess(processes: ApplicationProcess[], processId: number) {
|
||||
const buyer = await this._checkoutService.getBuyer({ processId }).pipe(first()).toPromise();
|
||||
const customerFeatures = await this._checkoutService.getCustomerFeatures({ processId }).pipe(first()).toPromise();
|
||||
const name = buyer
|
||||
? customerFeatures?.b2b
|
||||
? buyer.organisation?.name
|
||||
? buyer.organisation?.name
|
||||
: buyer.lastName
|
||||
: buyer.lastName
|
||||
: `Vorgang ${this.processNumber(processes.filter((process) => process.type === "cart"))}`;
|
||||
: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`;
|
||||
|
||||
// Ändere type goods-out zu cart
|
||||
this._applicationService.patchProcess(processId, {
|
||||
id: processId,
|
||||
type: "cart",
|
||||
section: "customer",
|
||||
type: 'cart',
|
||||
section: 'customer',
|
||||
name,
|
||||
});
|
||||
|
||||
@@ -182,20 +119,12 @@ export class CanActivateCustomerGuard {
|
||||
}
|
||||
|
||||
processNumber(processes: ApplicationProcess[]) {
|
||||
const processNumbers = processes?.map((process) =>
|
||||
Number(process?.name?.replace(/\D/g, "")),
|
||||
);
|
||||
return !!processNumbers && processNumbers.length > 0
|
||||
? this.findMissingNumber(processNumbers)
|
||||
: 1;
|
||||
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
|
||||
return !!processNumbers && processNumbers.length > 0 ? this.findMissingNumber(processNumbers) : 1;
|
||||
}
|
||||
|
||||
findMissingNumber(processNumbers: number[]) {
|
||||
for (
|
||||
let missingNumber = 1;
|
||||
missingNumber < Math.max(...processNumbers);
|
||||
missingNumber++
|
||||
) {
|
||||
for (let missingNumber = 1; missingNumber < Math.max(...processNumbers); missingNumber++) {
|
||||
if (!processNumbers.find((number) => number === missingNumber)) {
|
||||
return missingNumber;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Platform, PlatformModule } from '@angular/cdk/platform';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { BranchSelectorComponent } from '@shared/components/branch-selector';
|
||||
import { BranchDTO } from '@generated/swagger/checkout-api';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
@@ -8,9 +9,9 @@ import { BehaviorSubject } from 'rxjs';
|
||||
selector: 'app-preview',
|
||||
templateUrl: 'preview.component.html',
|
||||
styleUrls: ['preview.component.css'],
|
||||
imports: [CommonModule, PlatformModule],
|
||||
imports: [CommonModule, BranchSelectorComponent, PlatformModule],
|
||||
})
|
||||
export class PreviewComponent {
|
||||
export class PreviewComponent implements OnInit {
|
||||
selectedBranch$ = new BehaviorSubject<BranchDTO>({});
|
||||
|
||||
get appVersion() {
|
||||
@@ -23,7 +24,7 @@ export class PreviewComponent {
|
||||
|
||||
get navigator() {
|
||||
const nav = {};
|
||||
for (const i in window.navigator) nav[i] = navigator[i];
|
||||
for (let i in window.navigator) nav[i] = navigator[i];
|
||||
return nav;
|
||||
}
|
||||
|
||||
@@ -50,6 +51,8 @@ export class PreviewComponent {
|
||||
|
||||
constructor(private readonly _platform: Platform) {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
setNewBranch(branch: BranchDTO) {
|
||||
this.selectedBranch$.next(branch);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
import { HttpErrorResponse } from "@angular/common/http";
|
||||
import { ErrorHandler, Injectable } from "@angular/core";
|
||||
import { AuthService } from "@core/auth";
|
||||
import {
|
||||
DialogModel,
|
||||
UiDialogModalComponent,
|
||||
UiErrorModalComponent,
|
||||
UiModalService,
|
||||
} from "@ui/modal";
|
||||
import { IsaLogProvider } from "./isa.log-provider";
|
||||
import { LogLevel } from "@core/logger";
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { ErrorHandler, Injectable } from '@angular/core';
|
||||
import { AuthService } from '@core/auth';
|
||||
import { DialogModel, UiDialogModalComponent, UiErrorModalComponent, UiModalService } from '@ui/modal';
|
||||
import { IsaLogProvider } from './isa.log-provider';
|
||||
import { LogLevel } from '@core/logger';
|
||||
|
||||
@Injectable({ providedIn: "root" })
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class IsaErrorHandler implements ErrorHandler {
|
||||
constructor(
|
||||
private _modal: UiModalService,
|
||||
@@ -22,7 +17,7 @@ export class IsaErrorHandler implements ErrorHandler {
|
||||
console.error(error);
|
||||
|
||||
// Bei Klick auf Abbrechen auf der Login Seite erneut zur Login Seite weiterleiten
|
||||
if (error?.type === "token_error") {
|
||||
if (error?.type === 'token_error') {
|
||||
this._authService.login();
|
||||
return;
|
||||
}
|
||||
@@ -31,14 +26,11 @@ export class IsaErrorHandler implements ErrorHandler {
|
||||
await this._modal
|
||||
.open({
|
||||
content: UiDialogModalComponent,
|
||||
title: "Sitzung abgelaufen",
|
||||
title: 'Sitzung abgelaufen',
|
||||
data: {
|
||||
handleCommand: false,
|
||||
content:
|
||||
"Sie waren zu lange nicht in der ISA aktiv. Bitte melden Sie sich erneut an",
|
||||
actions: [
|
||||
{ command: "CLOSE", selected: true, label: "Erneut anmelden" },
|
||||
],
|
||||
content: 'Sie waren zu lange nicht in der ISA aktiv. Bitte melden Sie sich erneut an',
|
||||
actions: [{ command: 'CLOSE', selected: true, label: 'Erneut anmelden' }],
|
||||
} as DialogModel,
|
||||
})
|
||||
.afterClosed$.toPromise();
|
||||
@@ -47,11 +39,7 @@ export class IsaErrorHandler implements ErrorHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this._isaLogProvider.log(LogLevel.ERROR, "Client Error", error);
|
||||
} catch (logError) {
|
||||
console.error("Error logging to IsaLogProvider:", logError);
|
||||
}
|
||||
this._isaLogProvider.log(LogLevel.ERROR, 'Client Error', error);
|
||||
|
||||
// this._modal.open({
|
||||
// content: UiErrorModalComponent,
|
||||
|
||||
@@ -1,36 +1,28 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { LogLevel, LogProvider } from "@core/logger";
|
||||
import { UserStateService } from "@generated/swagger/isa-api";
|
||||
import { environment } from "../../environments/environment";
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { LogLevel, LogProvider } from '@core/logger';
|
||||
import { UserStateService } from '@generated/swagger/isa-api';
|
||||
import { environment } from '../../environments/environment';
|
||||
|
||||
@Injectable({ providedIn: "root" })
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class IsaLogProvider implements LogProvider {
|
||||
static InfoService: UserStateService | undefined;
|
||||
|
||||
log(
|
||||
logLevel: LogLevel,
|
||||
message: string,
|
||||
error: Error,
|
||||
...optionalParams: any[]
|
||||
): void {
|
||||
try {
|
||||
if (
|
||||
!environment.production &&
|
||||
(logLevel === LogLevel.WARN || logLevel === LogLevel.ERROR)
|
||||
) {
|
||||
IsaLogProvider.InfoService?.UserStateSaveLog({
|
||||
logType: logLevel,
|
||||
message: message,
|
||||
content: JSON.stringify({
|
||||
error: error?.name,
|
||||
message: error?.message,
|
||||
stack: error?.stack,
|
||||
data: optionalParams,
|
||||
}),
|
||||
}).toPromise();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error logging to InfoService:", error);
|
||||
constructor() {}
|
||||
|
||||
log(logLevel: LogLevel, message: string, error: Error, ...optionalParams: any[]): void {
|
||||
if (!environment.production && (logLevel === LogLevel.WARN || logLevel === LogLevel.ERROR)) {
|
||||
IsaLogProvider.InfoService?.UserStateSaveLog({
|
||||
logType: logLevel,
|
||||
message: message,
|
||||
content: JSON.stringify({
|
||||
error: error?.name,
|
||||
message: error?.message,
|
||||
stack: error?.stack,
|
||||
data: optionalParams,
|
||||
}),
|
||||
})
|
||||
.toPromise()
|
||||
.catch(() => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Logger, LogLevel } from "@core/logger";
|
||||
import { Store } from "@ngrx/store";
|
||||
import { debounceTime, switchMap, takeUntil } from "rxjs/operators";
|
||||
import { RootState } from "./root.state";
|
||||
import packageInfo from "packageJson";
|
||||
import { environment } from "../../environments/environment";
|
||||
import { Subject } from "rxjs";
|
||||
import { AuthService } from "@core/auth";
|
||||
import { injectStorage, UserStorageProvider } from "@isa/core/storage";
|
||||
import { isEqual } from "lodash";
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Logger, LogLevel } from '@core/logger';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { debounceTime, switchMap, takeUntil } from 'rxjs/operators';
|
||||
import { RootState } from './root.state';
|
||||
import packageInfo from 'packageJson';
|
||||
import { environment } from '../../environments/environment';
|
||||
import { Subject } from 'rxjs';
|
||||
import { AuthService } from '@core/auth';
|
||||
import { injectStorage, UserStorageProvider } from '@isa/core/storage';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
@Injectable({ providedIn: "root" })
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class RootStateService {
|
||||
static LOCAL_STORAGE_KEY = "ISA_APP_INITIALSTATE";
|
||||
static LOCAL_STORAGE_KEY = 'ISA_APP_INITIALSTATE';
|
||||
|
||||
#storage = injectStorage(UserStorageProvider);
|
||||
|
||||
@@ -29,17 +29,14 @@ export class RootStateService {
|
||||
);
|
||||
}
|
||||
|
||||
window["clearUserState"] = () => {
|
||||
window['clearUserState'] = () => {
|
||||
this.clear();
|
||||
};
|
||||
}
|
||||
|
||||
async init() {
|
||||
await this.load();
|
||||
this._store.dispatch({
|
||||
type: "HYDRATE",
|
||||
payload: RootStateService.LoadFromLocalStorage(),
|
||||
});
|
||||
this._store.dispatch({ type: 'HYDRATE', payload: RootStateService.LoadFromLocalStorage() });
|
||||
this.initSave();
|
||||
}
|
||||
|
||||
@@ -53,10 +50,14 @@ export class RootStateService {
|
||||
const data = {
|
||||
...state,
|
||||
version: packageInfo.version,
|
||||
sub: this._authService.getClaimByKey("sub"),
|
||||
sub: this._authService.getClaimByKey('sub'),
|
||||
};
|
||||
RootStateService.SaveToLocalStorageRaw(JSON.stringify(data));
|
||||
return this.#storage.set("state", data);
|
||||
return this.#storage.set('state', {
|
||||
...state,
|
||||
version: packageInfo.version,
|
||||
sub: this._authService.getClaimByKey('sub'),
|
||||
});
|
||||
}),
|
||||
)
|
||||
.subscribe();
|
||||
@@ -67,7 +68,7 @@ export class RootStateService {
|
||||
*/
|
||||
async load(): Promise<boolean> {
|
||||
try {
|
||||
const res = await this.#storage.get("state");
|
||||
const res = await this.#storage.get('state');
|
||||
|
||||
const storageContent = RootStateService.LoadFromLocalStorageRaw();
|
||||
|
||||
@@ -87,7 +88,7 @@ export class RootStateService {
|
||||
async clear() {
|
||||
try {
|
||||
this._cancelSave.next();
|
||||
await this.#storage.clear("state");
|
||||
await this.#storage.clear('state');
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
RootStateService.RemoveFromLocalStorage();
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
@@ -111,7 +112,7 @@ export class RootStateService {
|
||||
try {
|
||||
return JSON.parse(raw);
|
||||
} catch (error) {
|
||||
console.error("Error parsing local storage:", error);
|
||||
console.error('Error parsing local storage:', error);
|
||||
this.RemoveFromLocalStorage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,85 +1,86 @@
|
||||
{
|
||||
"title": "ISA - Feature",
|
||||
"silentRefresh": {
|
||||
"interval": 300000
|
||||
},
|
||||
"@cdn/product-image": {
|
||||
"url": "https://produktbilder.paragon-data.net"
|
||||
},
|
||||
"@core/auth": {
|
||||
"issuer": "https://sso-test.paragon-data.de",
|
||||
"clientId": "hug-isa",
|
||||
"responseType": "id_token token",
|
||||
"oidc": true,
|
||||
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi isa-wws-webapi"
|
||||
},
|
||||
"@core/logger": {
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"@domain/checkout": {
|
||||
"olaExpiration": "5m"
|
||||
},
|
||||
"@swagger/isa": {
|
||||
"rootUrl": "https://isa-feature.paragon-data.net/isa/v1"
|
||||
},
|
||||
"@swagger/cat": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/catsearch/v6"
|
||||
},
|
||||
"@swagger/av": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/ava/v6"
|
||||
},
|
||||
"@swagger/checkout": {
|
||||
"rootUrl": "https://isa-feature.paragon-data.net/checkout/v6"
|
||||
},
|
||||
"@swagger/crm": {
|
||||
"rootUrl": "https://isa-feature.paragon-data.net/crm/v6"
|
||||
},
|
||||
"@swagger/oms": {
|
||||
"rootUrl": "https://isa-feature.paragon-data.net/oms/v6"
|
||||
},
|
||||
"@swagger/print": {
|
||||
"rootUrl": "https://isa-feature.paragon-data.net/print/v1"
|
||||
},
|
||||
"@swagger/eis": {
|
||||
"rootUrl": "https://filialinformationsystem-test.paragon-systems.de/eiswebapi/v1"
|
||||
},
|
||||
"@swagger/remi": {
|
||||
"rootUrl": "https://isa-feature.paragon-data.net/inv/v6"
|
||||
},
|
||||
"@swagger/wws": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/wws/v1"
|
||||
},
|
||||
"hubs": {
|
||||
"notifications": {
|
||||
"url": "https://isa-feature.paragon-data.net/isa/v1/rt",
|
||||
"enableAutomaticReconnect": false,
|
||||
"httpOptions": {
|
||||
"transport": 1,
|
||||
"logMessageContent": true,
|
||||
"skipNegotiation": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"process": {
|
||||
"ids": {
|
||||
"goodsOut": 1000,
|
||||
"goodsIn": 2000,
|
||||
"taskCalendar": 3000,
|
||||
"remission": 4000,
|
||||
"packageInspection": 5000,
|
||||
"assortment": 6000,
|
||||
"pickupShelf": 7000
|
||||
}
|
||||
},
|
||||
"checkForUpdates": 3600000,
|
||||
"licence": {
|
||||
"scandit": "Ae8F2Wx2RMq5Lvn7UUAlWzVFZTt2+ubMAF8XtDpmPlNkBeG/LWs1M7AbgDW0LQqYLnszClEENaEHS56/6Ts2vrJ1Ux03CXUjK3jUvZpF5OchXR1CpnmpepJ6WxPCd7LMVHUGG1BbwPLDTFjP3y8uT0caTSmmGrYQWAs4CZcEF+ZBabP0z7vfm+hCZF/ebj9qqCJZcW8nH/n19hohshllzYBjFXjh87P2lIh1s6yZS3OaQWWXo/o0AKdxx7T6CVyR0/G5zq6uYJWf6rs3euUBEhpzOZHbHZK86Lvy2AVBEyVkkcttlDW1J2fA4l1W1JV/Xibz8AQV6kG482EpGF42KEoK48paZgX3e1AQsqUtmqzw294dcP4zMVstnw5/WrwKKi/5E/nOOJT2txYP1ZufIjPrwNFsqTlv7xCQlHjMzFGYwT816yD5qLRLbwOtjrkUPXNZLZ06T4upvWwJDmm8XgdeoDqMjHdcO4lwji1bl9EiIYJ/2qnsk9yZ2FqSaHzn4cbiL0f5u2HFlNAP0GUujGRlthGhHi6o4dFU+WAxKsFMKVt+SfoQUazNKHFVQgiAklTIZxIc/HUVzRvOLMxf+wFDerraBtcqGJg+g/5mrWYqeDBGhCBHtKiYf6244IJ4afzNTiH1/30SJcRzXwbEa3A7q1fJTx9/nLTOfVPrJKBQs7f/OQs2dA7LDCel8mzXdbjvsNQaeU5+iCIAq6zbTNKy1xT8wwj+VZrQmtNJs+qeznD+u29nCM24h8xCmRpvNPo4/Mww/lrTNrrNwLBSn1pMIwsH7yS9hH0v0oNAM3A6bVtk1D9qEkbyw+xZa+MZGpMP0D0CdcsqHalPcm5r/Ik="
|
||||
},
|
||||
"gender": {
|
||||
"0": "Keine Anrede",
|
||||
"1": "Enby",
|
||||
"2": "Herr",
|
||||
"4": "Frau"
|
||||
},
|
||||
"@shared/icon": "/assets/icons.json"
|
||||
}
|
||||
"title": "ISA - Feature",
|
||||
"silentRefresh": {
|
||||
"interval": 300000
|
||||
},
|
||||
"@cdn/product-image": {
|
||||
"url": "https://produktbilder.paragon-data.net"
|
||||
},
|
||||
"@core/auth": {
|
||||
"issuer": "https://sso-test.paragon-data.de",
|
||||
"clientId": "hug-isa",
|
||||
"responseType": "id_token token",
|
||||
"oidc": true,
|
||||
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi isa-wws-webapi"
|
||||
},
|
||||
"@core/logger": {
|
||||
"logLevel": "debug"
|
||||
},
|
||||
"@domain/checkout": {
|
||||
"olaExpiration": "5m"
|
||||
},
|
||||
"@swagger/isa": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/isa/v1"
|
||||
},
|
||||
"@swagger/cat": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/catsearch/v6"
|
||||
},
|
||||
"@swagger/av": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/ava/v6"
|
||||
},
|
||||
"@swagger/checkout": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/checkout/v6"
|
||||
},
|
||||
"@swagger/crm": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/crm/v6"
|
||||
},
|
||||
"@swagger/oms": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/oms/v6"
|
||||
},
|
||||
"@swagger/print": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/print/v1"
|
||||
},
|
||||
"@swagger/eis": {
|
||||
"rootUrl": "https://filialinformationsystem-test.paragon-systems.de/eiswebapi/v1"
|
||||
},
|
||||
"@swagger/remi": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/inv/v6"
|
||||
},
|
||||
"@swagger/wws": {
|
||||
"rootUrl": "https://isa-test.paragon-data.net/wws/v1"
|
||||
},
|
||||
"hubs": {
|
||||
"notifications": {
|
||||
"url": "https://isa-test.paragon-data.net/isa/v1/rt",
|
||||
"enableAutomaticReconnect": false,
|
||||
"httpOptions": {
|
||||
"transport": 1,
|
||||
"logMessageContent": true,
|
||||
"skipNegotiation": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"process": {
|
||||
"ids": {
|
||||
"goodsOut": 1000,
|
||||
"goodsIn": 2000,
|
||||
"taskCalendar": 3000,
|
||||
"remission": 4000,
|
||||
"packageInspection": 5000,
|
||||
"assortment": 6000,
|
||||
"pickupShelf": 7000
|
||||
}
|
||||
},
|
||||
"checkForUpdates": 3600000,
|
||||
"licence": {
|
||||
"scandit": "Ae8F2Wx2RMq5Lvn7UUAlWzVFZTt2+ubMAF8XtDpmPlNkBeG/LWs1M7AbgDW0LQqYLnszClEENaEHS56/6Ts2vrJ1Ux03CXUjK3jUvZpF5OchXR1CpnmpepJ6WxPCd7LMVHUGG1BbwPLDTFjP3y8uT0caTSmmGrYQWAs4CZcEF+ZBabP0z7vfm+hCZF/ebj9qqCJZcW8nH/n19hohshllzYBjFXjh87P2lIh1s6yZS3OaQWWXo/o0AKdxx7T6CVyR0/G5zq6uYJWf6rs3euUBEhpzOZHbHZK86Lvy2AVBEyVkkcttlDW1J2fA4l1W1JV/Xibz8AQV6kG482EpGF42KEoK48paZgX3e1AQsqUtmqzw294dcP4zMVstnw5/WrwKKi/5E/nOOJT2txYP1ZufIjPrwNFsqTlv7xCQlHjMzFGYwT816yD5qLRLbwOtjrkUPXNZLZ06T4upvWwJDmm8XgdeoDqMjHdcO4lwji1bl9EiIYJ/2qnsk9yZ2FqSaHzn4cbiL0f5u2HFlNAP0GUujGRlthGhHi6o4dFU+WAxKsFMKVt+SfoQUazNKHFVQgiAklTIZxIc/HUVzRvOLMxf+wFDerraBtcqGJg+g/5mrWYqeDBGhCBHtKiYf6244IJ4afzNTiH1/30SJcRzXwbEa3A7q1fJTx9/nLTOfVPrJKBQs7f/OQs2dA7LDCel8mzXdbjvsNQaeU5+iCIAq6zbTNKy1xT8wwj+VZrQmtNJs+qeznD+u29nCM24h8xCmRpvNPo4/Mww/lrTNrrNwLBSn1pMIwsH7yS9hH0v0oNAM3A6bVtk1D9qEkbyw+xZa+MZGpMP0D0CdcsqHalPcm5r/Ik="
|
||||
},
|
||||
"gender": {
|
||||
"0": "Keine Anrede",
|
||||
"1": "Enby",
|
||||
"2": "Herr",
|
||||
"4": "Frau"
|
||||
},
|
||||
"@shared/icon": "/assets/icons.json"
|
||||
|
||||
}
|
||||
@@ -1,18 +1,13 @@
|
||||
import { coerceArray } from "@angular/cdk/coercion";
|
||||
import { inject, Injectable } from "@angular/core";
|
||||
import { Config } from "@core/config";
|
||||
import { isNullOrUndefined } from "@utils/common";
|
||||
import { AuthConfig, OAuthService } from "angular-oauth2-oidc";
|
||||
import { JwksValidationHandler } from "angular-oauth2-oidc-jwks";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
/**
|
||||
* Storage key for the URL to redirect to after login
|
||||
*/
|
||||
const REDIRECT_URL_KEY = "auth_redirect_url";
|
||||
import { coerceArray } from '@angular/cdk/coercion';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { Config } from '@core/config';
|
||||
import { isNullOrUndefined } from '@utils/common';
|
||||
import { AuthConfig, OAuthService } from 'angular-oauth2-oidc';
|
||||
import { JwksValidationHandler } from 'angular-oauth2-oidc-jwks';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AuthService {
|
||||
private readonly _initialized = new BehaviorSubject<boolean>(false);
|
||||
@@ -21,39 +16,28 @@ export class AuthService {
|
||||
}
|
||||
|
||||
private _authConfig: AuthConfig;
|
||||
|
||||
constructor(
|
||||
private _config: Config,
|
||||
private readonly _oAuthService: OAuthService,
|
||||
) {
|
||||
this._oAuthService.events?.subscribe((event) => {
|
||||
if (event.type === "token_received") {
|
||||
console.log(
|
||||
"SSO Token Expiration:",
|
||||
new Date(this._oAuthService.getAccessTokenExpiration()),
|
||||
);
|
||||
|
||||
// Handle redirect after successful authentication
|
||||
setTimeout(() => {
|
||||
const redirectUrl = this._getAndClearRedirectUrl();
|
||||
if (redirectUrl) {
|
||||
window.location.href = redirectUrl;
|
||||
}
|
||||
}, 100);
|
||||
if (event.type === 'token_received') {
|
||||
console.log('SSO Token Expiration:', new Date(this._oAuthService.getAccessTokenExpiration()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (this._initialized.getValue()) {
|
||||
throw new Error("AuthService is already initialized");
|
||||
throw new Error('AuthService is already initialized');
|
||||
}
|
||||
|
||||
this._authConfig = this._config.get("@core/auth");
|
||||
this._authConfig = this._config.get('@core/auth');
|
||||
|
||||
this._authConfig.redirectUri = window.location.origin;
|
||||
|
||||
this._authConfig.silentRefreshRedirectUri =
|
||||
window.location.origin + "/silent-refresh.html";
|
||||
this._authConfig.silentRefreshRedirectUri = window.location.origin + '/silent-refresh.html';
|
||||
this._authConfig.useSilentRefresh = true;
|
||||
|
||||
this._oAuthService.configure(this._authConfig);
|
||||
@@ -71,18 +55,12 @@ export class AuthService {
|
||||
}
|
||||
|
||||
isIdTokenValid() {
|
||||
console.log(
|
||||
"ID Token Expiration:",
|
||||
new Date(this._oAuthService.getIdTokenExpiration()),
|
||||
);
|
||||
console.log('ID Token Expiration:', new Date(this._oAuthService.getIdTokenExpiration()));
|
||||
return this._oAuthService.hasValidIdToken();
|
||||
}
|
||||
|
||||
isAccessTokenValid() {
|
||||
console.log(
|
||||
"ACCESS Token Expiration:",
|
||||
new Date(this._oAuthService.getAccessTokenExpiration()),
|
||||
);
|
||||
console.log('ACCESS Token Expiration:', new Date(this._oAuthService.getAccessTokenExpiration()));
|
||||
return this._oAuthService.hasValidAccessToken();
|
||||
}
|
||||
|
||||
@@ -107,31 +85,14 @@ export class AuthService {
|
||||
if (isNullOrUndefined(token)) {
|
||||
return null;
|
||||
}
|
||||
const base64Url = token.split(".")[1];
|
||||
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
|
||||
const base64Url = token.split('.')[1];
|
||||
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
||||
|
||||
const encoded = window.atob(base64);
|
||||
return JSON.parse(encoded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the URL to redirect to after successful login
|
||||
*/
|
||||
_saveRedirectUrl(): void {
|
||||
localStorage.setItem(REDIRECT_URL_KEY, window.location.href);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and clears the saved redirect URL
|
||||
*/
|
||||
_getAndClearRedirectUrl(): string | null {
|
||||
const url = localStorage.getItem(REDIRECT_URL_KEY);
|
||||
localStorage.removeItem(REDIRECT_URL_KEY);
|
||||
return url;
|
||||
}
|
||||
|
||||
login() {
|
||||
this._saveRedirectUrl();
|
||||
this._oAuthService.initLoginFlow();
|
||||
}
|
||||
|
||||
@@ -148,7 +109,7 @@ export class AuthService {
|
||||
hasRole(role: string | string[]) {
|
||||
const roles = coerceArray(role);
|
||||
|
||||
const userRoles = this.getClaimByKey("role");
|
||||
const userRoles = this.getClaimByKey('role');
|
||||
|
||||
if (isNullOrUndefined(userRoles)) {
|
||||
return false;
|
||||
@@ -159,10 +120,7 @@ export class AuthService {
|
||||
|
||||
async refresh() {
|
||||
try {
|
||||
if (
|
||||
this._authConfig.responseType.includes("code") &&
|
||||
this._authConfig.scope.includes("offline_access")
|
||||
) {
|
||||
if (this._authConfig.responseType.includes('code') && this._authConfig.scope.includes('offline_access')) {
|
||||
await this._oAuthService.refreshToken();
|
||||
} else {
|
||||
await this._oAuthService.silentRefresh();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { LogLevel } from "./log-level";
|
||||
import { Injectable } from '@angular/core';
|
||||
import { LogLevel } from './log-level';
|
||||
|
||||
export interface LogProvider {
|
||||
log(logLevel: LogLevel, message: string, ...optionalParams: any[]): void;
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ArticleDTO,
|
||||
DisplayInfoDTO,
|
||||
EISPublicDocumentService,
|
||||
} from '@generated/swagger/eis-api';
|
||||
import { ArticleDTO, DisplayInfoDTO, EISPublicDocumentService } from '@generated/swagger/eis-api';
|
||||
import {
|
||||
CatalogPrintService,
|
||||
CheckoutPrintService,
|
||||
@@ -46,9 +42,7 @@ export class DomainPrinterService {
|
||||
map((response: any) => {
|
||||
if (response.error && response.error.status === 503) {
|
||||
return {
|
||||
error: response.message
|
||||
? response.message
|
||||
: 'Das Backend ist derzeit nicht erreichbar',
|
||||
error: response.message ? response.message : 'Das Backend ist derzeit nicht erreichbar',
|
||||
};
|
||||
}
|
||||
if (response.error && response.error.name === 'TimeoutError') {
|
||||
@@ -83,9 +77,7 @@ export class DomainPrinterService {
|
||||
map((response: any) => {
|
||||
if (response.error && response.error.status === 503) {
|
||||
return {
|
||||
error: response.message
|
||||
? response.message
|
||||
: 'Das Backend ist derzeit nicht erreichbar',
|
||||
error: response.message ? response.message : 'Das Backend ist derzeit nicht erreichbar',
|
||||
};
|
||||
}
|
||||
if (response.error && response.error.name === 'TimeoutError') {
|
||||
@@ -120,9 +112,7 @@ export class DomainPrinterService {
|
||||
map((response: any) => {
|
||||
if (response.error && response.error.status === 503) {
|
||||
return {
|
||||
error: response.message
|
||||
? response.message
|
||||
: 'Das Backend ist derzeit nicht erreichbar',
|
||||
error: response.message ? response.message : 'Das Backend ist derzeit nicht erreichbar',
|
||||
};
|
||||
}
|
||||
if (response.error && response.error.name === 'TimeoutError') {
|
||||
@@ -151,61 +141,28 @@ export class DomainPrinterService {
|
||||
);
|
||||
}
|
||||
|
||||
printOrder({
|
||||
orderIds,
|
||||
printer,
|
||||
}: {
|
||||
orderIds: number[];
|
||||
printer: string;
|
||||
}): Observable<ResponseArgs> {
|
||||
printOrder({ orderIds, printer }: { orderIds: number[]; printer: string }): Observable<ResponseArgs> {
|
||||
const params = <any>{
|
||||
printer: printer,
|
||||
data: orderIds,
|
||||
};
|
||||
return this.oMSPrintService
|
||||
.OMSPrintAbholscheinById(params)
|
||||
.pipe(timeout(20000));
|
||||
return this.oMSPrintService.OMSPrintAbholscheinById(params).pipe(timeout(20000));
|
||||
}
|
||||
|
||||
printShippingNote({
|
||||
receipts,
|
||||
printer,
|
||||
}: {
|
||||
receipts: number[];
|
||||
printer: string;
|
||||
}) {
|
||||
printShippingNote({ receipts, printer }: { receipts: number[]; printer: string }) {
|
||||
return this.oMSPrintService.OMSPrintLieferschein({
|
||||
printer,
|
||||
data: receipts,
|
||||
});
|
||||
}
|
||||
|
||||
printCompartmentLabel({
|
||||
orderItemSubsetIds,
|
||||
printer,
|
||||
}: {
|
||||
orderItemSubsetIds: number[];
|
||||
printer: string;
|
||||
}) {
|
||||
printCompartmentLabel({ orderItemSubsetIds, printer }: { orderItemSubsetIds: number[]; printer: string }) {
|
||||
return this.oMSPrintService.OMSPrintAbholfachetikett({
|
||||
printer,
|
||||
data: orderItemSubsetIds,
|
||||
});
|
||||
}
|
||||
|
||||
printReturnReceipt({
|
||||
receiptIds,
|
||||
printer,
|
||||
}: {
|
||||
receiptIds: number[];
|
||||
printer: string;
|
||||
}) {
|
||||
return this.oMSPrintService.OMSPrintReturnReceipt({
|
||||
printer,
|
||||
data: receiptIds,
|
||||
});
|
||||
}
|
||||
|
||||
printKubiAgb({ p4mCode, printer }: { p4mCode: string; printer: string }) {
|
||||
return this._loyaltyCardPrintService.LoyaltyCardPrintPrintLoyaltyCardAGB({
|
||||
printer,
|
||||
@@ -213,36 +170,20 @@ export class DomainPrinterService {
|
||||
});
|
||||
}
|
||||
|
||||
printProduct({
|
||||
item,
|
||||
printer,
|
||||
}: {
|
||||
item: ItemDTO;
|
||||
printer: string;
|
||||
}): Observable<ResponseArgs> {
|
||||
printProduct({ item, printer }: { item: ItemDTO; printer: string }): Observable<ResponseArgs> {
|
||||
const params = <PrintRequestOfIEnumerableOfItemDTO>{
|
||||
printer: printer,
|
||||
data: [item],
|
||||
};
|
||||
return this.catalogPrintService
|
||||
.CatalogPrintArtikelDetail(params)
|
||||
.pipe(timeout(20000));
|
||||
return this.catalogPrintService.CatalogPrintArtikelDetail(params).pipe(timeout(20000));
|
||||
}
|
||||
|
||||
printCart({
|
||||
cartId,
|
||||
printer,
|
||||
}: {
|
||||
cartId: number;
|
||||
printer: string;
|
||||
}): Observable<ResponseArgs> {
|
||||
printCart({ cartId, printer }: { cartId: number; printer: string }): Observable<ResponseArgs> {
|
||||
const params = <any>{
|
||||
printer: printer,
|
||||
data: cartId,
|
||||
};
|
||||
return this.checkoutPrintService
|
||||
.CheckoutPrintWarenkorbById(params)
|
||||
.pipe(timeout(20000));
|
||||
return this.checkoutPrintService.CheckoutPrintWarenkorbById(params).pipe(timeout(20000));
|
||||
}
|
||||
|
||||
async printGoodsInLabel(subsetItemIds: number[]): Promise<ResponseArgs> {
|
||||
@@ -282,9 +223,7 @@ export class DomainPrinterService {
|
||||
printProductListItemsResponse(
|
||||
payload: DocumentPayloadOfIEnumerableOfProductListItemDTO,
|
||||
): Observable<ResponseArgsOfString> {
|
||||
return this._productListService.ProductListProductListItemPdfAsBase64(
|
||||
payload,
|
||||
);
|
||||
return this._productListService.ProductListProductListItemPdfAsBase64(payload);
|
||||
}
|
||||
|
||||
printProductListItems({
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ItemDTO,
|
||||
ListResponseArgsOfItemDTO,
|
||||
SearchService,
|
||||
} from '@generated/swagger/cat-search-api';
|
||||
import { ItemDTO, ListResponseArgsOfItemDTO, SearchService } from '@generated/swagger/cat-search-api';
|
||||
import {
|
||||
RemiService,
|
||||
StockService,
|
||||
@@ -22,11 +18,7 @@ import { memorize } from '@utils/common';
|
||||
import { Observable, of, throwError } from 'rxjs';
|
||||
import { catchError, map, shareReplay, switchMap } from 'rxjs/operators';
|
||||
import { RemissionListItem } from './defs';
|
||||
import {
|
||||
fromItemDto,
|
||||
mapFromReturnItemDTO,
|
||||
mapFromReturnSuggestionDTO,
|
||||
} from './mappings';
|
||||
import { fromItemDto, mapFromReturnItemDTO, mapFromReturnSuggestionDTO } from './mappings';
|
||||
import { Logger } from '@core/logger';
|
||||
import { RemissionPlacementType } from '@domain/remission';
|
||||
|
||||
@@ -212,10 +204,7 @@ export class DomainRemissionService {
|
||||
);
|
||||
}
|
||||
|
||||
getStockInformation(
|
||||
items: RemissionListItem[],
|
||||
recalculate: boolean = false,
|
||||
) {
|
||||
getStockInformation(items: RemissionListItem[], recalculate: boolean = false) {
|
||||
return this.getCurrentStock().pipe(
|
||||
switchMap((stock) =>
|
||||
this._stockService
|
||||
@@ -229,8 +218,7 @@ export class DomainRemissionService {
|
||||
map((res) => {
|
||||
const o = items.map((item) => {
|
||||
const stockInfo = res?.result?.find(
|
||||
(stockInfo) =>
|
||||
stockInfo.itemId === +item.dto.product.catalogProductNumber,
|
||||
(stockInfo) => stockInfo.itemId === +item.dto.product.catalogProductNumber,
|
||||
);
|
||||
|
||||
if (!stockInfo) {
|
||||
@@ -243,8 +231,7 @@ export class DomainRemissionService {
|
||||
return { ...item, ...defaultStockData };
|
||||
}
|
||||
|
||||
const availableStock =
|
||||
stockInfo.inStock - stockInfo.removedFromStock;
|
||||
const availableStock = stockInfo.inStock - stockInfo.removedFromStock;
|
||||
const inStock = availableStock < 0 ? 0 : availableStock;
|
||||
|
||||
let { remainingQuantity, remissionQuantity } = item;
|
||||
@@ -262,12 +249,7 @@ export class DomainRemissionService {
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
remainingQuantity,
|
||||
remissionQuantity,
|
||||
inStock,
|
||||
};
|
||||
return { ...item, remainingQuantity, remissionQuantity, inStock };
|
||||
});
|
||||
|
||||
return o;
|
||||
@@ -277,10 +259,7 @@ export class DomainRemissionService {
|
||||
);
|
||||
}
|
||||
|
||||
getRequiredCapacities(params: {
|
||||
departments?: string[];
|
||||
supplierId: number;
|
||||
}) {
|
||||
getRequiredCapacities(params: { departments?: string[]; supplierId: number }) {
|
||||
return this.getCurrentStock().pipe(
|
||||
switchMap((stock) =>
|
||||
this._remiService
|
||||
@@ -322,18 +301,13 @@ export class DomainRemissionService {
|
||||
);
|
||||
}
|
||||
|
||||
canAddReturnItem(
|
||||
item: ReturnItemDTO,
|
||||
): Observable<BatchResponseArgsOfReturnItemDTOAndReturnItemDTO> {
|
||||
canAddReturnItem(item: ReturnItemDTO): Observable<BatchResponseArgsOfReturnItemDTOAndReturnItemDTO> {
|
||||
return this._remiService.RemiCanAddReturnItem({
|
||||
data: [item],
|
||||
});
|
||||
}
|
||||
|
||||
async createReturn(
|
||||
supplierId: number,
|
||||
returnGroup?: string,
|
||||
): Promise<ReturnDTO> {
|
||||
async createReturn(supplierId: number, returnGroup?: string): Promise<ReturnDTO> {
|
||||
const response = await this._returnService
|
||||
.ReturnCreateReturn({
|
||||
data: {
|
||||
@@ -369,10 +343,7 @@ export class DomainRemissionService {
|
||||
.toPromise();
|
||||
}
|
||||
|
||||
getReturns(params: {
|
||||
start?: Date;
|
||||
returncompleted: boolean;
|
||||
}): Observable<ReturnDTO[]> {
|
||||
getReturns(params: { start?: Date; returncompleted: boolean }): Observable<ReturnDTO[]> {
|
||||
const queryToken: ReturnQueryTokenDTO = {
|
||||
start: params.start?.toISOString(),
|
||||
filter: {
|
||||
@@ -389,20 +360,13 @@ export class DomainRemissionService {
|
||||
});
|
||||
|
||||
return this.getCurrentStock().pipe(
|
||||
switchMap((stock) =>
|
||||
this._returnService.ReturnQueryReturns({
|
||||
stockId: stock.id,
|
||||
queryToken,
|
||||
}),
|
||||
),
|
||||
switchMap((stock) => this._returnService.ReturnQueryReturns({ stockId: stock.id, queryToken })),
|
||||
map((res) => res.result),
|
||||
);
|
||||
}
|
||||
|
||||
getReturn(returnId: number): Observable<ReturnDTO> {
|
||||
return this._returnService
|
||||
.ReturnGetReturn({ returnId, eagerLoading: 3 })
|
||||
.pipe(map((res) => res.result));
|
||||
return this._returnService.ReturnGetReturn({ returnId, eagerLoading: 3 }).pipe(map((res) => res.result));
|
||||
}
|
||||
|
||||
async deleteReturn(returnId: number) {
|
||||
@@ -429,11 +393,7 @@ export class DomainRemissionService {
|
||||
inStock: number;
|
||||
}) {
|
||||
return this._returnService
|
||||
.ReturnAddReturnItem({
|
||||
returnId,
|
||||
receiptId,
|
||||
data: { returnItemId, quantity, placementType, inStock },
|
||||
})
|
||||
.ReturnAddReturnItem({ returnId, receiptId, data: { returnItemId, quantity, placementType, inStock } })
|
||||
.pipe(map((r) => r.result));
|
||||
}
|
||||
|
||||
@@ -460,14 +420,7 @@ export class DomainRemissionService {
|
||||
.ReturnAddReturnSuggestion({
|
||||
returnId,
|
||||
receiptId,
|
||||
data: {
|
||||
returnSuggestionId,
|
||||
quantity,
|
||||
placementType,
|
||||
inStock,
|
||||
impedimentComment,
|
||||
remainingQuantity,
|
||||
},
|
||||
data: { returnSuggestionId, quantity, placementType, inStock, impedimentComment, remainingQuantity },
|
||||
})
|
||||
.pipe(map((r) => r.result));
|
||||
}
|
||||
@@ -485,28 +438,18 @@ export class DomainRemissionService {
|
||||
receiptId: number;
|
||||
receiptItemId: number;
|
||||
}) {
|
||||
return this._returnService.ReturnRemoveReturnItem({
|
||||
returnId,
|
||||
receiptItemId,
|
||||
receiptId,
|
||||
});
|
||||
return this._returnService.ReturnRemoveReturnItem({ returnId, receiptItemId, receiptId });
|
||||
}
|
||||
|
||||
returnImpediment(itemId: number) {
|
||||
return this._returnService
|
||||
.ReturnReturnItemImpediment({
|
||||
itemId,
|
||||
data: { comment: 'Produkt nicht gefunden' },
|
||||
})
|
||||
.ReturnReturnItemImpediment({ itemId, data: { comment: 'Produkt nicht gefunden' } })
|
||||
.pipe(map((r) => r.result));
|
||||
}
|
||||
|
||||
returnSuggestion(itemId: number) {
|
||||
return this._returnService
|
||||
.ReturnReturnSuggestionImpediment({
|
||||
itemId,
|
||||
data: { comment: 'Produkt nicht gefunden' },
|
||||
})
|
||||
.ReturnReturnSuggestionImpediment({ itemId, data: { comment: 'Produkt nicht gefunden' } })
|
||||
.pipe(map((r) => r.result));
|
||||
}
|
||||
|
||||
@@ -516,10 +459,7 @@ export class DomainRemissionService {
|
||||
* @param receiptNumber Receipt number
|
||||
* @returns ReceiptDTO
|
||||
*/
|
||||
async createReceipt(
|
||||
returnDTO: ReturnDTO,
|
||||
receiptNumber?: string,
|
||||
): Promise<ReceiptDTO> {
|
||||
async createReceipt(returnDTO: ReturnDTO, receiptNumber?: string): Promise<ReceiptDTO> {
|
||||
const stock = await this._getStock();
|
||||
|
||||
const response = await this._returnService
|
||||
@@ -570,10 +510,7 @@ export class DomainRemissionService {
|
||||
return receipt;
|
||||
}
|
||||
|
||||
async completeReceipt(
|
||||
returnId: number,
|
||||
receiptId: number,
|
||||
): Promise<ReceiptDTO> {
|
||||
async completeReceipt(returnId: number, receiptId: number): Promise<ReceiptDTO> {
|
||||
const res = await this._returnService
|
||||
.ReturnFinalizeReceipt({
|
||||
returnId,
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
import { enableProdMode, isDevMode } from "@angular/core";
|
||||
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
|
||||
import { CONFIG_DATA } from "@isa/core/config";
|
||||
import { setDefaultOptions } from "date-fns";
|
||||
import { de } from "date-fns/locale";
|
||||
import * as moment from "moment";
|
||||
import "moment/locale/de";
|
||||
import { enableProdMode, isDevMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { CONFIG_DATA } from '@isa/core/config';
|
||||
import { setDefaultOptions } from 'date-fns';
|
||||
import { de } from 'date-fns/locale';
|
||||
import * as moment from 'moment';
|
||||
|
||||
setDefaultOptions({ locale: de });
|
||||
moment.locale("de");
|
||||
moment.locale('de');
|
||||
|
||||
import { AppModule } from "./app/app.module";
|
||||
import { AppModule } from './app/app.module';
|
||||
|
||||
if (!isDevMode()) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
async function bootstrap() {
|
||||
const configRes = await fetch("/config/config.json");
|
||||
const configRes = await fetch('/config/config.json');
|
||||
|
||||
const config = await configRes.json();
|
||||
|
||||
|
||||
@@ -3,12 +3,10 @@
|
||||
<p>Vorschläge:</p>
|
||||
|
||||
<ul class="content">
|
||||
@for (item of ref?.data; track item) {
|
||||
<li>
|
||||
<span>{{ item.street }} {{ item.streetNumber }}, {{ item.zipCode }} {{ item.city }}</span>
|
||||
<button (click)="ref.close(item)">Übernehmen</button>
|
||||
</li>
|
||||
}
|
||||
<li *ngFor="let item of ref?.data">
|
||||
<span>{{ item.street }} {{ item.streetNumber }}, {{ item.zipCode }} {{ item.city }}</span>
|
||||
<button (click)="ref.close(item)">Übernehmen</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="center">
|
||||
|
||||
@@ -6,19 +6,13 @@
|
||||
uiSearchboxSearchButton
|
||||
(click)="filter(input.value)"
|
||||
[disabled]="branchesFetching$ | async"
|
||||
>
|
||||
@if (branchesFetching$ | async) {
|
||||
<ui-icon class="spin" icon="spinner" size="32px"></ui-icon>
|
||||
}
|
||||
@if (!(branchesFetching$ | async)) {
|
||||
<ui-icon icon="search" size="24px"></ui-icon>
|
||||
}
|
||||
>
|
||||
<ui-icon class="spin" *ngIf="branchesFetching$ | async" icon="spinner" size="32px"></ui-icon>
|
||||
<ui-icon *ngIf="!(branchesFetching$ | async)" icon="search" size="24px"></ui-icon>
|
||||
</button>
|
||||
<button *ngIf="input.value" type="reset" uiSearchboxClearButton (click)="filter(''); cancelSearch(); input.value = ''">
|
||||
<ui-icon icon="close" size="22px"></ui-icon>
|
||||
</button>
|
||||
@if (input.value) {
|
||||
<button type="reset" uiSearchboxClearButton (click)="filter(''); cancelSearch(); input.value = ''">
|
||||
<ui-icon icon="close" size="22px"></ui-icon>
|
||||
</button>
|
||||
}
|
||||
</ui-searchbox>
|
||||
|
||||
<p class="subtitle">
|
||||
@@ -31,7 +25,7 @@
|
||||
<hr />
|
||||
|
||||
<div class="branches">
|
||||
@for (branch of filteredBranches$ | async; track branch) {
|
||||
<ng-container *ngFor="let branch of filteredBranches$ | async">
|
||||
<div class="branch">
|
||||
<div class="branch-info">
|
||||
<span class="branch-name">
|
||||
@@ -42,23 +36,25 @@
|
||||
{{ branch.address.city }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="branch-actions">
|
||||
@if ((branch.id | stockInfo: (inStock$ | async))?.availableQuantity > 0 && branch?.isShippingEnabled) {
|
||||
<button
|
||||
class="cta-reserve"
|
||||
(click)="reserve(branch)"
|
||||
>
|
||||
Reservieren
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
*ngIf="(branch.id | stockInfo: (inStock$ | async))?.availableQuantity > 0 && branch?.isShippingEnabled"
|
||||
class="cta-reserve"
|
||||
(click)="reserve(branch)"
|
||||
>
|
||||
Reservieren
|
||||
</button>
|
||||
|
||||
<ui-spinner [show]="stockFetching$ | async">
|
||||
<span class="branch-stock">
|
||||
<ui-icon icon="home" size="22px"></ui-icon>
|
||||
|
||||
<span>{{ branch.id | inStock: (inStock$ | async) }}x</span>
|
||||
</span>
|
||||
</ui-spinner>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
}
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
</div>
|
||||
|
||||
<div class="thumbnails-wrapper">
|
||||
@for (image of images; track image) {
|
||||
<button (click)="activeImage = image" [class.selected]="activeImage.url === image.url">
|
||||
<img class="thumbnail" [src]="image.thumbUrl" />
|
||||
</button>
|
||||
}
|
||||
<button *ngFor="let image of images" (click)="activeImage = image" [class.selected]="activeImage.url === image.url">
|
||||
<img class="thumbnail" [src]="image.thumbUrl" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
|
||||
@@ -18,11 +18,9 @@
|
||||
<ui-quantity-dropdown [quantity]="itemQuantity$ | async" (quantityChange)="onQuantityChange($event)"></ui-quantity-dropdown>
|
||||
</div>
|
||||
<div class="relative">
|
||||
@if (stockWarning$ | async) {
|
||||
<div class="text-warning font-bold absolute right-0 top-0 whitespace-nowrap">
|
||||
Es befinden sich {{ availableQuantity$ | async }} Exemplare in der Filiale
|
||||
</div>
|
||||
}
|
||||
<div *ngIf="stockWarning$ | async" class="text-warning font-bold absolute right-0 top-0 whitespace-nowrap">
|
||||
Es befinden sich {{ availableQuantity$ | async }} Exemplare in der Filiale
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -20,21 +20,18 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-y-auto -mx-4 scroll-bar">
|
||||
@if (emptyShoppingCart$ | async) {
|
||||
<div class="h-full grid items-center justify-center">
|
||||
<h3 class="text-xl font-bold text-center text-gray-500">
|
||||
Warenkorb ist leer, bitte suchen oder scannen
|
||||
<br />
|
||||
Sie Artikel um den Warenkob zu füllen.
|
||||
</h3>
|
||||
</div>
|
||||
}
|
||||
@for (item of items$ | async; track trackItemById($index, item)) {
|
||||
<shared-kulturpass-order-item
|
||||
class="border-b border-solid border-[#EFF1F5]"
|
||||
[item]="item"
|
||||
></shared-kulturpass-order-item>
|
||||
}
|
||||
<div *ngIf="emptyShoppingCart$ | async" class="h-full grid items-center justify-center">
|
||||
<h3 class="text-xl font-bold text-center text-gray-500">
|
||||
Warenkorb ist leer, bitte suchen oder scannen
|
||||
<br />
|
||||
Sie Artikel um den Warenkob zu füllen.
|
||||
</h3>
|
||||
</div>
|
||||
<shared-kulturpass-order-item
|
||||
class="border-b border-solid border-[#EFF1F5]"
|
||||
*ngFor="let item of items$ | async; trackBy: trackItemById"
|
||||
[item]="item"
|
||||
></shared-kulturpass-order-item>
|
||||
</div>
|
||||
<div class="flex flex-row justify-evenly items-stretch border-t border-solid border-[#EFF1F5] py-3 px-4 -mx-4">
|
||||
<div class="grid grid-flow-row text-xl">
|
||||
@@ -52,15 +49,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid items-end justify-between">
|
||||
@if (negativeBalance$ | async) {
|
||||
<div class="text-xl text-warning font-bold text-center">Der Betrag übersteigt ihr Guthaben</div>
|
||||
}
|
||||
<div *ngIf="negativeBalance$ | async" class="text-xl text-warning font-bold text-center">Der Betrag übersteigt ihr Guthaben</div>
|
||||
<button
|
||||
type="button"
|
||||
class="bg-brand text-white px-6 py-3 font-bold rounded-full disabled:bg-disabled-branch disabled:text-active-branch"
|
||||
[disabled]="orderButtonDisabled$ | async"
|
||||
(click)="order()"
|
||||
>
|
||||
>
|
||||
<shared-loader [loading]="ordering$ | async" hideContent="true">Kauf abschließen und Rechnung drucken</shared-loader>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -2,20 +2,19 @@
|
||||
{{ message }}
|
||||
</p>
|
||||
<div class="message-modal-actions grid grid-flow-col gap-4 items-center justify-center">
|
||||
@for (action of actions; track action) {
|
||||
<button
|
||||
type="button"
|
||||
class="btn rounded-full font-bold text-p1 border-[2px] border-solid border-brand px-6"
|
||||
[class.bg-brand]="action.primary"
|
||||
[class.hover:bg-brand]="action.primary"
|
||||
[class.text-white]="action.primary"
|
||||
[class.bg-white]="!action.primary"
|
||||
[class.text-brand]="!action.primary"
|
||||
[class.hover:bg-white]="!action.primary"
|
||||
[class.hover:text-brand]="!action.primary"
|
||||
(click)="clickAction(action)"
|
||||
>
|
||||
{{ action.label }}
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
*ngFor="let action of actions"
|
||||
type="button"
|
||||
class="btn rounded-full font-bold text-p1 border-[2px] border-solid border-brand px-6"
|
||||
[class.bg-brand]="action.primary"
|
||||
[class.hover:bg-brand]="action.primary"
|
||||
[class.text-white]="action.primary"
|
||||
[class.bg-white]="!action.primary"
|
||||
[class.text-brand]="!action.primary"
|
||||
[class.hover:bg-white]="!action.primary"
|
||||
[class.hover:text-brand]="!action.primary"
|
||||
(click)="clickAction(action)"
|
||||
>
|
||||
{{ action.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { UiModalRef } from '@ui/modal';
|
||||
import { MessageModalData } from './message-modal.data';
|
||||
@@ -10,7 +10,7 @@ import { MessageModalAction } from './message-modal.action';
|
||||
styleUrls: ['message-modal.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: { class: 'shared-message-modal' },
|
||||
imports: [],
|
||||
imports: [CommonModule],
|
||||
})
|
||||
export class MessageModalComponent {
|
||||
get message() {
|
||||
|
||||
@@ -4,10 +4,8 @@
|
||||
<div class="notification-text">{{ item.text }}</div>
|
||||
</div>
|
||||
<div>
|
||||
@if (editButton) {
|
||||
<button class="notification-edit-cta text-brand font-bold text-lg px-4 py-3" (click)="itemSelected.emit(item)">
|
||||
{{ editButtonLabel }}
|
||||
</button>
|
||||
}
|
||||
<button *ngIf="editButton" class="notification-edit-cta text-brand font-bold text-lg px-4 py-3" (click)="itemSelected.emit(item)">
|
||||
{{ editButtonLabel }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="notification-list scroll-bar">
|
||||
@for (notification of notifications; track notification) {
|
||||
<ng-container *ngFor="let notification of notifications">
|
||||
<modal-notifications-list-item
|
||||
(click)="itemSelected(notification)"
|
||||
[editButtonLabel]="'Packstück-Prüfung'"
|
||||
@@ -7,5 +7,5 @@
|
||||
(itemSelected)="itemSelected($event)"
|
||||
></modal-notifications-list-item>
|
||||
<hr />
|
||||
}
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
<div class="notification-list scroll-bar">
|
||||
@for (notification of notifications; track notification) {
|
||||
<modal-notifications-list-item
|
||||
[item]="notification"
|
||||
(itemSelected)="itemSelected($event)"
|
||||
></modal-notifications-list-item>
|
||||
<ng-container *ngFor="let notification of notifications">
|
||||
<modal-notifications-list-item [item]="notification" (itemSelected)="itemSelected($event)"></modal-notifications-list-item>
|
||||
<hr />
|
||||
}
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<a
|
||||
class="cta-primary"
|
||||
[routerLink]="remissionPath()"
|
||||
(click)="navigated.emit()"
|
||||
>Zur Remission</a
|
||||
>
|
||||
<a class="cta-primary" [routerLink]="['/filiale/remission/create']" (click)="navigated.emit()">Zur Remission</a>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
inject,
|
||||
linkedSignal,
|
||||
} from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, inject } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { PickupShelfInNavigationService } from '@shared/services/navigation';
|
||||
import { UiFilter } from '@ui/filter';
|
||||
import { MessageBoardItemDTO } from '@hub/notifications';
|
||||
import { TabService } from '@isa/core/tabs';
|
||||
|
||||
@Component({
|
||||
selector: 'modal-notifications-remission-group',
|
||||
@@ -20,10 +11,7 @@ import { TabService } from '@isa/core/tabs';
|
||||
standalone: false,
|
||||
})
|
||||
export class ModalNotificationsRemissionGroupComponent {
|
||||
tabService = inject(TabService);
|
||||
private _pickupShelfInNavigationService = inject(
|
||||
PickupShelfInNavigationService,
|
||||
);
|
||||
private _pickupShelfInNavigationService = inject(PickupShelfInNavigationService);
|
||||
|
||||
@Input()
|
||||
notifications: MessageBoardItemDTO[];
|
||||
@@ -31,19 +19,11 @@ export class ModalNotificationsRemissionGroupComponent {
|
||||
@Output()
|
||||
navigated = new EventEmitter<void>();
|
||||
|
||||
remissionPath = linkedSignal(() => [
|
||||
'/',
|
||||
this.tabService.activatedTab()?.id || this.tabService.nextId(),
|
||||
'remission',
|
||||
]);
|
||||
|
||||
constructor(private _router: Router) {}
|
||||
|
||||
itemSelected(item: MessageBoardItemDTO) {
|
||||
const defaultNav = this._pickupShelfInNavigationService.listRoute();
|
||||
const queryParams = UiFilter.getQueryParamsFromQueryTokenDTO(
|
||||
item.queryToken,
|
||||
);
|
||||
const queryParams = UiFilter.getQueryParamsFromQueryTokenDTO(item.queryToken);
|
||||
this._router.navigate(defaultNav.path, {
|
||||
queryParams: {
|
||||
...defaultNav.queryParams,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<div class="notification-list scroll-bar">
|
||||
@for (notification of notifications; track notification) {
|
||||
<ng-container *ngFor="let notification of notifications">
|
||||
<modal-notifications-list-item [item]="notification" (itemSelected)="itemSelected($event)"></modal-notifications-list-item>
|
||||
<hr />
|
||||
}
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<div class="notification-list scroll-bar">
|
||||
@for (notification of notifications; track notification) {
|
||||
<ng-container *ngFor="let notification of notifications">
|
||||
<modal-notifications-list-item [item]="notification" (itemSelected)="itemSelected($event)"></modal-notifications-list-item>
|
||||
<hr />
|
||||
}
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<div class="notification-list scroll-bar">
|
||||
@for (notification of notifications; track notification) {
|
||||
<ng-container *ngFor="let notification of notifications">
|
||||
<div class="notification-headline">
|
||||
<h1>{{ notification.headline }}</h1>
|
||||
</div>
|
||||
<div class="notification-text">{{ notification.text }}</div>
|
||||
<hr />
|
||||
}
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<h1>Sie haben neue Nachrichten</h1>
|
||||
|
||||
@for (notification of notifications$ | async | keyvalue; track notification) {
|
||||
<ng-container *ngFor="let notification of notifications$ | async | keyvalue">
|
||||
<button type="button" class="notification-card" (click)="selectArea(notification.key)">
|
||||
<div class="notification-icon">
|
||||
<div class="notification-counter">{{ notification.value?.length }}</div>
|
||||
@@ -9,37 +9,30 @@
|
||||
<span>{{ notification.value?.[0]?.category }}</span>
|
||||
</button>
|
||||
<hr class="-mx-4" />
|
||||
@if (notification.key === selectedArea) {
|
||||
@switch (notification.value?.[0]?.category) {
|
||||
@case ('ISA-Update') {
|
||||
<modal-notifications-update-group
|
||||
[notifications]="notifications[selectedArea]"
|
||||
></modal-notifications-update-group>
|
||||
}
|
||||
@case ('Reservierungsanfragen') {
|
||||
<modal-notifications-reservation-group
|
||||
[notifications]="notifications[selectedArea]"
|
||||
(navigated)="close()"
|
||||
></modal-notifications-reservation-group>
|
||||
}
|
||||
@case ('Remission') {
|
||||
<modal-notifications-remission-group
|
||||
[notifications]="notifications[selectedArea]"
|
||||
(navigated)="close()"
|
||||
></modal-notifications-remission-group>
|
||||
}
|
||||
@case ('Tätigkeitskalender') {
|
||||
<modal-notifications-task-calendar-group
|
||||
[notifications]="notifications[selectedArea]"
|
||||
(navigated)="close()"
|
||||
></modal-notifications-task-calendar-group>
|
||||
}
|
||||
@case ('Wareneingang Lagerware') {
|
||||
<modal-notifications-package-inspection-group
|
||||
[notifications]="notifications[selectedArea]"
|
||||
(navigated)="close()"
|
||||
></modal-notifications-package-inspection-group>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
<ng-container *ngIf="notification.key === selectedArea" [ngSwitch]="notification.value?.[0]?.category">
|
||||
<modal-notifications-update-group
|
||||
*ngSwitchCase="'ISA-Update'"
|
||||
[notifications]="notifications[selectedArea]"
|
||||
></modal-notifications-update-group>
|
||||
<modal-notifications-reservation-group
|
||||
*ngSwitchCase="'Reservierungsanfragen'"
|
||||
[notifications]="notifications[selectedArea]"
|
||||
(navigated)="close()"
|
||||
></modal-notifications-reservation-group>
|
||||
<modal-notifications-remission-group
|
||||
*ngSwitchCase="'Remission'"
|
||||
[notifications]="notifications[selectedArea]"
|
||||
(navigated)="close()"
|
||||
></modal-notifications-remission-group>
|
||||
<modal-notifications-task-calendar-group
|
||||
*ngSwitchCase="'Tätigkeitskalender'"
|
||||
[notifications]="notifications[selectedArea]"
|
||||
(navigated)="close()"
|
||||
></modal-notifications-task-calendar-group>
|
||||
<modal-notifications-package-inspection-group
|
||||
*ngSwitchCase="'Wareneingang Lagerware'"
|
||||
[notifications]="notifications[selectedArea]"
|
||||
(navigated)="close()"
|
||||
></modal-notifications-package-inspection-group>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
@@ -2,17 +2,13 @@
|
||||
<div class="header">
|
||||
<h1>Wählen Sie einen Drucker aus</h1>
|
||||
|
||||
@if (error) {
|
||||
<span class="error-message">{{ errorMessage }}</span>
|
||||
}
|
||||
<span *ngIf="error" class="error-message">{{ errorMessage }}</span>
|
||||
</div>
|
||||
|
||||
<div class="body">
|
||||
<ui-spinner [show]="!loaded">
|
||||
<ui-select class="select" [(ngModel)]="selectedPrinterKey">
|
||||
@for (printer of printers$ | async; track printer) {
|
||||
<ui-select-option [label]="printer.description" [value]="printer.key"></ui-select-option>
|
||||
}
|
||||
<ui-select-option *ngFor="let printer of printers$ | async" [label]="printer.description" [value]="printer.key"></ui-select-option>
|
||||
</ui-select>
|
||||
</ui-spinner>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
<div class="flex flex-col text-right" [class.hidden]="hideHeader$ | async">
|
||||
@if (selectButton$ | async) {
|
||||
<button type="button" class="font-bold text-[#0556B4]" (click)="selectAll()">Alle auswählen</button>
|
||||
}
|
||||
@if (unselectButton$ | async) {
|
||||
<button type="button" class="font-bold text-[#0556B4]" (click)="unselectAll()">Alle abwählen</button>
|
||||
}
|
||||
<button type="button" class="font-bold text-[#0556B4]" *ngIf="selectButton$ | async" (click)="selectAll()">Alle auswählen</button>
|
||||
<button type="button" class="font-bold text-[#0556B4]" *ngIf="unselectButton$ | async" (click)="unselectAll()">Alle abwählen</button>
|
||||
<span class="mt-2">{{ selectedItemsCount$ | async }} von {{ itemsCount$ | async }} Artikel</span>
|
||||
</div>
|
||||
|
||||
@@ -15,171 +15,147 @@
|
||||
</div>
|
||||
<div class="shared-purchase-options-list-item__manufacturer-and-ean">
|
||||
{{ product?.manufacturer }}
|
||||
@if (product?.manufacturer && product?.ean) {
|
||||
<span>|</span>
|
||||
}
|
||||
<span *ngIf="product?.manufacturer && product?.ean">|</span>
|
||||
{{ product?.ean }}
|
||||
</div>
|
||||
<div class="shared-purchase-options-list-item__volume-and-publication-date">
|
||||
{{ product?.volume }}
|
||||
@if (product?.volume && product?.publicationDate) {
|
||||
<span>|</span>
|
||||
}
|
||||
<span *ngIf="product?.volume && product?.publicationDate">|</span>
|
||||
{{ product?.publicationDate | date: 'dd. MMMM yyyy' }}
|
||||
</div>
|
||||
<div class="shared-purchase-options-list-item__availabilities mt-3 grid grid-flow-row gap-2 justify-start">
|
||||
@if ((availabilities$ | async)?.length) {
|
||||
<div class="whitespace-nowrap self-center">Verfügbar als</div>
|
||||
}
|
||||
@for (availability of availabilities$ | async; track availability) {
|
||||
<div class="grid grid-flow-col gap-4 justify-start">
|
||||
<div
|
||||
class="shared-purchase-options-list-item__availability grid grid-flow-col gap-2 items-center"
|
||||
[attr.data-option]="availability.purchaseOption"
|
||||
<div class="whitespace-nowrap self-center" *ngIf="(availabilities$ | async)?.length">Verfügbar als</div>
|
||||
<div *ngFor="let availability of availabilities$ | async" class="grid grid-flow-col gap-4 justify-start">
|
||||
<div
|
||||
[ngSwitch]="availability.purchaseOption"
|
||||
class="shared-purchase-options-list-item__availability grid grid-flow-col gap-2 items-center"
|
||||
[attr.data-option]="availability.purchaseOption"
|
||||
>
|
||||
<ng-container *ngSwitchCase="'delivery'">
|
||||
<shared-icon icon="isa-truck" [size]="22"></shared-icon>
|
||||
{{ availability.data.estimatedDelivery?.start | date: 'EE dd.MM.' }}
|
||||
-
|
||||
{{ availability.data.estimatedDelivery?.stop | date: 'EE dd.MM.' }}
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'dig-delivery'">
|
||||
<shared-icon icon="isa-truck" [size]="22"></shared-icon>
|
||||
{{ availability.data.estimatedDelivery?.start | date: 'EE dd.MM.' }}
|
||||
-
|
||||
{{ availability.data.estimatedDelivery?.stop | date: 'EE dd.MM.' }}
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'b2b-delivery'">
|
||||
<shared-icon icon="isa-b2b-truck" [size]="24"></shared-icon>
|
||||
{{ availability.data.estimatedShippingDate | date: 'dd. MMMM yyyy' }}
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'pickup'">
|
||||
<shared-icon
|
||||
class="cursor-pointer"
|
||||
#uiOverlayTrigger="uiOverlayTrigger"
|
||||
[uiOverlayTrigger]="orderDeadlineTooltip"
|
||||
[class.tooltip-active]="uiOverlayTrigger.opened"
|
||||
icon="isa-box-out"
|
||||
[size]="18"
|
||||
></shared-icon>
|
||||
{{ availability.data.estimatedShippingDate | date: 'dd. MMMM yyyy' }}
|
||||
<ui-tooltip
|
||||
#orderDeadlineTooltip
|
||||
yPosition="above"
|
||||
xPosition="after"
|
||||
[yOffset]="-12"
|
||||
[xOffset]="4"
|
||||
[warning]="true"
|
||||
[closeable]="true"
|
||||
>
|
||||
@switch (availability.purchaseOption) {
|
||||
@case ('delivery') {
|
||||
<shared-icon icon="isa-truck" [size]="22"></shared-icon>
|
||||
{{ availability.data.estimatedDelivery?.start | date: 'EE dd.MM.' }}
|
||||
-
|
||||
{{ availability.data.estimatedDelivery?.stop | date: 'EE dd.MM.' }}
|
||||
}
|
||||
@case ('dig-delivery') {
|
||||
<shared-icon icon="isa-truck" [size]="22"></shared-icon>
|
||||
{{ availability.data.estimatedDelivery?.start | date: 'EE dd.MM.' }}
|
||||
-
|
||||
{{ availability.data.estimatedDelivery?.stop | date: 'EE dd.MM.' }}
|
||||
}
|
||||
@case ('b2b-delivery') {
|
||||
<shared-icon icon="isa-b2b-truck" [size]="24"></shared-icon>
|
||||
{{ availability.data.estimatedShippingDate | date: 'dd. MMMM yyyy' }}
|
||||
}
|
||||
@case ('pickup') {
|
||||
<shared-icon
|
||||
class="cursor-pointer"
|
||||
#uiOverlayTrigger="uiOverlayTrigger"
|
||||
[uiOverlayTrigger]="orderDeadlineTooltip"
|
||||
[class.tooltip-active]="uiOverlayTrigger.opened"
|
||||
icon="isa-box-out"
|
||||
[size]="18"
|
||||
></shared-icon>
|
||||
{{ availability.data.estimatedShippingDate | date: 'dd. MMMM yyyy' }}
|
||||
<ui-tooltip
|
||||
#orderDeadlineTooltip
|
||||
yPosition="above"
|
||||
xPosition="after"
|
||||
[yOffset]="-12"
|
||||
[xOffset]="4"
|
||||
[warning]="true"
|
||||
[closeable]="true"
|
||||
>
|
||||
<b>{{ availability.data?.orderDeadline | orderDeadline }}</b>
|
||||
</ui-tooltip>
|
||||
}
|
||||
@case ('in-store') {
|
||||
<shared-icon icon="isa-shopping-bag" [size]="18"></shared-icon>
|
||||
{{ availability.data.inStock }}x
|
||||
@if (isEVT) {
|
||||
ab {{ isEVT | date: 'dd. MMMM yyyy' }}
|
||||
} @else {
|
||||
ab sofort
|
||||
}
|
||||
}
|
||||
@case ('download') {
|
||||
<shared-icon icon="isa-download" [size]="22"></shared-icon>
|
||||
Download
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<b>{{ availability.data?.orderDeadline | orderDeadline }}</b>
|
||||
</ui-tooltip>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'in-store'">
|
||||
<shared-icon icon="isa-shopping-bag" [size]="18"></shared-icon>
|
||||
{{ availability.data.inStock }}x
|
||||
<ng-container *ngIf="isEVT; else noEVT">ab {{ isEVT | date: 'dd. MMMM yyyy' }}</ng-container>
|
||||
<ng-template #noEVT>ab sofort</ng-template>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'download'">
|
||||
<shared-icon icon="isa-download" [size]="22"></shared-icon>
|
||||
Download
|
||||
</ng-container>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shared-purchase-options-list-item__price text-right ml-4 flex flex-col items-end">
|
||||
<div class="shared-purchase-options-list-item__price-value font-bold text-xl flex flex-row items-center">
|
||||
<div class="relative flex flex-row justify-end items-start">
|
||||
@if (canEditVat$ | async) {
|
||||
<ui-select
|
||||
class="w-[6.5rem] min-h-[3.4375rem] p-4 rounded-card border border-solid border-[#AEB7C1] mr-4"
|
||||
tabindex="-1"
|
||||
[formControl]="manualVatFormControl"
|
||||
[defaultLabel]="'MwSt'"
|
||||
>
|
||||
@for (vat of vats$ | async; track vat) {
|
||||
<ui-select-option [label]="vat.name + '%'" [value]="vat.vatType"></ui-select-option>
|
||||
}
|
||||
</ui-select>
|
||||
}
|
||||
@if (canEditPrice$ | async) {
|
||||
<shared-input-control
|
||||
[class.ml-6]="priceFormControl?.invalid && priceFormControl?.dirty"
|
||||
>
|
||||
<shared-input-control-indicator>
|
||||
@if (priceFormControl?.invalid && priceFormControl?.dirty) {
|
||||
<shared-icon icon="mat-info"></shared-icon>
|
||||
}
|
||||
</shared-input-control-indicator>
|
||||
<input
|
||||
[uiOverlayTrigger]="giftCardTooltip"
|
||||
triggerOn="none"
|
||||
#quantityInput
|
||||
#priceOverlayTrigger="uiOverlayTrigger"
|
||||
sharedInputControlInput
|
||||
type="string"
|
||||
class="w-24"
|
||||
[formControl]="priceFormControl"
|
||||
placeholder="00,00"
|
||||
(sharedOnInit)="onPriceInputInit(quantityInput, priceOverlayTrigger)"
|
||||
sharedNumberValue
|
||||
/>
|
||||
<shared-input-control-suffix>EUR</shared-input-control-suffix>
|
||||
<shared-input-control-error error="required">Preis ist ungültig</shared-input-control-error>
|
||||
<shared-input-control-error error="pattern">Preis ist ungültig</shared-input-control-error>
|
||||
<shared-input-control-error error="min">Preis ist ungültig</shared-input-control-error>
|
||||
<shared-input-control-error error="max">Preis ist ungültig</shared-input-control-error>
|
||||
</shared-input-control>
|
||||
} @else {
|
||||
{{ priceValue$ | async | currency: 'EUR' : 'code' }}
|
||||
}
|
||||
|
||||
<ui-tooltip [warning]="true" xPosition="after" yPosition="below" [xOffset]="-55" [yOffset]="18" [closeable]="true" #giftCardTooltip>
|
||||
Tragen Sie hier den
|
||||
<br />
|
||||
Gutscheinbetrag ein.
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<ui-quantity-dropdown class="mt-2" [formControl]="quantityFormControl" [range]="maxSelectableQuantity$ | async"></ui-quantity-dropdown>
|
||||
<div class="pt-7">
|
||||
@if ((canAddResult$ | async)?.canAdd) {
|
||||
<ui-select
|
||||
*ngIf="canEditVat$ | async"
|
||||
class="w-[6.5rem] min-h-[3.4375rem] p-4 rounded-card border border-solid border-[#AEB7C1] mr-4"
|
||||
tabindex="-1"
|
||||
[formControl]="manualVatFormControl"
|
||||
[defaultLabel]="'MwSt'"
|
||||
>
|
||||
<ui-select-option *ngFor="let vat of vats$ | async" [label]="vat.name + '%'" [value]="vat.vatType"></ui-select-option>
|
||||
</ui-select>
|
||||
<shared-input-control
|
||||
[class.ml-6]="priceFormControl?.invalid && priceFormControl?.dirty"
|
||||
*ngIf="canEditPrice$ | async; else priceTmpl"
|
||||
>
|
||||
<shared-input-control-indicator>
|
||||
<shared-icon *ngIf="priceFormControl?.invalid && priceFormControl?.dirty" icon="mat-info"></shared-icon>
|
||||
</shared-input-control-indicator>
|
||||
<input
|
||||
class="fancy-checkbox"
|
||||
[class.checked]="selectedFormControl?.value"
|
||||
[formControl]="selectedFormControl"
|
||||
type="checkbox"
|
||||
/>
|
||||
}
|
||||
[uiOverlayTrigger]="giftCardTooltip"
|
||||
triggerOn="none"
|
||||
#quantityInput
|
||||
#priceOverlayTrigger="uiOverlayTrigger"
|
||||
sharedInputControlInput
|
||||
type="string"
|
||||
class="w-24"
|
||||
[formControl]="priceFormControl"
|
||||
placeholder="00,00"
|
||||
(sharedOnInit)="onPriceInputInit(quantityInput, priceOverlayTrigger)"
|
||||
sharedNumberValue
|
||||
/>
|
||||
<shared-input-control-suffix>EUR</shared-input-control-suffix>
|
||||
<shared-input-control-error error="required">Preis ist ungültig</shared-input-control-error>
|
||||
<shared-input-control-error error="pattern">Preis ist ungültig</shared-input-control-error>
|
||||
<shared-input-control-error error="min">Preis ist ungültig</shared-input-control-error>
|
||||
<shared-input-control-error error="max">Preis ist ungültig</shared-input-control-error>
|
||||
</shared-input-control>
|
||||
|
||||
<ui-tooltip [warning]="true" xPosition="after" yPosition="below" [xOffset]="-55" [yOffset]="18" [closeable]="true" #giftCardTooltip>
|
||||
Tragen Sie hier den
|
||||
<br />
|
||||
Gutscheinbetrag ein.
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
|
||||
@if (canAddResult$ | async; as canAddResult) {
|
||||
@if (!canAddResult.canAdd) {
|
||||
<span class="inline-block font-bold text-[#BE8100] mt-[14px] max-w-[19rem]">
|
||||
{{ canAddResult.message }}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
|
||||
@if (showMaxAvailableQuantity$ | async) {
|
||||
<span class="font-bold text-[#BE8100] mt-[14px]">
|
||||
{{ (availability$ | async)?.inStock }} Exemplare sofort lieferbar
|
||||
</span>
|
||||
}
|
||||
@if (showNotAvailable$ | async) {
|
||||
<span class="font-bold text-[#BE8100] mt-[14px]">Derzeit nicht bestellbar</span>
|
||||
}
|
||||
<ng-template #priceTmpl>
|
||||
{{ priceValue$ | async | currency: 'EUR' : 'code' }}
|
||||
</ng-template>
|
||||
</div>
|
||||
<ui-quantity-dropdown class="mt-2" [formControl]="quantityFormControl" [range]="maxSelectableQuantity$ | async"></ui-quantity-dropdown>
|
||||
<div class="pt-7">
|
||||
<input
|
||||
*ngIf="(canAddResult$ | async)?.canAdd"
|
||||
class="fancy-checkbox"
|
||||
[class.checked]="selectedFormControl?.value"
|
||||
[formControl]="selectedFormControl"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="canAddResult$ | async; let canAddResult">
|
||||
<span *ngIf="!canAddResult.canAdd" class="inline-block font-bold text-[#BE8100] mt-[14px] max-w-[19rem]">
|
||||
{{ canAddResult.message }}
|
||||
</span>
|
||||
</ng-container>
|
||||
|
||||
<span *ngIf="showMaxAvailableQuantity$ | async" class="font-bold text-[#BE8100] mt-[14px]">
|
||||
{{ (availability$ | async)?.inStock }} Exemplare sofort lieferbar
|
||||
</span>
|
||||
<span *ngIf="showNotAvailable$ | async" class="font-bold text-[#BE8100] mt-[14px]">Derzeit nicht bestellbar</span>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<div class="w-16"></div>
|
||||
<div class="grow shared-purchase-options-list-item__availabilities"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<div class="w-16"></div>
|
||||
<div class="grow shared-purchase-options-list-item__availabilities"></div>
|
||||
</div>
|
||||
|
||||
@@ -1,37 +1,28 @@
|
||||
<h3 class="text-center font-bold text-h3">Lieferung auswählen</h3>
|
||||
<p class="text-center font-2xl mt-4">Wie möchten Sie die Artikel erhalten?</p>
|
||||
<div class="rounded p-4 shadow-card mt-4 grid grid-flow-col gap-4 justify-center items-center relative">
|
||||
@if (!(isDownloadOnly$ | async)) {
|
||||
@if (!(isGiftCardOnly$ | async)) {
|
||||
@if (showOption('in-store')) {
|
||||
<app-in-store-purchase-options-tile></app-in-store-purchase-options-tile>
|
||||
}
|
||||
@if (showOption('pickup')) {
|
||||
<app-pickup-purchase-options-tile></app-pickup-purchase-options-tile>
|
||||
}
|
||||
}
|
||||
@if (showOption('delivery')) {
|
||||
<app-delivery-purchase-options-tile></app-delivery-purchase-options-tile>
|
||||
}
|
||||
}
|
||||
<ng-container *ngIf="!(isDownloadOnly$ | async)">
|
||||
<ng-container *ngIf="!(isGiftCardOnly$ | async)">
|
||||
<app-in-store-purchase-options-tile *ngIf="showOption('in-store')"></app-in-store-purchase-options-tile>
|
||||
<app-pickup-purchase-options-tile *ngIf="showOption('pickup')"></app-pickup-purchase-options-tile>
|
||||
</ng-container>
|
||||
<app-delivery-purchase-options-tile *ngIf="showOption('delivery')"></app-delivery-purchase-options-tile>
|
||||
</ng-container>
|
||||
|
||||
@if (hasDownload$ | async) {
|
||||
@if (showOption('download')) {
|
||||
<app-download-purchase-options-tile></app-download-purchase-options-tile>
|
||||
}
|
||||
}
|
||||
<ng-container *ngIf="hasDownload$ | async">
|
||||
<app-download-purchase-options-tile *ngIf="showOption('download')"></app-download-purchase-options-tile>
|
||||
</ng-container>
|
||||
</div>
|
||||
<shared-purchase-options-list-header></shared-purchase-options-list-header>
|
||||
<div class="shared-purchase-options-modal__items -mx-4">
|
||||
@for (item of items$ | async; track itemTrackBy($index, item)) {
|
||||
<shared-purchase-options-list-item
|
||||
class="border-t border-gray-200 p-4 border-solid"
|
||||
[item]="item"
|
||||
></shared-purchase-options-list-item>
|
||||
}
|
||||
<shared-purchase-options-list-item
|
||||
class="border-t border-gray-200 p-4 border-solid"
|
||||
*ngFor="let item of items$ | async; trackBy: itemTrackBy"
|
||||
[item]="item"
|
||||
></shared-purchase-options-list-item>
|
||||
</div>
|
||||
<div class="text-center -mx-4 border-t border-gray-200 p-4 border-solid">
|
||||
@if (type === 'add') {
|
||||
<ng-container *ngIf="type === 'add'">
|
||||
<button type="button" class="isa-cta-button" [disabled]="!(canContinue$ | async) || saving" (click)="save('continue-shopping')">
|
||||
Weiter einkaufen
|
||||
</button>
|
||||
@@ -40,18 +31,18 @@
|
||||
class="ml-4 isa-cta-button isa-button-primary"
|
||||
[disabled]="!(canContinue$ | async) || saving"
|
||||
(click)="save('continue')"
|
||||
>
|
||||
>
|
||||
Fortfahren
|
||||
</button>
|
||||
}
|
||||
@if (type === 'update') {
|
||||
</ng-container>
|
||||
<ng-container *ngIf="type === 'update'">
|
||||
<button
|
||||
type="button"
|
||||
class="ml-4 isa-cta-button isa-button-primary"
|
||||
[disabled]="!(canContinue$ | async) || saving"
|
||||
(click)="save('continue')"
|
||||
>
|
||||
>
|
||||
Fortfahren
|
||||
</button>
|
||||
}
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
||||
import { PurchaseOptionsStore } from '../store';
|
||||
import { BasePurchaseOptionDirective } from './base-purchase-option.directive';
|
||||
@@ -9,7 +9,7 @@ import { IconComponent } from '@shared/components/icon';
|
||||
templateUrl: 'download-purchase-options-tile.component.html',
|
||||
styleUrls: ['purchase-options-tile.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [IconComponent],
|
||||
imports: [CommonModule, IconComponent],
|
||||
})
|
||||
export class DownloadPurchaseOptionTileComponent extends BasePurchaseOptionDirective {
|
||||
constructor(
|
||||
|
||||
@@ -2,31 +2,30 @@
|
||||
|
||||
<hr />
|
||||
|
||||
@if (orderItem$ | async; as orderItem) {
|
||||
<ng-container *ngIf="orderItem$ | async; let orderItem">
|
||||
<div class="header">
|
||||
@if (orderItem?.product?.ean | productImage; as productImage) {
|
||||
<img
|
||||
class="thumbnail"
|
||||
loading="lazy"
|
||||
[src]="productImage"
|
||||
[alt]="orderItem?.product?.name"
|
||||
/>
|
||||
}
|
||||
<img
|
||||
class="thumbnail"
|
||||
loading="lazy"
|
||||
*ngIf="orderItem?.product?.ean | productImage; let productImage"
|
||||
[src]="productImage"
|
||||
[alt]="orderItem?.product?.name"
|
||||
/>
|
||||
|
||||
<div class="details">
|
||||
<div class="product-name">{{ orderItem.product?.name }}</div>
|
||||
@if (orderItem.product?.format && orderItem.product.formatDetail) {
|
||||
<div class="product-format">
|
||||
<img class="format-icon" [src]="'/assets/images/Icon_' + orderItem.product?.format + '.svg'" alt="format icon" />
|
||||
{{ orderItem.product.formatDetail }}
|
||||
</div>
|
||||
}
|
||||
<div *ngIf="orderItem.product?.format && orderItem.product.formatDetail" class="product-format">
|
||||
<img class="format-icon" [src]="'/assets/images/Icon_' + orderItem.product?.format + '.svg'" alt="format icon" />
|
||||
{{ orderItem.product.formatDetail }}
|
||||
</div>
|
||||
<div class="product-ean">
|
||||
{{ orderItem.product?.ean }}
|
||||
</div>
|
||||
<div class="quantity">{{ orderItem.quantity }}x</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (availabilities$ | async; as availabilities) {
|
||||
|
||||
<ng-container *ngIf="availabilities$ | async; let availabilities; else: showLoadingSpinner">
|
||||
<div class="supplier-grid">
|
||||
<span></span>
|
||||
<span class="number">Bestand</span>
|
||||
@@ -35,56 +34,53 @@
|
||||
<span>vsl. Lieferdatum</span>
|
||||
<span class="number">Preis</span>
|
||||
<span></span>
|
||||
@for (availability of availabilities; track availability; let i = $index) {
|
||||
@if (availability) {
|
||||
|
||||
<ng-container *ngFor="let availability of availabilities; let i = index">
|
||||
<ng-container *ngIf="availability">
|
||||
<span class="first-cell">{{ availability.supplier | supplierName }}</span>
|
||||
<span class="number">{{ availability.qty || 0 }}</span>
|
||||
<span>{{ availability.ssc }}</span>
|
||||
<span>
|
||||
@if (availability.supplier !== 'F') {
|
||||
<ui-checkbox [(ngModel)]="availability.isPrebooked"></ui-checkbox>
|
||||
}
|
||||
<ui-checkbox *ngIf="availability.supplier !== 'F'" [(ngModel)]="availability.isPrebooked"></ui-checkbox>
|
||||
</span>
|
||||
<span>{{ availability.at | date: 'dd.MM.yy' }}</span>
|
||||
<span class="number">{{ availability.price?.value?.value | currency: 'EUR' : 'code' }}</span>
|
||||
<span>
|
||||
@if (availability.supplier !== 'F' || availability.qty > 0) {
|
||||
<ui-select-bullet
|
||||
[(ngModel)]="checkedSupplier"
|
||||
[value]="availability.supplier"
|
||||
(ngModelChange)="checked($event, availability)"
|
||||
></ui-select-bullet>
|
||||
}
|
||||
<ui-select-bullet
|
||||
*ngIf="availability.supplier !== 'F' || availability.qty > 0"
|
||||
[(ngModel)]="checkedSupplier"
|
||||
[value]="availability.supplier"
|
||||
(ngModelChange)="checked($event, availability)"
|
||||
></ui-select-bullet>
|
||||
</span>
|
||||
}
|
||||
}
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
@if (storeAvailabilityError$ | async) {
|
||||
|
||||
<ng-container *ngIf="storeAvailabilityError$ | async">
|
||||
<div class="availability-error">Lieferantenbestand nicht verfügbar</div>
|
||||
<hr />
|
||||
}
|
||||
@if (takeAwayAvailabilityError$ | async) {
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="takeAwayAvailabilityError$ | async">
|
||||
<div class="availability-error">Filialbestand nicht verfügbar</div>
|
||||
<hr />
|
||||
}
|
||||
@if (showReasons$ | async) {
|
||||
<div class="reason">
|
||||
<button class="reason-dropdown" [uiOverlayTrigger]="statusDropdown" #dropdown="uiOverlayTrigger">
|
||||
{{ selectedReason || 'Warum wird nachbestellt?' }}
|
||||
<ui-icon [rotate]="dropdown.opened ? '270deg' : '90deg'" icon="arrow_head"></ui-icon>
|
||||
</ng-container>
|
||||
|
||||
<div class="reason" *ngIf="showReasons$ | async">
|
||||
<button class="reason-dropdown" [uiOverlayTrigger]="statusDropdown" #dropdown="uiOverlayTrigger">
|
||||
{{ selectedReason || 'Warum wird nachbestellt?' }}
|
||||
<ui-icon [rotate]="dropdown.opened ? '270deg' : '90deg'" icon="arrow_head"></ui-icon>
|
||||
</button>
|
||||
<ui-dropdown #statusDropdown yPosition="above" xPosition="after" [xOffset]="8">
|
||||
<button uiDropdownItem *ngFor="let reason of reorderReasons$ | async" (click)="selectedReason = reason.value; dropdown.close()">
|
||||
{{ reason.value }}
|
||||
</button>
|
||||
<ui-dropdown #statusDropdown yPosition="above" xPosition="after" [xOffset]="8">
|
||||
@for (reason of reorderReasons$ | async; track reason) {
|
||||
<button uiDropdownItem (click)="selectedReason = reason.value; dropdown.close()">
|
||||
{{ reason.value }}
|
||||
</button>
|
||||
}
|
||||
</ui-dropdown>
|
||||
@if (showReasonError$ | async) {
|
||||
<span class="error">Bitte wählen Sie einen Grund für das nachbestellen</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ui-dropdown>
|
||||
|
||||
<span *ngIf="showReasonError$ | async" class="error">Bitte wählen Sie einen Grund für das nachbestellen</span>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="cta-not-available cta-action-secondary" [disabled]="ctaDisabled$ | async" (click)="notAvailable()">
|
||||
<ui-spinner [show]="ctaDisabled$ | async">Nicht lieferbar</ui-spinner>
|
||||
@@ -93,7 +89,9 @@
|
||||
<ui-spinner [show]="ctaDisabled$ | async">Bestellen</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
} @else {
|
||||
</ng-container>
|
||||
|
||||
<ng-template #showLoadingSpinner>
|
||||
<ui-spinner class="load-spinner" [show]="true"></ui-spinner>
|
||||
}
|
||||
}
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<div class="reviews">
|
||||
<hr />
|
||||
@for (review of reviews; track review) {
|
||||
<ng-container *ngFor="let review of reviews">
|
||||
<div class="review">
|
||||
<div class="row">
|
||||
<ui-stars [rating]="review.rating"></ui-stars>
|
||||
@@ -16,29 +16,21 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<span>
|
||||
@if (expandIds.indexOf(review.id) === -1) {
|
||||
<span class="text">{{ review.text | substr: 150 }}</span>
|
||||
}
|
||||
@if (expandIds.indexOf(review.id) > -1) {
|
||||
<span class="text">{{ review.text }}</span>
|
||||
}
|
||||
<span class="text" *ngIf="expandIds.indexOf(review.id) === -1">{{ review.text | substr: 150 }}</span>
|
||||
<span class="text" *ngIf="expandIds.indexOf(review.id) > -1">{{ review.text }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="row right">
|
||||
@if (expandIds.indexOf(review.id) === -1) {
|
||||
<button class="btn-expand" (click)="expand(review.id)">
|
||||
Mehr
|
||||
<ui-icon icon="arrow"></ui-icon>
|
||||
</button>
|
||||
}
|
||||
@if (expandIds.indexOf(review.id) > -1) {
|
||||
<button class="btn-collapse" (click)="expand(review.id)">
|
||||
<ui-icon icon="arrow" rotate="180deg"></ui-icon>
|
||||
Weniger
|
||||
</button>
|
||||
}
|
||||
<button *ngIf="expandIds.indexOf(review.id) === -1" class="btn-expand" (click)="expand(review.id)">
|
||||
Mehr
|
||||
<ui-icon icon="arrow"></ui-icon>
|
||||
</button>
|
||||
<button *ngIf="expandIds.indexOf(review.id) > -1" class="btn-collapse" (click)="expand(review.id)">
|
||||
<ui-icon icon="arrow" rotate="180deg"></ui-icon>
|
||||
Weniger
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
}
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div
|
||||
class="page-price-update-item__item-header flex flex-row w-full items-center justify-between bg-[rgba(0,128,121,0.15)] mb-px-2 px-5 h-[53px] rounded-t"
|
||||
>
|
||||
>
|
||||
<p class="page-price-update-item__item-instruction font-bold text-lg">{{ item?.task?.instruction }}</p>
|
||||
<p class="page-price-update-item__item-due-date text-p2">
|
||||
gültig ab
|
||||
@@ -10,14 +10,13 @@
|
||||
|
||||
<div class="page-price-update-item__item-card p-5 bg-white">
|
||||
<div class="page-price-update-item__item-thumbnail text-center mr-4 w-[47px] h-[73px]">
|
||||
@if (item?.product?.ean | productImage; as productImage) {
|
||||
<img
|
||||
class="page-price-update-item__item-image w-[47px] max-h-[73px]"
|
||||
loading="lazy"
|
||||
[src]="productImage"
|
||||
[alt]="item?.product?.name"
|
||||
/>
|
||||
}
|
||||
<img
|
||||
class="page-price-update-item__item-image w-[47px] max-h-[73px]"
|
||||
loading="lazy"
|
||||
*ngIf="item?.product?.ean | productImage; let productImage"
|
||||
[src]="productImage"
|
||||
[alt]="item?.product?.name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="page-price-update-item__item-details">
|
||||
@@ -32,33 +31,28 @@
|
||||
[class.text-md]="item?.product?.name?.length >= 50"
|
||||
[class.text-p3]="item?.product?.name?.length >= 60"
|
||||
[class.text-xs]="item?.product?.name?.length >= 100"
|
||||
>
|
||||
>
|
||||
{{ item?.product?.name }}
|
||||
</div>
|
||||
|
||||
<div class="page-price-update-item__item-format">
|
||||
@if (item?.product?.format && item?.product?.formatDetail) {
|
||||
<div class="font-bold flex flex-row">
|
||||
@if (item?.product?.format !== '--') {
|
||||
<img
|
||||
class="mr-3"
|
||||
loading="lazy"
|
||||
src="assets/images/Icon_{{ item?.product?.format }}.svg"
|
||||
[alt]="item?.product?.formatDetail"
|
||||
/>
|
||||
}
|
||||
{{ environment.isTablet() ? (item?.product?.formatDetail | substr: 25) : item?.product?.formatDetail }}
|
||||
</div>
|
||||
}
|
||||
<div *ngIf="item?.product?.format && item?.product?.formatDetail" class="font-bold flex flex-row">
|
||||
<img
|
||||
class="mr-3"
|
||||
*ngIf="item?.product?.format !== '--'"
|
||||
loading="lazy"
|
||||
src="assets/images/Icon_{{ item?.product?.format }}.svg"
|
||||
[alt]="item?.product?.formatDetail"
|
||||
/>
|
||||
{{ environment.isTablet() ? (item?.product?.formatDetail | substr: 25) : item?.product?.formatDetail }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-price-update-item__item-misc">
|
||||
{{ environment.isTablet() ? (item?.product?.manufacturer | substr: 18) : item?.product?.manufacturer }} | {{ item?.product?.ean }}
|
||||
<br />
|
||||
{{ item?.product?.volume }}
|
||||
@if (item?.product?.volume && item?.product?.publicationDate) {
|
||||
<span>|</span>
|
||||
}
|
||||
<span *ngIf="item?.product?.volume && item?.product?.publicationDate">|</span>
|
||||
{{ publicationDate }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -75,21 +69,18 @@
|
||||
</div>
|
||||
|
||||
<div class="page-price-update-item__item-select-bullet">
|
||||
@if (isSelectable) {
|
||||
<input [ngModel]="selected" (ngModelChange)="setSelected()" class="isa-select-bullet" type="checkbox" />
|
||||
}
|
||||
<input *ngIf="isSelectable" [ngModel]="selected" (ngModelChange)="setSelected()" class="isa-select-bullet" type="checkbox" />
|
||||
</div>
|
||||
|
||||
<div class="page-price-update-item__item-stock flex flex-row font-bold">
|
||||
<ui-icon class="mt-px-2 mr-1" icon="home" size="1em"></ui-icon>
|
||||
@if (inStock$ | async; as stock) {
|
||||
<span
|
||||
[class.skeleton]="stock?.inStock === undefined"
|
||||
class="min-w-[1rem] text-right inline-block"
|
||||
>
|
||||
{{ stock?.inStock }}
|
||||
</span>
|
||||
}
|
||||
<span
|
||||
*ngIf="inStock$ | async; let stock"
|
||||
[class.skeleton]="stock?.inStock === undefined"
|
||||
class="min-w-[1rem] text-right inline-block"
|
||||
>
|
||||
{{ stock?.inStock }}
|
||||
</span>
|
||||
x
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,26 +4,20 @@
|
||||
(click)="print()"
|
||||
type="button"
|
||||
class="page-price-update-list__print-cta text-lg font-bold text-[#F70400] pr-5 mb-3"
|
||||
>
|
||||
>
|
||||
Drucken
|
||||
</button>
|
||||
<div class="flex flex-row items-center justify-end">
|
||||
@if (getSelectableItems().length > 0) {
|
||||
<div class="text-[#0556B4] font-bold text-p3 mr-5">
|
||||
@if (selectedItemUids$ | async; as selectedItems) {
|
||||
@if (selectedItems?.length > 0) {
|
||||
<button class="page-price-update-list__cta-unselect-all" type="button" (click)="unselectAll()">
|
||||
Alle entfernen ({{ selectedItems?.length }})
|
||||
</button>
|
||||
}
|
||||
@if (selectedItems?.length === 0) {
|
||||
<button class="page-price-update-list__cta-select-all" type="button" (click)="selectAll()">
|
||||
Alle auswählen ({{ getSelectableItems().length }})
|
||||
</button>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div *ngIf="getSelectableItems().length > 0" class="text-[#0556B4] font-bold text-p3 mr-5">
|
||||
<ng-container *ngIf="selectedItemUids$ | async; let selectedItems">
|
||||
<button class="page-price-update-list__cta-unselect-all" *ngIf="selectedItems?.length > 0" type="button" (click)="unselectAll()">
|
||||
Alle entfernen ({{ selectedItems?.length }})
|
||||
</button>
|
||||
<button class="page-price-update-list__cta-select-all" type="button" (click)="selectAll()" *ngIf="selectedItems?.length === 0">
|
||||
Alle auswählen ({{ getSelectableItems().length }})
|
||||
</button>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="page-price-update-list__items-count inline-flex flex-row items-center pr-5 text-p3">
|
||||
{{ items?.length ?? 0 }}
|
||||
Titel
|
||||
@@ -38,41 +32,38 @@
|
||||
<div class="items scroll-bar">
|
||||
@for (item of items; track item.uId; let first = $first) {
|
||||
@defer (on viewport) {
|
||||
<page-price-update-item [item]="item" [selected]="isSelected(item)" [class.mt-px-10]="!first"></page-price-update-item>
|
||||
<page-price-update-item [item]="item" [selected]="isSelected(item)" [class.mt-px-10]="!first"></page-price-update-item>
|
||||
} @placeholder {
|
||||
<page-price-update-item-loader></page-price-update-item-loader>
|
||||
<page-price-update-item-loader></page-price-update-item-loader>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@if (fetching) {
|
||||
<page-price-update-item-loader></page-price-update-item-loader>
|
||||
}
|
||||
<div class="h-28"></div>
|
||||
<page-price-update-item-loader *ngIf="fetching"></page-price-update-item-loader>
|
||||
<div class="h-28"></div>
|
||||
</div>
|
||||
|
||||
<!-- <cdk-virtual-scroll-viewport #scrollContainer [itemSize]="267" minBufferPx="1200" maxBufferPx="1200" class="scroll-bar">
|
||||
<page-price-update-item
|
||||
*cdkVirtualFor="let item of items; let first; trackBy: trackByFn"
|
||||
[item]="item"
|
||||
[selected]="isSelected(item)"
|
||||
[class.mt-px-10]="!first"
|
||||
></page-price-update-item>
|
||||
<page-price-update-item
|
||||
*cdkVirtualFor="let item of items; let first; trackBy: trackByFn"
|
||||
[item]="item"
|
||||
[selected]="isSelected(item)"
|
||||
[class.mt-px-10]="!first"
|
||||
></page-price-update-item>
|
||||
|
||||
<page-price-update-item-loader *ngIf="fetching"> </page-price-update-item-loader>
|
||||
<page-price-update-item-loader *ngIf="fetching"> </page-price-update-item-loader>
|
||||
|
||||
<div class="h-28"></div>
|
||||
<div class="h-28"></div>
|
||||
</cdk-virtual-scroll-viewport> -->
|
||||
|
||||
<div class="page-price-update-list__action-wrapper">
|
||||
@if (!fetching) {
|
||||
<button
|
||||
[@cta]
|
||||
[disabled]="(selectedItemUids$ | async).length === 0 || (loading$ | async)"
|
||||
class="page-price-update-list__complete-items isa-button isa-cta-button isa-button-primary px-11"
|
||||
type="button"
|
||||
(click)="onComplete()"
|
||||
>
|
||||
<ui-spinner [show]="loading$ | async">Erledigt</ui-spinner>
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
[@cta]
|
||||
*ngIf="!fetching"
|
||||
[disabled]="(selectedItemUids$ | async).length === 0 || (loading$ | async)"
|
||||
class="page-price-update-list__complete-items isa-button isa-cta-button isa-button-primary px-11"
|
||||
type="button"
|
||||
(click)="onComplete()"
|
||||
>
|
||||
<ui-spinner [show]="loading$ | async">Erledigt</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -5,20 +5,17 @@
|
||||
id="asortment-filter-button"
|
||||
class="absolute right-0 top-0 h-14 rounded px-5 text-lg bg-cadet-blue flex flex-row flex-nowrap items-center justify-center"
|
||||
type="button"
|
||||
>
|
||||
>
|
||||
<shared-icon class="mr-2" icon="filter-variant"></shared-icon>
|
||||
Filter
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (showList$ | async) {
|
||||
<page-price-update-list
|
||||
[items]="store.items$ | async"
|
||||
[fetching]="store.fetching$ | async"
|
||||
></page-price-update-list>
|
||||
} @else {
|
||||
<div class="bg-white text-h3 text-center pt-10 font-bold rounded-b h-[calc(100vh_-_370px)]">Keine Preisänderungen vorhanden.</div>
|
||||
}
|
||||
<page-price-update-list
|
||||
*ngIf="showList$ | async; else noResults"
|
||||
[items]="store.items$ | async"
|
||||
[fetching]="store.fetching$ | async"
|
||||
></page-price-update-list>
|
||||
|
||||
<shell-filter-overlay #filterOverlay class="relative">
|
||||
<div class="relative">
|
||||
@@ -29,23 +26,22 @@
|
||||
|
||||
<h3 class="text-3xl text-center font-bold mt-8">Filter</h3>
|
||||
|
||||
@if (filterOverlay.isOpen) {
|
||||
<ui-filter
|
||||
#filter
|
||||
class="mx-4"
|
||||
[filter]="store.pendingFilter$ | async"
|
||||
(search)="applyFilter()"
|
||||
[loading]="store.fetching$ | async"
|
||||
[hint]="hint$ | async"
|
||||
></ui-filter>
|
||||
}
|
||||
<ui-filter
|
||||
*ngIf="filterOverlay.isOpen"
|
||||
#filter
|
||||
class="mx-4"
|
||||
[filter]="store.pendingFilter$ | async"
|
||||
(search)="applyFilter()"
|
||||
[loading]="store.fetching$ | async"
|
||||
[hint]="hint$ | async"
|
||||
></ui-filter>
|
||||
|
||||
<div class="absolute bottom-8 left-0 right-0 grid grid-flow-col gap-4 justify-center">
|
||||
<button
|
||||
type="button"
|
||||
class="px-6 py-4 font-bold bg-white text-brand border-2 border-solid border-brand rounded-full"
|
||||
(click)="store.resetPendingFilter()"
|
||||
>
|
||||
>
|
||||
Filter zurücksetzen
|
||||
</button>
|
||||
<button
|
||||
@@ -53,9 +49,12 @@
|
||||
class="px-6 py-4 font-bold bg-brand text-white border-2 border-solid border-brand rounded-full disabled:bg-cadet-blue disabled:cursor-progress disabled:border-cadet-blue"
|
||||
(click)="applyFilter()"
|
||||
[disabled]="store.fetching$ | async"
|
||||
>
|
||||
>
|
||||
<ui-spinner [show]="store.fetching$ | async">Filter anwenden</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</shell-filter-overlay>
|
||||
|
||||
<ng-template #noResults>
|
||||
<div class="bg-white text-h3 text-center pt-10 font-bold rounded-b h-[calc(100vh_-_370px)]">Keine Preisänderungen vorhanden.</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
@for (line of lines; track line) {
|
||||
@switch (line | lineType) {
|
||||
@case ('reihe') {
|
||||
@for (reihe of getReihen(line); track reihe) {
|
||||
<page-article-details-text-link [route]="reihe | reiheRoute">
|
||||
{{ reihe }}
|
||||
</page-article-details-text-link>
|
||||
}
|
||||
<ng-container *ngFor="let line of lines">
|
||||
<ng-container [ngSwitch]="line | lineType">
|
||||
<ng-container *ngSwitchCase="'reihe'">
|
||||
<page-article-details-text-link *ngFor="let reihe of getReihen(line)" [route]="reihe | reiheRoute">
|
||||
{{ reihe }}
|
||||
</page-article-details-text-link>
|
||||
<br />
|
||||
}
|
||||
@default {
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchDefault>
|
||||
{{ line }}
|
||||
<br />
|
||||
}
|
||||
}
|
||||
}
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
|
||||
import { TextDTO } from '@generated/swagger/cat-search-api';
|
||||
import { ArticleDetailsTextLinkComponent } from './article-details-text-link.component';
|
||||
|
||||
import { NgFor, NgSwitch, NgSwitchCase, NgSwitchDefault } from '@angular/common';
|
||||
import { LineTypePipe } from './line-type.pipe';
|
||||
import { ReiheRoutePipe } from './reihe-route.pipe';
|
||||
|
||||
@@ -13,9 +13,13 @@ import { ReiheRoutePipe } from './reihe-route.pipe';
|
||||
host: { class: 'page-article-details-text' },
|
||||
imports: [
|
||||
ArticleDetailsTextLinkComponent,
|
||||
NgFor,
|
||||
NgSwitch,
|
||||
NgSwitchCase,
|
||||
NgSwitchDefault,
|
||||
LineTypePipe,
|
||||
ReiheRoutePipe
|
||||
],
|
||||
ReiheRoutePipe,
|
||||
],
|
||||
})
|
||||
export class ArticleDetailsTextComponent {
|
||||
@Input()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,39 +1,37 @@
|
||||
@if (store.item$ | async; as item) {
|
||||
<ng-container *ngIf="store.item$ | async; let item">
|
||||
<button
|
||||
class="h-[3.75rem] shadow-[0_-2px_24px_0_#dce2e9] flex flex-row justify-center items-center w-full text-xl bg-white text-[#0556B4] font-bold border-none outline-none rounded-t"
|
||||
(click)="close.emit()"
|
||||
>
|
||||
>
|
||||
{{ item?.product?.name }}
|
||||
</button>
|
||||
<h1>Empfehlungen für Sie</h1>
|
||||
<p>Neben dem Titel "{{ item.product?.name }}" gibt es noch andere Artikel, die Sie interessieren könnten.</p>
|
||||
|
||||
<div class="articles">
|
||||
<span class="label mb-2">
|
||||
<ui-icon icon="recommendation" size="20px"></ui-icon>
|
||||
Artikel
|
||||
</span>
|
||||
@if (store.recommendations$ | async; as recommendations) {
|
||||
@if (recommendations.length === 0) {
|
||||
<span class="empty-message">Keine Empfehlungen verfügbar</span>
|
||||
}
|
||||
@if (recommendations.length > 0) {
|
||||
<ui-slider [scrollDistance]="210">
|
||||
@for (recommendation of store.recommendations$ | async; track recommendation) {
|
||||
<a
|
||||
class="article"
|
||||
[routerLink]="getDetailsPath(recommendation.product.ean)"
|
||||
[queryParams]="{ main_qs: recommendation.product.ean, filter_format: '' }"
|
||||
(click)="close.emit()"
|
||||
>
|
||||
<img [src]="recommendation.product?.ean | productImage: 195 : 315 : true" alt="product-image" />
|
||||
<div class="flex flex-col">
|
||||
<span class="format">{{ recommendation.product?.formatDetail }}</span>
|
||||
<span class="price">{{ recommendation.catalogAvailability?.price?.value?.value | currency: ' ' }} EUR</span>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
</ui-slider>
|
||||
}
|
||||
}
|
||||
|
||||
<ng-container *ngIf="store.recommendations$ | async; let recommendations">
|
||||
<span *ngIf="recommendations.length === 0" class="empty-message">Keine Empfehlungen verfügbar</span>
|
||||
|
||||
<ui-slider *ngIf="recommendations.length > 0" [scrollDistance]="210">
|
||||
<a
|
||||
class="article"
|
||||
*ngFor="let recommendation of store.recommendations$ | async"
|
||||
[routerLink]="getDetailsPath(recommendation.product.ean)"
|
||||
[queryParams]="{ main_qs: recommendation.product.ean, filter_format: '' }"
|
||||
(click)="close.emit()"
|
||||
>
|
||||
<img [src]="recommendation.product?.ean | productImage: 195 : 315 : true" alt="product-image" />
|
||||
<div class="flex flex-col">
|
||||
<span class="format">{{ recommendation.product?.formatDetail }}</span>
|
||||
<span class="price">{{ recommendation.catalogAvailability?.price?.value?.value | currency: ' ' }} EUR</span>
|
||||
</div>
|
||||
</a>
|
||||
</ui-slider>
|
||||
</ng-container>
|
||||
</div>
|
||||
}
|
||||
</ng-container>
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<div class="hidden desktop-large:block" [class.show-filter]="showFilter">
|
||||
@if (filter$ | async; as filter) {
|
||||
<ng-container *ngIf="filter$ | async; let filter">
|
||||
<div class="catalog-search-filter-content">
|
||||
<div class="w-full flex flex-row justify-end items-center">
|
||||
<button (click)="clearFilter(filter)" class="text-[#0556B4] p-4">Alle Filter entfernen</button>
|
||||
@if (showFilterClose$ | async) {
|
||||
<a
|
||||
class="text-black p-4 outline-none border-none bg-transparent"
|
||||
[routerLink]="closeFilterRoute"
|
||||
(click)="showFilter = false"
|
||||
queryParamsHandling="preserve"
|
||||
>
|
||||
<shared-icon icon="close" [size]="25"></shared-icon>
|
||||
</a>
|
||||
}
|
||||
<a
|
||||
*ngIf="showFilterClose$ | async"
|
||||
class="text-black p-4 outline-none border-none bg-transparent"
|
||||
[routerLink]="closeFilterRoute"
|
||||
(click)="showFilter = false"
|
||||
queryParamsHandling="preserve"
|
||||
>
|
||||
<shared-icon icon="close" [size]="25"></shared-icon>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="catalog-search-filter-content-main -mt-14 desktop-small:-mt-8 desktop-large:-mt-12">
|
||||
<h1 class="text-h3 text-[1.625rem] font-bold text-center pt-6 pb-10">Filter</h1>
|
||||
<shared-filter
|
||||
@@ -24,14 +24,16 @@
|
||||
[scanner]="true"
|
||||
></shared-filter>
|
||||
</div>
|
||||
|
||||
<div class="cta-wrapper">
|
||||
<button class="cta-reset-filter" (click)="resetFilter(filter)" [disabled]="fetching$ | async">Filter zurücksetzen</button>
|
||||
|
||||
<button class="cta-apply-filter" (click)="applyFilter(filter)" [disabled]="(fetching$ | async) || !hasSelectedOptions(filter)">
|
||||
<ui-spinner [show]="fetching$ | async">Filter anwenden</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="desktop-large:hidden" [class.hidden]="showFilter">
|
||||
<page-article-search-main (showFilter)="showFilter = true"></page-article-search-main>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<div class="bg-white rounded py-10 px-4 text-center shadow-[0_-2px_24px_0_#dce2e9] h-full">
|
||||
<h1 class="text-h3 text-[1.625rem] font-bold mb-[0.375rem]">Artikelsuche</h1>
|
||||
<p class="text-lg mb-10">Welchen Artikel suchen Sie?</p>
|
||||
@if (filter$ | async; as filter) {
|
||||
@if (!(isDesktop$ | async)) {
|
||||
<shared-filter-filter-group-main
|
||||
class="mb-8 w-full"
|
||||
[inputGroup]="filter?.filter | group: 'main'"
|
||||
></shared-filter-filter-group-main>
|
||||
}
|
||||
<ng-container *ngIf="filter$ | async; let filter">
|
||||
<shared-filter-filter-group-main
|
||||
class="mb-8 w-full"
|
||||
*ngIf="!(isDesktop$ | async)"
|
||||
[inputGroup]="filter?.filter | group: 'main'"
|
||||
></shared-filter-filter-group-main>
|
||||
<div class="flex flex-row px-12 justify-center desktop-large:px-0">
|
||||
<shared-filter-input-group-main
|
||||
class="block w-full mr-3 desktop-large:mx-auto"
|
||||
@@ -18,40 +17,38 @@
|
||||
[showDescription]="false"
|
||||
[scanner]="true"
|
||||
></shared-filter-input-group-main>
|
||||
@if (!(isDesktop$ | async)) {
|
||||
<button
|
||||
type="button"
|
||||
(click)="showFilter.emit()"
|
||||
class="page-search-main__filter w-[6.75rem] h-14 rounded font-bold px-5 mb-4 text-lg bg-[#AEB7C1] flex flex-row flex-nowrap items-center justify-center"
|
||||
[class.active]="hasFilter$ | async"
|
||||
>
|
||||
<shared-icon class="mr-2" icon="filter-variant"></shared-icon>
|
||||
Filter
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
type="button"
|
||||
*ngIf="!(isDesktop$ | async)"
|
||||
(click)="showFilter.emit()"
|
||||
class="page-search-main__filter w-[6.75rem] h-14 rounded font-bold px-5 mb-4 text-lg bg-[#AEB7C1] flex flex-row flex-nowrap items-center justify-center"
|
||||
[class.active]="hasFilter$ | async"
|
||||
>
|
||||
<shared-icon class="mr-2" icon="filter-variant"></shared-icon>
|
||||
Filter
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-start ml-12 desktop-large:ml-8 py-6 bg-white overflow-hidden h-[calc(100%-13.5rem)]">
|
||||
<h3 class="text-p3 font-bold mb-3">Deine letzten Suchanfragen</h3>
|
||||
<ul class="flex flex-col justify-start overflow-hidden overflow-y-scroll items-start m-0 p-0 bg-white w-full">
|
||||
@for (recentQuery of history$ | async; track recentQuery) {
|
||||
<li class="list-none pb-3">
|
||||
<button
|
||||
class="flex flex-row items-center outline-none border-none bg-white text-black text-p2 m-0 p-0"
|
||||
(click)="setQueryHistory(filter, recentQuery.friendlyName)"
|
||||
matomoClickCategory="search"
|
||||
matomoClickAction="click"
|
||||
matomoClickName="recent-search"
|
||||
>
|
||||
<shared-icon
|
||||
class="flex w-8 h-8 justify-center items-center mr-3 rounded-full text-black bg-[#edeff0]"
|
||||
icon="magnify"
|
||||
[size]="20"
|
||||
></shared-icon>
|
||||
<p class="m-0 p-0 whitespace-nowrap overflow-hidden overflow-ellipsis max-w-[25rem]">{{ recentQuery.friendlyName }}</p>
|
||||
</button>
|
||||
</li>
|
||||
}
|
||||
<li class="list-none pb-3" *ngFor="let recentQuery of history$ | async">
|
||||
<button
|
||||
class="flex flex-row items-center outline-none border-none bg-white text-black text-p2 m-0 p-0"
|
||||
(click)="setQueryHistory(filter, recentQuery.friendlyName)"
|
||||
matomoClickCategory="search"
|
||||
matomoClickAction="click"
|
||||
matomoClickName="recent-search"
|
||||
>
|
||||
<shared-icon
|
||||
class="flex w-8 h-8 justify-center items-center mr-3 rounded-full text-black bg-[#edeff0]"
|
||||
icon="magnify"
|
||||
[size]="20"
|
||||
></shared-icon>
|
||||
<p class="m-0 p-0 whitespace-nowrap overflow-hidden overflow-ellipsis max-w-[25rem]">{{ recentQuery.friendlyName }}</p>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
@if (ref.data.canAddMessage) {
|
||||
<p class="can-add-message">{{ ref.data.canAddMessage }}</p>
|
||||
}
|
||||
<p class="can-add-message" *ngIf="ref.data.canAddMessage">{{ ref.data.canAddMessage }}</p>
|
||||
|
||||
<div class="actions">
|
||||
<button (click)="continue()" class="cta cta-action-secondary">Weiter Einkaufen</button>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@if (!primaryOutletActive) {
|
||||
<ng-container *ngIf="!primaryOutletActive; else primaryOutlet">
|
||||
<div class="bg-ucla-blue rounded w-[4.375rem] h-[5.625rem] animate-[load_1s_linear_infinite]"></div>
|
||||
<div class="flex flex-col flex-grow">
|
||||
<div class="h-4 bg-ucla-blue ml-4 mb-2 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
@@ -16,7 +16,9 @@
|
||||
<div class="h-4 bg-ucla-blue ml-4 w-[7.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
</ng-container>
|
||||
|
||||
<ng-template #primaryOutlet>
|
||||
<div class="bg-ucla-blue rounded w-[3rem] h-[4.125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
<div class="flex flex-col ml-4 w-[36.6%]">
|
||||
<div class="h-4 bg-ucla-blue mb-2 w-[8.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
@@ -33,5 +35,4 @@
|
||||
<div class="h-4 bg-ucla-blue mb-2 w-[8.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
<div class="h-4 bg-ucla-blue w-[8.8125rem] animate-[load_1s_linear_infinite]"></div>
|
||||
</div>
|
||||
}
|
||||
|
||||
</ng-template>
|
||||
|
||||
@@ -2,34 +2,32 @@
|
||||
class="page-search-result-item__item-card hover p-5 desktop-small:px-4 desktop-small:py-[0.625rem] h-[13.25rem] desktop-small:h-[11.3125rem] bg-white border border-solid border-transparent rounded"
|
||||
[class.page-search-result-item__item-card-primary]="primaryOutletActive"
|
||||
[class.active]="isActive"
|
||||
>
|
||||
>
|
||||
<div class="page-search-result-item__item-thumbnail text-center mr-4 w-[3.125rem] h-[4.9375rem]">
|
||||
@if (item?.imageId | thumbnailUrl; as thumbnailUrl) {
|
||||
<img
|
||||
class="page-search-result-item__item-image w-[3.125rem] max-h-[4.9375rem]"
|
||||
loading="lazy"
|
||||
[src]="thumbnailUrl"
|
||||
[alt]="item?.product?.name"
|
||||
/>
|
||||
}
|
||||
<img
|
||||
class="page-search-result-item__item-image w-[3.125rem] max-h-[4.9375rem]"
|
||||
loading="lazy"
|
||||
*ngIf="item?.imageId | thumbnailUrl; let thumbnailUrl"
|
||||
[src]="thumbnailUrl"
|
||||
[alt]="item?.product?.name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="page-search-result-item__item-grid-container"
|
||||
[class.page-search-result-item__item-grid-container-primary]="primaryOutletActive"
|
||||
>
|
||||
>
|
||||
<div
|
||||
class="page-search-result-item__item-contributors desktop-small:text-p3 font-bold text-[#0556B4] text-ellipsis overflow-hidden max-w-[24rem] whitespace-nowrap"
|
||||
>
|
||||
<a
|
||||
*ngFor="let contributor of contributors; let last = last"
|
||||
[routerLink]="resultsPath"
|
||||
[queryParams]="{ main_qs: contributor, main_author: 'author' }"
|
||||
(click)="$event?.stopPropagation()"
|
||||
>
|
||||
@for (contributor of contributors; track contributor; let last = $last) {
|
||||
<a
|
||||
[routerLink]="resultsPath"
|
||||
[queryParams]="{ main_qs: contributor, main_author: 'author' }"
|
||||
(click)="$event?.stopPropagation()"
|
||||
>
|
||||
{{ contributor }}{{ last ? '' : ';' }}
|
||||
</a>
|
||||
}
|
||||
{{ contributor }}{{ last ? '' : ';' }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -39,24 +37,21 @@
|
||||
[class.text-md]="item?.product?.name?.length >= 50 && isTablet"
|
||||
[class.text-p3]="item?.product?.name?.length >= 60 || !isTablet"
|
||||
[class.text-xs]="item?.product?.name?.length >= 100"
|
||||
>
|
||||
>
|
||||
{{ item?.product?.name }}
|
||||
</div>
|
||||
|
||||
<div class="page-search-result-item__item-format desktop-small:text-p3">
|
||||
@if (item?.product?.format && item?.product?.formatDetail) {
|
||||
<div class="font-bold flex flex-row">
|
||||
@if (item?.product?.format !== '--') {
|
||||
<img
|
||||
class="mr-3"
|
||||
loading="lazy"
|
||||
src="assets/images/Icon_{{ item?.product?.format }}.svg"
|
||||
[alt]="item?.product?.formatDetail"
|
||||
/>
|
||||
}
|
||||
{{ item?.product?.formatDetail | substr: 30 }}
|
||||
</div>
|
||||
}
|
||||
<div *ngIf="item?.product?.format && item?.product?.formatDetail" class="font-bold flex flex-row">
|
||||
<img
|
||||
class="mr-3"
|
||||
*ngIf="item?.product?.format !== '--'"
|
||||
loading="lazy"
|
||||
src="assets/images/Icon_{{ item?.product?.format }}.svg"
|
||||
[alt]="item?.product?.formatDetail"
|
||||
/>
|
||||
{{ item?.product?.formatDetail | substr: 30 }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-search-result-item__item-manufacturer desktop-small:text-p3">
|
||||
@@ -65,34 +60,31 @@
|
||||
|
||||
<div class="page-search-result-item__item-misc desktop-small:text-p3">
|
||||
{{ item?.product?.volume }}
|
||||
@if (item?.product?.volume && item?.product?.publicationDate) {
|
||||
<span>|</span>
|
||||
}
|
||||
<span *ngIf="item?.product?.volume && item?.product?.publicationDate">|</span>
|
||||
{{ publicationDate }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="page-search-result-item__item-price desktop-small:text-p3 font-bold justify-self-end"
|
||||
[class.page-search-result-item__item-price-primary]="primaryOutletActive"
|
||||
>
|
||||
>
|
||||
{{ item?.catalogAvailability?.price?.value?.value | currency: 'EUR' : 'code' }}
|
||||
</div>
|
||||
|
||||
<div class="page-search-result-item__item-select-bullet justify-self-end">
|
||||
@if (selectable) {
|
||||
<input
|
||||
(click)="$event.stopPropagation()"
|
||||
[ngModel]="selected"
|
||||
<input
|
||||
*ngIf="selectable"
|
||||
(click)="$event.stopPropagation()"
|
||||
[ngModel]="selected"
|
||||
(ngModelChange)="
|
||||
setSelected();
|
||||
tracker.trackEvent({ category: 'Trefferliste', action: 'select', name: item.product.name, value: selected ? 1 : 0 })
|
||||
"
|
||||
class="isa-select-bullet"
|
||||
type="checkbox"
|
||||
matomoTracker
|
||||
#tracker="matomo"
|
||||
/>
|
||||
}
|
||||
class="isa-select-bullet"
|
||||
type="checkbox"
|
||||
matomoTracker
|
||||
#tracker="matomo"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@@ -102,21 +94,20 @@
|
||||
[overlayTriggerDisabled]="!(stockTooltipText$ | async)"
|
||||
type="button"
|
||||
(click)="$event.stopPropagation(); $event.preventDefault(); showTooltip()"
|
||||
>
|
||||
>
|
||||
<ui-icon class="mr-[0.125rem] -mt-[0.275rem]" icon="home" size="1rem"></ui-icon>
|
||||
@if (isOrderBranch$ | async) {
|
||||
@if (inStock$ | async; as stock) {
|
||||
<span
|
||||
[class.skeleton]="stock.inStock === undefined"
|
||||
class="min-w-[0.75rem] text-right inline-block"
|
||||
>
|
||||
{{ stock?.inStock }}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
@if (!(isOrderBranch$ | async)) {
|
||||
<ng-container *ngIf="isOrderBranch$ | async">
|
||||
<span
|
||||
*ngIf="inStock$ | async; let stock"
|
||||
[class.skeleton]="stock.inStock === undefined"
|
||||
class="min-w-[0.75rem] text-right inline-block"
|
||||
>
|
||||
{{ stock?.inStock }}
|
||||
</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!(isOrderBranch$ | async)">
|
||||
<span class="min-w-[1rem] text-center inline-block">-</span>
|
||||
}
|
||||
</ng-container>
|
||||
<span>x</span>
|
||||
</button>
|
||||
<ui-tooltip #tooltip yPosition="above" xPosition="after" [yOffset]="-12" [closeable]="true">
|
||||
@@ -126,14 +117,14 @@
|
||||
<div
|
||||
class="page-search-result-item__item-ssc desktop-small:text-p3 w-full text-right overflow-hidden text-ellipsis whitespace-nowrap"
|
||||
[class.page-search-result-item__item-ssc-primary]="primaryOutletActive"
|
||||
>
|
||||
@if (ssc$ | async; as ssc) {
|
||||
>
|
||||
<ng-container *ngIf="ssc$ | async; let ssc">
|
||||
<div class="hidden" [class.page-search-result-item__item-ssc-tooltip]="primaryOutletActive">
|
||||
{{ ssc?.ssc }} - {{ ssc?.sscText }}
|
||||
</div>
|
||||
<strong>{{ ssc?.ssc }}</strong>
|
||||
- {{ ssc?.sscText }}
|
||||
}
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,53 +2,50 @@
|
||||
class="page-search-results__header bg-background-liste flex items-end justify-between"
|
||||
[class.pb-4]="!(primaryOutletActive$ | async)"
|
||||
[class.flex-col]="!(primaryOutletActive$ | async)"
|
||||
>
|
||||
>
|
||||
<div class="flex flex-row w-full desktop:w-min" [class.desktop-large:w-full]="!(primaryOutletActive$ | async)">
|
||||
@if (filter$ | async; as filter) {
|
||||
<shared-filter-input-group-main
|
||||
class="block mr-3 w-full desktop:w-[23.5rem]"
|
||||
[class.desktop-large:w-full]="!(primaryOutletActive$ | async)"
|
||||
[hint]="searchboxHint$ | async"
|
||||
[loading]="fetching$ | async"
|
||||
[inputGroup]="filter?.input | group: 'main'"
|
||||
(search)="search({ filter, clear: true })"
|
||||
[showDescription]="false"
|
||||
[scanner]="true"
|
||||
></shared-filter-input-group-main>
|
||||
}
|
||||
<shared-filter-input-group-main
|
||||
*ngIf="filter$ | async; let filter"
|
||||
class="block mr-3 w-full desktop:w-[23.5rem]"
|
||||
[class.desktop-large:w-full]="!(primaryOutletActive$ | async)"
|
||||
[hint]="searchboxHint$ | async"
|
||||
[loading]="fetching$ | async"
|
||||
[inputGroup]="filter?.input | group: 'main'"
|
||||
(search)="search({ filter, clear: true })"
|
||||
[showDescription]="false"
|
||||
[scanner]="true"
|
||||
></shared-filter-input-group-main>
|
||||
|
||||
<a
|
||||
class="page-search-results__filter w-[6.75rem] h-14 rounded font-bold px-5 mb-4 text-lg bg-[#AEB7C1] flex flex-row flex-nowrap items-center justify-center"
|
||||
[class.active]="hasFilter$ | async"
|
||||
[routerLink]="filterRoute"
|
||||
[queryParams]="filterQueryParams"
|
||||
>
|
||||
>
|
||||
<shared-icon class="mr-2" icon="filter-variant"></shared-icon>
|
||||
Filter
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if (hits$ | async; as hits) {
|
||||
<div
|
||||
class="page-search-results__items-count inline-flex flex-row items-center pr-5 text-p3"
|
||||
[class.mb-4]="primaryOutletActive$ | async"
|
||||
>
|
||||
{{ hits ?? 0 }}
|
||||
Titel
|
||||
</div>
|
||||
}
|
||||
<div
|
||||
*ngIf="hits$ | async; let hits"
|
||||
class="page-search-results__items-count inline-flex flex-row items-center pr-5 text-p3"
|
||||
[class.mb-4]="primaryOutletActive$ | async"
|
||||
>
|
||||
{{ hits ?? 0 }}
|
||||
Titel
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-search-results__order-by mb-[0.125rem]" [class.page-search-results__order-by-primary]="primaryOutletActive$ | async">
|
||||
@if (filter$ | async; as filter) {
|
||||
<shared-order-by-filter
|
||||
[orderBy]="filter?.orderBy"
|
||||
(selectedOrderByChange)="search({ filter, clear: true, orderBy: true }); updateBreadcrumbs()"
|
||||
></shared-order-by-filter>
|
||||
}
|
||||
<shared-order-by-filter
|
||||
*ngIf="filter$ | async; let filter"
|
||||
[orderBy]="filter?.orderBy"
|
||||
(selectedOrderByChange)="search({ filter, clear: true, orderBy: true }); updateBreadcrumbs()"
|
||||
></shared-order-by-filter>
|
||||
</div>
|
||||
|
||||
@if (primaryOutletActive$ | async) {
|
||||
<ng-container *ngIf="primaryOutletActive$ | async; else sideOutlet">
|
||||
<cdk-virtual-scroll-viewport class="product-list" [itemSize]="103 * (scale$ | async)" (scrolledIndexChange)="scrolledIndexChange($event)">
|
||||
<a
|
||||
*cdkVirtualFor="let item of results$ | async; let i = index; trackBy: trackByItemId"
|
||||
@@ -57,7 +54,7 @@
|
||||
#rla="routerLinkActive"
|
||||
queryParamsHandling="preserve"
|
||||
(click)="scrollToItem(i)"
|
||||
>
|
||||
>
|
||||
<search-result-item
|
||||
class="page-search-results__result-item page-search-results__result-item-primary"
|
||||
(selectedChange)="addToCart($event)"
|
||||
@@ -68,25 +65,24 @@
|
||||
[isActive]="rla.isActive"
|
||||
></search-result-item>
|
||||
</a>
|
||||
@if (fetching$ | async) {
|
||||
<page-search-result-item-loading [primaryOutletActive]="true"></page-search-result-item-loading>
|
||||
}
|
||||
<page-search-result-item-loading [primaryOutletActive]="true" *ngIf="fetching$ | async"></page-search-result-item-loading>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
<div class="actions z-sticky h-0">
|
||||
@if ((selectedItemIds$ | async)?.length > 0) {
|
||||
<button
|
||||
[disabled]="loading$ | async"
|
||||
class="cta-cart cta-action-primary"
|
||||
(click)="addToCart()"
|
||||
matomoClickCategory="Trefferliste"
|
||||
matomoClickAction="click"
|
||||
matomoClickName="In den Warenkorb legen"
|
||||
>
|
||||
<ui-spinner [show]="loading$ | async">In den Warenkorb legen</ui-spinner>
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
[disabled]="loading$ | async"
|
||||
*ngIf="(selectedItemIds$ | async)?.length > 0"
|
||||
class="cta-cart cta-action-primary"
|
||||
(click)="addToCart()"
|
||||
matomoClickCategory="Trefferliste"
|
||||
matomoClickAction="click"
|
||||
matomoClickName="In den Warenkorb legen"
|
||||
>
|
||||
<ui-spinner [show]="loading$ | async">In den Warenkorb legen</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
} @else {
|
||||
</ng-container>
|
||||
|
||||
<ng-template #sideOutlet>
|
||||
<cdk-virtual-scroll-viewport class="product-list" [itemSize]="191 * (scale$ | async)" (scrolledIndexChange)="scrolledIndexChange($event)">
|
||||
<a
|
||||
*cdkVirtualFor="let item of results$ | async; let i = index; trackBy: trackByItemId"
|
||||
@@ -95,7 +91,7 @@
|
||||
#rla="routerLinkActive"
|
||||
queryParamsHandling="preserve"
|
||||
(click)="scrollToItem(i)"
|
||||
>
|
||||
>
|
||||
<search-result-item
|
||||
class="page-search-results__result-item"
|
||||
(selectedChange)="addToCart($event)"
|
||||
@@ -106,20 +102,16 @@
|
||||
[isActive]="rla.isActive"
|
||||
></search-result-item>
|
||||
</a>
|
||||
@if (fetching$ | async) {
|
||||
<page-search-result-item-loading [primaryOutletActive]="false"></page-search-result-item-loading>
|
||||
}
|
||||
<page-search-result-item-loading [primaryOutletActive]="false" *ngIf="fetching$ | async"></page-search-result-item-loading>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
<div class="actions z-sticky h-0">
|
||||
@if ((selectedItemIds$ | async)?.length > 0) {
|
||||
<button
|
||||
[disabled]="loading$ | async"
|
||||
class="cta-cart cta-action-primary"
|
||||
(click)="addToCart()"
|
||||
>
|
||||
<ui-spinner [show]="loading$ | async">In den Warenkorb legen</ui-spinner>
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
[disabled]="loading$ | async"
|
||||
*ngIf="(selectedItemIds$ | async)?.length > 0"
|
||||
class="cta-cart cta-action-primary"
|
||||
(click)="addToCart()"
|
||||
>
|
||||
<ui-spinner [show]="loading$ | async">In den Warenkorb legen</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
</ng-template>
|
||||
|
||||
@@ -9,93 +9,87 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@if (control) {
|
||||
<form [formGroup]="control" (submit)="submit()">
|
||||
<ui-form-control class="searchbox-control" label="EAN/ISBN">
|
||||
<ui-searchbox
|
||||
formControlName="ean"
|
||||
[query]="query$ | async"
|
||||
(queryChange)="setQuery($event)"
|
||||
(search)="search($event)"
|
||||
(scan)="search($event)"
|
||||
[loading]="loading$ | async"
|
||||
[hint]="message$ | async"
|
||||
tabindex="0"
|
||||
[scanner]="true"
|
||||
></ui-searchbox>
|
||||
<form *ngIf="control" [formGroup]="control" (submit)="submit()">
|
||||
<ui-form-control class="searchbox-control" label="EAN/ISBN">
|
||||
<ui-searchbox
|
||||
formControlName="ean"
|
||||
[query]="query$ | async"
|
||||
(queryChange)="setQuery($event)"
|
||||
(search)="search($event)"
|
||||
(scan)="search($event)"
|
||||
[loading]="loading$ | async"
|
||||
[hint]="message$ | async"
|
||||
tabindex="0"
|
||||
[scanner]="true"
|
||||
></ui-searchbox>
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Titel" requiredMark="*">
|
||||
<input tabindex="0" uiInput formControlName="name" />
|
||||
</ui-form-control>
|
||||
<div class="control-row">
|
||||
<ui-form-control label="Menge" requiredMark="*">
|
||||
<input tabindex="0" uiInput formControlName="quantity" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Titel" requiredMark="*">
|
||||
<input tabindex="0" uiInput formControlName="name" />
|
||||
<ui-form-control class="datepicker" label="vsl. Lieferdatum" requiredMark="*">
|
||||
<button
|
||||
tabindex="-1"
|
||||
class="date-btn"
|
||||
type="button"
|
||||
[class.content-selected]="!!(estimatedShippingDate$ | async)"
|
||||
[uiOverlayTrigger]="uiDatepicker"
|
||||
#datepicker="uiOverlayTrigger"
|
||||
>
|
||||
<strong>
|
||||
{{ estimatedShippingDate$ | async | date: 'dd.MM.yy' }}
|
||||
</strong>
|
||||
<ui-icon icon="arrow_head" class="dp-button-icon" size="20px" [rotate]="datepicker.opened ? '270deg' : '90deg'"></ui-icon>
|
||||
</button>
|
||||
<ui-datepicker
|
||||
formControlName="estimatedShippingDate"
|
||||
#uiDatepicker
|
||||
yPosition="below"
|
||||
xPosition="after"
|
||||
[min]="minDate"
|
||||
[disabledDaysOfWeek]="[0]"
|
||||
[selected]="estimatedShippingDate$ | async"
|
||||
saveLabel="Übernehmen"
|
||||
(save)="changeEstimatedShippingDate($event); uiDatepicker.close()"
|
||||
></ui-datepicker>
|
||||
</ui-form-control>
|
||||
<div class="control-row">
|
||||
<ui-form-control label="Menge" requiredMark="*">
|
||||
<input tabindex="0" uiInput formControlName="quantity" />
|
||||
</ui-form-control>
|
||||
<ui-form-control class="datepicker" label="vsl. Lieferdatum" requiredMark="*">
|
||||
<button
|
||||
tabindex="-1"
|
||||
class="date-btn"
|
||||
type="button"
|
||||
[class.content-selected]="!!(estimatedShippingDate$ | async)"
|
||||
[uiOverlayTrigger]="uiDatepicker"
|
||||
#datepicker="uiOverlayTrigger"
|
||||
>
|
||||
<strong>
|
||||
{{ estimatedShippingDate$ | async | date: 'dd.MM.yy' }}
|
||||
</strong>
|
||||
<ui-icon icon="arrow_head" class="dp-button-icon" size="20px" [rotate]="datepicker.opened ? '270deg' : '90deg'"></ui-icon>
|
||||
</button>
|
||||
<ui-datepicker
|
||||
formControlName="estimatedShippingDate"
|
||||
#uiDatepicker
|
||||
yPosition="below"
|
||||
xPosition="after"
|
||||
[min]="minDate"
|
||||
[disabledDaysOfWeek]="[0]"
|
||||
[selected]="estimatedShippingDate$ | async"
|
||||
saveLabel="Übernehmen"
|
||||
(save)="changeEstimatedShippingDate($event); uiDatepicker.close()"
|
||||
></ui-datepicker>
|
||||
</ui-form-control>
|
||||
</div>
|
||||
<ui-form-control label="Autor">
|
||||
<input tabindex="0" uiInput formControlName="contributors" />
|
||||
</div>
|
||||
<ui-form-control label="Autor">
|
||||
<input tabindex="0" uiInput formControlName="contributors" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Verlag">
|
||||
<input tabindex="0" uiInput formControlName="manufacturer" />
|
||||
</ui-form-control>
|
||||
<ui-form-control class="supplier-dropdown" label="Lieferant" requiredMark="*">
|
||||
<ui-select tabindex="-1" formControlName="supplier">
|
||||
<ui-select-option *ngFor="let supplier of suppliers$ | async" [label]="supplier.name" [value]="supplier.id"></ui-select-option>
|
||||
</ui-select>
|
||||
</ui-form-control>
|
||||
<div class="control-row">
|
||||
<ui-form-control class="price" label="Stückpreis" [suffix]="price.value ? '€' : ''" requiredMark="*">
|
||||
<input tabindex="0" #price uiInput formControlName="price" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Verlag">
|
||||
<input tabindex="0" uiInput formControlName="manufacturer" />
|
||||
</ui-form-control>
|
||||
<ui-form-control class="supplier-dropdown" label="Lieferant" requiredMark="*">
|
||||
<ui-select tabindex="-1" formControlName="supplier">
|
||||
@for (supplier of suppliers$ | async; track supplier) {
|
||||
<ui-select-option [label]="supplier.name" [value]="supplier.id"></ui-select-option>
|
||||
}
|
||||
<ui-form-control class="mwst-dropdown" label="MwSt" requiredMark="*">
|
||||
<ui-select tabindex="-1" formControlName="vat">
|
||||
<ui-select-option *ngFor="let vat of vats$ | async" [label]="vat.name + '%'" [value]="vat.vatType"></ui-select-option>
|
||||
</ui-select>
|
||||
</ui-form-control>
|
||||
<div class="control-row">
|
||||
<ui-form-control class="price" label="Stückpreis" [suffix]="price.value ? '€' : ''" requiredMark="*">
|
||||
<input tabindex="0" #price uiInput formControlName="price" />
|
||||
</ui-form-control>
|
||||
<ui-form-control class="mwst-dropdown" label="MwSt" requiredMark="*">
|
||||
<ui-select tabindex="-1" formControlName="vat">
|
||||
@for (vat of vats$ | async; track vat) {
|
||||
<ui-select-option [label]="vat.name + '%'" [value]="vat.vatType"></ui-select-option>
|
||||
}
|
||||
</ui-select>
|
||||
</ui-form-control>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button
|
||||
class="cta-secondary"
|
||||
(click)="nextItem()"
|
||||
[disabled]="control.invalid || control.disabled || (loading$ | async)"
|
||||
type="button"
|
||||
>
|
||||
Weitere Artikel hinzufügen
|
||||
</button>
|
||||
<button class="cta-primary" [disabled]="control.invalid || control.disabled || (loading$ | async)" type="submit">
|
||||
Bestellung anlegen
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button
|
||||
class="cta-secondary"
|
||||
(click)="nextItem()"
|
||||
[disabled]="control.invalid || control.disabled || (loading$ | async)"
|
||||
type="button"
|
||||
>
|
||||
Weitere Artikel hinzufügen
|
||||
</button>
|
||||
<button class="cta-primary" [disabled]="control.invalid || control.disabled || (loading$ | async)" type="submit">
|
||||
Bestellung anlegen
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
@if ((groupedItems$ | async)?.length <= 0 && !(fetching$ | async)) {
|
||||
<ng-container *ngIf="(groupedItems$ | async)?.length <= 0 && !(fetching$ | async); else shoppingCart">
|
||||
<div class="card stretch">
|
||||
<div class="empty-message">
|
||||
<span class="cart-icon flex items-center justify-center">
|
||||
<shared-icon icon="shopping-cart-bold" [size]="24"></shared-icon>
|
||||
</span>
|
||||
|
||||
<h1>Ihr Warenkorb ist leer.</h1>
|
||||
<p>
|
||||
Sie haben alle Artikel aus dem
|
||||
@@ -12,75 +13,84 @@
|
||||
<br />
|
||||
keinen Artikel hinzugefügt.
|
||||
</p>
|
||||
|
||||
<div class="btn-wrapper">
|
||||
<a class="cta-primary" [routerLink]="productSearchBasePath">Artikel suchen</a>
|
||||
<button class="cta-secondary" (click)="openDummyModal({})">Neuanlage</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
@if (shoppingCart$ | async; as shoppingCart) {
|
||||
</ng-container>
|
||||
|
||||
<div class="flex items-center justify-center card stretch" *ngIf="fetching$ | async">
|
||||
<ui-spinner [show]="true"></ui-spinner>
|
||||
</div>
|
||||
|
||||
<ng-template #shoppingCart>
|
||||
<ng-container *ngIf="shoppingCart$ | async; let shoppingCart">
|
||||
<div class="card stretch">
|
||||
<div class="cta-print-wrapper">
|
||||
<button class="cta-print" (click)="openPrintModal()">Drucken</button>
|
||||
</div>
|
||||
<h1 class="header">Warenkorb</h1>
|
||||
@if (!(isDesktop$ | async)) {
|
||||
|
||||
<ng-container *ngIf="!(isDesktop$ | async)">
|
||||
<page-checkout-review-details></page-checkout-review-details>
|
||||
}
|
||||
@for (group of groupedItems$ | async; track trackByGroupedItems($index, group); let lastGroup = $last) {
|
||||
@if (group?.orderType !== undefined) {
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngFor="let group of groupedItems$ | async; let lastGroup = last; trackBy: trackByGroupedItems">
|
||||
<ng-container *ngIf="group?.orderType !== undefined">
|
||||
<hr />
|
||||
<div class="row item-group-header bg-[#F5F7FA]">
|
||||
@if (group.orderType !== 'Dummy') {
|
||||
<shared-icon
|
||||
class="icon-order-type"
|
||||
[size]="group.orderType === 'B2B-Versand' ? 36 : 24"
|
||||
[icon]="group.orderType"
|
||||
></shared-icon>
|
||||
}
|
||||
<shared-icon
|
||||
*ngIf="group.orderType !== 'Dummy'"
|
||||
class="icon-order-type"
|
||||
[size]="group.orderType === 'B2B-Versand' ? 36 : 24"
|
||||
[icon]="group.orderType"
|
||||
></shared-icon>
|
||||
|
||||
<div class="label" [class.dummy]="group.orderType === 'Dummy'">
|
||||
{{ group.orderType !== 'Dummy' ? group.orderType : 'Manuelle Anlage / Dummy Bestellung' }}
|
||||
@if (group.orderType === 'Dummy') {
|
||||
<button
|
||||
class="text-brand border-none font-bold text-p1 outline-none pl-4"
|
||||
(click)="openDummyModal({ changeDataFromCart: true })"
|
||||
>
|
||||
Hinzufügen
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
*ngIf="group.orderType === 'Dummy'"
|
||||
class="text-brand border-none font-bold text-p1 outline-none pl-4"
|
||||
(click)="openDummyModal({ changeDataFromCart: true })"
|
||||
>
|
||||
Hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grow"></div>
|
||||
@if (group.orderType !== 'Download' && group.orderType !== 'Dummy') {
|
||||
<div class="pl-4">
|
||||
<button class="cta-edit" (click)="showPurchasingListModal(group.items)">Ändern</button>
|
||||
</div>
|
||||
}
|
||||
<div class="pl-4" *ngIf="group.orderType !== 'Download' && group.orderType !== 'Dummy'">
|
||||
<button class="cta-edit" (click)="showPurchasingListModal(group.items)">Ändern</button>
|
||||
</div>
|
||||
</div>
|
||||
@if (
|
||||
group.orderType === 'Download' ||
|
||||
group.orderType === 'Versand' ||
|
||||
group.orderType === 'B2B-Versand' ||
|
||||
group.orderType === 'DIG-Versand'
|
||||
) {
|
||||
<hr
|
||||
/>
|
||||
}
|
||||
}
|
||||
@for (item of group.items; track trackByItemId(i, item); let lastItem = $last; let i = $index) {
|
||||
@if (group?.orderType !== undefined && (item.features?.orderType === 'Abholung' || item.features?.orderType === 'Rücklage')) {
|
||||
@if (item?.destination?.data?.targetBranch?.data; as targetBranch) {
|
||||
@if (i === 0 || checkIfMultipleDestinationsForOrderTypeExist(targetBranch, group, i)) {
|
||||
<hr
|
||||
*ngIf="
|
||||
group.orderType === 'Download' ||
|
||||
group.orderType === 'Versand' ||
|
||||
group.orderType === 'B2B-Versand' ||
|
||||
group.orderType === 'DIG-Versand'
|
||||
"
|
||||
/>
|
||||
</ng-container>
|
||||
<ng-container *ngFor="let item of group.items; let lastItem = last; let i = index; trackBy: trackByItemId">
|
||||
<ng-container
|
||||
*ngIf="group?.orderType !== undefined && (item.features?.orderType === 'Abholung' || item.features?.orderType === 'Rücklage')"
|
||||
>
|
||||
<ng-container *ngIf="item?.destination?.data?.targetBranch?.data; let targetBranch">
|
||||
<ng-container *ngIf="i === 0 || checkIfMultipleDestinationsForOrderTypeExist(targetBranch, group, i)">
|
||||
<div
|
||||
class="flex flex-row items-center px-5 pt-0 pb-[0.875rem] -mt-2 bg-[#F5F7FA]"
|
||||
[class.multiple-destinations]="checkIfMultipleDestinationsForOrderTypeExist(targetBranch, group, i)"
|
||||
>
|
||||
>
|
||||
<span class="branch-name">{{ targetBranch?.name }} | {{ targetBranch | branchAddress }}</span>
|
||||
</div>
|
||||
<hr />
|
||||
}
|
||||
}
|
||||
}
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<page-shopping-cart-item
|
||||
(changeItem)="changeItem($event)"
|
||||
(changeDummyItem)="changeDummyItem($event)"
|
||||
@@ -91,22 +101,19 @@
|
||||
[loadingOnItemChangeById]="loadingOnItemChangeById$ | async"
|
||||
[loadingOnQuantityChangeById]="loadingOnQuantityChangeById$ | async"
|
||||
></page-shopping-cart-item>
|
||||
@if (!lastItem) {
|
||||
<hr />
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<hr *ngIf="!lastItem" />
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<div class="h-[8.9375rem]"></div>
|
||||
</div>
|
||||
<div class="card footer flex flex-col justify-center items-center">
|
||||
<div class="flex flex-row items-start justify-between w-full mb-1">
|
||||
@if (totalItemCount$ | async; as totalItemCount) {
|
||||
@if (totalReadingPoints$ | async; as totalReadingPoints) {
|
||||
<div class="total-item-reading-points w-full">
|
||||
{{ totalItemCount }} Artikel | {{ totalReadingPoints }} Lesepunkte
|
||||
</div>
|
||||
}
|
||||
}
|
||||
<ng-container *ngIf="totalItemCount$ | async; let totalItemCount">
|
||||
<div *ngIf="totalReadingPoints$ | async; let totalReadingPoints" class="total-item-reading-points w-full">
|
||||
{{ totalItemCount }} Artikel | {{ totalReadingPoints }} Lesepunkte
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="flex flex-col w-full">
|
||||
<strong class="total-value">
|
||||
Zwischensumme {{ shoppingCart?.total?.value | currency: shoppingCart?.total?.currency : 'code' }}
|
||||
@@ -123,18 +130,11 @@
|
||||
notificationsControl?.invalid ||
|
||||
((primaryCtaLabel$ | async) === 'Bestellen' && ((checkingOla$ | async) || (checkoutIsInValid$ | async)))
|
||||
"
|
||||
>
|
||||
>
|
||||
<ui-spinner [show]="showOrderButtonSpinner">
|
||||
{{ primaryCtaLabel$ | async }}
|
||||
</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@if (fetching$ | async) {
|
||||
<div class="flex items-center justify-center card stretch">
|
||||
<ui-spinner [show]="true"></ui-spinner>
|
||||
</div>
|
||||
}
|
||||
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
|
||||
@@ -9,53 +9,37 @@ import {
|
||||
AfterViewInit,
|
||||
TrackByFunction,
|
||||
inject,
|
||||
} from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { ApplicationService } from "@core/application";
|
||||
import { DomainAvailabilityService } from "@domain/availability";
|
||||
import { DomainCheckoutService } from "@domain/checkout";
|
||||
import {
|
||||
AvailabilityDTO,
|
||||
BranchDTO,
|
||||
DestinationDTO,
|
||||
ShoppingCartItemDTO,
|
||||
} from "@generated/swagger/checkout-api";
|
||||
import { UiMessageModalComponent, UiModalService } from "@ui/modal";
|
||||
import { PrintModalData, PrintModalComponent } from "@modal/printer";
|
||||
import { delay, first, map, switchMap, takeUntil, tap } from "rxjs/operators";
|
||||
import {
|
||||
Subject,
|
||||
NEVER,
|
||||
combineLatest,
|
||||
BehaviorSubject,
|
||||
Subscription,
|
||||
} from "rxjs";
|
||||
import { DomainCatalogService } from "@domain/catalog";
|
||||
import { BreadcrumbService } from "@core/breadcrumb";
|
||||
import { DomainPrinterService } from "@domain/printer";
|
||||
import { CheckoutDummyComponent } from "../checkout-dummy/checkout-dummy.component";
|
||||
import { CheckoutDummyData } from "../checkout-dummy/checkout-dummy-data";
|
||||
import { PurchaseOptionsModalService } from "@modal/purchase-options";
|
||||
import {
|
||||
CheckoutNavigationService,
|
||||
ProductCatalogNavigationService,
|
||||
} from "@shared/services/navigation";
|
||||
import { EnvironmentService } from "@core/environment";
|
||||
import { CheckoutReviewStore } from "./checkout-review.store";
|
||||
import { ToasterService } from "@shared/shell";
|
||||
import { ShoppingCartItemComponent } from "./shopping-cart-item/shopping-cart-item.component";
|
||||
import { CustomerSearchNavigation } from "@shared/services/navigation";
|
||||
} from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { DomainAvailabilityService } from '@domain/availability';
|
||||
import { DomainCheckoutService } from '@domain/checkout';
|
||||
import { AvailabilityDTO, BranchDTO, DestinationDTO, ShoppingCartItemDTO } from '@generated/swagger/checkout-api';
|
||||
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
|
||||
import { PrintModalData, PrintModalComponent } from '@modal/printer';
|
||||
import { delay, first, map, switchMap, takeUntil, tap } from 'rxjs/operators';
|
||||
import { Subject, NEVER, combineLatest, BehaviorSubject, Subscription } from 'rxjs';
|
||||
import { DomainCatalogService } from '@domain/catalog';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { DomainPrinterService } from '@domain/printer';
|
||||
import { CheckoutDummyComponent } from '../checkout-dummy/checkout-dummy.component';
|
||||
import { CheckoutDummyData } from '../checkout-dummy/checkout-dummy-data';
|
||||
import { PurchaseOptionsModalService } from '@modal/purchase-options';
|
||||
import { CheckoutNavigationService, ProductCatalogNavigationService } from '@shared/services/navigation';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { CheckoutReviewStore } from './checkout-review.store';
|
||||
import { ToasterService } from '@shared/shell';
|
||||
import { ShoppingCartItemComponent } from './shopping-cart-item/shopping-cart-item.component';
|
||||
import { CustomerSearchNavigation } from '@shared/services/navigation';
|
||||
|
||||
@Component({
|
||||
selector: "page-checkout-review",
|
||||
templateUrl: "checkout-review.component.html",
|
||||
styleUrls: ["checkout-review.component.scss"],
|
||||
selector: 'page-checkout-review',
|
||||
templateUrl: 'checkout-review.component.html',
|
||||
styleUrls: ['checkout-review.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false,
|
||||
})
|
||||
export class CheckoutReviewComponent
|
||||
implements OnInit, OnDestroy, AfterViewInit
|
||||
{
|
||||
export class CheckoutReviewComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
|
||||
private _customerSearchNavigation = inject(CustomerSearchNavigation);
|
||||
@@ -73,9 +57,7 @@ export class CheckoutReviewComponent
|
||||
|
||||
shoppingCartItemsWithoutOrderType$ = this._store.shoppingCartItems$.pipe(
|
||||
takeUntil(this._store.orderCompleted),
|
||||
map((items) =>
|
||||
items?.filter((item) => item?.features?.orderType === undefined),
|
||||
),
|
||||
map((items) => items?.filter((item) => item?.features?.orderType === undefined)),
|
||||
);
|
||||
|
||||
trackByGroupedItems: TrackByFunction<{
|
||||
@@ -89,11 +71,11 @@ export class CheckoutReviewComponent
|
||||
map((items) =>
|
||||
items.reduce(
|
||||
(grouped, item) => {
|
||||
const index = grouped.findIndex((g) =>
|
||||
item?.availability?.supplyChannel === "MANUALLY"
|
||||
? g?.orderType === "Dummy"
|
||||
: item?.features?.orderType === "DIG-Versand"
|
||||
? g?.orderType === "Versand"
|
||||
let index = grouped.findIndex((g) =>
|
||||
item?.availability?.supplyChannel === 'MANUALLY'
|
||||
? g?.orderType === 'Dummy'
|
||||
: item?.features?.orderType === 'DIG-Versand'
|
||||
? g?.orderType === 'Versand'
|
||||
: g?.orderType === item?.features?.orderType,
|
||||
);
|
||||
|
||||
@@ -101,10 +83,10 @@ export class CheckoutReviewComponent
|
||||
if (!group) {
|
||||
group = {
|
||||
orderType:
|
||||
item?.availability?.supplyChannel === "MANUALLY"
|
||||
? "Dummy"
|
||||
: item?.features?.orderType === "DIG-Versand"
|
||||
? "Versand"
|
||||
item?.availability?.supplyChannel === 'MANUALLY'
|
||||
? 'Dummy'
|
||||
: item?.features?.orderType === 'DIG-Versand'
|
||||
? 'Versand'
|
||||
: item?.features?.orderType,
|
||||
destination: item?.destination?.data,
|
||||
items: [],
|
||||
@@ -113,8 +95,7 @@ export class CheckoutReviewComponent
|
||||
|
||||
group.items = [...group.items, item]?.sort(
|
||||
(a, b) =>
|
||||
a.destination?.data?.targetBranch?.id -
|
||||
b.destination?.data?.targetBranch?.id ||
|
||||
a.destination?.data?.targetBranch?.id - b.destination?.data?.targetBranch?.id ||
|
||||
a.product?.name.localeCompare(b.product?.name),
|
||||
);
|
||||
|
||||
@@ -124,19 +105,9 @@ export class CheckoutReviewComponent
|
||||
grouped.push(group);
|
||||
}
|
||||
|
||||
return [...grouped].sort((a, b) =>
|
||||
a?.orderType === undefined
|
||||
? -1
|
||||
: b?.orderType === undefined
|
||||
? 1
|
||||
: 0,
|
||||
);
|
||||
return [...grouped].sort((a, b) => (a?.orderType === undefined ? -1 : b?.orderType === undefined ? 1 : 0));
|
||||
},
|
||||
[] as {
|
||||
orderType: string;
|
||||
destination: DestinationDTO;
|
||||
items: ShoppingCartItemDTO[];
|
||||
}[],
|
||||
[] as { orderType: string; destination: DestinationDTO; items: ShoppingCartItemDTO[] }[],
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -167,14 +138,7 @@ export class CheckoutReviewComponent
|
||||
.getPromotionPoints({
|
||||
items,
|
||||
})
|
||||
.pipe(
|
||||
map((response) =>
|
||||
Object.values(response.result).reduce(
|
||||
(sum, points) => sum + points,
|
||||
0,
|
||||
),
|
||||
),
|
||||
);
|
||||
.pipe(map((response) => Object.values(response.result).reduce((sum, points) => sum + points, 0)));
|
||||
} else {
|
||||
return NEVER;
|
||||
}
|
||||
@@ -183,25 +147,20 @@ export class CheckoutReviewComponent
|
||||
|
||||
customerFeatures$ = this._store.customerFeatures$;
|
||||
|
||||
checkNotificationChannelControl$ =
|
||||
this._store.checkNotificationChannelControl$;
|
||||
checkNotificationChannelControl$ = this._store.checkNotificationChannelControl$;
|
||||
|
||||
showQuantityControlSpinnerItemId: number;
|
||||
quantityError$ = new BehaviorSubject<{ [key: string]: string }>({});
|
||||
|
||||
primaryCtaLabel$ = combineLatest([
|
||||
this.payer$,
|
||||
this.buyer$,
|
||||
this.shoppingCartItemsWithoutOrderType$,
|
||||
]).pipe(
|
||||
primaryCtaLabel$ = combineLatest([this.payer$, this.buyer$, this.shoppingCartItemsWithoutOrderType$]).pipe(
|
||||
map(([payer, buyer, shoppingCartItemsWithoutOrderType]) => {
|
||||
if (shoppingCartItemsWithoutOrderType?.length > 0) {
|
||||
return "Kaufoptionen";
|
||||
return 'Kaufoptionen';
|
||||
}
|
||||
if (!(payer || buyer)) {
|
||||
return "Weiter";
|
||||
return 'Weiter';
|
||||
}
|
||||
return "Bestellen";
|
||||
return 'Bestellen';
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -222,16 +181,12 @@ export class CheckoutReviewComponent
|
||||
|
||||
checkoutIsInValid$ = this.applicationService.activatedProcessId$.pipe(
|
||||
takeUntil(this._onDestroy$),
|
||||
switchMap((processId) =>
|
||||
this.domainCheckoutService.checkoutIsValid({ processId }),
|
||||
),
|
||||
switchMap((processId) => this.domainCheckoutService.checkoutIsValid({ processId })),
|
||||
map((valid) => !valid),
|
||||
);
|
||||
|
||||
get productSearchBasePath() {
|
||||
return this._productNavigationService.getArticleSearchBasePath(
|
||||
this.applicationService.activatedProcessId,
|
||||
).path;
|
||||
return this._productNavigationService.getArticleSearchBasePath(this.applicationService.activatedProcessId).path;
|
||||
}
|
||||
|
||||
get isDesktop$() {
|
||||
@@ -264,16 +219,14 @@ export class CheckoutReviewComponent
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.applicationService.activatedProcessId$
|
||||
.pipe(takeUntil(this._onDestroy$))
|
||||
.subscribe((_) => {
|
||||
this._store.loadShoppingCart();
|
||||
});
|
||||
this.applicationService.activatedProcessId$.pipe(takeUntil(this._onDestroy$)).subscribe((_) => {
|
||||
this._store.loadShoppingCart();
|
||||
});
|
||||
|
||||
await this.removeBreadcrumbs();
|
||||
await this.updateBreadcrumb();
|
||||
|
||||
window["Checkout"] = {
|
||||
window['Checkout'] = {
|
||||
refreshAvailabilities: this.refreshAvailabilities.bind(this),
|
||||
};
|
||||
}
|
||||
@@ -314,16 +267,13 @@ export class CheckoutReviewComponent
|
||||
group: { items: ShoppingCartItemDTO[] },
|
||||
i: number,
|
||||
) {
|
||||
return i === 0
|
||||
? false
|
||||
: targetBranch.id !==
|
||||
group.items[i - 1].destination?.data?.targetBranch?.data.id;
|
||||
return i === 0 ? false : targetBranch.id !== group.items[i - 1].destination?.data?.targetBranch?.data.id;
|
||||
}
|
||||
|
||||
async refreshAvailabilities() {
|
||||
this.checkingOla$.next(true);
|
||||
|
||||
for (const itemComp of this._shoppingCartItems.toArray()) {
|
||||
for (let itemComp of this._shoppingCartItems.toArray()) {
|
||||
await itemComp.refreshAvailability();
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
@@ -333,22 +283,16 @@ export class CheckoutReviewComponent
|
||||
async updateBreadcrumb() {
|
||||
await this.breadcrumb.addOrUpdateBreadcrumbIfNotExists({
|
||||
key: this.applicationService.activatedProcessId,
|
||||
name: "Warenkorb",
|
||||
path: this._navigationService.getCheckoutReviewPath(
|
||||
this.applicationService.activatedProcessId,
|
||||
).path,
|
||||
tags: ["checkout", "cart"],
|
||||
section: "customer",
|
||||
name: 'Warenkorb',
|
||||
path: this._navigationService.getCheckoutReviewPath(this.applicationService.activatedProcessId).path,
|
||||
tags: ['checkout', 'cart'],
|
||||
section: 'customer',
|
||||
});
|
||||
}
|
||||
|
||||
async removeBreadcrumbs() {
|
||||
const checkoutDummyCrumbs = await this.breadcrumb
|
||||
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, [
|
||||
"checkout",
|
||||
"cart",
|
||||
"dummy",
|
||||
])
|
||||
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['checkout', 'cart', 'dummy'])
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
checkoutDummyCrumbs.forEach(async (crumb) => {
|
||||
@@ -360,49 +304,32 @@ export class CheckoutReviewComponent
|
||||
this._store.notificationsControl = undefined;
|
||||
}
|
||||
|
||||
openDummyModal({
|
||||
data,
|
||||
changeDataFromCart = false,
|
||||
}: {
|
||||
data?: CheckoutDummyData;
|
||||
changeDataFromCart?: boolean;
|
||||
}) {
|
||||
openDummyModal({ data, changeDataFromCart = false }: { data?: CheckoutDummyData; changeDataFromCart?: boolean }) {
|
||||
this.uiModal.open({
|
||||
content: CheckoutDummyComponent,
|
||||
data: { ...data, changeDataFromCart },
|
||||
});
|
||||
}
|
||||
|
||||
changeDummyItem({
|
||||
shoppingCartItem,
|
||||
}: {
|
||||
shoppingCartItem: ShoppingCartItemDTO;
|
||||
}) {
|
||||
changeDummyItem({ shoppingCartItem }: { shoppingCartItem: ShoppingCartItemDTO }) {
|
||||
this.openDummyModal({ data: shoppingCartItem, changeDataFromCart: true });
|
||||
}
|
||||
|
||||
async changeItem({
|
||||
shoppingCartItem,
|
||||
}: {
|
||||
shoppingCartItem: ShoppingCartItemDTO;
|
||||
}) {
|
||||
async changeItem({ shoppingCartItem }: { shoppingCartItem: ShoppingCartItemDTO }) {
|
||||
this._purchaseOptionsModalService.open({
|
||||
processId: this.applicationService.activatedProcessId,
|
||||
items: [shoppingCartItem],
|
||||
type: "update",
|
||||
type: 'update',
|
||||
});
|
||||
}
|
||||
|
||||
async openPrintModal() {
|
||||
const shoppingCart = await this.shoppingCart$.pipe(first()).toPromise();
|
||||
let shoppingCart = await this.shoppingCart$.pipe(first()).toPromise();
|
||||
this.uiModal.open({
|
||||
content: PrintModalComponent,
|
||||
data: {
|
||||
printerType: "Label",
|
||||
print: (printer) =>
|
||||
this.domainPrinterService
|
||||
.printCart({ cartId: shoppingCart.id, printer })
|
||||
.toPromise(),
|
||||
printerType: 'Label',
|
||||
print: (printer) => this.domainPrinterService.printCart({ cartId: shoppingCart.id, printer }).toPromise(),
|
||||
} as PrintModalData,
|
||||
config: {
|
||||
panelClass: [],
|
||||
@@ -424,8 +351,7 @@ export class CheckoutReviewComponent
|
||||
|
||||
this.loadingOnQuantityChangeById$.next(shoppingCartItem.id);
|
||||
|
||||
const shoppingCartItemPrice =
|
||||
shoppingCartItem?.availability?.price?.value?.value;
|
||||
const shoppingCartItemPrice = shoppingCartItem?.availability?.price?.value?.value;
|
||||
const orderType = shoppingCartItem?.features?.orderType;
|
||||
let availability: AvailabilityDTO;
|
||||
|
||||
@@ -434,7 +360,7 @@ export class CheckoutReviewComponent
|
||||
|
||||
if (orderType) {
|
||||
switch (orderType) {
|
||||
case "Rücklage":
|
||||
case 'Rücklage':
|
||||
availability = await this.availabilityService
|
||||
.getTakeAwayAvailability({
|
||||
item: {
|
||||
@@ -443,13 +369,12 @@ export class CheckoutReviewComponent
|
||||
price: shoppingCartItem.availability.price,
|
||||
},
|
||||
quantity,
|
||||
branch,
|
||||
})
|
||||
.toPromise();
|
||||
|
||||
// this.setQuantityError(shoppingCartItem, availability, availability?.inStock < quantity);
|
||||
break;
|
||||
case "Abholung":
|
||||
case 'Abholung':
|
||||
availability = await this.availabilityService
|
||||
.getPickUpAvailability({
|
||||
branch,
|
||||
@@ -463,7 +388,7 @@ export class CheckoutReviewComponent
|
||||
.pipe(map((av) => av[0]))
|
||||
.toPromise();
|
||||
break;
|
||||
case "Versand":
|
||||
case 'Versand':
|
||||
availability = await this.availabilityService
|
||||
.getDeliveryAvailability({
|
||||
item: {
|
||||
@@ -475,7 +400,7 @@ export class CheckoutReviewComponent
|
||||
})
|
||||
.toPromise();
|
||||
break;
|
||||
case "DIG-Versand":
|
||||
case 'DIG-Versand':
|
||||
availability = await this.availabilityService
|
||||
.getDigDeliveryAvailability({
|
||||
item: {
|
||||
@@ -487,7 +412,7 @@ export class CheckoutReviewComponent
|
||||
})
|
||||
.toPromise();
|
||||
break;
|
||||
case "B2B-Versand":
|
||||
case 'B2B-Versand':
|
||||
availability = await this.availabilityService
|
||||
.getB2bDeliveryAvailability({
|
||||
item: {
|
||||
@@ -499,7 +424,7 @@ export class CheckoutReviewComponent
|
||||
})
|
||||
.toPromise();
|
||||
break;
|
||||
case "Download":
|
||||
case 'Download':
|
||||
availability = await this.availabilityService
|
||||
.getDownloadAvailability({
|
||||
item: {
|
||||
@@ -538,11 +463,7 @@ export class CheckoutReviewComponent
|
||||
shoppingCartItemId: shoppingCartItem.id,
|
||||
update: {
|
||||
quantity,
|
||||
availability: this.compareDeliveryAndCatalogPrice(
|
||||
updateAvailability,
|
||||
orderType,
|
||||
shoppingCartItemPrice,
|
||||
),
|
||||
availability: this.compareDeliveryAndCatalogPrice(updateAvailability, orderType, shoppingCartItemPrice),
|
||||
},
|
||||
})
|
||||
.toPromise();
|
||||
@@ -562,15 +483,8 @@ export class CheckoutReviewComponent
|
||||
}
|
||||
|
||||
// Bei unbekannten Kunden und DIG Bestellung findet ein Vergleich der Preise statt
|
||||
compareDeliveryAndCatalogPrice(
|
||||
availability: AvailabilityDTO,
|
||||
orderType: string,
|
||||
shoppingCartItemPrice: number,
|
||||
) {
|
||||
if (
|
||||
["Versand", "DIG-Versand"].includes(orderType) &&
|
||||
shoppingCartItemPrice < availability?.price?.value?.value
|
||||
) {
|
||||
compareDeliveryAndCatalogPrice(availability: AvailabilityDTO, orderType: string, shoppingCartItemPrice: number) {
|
||||
if (['Versand', 'DIG-Versand'].includes(orderType) && shoppingCartItemPrice < availability?.price?.value?.value) {
|
||||
return {
|
||||
...availability,
|
||||
price: {
|
||||
@@ -593,10 +507,7 @@ export class CheckoutReviewComponent
|
||||
.pipe(
|
||||
first(),
|
||||
switchMap((customerFeatures) => {
|
||||
return this.domainCheckoutService.canSetCustomer({
|
||||
processId,
|
||||
customerFeatures,
|
||||
});
|
||||
return this.domainCheckoutService.canSetCustomer({ processId, customerFeatures });
|
||||
}),
|
||||
)
|
||||
.toPromise();
|
||||
@@ -613,31 +524,24 @@ export class CheckoutReviewComponent
|
||||
this._purchaseOptionsModalService.open({
|
||||
processId: this.applicationService.activatedProcessId,
|
||||
items: shoppingCartItems,
|
||||
type: "update",
|
||||
type: 'update',
|
||||
});
|
||||
}
|
||||
|
||||
async changeAddress() {
|
||||
const processId = this.applicationService.activatedProcessId;
|
||||
const customer = await this.domainCheckoutService
|
||||
.getBuyer({ processId })
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
const customer = await this.domainCheckoutService.getBuyer({ processId }).pipe(first()).toPromise();
|
||||
if (!customer) {
|
||||
this.navigateToCustomerSearch(processId);
|
||||
return;
|
||||
}
|
||||
const customerId = customer.source;
|
||||
const nav = this._customerSearchNavigation.detailsRoute({
|
||||
processId,
|
||||
customerId,
|
||||
});
|
||||
const nav = this._customerSearchNavigation.detailsRoute({ processId, customerId });
|
||||
this.router.navigate(nav.path);
|
||||
}
|
||||
|
||||
async order() {
|
||||
const shoppingCartItemsWithoutOrderType =
|
||||
await this.shoppingCartItemsWithoutOrderType$.pipe(first()).toPromise();
|
||||
const shoppingCartItemsWithoutOrderType = await this.shoppingCartItemsWithoutOrderType$.pipe(first()).toPromise();
|
||||
|
||||
if (shoppingCartItemsWithoutOrderType?.length > 0) {
|
||||
this.showPurchasingListModal(shoppingCartItemsWithoutOrderType);
|
||||
@@ -645,10 +549,7 @@ export class CheckoutReviewComponent
|
||||
}
|
||||
|
||||
const processId = this.applicationService.activatedProcessId;
|
||||
const customer = await this.domainCheckoutService
|
||||
.getBuyer({ processId })
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
const customer = await this.domainCheckoutService.getBuyer({ processId }).pipe(first()).toPromise();
|
||||
if (!customer) {
|
||||
this.navigateToCustomerSearch(processId);
|
||||
} else {
|
||||
@@ -656,42 +557,33 @@ export class CheckoutReviewComponent
|
||||
this.showOrderButtonSpinner = true;
|
||||
// Ticket #3287 Um nur E-Mail und SMS Benachrichtigungen zu setzen und um alle anderen Benachrichtigungskanäle wie z.B. Brief zu deaktivieren
|
||||
await this._store.onNotificationChange();
|
||||
const orders = await this.domainCheckoutService
|
||||
.completeCheckout({ processId })
|
||||
.toPromise();
|
||||
const orderIds = orders.map((order) => order.id).join(",");
|
||||
const orders = await this.domainCheckoutService.completeCheckout({ processId }).toPromise();
|
||||
const orderIds = orders.map((order) => order.id).join(',');
|
||||
this._store.orderCompleted.next();
|
||||
await this.patchProcess(processId);
|
||||
await this._navigationService
|
||||
.getCheckoutSummaryPath({ processId, orderIds })
|
||||
.navigate();
|
||||
await this._navigationService.getCheckoutSummaryPath({ processId, orderIds }).navigate();
|
||||
} catch (error) {
|
||||
const response = error?.error;
|
||||
let message: string = response?.message ?? "";
|
||||
let message: string = response?.message ?? '';
|
||||
|
||||
if (
|
||||
response?.invalidProperties &&
|
||||
Object.values(response?.invalidProperties)?.length
|
||||
) {
|
||||
message += `\n${Object.values(response.invalidProperties).join("\n")}`;
|
||||
if (response?.invalidProperties && Object.values(response?.invalidProperties)?.length) {
|
||||
message += `\n${Object.values(response.invalidProperties).join('\n')}`;
|
||||
}
|
||||
|
||||
if (message?.length) {
|
||||
this.uiModal.open({
|
||||
content: UiMessageModalComponent,
|
||||
title: "Hinweis",
|
||||
title: 'Hinweis',
|
||||
data: { message: message.trim() },
|
||||
});
|
||||
} else if (error) {
|
||||
this.uiModal.error("Fehler beim abschließen der Bestellung", error);
|
||||
this.uiModal.error('Fehler beim abschließen der Bestellung', error);
|
||||
}
|
||||
|
||||
if (error.status === 409) {
|
||||
this._store.orderCompleted.next();
|
||||
await this.patchProcess(processId);
|
||||
await this._navigationService
|
||||
.getCheckoutSummaryPath({ processId })
|
||||
.navigate();
|
||||
await this._navigationService.getCheckoutSummaryPath({ processId }).navigate();
|
||||
}
|
||||
} finally {
|
||||
this.showOrderButtonSpinner = false;
|
||||
@@ -701,14 +593,11 @@ export class CheckoutReviewComponent
|
||||
}
|
||||
|
||||
async patchProcess(processId: number) {
|
||||
const process = await this.applicationService
|
||||
.getProcessById$(processId)
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
const process = await this.applicationService.getProcessById$(processId).pipe(first()).toPromise();
|
||||
if (process) {
|
||||
this.applicationService.patchProcess(process.id, {
|
||||
name: `${process.name} Bestellbestätigung`,
|
||||
type: "cart-checkout",
|
||||
type: 'cart-checkout',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,61 +2,56 @@
|
||||
Überprüfen Sie die Details.
|
||||
</h1>
|
||||
|
||||
@if (buyer$ | async; as buyer) {
|
||||
@if (!(showAddresses$ | async)) {
|
||||
<div class="flex flex-row items-start justify-between p-5">
|
||||
<div class="flex flex-row flex-wrap pr-4">
|
||||
@if (getNameFromBuyer(buyer); as name) {
|
||||
<div class="mr-3">{{ name.label }}</div>
|
||||
<div class="font-bold">{{ name.value }}</div>
|
||||
}
|
||||
</div>
|
||||
<button (click)="changeAddress()" class="text-p1 font-bold text-[#F70400]">Ändern</button>
|
||||
<ng-container *ngIf="buyer$ | async; let buyer">
|
||||
<div *ngIf="!(showAddresses$ | async)" class="flex flex-row items-start justify-between p-5">
|
||||
<div class="flex flex-row flex-wrap pr-4">
|
||||
<ng-container *ngIf="getNameFromBuyer(buyer); let name">
|
||||
<div class="mr-3">{{ name.label }}</div>
|
||||
<div class="font-bold">{{ name.value }}</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@if (showNotificationChannels$ | async) {
|
||||
@if (control) {
|
||||
<form class="pb-4" [formGroup]="control">
|
||||
<shared-notification-channel-control
|
||||
[communicationDetails]="communicationDetails$ | async"
|
||||
(channelActionEvent)="updateNotifications($event)"
|
||||
[channelActionName]="'Speichern'"
|
||||
[channelActionLoading]="notificationChannelLoading$ | async"
|
||||
formGroupName="notificationChannel"
|
||||
></shared-notification-channel-control>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
<button (click)="changeAddress()" class="text-p1 font-bold text-[#F70400]">Ändern</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@if (payer$ | async; as payer) {
|
||||
@if (showAddresses$ | async) {
|
||||
<div class="flex flex-row items-start justify-between p-5 pt-0">
|
||||
<div class="flex flex-row flex-wrap pr-4" data-address-type="Rechnungsadresse" data-which="Rechnungsadresse">
|
||||
<div class="mr-3" data-what="title">Rechnungsadresse</div>
|
||||
<div class="font-bold" data-what="address">
|
||||
{{ payer | payerAddress }}
|
||||
</div>
|
||||
<ng-container *ngIf="showNotificationChannels$ | async">
|
||||
<form class="pb-4" *ngIf="control" [formGroup]="control">
|
||||
<shared-notification-channel-control
|
||||
[communicationDetails]="communicationDetails$ | async"
|
||||
(channelActionEvent)="updateNotifications($event)"
|
||||
[channelActionName]="'Speichern'"
|
||||
[channelActionLoading]="notificationChannelLoading$ | async"
|
||||
formGroupName="notificationChannel"
|
||||
></shared-notification-channel-control>
|
||||
</form>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="payer$ | async; let payer">
|
||||
<div *ngIf="showAddresses$ | async" class="flex flex-row items-start justify-between p-5 pt-0">
|
||||
<div class="flex flex-row flex-wrap pr-4" data-address-type="Rechnungsadresse" data-which="Rechnungsadresse">
|
||||
<div class="mr-3" data-what="title">Rechnungsadresse</div>
|
||||
<div class="font-bold" data-what="address">
|
||||
{{ payer | payerAddress }}
|
||||
</div>
|
||||
<button (click)="changeAddress()" class="text-p1 font-bold text-[#F70400]">Ändern</button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@if (payer$ | async; as payer) {
|
||||
@if (showAddresses$ | async) {
|
||||
<div class="flex flex-row items-start justify-between px-5">
|
||||
<div class="flex flex-row flex-wrap pr-4" data-address-type="Lieferadresse" data-which="Lieferadresse">
|
||||
<div class="mr-3" data-what="title">Lieferadresse</div>
|
||||
<div class="font-bold" data-what="address">
|
||||
{{ shippingAddress$ | async | shippingAddress }}
|
||||
</div>
|
||||
<button (click)="changeAddress()" class="text-p1 font-bold text-[#F70400]">Ändern</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="payer$ | async; let payer">
|
||||
<div *ngIf="showAddresses$ | async" class="flex flex-row items-start justify-between px-5">
|
||||
<div class="flex flex-row flex-wrap pr-4" data-address-type="Lieferadresse" data-which="Lieferadresse">
|
||||
<div class="mr-3" data-what="title">Lieferadresse</div>
|
||||
<div class="font-bold" data-what="address">
|
||||
{{ shippingAddress$ | async | shippingAddress }}
|
||||
</div>
|
||||
<button (click)="changeAddress()" class="text-p1 font-bold text-[#F70400]">Ändern</button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
<button (click)="changeAddress()" class="text-p1 font-bold text-[#F70400]">Ändern</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<page-special-comment
|
||||
class="mb-6 mt-4"
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
<div class="item-thumbnail">
|
||||
<a [routerLink]="productSearchDetailsPath" [queryParams]="{ main_qs: item?.product?.ean }">
|
||||
@if (item?.product?.ean | productImage; as thumbnailUrl) {
|
||||
<img loading="lazy" [src]="thumbnailUrl" [alt]="item?.product?.name" />
|
||||
}
|
||||
<img loading="lazy" *ngIf="item?.product?.ean | productImage; let thumbnailUrl" [src]="thumbnailUrl" [alt]="item?.product?.name" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="item-contributors">
|
||||
@for (contributor of contributors$ | async; track contributor; let last = $last) {
|
||||
<a
|
||||
[routerLink]="productSearchResultsPath"
|
||||
[queryParams]="{ main_qs: contributor, main_author: 'author' }"
|
||||
(click)="$event?.stopPropagation()"
|
||||
>
|
||||
{{ contributor }}{{ last ? '' : ';' }}
|
||||
</a>
|
||||
}
|
||||
<a
|
||||
*ngFor="let contributor of contributors$ | async; let last = last"
|
||||
[routerLink]="productSearchResultsPath"
|
||||
[queryParams]="{ main_qs: contributor, main_author: 'author' }"
|
||||
(click)="$event?.stopPropagation()"
|
||||
>
|
||||
{{ contributor }}{{ last ? '' : ';' }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -24,106 +21,86 @@
|
||||
[class.text-p1]="item?.product?.name?.length >= 50 || !isTablet"
|
||||
[class.text-p2]="item?.product?.name?.length >= 60 && isTablet"
|
||||
[class.text-p3]="item?.product?.name?.length >= 100"
|
||||
>
|
||||
>
|
||||
<a [routerLink]="productSearchDetailsPath" [queryParams]="{ main_qs: item?.product?.ean }">{{ item?.product?.name }}</a>
|
||||
</div>
|
||||
|
||||
@if (item?.product?.format && item?.product?.formatDetail) {
|
||||
<div class="item-format">
|
||||
@if (item?.product?.format !== '--') {
|
||||
<img
|
||||
src="assets/images/Icon_{{ item?.product?.format }}.svg"
|
||||
[alt]="item?.product?.formatDetail"
|
||||
/>
|
||||
}
|
||||
{{ item?.product?.formatDetail }}
|
||||
</div>
|
||||
}
|
||||
<div class="item-format" *ngIf="item?.product?.format && item?.product?.formatDetail">
|
||||
<img
|
||||
*ngIf="item?.product?.format !== '--'"
|
||||
src="assets/images/Icon_{{ item?.product?.format }}.svg"
|
||||
[alt]="item?.product?.formatDetail"
|
||||
/>
|
||||
{{ item?.product?.formatDetail }}
|
||||
</div>
|
||||
|
||||
<div class="item-info text-p2">
|
||||
<div class="mb-1">{{ item?.product?.manufacturer | substr: 25 }} | {{ item?.product?.ean }}</div>
|
||||
<div class="mb-1">
|
||||
{{ item?.product?.volume }}
|
||||
@if (item?.product?.volume && item?.product?.publicationDate) {
|
||||
<span>|</span>
|
||||
}
|
||||
<span *ngIf="item?.product?.volume && item?.product?.publicationDate">|</span>
|
||||
{{ item?.product?.publicationDate | date }}
|
||||
</div>
|
||||
@if (notAvailable$ | async) {
|
||||
<div>
|
||||
<span class="text-brand item-date">Nicht verfügbar</span>
|
||||
<div *ngIf="notAvailable$ | async">
|
||||
<span class="text-brand item-date">Nicht verfügbar</span>
|
||||
</div>
|
||||
|
||||
<shared-skeleton-loader class="w-40" *ngIf="refreshingAvailabilit$ | async; else avaTmplt"></shared-skeleton-loader>
|
||||
|
||||
<ng-template #avaTmplt>
|
||||
<div class="item-date" [class.availability-changed]="estimatedShippingDateChanged$ | async" *ngIf="orderType === 'Abholung'">
|
||||
Abholung ab {{ item?.availability?.estimatedShippingDate | date }}
|
||||
</div>
|
||||
}
|
||||
<div
|
||||
class="item-date"
|
||||
[class.availability-changed]="estimatedShippingDateChanged$ | async"
|
||||
*ngIf="orderType === 'Versand' || orderType === 'B2B-Versand' || orderType === 'DIG-Versand'"
|
||||
>
|
||||
<ng-container *ngIf="item?.availability?.estimatedDelivery; else estimatedShippingDate">
|
||||
Zustellung zwischen {{ (item?.availability?.estimatedDelivery?.start | date: 'EEE, dd.MM.')?.replace('.', '') }}
|
||||
und
|
||||
{{ (item?.availability?.estimatedDelivery?.stop | date: 'EEE, dd.MM.')?.replace('.', '') }}
|
||||
</ng-container>
|
||||
<ng-template #estimatedShippingDate>Versand {{ item?.availability?.estimatedShippingDate | date }}</ng-template>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@if (refreshingAvailabilit$ | async) {
|
||||
<shared-skeleton-loader class="w-40"></shared-skeleton-loader>
|
||||
} @else {
|
||||
@if (orderType === 'Abholung') {
|
||||
<div class="item-date" [class.availability-changed]="estimatedShippingDateChanged$ | async">
|
||||
Abholung ab {{ item?.availability?.estimatedShippingDate | date }}
|
||||
</div>
|
||||
}
|
||||
@if (orderType === 'Versand' || orderType === 'B2B-Versand' || orderType === 'DIG-Versand') {
|
||||
<div
|
||||
class="item-date"
|
||||
[class.availability-changed]="estimatedShippingDateChanged$ | async"
|
||||
>
|
||||
@if (item?.availability?.estimatedDelivery) {
|
||||
Zustellung zwischen {{ (item?.availability?.estimatedDelivery?.start | date: 'EEE, dd.MM.')?.replace('.', '') }}
|
||||
und
|
||||
{{ (item?.availability?.estimatedDelivery?.stop | date: 'EEE, dd.MM.')?.replace('.', '') }}
|
||||
} @else {
|
||||
Versand {{ item?.availability?.estimatedShippingDate | date }}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@if (olaError$ | async) {
|
||||
<div class="item-availability-message">Artikel nicht verfügbar</div>
|
||||
}
|
||||
<div class="item-availability-message" *ngIf="olaError$ | async">Artikel nicht verfügbar</div>
|
||||
</div>
|
||||
|
||||
<div class="item-price-stock flex flex-col">
|
||||
<div class="text-p2 font-bold">{{ item?.availability?.price?.value?.value | currency: 'EUR' : 'code' }}</div>
|
||||
<div class="text-p2 font-normal">
|
||||
@if (!(isDummy$ | async)) {
|
||||
<ui-quantity-dropdown
|
||||
[ngModel]="item?.quantity"
|
||||
(ngModelChange)="onChangeQuantity($event)"
|
||||
[showSpinner]="(loadingOnQuantityChangeById$ | async) === item?.id"
|
||||
[disabled]="(loadingOnItemChangeById$ | async) === item?.id"
|
||||
[range]="quantityRange$ | async"
|
||||
></ui-quantity-dropdown>
|
||||
} @else {
|
||||
<ui-quantity-dropdown
|
||||
*ngIf="!(isDummy$ | async); else quantityDummy"
|
||||
[ngModel]="item?.quantity"
|
||||
(ngModelChange)="onChangeQuantity($event)"
|
||||
[showSpinner]="(loadingOnQuantityChangeById$ | async) === item?.id"
|
||||
[disabled]="(loadingOnItemChangeById$ | async) === item?.id"
|
||||
[range]="quantityRange$ | async"
|
||||
></ui-quantity-dropdown>
|
||||
<ng-template #quantityDummy>
|
||||
<div class="mt-2">{{ item?.quantity }}x</div>
|
||||
}
|
||||
</ng-template>
|
||||
</div>
|
||||
<div class="quantity-error" *ngIf="quantityError">
|
||||
{{ quantityError }}
|
||||
</div>
|
||||
@if (quantityError) {
|
||||
<div class="quantity-error">
|
||||
{{ quantityError }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (orderType !== 'Download') {
|
||||
<div class="actions">
|
||||
@if (!(hasOrderType$ | async)) {
|
||||
<button
|
||||
[disabled]="(loadingOnQuantityChangeById$ | async) === item?.id || (loadingOnItemChangeById$ | async) === item?.id"
|
||||
(click)="onChangeItem()"
|
||||
>
|
||||
<ui-spinner [show]="(loadingOnItemChangeById$ | async) === item?.id">Lieferweg auswählen</ui-spinner>
|
||||
</button>
|
||||
}
|
||||
@if (canEdit$ | async) {
|
||||
<button
|
||||
[disabled]="(loadingOnQuantityChangeById$ | async) === item?.id || (loadingOnItemChangeById$ | async) === item?.id"
|
||||
(click)="onChangeItem()"
|
||||
>
|
||||
<ui-spinner [show]="(loadingOnItemChangeById$ | async) === item?.id">Lieferweg ändern</ui-spinner>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div class="actions" *ngIf="orderType !== 'Download'">
|
||||
<button
|
||||
[disabled]="(loadingOnQuantityChangeById$ | async) === item?.id || (loadingOnItemChangeById$ | async) === item?.id"
|
||||
(click)="onChangeItem()"
|
||||
*ngIf="!(hasOrderType$ | async)"
|
||||
>
|
||||
<ui-spinner [show]="(loadingOnItemChangeById$ | async) === item?.id">Lieferweg auswählen</ui-spinner>
|
||||
</button>
|
||||
<button
|
||||
[disabled]="(loadingOnQuantityChangeById$ | async) === item?.id || (loadingOnItemChangeById$ | async) === item?.id"
|
||||
(click)="onChangeItem()"
|
||||
*ngIf="canEdit$ | async"
|
||||
>
|
||||
<ui-spinner [show]="(loadingOnItemChangeById$ | async) === item?.id">Lieferweg ändern</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -24,15 +24,11 @@
|
||||
></textarea>
|
||||
|
||||
<div class="comment-actions py-4">
|
||||
@if (!disabled && !!value) {
|
||||
<button type="reset" class="clear pl-4" (click)="clear(); triggerResize()">
|
||||
<shared-icon icon="close" [size]="24"></shared-icon>
|
||||
</button>
|
||||
}
|
||||
<button type="reset" class="clear pl-4" *ngIf="!disabled && !!value" (click)="clear(); triggerResize()">
|
||||
<shared-icon icon="close" [size]="24"></shared-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!(hasPayer || hasBuyer)) {
|
||||
<div class="text-p3">Zur Info: Sie haben dem Warenkorb noch keinen Kunden hinzugefügt.</div>
|
||||
}
|
||||
<div *ngIf="!(hasPayer || hasBuyer)" class="text-p3">Zur Info: Sie haben dem Warenkorb noch keinen Kunden hinzugefügt.</div>
|
||||
</div>
|
||||
|
||||
@@ -7,63 +7,66 @@
|
||||
<h1 class="text-center text-h2 my-1 font-bold">Bestellbestätigung</h1>
|
||||
<p class="text-center text-p1 mb-10">Nachfolgend erhalten Sie die Übersicht Ihrer Bestellung.</p>
|
||||
|
||||
@for (displayOrder of displayOrders$ | async; track displayOrder; let i = $index; let orderLast = $last) {
|
||||
@if (i === 0) {
|
||||
<ng-container *ngFor="let displayOrder of displayOrders$ | async; let i = index; let orderLast = last">
|
||||
<ng-container *ngIf="i === 0">
|
||||
<div class="flex flex-row items-center bg-white shadow-card min-h-[3.3125rem]">
|
||||
<div class="text-h3 font-bold px-5 py-[0.875rem]">
|
||||
{{ displayOrder?.buyer | buyerName }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
}
|
||||
</ng-container>
|
||||
|
||||
<div class="flex flex-row items-center bg-[#F5F7FA] min-h-[3.3125rem]">
|
||||
<div class="flex flex-row items-center justify-center px-5 py-[0.875rem]">
|
||||
@if ((displayOrder?.items)[0]?.features?.orderType !== 'Dummy') {
|
||||
<shared-icon
|
||||
class="mr-2"
|
||||
[size]="(displayOrder?.items)[0]?.features?.orderType === 'B2B-Versand' ? 36 : 24"
|
||||
[icon]="(displayOrder?.items)[0]?.features?.orderType"
|
||||
></shared-icon>
|
||||
}
|
||||
<shared-icon
|
||||
*ngIf="(displayOrder?.items)[0]?.features?.orderType !== 'Dummy'"
|
||||
class="mr-2"
|
||||
[size]="(displayOrder?.items)[0]?.features?.orderType === 'B2B-Versand' ? 36 : 24"
|
||||
[icon]="(displayOrder?.items)[0]?.features?.orderType"
|
||||
></shared-icon>
|
||||
<p class="text-p1 font-bold mr-3">{{ (displayOrder?.items)[0]?.features?.orderType }}</p>
|
||||
@if (
|
||||
(displayOrder?.items)[0]?.features?.orderType === 'Abholung' || (displayOrder?.items)[0]?.features?.orderType === 'Rücklage') {
|
||||
<div
|
||||
>
|
||||
{{ displayOrder.targetBranch?.name }}, {{ displayOrder.targetBranch | branchAddress }}
|
||||
</div>
|
||||
} @else {
|
||||
<div
|
||||
*ngIf="
|
||||
(displayOrder?.items)[0]?.features?.orderType === 'Abholung' || (displayOrder?.items)[0]?.features?.orderType === 'Rücklage';
|
||||
else shippingAddress
|
||||
"
|
||||
>
|
||||
{{ displayOrder.targetBranch?.name }}, {{ displayOrder.targetBranch | branchAddress }}
|
||||
</div>
|
||||
<ng-template #shippingAddress>
|
||||
{{ displayOrder.shippingAddress | branchAddress }}
|
||||
}
|
||||
@if ((displayOrder?.items)[0]?.features?.orderType === 'Download') {
|
||||
<div>
|
||||
| {{ displayOrder.buyer?.communicationDetails?.email }}
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
<div *ngIf="(displayOrder?.items)[0]?.features?.orderType === 'Download'">
|
||||
| {{ displayOrder.buyer?.communicationDetails?.email }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="flex flex-row justify-between items-center">
|
||||
<div class="flex flex-col px-5 py-4 bg-white" [attr.data-order-type]="(displayOrder?.items)[0]?.features?.orderType">
|
||||
<div class="flex flex-row justify-between items-center mb-[0.375rem]">
|
||||
<div class="flex flex-row">
|
||||
<span class="w-32">Vorgangs-ID</span>
|
||||
@if (customer$ | async; as customer) {
|
||||
@if (customer$ | async; as customer) {
|
||||
<a
|
||||
data-which="Vorgangs-ID"
|
||||
data-what="link"
|
||||
class="font-bold text-[#0556B4] no-underline"
|
||||
[routerLink]="['/kunde', processId, 'customer', 'search', customer?.id, 'orders', displayOrder.id]"
|
||||
[queryParams]="{ main_qs: customer?.customerNumber, filter_customertype: '' }"
|
||||
>
|
||||
{{ displayOrder.orderNumber }}
|
||||
</a>
|
||||
}
|
||||
}
|
||||
<ng-container *ngIf="customer$ | async; let customer">
|
||||
<a
|
||||
data-which="Vorgangs-ID"
|
||||
data-what="link"
|
||||
*ngIf="customer$ | async; let customer"
|
||||
class="font-bold text-[#0556B4] no-underline"
|
||||
[routerLink]="['/kunde', processId, 'customer', 'search', customer?.id, 'orders', displayOrder.id]"
|
||||
[queryParams]="{ main_qs: customer?.customerNumber, filter_customertype: '' }"
|
||||
>
|
||||
{{ displayOrder.orderNumber }}
|
||||
</a>
|
||||
</ng-container>
|
||||
<ui-spinner class="text-[#0556B4] h-4 w-4" [show]="!(customer$ | async)"></ui-spinner>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row justify-between items-center">
|
||||
<div class="flex flex-row">
|
||||
<span class="w-32">Bestelldatum</span>
|
||||
@@ -71,13 +74,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mr-4">
|
||||
<button
|
||||
(click)="expanded[i] = !expanded[i]"
|
||||
type="button"
|
||||
class="text-[#0556B4] font-bold flex flex-row items-center justify-center"
|
||||
[class.flex-row-reverse]="!expanded[i]"
|
||||
>
|
||||
>
|
||||
<shared-icon
|
||||
class="mr-1"
|
||||
icon="arrow-back"
|
||||
@@ -89,139 +93,135 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@for (order of displayOrder.items; track order; let last = $last) {
|
||||
@if (expanded[i]) {
|
||||
|
||||
<ng-container *ngFor="let order of displayOrder.items; let last = last">
|
||||
<ng-container *ngIf="expanded[i]">
|
||||
<div
|
||||
class="page-checkout-summary__items-tablet px-5 pb-[1.875rem] bg-white"
|
||||
[class.page-checkout-summary__items]="isDesktop$ | async"
|
||||
[class.last]="last"
|
||||
>
|
||||
>
|
||||
<div class="page-checkout-summary__items-thumbnail flex flex-row">
|
||||
<a [routerLink]="getProductSearchDetailsPath(order?.product?.ean)" [queryParams]="getProductSearchDetailsQueryParams(order)">
|
||||
<img class="w-[3.125rem] max-h-20 mr-2" [src]="order.product?.ean | productImage: 195 : 315 : true" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="page-checkout-summary__items-title whitespace-nowrap overflow-ellipsis overflow-hidden">
|
||||
<a
|
||||
class="font-bold no-underline text-[#0556B4]"
|
||||
[routerLink]="getProductSearchDetailsPath(order?.product?.ean)"
|
||||
[queryParams]="getProductSearchDetailsQueryParams(order)"
|
||||
>
|
||||
>
|
||||
{{ order?.product?.name }}
|
||||
</a>
|
||||
</div>
|
||||
@if ((order?.subsetItems)[0]; as subsetItem) {
|
||||
<div class="page-checkout-summary__items-ssc">
|
||||
<span class="mr-2">{{ subsetItem.supplierName }}</span>
|
||||
@if (subsetItem?.ssc && subsetItem?.sscText) {
|
||||
<span class="font-bold border-l border-black pl-2">
|
||||
{{ subsetItem.ssc }} - {{ subsetItem.sscText }}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="page-checkout-summary__items-ssc" *ngIf="(order?.subsetItems)[0]; let subsetItem">
|
||||
<span class="mr-2">{{ subsetItem.supplierName }}</span>
|
||||
<span *ngIf="subsetItem?.ssc && subsetItem?.sscText" class="font-bold border-l border-black pl-2">
|
||||
{{ subsetItem.ssc }} - {{ subsetItem.sscText }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="page-checkout-summary__items-quantity font-bold justify-self-end">
|
||||
<span>{{ order.quantity }}x</span>
|
||||
</div>
|
||||
|
||||
<div class="page-checkout-summary__items-price font-bold justify-self-end">
|
||||
<span>{{ order.price?.value?.value | currency: ' ' }} {{ order.price?.value?.currency }}</span>
|
||||
</div>
|
||||
|
||||
<div class="page-checkout-summary__items-delivery product-details">
|
||||
<div class="delivery-row">
|
||||
@switch (order?.features?.orderType) {
|
||||
@case ('Abholung') {
|
||||
<div class="delivery-row" [ngSwitch]="order?.features?.orderType">
|
||||
<ng-container *ngSwitchCase="'Abholung'">
|
||||
<span class="order-type">
|
||||
Abholung ab {{ (order?.subsetItems)[0]?.estimatedShippingDate | date }}
|
||||
<ng-container [ngTemplateOutlet]="abholfrist" [ngTemplateOutletContext]="{ order: order }"></ng-container>
|
||||
</span>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'Rücklage'">
|
||||
<span class="order-type">
|
||||
{{ order?.features?.orderType }}
|
||||
<ng-container [ngTemplateOutlet]="abholfrist" [ngTemplateOutletContext]="{ order: order }"></ng-container>
|
||||
</span>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="['Versand', 'B2B-Versand', 'DIG-Versand'].indexOf(order?.features?.orderType) > -1">
|
||||
<ng-container *ngIf="(order?.subsetItems)[0]?.estimatedDelivery; else estimatedShippingDate">
|
||||
<span class="order-type">
|
||||
Abholung ab {{ (order?.subsetItems)[0]?.estimatedShippingDate | date }}
|
||||
<ng-container [ngTemplateOutlet]="abholfrist" [ngTemplateOutletContext]="{ order: order }"></ng-container>
|
||||
Zustellung zwischen
|
||||
{{ ((order?.subsetItems)[0]?.estimatedDelivery?.start | date: 'EEE, dd.MM.')?.replace('.', '') }} und
|
||||
{{ ((order?.subsetItems)[0]?.estimatedDelivery?.stop | date: 'EEE, dd.MM.')?.replace('.', '') }}
|
||||
</span>
|
||||
}
|
||||
@case ('Rücklage') {
|
||||
<span class="order-type">
|
||||
{{ order?.features?.orderType }}
|
||||
<ng-container [ngTemplateOutlet]="abholfrist" [ngTemplateOutletContext]="{ order: order }"></ng-container>
|
||||
</span>
|
||||
}
|
||||
@case (['Versand', 'B2B-Versand', 'DIG-Versand'].indexOf(order?.features?.orderType) > -1) {
|
||||
@if ((order?.subsetItems)[0]?.estimatedDelivery) {
|
||||
<span class="order-type">
|
||||
Zustellung zwischen
|
||||
{{ ((order?.subsetItems)[0]?.estimatedDelivery?.start | date: 'EEE, dd.MM.')?.replace('.', '') }} und
|
||||
{{ ((order?.subsetItems)[0]?.estimatedDelivery?.stop | date: 'EEE, dd.MM.')?.replace('.', '') }}
|
||||
</span>
|
||||
} @else {
|
||||
<span class="order-type">Versanddatum {{ (order?.subsetItems)[0]?.estimatedShippingDate | date }}</span>
|
||||
}
|
||||
}
|
||||
@default {
|
||||
<span class="order-type">{{ order?.features?.orderType }}</span>
|
||||
}
|
||||
}
|
||||
</ng-container>
|
||||
<ng-template #estimatedShippingDate>
|
||||
<span class="order-type">Versanddatum {{ (order?.subsetItems)[0]?.estimatedShippingDate | date }}</span>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchDefault>
|
||||
<span class="order-type">{{ order?.features?.orderType }}</span>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (last) {
|
||||
<hr />
|
||||
}
|
||||
}
|
||||
@if (orderLast) {
|
||||
</ng-container>
|
||||
|
||||
<hr *ngIf="last" />
|
||||
</ng-container>
|
||||
<ng-container *ngIf="orderLast">
|
||||
<div class="flex flex-row justify-between items-center min-h-[3.3125rem] bg-white px-5 py-4 rounded-b">
|
||||
@if (totalReadingPoints$ | async; as totalReadingPoints) {
|
||||
<span class="text-p2 font-bold">
|
||||
{{ totalItemCount$ | async }} Artikel | {{ totalReadingPoints }} Lesepunkte
|
||||
</span>
|
||||
}
|
||||
<span *ngIf="totalReadingPoints$ | async; let totalReadingPoints" class="text-p2 font-bold">
|
||||
{{ totalItemCount$ | async }} Artikel | {{ totalReadingPoints }} Lesepunkte
|
||||
</span>
|
||||
|
||||
<div class="flex flex-row items-center justify-center">
|
||||
<div class="text-p1 font-bold flex flex-row items-center">
|
||||
<div class="mr-1">Gesamtsumme {{ totalPrice$ | async | currency: ' ' }} {{ totalPriceCurrency$ | async }}</div>
|
||||
</div>
|
||||
@if ((takeNowOrders$ | async)?.length === 1 && (isB2BCustomer$ | async)) {
|
||||
<div
|
||||
class="bg-brand text-white font-bold text-p1 outline-none border-none rounded-full px-6 py-3 ml-2"
|
||||
>
|
||||
<button class="cta-goods-out" (click)="navigateToShelfOut()">Zur Warenausgabe</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div
|
||||
class="bg-brand text-white font-bold text-p1 outline-none border-none rounded-full px-6 py-3 ml-2"
|
||||
*ngIf="(takeNowOrders$ | async)?.length === 1 && (isB2BCustomer$ | async)"
|
||||
>
|
||||
<button class="cta-goods-out" (click)="navigateToShelfOut()">Zur Warenausgabe</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #abholfrist let-order="order">
|
||||
@if (!(updatingPreferredPickUpDate$ | async)[(order?.subsetItems)[0].id]) {
|
||||
<div class="inline-flex">
|
||||
<button [uiOverlayTrigger]="deadlineDatepicker" #deadlineDatepickerTrigger="uiOverlayTrigger" class="flex flex-row items-center">
|
||||
<span class="mx-[0.625rem] font-normal">bis</span>
|
||||
<strong class="border-r border-[#AEB7C1] pr-4">
|
||||
{{ ((order?.subsetItems)[0]?.preferredPickUpDate | date: 'dd.MM.yy') || 'TT.MM.JJJJ' }}
|
||||
</strong>
|
||||
<shared-icon class="text-[#596470] ml-4" [size]="24" icon="isa-calendar"></shared-icon>
|
||||
</button>
|
||||
<ui-datepicker
|
||||
#deadlineDatepicker
|
||||
yPosition="below"
|
||||
xPosition="after"
|
||||
[xOffset]="8"
|
||||
[min]="minDateDatepicker"
|
||||
[disabledDaysOfWeek]="[0]"
|
||||
[(selected)]="selectedDate"
|
||||
<div *ngIf="!(updatingPreferredPickUpDate$ | async)[(order?.subsetItems)[0].id]" class="inline-flex">
|
||||
<button [uiOverlayTrigger]="deadlineDatepicker" #deadlineDatepickerTrigger="uiOverlayTrigger" class="flex flex-row items-center">
|
||||
<span class="mx-[0.625rem] font-normal">bis</span>
|
||||
<strong class="border-r border-[#AEB7C1] pr-4">
|
||||
{{ ((order?.subsetItems)[0]?.preferredPickUpDate | date: 'dd.MM.yy') || 'TT.MM.JJJJ' }}
|
||||
</strong>
|
||||
|
||||
<shared-icon class="text-[#596470] ml-4" [size]="24" icon="isa-calendar"></shared-icon>
|
||||
</button>
|
||||
<ui-datepicker
|
||||
#deadlineDatepicker
|
||||
yPosition="below"
|
||||
xPosition="after"
|
||||
[xOffset]="8"
|
||||
[min]="minDateDatepicker"
|
||||
[disabledDaysOfWeek]="[0]"
|
||||
[(selected)]="selectedDate"
|
||||
>
|
||||
<div #content class="grid grid-flow-row gap-2">
|
||||
<button
|
||||
class="rounded-full font-bold text-white bg-brand py-px-15 px-px-25"
|
||||
(click)="updatePreferredPickUpDate(undefined, selectedDate); deadlineDatepickerTrigger.close()"
|
||||
>
|
||||
<div #content class="grid grid-flow-row gap-2">
|
||||
<button
|
||||
class="rounded-full font-bold text-white bg-brand py-px-15 px-px-25"
|
||||
(click)="updatePreferredPickUpDate(undefined, selectedDate); deadlineDatepickerTrigger.close()"
|
||||
>
|
||||
Für den Warenkorb festlegen
|
||||
</button>
|
||||
</div>
|
||||
</ui-datepicker>
|
||||
</div>
|
||||
}
|
||||
@if (!!(updatingPreferredPickUpDate$ | async)[(order?.subsetItems)[0].id]) {
|
||||
<div class="fetching"></div>
|
||||
}
|
||||
Für den Warenkorb festlegen
|
||||
</button>
|
||||
</div>
|
||||
</ui-datepicker>
|
||||
</div>
|
||||
<div class="fetching" *ngIf="!!(updatingPreferredPickUpDate$ | async)[(order?.subsetItems)[0].id]"></div>
|
||||
</ng-template>
|
||||
|
||||
<div class="relative">
|
||||
@@ -232,18 +232,17 @@
|
||||
type="button"
|
||||
class="px-6 py-2 rounded-full border-2 border-solid border-brand text-brand bg-white font-bold text-lg whitespace-nowrap h-14 flex flex-row items-center justify-center print-button"
|
||||
(click)="printOrderConfirmation()"
|
||||
>
|
||||
>
|
||||
<ui-spinner class="min-h-4 min-w-4" [show]="isPrinting$ | async">Bestellbestätigung drucken</ui-spinner>
|
||||
</button>
|
||||
|
||||
@if (hasAbholung$ | async) {
|
||||
<button
|
||||
type="button"
|
||||
class="px-6 py-2 rounded-full border-2 border-solid border-brand text-brand bg-white font-bold text-lg whitespace-nowrap h-14"
|
||||
(click)="sendOrderConfirmation()"
|
||||
>
|
||||
Bestellbestätigung senden
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
*ngIf="hasAbholung$ | async"
|
||||
type="button"
|
||||
class="px-6 py-2 rounded-full border-2 border-solid border-brand text-brand bg-white font-bold text-lg whitespace-nowrap h-14"
|
||||
(click)="sendOrderConfirmation()"
|
||||
>
|
||||
Bestellbestätigung senden
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,11 +15,7 @@ import { CrmCustomerService } from '@domain/crm';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { DomainOmsService } from '@domain/oms';
|
||||
import { DomainCatalogService } from '@domain/catalog';
|
||||
import {
|
||||
DisplayOrderDTO,
|
||||
DisplayOrderItemDTO,
|
||||
DisplayOrderItemSubsetDTO,
|
||||
} from '@generated/swagger/oms-api';
|
||||
import { DisplayOrderDTO, DisplayOrderItemDTO, DisplayOrderItemSubsetDTO } from '@generated/swagger/oms-api';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { DomainPrinterService } from '@domain/printer';
|
||||
@@ -53,73 +49,45 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
processId = Date.now();
|
||||
selectedDate = this.dateAdapter.today();
|
||||
minDateDatepicker = this.dateAdapter.addCalendarDays(
|
||||
this.dateAdapter.today(),
|
||||
-1,
|
||||
);
|
||||
minDateDatepicker = this.dateAdapter.addCalendarDays(this.dateAdapter.today(), -1);
|
||||
|
||||
updatingPreferredPickUpDate$ = new BehaviorSubject<Record<string, string>>(
|
||||
{},
|
||||
);
|
||||
updatingPreferredPickUpDate$ = new BehaviorSubject<Record<string, string>>({});
|
||||
|
||||
displayOrders$ = combineLatest([
|
||||
this.domainCheckoutService.getOrders(),
|
||||
this._route.params,
|
||||
]).pipe(
|
||||
displayOrders$ = combineLatest([this.domainCheckoutService.getOrders(), this._route.params]).pipe(
|
||||
map(([orders, params]) => {
|
||||
let filteredOrders: DisplayOrderDTO[] = [];
|
||||
if (params?.orderIds) {
|
||||
const orderIds: string[] = params.orderIds.split(',');
|
||||
filteredOrders = orders.filter((order) =>
|
||||
orderIds.find((id) => Number(id) === order.id),
|
||||
);
|
||||
filteredOrders = orders.filter((order) => orderIds.find((id) => Number(id) === order.id));
|
||||
} else {
|
||||
return filteredOrders;
|
||||
}
|
||||
|
||||
// Ticket #4228 Für die korrekte Gruppierung der Items bei gleichem Bestellziel (Aufsplitten von Abholung und Rücklage)
|
||||
const ordersWithMultipleFeatures = filteredOrders.filter((order) =>
|
||||
order.items.find(
|
||||
(item) => item.features.orderType !== order.features.orderType,
|
||||
),
|
||||
order.items.find((item) => item.features.orderType !== order.features.orderType),
|
||||
);
|
||||
|
||||
if (ordersWithMultipleFeatures) {
|
||||
for (let orderWithMultipleFeatures of ordersWithMultipleFeatures) {
|
||||
if (orderWithMultipleFeatures?.items?.length > 1) {
|
||||
const itemsWithOrderFeature =
|
||||
orderWithMultipleFeatures.items.filter(
|
||||
(item) =>
|
||||
item.features.orderType ===
|
||||
orderWithMultipleFeatures.features.orderType,
|
||||
);
|
||||
const itemsWithDifferentOrderFeature =
|
||||
orderWithMultipleFeatures.items.filter(
|
||||
(item) =>
|
||||
item.features.orderType !==
|
||||
orderWithMultipleFeatures.features.orderType,
|
||||
);
|
||||
const itemsWithOrderFeature = orderWithMultipleFeatures.items.filter(
|
||||
(item) => item.features.orderType === orderWithMultipleFeatures.features.orderType,
|
||||
);
|
||||
const itemsWithDifferentOrderFeature = orderWithMultipleFeatures.items.filter(
|
||||
(item) => item.features.orderType !== orderWithMultipleFeatures.features.orderType,
|
||||
);
|
||||
|
||||
filteredOrders = [
|
||||
...filteredOrders.filter(
|
||||
(order) => order.id !== orderWithMultipleFeatures.id,
|
||||
),
|
||||
];
|
||||
filteredOrders = [...filteredOrders.filter((order) => order.id !== orderWithMultipleFeatures.id)];
|
||||
|
||||
if (itemsWithOrderFeature?.length > 0) {
|
||||
filteredOrders = [
|
||||
...filteredOrders,
|
||||
{ ...orderWithMultipleFeatures, items: itemsWithOrderFeature },
|
||||
];
|
||||
filteredOrders = [...filteredOrders, { ...orderWithMultipleFeatures, items: itemsWithOrderFeature }];
|
||||
}
|
||||
|
||||
if (itemsWithDifferentOrderFeature?.length > 0) {
|
||||
filteredOrders = [
|
||||
...filteredOrders,
|
||||
{
|
||||
...orderWithMultipleFeatures,
|
||||
items: itemsWithDifferentOrderFeature,
|
||||
},
|
||||
{ ...orderWithMultipleFeatures, items: itemsWithDifferentOrderFeature },
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -129,9 +97,7 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
return filteredOrders?.map((order) => {
|
||||
return {
|
||||
...order,
|
||||
items: [...order.items]?.sort((a, b) =>
|
||||
a.product?.name.localeCompare(b.product?.name),
|
||||
),
|
||||
items: [...order.items]?.sort((a, b) => a.product?.name.localeCompare(b.product?.name)),
|
||||
};
|
||||
});
|
||||
}),
|
||||
@@ -139,23 +105,14 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
|
||||
hasAbholung$ = this.displayOrders$.pipe(
|
||||
map(
|
||||
(displayOrders) =>
|
||||
displayOrders.filter(
|
||||
(order) => order.features?.orderType === 'Abholung',
|
||||
)?.length > 0,
|
||||
),
|
||||
map((displayOrders) => displayOrders.filter((order) => order.features?.orderType === 'Abholung')?.length > 0),
|
||||
);
|
||||
|
||||
totalItemCount$ = this.displayOrders$.pipe(
|
||||
map((displayOrders) =>
|
||||
displayOrders.reduce(
|
||||
(total, displayOrder) =>
|
||||
total +
|
||||
displayOrder?.items?.reduce(
|
||||
(subTotal, order) => subTotal + order?.quantity,
|
||||
0,
|
||||
),
|
||||
total + displayOrder?.items?.reduce((subTotal, order) => subTotal + order?.quantity, 0),
|
||||
0,
|
||||
),
|
||||
),
|
||||
@@ -164,10 +121,7 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
totalReadingPoints$ = this.displayOrders$.pipe(
|
||||
switchMap((displayOrders) => {
|
||||
const items = displayOrders
|
||||
.reduce<DisplayOrderItemDTO[]>(
|
||||
(items, order) => [...items, ...order.items],
|
||||
[],
|
||||
)
|
||||
.reduce<DisplayOrderItemDTO[]>((items, order) => [...items, ...order.items], [])
|
||||
.map((i) => {
|
||||
if (i?.product?.catalogProductNumber) {
|
||||
return {
|
||||
@@ -181,14 +135,7 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
if (items.length !== 0) {
|
||||
return this.domainCatalogService
|
||||
.getPromotionPoints({ items })
|
||||
.pipe(
|
||||
map((response) =>
|
||||
Object.values(response.result).reduce(
|
||||
(sum, points) => sum + points,
|
||||
0,
|
||||
),
|
||||
),
|
||||
);
|
||||
.pipe(map((response) => Object.values(response.result).reduce((sum, points) => sum + points, 0)));
|
||||
} else {
|
||||
return NEVER;
|
||||
}
|
||||
@@ -200,11 +147,7 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
displayOrders.reduce(
|
||||
(total, displayOrder) =>
|
||||
total +
|
||||
displayOrder?.items?.reduce(
|
||||
(subTotal, order) =>
|
||||
subTotal + order?.price?.value?.value * order.quantity,
|
||||
0,
|
||||
),
|
||||
displayOrder?.items?.reduce((subTotal, order) => subTotal + order?.price?.value?.value * order.quantity, 0),
|
||||
0,
|
||||
),
|
||||
),
|
||||
@@ -219,33 +162,22 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
containsDeliveryOrder$ = this.displayOrders$.pipe(
|
||||
map(
|
||||
(displayOrders) =>
|
||||
displayOrders.filter(
|
||||
(o) =>
|
||||
['Versand', 'B2B-Versand', 'DIG-Versand'].indexOf(
|
||||
o.features?.orderType,
|
||||
) > -1,
|
||||
)?.length > 0,
|
||||
displayOrders.filter((o) => ['Versand', 'B2B-Versand', 'DIG-Versand'].indexOf(o.features?.orderType) > -1)
|
||||
?.length > 0,
|
||||
),
|
||||
);
|
||||
|
||||
customer$ = this.displayOrders$.pipe(
|
||||
switchMap((o) =>
|
||||
this.customerService.getCustomers(o[0].buyerNumber, { take: 5 }),
|
||||
),
|
||||
switchMap((o) => this.customerService.getCustomers(o[0].buyerNumber, { take: 5 })),
|
||||
map((customers) => customers.result[0]),
|
||||
shareReplay(),
|
||||
);
|
||||
|
||||
isB2BCustomer$ = this.customer$.pipe(
|
||||
map((customer) => customer?.features?.find((f) => f.key === 'b2b') != null),
|
||||
);
|
||||
isB2BCustomer$ = this.customer$.pipe(map((customer) => customer?.features?.find((f) => f.key === 'b2b') != null));
|
||||
|
||||
takeNowOrders$ = this.displayOrders$.pipe(
|
||||
map((displayOrders) =>
|
||||
displayOrders.filter(
|
||||
(o) =>
|
||||
o.items.find((oi) => oi.features?.orderType === 'Rücklage') != null,
|
||||
),
|
||||
displayOrders.filter((o) => o.items.find((oi) => oi.features?.orderType === 'Rücklage') != null),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -278,9 +210,7 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
private _cdr: ChangeDetectorRef,
|
||||
) {
|
||||
this.breadcrumb
|
||||
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, [
|
||||
'checkout',
|
||||
])
|
||||
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['checkout'])
|
||||
.pipe(first())
|
||||
.subscribe(async (crumbs) => {
|
||||
for await (const crumb of crumbs) {
|
||||
@@ -334,8 +264,7 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
getProductSearchDetailsQueryParams(item: DisplayOrderItemDTO) {
|
||||
return {
|
||||
main_qs: item?.product?.ean,
|
||||
filter_format:
|
||||
item?.features?.orderType === 'Download' ? 'eb;dl' : undefined,
|
||||
filter_format: item?.features?.orderType === 'Download' ? 'eb;dl' : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -345,14 +274,9 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
try {
|
||||
const items = item ? [item] : await this.getAllOrderItems();
|
||||
const subsetItems = items
|
||||
.filter((item) =>
|
||||
['Rücklage', 'Abholung'].includes(item.features.orderType),
|
||||
)
|
||||
.filter((item) => ['Rücklage', 'Abholung'].includes(item.features.orderType))
|
||||
// .flatMap((item) => item.subsetItems);
|
||||
.reduce<DisplayOrderItemSubsetDTO[]>(
|
||||
(acc, item) => [...acc, ...item.subsetItems],
|
||||
[],
|
||||
);
|
||||
.reduce<DisplayOrderItemSubsetDTO[]>((acc, item) => [...acc, ...item.subsetItems], []);
|
||||
subsetItems.forEach((item) => (data[`${item.id}`] = date?.toISOString()));
|
||||
|
||||
try {
|
||||
@@ -386,10 +310,7 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
|
||||
async getAllOrderItems() {
|
||||
const orders = await this.displayOrders$.pipe(first()).toPromise();
|
||||
return orders.reduce<DisplayOrderItemDTO[]>(
|
||||
(agg, order) => [...agg, ...order.items],
|
||||
[],
|
||||
);
|
||||
return orders.reduce<DisplayOrderItemDTO[]>((agg, order) => [...agg, ...order.items], []);
|
||||
}
|
||||
|
||||
async updateDisplayOrderItem(item: DisplayOrderItemDTO) {
|
||||
@@ -401,16 +322,9 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
if (takeNowOrders.length != 1) return;
|
||||
|
||||
try {
|
||||
await this.router.navigate(
|
||||
this._shelfOutNavigationService.listRoute({ processId: Date.now() })
|
||||
.path,
|
||||
{
|
||||
queryParams: {
|
||||
main_qs: takeNowOrders[0].orderNumber,
|
||||
filter_supplier_id: '16',
|
||||
},
|
||||
},
|
||||
);
|
||||
await this.router.navigate(this._shelfOutNavigationService.listRoute({ processId: Date.now() }).path, {
|
||||
queryParams: { main_qs: takeNowOrders[0].orderNumber, filter_supplier_id: '16' },
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
@@ -430,8 +344,7 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
.pipe(
|
||||
first(),
|
||||
map((printers) => {
|
||||
if (Array.isArray(printers))
|
||||
return printers.find((printer) => printer.selected === true);
|
||||
if (Array.isArray(printers)) return printers.find((printer) => printer.selected === true);
|
||||
}),
|
||||
)
|
||||
.toPromise();
|
||||
@@ -449,16 +362,10 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
const result = await this.domainPrinterService
|
||||
.printOrder({ orderIds: orders.map((o) => o.id), printer })
|
||||
.toPromise();
|
||||
this._toaster.open({
|
||||
type: 'success',
|
||||
message: 'Bestellbestätigung wurde gedruckt',
|
||||
});
|
||||
this._toaster.open({ type: 'success', message: 'Bestellbestätigung wurde gedruckt' });
|
||||
return result;
|
||||
} catch (error) {
|
||||
this._toaster.open({
|
||||
type: 'danger',
|
||||
message: 'Fehler beim Drucken der Bestellbestätigung',
|
||||
});
|
||||
this._toaster.open({ type: 'danger', message: 'Fehler beim Drucken der Bestellbestätigung' });
|
||||
} finally {
|
||||
this.isPrinting$.next(false);
|
||||
}
|
||||
@@ -474,21 +381,12 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
|
||||
} else {
|
||||
try {
|
||||
const result = await this.domainPrinterService
|
||||
.printOrder({
|
||||
orderIds: orders.map((o) => o.id),
|
||||
printer: selectedPrinter.key,
|
||||
})
|
||||
.printOrder({ orderIds: orders.map((o) => o.id), printer: selectedPrinter.key })
|
||||
.toPromise();
|
||||
this._toaster.open({
|
||||
type: 'success',
|
||||
message: 'Bestellbestätigung wurde gedruckt',
|
||||
});
|
||||
this._toaster.open({ type: 'success', message: 'Bestellbestätigung wurde gedruckt' });
|
||||
return result;
|
||||
} catch (error) {
|
||||
this._toaster.open({
|
||||
type: 'danger',
|
||||
message: 'Fehler beim Drucken der Bestellbestätigung',
|
||||
});
|
||||
this._toaster.open({ type: 'danger', message: 'Fehler beim Drucken der Bestellbestätigung' });
|
||||
} finally {
|
||||
this.isPrinting$.next(false);
|
||||
}
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
@if (orderItem$ | async; as orderItem) {
|
||||
<ng-container *ngIf="orderItem$ | async; let orderItem">
|
||||
<div #features class="page-customer-order-details-item__features">
|
||||
@if (orderItem?.features?.prebooked) {
|
||||
<ng-container *ngIf="orderItem?.features?.prebooked">
|
||||
<img [uiOverlayTrigger]="prebookedTooltip" src="/assets/images/tag_icon_preorder.svg" [alt]="orderItem?.features?.prebooked" />
|
||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #prebookedTooltip [closeable]="true">
|
||||
Artikel wird für Sie vorgemerkt.
|
||||
</ui-tooltip>
|
||||
}
|
||||
@if (notificationsSent$ | async; as notificationsSent) {
|
||||
@if (notificationsSent?.NOTIFICATION_EMAIL) {
|
||||
</ng-container>
|
||||
<ng-container *ngIf="notificationsSent$ | async; let notificationsSent">
|
||||
<ng-container *ngIf="notificationsSent?.NOTIFICATION_EMAIL">
|
||||
<img [uiOverlayTrigger]="emailTooltip" src="/assets/images/email_bookmark.svg" />
|
||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #emailTooltip [closeable]="true">
|
||||
Per E-Mail benachrichtigt
|
||||
<br />
|
||||
@for (notification of notificationsSent?.NOTIFICATION_EMAIL; track notification) {
|
||||
<ng-container *ngFor="let notification of notificationsSent?.NOTIFICATION_EMAIL">
|
||||
{{ notification | date: 'dd.MM.yyyy | HH:mm' }} Uhr
|
||||
<br />
|
||||
}
|
||||
</ng-container>
|
||||
</ui-tooltip>
|
||||
}
|
||||
@if (notificationsSent?.NOTIFICATION_SMS) {
|
||||
</ng-container>
|
||||
<ng-container *ngIf="notificationsSent?.NOTIFICATION_SMS">
|
||||
<img [uiOverlayTrigger]="smsTooltip" src="/assets/images/sms_bookmark.svg" />
|
||||
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #smsTooltip [closeable]="true">
|
||||
Per SMS benachrichtigt
|
||||
<br />
|
||||
@for (notification of notificationsSent?.NOTIFICATION_SMS; track notification) {
|
||||
<ng-container *ngFor="let notification of notificationsSent?.NOTIFICATION_SMS">
|
||||
{{ notification | date: 'dd.MM.yyyy | HH:mm' }} Uhr
|
||||
<br />
|
||||
}
|
||||
</ng-container>
|
||||
</ui-tooltip>
|
||||
}
|
||||
}
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="page-customer-order-details-item__item-container">
|
||||
<div class="page-customer-order-details-item__thumbnail">
|
||||
@@ -42,190 +42,169 @@
|
||||
#elementDistance="uiElementDistance"
|
||||
[style.max-width.px]="elementDistance.distanceChange | async"
|
||||
class="flex flex-col"
|
||||
>
|
||||
>
|
||||
<div class="font-normal mb-[0.375rem]">{{ orderItem.product?.contributors }}</div>
|
||||
<div>{{ orderItem.product?.name }}</div>
|
||||
</h3>
|
||||
<div class="history-wrapper flex flex-col items-end justify-center">
|
||||
<button class="cta-history text-p1" (click)="historyClick.emit(orderItem)">Historie</button>
|
||||
@if (selectable$ | async) {
|
||||
<input
|
||||
[ngModel]="selected$ | async"
|
||||
(ngModelChange)="setSelected($event)"
|
||||
class="isa-select-bullet mt-4"
|
||||
type="checkbox"
|
||||
/>
|
||||
}
|
||||
|
||||
<input
|
||||
*ngIf="selectable$ | async"
|
||||
[ngModel]="selected$ | async"
|
||||
(ngModelChange)="setSelected($event)"
|
||||
class="isa-select-bullet mt-4"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail">
|
||||
<div class="label">Menge</div>
|
||||
<div class="value">
|
||||
@if (!(canChangeQuantity$ | async)) {
|
||||
{{ orderItem?.quantity }}x
|
||||
}
|
||||
@if (canChangeQuantity$ | async) {
|
||||
<ui-quantity-dropdown
|
||||
[showTrash]="false"
|
||||
[range]="orderItem?.quantity"
|
||||
[(ngModel)]="quantity"
|
||||
[showSpinner]="false"
|
||||
></ui-quantity-dropdown>
|
||||
}
|
||||
<ng-container *ngIf="!(canChangeQuantity$ | async)">{{ orderItem?.quantity }}x</ng-container>
|
||||
<ui-quantity-dropdown
|
||||
*ngIf="canChangeQuantity$ | async"
|
||||
[showTrash]="false"
|
||||
[range]="orderItem?.quantity"
|
||||
[(ngModel)]="quantity"
|
||||
[showSpinner]="false"
|
||||
></ui-quantity-dropdown>
|
||||
<span class="overall-quantity">(von {{ orderItem?.overallQuantity }})</span>
|
||||
</div>
|
||||
</div>
|
||||
@if (!!orderItem.product?.formatDetail) {
|
||||
<div class="detail">
|
||||
<div class="label">Format</div>
|
||||
<div class="value">
|
||||
@if (orderItem?.product?.format && orderItem?.product?.format !== 'UNKNOWN') {
|
||||
<img
|
||||
class="format-icon"
|
||||
[src]="'/assets/images/Icon_' + orderItem.product?.format + '.svg'"
|
||||
alt="format icon"
|
||||
/>
|
||||
}
|
||||
<span>{{ orderItem.product?.formatDetail }}</span>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!orderItem.product?.formatDetail">
|
||||
<div class="label">Format</div>
|
||||
<div class="value">
|
||||
<img
|
||||
*ngIf="orderItem?.product?.format && orderItem?.product?.format !== 'UNKNOWN'"
|
||||
class="format-icon"
|
||||
[src]="'/assets/images/Icon_' + orderItem.product?.format + '.svg'"
|
||||
alt="format icon"
|
||||
/>
|
||||
<span>{{ orderItem.product?.formatDetail }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (!!orderItem.product?.ean) {
|
||||
<div class="detail">
|
||||
<div class="label">ISBN/EAN</div>
|
||||
<div class="value">{{ orderItem.product?.ean }}</div>
|
||||
</div>
|
||||
}
|
||||
@if (orderItem.price !== undefined) {
|
||||
<div class="detail">
|
||||
<div class="label">Preis</div>
|
||||
<div class="value">{{ orderItem.price | currency: 'EUR' }}</div>
|
||||
</div>
|
||||
}
|
||||
@if (!!orderItem.retailPrice?.vat?.inPercent) {
|
||||
<div class="detail">
|
||||
<div class="label">MwSt</div>
|
||||
<div class="value">{{ orderItem.retailPrice?.vat?.inPercent }}%</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!orderItem.product?.ean">
|
||||
<div class="label">ISBN/EAN</div>
|
||||
<div class="value">{{ orderItem.product?.ean }}</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="orderItem.price !== undefined">
|
||||
<div class="label">Preis</div>
|
||||
<div class="value">{{ orderItem.price | currency: 'EUR' }}</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!orderItem.retailPrice?.vat?.inPercent">
|
||||
<div class="label">MwSt</div>
|
||||
<div class="value">{{ orderItem.retailPrice?.vat?.inPercent }}%</div>
|
||||
</div>
|
||||
|
||||
<hr class="border-[#EDEFF0] border-t-2 my-4" />
|
||||
@if (orderItem.supplier) {
|
||||
<div class="detail">
|
||||
<div class="label">Lieferant</div>
|
||||
<div class="value">{{ orderItem.supplier }}</div>
|
||||
</div>
|
||||
}
|
||||
@if (!!orderItem.ssc || !!orderItem.sscText) {
|
||||
<div class="detail">
|
||||
<div class="label">Meldenummer</div>
|
||||
<div class="value">{{ orderItem.ssc }} - {{ orderItem.sscText }}</div>
|
||||
</div>
|
||||
}
|
||||
@if (!!orderItem.targetBranch) {
|
||||
<div class="detail">
|
||||
<div class="label">Zielfiliale</div>
|
||||
<div class="value">{{ orderItem.targetBranch }}</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="detail" *ngIf="orderItem.supplier">
|
||||
<div class="label">Lieferant</div>
|
||||
<div class="value">{{ orderItem.supplier }}</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!orderItem.ssc || !!orderItem.sscText">
|
||||
<div class="label">Meldenummer</div>
|
||||
<div class="value">{{ orderItem.ssc }} - {{ orderItem.sscText }}</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!orderItem.targetBranch">
|
||||
<div class="label">Zielfiliale</div>
|
||||
<div class="value">{{ orderItem.targetBranch }}</div>
|
||||
</div>
|
||||
<div class="detail">
|
||||
<div class="label">
|
||||
@if (
|
||||
orderItemFeature(orderItem) === 'Versand' ||
|
||||
orderItemFeature(orderItem) === 'B2B-Versand' ||
|
||||
orderItemFeature(orderItem) === 'DIG-Versand'
|
||||
) {
|
||||
<ng-container
|
||||
*ngIf="
|
||||
orderItemFeature(orderItem) === 'Versand' ||
|
||||
orderItemFeature(orderItem) === 'B2B-Versand' ||
|
||||
orderItemFeature(orderItem) === 'DIG-Versand'
|
||||
"
|
||||
>
|
||||
{{ orderItem?.estimatedDelivery ? 'Lieferung zwischen' : 'Lieferung ab' }}
|
||||
}
|
||||
@if (orderItemFeature(orderItem) === 'Abholung' || orderItemFeature(orderItem) === 'Rücklage') {
|
||||
</ng-container>
|
||||
<ng-container *ngIf="orderItemFeature(orderItem) === 'Abholung' || orderItemFeature(orderItem) === 'Rücklage'">
|
||||
Abholung ab
|
||||
}
|
||||
</ng-container>
|
||||
</div>
|
||||
@if (!!orderItem?.estimatedDelivery || !!orderItem?.estimatedShippingDate) {
|
||||
|
||||
<ng-container *ngIf="!!orderItem?.estimatedDelivery || !!orderItem?.estimatedShippingDate">
|
||||
<div class="value bg-[#D8DFE5] rounded w-max px-2">
|
||||
@if (!!orderItem?.estimatedDelivery) {
|
||||
<ng-container *ngIf="!!orderItem?.estimatedDelivery; else estimatedShippingDate">
|
||||
{{ orderItem?.estimatedDelivery?.start | date: 'dd.MM.yy' }} und
|
||||
{{ orderItem?.estimatedDelivery?.stop | date: 'dd.MM.yy' }}
|
||||
} @else {
|
||||
@if (!!orderItem?.estimatedShippingDate) {
|
||||
{{ orderItem?.estimatedShippingDate | date: 'dd.MM.yy' }}
|
||||
}
|
||||
}
|
||||
</ng-container>
|
||||
</div>
|
||||
}
|
||||
</ng-container>
|
||||
|
||||
<ng-template #estimatedShippingDate>
|
||||
<ng-container *ngIf="!!orderItem?.estimatedShippingDate">
|
||||
{{ orderItem?.estimatedShippingDate | date: 'dd.MM.yy' }}
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</div>
|
||||
@if (getOrderItemTrackingData(orderItem); as trackingData) {
|
||||
<div class="page-customer-order-details-item__tracking-details">
|
||||
<div class="label">{{ trackingData.length > 1 ? 'Sendungsnummern' : 'Sendungsnummer' }}</div>
|
||||
@for (tracking of trackingData; track tracking) {
|
||||
@if (tracking.trackingProvider === 'DHL' && !isNative) {
|
||||
<a class="value text-[#0556B4]" [href]="getTrackingNumberLink(tracking.trackingNumber)" target="_blank">
|
||||
{{ tracking.trackingProvider }}: {{ tracking.trackingNumber }}
|
||||
</a>
|
||||
} @else {
|
||||
<p class="value">{{ tracking.trackingProvider }}: {{ tracking.trackingNumber }}</p>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div class="page-customer-order-details-item__tracking-details" *ngIf="getOrderItemTrackingData(orderItem); let trackingData">
|
||||
<div class="label">{{ trackingData.length > 1 ? 'Sendungsnummern' : 'Sendungsnummer' }}</div>
|
||||
<ng-container *ngFor="let tracking of trackingData">
|
||||
<ng-container *ngIf="tracking.trackingProvider === 'DHL' && !isNative; else noTrackingLink">
|
||||
<a class="value text-[#0556B4]" [href]="getTrackingNumberLink(tracking.trackingNumber)" target="_blank">
|
||||
{{ tracking.trackingProvider }}: {{ tracking.trackingNumber }}
|
||||
</a>
|
||||
</ng-container>
|
||||
<ng-template #noTrackingLink>
|
||||
<p class="value">{{ tracking.trackingProvider }}: {{ tracking.trackingNumber }}</p>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<hr class="border-[#EDEFF0] border-t-2 my-4" />
|
||||
@if (!!orderItem?.compartmentCode) {
|
||||
<div class="detail">
|
||||
<div class="label">Abholfachnr.</div>
|
||||
<div class="value">{{ orderItem?.compartmentCode }}</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="detail" *ngIf="!!orderItem?.compartmentCode">
|
||||
<div class="label">Abholfachnr.</div>
|
||||
<div class="value">{{ orderItem?.compartmentCode }}</div>
|
||||
</div>
|
||||
<div class="detail">
|
||||
<div class="label">Vormerker</div>
|
||||
<div class="value">{{ orderItem.isPrebooked ? 'Ja' : 'Nein' }}</div>
|
||||
</div>
|
||||
|
||||
<hr class="border-[#EDEFF0] border-t-2 my-4" />
|
||||
@if (!!orderItem.paymentProcessing) {
|
||||
<div class="detail">
|
||||
<div class="label">Zahlungsweg</div>
|
||||
<div class="value">{{ orderItem.paymentProcessing || '-' }}</div>
|
||||
|
||||
<div class="detail" *ngIf="!!orderItem.paymentProcessing">
|
||||
<div class="label">Zahlungsweg</div>
|
||||
<div class="value">{{ orderItem.paymentProcessing || '-' }}</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!orderItem.paymentType">
|
||||
<div class="label">Zahlungsart</div>
|
||||
<div class="value">{{ orderItem.paymentType | paymentType }}</div>
|
||||
</div>
|
||||
|
||||
<h4 class="receipt-header" *ngIf="receiptCount$ | async; let count">
|
||||
{{ count > 1 ? 'Belege' : 'Beleg' }}
|
||||
</h4>
|
||||
<ng-container *ngFor="let receipt of receipts$ | async">
|
||||
<div class="detail" *ngIf="!!receipt?.receiptNumber">
|
||||
<div class="label">Belegnummer</div>
|
||||
<div class="value">{{ receipt?.receiptNumber }}</div>
|
||||
</div>
|
||||
}
|
||||
@if (!!orderItem.paymentType) {
|
||||
<div class="detail">
|
||||
<div class="label">Zahlungsart</div>
|
||||
<div class="value">{{ orderItem.paymentType | paymentType }}</div>
|
||||
<div class="detail" *ngIf="!!receipt?.printedDate">
|
||||
<div class="label">Erstellt am</div>
|
||||
<div class="value">{{ receipt?.printedDate | date: 'dd.MM.yy | HH:mm' }} Uhr</div>
|
||||
</div>
|
||||
}
|
||||
@if (receiptCount$ | async; as count) {
|
||||
<h4 class="receipt-header">
|
||||
{{ count > 1 ? 'Belege' : 'Beleg' }}
|
||||
</h4>
|
||||
}
|
||||
@for (receipt of receipts$ | async; track receipt) {
|
||||
@if (!!receipt?.receiptNumber) {
|
||||
<div class="detail">
|
||||
<div class="label">Belegnummer</div>
|
||||
<div class="value">{{ receipt?.receiptNumber }}</div>
|
||||
<div class="detail" *ngIf="!!receipt?.receiptText">
|
||||
<div class="label">Rechnungstext</div>
|
||||
<div class="value">{{ receipt?.receiptText || '-' }}</div>
|
||||
</div>
|
||||
<div class="detail" *ngIf="!!receipt?.receiptType">
|
||||
<div class="label">Belegart</div>
|
||||
<div class="value">
|
||||
{{ receipt?.receiptType === 1 ? 'Lieferschein' : receipt?.receiptType === 64 ? 'Zahlungsbeleg' : '-' }}
|
||||
</div>
|
||||
}
|
||||
@if (!!receipt?.printedDate) {
|
||||
<div class="detail">
|
||||
<div class="label">Erstellt am</div>
|
||||
<div class="value">{{ receipt?.printedDate | date: 'dd.MM.yy | HH:mm' }} Uhr</div>
|
||||
</div>
|
||||
}
|
||||
@if (!!receipt?.receiptText) {
|
||||
<div class="detail">
|
||||
<div class="label">Rechnungstext</div>
|
||||
<div class="value">{{ receipt?.receiptText || '-' }}</div>
|
||||
</div>
|
||||
}
|
||||
@if (!!receipt?.receiptType) {
|
||||
<div class="detail">
|
||||
<div class="label">Belegart</div>
|
||||
<div class="value">
|
||||
{{ receipt?.receiptType === 1 ? 'Lieferschein' : receipt?.receiptType === 64 ? 'Zahlungsbeleg' : '-' }}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="page-customer-order-details-item__comment flex flex-col items-start mt-[1.625rem]">
|
||||
<div class="label mb-[0.375rem]">Anmerkung</div>
|
||||
|
||||
<div class="flex flex-row w-full">
|
||||
<textarea
|
||||
matInput
|
||||
@@ -243,28 +222,27 @@
|
||||
[formControl]="specialCommentControl"
|
||||
[class.inactive]="!specialCommentControl.dirty"
|
||||
></textarea>
|
||||
|
||||
<div class="comment-actions">
|
||||
@if (!!specialCommentControl.value?.length) {
|
||||
<button
|
||||
type="reset"
|
||||
class="clear"
|
||||
(click)="specialCommentControl.setValue(''); saveSpecialComment(); triggerResize()"
|
||||
>
|
||||
<shared-icon icon="close" [size]="24"></shared-icon>
|
||||
</button>
|
||||
}
|
||||
@if (specialCommentControl?.enabled && specialCommentControl.dirty) {
|
||||
<button
|
||||
class="cta-save"
|
||||
type="submit"
|
||||
(click)="saveSpecialComment()"
|
||||
>
|
||||
Speichern
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
type="reset"
|
||||
class="clear"
|
||||
*ngIf="!!specialCommentControl.value?.length"
|
||||
(click)="specialCommentControl.setValue(''); saveSpecialComment(); triggerResize()"
|
||||
>
|
||||
<shared-icon icon="close" [size]="24"></shared-icon>
|
||||
</button>
|
||||
<button
|
||||
class="cta-save"
|
||||
type="submit"
|
||||
*ngIf="specialCommentControl?.enabled && specialCommentControl.dirty"
|
||||
(click)="saveSpecialComment()"
|
||||
>
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</ng-container>
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
<div class="page-customer-order-details-tags__wrapper">
|
||||
@for (tag of defaultTags; track tag) {
|
||||
<button
|
||||
class="page-customer-order-details-tags__tag"
|
||||
type="button"
|
||||
[class.selected]="tag === (selected$ | async) && !inputFocus.focused"
|
||||
(click)="setCompartmentInfo(tag)"
|
||||
>
|
||||
{{ tag }}
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
class="page-customer-order-details-tags__tag"
|
||||
type="button"
|
||||
[class.selected]="tag === (selected$ | async) && !inputFocus.focused"
|
||||
*ngFor="let tag of defaultTags"
|
||||
(click)="setCompartmentInfo(tag)"
|
||||
>
|
||||
{{ tag }}
|
||||
</button>
|
||||
<button
|
||||
(click)="inputFocus.focus()"
|
||||
type="button"
|
||||
class="page-customer-order-details-tags__tag"
|
||||
[class.selected]="(inputValue$ | async) === (selected$ | async) && (inputValue$ | async)"
|
||||
>
|
||||
>
|
||||
<input
|
||||
#inputFocus="uiFocus"
|
||||
uiFocus
|
||||
@@ -24,6 +23,6 @@
|
||||
placeholder="..."
|
||||
[size]="controlSize$ | async"
|
||||
maxlength="15"
|
||||
/>
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AsyncPipe } from '@angular/common';
|
||||
import { NgFor, AsyncPipe } from '@angular/common';
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, forwardRef } from '@angular/core';
|
||||
import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
@@ -14,7 +14,7 @@ import { PickUpShelfDetailsTagsComponent } from '../../../pickup-shelf/shared/pi
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: { class: 'page-pickup-shelf-details-tags' },
|
||||
standalone: true,
|
||||
imports: [UiCommonModule, FormsModule, AsyncPipe, MatomoModule],
|
||||
imports: [NgFor, UiCommonModule, FormsModule, AsyncPipe, MatomoModule],
|
||||
providers: [
|
||||
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => PickUpShelfDetailsTagsComponent), multi: true },
|
||||
],
|
||||
|
||||
@@ -4,44 +4,39 @@
|
||||
(handleAction)="handleAction($event)"
|
||||
[order]="order$ | async"
|
||||
></page-customer-order-details-header>
|
||||
@for (item of items$ | async; track item) {
|
||||
<page-customer-order-details-item
|
||||
class="mb-px-2"
|
||||
[orderItem]="item"
|
||||
[order]="order$ | async"
|
||||
[selected]="true"
|
||||
(historyClick)="navigateToHistoryPage($event)"
|
||||
(specialCommentChanged)="onSpecialCommentChange()"
|
||||
></page-customer-order-details-item>
|
||||
}
|
||||
@if (showTagsComponent$ | async) {
|
||||
<page-customer-order-details-tags></page-customer-order-details-tags>
|
||||
}
|
||||
<page-customer-order-details-item
|
||||
class="mb-px-2"
|
||||
*ngFor="let item of items$ | async"
|
||||
[orderItem]="item"
|
||||
[order]="order$ | async"
|
||||
[selected]="true"
|
||||
(historyClick)="navigateToHistoryPage($event)"
|
||||
(specialCommentChanged)="onSpecialCommentChange()"
|
||||
></page-customer-order-details-item>
|
||||
<page-customer-order-details-tags *ngIf="showTagsComponent$ | async"></page-customer-order-details-tags>
|
||||
</div>
|
||||
<div class="page-customer-order-details__action-wrapper">
|
||||
@if (addToPreviousCompartmentAction$ | async; as action) {
|
||||
<button
|
||||
[disabled]="addToPreviousCompartmentActionDisabled$ | async"
|
||||
class="cta-action shadow-action"
|
||||
[class.cta-action-primary]="action.selected"
|
||||
[class.cta-action-secondary]="!action.selected"
|
||||
(click)="handleAction(action, { compartmentCode: latestCompartmentCode, compartmentInfo: latestCompartmentInfo })"
|
||||
>
|
||||
<ui-spinner [show]="(changeActionLoader$ | async) === action.command">
|
||||
{{ latestCompartmentCode$ | async | addToPreviousCompartmentCodeLabelPipe }} zubuchen
|
||||
</ui-spinner>
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
[disabled]="addToPreviousCompartmentActionDisabled$ | async"
|
||||
*ngIf="addToPreviousCompartmentAction$ | async; let action"
|
||||
class="cta-action shadow-action"
|
||||
[class.cta-action-primary]="action.selected"
|
||||
[class.cta-action-secondary]="!action.selected"
|
||||
(click)="handleAction(action, { compartmentCode: latestCompartmentCode, compartmentInfo: latestCompartmentInfo })"
|
||||
>
|
||||
<ui-spinner [show]="(changeActionLoader$ | async) === action.command">
|
||||
{{ latestCompartmentCode$ | async | addToPreviousCompartmentCodeLabelPipe }} zubuchen
|
||||
</ui-spinner>
|
||||
</button>
|
||||
|
||||
@for (action of mainActions$ | async; track action) {
|
||||
<button
|
||||
[disabled]="actionsDisabled$ | async"
|
||||
class="cta-action shadow-action"
|
||||
[class.cta-action-primary]="action.selected"
|
||||
[class.cta-action-secondary]="!action.selected"
|
||||
(click)="handleAction(action)"
|
||||
>
|
||||
<ui-spinner [show]="(changeActionLoader$ | async) === action.command">{{ action.label }}</ui-spinner>
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
[disabled]="actionsDisabled$ | async"
|
||||
class="cta-action shadow-action"
|
||||
[class.cta-action-primary]="action.selected"
|
||||
[class.cta-action-secondary]="!action.selected"
|
||||
*ngFor="let action of mainActions$ | async"
|
||||
(click)="handleAction(action)"
|
||||
>
|
||||
<ui-spinner [show]="(changeActionLoader$ | async) === action.command">{{ action.label }}</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@if (items$ | async; as items) {
|
||||
<div>
|
||||
<shared-goods-in-out-order-edit (navigation)="navigateToDetailsPage($event)" [items]="items"></shared-goods-in-out-order-edit>
|
||||
</div>
|
||||
}
|
||||
<div *ngIf="items$ | async; let items">
|
||||
<shared-goods-in-out-order-edit (navigation)="navigateToDetailsPage($event)" [items]="items"></shared-goods-in-out-order-edit>
|
||||
</div>
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<div class="hidden desktop-large:block" [class.show-filter]="showFilter">
|
||||
@if (filter$ | async; as filter) {
|
||||
<ng-container *ngIf="filter$ | async; let filter">
|
||||
<div class="customer-orders-search-filter-content">
|
||||
<div class="w-full flex flex-row justify-end items-center">
|
||||
<button (click)="clearFilter(filter)" class="text-[#0556B4] p-4">Alle Filter entfernen</button>
|
||||
@if (showFilterClose$ | async) {
|
||||
<a
|
||||
class="text-black p-4 outline-none border-none bg-transparent"
|
||||
[routerLink]="closeFilterRoute"
|
||||
(click)="showFilter = false"
|
||||
queryParamsHandling="preserve"
|
||||
>
|
||||
<shared-icon icon="close" [size]="25"></shared-icon>
|
||||
</a>
|
||||
}
|
||||
<a
|
||||
*ngIf="showFilterClose$ | async"
|
||||
class="text-black p-4 outline-none border-none bg-transparent"
|
||||
[routerLink]="closeFilterRoute"
|
||||
(click)="showFilter = false"
|
||||
queryParamsHandling="preserve"
|
||||
>
|
||||
<shared-icon icon="close" [size]="25"></shared-icon>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="customer-orders-search-filter-content-main -mt-14 desktop-small:-mt-8 desktop-large:-mt-12">
|
||||
<h1 class="text-h3 text-[1.625rem] font-bold text-center pt-6 pb-10">Filter</h1>
|
||||
<shared-filter
|
||||
@@ -22,18 +22,20 @@
|
||||
(search)="applyFilter(filter)"
|
||||
[hint]="message$ | async"
|
||||
[scanner]="true"
|
||||
>
|
||||
>
|
||||
<page-order-branch-id-input *sharedFilterCustomInput="'order_branch_id'; let input" [input]="input"></page-order-branch-id-input>
|
||||
</shared-filter>
|
||||
</div>
|
||||
|
||||
<div class="cta-wrapper">
|
||||
<button class="cta-reset-filter" (click)="resetFilter()" [disabled]="loading$ | async">Filter zurücksetzen</button>
|
||||
|
||||
<button class="cta-apply-filter" (click)="applyFilter(filter)" [disabled]="(loading$ | async) || !hasSelectedOptions(filter)">
|
||||
<ui-spinner [show]="loading$ | async">Filter anwenden</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="desktop-large:hidden" [class.hidden]="showFilter">
|
||||
<page-customer-order-search-main (showFilter)="showFilter = true"></page-customer-order-search-main>
|
||||
|
||||
@@ -11,57 +11,53 @@
|
||||
<br />
|
||||
oder scannen Sie die Kundenkarte.
|
||||
</p>
|
||||
@if (filter$ | async; as filter) {
|
||||
@if (!(isDesktop$ | async)) {
|
||||
<shared-filter-filter-group-main
|
||||
class="mb-8 w-full"
|
||||
[inputGroup]="filter?.filter | group: 'main'"
|
||||
></shared-filter-filter-group-main>
|
||||
}
|
||||
<ng-container *ngIf="filter$ | async; let filter">
|
||||
<shared-filter-filter-group-main
|
||||
class="mb-8 w-full"
|
||||
*ngIf="!(isDesktop$ | async)"
|
||||
[inputGroup]="filter?.filter | group: 'main'"
|
||||
></shared-filter-filter-group-main>
|
||||
<div class="flex flex-row px-12 justify-center desktop-large:px-0">
|
||||
@if (filter?.input | group: 'main'; as inputGroup) {
|
||||
<shared-filter-input-group-main
|
||||
class="block w-full mr-3 desktop-large:mx-auto"
|
||||
[inputGroup]="inputGroup"
|
||||
[loading]="loading$ | async"
|
||||
(search)="search(filter)"
|
||||
[hint]="message$ | async"
|
||||
[scanner]="true"
|
||||
></shared-filter-input-group-main>
|
||||
}
|
||||
@if (!(isDesktop$ | async)) {
|
||||
<button
|
||||
(click)="showFilter.emit()"
|
||||
class="page-search-main__filter w-[6.75rem] h-14 rounded font-bold px-5 mb-4 text-lg bg-[#AEB7C1] flex flex-row flex-nowrap items-center justify-center"
|
||||
[class.active]="hasFilter$ | async"
|
||||
type="button"
|
||||
>
|
||||
<shared-icon class="mr-2" icon="filter-variant"></shared-icon>
|
||||
Filter
|
||||
</button>
|
||||
}
|
||||
<shared-filter-input-group-main
|
||||
class="block w-full mr-3 desktop-large:mx-auto"
|
||||
*ngIf="filter?.input | group: 'main'; let inputGroup"
|
||||
[inputGroup]="inputGroup"
|
||||
[loading]="loading$ | async"
|
||||
(search)="search(filter)"
|
||||
[hint]="message$ | async"
|
||||
[scanner]="true"
|
||||
></shared-filter-input-group-main>
|
||||
<button
|
||||
*ngIf="!(isDesktop$ | async)"
|
||||
(click)="showFilter.emit()"
|
||||
class="page-search-main__filter w-[6.75rem] h-14 rounded font-bold px-5 mb-4 text-lg bg-[#AEB7C1] flex flex-row flex-nowrap items-center justify-center"
|
||||
[class.active]="hasFilter$ | async"
|
||||
type="button"
|
||||
>
|
||||
<shared-icon class="mr-2" icon="filter-variant"></shared-icon>
|
||||
Filter
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-col items-start ml-12 desktop-large:ml-8 py-6 bg-white overflow-hidden h-[calc(100%-21rem)] desktop-large:h-[calc(100%-15rem)]"
|
||||
>
|
||||
>
|
||||
<h3 class="text-p3 font-bold mb-3">Deine letzten Suchanfragen</h3>
|
||||
<ul class="flex flex-col justify-start overflow-hidden overflow-y-scroll items-start m-0 p-0 bg-white w-full">
|
||||
@for (query of history$ | async; track query) {
|
||||
<li class="list-none pb-3">
|
||||
<button
|
||||
class="flex flex-row items-center outline-none border-none bg-white text-black text-p2 m-0 p-0"
|
||||
(click)="setQueryHistory(filter, query)"
|
||||
>
|
||||
<shared-icon
|
||||
class="flex w-8 h-8 justify-center items-center mr-3 rounded-full text-black bg-[#edeff0]"
|
||||
icon="magnify"
|
||||
[size]="20"
|
||||
></shared-icon>
|
||||
<p class="m-0 p-0 whitespace-nowrap overflow-hidden overflow-ellipsis max-w-[25rem]">{{ query }}</p>
|
||||
</button>
|
||||
</li>
|
||||
}
|
||||
<li class="list-none pb-3" *ngFor="let query of history$ | async">
|
||||
<button
|
||||
class="flex flex-row items-center outline-none border-none bg-white text-black text-p2 m-0 p-0"
|
||||
(click)="setQueryHistory(filter, query)"
|
||||
>
|
||||
<shared-icon
|
||||
class="flex w-8 h-8 justify-center items-center mr-3 rounded-full text-black bg-[#edeff0]"
|
||||
icon="magnify"
|
||||
[size]="20"
|
||||
></shared-icon>
|
||||
<p class="m-0 p-0 whitespace-nowrap overflow-hidden overflow-ellipsis max-w-[25rem]">{{ query }}</p>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@@ -5,20 +5,19 @@
|
||||
[routerLinkActive]="!isTablet && !primaryOutletActive ? 'active' : ''"
|
||||
[queryParams]="queryParams"
|
||||
(click)="isDesktopLarge ? scrollIntoView() : ''"
|
||||
>
|
||||
>
|
||||
<div
|
||||
class="page-customer-order-item__item-grid-container"
|
||||
[class.page-customer-order-item__item-grid-container-main]="primaryOutletActive"
|
||||
>
|
||||
>
|
||||
<div class="page-customer-order-item__item-thumbnail text-center mr-4 w-[3.125rem] h-[4.9375rem]">
|
||||
@if (item?.product?.ean | productImage; as productImage) {
|
||||
<img
|
||||
class="page-customer-order-item__item-image w-[3.125rem] max-h-[4.9375rem]"
|
||||
loading="lazy"
|
||||
[src]="productImage"
|
||||
[alt]="item?.product?.name"
|
||||
/>
|
||||
}
|
||||
<img
|
||||
class="page-customer-order-item__item-image w-[3.125rem] max-h-[4.9375rem]"
|
||||
loading="lazy"
|
||||
*ngIf="item?.product?.ean | productImage; let productImage"
|
||||
[src]="productImage"
|
||||
[alt]="item?.product?.name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -28,7 +27,7 @@
|
||||
[class.text-p2]="item?.product?.name?.length >= 50 && isTablet"
|
||||
[class.text-p3]="item?.product?.name?.length >= 60 || !isTablet"
|
||||
[class.text-p4]="item?.product?.name?.length >= 100"
|
||||
>
|
||||
>
|
||||
{{ item?.product?.name }}
|
||||
</div>
|
||||
|
||||
@@ -36,111 +35,88 @@
|
||||
{{ item?.specialComment }}
|
||||
</div>
|
||||
|
||||
@if (primaryOutletActive) {
|
||||
<div class="page-customer-order-item__item-format desktop-small:text-p2">
|
||||
@if (item?.product?.format && item?.product?.formatDetail) {
|
||||
<div class="font-bold flex flex-row">
|
||||
@if (item?.product?.format !== '--') {
|
||||
<img
|
||||
class="mr-3"
|
||||
loading="lazy"
|
||||
src="assets/images/Icon_{{ item?.product?.format }}.svg"
|
||||
[alt]="item?.product?.formatDetail"
|
||||
/>
|
||||
}
|
||||
{{ item?.product?.formatDetail | substr: 30 }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (primaryOutletActive) {
|
||||
<div class="page-customer-order-item__item-ean desktop-small:text-p2">
|
||||
{{ item?.product?.ean }}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (primaryOutletActive) {
|
||||
<div class="page-customer-order-item__item-price desktop-small:text-p2 font-bold">
|
||||
{{ item.price | currency: 'EUR' : 'code' }}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (primaryOutletActive) {
|
||||
<div class="page-customer-order-item__item-changed desktop-small:text-p2">
|
||||
@switch (showChangeDate$ | async) {
|
||||
@case (true) {
|
||||
<div class="flex flex-row">
|
||||
<div class="min-w-[7.5rem]">Geändert</div>
|
||||
<div class="font-bold">{{ item?.processingStatusDate | date: 'dd.MM.yy | HH:mm' }} Uhr</div>
|
||||
</div>
|
||||
}
|
||||
@case (false) {
|
||||
<div class="flex flex-row">
|
||||
<div class="min-w-[7.5rem]">Bestelldatum</div>
|
||||
<div class="font-bold">{{ item?.orderDate | date: 'dd.MM.yy | HH:mm' }} Uhr</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (primaryOutletActive) {
|
||||
<div class="page-customer-order-item__item-quantity flex flex-row desktop-small:text-p2">
|
||||
<div class="min-w-[7.5rem]">Menge</div>
|
||||
<div class="font-bold">{{ item.quantity }} x</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (primaryOutletActive) {
|
||||
<div class="page-customer-order-item__item-target-branch flex flex-row desktop-small:text-p2">
|
||||
@if (item.orderType === 1) {
|
||||
<div class="min-w-[7.5rem]">Zielfiliale</div>
|
||||
<div class="font-bold">{{ item.targetBranch }}</div>
|
||||
} @else {
|
||||
<div class="min-w-[7.5rem]">Versanddatum</div>
|
||||
<div class="font-bold">{{ item?.estimatedShippingDate | date: 'dd.MM.yy | HH:mm' }} Uhr</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!primaryOutletActive) {
|
||||
<hr
|
||||
class="page-customer-order-item__separator border-[#EDEFF0] border-solid border-[1px] -mx-[0.875rem]"
|
||||
<div *ngIf="primaryOutletActive" class="page-customer-order-item__item-format desktop-small:text-p2">
|
||||
<div *ngIf="item?.product?.format && item?.product?.formatDetail" class="font-bold flex flex-row">
|
||||
<img
|
||||
class="mr-3"
|
||||
*ngIf="item?.product?.format !== '--'"
|
||||
loading="lazy"
|
||||
src="assets/images/Icon_{{ item?.product?.format }}.svg"
|
||||
[alt]="item?.product?.formatDetail"
|
||||
/>
|
||||
}
|
||||
{{ item?.product?.formatDetail | substr: 30 }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="primaryOutletActive" class="page-customer-order-item__item-ean desktop-small:text-p2">
|
||||
{{ item?.product?.ean }}
|
||||
</div>
|
||||
|
||||
<div *ngIf="primaryOutletActive" class="page-customer-order-item__item-price desktop-small:text-p2 font-bold">
|
||||
{{ item.price | currency: 'EUR' : 'code' }}
|
||||
</div>
|
||||
|
||||
<div *ngIf="primaryOutletActive" class="page-customer-order-item__item-changed desktop-small:text-p2">
|
||||
<ng-container [ngSwitch]="showChangeDate$ | async">
|
||||
<div class="flex flex-row" *ngSwitchCase="true">
|
||||
<div class="min-w-[7.5rem]">Geändert</div>
|
||||
<div class="font-bold">{{ item?.processingStatusDate | date: 'dd.MM.yy | HH:mm' }} Uhr</div>
|
||||
</div>
|
||||
<div class="flex flex-row" *ngSwitchCase="false">
|
||||
<div class="min-w-[7.5rem]">Bestelldatum</div>
|
||||
<div class="font-bold">{{ item?.orderDate | date: 'dd.MM.yy | HH:mm' }} Uhr</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div *ngIf="primaryOutletActive" class="page-customer-order-item__item-quantity flex flex-row desktop-small:text-p2">
|
||||
<div class="min-w-[7.5rem]">Menge</div>
|
||||
<div class="font-bold">{{ item.quantity }} x</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="primaryOutletActive" class="page-customer-order-item__item-target-branch flex flex-row desktop-small:text-p2">
|
||||
<ng-container *ngIf="item.orderType === 1; else showDelivery">
|
||||
<div class="min-w-[7.5rem]">Zielfiliale</div>
|
||||
<div class="font-bold">{{ item.targetBranch }}</div>
|
||||
</ng-container>
|
||||
<ng-template #showDelivery>
|
||||
<div class="min-w-[7.5rem]">Versanddatum</div>
|
||||
<div class="font-bold">{{ item?.estimatedShippingDate | date: 'dd.MM.yy | HH:mm' }} Uhr</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<hr
|
||||
*ngIf="!primaryOutletActive"
|
||||
class="page-customer-order-item__separator border-[#EDEFF0] border-solid border-[1px] -mx-[0.875rem]"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="page-customer-order-item__item-order-number desktop-small:text-xl justify-self-end font-bold"
|
||||
[class.page-customer-order-item__item-order-number-main]="!primaryOutletActive"
|
||||
>
|
||||
@if (item?.compartmentCode) {
|
||||
>
|
||||
<ng-container *ngIf="item?.compartmentCode; else orderNumber">
|
||||
{{ item?.compartmentCode }}{{ item?.compartmentInfo && '_' + item?.compartmentInfo }}
|
||||
} @else {
|
||||
{{ item?.orderNumber }}
|
||||
}
|
||||
</ng-container>
|
||||
<ng-template #orderNumber>{{ item?.orderNumber }}</ng-template>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="page-customer-order-item__item-processing-paid-status flex flex-col font-bold desktop-small:text-p2 justify-self-end self-center"
|
||||
>
|
||||
>
|
||||
<div class="page-customer-order-item__item-processing-status flex flex-row mb-[0.375rem]">
|
||||
@if (item.processingStatus | processingStatus: 'icon'; as icon) {
|
||||
<shared-icon
|
||||
class="flex items-center justify-center mr-1"
|
||||
[size]="16"
|
||||
[icon]="icon"
|
||||
></shared-icon>
|
||||
}
|
||||
<shared-icon
|
||||
class="flex items-center justify-center mr-1"
|
||||
[size]="16"
|
||||
*ngIf="item.processingStatus | processingStatus: 'icon'; let icon"
|
||||
[icon]="icon"
|
||||
></shared-icon>
|
||||
{{ item.processingStatus | processingStatus }}
|
||||
</div>
|
||||
|
||||
<div class="page-customer-order-item__item-paid flex flex-row self-end">
|
||||
@if (item.features?.paid) {
|
||||
<div class="font-bold w-fit desktop-small:text-p2 px-3 py-[0.125rem] rounded text-white bg-[#26830C]">
|
||||
{{ item.features?.paid }}
|
||||
</div>
|
||||
}
|
||||
<div class="font-bold w-fit desktop-small:text-p2 px-3 py-[0.125rem] rounded text-white bg-[#26830C]" *ngIf="item.features?.paid">
|
||||
{{ item.features?.paid }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,111 +2,102 @@
|
||||
class="page-customer-order-search-results__header bg-background-liste flex items-end justify-between"
|
||||
[class.pb-4]="!(primaryOutletActive$ | async)"
|
||||
[class.flex-col]="!(primaryOutletActive$ | async)"
|
||||
>
|
||||
>
|
||||
<div class="flex flex-row w-full desktop-small:w-min" [class.desktop-large:w-full]="!(primaryOutletActive$ | async)">
|
||||
@if (filter$ | async; as filter) {
|
||||
<shared-filter-input-group-main
|
||||
class="block mr-3 w-full desktop-small:w-[23.5rem]"
|
||||
[class.desktop-large:w-full]="!(primaryOutletActive$ | async)"
|
||||
[hint]="message$ | async"
|
||||
[loading]="loading$ | async"
|
||||
[inputGroup]="filter?.input | group: 'main'"
|
||||
(search)="search({ filter, clear: true })"
|
||||
[showDescription]="false"
|
||||
[scanner]="true"
|
||||
></shared-filter-input-group-main>
|
||||
}
|
||||
<shared-filter-input-group-main
|
||||
*ngIf="filter$ | async; let filter"
|
||||
class="block mr-3 w-full desktop-small:w-[23.5rem]"
|
||||
[class.desktop-large:w-full]="!(primaryOutletActive$ | async)"
|
||||
[hint]="message$ | async"
|
||||
[loading]="loading$ | async"
|
||||
[inputGroup]="filter?.input | group: 'main'"
|
||||
(search)="search({ filter, clear: true })"
|
||||
[showDescription]="false"
|
||||
[scanner]="true"
|
||||
></shared-filter-input-group-main>
|
||||
|
||||
<a
|
||||
class="page-customer-orders-results__filter w-[6.75rem] h-14 rounded font-bold px-5 mb-4 text-lg bg-[#AEB7C1] flex flex-row flex-nowrap items-center justify-center"
|
||||
[class.active]="hasFilter$ | async"
|
||||
[routerLink]="filterRoute"
|
||||
queryParamsHandling="preserve"
|
||||
>
|
||||
>
|
||||
<shared-icon class="mr-2" icon="filter-variant"></shared-icon>
|
||||
Filter
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if (hits$ | async; as hits) {
|
||||
<div
|
||||
class="page-customer-order-search-results__items-count inline-flex flex-row items-center pr-5 text-p3"
|
||||
[class.mb-4]="primaryOutletActive$ | async"
|
||||
>
|
||||
{{ hits ?? 0 }}
|
||||
Titel
|
||||
</div>
|
||||
}
|
||||
<div
|
||||
*ngIf="hits$ | async; let hits"
|
||||
class="page-customer-order-search-results__items-count inline-flex flex-row items-center pr-5 text-p3"
|
||||
[class.mb-4]="primaryOutletActive$ | async"
|
||||
>
|
||||
{{ hits ?? 0 }}
|
||||
Titel
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!(listEmpty$ | async)) {
|
||||
<ui-scroll-container
|
||||
class="page-customer-order-results__scroll-container m-0 p-0"
|
||||
[showScrollbar]="false"
|
||||
[showScrollArrow]="false"
|
||||
(reachEnd)="loadMore()"
|
||||
[deltaEnd]="150"
|
||||
[itemLength]="itemLength$ | async"
|
||||
[containerHeight]="25"
|
||||
[showSpacer]="(primaryOutletActive$ | async) || (isTablet$ | async)"
|
||||
>
|
||||
@if (processId$ | async; as processId) {
|
||||
@for (bueryNumberGroup of items$ | async | groupBy: byBuyerNumberFn; track bueryNumberGroup) {
|
||||
<div class="page-customer-order-results__items-list w-full">
|
||||
@if (bueryNumberGroup.items[0]; as firstItem) {
|
||||
<div
|
||||
class="page-customer-order-search__item-header-group w-full grid grid-flow-col gap-x-4 items-center justify-between bg-white text-xl rounded-t px-4 py-[0.875rem] font-bold mb-px-2"
|
||||
>
|
||||
<h3 class="m-0 break-words" [class.w-72]="!(primaryOutletActive$ | async)">
|
||||
{{ firstItem?.organisation }}
|
||||
@if (!!firstItem?.organisation && (!!firstItem?.firstName || !!firstItem?.lastName)) {
|
||||
-
|
||||
}
|
||||
{{ firstItem?.lastName }}
|
||||
{{ firstItem?.firstName }}
|
||||
</h3>
|
||||
<h3 class="m-0 break-words text-right" [class.w-40]="!(primaryOutletActive$ | async)">{{ firstItem?.buyerNumber }}</h3>
|
||||
</div>
|
||||
}
|
||||
@for (orderNumberGroup of bueryNumberGroup.items | groupBy: byOrderNumberFn; track orderNumberGroup) {
|
||||
@for (processingStatusGroup of orderNumberGroup.items | groupBy: byProcessingStatusFn; track processingStatusGroup) {
|
||||
@for (compartmentCodeGroup of processingStatusGroup.items | groupBy: byCompartmentCodeFn; track compartmentCodeGroup) {
|
||||
@for (item of compartmentCodeGroup.items; track trackByFn($index, item); let firstItem = $first) {
|
||||
<page-customer-order-item
|
||||
class="page-customer-orders-results__result-item mb-[0.625rem]"
|
||||
[class.page-customer-orders-results__result-item-main]="primaryOutletActive$ | async"
|
||||
[item]="item"
|
||||
[primaryOutletActive]="primaryOutletActive$ | async"
|
||||
></page-customer-order-item>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
<ui-scroll-container
|
||||
*ngIf="!(listEmpty$ | async); else emptyMessage"
|
||||
class="page-customer-order-results__scroll-container m-0 p-0"
|
||||
[showScrollbar]="false"
|
||||
[showScrollArrow]="false"
|
||||
(reachEnd)="loadMore()"
|
||||
[deltaEnd]="150"
|
||||
[itemLength]="itemLength$ | async"
|
||||
[containerHeight]="25"
|
||||
[showSpacer]="(primaryOutletActive$ | async) || (isTablet$ | async)"
|
||||
>
|
||||
<ng-container *ngIf="processId$ | async; let processId">
|
||||
<div class="page-customer-order-results__items-list w-full" *ngFor="let bueryNumberGroup of items$ | async | groupBy: byBuyerNumberFn">
|
||||
<ng-container *ngIf="bueryNumberGroup.items[0]; let firstItem">
|
||||
<div
|
||||
class="page-customer-order-search__item-header-group w-full grid grid-flow-col gap-x-4 items-center justify-between bg-white text-xl rounded-t px-4 py-[0.875rem] font-bold mb-px-2"
|
||||
>
|
||||
<h3 class="m-0 break-words" [class.w-72]="!(primaryOutletActive$ | async)">
|
||||
{{ firstItem?.organisation }}
|
||||
<ng-container *ngIf="!!firstItem?.organisation && (!!firstItem?.firstName || !!firstItem?.lastName)">-</ng-container>
|
||||
{{ firstItem?.lastName }}
|
||||
{{ firstItem?.firstName }}
|
||||
</h3>
|
||||
<h3 class="m-0 break-words text-right" [class.w-40]="!(primaryOutletActive$ | async)">{{ firstItem?.buyerNumber }}</h3>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</ui-scroll-container>
|
||||
} @else {
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngFor="let orderNumberGroup of bueryNumberGroup.items | groupBy: byOrderNumberFn">
|
||||
<ng-container *ngFor="let processingStatusGroup of orderNumberGroup.items | groupBy: byProcessingStatusFn">
|
||||
<ng-container *ngFor="let compartmentCodeGroup of processingStatusGroup.items | groupBy: byCompartmentCodeFn">
|
||||
<page-customer-order-item
|
||||
*ngFor="let item of compartmentCodeGroup.items; let firstItem = first; trackBy: trackByFn"
|
||||
class="page-customer-orders-results__result-item mb-[0.625rem]"
|
||||
[class.page-customer-orders-results__result-item-main]="primaryOutletActive$ | async"
|
||||
[item]="item"
|
||||
[primaryOutletActive]="primaryOutletActive$ | async"
|
||||
></page-customer-order-item>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ui-scroll-container>
|
||||
|
||||
<ng-template #emptyMessage>
|
||||
<div class="empty-message">
|
||||
Es sind im Moment keine Bestellposten vorhanden,
|
||||
<br />
|
||||
die bearbeitet werden können.
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
|
||||
@if (actions$ | async; as actions) {
|
||||
<div class="actions z-fixed">
|
||||
@for (action of actions; track action) {
|
||||
<button
|
||||
[disabled]="(loadingFetchedActionButton$ | async) || (loading$ | async)"
|
||||
class="cta-action"
|
||||
[class.cta-action-primary]="action.selected"
|
||||
[class.cta-action-secondary]="!action.selected"
|
||||
(click)="handleAction(action)"
|
||||
>
|
||||
<ui-spinner [show]="(loadingFetchedActionButton$ | async) || (loading$ | async)">{{ action.label }}</ui-spinner>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div class="actions z-fixed" *ngIf="actions$ | async; let actions">
|
||||
<button
|
||||
[disabled]="(loadingFetchedActionButton$ | async) || (loading$ | async)"
|
||||
class="cta-action"
|
||||
*ngFor="let action of actions"
|
||||
[class.cta-action-primary]="action.selected"
|
||||
[class.cta-action-secondary]="!action.selected"
|
||||
(click)="handleAction(action)"
|
||||
>
|
||||
<ui-spinner [show]="(loadingFetchedActionButton$ | async) || (loading$ | async)">{{ action.label }}</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
[cdkMenuTriggerFor]="navMenu"
|
||||
#menuTrigger="cdkMenuTriggerFor"
|
||||
[class.open]="menuTrigger.isOpen()"
|
||||
>
|
||||
>
|
||||
<shared-icon icon="apps" [size]="24"></shared-icon>
|
||||
<shared-icon [icon]="menuTrigger.isOpen() ? 'arrow-drop-up' : 'arrow-drop-down'" [size]="24"></shared-icon>
|
||||
</button>
|
||||
@@ -12,46 +12,42 @@
|
||||
<ng-template #navMenu>
|
||||
<div class="pt-1">
|
||||
<shared-menu>
|
||||
@if (customerDetailsRoute$ | async; as customerDetailsRoute) {
|
||||
<a
|
||||
sharedMenuItem
|
||||
[routerLink]="customerDetailsRoute.path"
|
||||
[queryParams]="customerDetailsRoute.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
>
|
||||
Kundendetails
|
||||
</a>
|
||||
}
|
||||
@if (ordersRoute$ | async; as ordersRoute) {
|
||||
<a
|
||||
sharedMenuItem
|
||||
[routerLink]="ordersRoute.path"
|
||||
[queryParams]="ordersRoute.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
>
|
||||
Bestellungen
|
||||
</a>
|
||||
}
|
||||
@if (kundenkarteRoute$ | async; as kundenkarteRoute) {
|
||||
<a
|
||||
sharedMenuItem
|
||||
[routerLink]="kundenkarteRoute.path"
|
||||
[queryParams]="kundenkarteRoute.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
>
|
||||
Kundenkarte
|
||||
</a>
|
||||
}
|
||||
@if (historyRoute$ | async; as historyRoute) {
|
||||
<a
|
||||
sharedMenuItem
|
||||
[routerLink]="historyRoute.path"
|
||||
[queryParams]="historyRoute.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
>
|
||||
Historie
|
||||
</a>
|
||||
}
|
||||
<a
|
||||
sharedMenuItem
|
||||
*ngIf="customerDetailsRoute$ | async; let customerDetailsRoute"
|
||||
[routerLink]="customerDetailsRoute.path"
|
||||
[queryParams]="customerDetailsRoute.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
>
|
||||
Kundendetails
|
||||
</a>
|
||||
<a
|
||||
sharedMenuItem
|
||||
*ngIf="ordersRoute$ | async; let ordersRoute"
|
||||
[routerLink]="ordersRoute.path"
|
||||
[queryParams]="ordersRoute.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
>
|
||||
Bestellungen
|
||||
</a>
|
||||
<a
|
||||
sharedMenuItem
|
||||
*ngIf="kundenkarteRoute$ | async; let kundenkarteRoute"
|
||||
[routerLink]="kundenkarteRoute.path"
|
||||
[queryParams]="kundenkarteRoute.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
>
|
||||
Kundenkarte
|
||||
</a>
|
||||
<a
|
||||
sharedMenuItem
|
||||
*ngIf="historyRoute$ | async; let historyRoute"
|
||||
[routerLink]="historyRoute.path"
|
||||
[queryParams]="historyRoute.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
>
|
||||
Historie
|
||||
</a>
|
||||
</shared-menu>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { map } from 'rxjs/operators';
|
||||
import { CustomerSearchNavigation } from '@shared/services/navigation';
|
||||
import { ComponentStore } from '@ngrx/component-store';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { AsyncPipe } from '@angular/common';
|
||||
import { AsyncPipe, NgIf } from '@angular/common';
|
||||
|
||||
export interface CustomerMenuComponentState {
|
||||
customerId?: number;
|
||||
@@ -26,7 +26,7 @@ export interface CustomerMenuComponentState {
|
||||
styleUrls: ['customer-menu.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: { class: 'page-customer-menu' },
|
||||
imports: [CdkMenuModule, SharedMenuModule, IconComponent, RouterLink, AsyncPipe],
|
||||
imports: [CdkMenuModule, SharedMenuModule, IconComponent, RouterLink, NgIf, AsyncPipe],
|
||||
})
|
||||
export class CustomerMenuComponent extends ComponentStore<CustomerMenuComponentState> {
|
||||
private _navigation = inject(CustomerSearchNavigation);
|
||||
|
||||
@@ -1,66 +1,59 @@
|
||||
@if (!compact) {
|
||||
<cdk-virtual-scroll-viewport
|
||||
itemSize="100"
|
||||
class="h-[calc(100vh-20.125rem)] desktop-small:h-[calc(100vh-18.625rem)]"
|
||||
(scrolledIndexChange)="scrolledIndexChange($event)"
|
||||
>
|
||||
<a
|
||||
*cdkVirtualFor="let customer of customers; trackBy: trackByFn; let index = index"
|
||||
[routerLink]="customerSearchNavigation.detailsRoute({ processId: processId, customerId: customer.id })?.path"
|
||||
[queryParams]="customerSearchNavigation.detailsRoute({ processId: processId, customerId: customer.id })?.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
(click)="scrolledIndexChange(index)"
|
||||
routerLinkActive
|
||||
#rla="routerLinkActive"
|
||||
>
|
||||
<page-customer-result-list-item-full [class.active]="rla.isActive" [customer]="customer"></page-customer-result-list-item-full>
|
||||
</a>
|
||||
@if (hits === customers?.length && !fetching) {
|
||||
<div class="h-[6.125rem] bg-white rounded px-4 py-3">
|
||||
<ng-container *ngTemplateOutlet="customerNotFound"></ng-container>
|
||||
</div>
|
||||
}
|
||||
</cdk-virtual-scroll-viewport>
|
||||
}
|
||||
<cdk-virtual-scroll-viewport
|
||||
itemSize="100"
|
||||
class="h-[calc(100vh-20.125rem)] desktop-small:h-[calc(100vh-18.625rem)]"
|
||||
*ngIf="!compact"
|
||||
(scrolledIndexChange)="scrolledIndexChange($event)"
|
||||
>
|
||||
<a
|
||||
*cdkVirtualFor="let customer of customers; trackBy: trackByFn; let index = index"
|
||||
[routerLink]="customerSearchNavigation.detailsRoute({ processId: processId, customerId: customer.id })?.path"
|
||||
[queryParams]="customerSearchNavigation.detailsRoute({ processId: processId, customerId: customer.id })?.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
(click)="scrolledIndexChange(index)"
|
||||
routerLinkActive
|
||||
#rla="routerLinkActive"
|
||||
>
|
||||
<page-customer-result-list-item-full [class.active]="rla.isActive" [customer]="customer"></page-customer-result-list-item-full>
|
||||
</a>
|
||||
<div class="h-[6.125rem] bg-white rounded px-4 py-3" *ngIf="hits === customers?.length && !fetching">
|
||||
<ng-container *ngTemplateOutlet="customerNotFound"></ng-container>
|
||||
</div>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
|
||||
@if (compact) {
|
||||
<cdk-virtual-scroll-viewport
|
||||
itemSize="191"
|
||||
class="h-[calc(100vh-20.75rem)]"
|
||||
(scrolledIndexChange)="scrolledIndexChange($event)"
|
||||
>
|
||||
<a
|
||||
*cdkVirtualFor="let customer of customers; trackBy: trackByFn; let index = index"
|
||||
[routerLink]="customerSearchNavigation.detailsRoute({ processId: processId, customerId: customer.id })?.path"
|
||||
[queryParams]="customerSearchNavigation.detailsRoute({ processId: processId, customerId: customer.id })?.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
(click)="scrolledIndexChange(index)"
|
||||
routerLinkActive
|
||||
#rla="routerLinkActive"
|
||||
>
|
||||
<page-customer-result-list-item [class.active]="rla.isActive" [customer]="customer"></page-customer-result-list-item>
|
||||
</a>
|
||||
@if (hits === customers?.length && !fetching) {
|
||||
<div class="h-[11.3125rem] bg-white rounded px-4 py-3">
|
||||
<ng-container *ngTemplateOutlet="customerNotFound"></ng-container>
|
||||
</div>
|
||||
}
|
||||
</cdk-virtual-scroll-viewport>
|
||||
}
|
||||
<cdk-virtual-scroll-viewport
|
||||
itemSize="191"
|
||||
class="h-[calc(100vh-20.75rem)]"
|
||||
*ngIf="compact"
|
||||
(scrolledIndexChange)="scrolledIndexChange($event)"
|
||||
>
|
||||
<a
|
||||
*cdkVirtualFor="let customer of customers; trackBy: trackByFn; let index = index"
|
||||
[routerLink]="customerSearchNavigation.detailsRoute({ processId: processId, customerId: customer.id })?.path"
|
||||
[queryParams]="customerSearchNavigation.detailsRoute({ processId: processId, customerId: customer.id })?.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
(click)="scrolledIndexChange(index)"
|
||||
routerLinkActive
|
||||
#rla="routerLinkActive"
|
||||
>
|
||||
<page-customer-result-list-item [class.active]="rla.isActive" [customer]="customer"></page-customer-result-list-item>
|
||||
</a>
|
||||
<div class="h-[11.3125rem] bg-white rounded px-4 py-3" *ngIf="hits === customers?.length && !fetching">
|
||||
<ng-container *ngTemplateOutlet="customerNotFound"></ng-container>
|
||||
</div>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
<ng-template #customerNotFound>
|
||||
<div class="text-sm">
|
||||
Hinweis: Aus Datenschutzgründen werden nur Teilinformationen dargestellt. Tab auf einen Kunden um mehr zu erfahren.
|
||||
</div>
|
||||
<div class="font-bold text-lg mt-3">
|
||||
<span>Kunden nicht gefunden?</span>
|
||||
@if (customerCreateNavigation.defaultRoute({ processId: processId }); as route) {
|
||||
<a
|
||||
[routerLink]="route.path"
|
||||
[queryParams]="route.queryParams"
|
||||
class="text-brand"
|
||||
>
|
||||
Neue Kundendaten erfassen
|
||||
</a>
|
||||
}
|
||||
<a
|
||||
*ngIf="customerCreateNavigation.defaultRoute({ processId: processId }); let route"
|
||||
[routerLink]="route.path"
|
||||
[queryParams]="route.queryParams"
|
||||
class="text-brand"
|
||||
>
|
||||
Neue Kundendaten erfassen
|
||||
</a>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
<ng-container *ifRole="'Store'">
|
||||
@if (customerType !== 'b2b') {
|
||||
<shared-checkbox
|
||||
[ngModel]="p4mUser"
|
||||
(ngModelChange)="setValue({ p4mUser: !p4mUser })"
|
||||
[disabled]="p4mReadonly || readonly"
|
||||
>
|
||||
Kundenkarte
|
||||
</shared-checkbox>
|
||||
}
|
||||
<shared-checkbox
|
||||
*ngIf="customerType !== 'b2b'"
|
||||
[ngModel]="p4mUser"
|
||||
(ngModelChange)="setValue({ p4mUser: !p4mUser })"
|
||||
[disabled]="p4mReadonly || readonly"
|
||||
>
|
||||
Kundenkarte
|
||||
</shared-checkbox>
|
||||
</ng-container>
|
||||
<ng-container *ngFor="let option of filteredOptions$ | async">
|
||||
<shared-checkbox
|
||||
*ngIf="option?.enabled !== false"
|
||||
[ngModel]="option.value === customerType"
|
||||
(ngModelChange)="setValue({ customerType: $event ? option.value : undefined })"
|
||||
[disabled]="isOptionDisabled(option)"
|
||||
[name]="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
</shared-checkbox>
|
||||
</ng-container>
|
||||
@for (option of filteredOptions$ | async; track option) {
|
||||
@if (option?.enabled !== false) {
|
||||
<shared-checkbox
|
||||
[ngModel]="option.value === customerType"
|
||||
(ngModelChange)="setValue({ customerType: $event ? option.value : undefined })"
|
||||
[disabled]="isOptionDisabled(option)"
|
||||
[name]="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
</shared-checkbox>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,59 +6,57 @@
|
||||
type="text"
|
||||
formControlName="street"
|
||||
[tabindex]="tabIndexStart"
|
||||
|
||||
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
<shared-form-control label="Hausnummer">
|
||||
<input
|
||||
placeholder="Hausnummer"
|
||||
class="input-control"
|
||||
type="text"
|
||||
formControlName="streetNumber"
|
||||
[tabindex]="tabIndexStart + 1"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
<shared-form-control label="PLZ">
|
||||
<input
|
||||
placeholder="PLZ"
|
||||
class="input-control"
|
||||
type="text"
|
||||
formControlName="zipCode"
|
||||
[tabindex]="tabIndexStart + 2"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
<shared-form-control label="Ort">
|
||||
<input
|
||||
placeholder="Ort"
|
||||
class="input-control"
|
||||
type="text"
|
||||
formControlName="city"
|
||||
[tabindex]="tabIndexStart + 3"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
/>
|
||||
</shared-form-control>
|
||||
<shared-form-control label="Hausnummer">
|
||||
<input
|
||||
placeholder="Hausnummer"
|
||||
class="input-control"
|
||||
type="text"
|
||||
formControlName="streetNumber"
|
||||
[tabindex]="tabIndexStart + 1"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
<shared-form-control label="PLZ">
|
||||
<input
|
||||
placeholder="PLZ"
|
||||
class="input-control"
|
||||
type="text"
|
||||
formControlName="zipCode"
|
||||
[tabindex]="tabIndexStart + 2"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
<shared-form-control label="Ort">
|
||||
<input
|
||||
placeholder="Ort"
|
||||
class="input-control"
|
||||
type="text"
|
||||
formControlName="city"
|
||||
[tabindex]="tabIndexStart + 3"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control class="col-span-2" label="Adresszusatz">
|
||||
<input
|
||||
placeholder="Adresszusatz"
|
||||
class="input-control"
|
||||
type="text"
|
||||
formControlName="info"
|
||||
[tabindex]="tabIndexStart + 4"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
<shared-form-control class="col-span-2" label="Adresszusatz">
|
||||
<input
|
||||
placeholder="Adresszusatz"
|
||||
class="input-control"
|
||||
type="text"
|
||||
formControlName="info"
|
||||
[tabindex]="tabIndexStart + 4"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control class="col-span-2" label="Land">
|
||||
<shared-select placeholder="Land" formControlName="country" [tabindex]="tabIndexStart + 5" [readonly]="readonly">
|
||||
@for (country of countries || (countries$ | async); track country) {
|
||||
<shared-select-option [value]="country.isO3166_A_3">
|
||||
{{ country.name }}
|
||||
</shared-select-option>
|
||||
}
|
||||
</shared-select>
|
||||
</shared-form-control>
|
||||
</ng-container>
|
||||
<shared-form-control class="col-span-2" label="Land">
|
||||
<shared-select placeholder="Land" formControlName="country" [tabindex]="tabIndexStart + 5" [readonly]="readonly">
|
||||
<shared-select-option *ngFor="let country of countries || (countries$ | async)" [value]="country.isO3166_A_3">
|
||||
{{ country.name }}
|
||||
</shared-select-option>
|
||||
</shared-select>
|
||||
</shared-form-control>
|
||||
</ng-container>
|
||||
|
||||
@@ -4,64 +4,59 @@
|
||||
[tabindex]="tabIndexStart"
|
||||
[autofocus]="focusAfterInit"
|
||||
[readonly]="readonly"
|
||||
>
|
||||
>
|
||||
<ng-content></ng-content>
|
||||
</shared-checkbox>
|
||||
@if (control.value.deviatingAddress) {
|
||||
<div class="address-block">
|
||||
<div class="wrapper">
|
||||
@if (organisation) {
|
||||
<app-organisation-form-block
|
||||
[tabIndexStart]="tabIndexStart + 1"
|
||||
#orgaBlock
|
||||
(onInit)="addOrganisationGroup($event)"
|
||||
(onDestroy)="removeOrganisationGroup()"
|
||||
[data]="data?.organisation"
|
||||
#nameFormBlock
|
||||
[tabIndexStart]="tabIndexStart + 1"
|
||||
[requiredMarks]="organisationRequiredMarks"
|
||||
[validatorFns]="organisationValidatorFns"
|
||||
[readonly]="readonly"
|
||||
></app-organisation-form-block>
|
||||
}
|
||||
<app-name-form-block
|
||||
(onInit)="addNameGroup($event)"
|
||||
(onDestroy)="removeNameGroup()"
|
||||
[data]="data?.name"
|
||||
#nameFormBlock
|
||||
[requiredMarks]="nameRequiredMarks"
|
||||
[validatorFns]="nameValidatorFns"
|
||||
[readonly]="readonly"
|
||||
></app-name-form-block>
|
||||
<app-address-form-block
|
||||
#addressFormBlock
|
||||
(onInit)="addAddressGroup($event)"
|
||||
(onDestroy)="removeAddressGroup()"
|
||||
[data]="data?.address"
|
||||
[requiredMarks]="addressRequiredMarks"
|
||||
[validatorFns]="addressValidatorFns"
|
||||
[readonly]="readonly"
|
||||
></app-address-form-block>
|
||||
@if (email) {
|
||||
<app-email-form-block
|
||||
#emailFormBlock
|
||||
(onInit)="addEmailGroup($event)"
|
||||
(onDestroy)="removeEmailGroup()"
|
||||
[data]="data?.email"
|
||||
[requiredMark]="emailRequiredMark"
|
||||
[validatorFns]="emailValidationFns"
|
||||
[readonly]="readonly"
|
||||
></app-email-form-block>
|
||||
}
|
||||
@if (phoneNumbers) {
|
||||
<app-phone-numbers-form-block
|
||||
(onInit)="addPhoneNumbersGroup($event)"
|
||||
(onDestroy)="removePhoneNumbersGroup()"
|
||||
[readonly]="readonly"
|
||||
>
|
||||
[tabIndexStart]="emailFormBlock?.tabIndexEnd+1" [requiredMarks]="phoneNumbersRequiredMarks" [validatorFns]="phoneNumbersValidatorFns">
|
||||
</app-phone-numbers-form-block>
|
||||
}
|
||||
</div>
|
||||
<div class="address-block" *ngIf="control.value.deviatingAddress">
|
||||
<div class="wrapper">
|
||||
<app-organisation-form-block
|
||||
*ngIf="organisation"
|
||||
[tabIndexStart]="tabIndexStart + 1"
|
||||
#orgaBlock
|
||||
(onInit)="addOrganisationGroup($event)"
|
||||
(onDestroy)="removeOrganisationGroup()"
|
||||
[data]="data?.organisation"
|
||||
#nameFormBlock
|
||||
[tabIndexStart]="tabIndexStart + 1"
|
||||
[requiredMarks]="organisationRequiredMarks"
|
||||
[validatorFns]="organisationValidatorFns"
|
||||
[readonly]="readonly"
|
||||
></app-organisation-form-block>
|
||||
<app-name-form-block
|
||||
(onInit)="addNameGroup($event)"
|
||||
(onDestroy)="removeNameGroup()"
|
||||
[data]="data?.name"
|
||||
#nameFormBlock
|
||||
[requiredMarks]="nameRequiredMarks"
|
||||
[validatorFns]="nameValidatorFns"
|
||||
[readonly]="readonly"
|
||||
></app-name-form-block>
|
||||
<app-address-form-block
|
||||
#addressFormBlock
|
||||
(onInit)="addAddressGroup($event)"
|
||||
(onDestroy)="removeAddressGroup()"
|
||||
[data]="data?.address"
|
||||
[requiredMarks]="addressRequiredMarks"
|
||||
[validatorFns]="addressValidatorFns"
|
||||
[readonly]="readonly"
|
||||
></app-address-form-block>
|
||||
<app-email-form-block
|
||||
*ngIf="email"
|
||||
#emailFormBlock
|
||||
(onInit)="addEmailGroup($event)"
|
||||
(onDestroy)="removeEmailGroup()"
|
||||
[data]="data?.email"
|
||||
[requiredMark]="emailRequiredMark"
|
||||
[validatorFns]="emailValidationFns"
|
||||
[readonly]="readonly"
|
||||
></app-email-form-block>
|
||||
<app-phone-numbers-form-block
|
||||
*ngIf="phoneNumbers"
|
||||
(onInit)="addPhoneNumbersGroup($event)"
|
||||
(onDestroy)="removePhoneNumbersGroup()"
|
||||
[readonly]="readonly"
|
||||
>
|
||||
[tabIndexStart]="emailFormBlock?.tabIndexEnd+1" [requiredMarks]="phoneNumbersRequiredMarks" [validatorFns]="phoneNumbersValidatorFns">
|
||||
</app-phone-numbers-form-block>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<div class="interests-description">Geben Sie Interessen an, um Ihre persönlichen Kontoangaben zu verfeinern.</div>
|
||||
<div class="interests-wrapper" [formGroup]="control">
|
||||
@for (pair of interests | keyvalue; track pair; let idx = $index) {
|
||||
<shared-checkbox
|
||||
[formControlName]="pair.key"
|
||||
[tabindex]="tabIndexStart + idx"
|
||||
[autofocus]="focusAfterInit"
|
||||
[readonly]="readonly"
|
||||
>
|
||||
{{ pair.value }}
|
||||
</shared-checkbox>
|
||||
}
|
||||
<shared-checkbox
|
||||
*ngFor="let pair of interests | keyvalue; let idx = index"
|
||||
[formControlName]="pair.key"
|
||||
[tabindex]="tabIndexStart + idx"
|
||||
[autofocus]="focusAfterInit"
|
||||
[readonly]="readonly"
|
||||
>
|
||||
{{ pair.value }}
|
||||
</shared-checkbox>
|
||||
</div>
|
||||
|
||||
@@ -6,10 +6,8 @@
|
||||
[readonly]="readonly"
|
||||
[tabindex]="tabIndexStart"
|
||||
[autofocus]="focusAfterInit"
|
||||
>
|
||||
@for (gender of genderSettings.genders; track gender) {
|
||||
<shared-select-option [value]="gender.value">{{ gender.label }}</shared-select-option>
|
||||
}
|
||||
>
|
||||
<shared-select-option *ngFor="let gender of genderSettings.genders" [value]="gender.value">{{ gender.label }}</shared-select-option>
|
||||
</shared-select>
|
||||
</shared-form-control>
|
||||
|
||||
@@ -32,7 +30,7 @@
|
||||
formControlName="lastName"
|
||||
[tabindex]="tabIndexStart + 2"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
/>
|
||||
</shared-form-control>
|
||||
|
||||
<shared-form-control label="Vorname">
|
||||
@@ -43,6 +41,6 @@
|
||||
formControlName="firstName"
|
||||
[tabindex]="tabIndexStart + 3"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
/>
|
||||
</shared-form-control>
|
||||
</ng-container>
|
||||
|
||||
@@ -6,30 +6,30 @@
|
||||
type="text"
|
||||
formControlName="name"
|
||||
[tabindex]="tabIndexStart"
|
||||
|
||||
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
<ng-container *ngIf="appearence === 'default'">
|
||||
<shared-form-control label="Abteilung">
|
||||
<input
|
||||
placeholder="Abteilung"
|
||||
class="input-control"
|
||||
type="text"
|
||||
formControlName="department"
|
||||
[tabindex]="tabIndexStart + 1"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
@if (appearence === 'default') {
|
||||
<shared-form-control label="Abteilung">
|
||||
<input
|
||||
placeholder="Abteilung"
|
||||
class="input-control"
|
||||
type="text"
|
||||
formControlName="department"
|
||||
[tabindex]="tabIndexStart + 1"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
<shared-form-control label="USt-ID">
|
||||
<input
|
||||
placeholder="USt-ID"
|
||||
class="input-control"
|
||||
type="text"
|
||||
formControlName="vatId"
|
||||
[tabindex]="tabIndexStart + 2"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
}
|
||||
</ng-container>
|
||||
<shared-form-control label="USt-ID">
|
||||
<input
|
||||
placeholder="USt-ID"
|
||||
class="input-control"
|
||||
type="text"
|
||||
formControlName="vatId"
|
||||
[tabindex]="tabIndexStart + 2"
|
||||
[readonly]="readonly"
|
||||
/>
|
||||
</shared-form-control>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
@@ -6,11 +6,9 @@
|
||||
[formControl]="control"
|
||||
[tabindex]="tabIndexStart"
|
||||
[readonly]="readonly"
|
||||
|
||||
/>
|
||||
</shared-form-control>
|
||||
@if (!readonly && canScan()) {
|
||||
<button type="button" (click)="scan()">
|
||||
<shared-icon icon="barcode-scan" [size]="32"></shared-icon>
|
||||
</button>
|
||||
}
|
||||
|
||||
/>
|
||||
</shared-form-control>
|
||||
<button type="button" *ngIf="!readonly && canScan()" (click)="scan()">
|
||||
<shared-icon icon="barcode-scan" [size]="32"></shared-icon>
|
||||
</button>
|
||||
|
||||
@@ -1,62 +1,38 @@
|
||||
<div class="wrapper text-center" [@cardFlip]="state" (@cardFlip.done)="flipAnimationDone($event)">
|
||||
@if (cardDetails) {
|
||||
<div class="card-main">
|
||||
<div class="icons text-brand">
|
||||
@if (isCustomerCard && frontside) {
|
||||
<button class="icon-barcode" (click)="flipCard()">
|
||||
<shared-icon [size]="35" icon="barcode-scanner"></shared-icon>
|
||||
</button>
|
||||
}
|
||||
@if (isCustomerCard && !frontside) {
|
||||
<button class="icon-back" (click)="flipCard()">
|
||||
<shared-icon [size]="35" icon="refresh"></shared-icon>
|
||||
</button>
|
||||
}
|
||||
<!-- <div *ngIf="!isCustomerCard" class="icon-delete"><ui-icon (click)="onDeletePartnerCard()" size="25px" icon="trash"></ui-icon></div> -->
|
||||
<div *ngIf="cardDetails" class="card-main">
|
||||
<div class="icons text-brand">
|
||||
<button *ngIf="isCustomerCard && frontside" class="icon-barcode" (click)="flipCard()">
|
||||
<shared-icon [size]="35" icon="barcode-scanner"></shared-icon>
|
||||
</button>
|
||||
<button *ngIf="isCustomerCard && !frontside" class="icon-back" (click)="flipCard()">
|
||||
<shared-icon [size]="35" icon="refresh"></shared-icon>
|
||||
</button>
|
||||
<!-- <div *ngIf="!isCustomerCard" class="icon-delete"><ui-icon (click)="onDeletePartnerCard()" size="25px" icon="trash"></ui-icon></div> -->
|
||||
</div>
|
||||
<div class="headline">
|
||||
<p *ngIf="isCustomerCard && frontside">Ihre Lesepunkte</p>
|
||||
<p *ngIf="isCustomerCard && !frontside">Kartennummer</p>
|
||||
<p *ngIf="!isCustomerCard">Partnerkartennummer</p>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<div *ngIf="!isCustomerCard || (isCustomerCard && !frontside)" class="card-number">{{ cardDetails.code }}</div>
|
||||
<div *ngIf="isCustomerCard && frontside" class="points">{{ cardDetails.totalPoints | number }}</div>
|
||||
</div>
|
||||
<div class="barcode-button">
|
||||
<div *ngIf="!isCustomerCard || (isCustomerCard && !frontside)" class="barcode-field">
|
||||
<img class="barcode" src="/assets/images/barcode.png" alt="Barcode" />
|
||||
</div>
|
||||
<div class="headline">
|
||||
@if (isCustomerCard && frontside) {
|
||||
<p>Ihre Lesepunkte</p>
|
||||
}
|
||||
@if (isCustomerCard && !frontside) {
|
||||
<p>Kartennummer</p>
|
||||
}
|
||||
@if (!isCustomerCard) {
|
||||
<p>Partnerkartennummer</p>
|
||||
}
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
@if (!isCustomerCard || (isCustomerCard && !frontside)) {
|
||||
<div class="card-number">{{ cardDetails.code }}</div>
|
||||
}
|
||||
@if (isCustomerCard && frontside) {
|
||||
<div class="points">{{ cardDetails.totalPoints | number }}</div>
|
||||
}
|
||||
</div>
|
||||
<div class="barcode-button">
|
||||
@if (!isCustomerCard || (isCustomerCard && !frontside)) {
|
||||
<div class="barcode-field">
|
||||
<img class="barcode" src="/assets/images/barcode.png" alt="Barcode" />
|
||||
</div>
|
||||
}
|
||||
@if (isCustomerCard && frontside) {
|
||||
<div>
|
||||
<button class="button" (click)="onRewardShop()">Zum Prämienshop</button>
|
||||
</div>
|
||||
}
|
||||
<div *ngIf="isCustomerCard && frontside">
|
||||
<button class="button" (click)="onRewardShop()">Zum Prämienshop</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="card-bottom">
|
||||
@if (!isCustomerCard || (isCustomerCard && !frontside)) {
|
||||
<div class="customer-name">
|
||||
<p>{{ cardDetails.firstName }} {{ cardDetails.lastName }}</p>
|
||||
</div>
|
||||
}
|
||||
@if (isCustomerCard && frontside) {
|
||||
<div class="logo ml-2">
|
||||
<img class="logo-picture" src="/assets/images/Hugendubel_Logo.png" alt="Hugendubel Logo" />
|
||||
</div>
|
||||
}
|
||||
<div *ngIf="!isCustomerCard || (isCustomerCard && !frontside)" class="customer-name">
|
||||
<p>{{ cardDetails.firstName }} {{ cardDetails.lastName }}</p>
|
||||
</div>
|
||||
<div *ngIf="isCustomerCard && frontside" class="logo ml-2">
|
||||
<img class="logo-picture" src="/assets/images/Hugendubel_Logo.png" alt="Hugendubel Logo" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||
import { DecimalPipe } from '@angular/common';
|
||||
import { DecimalPipe, NgIf } from '@angular/common';
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { IconComponent } from '@shared/components/icon';
|
||||
import { BonusCardInfoDTO } from '@generated/swagger/crm-api';
|
||||
@@ -8,7 +8,7 @@ import { BonusCardInfoDTO } from '@generated/swagger/crm-api';
|
||||
selector: 'page-customer-kundenkarte',
|
||||
templateUrl: 'kundenkarte.component.html',
|
||||
styleUrls: ['kundenkarte.component.scss'],
|
||||
imports: [IconComponent, DecimalPipe],
|
||||
imports: [IconComponent, NgIf, DecimalPipe],
|
||||
animations: [
|
||||
trigger('cardFlip', [
|
||||
state(
|
||||
|
||||
@@ -1,83 +1,89 @@
|
||||
@if (formData$ | async; as data) {
|
||||
<form (keydown.enter)="$event.preventDefault()">
|
||||
<h1 class="title">Kundendaten erfassen</h1>
|
||||
<p class="description">
|
||||
Um Ihnen den ausgewählten Service zu
|
||||
<br />
|
||||
ermöglichen, legen wir Ihnen gerne
|
||||
<br />
|
||||
ein Kundenkonto an.
|
||||
</p>
|
||||
<app-customer-type-selector
|
||||
[processId]="processId$ | async"
|
||||
[p4mUser]="false"
|
||||
customerType="b2b"
|
||||
(valueChanges)="customerTypeChanged($event)"
|
||||
></app-customer-type-selector>
|
||||
<app-organisation-form-block
|
||||
#orga
|
||||
[tabIndexStart]="1"
|
||||
[data]="data.organisation"
|
||||
(dataChanges)="patchFormData('organisation', $event)"
|
||||
(onInit)="addFormBlock('organisation', $event)"
|
||||
[requiredMarks]="organisationFormBlockRequiredMarks"
|
||||
[validatorFns]="organisationFormBlockValidators"
|
||||
></app-organisation-form-block>
|
||||
<app-name-form-block
|
||||
#name
|
||||
[tabIndexStart]="orga.tabIndexEnd + 1"
|
||||
[data]="data.name"
|
||||
(dataChanges)="patchFormData('name', $event)"
|
||||
(onInit)="addFormBlock('name', $event)"
|
||||
></app-name-form-block>
|
||||
<app-address-form-block
|
||||
#address
|
||||
[tabIndexStart]="name.tabIndexEnd + 1"
|
||||
[data]="data.address"
|
||||
(dataChanges)="patchFormData('address', $event)"
|
||||
(onInit)="addFormBlock('address', $event)"
|
||||
[requiredMarks]="addressRequiredMarks"
|
||||
[validatorFns]="addressValidators"
|
||||
[defaults]="{ country: 'DEU' }"
|
||||
></app-address-form-block>
|
||||
<app-email-form-block
|
||||
#email
|
||||
[tabIndexStart]="address.tabIndexEnd + 1"
|
||||
[data]="data.email"
|
||||
(dataChanges)="patchFormData('email', $event)"
|
||||
[validatorFns]="emailFormBlockValidators"
|
||||
(onInit)="addFormBlock('email', $event)"
|
||||
></app-email-form-block>
|
||||
<app-phone-numbers-form-block
|
||||
#phoneNumbers
|
||||
[tabIndexStart]="email.tabIndexEnd + 1"
|
||||
[data]="data.phoneNumbers"
|
||||
(dataChanges)="patchFormData('phoneNumbers', $event)"
|
||||
(onInit)="addFormBlock('phoneNumbers', $event)"
|
||||
></app-phone-numbers-form-block>
|
||||
<app-deviating-address-form-block
|
||||
[tabIndexStart]="phoneNumbers.tabIndexEnd + 1"
|
||||
[data]="data.deviatingDeliveryAddress"
|
||||
(dataChanges)="patchFormData('deviatingDeliveryAddress', $event)"
|
||||
(onInit)="addFormBlock('deviatingDeliveryAddress', $event)"
|
||||
[nameRequiredMarks]="deviatingNameRequiredMarks"
|
||||
[nameValidatorFns]="deviatingNameValidationFns"
|
||||
[addressRequiredMarks]="addressRequiredMarks"
|
||||
[addressValidatorFns]="addressValidators"
|
||||
[organisationRequiredMarks]="organisationFormBlockRequiredMarks"
|
||||
[organisationValidatorFns]="organisationFormBlockValidators"
|
||||
[defaults]="{ address: { country: 'DEU' } }"
|
||||
[organisation]="true"
|
||||
[email]="true"
|
||||
[phoneNumbers]="true"
|
||||
>
|
||||
Die Lieferadresse weicht von der Rechnungsadresse ab
|
||||
</app-deviating-address-form-block>
|
||||
<div class="spacer"></div>
|
||||
<div class="sticky w-full flex items-center justify-center">
|
||||
<button class="cta-submit" type="button" (click)="save()" [disabled]="form.invalid || form.pending">
|
||||
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
<form *ngIf="formData$ | async; let data" (keydown.enter)="$event.preventDefault()">
|
||||
<h1 class="title">Kundendaten erfassen</h1>
|
||||
<p class="description">
|
||||
Um Ihnen den ausgewählten Service zu
|
||||
<br />
|
||||
ermöglichen, legen wir Ihnen gerne
|
||||
<br />
|
||||
ein Kundenkonto an.
|
||||
</p>
|
||||
|
||||
<app-customer-type-selector
|
||||
[processId]="processId$ | async"
|
||||
[p4mUser]="false"
|
||||
customerType="b2b"
|
||||
(valueChanges)="customerTypeChanged($event)"
|
||||
></app-customer-type-selector>
|
||||
|
||||
<app-organisation-form-block
|
||||
#orga
|
||||
[tabIndexStart]="1"
|
||||
[data]="data.organisation"
|
||||
(dataChanges)="patchFormData('organisation', $event)"
|
||||
(onInit)="addFormBlock('organisation', $event)"
|
||||
[requiredMarks]="organisationFormBlockRequiredMarks"
|
||||
[validatorFns]="organisationFormBlockValidators"
|
||||
></app-organisation-form-block>
|
||||
|
||||
<app-name-form-block
|
||||
#name
|
||||
[tabIndexStart]="orga.tabIndexEnd + 1"
|
||||
[data]="data.name"
|
||||
(dataChanges)="patchFormData('name', $event)"
|
||||
(onInit)="addFormBlock('name', $event)"
|
||||
></app-name-form-block>
|
||||
|
||||
<app-address-form-block
|
||||
#address
|
||||
[tabIndexStart]="name.tabIndexEnd + 1"
|
||||
[data]="data.address"
|
||||
(dataChanges)="patchFormData('address', $event)"
|
||||
(onInit)="addFormBlock('address', $event)"
|
||||
[requiredMarks]="addressRequiredMarks"
|
||||
[validatorFns]="addressValidators"
|
||||
[defaults]="{ country: 'DEU' }"
|
||||
></app-address-form-block>
|
||||
|
||||
<app-email-form-block
|
||||
#email
|
||||
[tabIndexStart]="address.tabIndexEnd + 1"
|
||||
[data]="data.email"
|
||||
(dataChanges)="patchFormData('email', $event)"
|
||||
[validatorFns]="emailFormBlockValidators"
|
||||
(onInit)="addFormBlock('email', $event)"
|
||||
></app-email-form-block>
|
||||
|
||||
<app-phone-numbers-form-block
|
||||
#phoneNumbers
|
||||
[tabIndexStart]="email.tabIndexEnd + 1"
|
||||
[data]="data.phoneNumbers"
|
||||
(dataChanges)="patchFormData('phoneNumbers', $event)"
|
||||
(onInit)="addFormBlock('phoneNumbers', $event)"
|
||||
></app-phone-numbers-form-block>
|
||||
|
||||
<app-deviating-address-form-block
|
||||
[tabIndexStart]="phoneNumbers.tabIndexEnd + 1"
|
||||
[data]="data.deviatingDeliveryAddress"
|
||||
(dataChanges)="patchFormData('deviatingDeliveryAddress', $event)"
|
||||
(onInit)="addFormBlock('deviatingDeliveryAddress', $event)"
|
||||
[nameRequiredMarks]="deviatingNameRequiredMarks"
|
||||
[nameValidatorFns]="deviatingNameValidationFns"
|
||||
[addressRequiredMarks]="addressRequiredMarks"
|
||||
[addressValidatorFns]="addressValidators"
|
||||
[organisationRequiredMarks]="organisationFormBlockRequiredMarks"
|
||||
[organisationValidatorFns]="organisationFormBlockValidators"
|
||||
[defaults]="{ address: { country: 'DEU' } }"
|
||||
[organisation]="true"
|
||||
[email]="true"
|
||||
[phoneNumbers]="true"
|
||||
>
|
||||
Die Lieferadresse weicht von der Rechnungsadresse ab
|
||||
</app-deviating-address-form-block>
|
||||
|
||||
<div class="spacer"></div>
|
||||
<div class="sticky w-full flex items-center justify-center">
|
||||
<button class="cta-submit" type="button" (click)="save()" [disabled]="form.invalid || form.pending">
|
||||
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { ActivatedRoute, RouterModule } from '@angular/router';
|
||||
import { CustomerCreateSideViewModule } from './customer-create-side-view';
|
||||
@@ -10,7 +10,7 @@ import { SharedSplitscreenComponent } from '@shared/components/splitscreen';
|
||||
templateUrl: 'create-customer.component.html',
|
||||
styleUrls: ['create-customer.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [RouterModule, SharedSplitscreenComponent, CustomerCreateSideViewModule],
|
||||
imports: [CommonModule, RouterModule, SharedSplitscreenComponent, CustomerCreateSideViewModule],
|
||||
})
|
||||
export class CreateCustomerComponent {
|
||||
processId$ = this._activatedRoute.parent.data.pipe(map((data) => data.processId));
|
||||
|
||||
@@ -1,94 +1,102 @@
|
||||
@if (formData$ | async; as data) {
|
||||
<form (keydown.enter)="$event.preventDefault()">
|
||||
<h1 class="title">Kundendaten erfassen</h1>
|
||||
<p class="description">
|
||||
Um Ihnen den ausgewählten Service
|
||||
<br />
|
||||
zu ermöglichen, legen wir Ihnen
|
||||
<br />
|
||||
gerne ein Kundenkonto an.
|
||||
<br />
|
||||
</p>
|
||||
<app-customer-type-selector
|
||||
[processId]="processId$ | async"
|
||||
[p4mUser]="false"
|
||||
customerType="guest"
|
||||
(valueChanges)="customerTypeChanged($event)"
|
||||
></app-customer-type-selector>
|
||||
<app-name-form-block
|
||||
#name
|
||||
[tabIndexStart]="1"
|
||||
[data]="data.name"
|
||||
(dataChanges)="patchFormData('name', $event)"
|
||||
(onInit)="addFormBlock('name', $event)"
|
||||
[requiredMarks]="nameRequiredMarks"
|
||||
[validatorFns]="nameValidationFns"
|
||||
></app-name-form-block>
|
||||
<p class="info">
|
||||
Wir werden Ihnen Werbung zu ähnlichen Produkten oder Dienstleistungen aus unserem Sortiment per E-Mail zusenden. Sie können der
|
||||
Verwendung Ihrer Daten jederzeit z.B. mittels der in den E-Mails enthaltenen Abmeldelinks widersprechen, ohne dass hierfür andere als
|
||||
die Übermittlungskosten nach den Basistarifen entstehen.
|
||||
</p>
|
||||
<app-email-form-block
|
||||
#email
|
||||
[tabIndexStart]="name.tabIndexEnd + 1"
|
||||
[data]="data.email"
|
||||
[requiredMark]="true"
|
||||
(dataChanges)="patchFormData('email', $event)"
|
||||
[validatorFns]="emailFormBlockValidators"
|
||||
(onInit)="addFormBlock('email', $event)"
|
||||
></app-email-form-block>
|
||||
<app-organisation-form-block
|
||||
#orga
|
||||
[tabIndexStart]="email.tabIndexStart + 1"
|
||||
[data]="data.organisation"
|
||||
(dataChanges)="patchFormData('organisation', $event)"
|
||||
(onInit)="addFormBlock('organisation', $event)"
|
||||
appearence="compact"
|
||||
></app-organisation-form-block>
|
||||
<app-address-form-block
|
||||
#address
|
||||
[tabIndexStart]="orga.tabIndexEnd + 1"
|
||||
[data]="data.address"
|
||||
(dataChanges)="patchFormData('address', $event)"
|
||||
(onInit)="addFormBlock('address', $event)"
|
||||
[requiredMarks]="addressRequiredMarks"
|
||||
[validatorFns]="addressValidators"
|
||||
[defaults]="{ country: 'DEU' }"
|
||||
></app-address-form-block>
|
||||
<app-phone-numbers-form-block
|
||||
#phoneNumbers
|
||||
[tabIndexStart]="address.tabIndexEnd + 1"
|
||||
[data]="data.phoneNumbers"
|
||||
(dataChanges)="patchFormData('phoneNumbers', $event)"
|
||||
(onInit)="addFormBlock('phoneNumbers', $event)"
|
||||
></app-phone-numbers-form-block>
|
||||
<app-birth-date-form-block
|
||||
#birthDate
|
||||
[tabIndexStart]="phoneNumbers.tabIndexEnd + 1"
|
||||
(onInit)="addFormBlock('birthDate', $event)"
|
||||
[data]="data.birthDate"
|
||||
(dataChanges)="patchFormData('birthDate', $event)"
|
||||
></app-birth-date-form-block>
|
||||
<app-deviating-address-form-block
|
||||
[tabIndexStart]="birthDate.tabIndexEnd + 1"
|
||||
[data]="data.deviatingDeliveryAddress"
|
||||
(dataChanges)="patchFormData('deviatingDeliveryAddress', $event)"
|
||||
(onInit)="addFormBlock('deviatingDeliveryAddress', $event)"
|
||||
[nameRequiredMarks]="deviatingNameRequiredMarks"
|
||||
[nameValidatorFns]="deviatingNameValidationFns"
|
||||
[addressRequiredMarks]="addressRequiredMarks"
|
||||
[addressValidatorFns]="addressValidators"
|
||||
[defaults]="{ address: { country: 'DEU' } }"
|
||||
[organisation]="true"
|
||||
>
|
||||
Die Lieferadresse weicht von der Rechnungsadresse ab
|
||||
</app-deviating-address-form-block>
|
||||
<div class="spacer"></div>
|
||||
<div class="sticky w-full flex items-center justify-center">
|
||||
<button class="cta-submit" type="button" (click)="save()" [disabled]="form.invalid || form.pending">
|
||||
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
<form *ngIf="formData$ | async; let data" (keydown.enter)="$event.preventDefault()">
|
||||
<h1 class="title">Kundendaten erfassen</h1>
|
||||
<p class="description">
|
||||
Um Ihnen den ausgewählten Service
|
||||
<br />
|
||||
zu ermöglichen, legen wir Ihnen
|
||||
<br />
|
||||
gerne ein Kundenkonto an.
|
||||
<br />
|
||||
</p>
|
||||
|
||||
<app-customer-type-selector
|
||||
[processId]="processId$ | async"
|
||||
[p4mUser]="false"
|
||||
customerType="guest"
|
||||
(valueChanges)="customerTypeChanged($event)"
|
||||
></app-customer-type-selector>
|
||||
|
||||
<app-name-form-block
|
||||
#name
|
||||
[tabIndexStart]="1"
|
||||
[data]="data.name"
|
||||
(dataChanges)="patchFormData('name', $event)"
|
||||
(onInit)="addFormBlock('name', $event)"
|
||||
[requiredMarks]="nameRequiredMarks"
|
||||
[validatorFns]="nameValidationFns"
|
||||
></app-name-form-block>
|
||||
|
||||
<p class="info">
|
||||
Wir werden Ihnen Werbung zu ähnlichen Produkten oder Dienstleistungen aus unserem Sortiment per E-Mail zusenden. Sie können der
|
||||
Verwendung Ihrer Daten jederzeit z.B. mittels der in den E-Mails enthaltenen Abmeldelinks widersprechen, ohne dass hierfür andere als
|
||||
die Übermittlungskosten nach den Basistarifen entstehen.
|
||||
</p>
|
||||
|
||||
<app-email-form-block
|
||||
#email
|
||||
[tabIndexStart]="name.tabIndexEnd + 1"
|
||||
[data]="data.email"
|
||||
[requiredMark]="true"
|
||||
(dataChanges)="patchFormData('email', $event)"
|
||||
[validatorFns]="emailFormBlockValidators"
|
||||
(onInit)="addFormBlock('email', $event)"
|
||||
></app-email-form-block>
|
||||
|
||||
<app-organisation-form-block
|
||||
#orga
|
||||
[tabIndexStart]="email.tabIndexStart + 1"
|
||||
[data]="data.organisation"
|
||||
(dataChanges)="patchFormData('organisation', $event)"
|
||||
(onInit)="addFormBlock('organisation', $event)"
|
||||
appearence="compact"
|
||||
></app-organisation-form-block>
|
||||
|
||||
<app-address-form-block
|
||||
#address
|
||||
[tabIndexStart]="orga.tabIndexEnd + 1"
|
||||
[data]="data.address"
|
||||
(dataChanges)="patchFormData('address', $event)"
|
||||
(onInit)="addFormBlock('address', $event)"
|
||||
[requiredMarks]="addressRequiredMarks"
|
||||
[validatorFns]="addressValidators"
|
||||
[defaults]="{ country: 'DEU' }"
|
||||
></app-address-form-block>
|
||||
|
||||
<app-phone-numbers-form-block
|
||||
#phoneNumbers
|
||||
[tabIndexStart]="address.tabIndexEnd + 1"
|
||||
[data]="data.phoneNumbers"
|
||||
(dataChanges)="patchFormData('phoneNumbers', $event)"
|
||||
(onInit)="addFormBlock('phoneNumbers', $event)"
|
||||
></app-phone-numbers-form-block>
|
||||
|
||||
<app-birth-date-form-block
|
||||
#birthDate
|
||||
[tabIndexStart]="phoneNumbers.tabIndexEnd + 1"
|
||||
(onInit)="addFormBlock('birthDate', $event)"
|
||||
[data]="data.birthDate"
|
||||
(dataChanges)="patchFormData('birthDate', $event)"
|
||||
></app-birth-date-form-block>
|
||||
|
||||
<app-deviating-address-form-block
|
||||
[tabIndexStart]="birthDate.tabIndexEnd + 1"
|
||||
[data]="data.deviatingDeliveryAddress"
|
||||
(dataChanges)="patchFormData('deviatingDeliveryAddress', $event)"
|
||||
(onInit)="addFormBlock('deviatingDeliveryAddress', $event)"
|
||||
[nameRequiredMarks]="deviatingNameRequiredMarks"
|
||||
[nameValidatorFns]="deviatingNameValidationFns"
|
||||
[addressRequiredMarks]="addressRequiredMarks"
|
||||
[addressValidatorFns]="addressValidators"
|
||||
[defaults]="{ address: { country: 'DEU' } }"
|
||||
[organisation]="true"
|
||||
>
|
||||
Die Lieferadresse weicht von der Rechnungsadresse ab
|
||||
</app-deviating-address-form-block>
|
||||
|
||||
<div class="spacer"></div>
|
||||
<div class="sticky w-full flex items-center justify-center">
|
||||
<button class="cta-submit" type="button" (click)="save()" [disabled]="form.invalid || form.pending">
|
||||
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
@if (formData$ | async; as data) {
|
||||
<form (keydown.enter)="$event.preventDefault()">
|
||||
<h1 class="title flex flex-row items-center justify-center">
|
||||
Kundendaten erfassen
|
||||
<!-- <span
|
||||
class="rounded-full ml-4 h-8 w-8 text-xl text-center border-2 border-solid border-brand text-brand">i</span> -->
|
||||
<form *ngIf="formData$ | async; let data" (keydown.enter)="$event.preventDefault()">
|
||||
<h1 class="title flex flex-row items-center justify-center">
|
||||
Kundendaten erfassen
|
||||
<!-- <span
|
||||
class="rounded-full ml-4 h-8 w-8 text-xl text-center border-2 border-solid border-brand text-brand">i</span> -->
|
||||
</h1>
|
||||
|
||||
<p class="description">
|
||||
Um Sie als Kunde beim nächsten
|
||||
<br />
|
||||
@@ -12,6 +12,7 @@
|
||||
<br />
|
||||
wir Ihnen gerne eine Kundenkarte an.
|
||||
</p>
|
||||
|
||||
<app-customer-type-selector
|
||||
[processId]="processId$ | async"
|
||||
[p4mUser]="true"
|
||||
@@ -19,6 +20,7 @@
|
||||
(valueChanges)="customerTypeChanged($event)"
|
||||
[p4mReadonly]="data?._meta?.p4mRequired"
|
||||
></app-customer-type-selector>
|
||||
|
||||
<app-p4m-number-form-block
|
||||
#p4mBlock
|
||||
[tabIndexStart]="1"
|
||||
@@ -29,6 +31,7 @@
|
||||
[focusAfterInit]="!data?._meta?.p4mRequired"
|
||||
[asyncValidatorFns]="asyncLoyaltyCardValidatorFn"
|
||||
></app-p4m-number-form-block>
|
||||
|
||||
<app-accept-agb-form-block
|
||||
[tabIndexStart]="inBlock.tabIndexEnd + 1"
|
||||
(onInit)="addFormBlock('agb', $event)"
|
||||
@@ -37,6 +40,7 @@
|
||||
[requiredMark]="true"
|
||||
[validatorFns]="agbValidatorFns"
|
||||
></app-accept-agb-form-block>
|
||||
|
||||
<app-newsletter-form-block
|
||||
class="mb-4"
|
||||
#newsletterBlock
|
||||
@@ -46,6 +50,7 @@
|
||||
(dataChanges)="patchFormData('newsletter', $event)"
|
||||
[focusAfterInit]="data?._meta?.p4mRequired"
|
||||
></app-newsletter-form-block>
|
||||
|
||||
<app-name-form-block
|
||||
#nameBlock
|
||||
[tabIndexStart]="newsletterBlock.tabIndexEnd + 1"
|
||||
@@ -55,13 +60,13 @@
|
||||
[validatorFns]="nameValidationFns"
|
||||
(dataChanges)="patchFormData('name', $event)"
|
||||
></app-name-form-block>
|
||||
@if (customerType === 'webshop-p4m') {
|
||||
<p class="info">
|
||||
Wir werden Ihnen Werbung zu ähnlichen Produkten oder Dienstleistungen aus unserem Sortiment per E-Mail zusenden. Sie können der
|
||||
Verwendung Ihrer Daten jederzeit z.B. mittels der in den E-Mails enthaltenen Abmeldelinks widersprechen, ohne dass hierfür andere als
|
||||
die Übermittlungskosten nach den Basistarifen entstehen.
|
||||
</p>
|
||||
}
|
||||
|
||||
<p class="info" *ngIf="customerType === 'webshop-p4m'">
|
||||
Wir werden Ihnen Werbung zu ähnlichen Produkten oder Dienstleistungen aus unserem Sortiment per E-Mail zusenden. Sie können der
|
||||
Verwendung Ihrer Daten jederzeit z.B. mittels der in den E-Mails enthaltenen Abmeldelinks widersprechen, ohne dass hierfür andere als
|
||||
die Übermittlungskosten nach den Basistarifen entstehen.
|
||||
</p>
|
||||
|
||||
<app-email-form-block
|
||||
class="flex-grow"
|
||||
#email
|
||||
@@ -73,6 +78,7 @@
|
||||
[validatorFns]="emailValidatorFn"
|
||||
[asyncValidatorFns]="asyncEmailVlaidtorFn"
|
||||
></app-email-form-block>
|
||||
|
||||
<app-organisation-form-block
|
||||
#orgBlock
|
||||
[tabIndexStart]="email.tabIndexEnd + 1"
|
||||
@@ -81,6 +87,7 @@
|
||||
[data]="data.organisation"
|
||||
(dataChanges)="patchFormData('organisation', $event)"
|
||||
></app-organisation-form-block>
|
||||
|
||||
<app-address-form-block
|
||||
[defaults]="{ country: 'DEU' }"
|
||||
#addressBlock
|
||||
@@ -91,6 +98,7 @@
|
||||
[requiredMarks]="addressRequiredMarks"
|
||||
[validatorFns]="addressValidatorFns"
|
||||
></app-address-form-block>
|
||||
|
||||
<app-deviating-address-form-block
|
||||
#ddaBlock
|
||||
[defaults]="{ address: { country: 'DEU' } }"
|
||||
@@ -102,9 +110,10 @@
|
||||
[nameValidatorFns]="nameValidationFns"
|
||||
[addressRequiredMarks]="shippingAddressRequiredMarks"
|
||||
[addressValidatorFns]="shippingAddressValidators"
|
||||
>
|
||||
>
|
||||
Die Lieferadresse weicht von der Rechnungsadresse ab
|
||||
</app-deviating-address-form-block>
|
||||
|
||||
<app-phone-numbers-form-block
|
||||
#phoneNumbers
|
||||
[tabIndexStart]="ddaBlock.tabIndexEnd + 1"
|
||||
@@ -112,6 +121,7 @@
|
||||
[data]="data.phoneNumbers"
|
||||
(dataChanges)="patchFormData('phoneNumbers', $event)"
|
||||
></app-phone-numbers-form-block>
|
||||
|
||||
<app-birth-date-form-block
|
||||
#bdBlock
|
||||
[tabIndexStart]="phoneNumbers.tabIndexEnd + 1"
|
||||
@@ -121,6 +131,7 @@
|
||||
[requiredMark]="true"
|
||||
[validatorFns]="birthDateValidatorFns"
|
||||
></app-birth-date-form-block>
|
||||
|
||||
<app-interests-form-block
|
||||
#inBlock
|
||||
[tabIndexStart]="bdBlock.tabIndexEnd + 1"
|
||||
@@ -128,11 +139,12 @@
|
||||
[data]="data.interests"
|
||||
(dataChanges)="patchFormData('interests', $event)"
|
||||
></app-interests-form-block>
|
||||
|
||||
<div class="spacer"></div>
|
||||
|
||||
<div class="sticky w-full flex items-center justify-center">
|
||||
<button class="cta-submit" type="button" [disabled]="form.invalid || form.pending" (click)="save()">
|
||||
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user