Compare commits

..

3 Commits

Author SHA1 Message Date
Lorenz Hilpert
3b466f7d65 css tag hinzugefügt für e2e 2023-01-19 13:04:46 +01:00
Nino Righi
62d93d1122 #3632 Updated Unit Test 2023-01-09 14:17:26 +01:00
Nino Righi
ed83fedd61 #3632 Fix Footer Active Styling 2023-01-09 14:02:23 +01:00
7262 changed files with 99460 additions and 590135 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -1,86 +0,0 @@
# 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.
## Format
The commit message structure is as follows:
```
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
```
---
### Components
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
```

View File

@@ -1,415 +0,0 @@
## ISA Frontend AI Assistant Working Rules
Concise, project-specific guidance so an AI agent can be productive quickly. Focus on THESE patterns; avoid generic boilerplate.
### 1. Monorepo & Tooling
- Nx workspace (Angular 20 + Libraries under `libs/**`, main app `apps/isa-app`).
- Scripts (see `package.json`):
- Dev serve: `npm start` (=> `nx serve isa-app --ssl`).
- Library tests (exclude app): `npm test` (Jest + emerging Vitest). CI uses `npm run ci`.
- Build dev: `npm run build`; prod: `npm run build-prod`.
- Storybook: `npm run storybook`.
- Swagger codegen: `npm run generate:swagger` then `npm run fix:files:swagger`.
- Default branch in Nx: `develop` (`nx.json: defaultBase`). Use affected commands when adding libs.
- Node >=22, TS 5.8, ESLint flat config (`eslint.config.js`).
### 1.a Project Tree (Detailed Overview)
```
.
├─ apps/
│ └─ isa-app/ # Main Angular app (Jest). Legacy non-standalone root component pattern.
│ ├─ project.json # Build/serve/test targets
│ ├─ src/
│ │ ├─ main.ts / index.html # Angular bootstrap
│ │ ├─ app/main.component.ts # Root component (standalone:false)
│ │ ├─ environments/ # Environment files (prod replace)
│ │ ├─ assets/ # Static assets
│ │ └─ config/ # Runtime config JSON (read via Config service)
│ └─ .storybook/ # App Storybook config
├─ libs/ # All reusable code (grouped by domain / concern)
│ ├─ core/ # Cross-cutting infrastructure
│ │ ├─ logging/ # Logging service + providers + sinks
│ │ │ ├─ src/lib/logging.service.ts
│ │ │ ├─ src/lib/logging.providers.ts
│ │ │ └─ README.md # Full API & patterns
│ │ ├─ config/ # `Config` service (Zod validated lookup)
│ │ └─ storage/ # User-scoped storage + signal store feature (`withStorage`)
│ │ ├─ src/lib/signal-store-feature.ts
│ │ └─ src/lib/storage.ts
│ │
│ ├─ shared/ # Shared UI/services not domain specific
│ │ └─ scanner/ # Scandit integration (tokens, service, components, platform gating)
│ │ ├─ src/lib/scanner.service.ts
│ │ └─ src/lib/render-if-scanner-is-ready.directive.ts
│ │
│ ├─ remission/ # Remission domain features (newer pattern; Vitest)
│ │ ├─ feature/
│ │ │ ├─ remission-return-receipt-details/
│ │ │ │ ├─ vite.config.mts # Signals + Vitest example
│ │ │ │ └─ src/lib/resources/ # Resource factories (signals async pattern)
│ │ │ └─ remission-return-receipt-list/
│ │ └─ shared/ # Dialogs / shared remission UI pieces
│ │
│ ├─ common/ # Cross-domain utilities (decorators, print, data-access)
│ ├─ utils/ # Narrow utility libs (ean-validation, z-safe-parse, etc.)
│ ├─ ui/ # Generic UI components (presentational)
│ ├─ icons/ # Icon sets / wrappers
│ ├─ catalogue/ # Domain area (legacy Jest)
│ ├─ customer/ # Domain area (legacy Jest)
│ └─ oms/ # Domain area (legacy Jest)
├─ generated/swagger/ # Generated API clients (regen via scripts; do not hand edit)
├─ tools/ # Helper scripts (e.g. swagger fix script)
├─ testresults/ # JUnit XML (jest-junit). CI artifact pickup.
├─ coverage/ # Per-project coverage outputs
├─ tailwind-plugins/ # Custom Tailwind plugin modules used by `tailwind.config.js`
├─ vitest.workspace.ts # Glob enabling multi-lib Vitest detection
├─ nx.json / package.json # Workspace + scripts + defaultBase=develop
└─ eslint.config.js # Flat ESLint root config
```
Guidelines: create new code in the closest domain folder; expose public API via each lib `src/index.ts`; follow existing naming (`feature-name.type.ts`). Keep generated swagger untouched—extend via wrapper libs if needed.
### 1.b Import Path Aliases
Use existing TS path aliases (see `tsconfig.base.json`) instead of long relative paths:
Core / Cross-cutting:
- `@isa/core/logging`, `@isa/core/config`, `@isa/core/storage`, `@isa/core/tabs`, `@isa/core/notifications`
Domain & Features:
- Catalogue: `@isa/catalogue/data-access`
- Customer: `@isa/customer/data-access`
- OMS features: `@isa/oms/feature/return-details`, `.../return-process`, `.../return-review`, `.../return-search`, `.../return-summary`
- OMS shared: `@isa/oms/shared/product-info`, `@isa/oms/shared/task-list`
- Remission: `@isa/remission/data-access`, feature libs (`@isa/remission/feature/remission-return-receipt-details`, `...-list`) and shared (`@isa/remission/shared/remission-start-dialog`, `.../search-item-to-remit-dialog`, `.../return-receipt-actions`, `.../product`)
Shared / UI:
- Shared libs: `@isa/shared/scanner`, `@isa/shared/filter`, `@isa/shared/product-image`, `@isa/shared/product-router-link`, `@isa/shared/product-format`
- UI components: `@isa/ui/buttons`, `@isa/ui/dialog`, `@isa/ui/input-controls`, `@isa/ui/layout`, `@isa/ui/menu`, `@isa/ui/toolbar`, etc. (one alias per folder under `libs/ui/*`)
- Icons: `@isa/icons`
Utilities:
- `@isa/utils/ean-validation`, `@isa/utils/z-safe-parse`, `@isa/utils/scroll-position`
Generated Swagger Clients:
- `@generated/swagger/isa-api`, `@generated/swagger/oms-api`, `@generated/swagger/inventory-api`, etc. (one per subfolder). Never edit generated sources—wrap in a domain lib if extension needed.
App-local (only inside `apps/isa-app` context):
- Namespaced folders: `@adapter/*`, `@domain/*`, `@hub/*`, `@modal/*`, `@page/*`, `@shared/*` (and nested: `@shared/components/*`, `@shared/services/*`, etc.), `@ui/*`, `@utils/*`, `@swagger/*`.
Patterns:
- Always add new reusable code as a library then expose via an `@isa/...` alias; do not add new generic code under app-local aliases if it may be reused later.
- When introducing a new library ensure its `src/index.ts` re-exports only stable public surface; internal helpers stay un-exported.
- For new generated API groups, extend via thin wrappers in a domain `data-access` lib rather than patching generated code.
### 2. Testing Strategy
- Legacy tests: Jest (`@nx/jest:jest`). New feature libs (e.g. remission feature) use Vitest + Vite plugin (`vite.config.mts`).
- When adding a new library today prefer Vitest unless consistency with existing Jest-only area is required.
- Do NOT mix frameworks inside one lib. Check presence of `vite.config.*` to know it is Vitest-enabled.
- App (`isa-app`) still uses Jest.
### 3. Architecture & Cross-Cutting Services
- Core libraries underpin features: `@isa/core/logging`, `@isa/core/config`, `@isa/core/storage`.
- Feature domains grouped (e.g. `libs/remission/**`, `libs/shared/**`, `libs/common/**`). Keep domain-specific code there; UI-only pieces in `ui/` or `shared/`.
- Prefer standalone components but some legacy components set `standalone: false` (see `MainComponent`). Maintain existing pattern unless doing a focused migration.
### 4. Logging (Critical Pattern)
- Central logging via `@isa/core/logging` (files: `logging.service.ts`, `logging.providers.ts`).
- Configure once in app config using provider builders: `provideLogging(withLogLevel(...), withSink(ConsoleLogSink), withContext({...}))`.
- Use factory `logger(() => ({ dynamicContext }))` (see README) rather than injecting `LoggingService` directly unless extending framework code.
- Context hierarchy: global -> component (`provideLoggerContext`) -> instance (factory param) -> message (callback arg). Always pass context as lazy function `() => ({ ... })` for perf.
- Respect log level threshold; do not perform expensive serialization before calling (let sinks handle it or gate behind dev checks).
### 5. Configuration Access
- Use `Config` service (`@isa/core/config/src/lib/config.ts`). Fetch values with Zod schema: `config.get('licence.scandit', z.string())` (see `SCANDIT_LICENSE` token). Avoid deprecated untyped access.
### 6. Storage & State Persistence
- Storage abstraction: `injectStorage(SomeProvider)` wraps a `StorageProvider` (local/session/indexedDB/custom user storage) and prefixes keys with current authenticated user `sub` (OAuth `sub` fallback 'anonymous').
- When adding persisted signal stores, use `withStorage(storageKey, ProviderType)` feature (`signal-store-feature.ts`) to auto debounce-save (1s) + restore on init. Only pass plain serializable state.
### 7. Signals & State
- Internal state often via Angular signals & NgRx Signals (`@ngrx/signals`). Avoid manual subscriptions—prefer computed/signals and `rxMethod` for side effects.
- When persisting, ensure objects are JSON-safe; validation via Zod if deserializing external data.
#### 7.a NgRx Signals Deep Dive
Core building blocks we use:
- `signalStore(...)` + features: `withState`, `withComputed`, `withMethods`, `withHooks`, `withStorage` (custom feature in `core/storage`).
- `rxMethod` (from `@ngrx/signals/rxjs-interop`) to bridge imperative async flows (HTTP calls, debounce, switchMap) into store-driven mutations.
- `getState`, `patchState` for immutable, shallow merges; avoid manually mutating nested objects—spread + patch.
Patterns:
1. Store Shape: Keep initial state small & serializable (no class instances, functions, DOM nodes). Derive heavy or view-specific projections with `withComputed`.
2. Side Effects: Wrap fetch/update flows inside `rxMethod` pipes; ensure cancellation semantics (`switchMap`) to drop stale requests.
3. Persistence: Apply `withStorage(key, Provider)` last so hooks run after other features; persisted state must be plain JSON (no Dates—convert to ISO strings). Debounce already handled (1s) in `withStorage`—do NOT add another debounce upstream unless burst traffic is extreme.
4. Error Handling: Keep an `error` field in state for presentation; log via `logger()` at Warn/Error levels but do not store full Error object (serialize minimal fields: `message`, maybe `code`).
5. Loading Flags: Prefer a boolean `loading` OR a discriminated union `status: 'idle'|'loading'|'success'|'error'` for richer UI; avoid multiple booleans that can drift.
6. Computed Selectors: Name as `XComputed` or just semantic (e.g. `filteredItems`) using `computed(() => ...)` inside `withComputed`; never cause side-effects in a computed.
7. Resource Factory Pattern: For remote data needed in multiple components, create a factory function returning an object with `value`, `isLoading`, `error` signals plus a `reload()` method; see remission `resources/` directory.
Store Lifecycle Hooks:
- Use `withHooks({ onInit() { ... }, onDestroy() { ... } })` for restoration, websockets, or timers. Pair cleanups explicitly.
Persistence Feature (`withStorage`):
- Implementation: Debounced `storeState` rxMethod listens to any state change, saves hashed userscoped key (see `hash.utils.ts`). On init it calls `restoreState()`.
- Extending: If you need to blacklist transient fields from persistence, add a method wrapping `getState` and remove keys before `storage.set` (extend feature locally rather than editing shared code unless broadly needed).
Typical Store Template:
```ts
// feature-x.store.ts
import {
signalStore,
withState,
withComputed,
withMethods,
withHooks,
} from '@ngrx/signals';
import { rxMethod } from '@ngrx/signals/rxjs-interop';
import { debounceTime, switchMap, tap, catchError, of } from 'rxjs';
import { withStorage } from '@isa/core/storage';
import { logger } from '@isa/core/logging';
interface FeatureXState {
items: ReadonlyArray<Item>;
query: string;
loading: boolean;
error?: string;
}
const initialState: FeatureXState = { items: [], query: '', loading: false };
export const FeatureXStore = signalStore(
withState(initialState),
withProps((store, logger = logger(() => ({ store: 'FeatureX' }))) => ({
_logger: logger,
})),
withComputed(({ items, query }) => ({
filtered: computed(() => items().filter((i) => i.name.includes(query()))),
hasError: computed(() => !!query() && !items().length),
})),
withMethods((store) => ({
setQuery: (q: string) => patchState(store, { query: q }),
// rxMethod side effect to load items
loadItems: rxMethod<string | void>(
pipe(
debounceTime(150),
tap(() => patchState(store, { loading: true, error: undefined })),
switchMap(() =>
fetchItems(store.query()).pipe(
tap((items) => patchState(store, { items, loading: false })),
catchError((err) => {
store._logger.error('Load failed', err as Error, () => ({
query: store.query(),
}));
patchState(store, {
loading: false,
error: (err as Error).message,
});
return of([]);
}),
),
),
),
),
})),
withHooks((store) => ({
onInit() {
store.loadItems();
},
})),
withStorage('feature-x', LocalStorageProvider),
);
```
Testing Signal Stores (Vitest or Jest):
- Use `runInInjectionContext(TestBed.inject(Injector), () => FeatureXStore)` or instantiate via exported factory if provided.
- For async rxMethod flows, flush microtasks (`await vi.runAllTimersAsync()` if timers used) or rely on returned observable completion when you subscribe inside the test harness.
- Snapshot only primitive slices (avoid full object snapshots with volatile ordering).
Migration Tips:
- Converting legacy NgRx reducers: Start by lifting static initial state + selectors into `withState` + `withComputed`; replace effects with `rxMethod` maintaining cancellation semantics (`switchMap` mirrors effect flattening strategy).
- Keep action names only if externally observed (analytics, logging). Otherwise remove ceremony—call store methods directly.
Anti-Patterns to Avoid:
- Writing to signals inside a computed or inside another signal setter (causes cascading updates).
- Storing large unnormalized arrays and then repeatedly filtering/sorting in multiple components—centralize that in computed selectors.
- Persisting secrets or PII directly; hash keys already user-scoped but content still plain—sanitize if needed.
- Returning raw subscriptions from store methods; expose signals or idempotent methods only.
#### 7.b Prefer Signals over Observables (Practical Rules)
Default to signals for all in-memory UI & derived state; keep Observables only at I/O edges.
Use Observables for:
- HTTP / WebSocket / SignalR streams at the boundary.
- Timer / interval / external event sources.
- Interop with legacy NgRx store pieces not yet migrated.
Immediately convert inbound Observables to signals:
```ts
// Legacy service returning Observable<Item[]>
items$ = http.get<Item[]>(url);
// New pattern
const items = toSignal(http.get<Item[]>(url), { initialValue: [] });
```
Expose signals from stores & services:
```ts
// BAD (forces template async pipe + subscription mgmt)
getItems(): Observable<Item[]> { return this.http.get(...); }
// GOOD
items = toSignal(this.http.get<Item[]>(url), { initialValue: [] });
```
Bridge when needed:
```ts
// Signal -> Observable (rare):
const queryChanges$ = fromSignal(query, { requireSync: true });
// Observable -> Signal (preferred):
const data = toSignal(data$, { initialValue: undefined });
```
Side-effects: never subscribe manually—wrap in `rxMethod` (cancels stale work via `switchMap`).
```ts
loadData: rxMethod<void>(
pipe(
switchMap(() =>
this.api.fetch().pipe(tap((r) => patchState(store, { data: r }))),
),
),
);
```
Template usage: reference signals directly (`{{ item.name }}`) or in control flow; no `| async` needed.
Replacing combineLatest / map chains:
```ts
// Before (Observable)
vm$ = combineLatest([a$, b$]).pipe(map(([a, b]) => buildVm(a, b)));
// After (Signals)
const vm = computed(() => buildVm(a(), b()));
```
Debounce / throttle user input:
Keep raw form value as a signal; create an rxMethod for debounced fetch instead of debouncing inside a computed.
```ts
search = signal('');
runSearch: rxMethod<string>(
pipe(
debounceTime(300),
switchMap((term) =>
this.api
.search(term)
.pipe(tap((results) => patchState(store, { results }))),
),
),
);
effect(() => {
runSearch(this.search());
});
```
Avoid converting a signal back to an Observable just to use a single RxJS operator; prefer inline signal `computed` or small helper.
Migration heuristic:
1. Identify component `foo$` fields used only in template -> convert to signal via `toSignal`.
2. Collapse chains of `combineLatest` + `map` into `computed`.
3. Replace imperative `subscribe` side-effects with `rxMethod` + `patchState`.
4. Add persistence last via `withStorage` if state must survive reload.
Performance tip: heavy derived computations (sorting large arrays) belong in a memoized `computed`; if expensive & infrequently needed, gate behind another signal flag.
### 8. Scanner Integration (Scandit)
- Barcode scanning encapsulated in `@isa/shared/scanner` (`scanner.service.ts`). Use provided injection tokens for license & defaults (override via DI if needed). Service auto-configures once; `ready` signal triggers `configure()` lazily.
- Always catch and log errors with proper context; platform gating throws `PlatformNotSupportedError` which is downgraded to warn.
### 9. Styling
- Tailwind with custom semantic tokens (`tailwind.config.js`). Prefer design tokens like `text-isa-neutral-700`, spacing utilities with custom `px-*` scales rather than adhoc raw values.
- Global overlays rely on CDK classes; retain `@angular/cdk/overlay-prebuilt.css` in style arrays when creating new entrypoints or Storybook stories.
### 10. Library Conventions
- File naming: kebab-case; feature first then type (e.g. `return-receipt-list.component.ts`).
- Provide public API via each lib `src/index.ts`. Export only stable symbols; keep internal utilities in subfolders not re-exported.
- Add `project.json` with `test` & `lint` targets; for new Vitest libs include `vite.config.mts` and adjust `tsconfig.spec.json` references to vitest types.
### 11. Adding / Modifying Tests
- For Jest libs: standard `*.spec.ts` with `TestBed`. Spectator may appear in legacy code—do not introduce Spectator in new tests; use Angular Testing Utilities.
- For Vitest libs: ensure `vite.config.mts` includes `setupFiles`. Use `describe/it` from `vitest` and Angular TestBed (see remission resource spec for pattern of using `runInInjectionContext`).
- Prefer resource-style factories returning signals for async state (pattern in `createSupplierResource`).
### 12. Performance & Safety
- Logging: rely on lazy context function; avoid `JSON.stringify()` unless behind a dev guard.
- Storage: hashing keys (see `hash.utils.ts`) ensures stable key space; do not bypass if you need consistent per-user scoping.
- Scanner overlay: always clean up overlay + event listeners (follow existing `open` implementation for pattern).
### 13. CI / Coverage / Artifacts
- JUnit XML placed in `testresults/` (Jest configured with `jest-junit`). Keep filename stability for pipeline consumption; do not rename those outputs.
- Coverage output under `coverage/libs/...`; respect Nx caching—avoid side effects outside project roots.
### 14. When Unsure
- Search existing domain folder for analogous implementation (e.g. new feature under remission: inspect sibling feature libs for structure).
- Preserve existing DI token patterns instead of introducing new global singletons.
### 15. Quick Examples
```ts
// New feature logger usage
const log = logger(() => ({ feature: 'ReturnReceipt', action: 'init' }));
log.info('Mount');
// Persisting a signal store slice
export const FeatureStore = signalStore(
withState(initState),
withStorage('return:filters', LocalStorageProvider),
);
// Fetch config value safely
const apiBase = inject(Config).get('api.baseUrl', z.string().url());
```
---
Let me know if any area (e.g. auth flow, NgRx usage, Swagger generation details) needs deeper coverage and I can extend this file.

View File

@@ -1,189 +0,0 @@
---
mode: agent
tools: ['edit', 'search', 'usages', 'vscodeAPI', 'problems', 'changes', 'fetch', 'githubRepo', 'Nx Mcp Server', 'context7']
description: Plan Mode - Research and create a detailed implementation plan before making any changes.
model: Gemini 2.5 Pro (copilot)
---
# Plan Mode
You are now operating in **Plan Mode** - a research and planning phase that ensures thorough analysis before implementation. Plan mode is **ALWAYS ACTIVE** when using this prompt. You must follow these strict guidelines for every request:
## Phase 1: Research & Analysis (MANDATORY)
### ALLOWED Operations:
- ✅ Read files using Read, Glob, Grep tools
- ✅ Search documentation and codebases
- ✅ Analyze existing patterns and structures
- ✅ Use WebFetch for documentation research
- ✅ List and explore project structure
- ✅ Use Nx/Angular/Context7 MCP tools for workspace analysis
- ✅ Review dependencies and configurations
### FORBIDDEN Operations:
-**NEVER** create, edit, or modify any files
-**NEVER** run commands that change system state
-**NEVER** make commits or push changes
-**NEVER** install packages or modify configurations
-**NEVER** run build/test commands during planning
## Phase 2: Plan Presentation (REQUIRED FORMAT)
After thorough research, present your plan using this exact structure:
```markdown
## 📋 Implementation Plan
### 🎯 Objective
[Clear statement of what will be accomplished]
### 🔍 Research Summary
- **Current State**: [What exists now]
- **Requirements**: [What needs to be built/changed]
- **Constraints**: [Limitations and considerations]
### 📁 Files to be Modified/Created
1. **File**: `path/to/file.ts`
- **Action**: Create/Modify/Delete
- **Purpose**: [Why this file needs changes]
- **Key Changes**: [Specific modifications planned]
2. **File**: `path/to/another-file.ts`
- **Action**: Create/Modify/Delete
- **Purpose**: [Why this file needs changes]
- **Key Changes**: [Specific modifications planned]
### 🏗️ Implementation Steps
1. **Step 1**: [Detailed description]
- Files affected: `file1.ts`, `file2.ts`
- Rationale: [Why this step is necessary]
2. **Step 2**: [Detailed description]
- Files affected: `file3.ts`
- Rationale: [Why this step is necessary]
3. **Step N**: [Continue numbering...]
### ⚠️ Risks & Considerations
- **Risk 1**: [Potential issue and mitigation]
- **Risk 2**: [Potential issue and mitigation]
### 🧪 Testing Strategy
- [How the changes will be tested]
- [Specific test files or approaches]
### 📚 Architecture Decisions
- **Pattern Used**: [Which architectural pattern will be followed]
- **Libraries/Dependencies**: [What will be used and why]
- **Integration Points**: [How this fits with existing code]
### ✅ Success Criteria
- [ ] Criterion 1
- [ ] Criterion 2
- [ ] All tests pass
- [ ] No lint errors
```
## Phase 3: Await Approval
After presenting the plan:
1. **STOP** all implementation activities
2. **WAIT** for explicit user approval
3. **DO NOT** proceed with any file changes
4. **RESPOND** to questions or plan modifications
5. **EXIT PLAN MODE** only when user explicitly says "execute", "implement", "go ahead", "approved", or similar approval language
## Phase 4: Implementation (After Exiting Plan Mode)
Once the user explicitly approves and you exit plan mode:
1. **PLAN MODE IS NOW DISABLED** - you can proceed with normal implementation
2. Use TodoWrite to create implementation todos
3. Follow the plan step-by-step
4. Update todos as you progress
5. Run tests and lint checks as specified
6. Provide progress updates
## Key Behavioral Rules
### Research Thoroughly
- Spend significant time understanding the codebase
- Look for existing patterns to follow
- Identify all dependencies and integration points
- Consider edge cases and error scenarios
### Be Comprehensive
- Plans should be detailed enough for another developer to implement
- Include all necessary file changes
- Consider testing, documentation, and deployment
- Address potential conflicts or breaking changes
### Show Your Work
- Explain reasoning behind architectural decisions
- Reference existing code patterns when applicable
- Cite documentation or best practices
- Provide alternatives when multiple approaches exist
### Safety First
- Never make changes during planning phase
- Always wait for explicit approval
- Flag potentially risky changes
- Suggest incremental implementation when complex
## Example Interactions
### Good Plan Mode Behavior:
```
User: "Add a dark mode toggle to the settings page"
Assistant: I'll research the current theming system and create a comprehensive plan for implementing dark mode.
[Extensive research using Read, Grep, Glob tools]
## 📋 Implementation Plan
[Follows complete format above]
Ready to proceed? Please approve this plan before I begin implementation.
```
### What NOT to do:
```
User: "Add a dark mode toggle"
Assistant: I'll add that right away!
[Immediately starts editing files - WRONG!]
```
# <<<<<<< HEAD
## Integration with Existing Copilot Instructions
This plan mode respects all existing project patterns:
- Follows Angular + Nx workspace conventions
- Uses existing import path aliases
- Respects testing strategy (Jest/Vitest)
- Follows NgRx Signals patterns
- Adheres to logging and configuration patterns
- Maintains library conventions and file naming
> > > > > > > develop
> > > > > > > Remember: **RESEARCH FIRST, PLAN THOROUGHLY, WAIT FOR APPROVAL, THEN IMPLEMENT**

View File

@@ -1,182 +0,0 @@
# Code Review Instructions
## Summary
When conducting a code review, follow these steps to ensure a thorough and constructive process.
**Ensure that all review guidelines are followed. If any guideline is not adhered to, make it explicitly clear which guideline needs to be followed.**
## Review Process
1. 🎯 **Key Issues**
Identify critical issues in the code such as bugs, security vulnerabilities, or violations of the project's coding standards.
_Include specific links to files and line numbers (e.g., file.js#L10) where applicable._
2. 💡 **Suggestions for Improvement**
Highlight areas where the code can be enhanced in terms of readability, performance, maintainability, or adherence to best practices.
_Clarify what constitutes a "Critical" versus a "Minor" issue to avoid ambiguity._
3.**Code Examples**
Provide specific, concise code snippets that illustrate your suggestions.
_Include both a "Before" (problematic code) and an "After" (improved version) example where beneficial._
4. 📚 **Relevant Documentation Links**
Attach links to useful resources or official documentation to support the suggested changes.
_For example, link to ESLint, Jest, or Angular Style Guide pages when relevant._
## Tone and Feedback
- Be constructive and supportive.
Frame suggestions as opportunities for growth rather than criticism.
- Use the following emojis to categorize your feedback:
- 🚨 **Critical issues**
-**Minor Issues**
- ⚠️ **Warnings**
- 💡 **Suggestions**
-**Good practices**
## 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
### Review Template
````markdown
# Code Review
## Summary
A brief overview of the codes overall quality, highlighting key strengths and areas needing attention. This sets the stage for the detailed feedback below.
---
## 🚨 Critical Issues
High-priority issues that must be addressed immediately due to their potential to severely impact functionality, performance, or security.
### 1. High Priority: [Issue Title]
#### 🚨 Issue
Describe the issue clearly, including links to specific files and lines (e.g., file.js#L10). Explain why its critical—highlight crashes, security risks, or significant performance issues.
#### 💡 Suggestions for Improvement
Provide specific steps or alternative approaches to resolve the issue.
#### ✨ Code Example
**Current**: [file](file.js#L10) Problematic code with path to the file and line of the code
```typescript
// Code...
```
**Improvement**: Improved version
```typescript
// Code...
```
#### 📚 Relevant Documentation
Include URLs for further research (e.g., [Jest Documentation](https://jestjs.io/docs/getting-started)).
---
## ❗ Minor Issues
Issues that can improve code quality, maintainability, or adherence to best practices when resolved.
### 1. Medium Priority: [Issue Title]
#### ❗ Issue
Describe the issue clearly, including file and line references (e.g., file.js#L10). Explain the impact on the project.
#### 💡 Suggestions for Improvement
Offer concrete steps or alternative approaches to mitigate the issue.
#### ✨ Code Example
**Current**: [file](file.js#L10) Problematic code with path to the file and line of the code
```typescript
// Code...
```
**Improvement**: Improved version
```typescript
// Code...
```
#### 📚 Relevant Documentation
Provide links to further resources.
---
## ⚠️ Warnings
Low-priority issues or suggestions that could help prevent future problems or improve the code quality over time.
### 1. Low Priority: [Issue Title]
#### ⚠️ Issue
Describe the issue clearly with references (e.g., file.js#L10). Explain the potential impact if left unaddressed.
#### 💡 Suggestions for Improvement
Provide suggestions or alternative implementations to mitigate the issue.
#### ✨ Code Example
**Current**: [file](file.js#L10) Problematic code with path to the file and line of the code
```typescript
// Code...
```
**Improvement**: Improved version
```typescript
// Code...
```
#### 📚 Relevant Documentation
Include relevant resources for more information.
---
## 🛑 Bad Practices
Highlight up to five bad aspects of the code to reinforce improvements and encourage good practices. Use different funny emoji at the beginning of each bad practice.
- Emoji **Bad Practice 1**:
Describe a specific weakness (e.g., clear code structure) with an example reference (e.g., file.js#L20). Explain why its bad.
- Emoji **Bad Practice 2**:
Outline another negative feature (e.g., effective error handling) with a snippet reference.
---
## ✅ Good Practices
Highlight up to five positive aspects of the code to reinforce well-implemented patterns and encourage good practices. Use different funny emoji at the beginning of each good practice.
- Emoji **Good Practice 1**:
Describe a specific strength (e.g., clear code structure) with an example reference (e.g., file.js#L20). Explain why its commendable.
- Emoji **Good Practice 2**:
Outline another positive feature (e.g., effective error handling) with a snippet reference.
---
## 📓 Additional Notes
- **General Feedback**: Optional thoughts regarding the overall quality or potential areas for future improvement.
- **Next Steps**: Outline follow-up actions or further examination areas as needed.
````

View File

@@ -1,73 +0,0 @@
# 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.
## 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.
- **Act**: Execute the functionality being tested.
- **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';
import { MyComponent } from './my-component.component';
describe('MyComponent', () => {
let component: MyComponent;
beforeEach(async () => {
const { fixture } = await render(MyComponent);
component = fixture.componentInstance;
});
it('should display the correct title', async () => {
// Arrange
const expectedTitle = 'Hello World';
// Act
component.title = expectedTitle;
// If using Angular, trigger change detection:
// fixture.detectChanges();
// Assert
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toBe(expectedTitle);
});
it('should handle error cases gracefully', () => {
// Arrange
const invalidInput = null;
// Act
component.input = invalidInput;
// Assert
expect(() => 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

128
.gitignore vendored
View File

@@ -1,78 +1,50 @@
# 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
CLAUDE.md
# See http://help.github.com/ignore-files/ for more about ignoring files.
# 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/*

View File

@@ -1 +0,0 @@
npx lint-staged

View File

@@ -1,7 +0,0 @@
{
"*.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"
}

View File

@@ -1,8 +1,10 @@
# Add files here to ignore them from prettier formatting
/dist
/coverage
/.nx/cache
/.nx/workspace-data
/node_modules
.angular
.vscode
/helmvalues
/apps/swagger
/ng-swagger-gen
*.json
*.yml

View File

@@ -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
}
}
]
}

5
.prettierrc.json Normal file
View File

@@ -0,0 +1,5 @@
{
"singleQuote": true,
"printWidth": 140
}

View File

@@ -1,11 +1,8 @@
{
"recommendations": [
"johnpapa.angular2",
"esbenp.prettier-vscode",
"angular.ng-template",
"nrwl.angular-console",
"dbaeumer.vscode-eslint",
"firsttris.vscode-jest-runner",
"editorconfig.editorconfig"
]
}
"recommendations": [
"johnpapa.angular2",
"esbenp.prettier-vscode",
"angular.ng-template",
"eg2.vscode-npm-script"
]
}

35
.vscode/launch.json vendored
View File

@@ -1,15 +1,22 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "ISA-App Chrome",
"url": "https://localhost:4200",
"webRoot": "${workspaceFolder}",
}
]
}
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.11.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome",
"url": "https://192.168.2.112:4200",
"webRoot": "${workspaceFolder}",
"sourceMaps": true,
"sourceMapPathOverrides": {
"/./*": "${webRoot}/*",
"/src/*": "${webRoot}/*",
"/*": "*",
"/./~/*": "${webRoot}/node_modules/*"
}
}
]
}

115
.vscode/settings.json vendored
View File

@@ -1,100 +1,15 @@
{
"editor.accessibilitySupport": "off",
"typescript.tsdk": "node_modules/typescript/lib",
"exportall.config.exclude": [".test.", ".spec.", ".stories."],
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"eslint.validate": [
"json"
],
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.formatOnSave": false
},
"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"
],
"github.copilot.chat.commitMessageGeneration.instructions": [
{
"file": ".github/commit-instructions.md"
}
],
"github.copilot.chat.codeGeneration.instructions": [
{
"file": ".vscode/llms/angular.txt"
},
{
"file": "docs/tech-stack.md"
},
{
"file": "docs/guidelines/code-style.md"
},
{
"file": "docs/guidelines/project-structure.md"
},
{
"file": "docs/guidelines/state-management.md"
},
{
"file": "docs/guidelines/testing.md"
}
],
"github.copilot.chat.testGeneration.instructions": [
{
"file": ".github/testing-instructions.md"
},
{
"file": "docs/tech-stack.md"
},
{
"file": "docs/guidelines/code-style.md"
},
{
"file": "docs/guidelines/testing.md"
}
],
"github.copilot.chat.reviewSelection.instructions": [
{
"file": ".github/copilot-instructions.md"
},
{
"file": ".github/review-instructions.md"
},
{
"file": "docs/tech-stack.md"
},
{
"file": "docs/guidelines/code-style.md"
},
{
"file": "docs/guidelines/project-structure.md"
},
{
"file": "docs/guidelines/state-management.md"
},
{
"file": "docs/guidelines/testing.md"
}
],
"nxConsole.generateAiAgentRules": true,
"chat.mcp.discovery.enabled": {
"claude-desktop": true,
"windsurf": true,
"cursor-global": true,
"cursor-workspace": true
},
"chat.mcp.access": "all"
}
{
"typescript.tsdk": "node_modules/typescript/lib",
"editor.formatOnSave": true,
"typescriptHero.imports.insertSemicolons": false,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"css.validate": false,
"less.validate": false,
"scss.validate": false
}

148
CLAUDE.md
View File

@@ -1,148 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is an Angular monorepo managed by Nx. The main application is `isa-app`, which appears to be an inventory and returns management system for retail/e-commerce.
## Architecture
### Monorepo Structure
- **apps/isa-app**: Main Angular application
- **libs/**: Reusable libraries organized by domain and type
- **core/**: Core utilities (config, logging, storage, tabs)
- **common/**: Shared utilities (data-access, decorators, print)
- **ui/**: UI component libraries (buttons, dialogs, inputs, etc.)
- **shared/**: Shared domain components (filter, scanner, product components)
- **oms/**: Order Management System features and utilities
- **remission/**: Remission/returns management features
- **catalogue/**: Product catalogue functionality
- **utils/**: General utilities (validation, scroll position, parsing)
- **icons/**: Icon library
- **generated/swagger/**: Auto-generated API client code from OpenAPI specs
### Key Architectural Patterns
- **Standalone Components**: Project uses Angular standalone components
- **Feature Libraries**: Domain features organized as separate libraries (e.g., `oms-feature-return-search`)
- **Data Access Layer**: Separate data-access libraries for each domain (e.g., `oms-data-access`, `remission-data-access`)
- **Shared UI Components**: Reusable UI components in `libs/ui/`
- **Generated API Clients**: Swagger/OpenAPI clients auto-generated in `generated/swagger/`
## Common Development Commands
### Build Commands
```bash
# Build the main application (development)
npx nx build isa-app --configuration=development
# Build for production
npx nx build isa-app --configuration=production
# Serve the application with SSL
npx nx serve isa-app --ssl
```
### Testing Commands
```bash
# Run tests for a specific library (always use --skip-cache)
npx nx run <project-name>:test --skip-cache
# Example: npx nx run remission-data-access:test --skip-cache
# Run tests for all libraries except the main app
npx nx run-many -t test --exclude isa-app --skip-cache
# Run a single test file
npx nx run <project-name>:test --testFile=<path-to-test-file> --skip-cache
# Run tests with coverage
npx nx run <project-name>:test --code-coverage --skip-cache
# Run tests in watch mode
npx nx run <project-name>:test --watch
```
### Linting Commands
```bash
# Lint a specific project
npx nx lint <project-name>
# Example: npx nx lint remission-data-access
# Run linting for all projects
npx nx run-many -t lint
```
### Other Useful Commands
```bash
# Generate Swagger API clients
npm run generate:swagger
# Start Storybook
npx nx run isa-app:storybook
# Format code with Prettier
npm run prettier
# List all projects in the workspace
npx nx list
# Show project dependencies graph
npx nx graph
# Run affected tests (based on git changes)
npx nx affected:test
```
## Testing Framework
### Current Setup
- **Jest**: Primary test runner for existing libraries
- **Vitest**: Being adopted for new libraries (migration in progress)
- **Testing Utilities**:
- **Angular Testing Utilities** (TestBed, ComponentFixture): Use for new tests
- **Spectator**: Legacy testing utility for existing tests
- **ng-mocks**: For advanced mocking scenarios
### Test File Requirements
- Test files must end with `.spec.ts`
- Use AAA pattern (Arrange-Act-Assert)
- Include E2E testing attributes (`data-what`, `data-which`) in HTML templates
- Mock external dependencies and child components
## State Management
- **NgRx**: Store, Effects, Entity, Component Store, Signals
- **RxJS**: For reactive programming patterns
## Styling
- **Tailwind CSS**: Primary styling framework with custom configuration
- **SCSS**: For component-specific styles
- **Custom Tailwind plugins**: For buttons, inputs, menus, typography
## API Integration
- **Generated Swagger Clients**: Auto-generated TypeScript clients from OpenAPI specs
- **Available APIs**: availability, cat-search, checkout, crm, eis, inventory, isa, oms, print, wws
## Build Configuration
- **Angular 20.1.2**: Latest Angular version
- **TypeScript 5.8.3**: For type safety
- **Node.js >= 22.0.0**: Required Node version
- **npm >= 10.0.0**: Required npm version
## Important Conventions
- **Component Prefix**: Each library has its own prefix (e.g., `remi` for remission, `oms` for OMS)
- **Standalone Components**: All new components should be standalone
- **Path Aliases**: Use TypeScript path aliases defined in `tsconfig.base.json` (e.g., `@isa/core/config`)
- **Project Names**: Can be found in each library's `project.json` file
## Development Workflow Tips
- Always use `npx nx run` pattern for executing tasks
- Include `--skip-cache` flag when running tests to ensure fresh results
- Use Nx's affected commands to optimize CI/CD pipelines
- Project graph visualization helps understand dependencies: `npx nx graph`
## Development Notes
- Use start target to start the application. Only one project can be started: isa-app
- Make sure to have a look at @docs/guidelines/testing.md before writing tests
- Make sure to add e2e attributes to the html. Those are important for my colleagues writen e2e tests
- Guide for the e2e testing attributes can be found in the testing.md
- When reviewing code follow the instructions @.github/review-instructions.md

View File

@@ -1,5 +1,5 @@
#stage 1
FROM node:22 as base
FROM node:14 as base
ARG IS_PRODUCTION=false
ARG SEMVERSION=1.0.0
ARG BuildUniqueID
@@ -8,7 +8,7 @@ WORKDIR /app
COPY . .
RUN umask 0022
RUN npm version ${SEMVERSION}
RUN npm ci --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
@@ -24,6 +24,6 @@ ARG BuildUniqueID
LABEL build.uniqueid="${BuildUniqueID:-1}"
RUN wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb -q -O /tmp/chrome.deb && apt update && apt install -y /tmp/chrome.deb
# ignore exitcode, sonst gibts keinen container
RUN npm run ci || true
RUN npm test || true
ENTRYPOINT [ "/bin/sleep", "60000" ]

4
TASKS.md Normal file
View File

@@ -0,0 +1,4 @@
- Neue Icon Module (z.B. mit SVG sprites)
- Breadcrumb Navigation (Neu)
- Remissions Produkt Liste (Refactoring / Neu)
- Angular Version (Upgrade)

2817
angular.json Normal file
View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,25 @@
# Scan
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 12.2.0.
## Code scaffolding
Run `ng generate component component-name --project scan` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project scan`.
> Note: Don't forget to add `--project scan` or else it will be added to the default project in your `angular.json` file.
## Build
Run `ng build scan` to build the project. The build artifacts will be stored in the `dist/` directory.
## Publishing
After building your library with `ng build scan`, go to the dist folder `cd dist/scan` and run `npm publish`.
## Running unit tests
Run `ng test scan` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.

View File

@@ -0,0 +1,41 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true, // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(__dirname, '../../../coverage/adapter/scan'),
subdir: '.',
reporters: [{ type: 'html' }, { type: 'text-summary' }],
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true,
});
};

View File

@@ -0,0 +1,7 @@
{
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../../dist/adapter/scan",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@@ -0,0 +1,11 @@
{
"name": "@adapter/scan",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^12.2.0",
"@angular/core": "^12.2.0"
},
"dependencies": {
"tslib": "^2.3.0"
}
}

View File

@@ -0,0 +1,42 @@
import { Injectable, isDevMode } from '@angular/core';
import { PromptModalData, UiModalService, UiPromptModalComponent } from '@ui/modal';
import { Observable } from 'rxjs';
import { ScanAdapter } from './scan-adapter';
@Injectable()
export class DevScanAdapter implements ScanAdapter {
readonly name = 'Dev';
constructor(private _modal: UiModalService) {}
init(): Promise<boolean> {
return new Promise((resolve, reject) => {
resolve(isDevMode());
});
}
scan(): Observable<string> {
return new Observable((observer) => {
const modalRef = this._modal.open({
content: UiPromptModalComponent,
title: 'Scannen',
data: {
message: 'Diese Eingabemaske dient nur zu Entwicklungs und Testzwecken.',
placeholder: 'Scan Code',
confirmText: 'weiter',
cancelText: 'abbrechen',
} as PromptModalData,
});
const sub = modalRef.afterClosed$.subscribe((result) => {
observer.next(result.data);
observer.complete();
});
return () => {
modalRef.close();
sub.unsubscribe();
};
});
}
}

View File

@@ -0,0 +1,26 @@
import { Injectable } from '@angular/core';
import { NativeContainerService } from 'native-container';
import { Observable } from 'rxjs';
import { filter, map, take } from 'rxjs/operators';
import { ScanAdapter } from './scan-adapter';
@Injectable()
export class NativeScanAdapter implements ScanAdapter {
readonly name = 'Native';
constructor(private readonly nativeContainerService: NativeContainerService) {}
init(): Promise<boolean> {
return new Promise((resolve, reject) => {
resolve(this.nativeContainerService.isUiWebview().isNative);
});
}
scan(): Observable<string> {
return this.nativeContainerService.openScanner('scanBook').pipe(
filter((result) => result.status === 'SUCCESS'),
map((result) => result.data),
take(1)
);
}
}

View File

@@ -0,0 +1,65 @@
import { Inject, Injectable } from '@angular/core';
import { Observable, throwError } from 'rxjs';
import { ScanAdapter } from './scan-adapter';
import { SCAN_ADAPTER } from './tokens';
@Injectable({
providedIn: 'root',
})
export class ScanAdapterService {
private _readyAdapters: Record<string, boolean> = {};
constructor(@Inject(SCAN_ADAPTER) private readonly scanAdapters: ScanAdapter[]) {}
async init(): Promise<void> {
for (const adapter of this.scanAdapters) {
const isReady = await adapter.init();
console.log('ScanAdapterService.init', adapter.name, isReady);
this._readyAdapters[adapter.name] = isReady;
}
}
adapters(): ScanAdapter[] {
return [...this.scanAdapters];
}
getAdapter(name: string): ScanAdapter | undefined {
return this.scanAdapters.find((adapter) => adapter.name === name);
}
// return true if at least one adapter is ready
isReady(): boolean {
return Object.values(this._readyAdapters).some((ready) => ready);
}
scan(ops: { use?: string; include?: string[]; exclude?: string[] } = { exclude: ['Dev'] }): Observable<string> {
let adapter: ScanAdapter;
if (ops.use == undefined) {
// get the first adapter that is ready to use
adapter = this.scanAdapters
.filter((adapter) => {
if (ops.include?.length) {
return ops.include.includes(adapter.name);
} else if (ops.exclude?.length) {
return !ops.exclude.includes(adapter.name);
} else {
return true;
}
})
.find((adapter) => this._readyAdapters[adapter.name]);
} else {
adapter = this.getAdapter(ops.use);
}
if (!adapter) {
return throwError('No adapter found');
}
if (this._readyAdapters[adapter.name] == false) {
return throwError('Adapter is not ready');
}
return adapter.scan();
}
}

View File

@@ -0,0 +1,20 @@
:host {
@apply block relative;
}
.scanner-container {
width: 100vw;
max-width: 95vw;
max-height: calc(95vh - 120px);
}
.close-scanner {
@apply block px-6 py-4 bg-white text-brand border-2 border-solid border-brand rounded-full text-lg font-bold mx-auto mt-4;
}
@screen desktop {
.scanner-container {
max-width: 900px;
max-height: 900px;
}
}

View File

@@ -0,0 +1,4 @@
<div class="scanner-container" #scanContainer></div>
<button class="close-scanner" type="button" (click)="close()">
Scan abbrechen
</button>

View File

@@ -0,0 +1,100 @@
import { Component, ChangeDetectionStrategy, ElementRef, ViewChild, NgZone, AfterViewInit, OnDestroy } from '@angular/core';
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
import { Barcode, BarcodePicker, ScanResult, ScanSettings } from 'scandit-sdk';
@Component({
selector: 'app-scandit-overlay',
templateUrl: 'scandit-overlay.component.html',
styleUrls: ['scandit-overlay.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ScanditOverlayComponent implements AfterViewInit, OnDestroy {
private _barcodePicker: BarcodePicker;
private _onScan?: (code: string) => void;
private _onClose?: () => void;
@ViewChild('scanContainer', { read: ElementRef, static: true }) scanContainer: ElementRef;
constructor(private _zone: NgZone, private _modal: UiModalService) {}
ngAfterViewInit(): void {
this.createBarcodePicker()
.then(() => {
this._barcodePicker.on('scan', (scanResult) => {
this._zone.run(() => this.handleScanrResult(scanResult));
});
})
.catch((err: Error) => {
this._modal
.open({
content: UiMessageModalComponent,
title: 'Zugriff auf Kamera verweigert',
data: { message: 'Falls Sie den Zugriff erlauben möchten, können Sie das über die Webseiteinstellung Ihres Browsers.' },
})
.afterClosed$.subscribe(() => {
this._onClose?.();
});
});
}
async createBarcodePicker() {
this._barcodePicker = await BarcodePicker.create(this.scanContainer.nativeElement, {
playSoundOnScan: true,
vibrateOnScan: true,
});
this._barcodePicker.applyScanSettings(this.getScanSettings());
}
getScanSettings(): ScanSettings {
return new ScanSettings({
blurryRecognition: false,
enabledSymbologies: [
Barcode.Symbology.EAN8,
Barcode.Symbology.EAN13,
Barcode.Symbology.UPCA,
Barcode.Symbology.UPCE,
Barcode.Symbology.CODE128,
Barcode.Symbology.CODE39,
Barcode.Symbology.CODE93,
Barcode.Symbology.INTERLEAVED_2_OF_5,
Barcode.Symbology.QR,
],
codeDuplicateFilter: 1000,
});
}
onScan(fn: (code: string) => void) {
this._onScan = fn;
}
onClose(fn: () => void) {
this._onClose = fn;
}
handleScanrResult(scanRestul: ScanResult) {
let result: string | undefined;
if (scanRestul.barcodes.length) {
result = scanRestul.barcodes[0].data;
} else if (scanRestul.texts.length) {
result = scanRestul.texts[0].value;
}
if (result) {
this._onScan?.(result);
}
}
close() {
this._onClose?.();
}
ngOnDestroy(): void {
this._zone.runOutsideAngular(() => {
this._barcodePicker?.destroy(true);
});
}
}

View File

@@ -0,0 +1,77 @@
import { Injectable } from '@angular/core';
import { Observable, Subscriber } from 'rxjs';
import { ScanAdapter } from '../scan-adapter';
import { Overlay } from '@angular/cdk/overlay';
import { configure } from 'scandit-sdk';
// import { ScanditModalComponent } from './scandit-modal';
import { Config } from '@core/config';
import { ComponentPortal } from '@angular/cdk/portal';
import { ScanditOverlayComponent } from './scandit-overlay.component';
@Injectable()
export class ScanditScanAdapter implements ScanAdapter {
readonly name = 'Scandit';
constructor(private readonly _config: Config, private _overlay: Overlay) {}
async init(): Promise<boolean> {
await configure(this._config.get('licence.scandit'), {
engineLocation: '/scandit/',
});
return true;
}
scan(): Observable<string> {
return new Observable((observer) => {
const overlay = this.createOverlay();
const portal = this.createPortal();
const ref = overlay.attach(portal);
const sub = new Subscriber();
const complete = () => {
overlay.detach();
ref.destroy();
sub.unsubscribe();
sub.complete();
observer.complete();
};
sub.add(
overlay.backdropClick().subscribe(() => {
complete();
})
);
ref.instance.onScan((code) => {
observer.next(code);
complete();
});
ref.instance.onClose(() => {
complete();
});
return complete;
});
}
createOverlay() {
const overlay = this._overlay.create({
positionStrategy: this._overlay.position().global().centerHorizontally().centerVertically(),
hasBackdrop: true,
});
return overlay;
}
createPortal() {
const portal = new ComponentPortal(ScanditOverlayComponent);
return portal;
}
}

View File

@@ -0,0 +1,11 @@
/*
* Public API Surface of scan
*/
export * from './lib/scandit';
export * from './lib/dev.scan-adapter';
export * from './lib/native.scan-adapter';
export * from './lib/scan-adapter';
export * from './lib/scan.module';
export * from './lib/scan.service';
export * from './lib/tokens';

View File

@@ -0,0 +1,9 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js';
import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), { teardown: { destroyAfterEach: true } });

View File

@@ -0,0 +1,19 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/lib",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": [],
"lib": [
"dom",
"es2018"
]
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

View File

@@ -0,0 +1,10 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.lib.json",
"compilerOptions": {
"declarationMap": false
},
"angularCompilerOptions": {
"compilationMode": "partial"
}
}

View File

@@ -0,0 +1,17 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/spec",
"types": [
"jasmine"
]
},
"files": [
"src/test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@@ -0,0 +1,25 @@
# ProductImage
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.1.2.
## Code scaffolding
Run `ng generate component component-name --project product-image` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project product-image`.
> Note: Don't forget to add `--project product-image` or else it will be added to the default project in your `angular.json` file.
## Build
Run `ng build product-image` to build the project. The build artifacts will be stored in the `dist/` directory.
## Publishing
After building your library with `ng build product-image`, go to the dist folder `cd dist/product-image` and run `npm publish`.
## Running unit tests
Run `ng test product-image` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

View File

@@ -0,0 +1,32 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../../coverage/cdn/product-image'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true,
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true,
});
};

View File

@@ -0,0 +1,7 @@
{
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../../dist/cdn/product-image",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@@ -0,0 +1,11 @@
{
"name": "@cdn/product-image",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^10.1.2",
"@angular/core": "^10.1.2"
},
"dependencies": {
"tslib": "^2.0.0"
}
}

View File

@@ -2,8 +2,8 @@ import { NgModule } from '@angular/core';
import { ProductImagePipe } from './product-image.pipe';
@NgModule({
declarations: [],
imports: [ProductImagePipe],
declarations: [ProductImagePipe],
imports: [],
exports: [ProductImagePipe],
})
export class ProductImageModule {}

View File

@@ -3,8 +3,6 @@ import { ProductImageService } from './product-image.service';
@Pipe({
name: 'productImage',
standalone: true,
pure: true,
})
export class ProductImagePipe implements PipeTransform {
constructor(private imageService: ProductImageService) {}

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { ProductImageService } from './product-image.service';
describe('ProductImageService', () => {
let service: ProductImageService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ProductImageService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,8 @@
/*
* Public API Surface of product-image
*/
export * from './lib/product-image.service';
export * from './lib/product-image.module';
export * from './lib/product-image.pipe';
export * from './lib/tokens';

View File

@@ -0,0 +1,11 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js';
import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
teardown: { destroyAfterEach: false },
});

View File

@@ -0,0 +1,24 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/lib",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": [],
"lib": [
"dom",
"es2018"
]
},
"angularCompilerOptions": {
"skipTemplateCodegen": true,
"strictMetadataEmit": true,
"enableResourceInlining": true
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

View File

@@ -0,0 +1,10 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.lib.json",
"compilerOptions": {
"declarationMap": false
},
"angularCompilerOptions": {
"compilationMode": "partial"
}
}

View File

@@ -0,0 +1,17 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/spec",
"types": [
"jasmine"
]
},
"files": [
"src/test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@@ -0,0 +1,17 @@
{
"extends": "../../../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
"cdn",
"camelCase"
],
"component-selector": [
true,
"element",
"cdn",
"kebab-case"
]
}
}

View File

@@ -0,0 +1,25 @@
# Application
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.1.2.
## Code scaffolding
Run `ng generate component component-name --project application` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project application`.
> Note: Don't forget to add `--project application` or else it will be added to the default project in your `angular.json` file.
## Build
Run `ng build application` to build the project. The build artifacts will be stored in the `dist/` directory.
## Publishing
After building your library with `ng build application`, go to the dist folder `cd dist/application` and run `npm publish`.
## Running unit tests
Run `ng test application` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

View File

@@ -0,0 +1,43 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
const customLaunchers = require('../../../karma/custom-launchers');
const junitReporter = require('../../../karma/junit-reporter')('core-application');
const coverageReporter = require('../../../karma/coverage-reporter')('core-application');
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('karma-junit-reporter'),
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true, // removes the duplicated traces
},
coverageReporter,
junitReporter,
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
customLaunchers,
singleRun: false,
restartOnFileChange: true,
});
};

View File

@@ -0,0 +1,7 @@
{
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../../dist/core/application",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@@ -0,0 +1,11 @@
{
"name": "@core/application",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^10.1.2",
"@angular/core": "^10.1.2"
},
"dependencies": {
"tslib": "^2.0.0"
}
}

View File

@@ -0,0 +1,233 @@
import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator';
import { Store } from '@ngrx/store';
import { Observable, of } from 'rxjs';
import { first } from 'rxjs/operators';
import { ApplicationProcess } from './defs';
import { ApplicationService } from './application.service';
import * as actions from './store/application.actions';
describe('ApplicationService', () => {
let spectator: SpectatorService<ApplicationService>;
let store: SpyObject<Store>;
const createService = createServiceFactory({
service: ApplicationService,
mocks: [Store],
});
beforeEach(() => {
spectator = createService({});
store = spectator.inject(Store);
});
it('should be created', () => {
expect(spectator.service).toBeTruthy();
});
describe('activatedProcessId$', () => {
it('should return an observable', () => {
expect(spectator.service.activatedProcessId$).toBeInstanceOf(Observable);
});
});
describe('activatedProcessId', () => {
it('should return the process id as a number', () => {
spyOnProperty(spectator.service['activatedProcessIdSubject'] as any, 'value').and.returnValue(2);
expect(spectator.service.activatedProcessId).toBe(2);
});
});
describe('getProcesses$()', () => {
it('should call select on store and return all selected processes', async () => {
const processes: ApplicationProcess[] = [
{ id: 1, name: 'Vorgang', type: 'cart', section: 'customer', data: { count: 1 } },
{ id: 2, name: 'Vorgang', type: 'task-calendar', section: 'branch' },
];
store.select.and.returnValue(of(processes));
const result = await spectator.service.getProcesses$().pipe(first()).toPromise();
expect(result).toEqual(processes);
expect(store.select).toHaveBeenCalled();
});
it('should call select on store and return all section customer processes', async () => {
const processes: ApplicationProcess[] = [
{ id: 1, name: 'Vorgang', type: 'cart', section: 'customer', data: { count: 1 } },
{ id: 2, name: 'Vorgang', type: 'task-calendar', section: 'branch' },
];
store.select.and.returnValue(of(processes));
const result = await spectator.service.getProcesses$('customer').pipe(first()).toPromise();
expect(result).toEqual([processes[0]]);
expect(store.select).toHaveBeenCalled();
});
it('should call select on store and return all section branch processes', async () => {
const processes: ApplicationProcess[] = [
{ id: 1, name: 'Vorgang', type: 'cart', section: 'customer', data: { count: 1 } },
{ id: 2, name: 'Vorgang', type: 'task-calendar', section: 'branch' },
];
store.select.and.returnValue(of(processes));
const result = await spectator.service.getProcesses$('branch').pipe(first()).toPromise();
expect(result).toEqual([processes[1]]);
expect(store.select).toHaveBeenCalled();
});
});
describe('getProcessById$()', () => {
it('should return the process by id', async () => {
const processes: ApplicationProcess[] = [
{ id: 1, name: 'Vorgang 1', section: 'customer' },
{ id: 2, name: 'Vorgang 2', section: 'customer' },
];
spyOn(spectator.service, 'getProcesses$').and.returnValue(of(processes));
const process = await spectator.service.getProcessById$(1).toPromise();
expect(process.id).toBe(1);
});
});
describe('getSection$()', () => {
it('should return the selected section branch', async () => {
const section = 'branch';
store.select.and.returnValue(of(section));
const result = await spectator.service.getSection$().pipe(first()).toPromise();
expect(result).toEqual(section);
expect(store.select).toHaveBeenCalled();
});
});
describe('getActivatedProcessId$', () => {
it('should return the current selected activated process id', async () => {
const activatedProcessId = 2;
store.select.and.returnValue(of({ id: activatedProcessId }));
const result = await spectator.service.getActivatedProcessId$().pipe(first()).toPromise();
expect(result).toEqual(activatedProcessId);
expect(store.select).toHaveBeenCalled();
});
});
describe('activateProcess()', () => {
it('should dispatch action setActivatedProcess with argument activatedProcessId and action type', () => {
const activatedProcessId = 2;
spectator.service.activateProcess(activatedProcessId);
expect(store.dispatch).toHaveBeenCalledWith({ activatedProcessId, type: actions.setActivatedProcess.type });
});
});
describe('removeProcess()', () => {
it('should dispatch action removeProcess with argument processId and action type', () => {
const processId = 2;
spectator.service.removeProcess(processId);
expect(store.dispatch).toHaveBeenCalledWith({ processId, type: actions.removeProcess.type });
});
});
describe('createProcess()', () => {
it('should dispatch action addProcess with process', async () => {
const process: ApplicationProcess = {
id: 1,
name: 'Vorgang 1',
section: 'customer',
type: 'cart',
};
const timestamp = 100;
spyOn(spectator.service as any, '_createTimestamp').and.returnValue(timestamp);
spyOn(spectator.service, 'getProcessById$').and.returnValue(of(undefined));
await spectator.service.createProcess(process);
expect(store.dispatch).toHaveBeenCalledWith({
type: actions.addProcess.type,
process: {
...process,
activated: 0,
created: timestamp,
},
});
});
it('should throw an error if the process id is already existing', async () => {
const process: ApplicationProcess = {
id: 1,
name: 'Vorgang 1',
section: 'customer',
type: 'cart',
};
spyOn(spectator.service, 'getProcessById$').and.returnValue(of(process));
await expectAsync(spectator.service.createProcess(process)).toBeRejectedWithError('Process Id existiert bereits');
});
it('should throw an error if the process id is not a number', async () => {
const process: ApplicationProcess = {
id: undefined,
name: 'Vorgang 1',
section: 'customer',
type: 'cart',
};
spyOn(spectator.service, 'getProcessById$').and.returnValue(of({ id: 5, name: 'Vorgang 2', section: 'customer' }));
await expectAsync(spectator.service.createProcess(process)).toBeRejectedWithError('Process Id nicht gesetzt');
});
});
describe('patchProcess', () => {
it('should dispatch action patchProcess with changes', async () => {
const process: ApplicationProcess = {
id: 1,
name: 'Vorgang 1',
section: 'customer',
type: 'cart',
};
await spectator.service.patchProcess(process.id, process);
expect(store.dispatch).toHaveBeenCalledWith({
type: actions.patchProcess.type,
processId: process.id,
changes: {
...process,
},
});
});
});
describe('setSection()', () => {
it('should dispatch action setSection with argument section and action type', () => {
const section = 'customer';
spectator.service.setSection(section);
expect(store.dispatch).toHaveBeenCalledWith({ section, type: actions.setSection.type });
});
});
describe('getLastActivatedProcessWithSectionAndType()', () => {
it('should return the last activated process by section and type', async () => {
const processes: ApplicationProcess[] = [
{ id: 1, name: 'Vorgang 1', section: 'customer', type: 'cart', activated: 100 },
{ id: 2, name: 'Vorgang 2', section: 'customer', type: 'cart', activated: 200 },
{ id: 3, name: 'Vorgang 3', section: 'customer', type: 'goodsOut', activated: 300 },
];
spyOn(spectator.service, 'getProcesses$').and.returnValue(of(processes));
expect(await spectator.service.getLastActivatedProcessWithSectionAndType$('customer', 'cart').pipe(first()).toPromise()).toBe(
processes[1]
);
});
});
describe('getLastActivatedProcessWithSection()', () => {
it('should return the last activated process by section', async () => {
const processes: ApplicationProcess[] = [
{ id: 1, name: 'Vorgang 1', section: 'customer', activated: 100 },
{ id: 2, name: 'Vorgang 2', section: 'customer', activated: 200 },
{ id: 3, name: 'Vorgang 3', section: 'customer', activated: 300 },
];
spyOn(spectator.service, 'getProcesses$').and.returnValue(of(processes));
expect(await spectator.service.getLastActivatedProcessWithSection$('customer').pipe(first()).toPromise()).toBe(processes[2]);
});
});
describe('_createTimestamp', () => {
it('should return the current timestamp in ms', () => {
expect(spectator.service['_createTimestamp']()).toBeCloseTo(Date.now());
});
});
});

View File

@@ -0,0 +1,129 @@
import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { isBoolean, isNumber } from '@utils/common';
import { BehaviorSubject, Observable } from 'rxjs';
import { first, map, switchMap } from 'rxjs/operators';
import { ApplicationProcess } from './defs';
import {
removeProcess,
selectSection,
selectProcesses,
setSection,
addProcess,
setActivatedProcess,
selectActivatedProcess,
patchProcess,
patchProcessData,
} from './store';
@Injectable()
export class ApplicationService {
/** @deprecated */
private activatedProcessIdSubject = new BehaviorSubject<number>(undefined);
/** @deprecated */
get activatedProcessId() {
return this.activatedProcessIdSubject.value;
}
/** @deprecated */
get activatedProcessId$() {
return this.activatedProcessIdSubject.asObservable();
}
constructor(private store: Store) {}
getProcesses$(section?: 'customer' | 'branch') {
const processes$ = this.store.select(selectProcesses);
return processes$.pipe(map((processes) => processes.filter((process) => (section ? process.section === section : true))));
}
getProcessById$(processId: number): Observable<ApplicationProcess> {
return this.getProcesses$().pipe(map((processes) => processes.find((process) => process.id === processId)));
}
getSection$() {
return this.store.select(selectSection);
}
/** @deprecated */
getActivatedProcessId$() {
return this.store.select(selectActivatedProcess).pipe(map((process) => process?.id));
}
activateProcess(activatedProcessId: number) {
this.store.dispatch(setActivatedProcess({ activatedProcessId }));
this.activatedProcessIdSubject.next(activatedProcessId);
}
removeProcess(processId: number) {
this.store.dispatch(removeProcess({ processId }));
}
patchProcess(processId: number, changes: Partial<ApplicationProcess>) {
this.store.dispatch(patchProcess({ processId, changes }));
}
patchProcessData(processId: number, data: Record<string, any>) {
this.store.dispatch(patchProcessData({ processId, data }));
}
async createProcess(process: ApplicationProcess) {
const existingProcess = await this.getProcessById$(process?.id).pipe(first()).toPromise();
if (existingProcess?.id === process?.id) {
throw new Error('Process Id existiert bereits');
}
if (!isNumber(process.id)) {
throw new Error('Process Id nicht gesetzt');
}
if (!isBoolean(process.closeable)) {
process.closeable = true;
}
if (!isBoolean(process.confirmClosing)) {
process.confirmClosing = true;
}
process.created = this._createTimestamp();
process.activated = 0;
this.store.dispatch(addProcess({ process }));
}
setSection(section: 'customer' | 'branch') {
this.store.dispatch(setSection({ section }));
}
getLastActivatedProcessWithSectionAndType$(section: 'customer' | 'branch', type: string): Observable<ApplicationProcess> {
return this.getProcesses$(section).pipe(
map((processes) =>
processes
?.filter((process) => process.type === type)
?.reduce((latest, current) => {
if (!latest) {
return current;
}
return latest?.activated > current?.activated ? latest : current;
}, undefined)
)
);
}
getLastActivatedProcessWithSection$(section: 'customer' | 'branch'): Observable<ApplicationProcess> {
return this.getProcesses$(section).pipe(
map((processes) =>
processes?.reduce((latest, current) => {
if (!latest) {
return current;
}
return latest?.activated > current?.activated ? latest : current;
}, undefined)
)
);
}
private _createTimestamp() {
return Date.now();
}
}

View File

@@ -0,0 +1,6 @@
// start:ng42.barrel
export * from './application.module';
export * from './application.service';
export * from './defs';
export * from './store';
// end:ng42.barrel

View File

@@ -0,0 +1,16 @@
import { createAction, props } from '@ngrx/store';
import { ApplicationProcess } from '..';
const prefix = '[CORE-APPLICATION]';
export const setSection = createAction(`${prefix} Set Section`, props<{ section: 'customer' | 'branch' }>());
export const addProcess = createAction(`${prefix} Add Process`, props<{ process: ApplicationProcess }>());
export const removeProcess = createAction(`${prefix} Remove Process`, props<{ processId: number }>());
export const setActivatedProcess = createAction(`${prefix} Set Activated Process`, props<{ activatedProcessId: number }>());
export const patchProcess = createAction(`${prefix} Patch Process`, props<{ processId: number; changes: Partial<ApplicationProcess> }>());
export const patchProcessData = createAction(`${prefix} Patch Process Data`, props<{ processId: number; data: Record<string, any> }>());

View File

@@ -66,7 +66,7 @@ describe('applicationReducer', () => {
...initialState,
processes: [process],
},
action,
action
);
expect(state.processes[0].name).toEqual('Test');
});
@@ -87,7 +87,7 @@ describe('applicationReducer', () => {
...initialState,
processes: [process],
},
action,
action
);
expect(state.processes).toEqual([process]);
});

View File

@@ -1,18 +1,9 @@
import { Action, createReducer, on } from '@ngrx/store';
import {
setSection,
addProcess,
removeProcess,
setActivatedProcess,
patchProcess,
patchProcessData,
setTitle,
} from './application.actions';
import { setSection, addProcess, removeProcess, setActivatedProcess, patchProcess, patchProcessData } from './application.actions';
import { ApplicationState, INITIAL_APPLICATION_STATE } from './application.state';
const _applicationReducer = createReducer(
INITIAL_APPLICATION_STATE,
on(setTitle, (state, { title }) => ({ ...state, title })),
on(setSection, (state, { section }) => ({ ...state, section })),
on(addProcess, (state, { process }) => ({ ...state, processes: [...state.processes, { data: {}, ...process }] })),
on(removeProcess, (state, { processId }) => {
@@ -48,7 +39,7 @@ const _applicationReducer = createReducer(
});
return { ...state, processes };
}),
})
);
export function applicationReducer(state: ApplicationState, action: Action) {

View File

@@ -0,0 +1,32 @@
import { ApplicationState } from './application.state';
import { ApplicationProcess } from '../defs';
import * as selectors from './application.selectors';
describe('applicationSelectors', () => {
it('should select the processes', () => {
const processes: ApplicationProcess[] = [{ id: 1, name: 'Vorgang 1', section: 'customer' }];
const state: Partial<ApplicationState> = {
processes,
};
expect(selectors.selectProcesses.projector(state)).toEqual(processes);
});
it('should select the section', () => {
const state: Partial<ApplicationState> = {
section: 'customer',
};
expect(selectors.selectSection.projector(state)).toEqual('customer');
});
it('should select the activatedProcess', () => {
const processes: ApplicationProcess[] = [
{ id: 1, name: 'Vorgang 1', section: 'customer', activated: 100 },
{ id: 2, name: 'Vorgang 2', section: 'customer', activated: 300 },
{ id: 3, name: 'Vorgang 3', section: 'customer', activated: 200 },
];
const state: Partial<ApplicationState> = {
processes,
};
expect(selectors.selectActivatedProcess.projector(state)).toEqual(processes[1]);
});
});

View File

@@ -2,8 +2,6 @@ import { createFeatureSelector, createSelector } from '@ngrx/store';
import { ApplicationState } from './application.state';
export const selectApplicationState = createFeatureSelector<ApplicationState>('core-application');
export const selectTitle = createSelector(selectApplicationState, (s) => s.title);
export const selectSection = createSelector(selectApplicationState, (s) => s.section);
export const selectProcesses = createSelector(selectApplicationState, (s) => s.processes);
@@ -14,5 +12,5 @@ export const selectActivatedProcess = createSelector(selectApplicationState, (s)
return current;
}
return process.activated > current.activated ? process : current;
}, undefined),
}, undefined)
);

View File

@@ -1,13 +1,11 @@
import { ApplicationProcess } from '../defs';
export interface ApplicationState {
title: string;
processes: ApplicationProcess[];
section: 'customer' | 'branch';
}
export const INITIAL_APPLICATION_STATE: ApplicationState = {
title: '',
processes: [],
section: 'customer',
};

View File

@@ -0,0 +1,5 @@
/*
* Public API Surface of application
*/
export * from './lib';

View File

@@ -0,0 +1,11 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js';
import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
teardown: { destroyAfterEach: false },
});

View File

@@ -0,0 +1,24 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/lib",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": [],
"lib": [
"dom",
"es2018"
]
},
"angularCompilerOptions": {
"skipTemplateCodegen": true,
"strictMetadataEmit": true,
"enableResourceInlining": true
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

View File

@@ -0,0 +1,10 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.lib.json",
"compilerOptions": {
"declarationMap": false
},
"angularCompilerOptions": {
"compilationMode": "partial"
}
}

View File

@@ -0,0 +1,17 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/spec",
"types": [
"jasmine"
]
},
"files": [
"src/test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@@ -0,0 +1,17 @@
{
"extends": "../../../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
"lib",
"camelCase"
],
"component-selector": [
true,
"element",
"lib",
"kebab-case"
]
}
}

25
apps/core/auth/README.md Normal file
View File

@@ -0,0 +1,25 @@
# Auth
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 12.2.0.
## Code scaffolding
Run `ng generate component component-name --project auth` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project auth`.
> Note: Don't forget to add `--project auth` or else it will be added to the default project in your `angular.json` file.
## Build
Run `ng build auth` to build the project. The build artifacts will be stored in the `dist/` directory.
## Publishing
After building your library with `ng build auth`, go to the dist folder `cd dist/auth` and run `npm publish`.
## Running unit tests
Run `ng test auth` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.

View File

@@ -0,0 +1,43 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
const customLaunchers = require('../../../karma/custom-launchers');
const junitReporter = require('../../../karma/junit-reporter')('core-auth');
const coverageReporter = require('../../../karma/coverage-reporter')('core-auth');
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('karma-junit-reporter'),
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true, // removes the duplicated traces
},
coverageReporter,
junitReporter,
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
customLaunchers,
singleRun: false,
restartOnFileChange: true,
});
};

View File

@@ -0,0 +1,7 @@
{
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../../dist/core/auth",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@@ -0,0 +1,11 @@
{
"name": "@core/auth",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^12.2.0",
"@angular/core": "^12.2.0"
},
"dependencies": {
"tslib": "^2.3.0"
}
}

View File

@@ -1,11 +1,7 @@
import { ModuleWithProviders, NgModule } from '@angular/core';
import { AuthService } from './auth.service';
import { OAuthModule } from 'angular-oauth2-oidc';
import { IfRoleDirective } from './if-role.directive';
@NgModule({
declarations: [IfRoleDirective],
exports: [IfRoleDirective],
})
@NgModule({})
export class AuthModule {
static forRoot(): ModuleWithProviders<AuthModule> {
return {

View File

@@ -0,0 +1,88 @@
import { 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',
})
export class AuthService {
private readonly _initialized = new BehaviorSubject<boolean>(false);
get initialized$() {
return this._initialized.asObservable();
}
constructor(private _config: Config, private readonly _oAuthService: OAuthService) {}
async init() {
if (this._initialized.getValue()) {
throw new Error('AuthService is already initialized');
}
const authConfig: AuthConfig = this._config.get('@core/auth');
authConfig.redirectUri = window.location.origin;
authConfig.silentRefreshRedirectUri = window.location.origin + '/silent-refresh.html';
authConfig.useSilentRefresh = true;
this._oAuthService.configure(authConfig);
this._oAuthService.tokenValidationHandler = new JwksValidationHandler();
this._oAuthService.setupAutomaticSilentRefresh();
try {
await this._oAuthService.loadDiscoveryDocumentAndTryLogin();
} catch (error) {
this.login();
}
this._initialized.next(true);
}
isAuthenticated() {
return this._oAuthService.hasValidIdToken();
}
getToken() {
return this._oAuthService.getAccessToken();
}
getClaims() {
const token = this._oAuthService.getAccessToken();
return this.parseJwt(token);
}
getClaimByKey(key: string) {
const claims = this.getClaims();
if (isNullOrUndefined(claims)) {
return null;
}
return claims[key];
}
parseJwt(token: string) {
if (isNullOrUndefined(token)) {
return null;
}
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const encoded = window.atob(base64);
return JSON.parse(encoded);
}
login() {
this._oAuthService.initLoginFlow();
}
setKeyCardToken(token: string) {
this._oAuthService.customQueryParams = {
temp_token: token,
};
}
async logout() {
await this._oAuthService.revokeTokenAndLogout();
}
}

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './auth.module';
export * from './auth.service';
// end:ng42.barrel

View File

@@ -0,0 +1,5 @@
/*
* Public API Surface of auth
*/
export * from './lib';

View File

@@ -0,0 +1,9 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js';
import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), { teardown: { destroyAfterEach: true } });

View File

@@ -0,0 +1,19 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/lib",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": [],
"lib": [
"dom",
"es2018"
]
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

View File

@@ -0,0 +1,10 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.lib.json",
"compilerOptions": {
"declarationMap": false
},
"angularCompilerOptions": {
"compilationMode": "partial"
}
}

View File

@@ -0,0 +1,17 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/spec",
"types": [
"jasmine"
]
},
"files": [
"src/test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@@ -0,0 +1,25 @@
# Breadcrumb
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.1.2.
## Code scaffolding
Run `ng generate component component-name --project breadcrumb` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project breadcrumb`.
> Note: Don't forget to add `--project breadcrumb` or else it will be added to the default project in your `angular.json` file.
## Build
Run `ng build breadcrumb` to build the project. The build artifacts will be stored in the `dist/` directory.
## Publishing
After building your library with `ng build breadcrumb`, go to the dist folder `cd dist/breadcrumb` and run `npm publish`.
## Running unit tests
Run `ng test breadcrumb` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

View File

@@ -0,0 +1,32 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../../coverage/core/breadcrumb'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true,
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true,
});
};

View File

@@ -0,0 +1,7 @@
{
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../../dist/core/breadcrumb",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@@ -0,0 +1,11 @@
{
"name": "@core/breadcrumb",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^10.1.2",
"@angular/core": "^10.1.2"
},
"dependencies": {
"tslib": "^2.0.0"
}
}

View File

@@ -22,9 +22,17 @@ describe('Breadcrumb Service', () => {
service = TestBed.inject(BreadcrumbService);
});
describe('getByKey$', () => {
it('should call store.select with the selectBreadcrumbsByKey selector', () => {
store.select.and.returnValue(of([]));
service.getByKey$('unit-test');
expect(store.select).toHaveBeenCalledWith(selectors.selectBreadcrumbsByKey, 'unit-test');
});
});
describe('addBreadcrumb', () => {
it('should call store.dispatch with the addBreadecrumb action and retuns a breadcrumb with an id', () => {
let breadcrumb: Breadcrumb = { name: 'unit-test', key: 'hello-key', path: 'Run The Test', section: 'customer' };
let breadcrumb: Breadcrumb = { name: 'unit-test', key: 'hello-key', path: 'Run The Test' };
breadcrumb = service.addBreadcrumb(breadcrumb);
expect(store.dispatch).toHaveBeenCalledWith(actions.addBreadcrumb({ breadcrumb }));
expect(isNumber(breadcrumb.id)).toBeTruthy();

View File

@@ -37,8 +37,8 @@ export class BreadcrumbService {
return current;
}
return latest.timestamp > current.timestamp ? latest : current;
}, undefined),
),
}, undefined)
)
);
}
@@ -120,10 +120,7 @@ export class BreadcrumbService {
if (recursive) {
const breadcrumbs = await this.getBreadcrumbByKey$(breadcrumb.key).pipe(take(1)).toPromise();
breadcrumbsToRemove = [
...breadcrumbsToRemove,
...breadcrumbs.filter((crumb) => crumb.timestamp > breadcrumb.timestamp),
];
breadcrumbsToRemove = [...breadcrumbsToRemove, ...breadcrumbs.filter((crumb) => crumb.timestamp > breadcrumb.timestamp)];
}
if (!breadcrumbsToRemove.length) {
@@ -138,12 +135,9 @@ export class BreadcrumbService {
crumbs.forEach((crumb) => this.removeBreadcrumb(crumb.id));
}
getLatestBreadcrumbForSection(
section: 'customer' | 'branch',
predicate: (crumb: Breadcrumb) => boolean = (_) => true,
) {
getLatestBreadcrumbForSection(section: 'customer' | 'branch') {
return this.store
.select(selectors.selectBreadcrumbsBySection, { section })
.pipe(map((crumbs) => crumbs.sort((a, b) => b.timestamp - a.timestamp).find((f) => predicate(f))));
.pipe(map((crumbs) => crumbs.sort((a, b) => b.changed - a.changed).find((f) => true)));
}
}

View File

@@ -22,12 +22,12 @@ export interface Breadcrumb {
/**
* Url
*/
path: string | any[];
path: string;
/**
* Query Parameter
*/
params?: object;
params?: Object;
/**
* Timestamp

Some files were not shown because too many files have changed in this diff Show More