mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Merge branch 'develop' into feature/5258-Praemie-Landing
This commit is contained in:
185
.github/prompts/plan.prompt.md
vendored
Normal file
185
.github/prompts/plan.prompt.md
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
---
|
||||
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.
|
||||
---
|
||||
|
||||
# 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!]
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
Remember: **RESEARCH FIRST, PLAN THOROUGHLY, WAIT FOR APPROVAL, THEN IMPLEMENT**
|
||||
148
CLAUDE.md
Normal file
148
CLAUDE.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# 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
|
||||
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
argsToTemplate,
|
||||
moduleMetadata,
|
||||
type Meta,
|
||||
type StoryObj,
|
||||
} from '@storybook/angular';
|
||||
import {
|
||||
InlineInputComponent,
|
||||
InputControlDirective,
|
||||
} from '@isa/ui/input-controls';
|
||||
|
||||
const meta: Meta<InlineInputComponent> = {
|
||||
component: InlineInputComponent,
|
||||
title: 'ui/input-controls/InlineInput',
|
||||
argTypes: {
|
||||
size: { control: 'select', options: ['small', 'medium'] },
|
||||
},
|
||||
args: {
|
||||
size: 'medium',
|
||||
},
|
||||
decorators: [
|
||||
moduleMetadata({
|
||||
imports: [InputControlDirective],
|
||||
}),
|
||||
],
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<ui-inline-input ${argsToTemplate(args)}>
|
||||
<input type="text" placeholder="Enter inline text" />
|
||||
</ui-inline-input>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<InlineInputComponent>;
|
||||
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
size: 'medium',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithLabel: Story = {
|
||||
args: {
|
||||
size: 'medium',
|
||||
},
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<ui-inline-input ${argsToTemplate(args)}>
|
||||
<label>Label</label>
|
||||
<input type="text" placeholder="Enter inline text" />
|
||||
</ui-inline-input>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "data-access",
|
||||
"name": "checkout-data-access",
|
||||
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "libs/checkout/data-access/src",
|
||||
"prefix": "lib",
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<reward-header></reward-header>
|
||||
|
||||
<!-- <filter-controls-panel (triggerSearch)="search($event)"></filter-controls-panel> -->
|
||||
<filter-controls-panel (triggerSearch)="search($event)"></filter-controls-panel>
|
||||
|
||||
<span
|
||||
class="text-isa-neutral-900 isa-text-body-2-regular self-start"
|
||||
data-what="result-count"
|
||||
>
|
||||
<!-- {{ hits() }} Einträge -->
|
||||
XXX Einträge
|
||||
{{ hits() }} Einträge
|
||||
</span>
|
||||
|
||||
<div class="flex flex-col gap-4 w-full items-center justify-center mb-24">
|
||||
|
||||
@@ -72,7 +72,7 @@ export class RewardCatalogComponent {
|
||||
* FilterService instance for managing filter state and queries.
|
||||
* @private
|
||||
*/
|
||||
// #filterService = inject(FilterService);
|
||||
#filterService = inject(FilterService);
|
||||
|
||||
/**
|
||||
* Restores scroll position when navigating back to this component.
|
||||
@@ -85,47 +85,47 @@ export class RewardCatalogComponent {
|
||||
* @private
|
||||
* @returns The reward catalog resource.
|
||||
*/
|
||||
// rewardCatalogResource = createRewardCatalogResource(() => {
|
||||
// return {
|
||||
// queryToken: this.#filterService.query(),
|
||||
// searchTrigger: this.searchTrigger(),
|
||||
// };
|
||||
// });
|
||||
rewardCatalogResource = createRewardCatalogResource(() => {
|
||||
return {
|
||||
queryToken: this.#filterService.query(),
|
||||
searchTrigger: this.searchTrigger(),
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Computed signal for the current reward catalog response.
|
||||
* @returns The latest reward catalog response or undefined.
|
||||
*/
|
||||
// listResponseValue = computed(() => this.rewardCatalogResource.value());
|
||||
listResponseValue = computed(() => this.rewardCatalogResource.value());
|
||||
|
||||
/**
|
||||
* Computed signal indicating whether the reward catalog resource is currently fetching data.
|
||||
* @returns True if fetching, false otherwise.
|
||||
*/
|
||||
// listFetching = computed(
|
||||
// () => this.rewardCatalogResource.status() === 'loading',
|
||||
// );
|
||||
listFetching = computed(
|
||||
() => this.rewardCatalogResource.status() === 'loading',
|
||||
);
|
||||
|
||||
/**
|
||||
* Computed signal for the reward catalog items to display.
|
||||
* @returns Array of Item.
|
||||
*/
|
||||
// items = computed(() => {
|
||||
// const value = this.listResponseValue();
|
||||
// return value?.result ? value.result : [];
|
||||
// });
|
||||
items = computed(() => {
|
||||
const value = this.listResponseValue();
|
||||
return value?.result ? value.result : [];
|
||||
});
|
||||
|
||||
/**
|
||||
* Computed signal for the total number of hits in the reward catalog.
|
||||
* @returns Number of hits, or 0 if unavailable.
|
||||
*/
|
||||
// hits = computed(() => {
|
||||
// const value = this.listResponseValue();
|
||||
// return value?.hits ? value.hits : 0;
|
||||
// });
|
||||
hits = computed(() => {
|
||||
const value = this.listResponseValue();
|
||||
return value?.hits ? value.hits : 0;
|
||||
});
|
||||
|
||||
search(trigger: SearchTrigger): void {
|
||||
// this.#filterService.commit();
|
||||
this.#filterService.commit();
|
||||
this.searchTrigger.set(trigger);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,19 +193,18 @@ export class FilterService {
|
||||
return input;
|
||||
}
|
||||
|
||||
const isParent =
|
||||
const hasChildren =
|
||||
Array.isArray(checkboxOption.values) &&
|
||||
checkboxOption.values.length > 0;
|
||||
let keys: string[] = [];
|
||||
|
||||
if (isParent) {
|
||||
// If the option has children, we need to include all child keys
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
keys = checkboxOption.values!.map((v) =>
|
||||
checkboxOptionKeysHelper(v).join('.'),
|
||||
// Collect the keys we want to add/remove. When the option has children we only include the
|
||||
// children's keys; otherwise we include the option's own key. Avoids non-null assertion.
|
||||
let keys: string[];
|
||||
if (hasChildren) {
|
||||
const children = checkboxOption.values as CheckboxFilterInputOption[]; // safe due to hasChildren
|
||||
keys = children.map((child) =>
|
||||
checkboxOptionKeysHelper(child).join('.'),
|
||||
);
|
||||
} else {
|
||||
// If no children, just use the current option key
|
||||
keys = [optionKeys.join('.')];
|
||||
}
|
||||
|
||||
@@ -361,6 +360,45 @@ export class FilterService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number range values for an input with the specified key.
|
||||
*
|
||||
* This updates the current minimum (min) and maximum (max) values for a
|
||||
* NumberRange filter input. Passing `undefined` for either side will clear that
|
||||
* boundary while leaving the other intact.
|
||||
*
|
||||
* @param key - The key of the number range input to update
|
||||
* @param min - The minimum numeric value (inclusive) or undefined to clear
|
||||
* @param max - The maximum numeric value (inclusive) or undefined to clear
|
||||
* @param options - Optional parameters
|
||||
* @param options.commit - If true, commits the changes immediately
|
||||
*/
|
||||
setInputNumberRangeValue(
|
||||
key: string,
|
||||
min?: number,
|
||||
max?: number,
|
||||
options?: { commit: boolean },
|
||||
): void {
|
||||
const inputs = this.#state.inputs().map((input) => {
|
||||
if (input.key !== key) {
|
||||
return input;
|
||||
}
|
||||
|
||||
if (input.type === InputType.NumberRange) {
|
||||
return { ...input, min, max };
|
||||
}
|
||||
|
||||
this.logUnsupportedInputType(input, 'setInputNumberRangeValue');
|
||||
return input;
|
||||
});
|
||||
|
||||
patchState(this.#state, { inputs });
|
||||
|
||||
if (options?.commit) {
|
||||
this.commit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to consistently log unsupported input type warnings
|
||||
* @private
|
||||
@@ -809,6 +847,17 @@ export class FilterService {
|
||||
result[input.key] = '';
|
||||
}
|
||||
break;
|
||||
case InputType.NumberRange: {
|
||||
const { min: minVal, max: maxVal } = input;
|
||||
if (minVal != null && maxVal != null) {
|
||||
result[input.key] = `"${minVal}-${maxVal}"`;
|
||||
} else if (minVal != null) {
|
||||
result[input.key] = `"${minVal}-"`;
|
||||
} else if (maxVal != null) {
|
||||
result[input.key] = `"-${maxVal}"`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -927,6 +976,18 @@ export class FilterService {
|
||||
this.setInputDateRangeValue(key, start, stop);
|
||||
break;
|
||||
}
|
||||
case InputType.NumberRange: {
|
||||
const decoded = decodeURIComponent(params[key]);
|
||||
const [minRaw, maxRaw] = decoded.split('-"');
|
||||
const min = minRaw
|
||||
? Number(minRaw.replace(/"-/g, '').replace(/"/g, ''))
|
||||
: undefined;
|
||||
const max = maxRaw
|
||||
? Number(maxRaw.replace(/"-/g, '').replace(/"/g, ''))
|
||||
: undefined;
|
||||
this.setInputNumberRangeValue(key, min, max);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
this.logUnsupportedInputType(input, 'parseQueryParams');
|
||||
break;
|
||||
|
||||
@@ -50,6 +50,15 @@ export function mapFilterInputToRecord(
|
||||
if (start || stop) {
|
||||
acc[input.key] = `${start}-${stop}`;
|
||||
}
|
||||
} else if (input.type === InputType.NumberRange) {
|
||||
const { min, max } = input;
|
||||
if (min && max) {
|
||||
acc[input.key] = `"${min}-${max}"`;
|
||||
} else if (min) {
|
||||
acc[input.key] = `"${min}-"`;
|
||||
} else if (max) {
|
||||
acc[input.key] = `"-${max}"`;
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
@@ -119,22 +119,4 @@ describe('filterInputMapping', () => {
|
||||
mapped: 'dateRange',
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error for unknown input type', () => {
|
||||
// Arrange
|
||||
const group = 'testGroup';
|
||||
const input: Input = {
|
||||
key: 'unknownInput',
|
||||
label: 'Unknown Input',
|
||||
type: 999 as unknown as InputType, // Invalid input type
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
expect(() => filterInputMapping(group, input)).toThrow(
|
||||
'Unknown input type: 999',
|
||||
);
|
||||
expect(mockTextFilterInputMapping).not.toHaveBeenCalled();
|
||||
expect(mockCheckboxFilterInputMapping).not.toHaveBeenCalled();
|
||||
expect(mockDateRangeFilterInputMapping).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,29 +1,32 @@
|
||||
import { Input, InputType } from '../../types';
|
||||
import { FilterInput } from '../schemas';
|
||||
import { checkboxFilterInputMapping } from './checkbox-filter-input.mapping';
|
||||
import { dateRangeFilterInputMapping } from './date-range-filter-input.mapping';
|
||||
import { textFilterInputMapping } from './text-filter-input.mapping';
|
||||
|
||||
/**
|
||||
* Maps an Input object to the appropriate FilterInput type based on its input type
|
||||
*
|
||||
* This function serves as a router that delegates to the specific mapping function
|
||||
* based on the input type (Text, Checkbox, DateRange). It ensures that each input
|
||||
* is converted to its corresponding strongly-typed filter input object.
|
||||
*
|
||||
* @param group - The group identifier that this input belongs to
|
||||
* @param input - The source input object to map
|
||||
* @returns A validated FilterInput object of the appropriate subtype
|
||||
* @throws Error if the input type is not supported
|
||||
*/
|
||||
export function filterInputMapping(group: string, input: Input): FilterInput {
|
||||
switch (input.type) {
|
||||
case InputType.Text:
|
||||
return textFilterInputMapping(group, input);
|
||||
case InputType.Checkbox:
|
||||
return checkboxFilterInputMapping(group, input);
|
||||
case InputType.DateRange:
|
||||
return dateRangeFilterInputMapping(group, input);
|
||||
}
|
||||
throw new Error(`Unknown input type: ${input.type}`);
|
||||
}
|
||||
import { Input, InputType } from '../../types';
|
||||
import { FilterInput } from '../schemas';
|
||||
import { checkboxFilterInputMapping } from './checkbox-filter-input.mapping';
|
||||
import { dateRangeFilterInputMapping } from './date-range-filter-input.mapping';
|
||||
import { numberRangeFilterInputMapping } from './number-range-filter-input.mapping';
|
||||
import { textFilterInputMapping } from './text-filter-input.mapping';
|
||||
|
||||
/**
|
||||
* Maps an Input object to the appropriate FilterInput type based on its input type
|
||||
*
|
||||
* This function serves as a router that delegates to the specific mapping function
|
||||
* based on the input type (Text, Checkbox, DateRange). It ensures that each input
|
||||
* is converted to its corresponding strongly-typed filter input object.
|
||||
*
|
||||
* @param group - The group identifier that this input belongs to
|
||||
* @param input - The source input object to map
|
||||
* @returns A validated FilterInput object of the appropriate subtype
|
||||
* @throws Error if the input type is not supported
|
||||
*/
|
||||
export function filterInputMapping(group: string, input: Input): FilterInput {
|
||||
switch (input.type) {
|
||||
case InputType.Text:
|
||||
return textFilterInputMapping(group, input);
|
||||
case InputType.Checkbox:
|
||||
return checkboxFilterInputMapping(group, input);
|
||||
case InputType.DateRange:
|
||||
return dateRangeFilterInputMapping(group, input);
|
||||
case InputType.NumberRange:
|
||||
return numberRangeFilterInputMapping(group, input);
|
||||
}
|
||||
console.warn('filterInputMapping: Unknown input type', input);
|
||||
}
|
||||
|
||||
@@ -31,8 +31,14 @@ export function filterMapping(settings: QuerySettings): Filter {
|
||||
for (const group of groups) {
|
||||
filter.groups.push(filterGroupMapping(group));
|
||||
|
||||
for (const input of group.input) {
|
||||
filter.inputs.push(filterInputMapping(group.group, input));
|
||||
if (Array.isArray(group.input)) {
|
||||
for (const input of group.input) {
|
||||
const inputs = filterInputMapping(group.group, input);
|
||||
if (inputs === undefined) {
|
||||
continue;
|
||||
}
|
||||
filter.inputs.push(inputs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Input, InputType } from '../../types';
|
||||
import {
|
||||
NumberRangeFilterInput,
|
||||
NumberRangeFilterInputSchema,
|
||||
} from '../schemas';
|
||||
|
||||
export function numberRangeFilterInputMapping(
|
||||
group: string,
|
||||
input: Input,
|
||||
): NumberRangeFilterInput {
|
||||
return NumberRangeFilterInputSchema.parse({
|
||||
group,
|
||||
key: input.key,
|
||||
label: input.label,
|
||||
description: input.description,
|
||||
type: InputType.NumberRange,
|
||||
min: input.options?.values?.[0]?.value
|
||||
? Number(input.options?.values?.[0]?.value)
|
||||
: undefined,
|
||||
max: input.options?.values?.[1]?.value
|
||||
? Number(input.options?.values?.[1]?.value)
|
||||
: undefined,
|
||||
minValue: input.options?.values?.[0]?.minValue
|
||||
? Number(input.options?.values?.[0]?.minValue.replace('-', ''))
|
||||
: undefined,
|
||||
maxValue: input.options?.values?.[0]?.maxValue
|
||||
? Number(input.options?.values?.[0]?.maxValue.replace('-', ''))
|
||||
: undefined,
|
||||
minLabel: input.options?.values?.[0]?.label || 'Min',
|
||||
maxLabel: input.options?.values?.[1]?.label || 'Max',
|
||||
});
|
||||
}
|
||||
@@ -1,21 +1,23 @@
|
||||
import { z } from 'zod';
|
||||
import { CheckboxFilterInputSchema } from './checkbox-filter-input.schema';
|
||||
import { DateRangeFilterInputSchema } from './date-range-filter-input.schema';
|
||||
import { TextFilterInputSchema } from './text-filter-input.schema';
|
||||
|
||||
/**
|
||||
* A union schema representing all possible filter input types in the system.
|
||||
* This schema allows for type discrimination based on the `type` property.
|
||||
*
|
||||
* Supported filter input types:
|
||||
* - TextFilterInput: Simple text input fields
|
||||
* - CheckboxFilterInput: Multiple-choice checkbox selections
|
||||
* - DateRangeFilterInput: Date range selectors for time-based filtering
|
||||
*/
|
||||
export const FilterInputSchema = z.union([
|
||||
TextFilterInputSchema,
|
||||
CheckboxFilterInputSchema,
|
||||
DateRangeFilterInputSchema,
|
||||
]);
|
||||
|
||||
export type FilterInput = z.infer<typeof FilterInputSchema>;
|
||||
import { z } from 'zod';
|
||||
import { CheckboxFilterInputSchema } from './checkbox-filter-input.schema';
|
||||
import { DateRangeFilterInputSchema } from './date-range-filter-input.schema';
|
||||
import { TextFilterInputSchema } from './text-filter-input.schema';
|
||||
import { NumberRangeFilterInputSchema } from './number-range-filter-input.schema';
|
||||
|
||||
/**
|
||||
* A union schema representing all possible filter input types in the system.
|
||||
* This schema allows for type discrimination based on the `type` property.
|
||||
*
|
||||
* Supported filter input types:
|
||||
* - TextFilterInput: Simple text input fields
|
||||
* - CheckboxFilterInput: Multiple-choice checkbox selections
|
||||
* - DateRangeFilterInput: Date range selectors for time-based filtering
|
||||
*/
|
||||
export const FilterInputSchema = z.union([
|
||||
TextFilterInputSchema,
|
||||
CheckboxFilterInputSchema,
|
||||
DateRangeFilterInputSchema,
|
||||
NumberRangeFilterInputSchema,
|
||||
]);
|
||||
|
||||
export type FilterInput = z.infer<typeof FilterInputSchema>;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
export * from './base-filter-input.schema';
|
||||
export * from './checkbox-filter-input-option.schema';
|
||||
export * from './checkbox-filter-input.schema';
|
||||
export * from './date-range-filter-input.schema';
|
||||
export * from './filter-group.schema';
|
||||
export * from './filter-input.schema';
|
||||
export * from './filter.schema';
|
||||
export * from './order-by-direction.schema';
|
||||
export * from './order-by-option.schema';
|
||||
export * from './query-order.schema';
|
||||
export * from './query.schema';
|
||||
export * from './text-filter-input.schema';
|
||||
export * from './base-filter-input.schema';
|
||||
export * from './checkbox-filter-input-option.schema';
|
||||
export * from './checkbox-filter-input.schema';
|
||||
export * from './date-range-filter-input.schema';
|
||||
export * from './filter-group.schema';
|
||||
export * from './filter-input.schema';
|
||||
export * from './filter.schema';
|
||||
export * from './number-range-filter-input.schema';
|
||||
export * from './order-by-direction.schema';
|
||||
export * from './order-by-option.schema';
|
||||
export * from './query-order.schema';
|
||||
export * from './query.schema';
|
||||
export * from './text-filter-input.schema';
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { z } from 'zod';
|
||||
import { BaseFilterInputSchema } from './base-filter-input.schema';
|
||||
import { InputType } from '../../types';
|
||||
|
||||
export const NumberRangeFilterInputSchema = BaseFilterInputSchema.extend({
|
||||
type: z
|
||||
.literal(InputType.NumberRange)
|
||||
.describe(
|
||||
'Specifies this as a number range input type. Must be InputType.NumberRange.',
|
||||
),
|
||||
min: z.number().optional().describe('Current minimum value of the range.'),
|
||||
max: z.number().optional().describe('Current maximum value of the range.'),
|
||||
minValue: z
|
||||
.number()
|
||||
.optional()
|
||||
.describe(
|
||||
'Minimum value of the number range. Optional if only a maximum is needed.',
|
||||
),
|
||||
maxValue: z
|
||||
.number()
|
||||
.optional()
|
||||
.describe(
|
||||
'Maximum value of the number range. Optional if only a minimum is needed.',
|
||||
),
|
||||
minLabel: z.string().describe('Label for the minimum input field.'),
|
||||
maxLabel: z.string().describe('Label for the maximum input field.'),
|
||||
}).describe('NumberRangeFilterInput');
|
||||
|
||||
export type NumberRangeFilterInput = z.infer<
|
||||
typeof NumberRangeFilterInputSchema
|
||||
>;
|
||||
@@ -1,15 +1,19 @@
|
||||
@switch (filterInput().type) {
|
||||
@case (InputType.Checkbox) {
|
||||
<filter-checkbox-input [inputKey]="filterInput().key">
|
||||
</filter-checkbox-input>
|
||||
}
|
||||
@case (InputType.DateRange) {
|
||||
<filter-datepicker-range-input [inputKey]="filterInput().key">
|
||||
</filter-datepicker-range-input>
|
||||
}
|
||||
@default {
|
||||
<div class="text-isa-accent-red isa-text-body-1-bold">
|
||||
Fehler: Kein Template für diesen Typ gefunden! {{ filterInput().type }}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@switch (filterInput().type) {
|
||||
@case (InputType.Checkbox) {
|
||||
<filter-checkbox-input [inputKey]="filterInput().key">
|
||||
</filter-checkbox-input>
|
||||
}
|
||||
@case (InputType.DateRange) {
|
||||
<filter-datepicker-range-input [inputKey]="filterInput().key">
|
||||
</filter-datepicker-range-input>
|
||||
}
|
||||
@case (InputType.NumberRange) {
|
||||
<filter-number-range-input [inputKey]="filterInput().key">
|
||||
</filter-number-range-input>
|
||||
}
|
||||
@default {
|
||||
<div class="text-isa-accent-red isa-text-body-1-bold">
|
||||
Fehler: Kein Template für diesen Typ gefunden! {{ filterInput().type }}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,32 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
input,
|
||||
ViewEncapsulation,
|
||||
} from '@angular/core';
|
||||
import { CheckboxInputComponent } from '../checkbox-input';
|
||||
import { DatepickerRangeInputComponent } from '../datepicker-range-input';
|
||||
import { FilterInput } from '../../core';
|
||||
import { InputType } from '../../types';
|
||||
|
||||
@Component({
|
||||
selector: 'filter-input-renderer',
|
||||
templateUrl: 'input-renderer.component.html',
|
||||
styleUrls: ['./input-renderer.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
standalone: true,
|
||||
imports: [CheckboxInputComponent, DatepickerRangeInputComponent],
|
||||
host: {
|
||||
'[class]': "['filter-input-renderer']",
|
||||
},
|
||||
})
|
||||
export class InputRendererComponent {
|
||||
filterInput = input.required<FilterInput>();
|
||||
InputType = InputType;
|
||||
}
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
input,
|
||||
ViewEncapsulation,
|
||||
} from '@angular/core';
|
||||
import { CheckboxInputComponent } from '../checkbox-input';
|
||||
import { DatepickerRangeInputComponent } from '../datepicker-range-input';
|
||||
import { NumberRangeInputComponent } from '../number-range-input';
|
||||
import { FilterInput } from '../../core';
|
||||
import { InputType } from '../../types';
|
||||
|
||||
@Component({
|
||||
selector: 'filter-input-renderer',
|
||||
templateUrl: 'input-renderer.component.html',
|
||||
styleUrls: ['./input-renderer.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
standalone: true,
|
||||
imports: [
|
||||
CheckboxInputComponent,
|
||||
DatepickerRangeInputComponent,
|
||||
NumberRangeInputComponent,
|
||||
],
|
||||
host: {
|
||||
'[class]': "['filter-input-renderer']",
|
||||
},
|
||||
})
|
||||
export class InputRendererComponent {
|
||||
filterInput = input.required<FilterInput>();
|
||||
InputType = InputType;
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './number-range-input.component';
|
||||
@@ -0,0 +1,43 @@
|
||||
@let inp = input();
|
||||
<ui-inline-input>
|
||||
<label>{{ inp.minLabel }}</label>
|
||||
<input
|
||||
type="number"
|
||||
uiInputControl
|
||||
[(ngModel)]="minValue"
|
||||
[min]="minInputLowerLimit()"
|
||||
[max]="minInputUpperLimit()"
|
||||
#minModel="ngModel"
|
||||
/>
|
||||
@if (minModel.invalid && minModel.touched) {
|
||||
<div class="text-isa-accent-red isa-text-caption-regular">
|
||||
@if (minModel.errors?.['min']) {
|
||||
Mindestens {{ minInputLowerLimit() }}.
|
||||
}
|
||||
@if (minModel.errors?.['max']) {
|
||||
Maximal {{ minInputUpperLimit() }}.
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ui-inline-input>
|
||||
<ui-inline-input>
|
||||
<label>{{ inp.maxLabel }}</label>
|
||||
<input
|
||||
type="number"
|
||||
uiInputControl
|
||||
[(ngModel)]="maxValue"
|
||||
[min]="maxInputLowerLimit()"
|
||||
[max]="maxInputUpperLimit()"
|
||||
#maxModel="ngModel"
|
||||
/>
|
||||
@if (minModel.invalid && minModel.touched) {
|
||||
<div class="text-isa-accent-red isa-text-caption-regular">
|
||||
@if (minModel.errors?.['min']) {
|
||||
Mindestens {{ minInputLowerLimit() }}.
|
||||
}
|
||||
@if (minModel.errors?.['max']) {
|
||||
Maximal {{ minInputUpperLimit() }}.
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ui-inline-input>
|
||||
@@ -0,0 +1,7 @@
|
||||
:host {
|
||||
@apply grid grid-cols-2 gap-2 p-5;
|
||||
}
|
||||
|
||||
.ui-input-control {
|
||||
@apply w-full;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
computed,
|
||||
effect,
|
||||
inject,
|
||||
input,
|
||||
linkedSignal,
|
||||
} from '@angular/core';
|
||||
import { FilterService, NumberRangeFilterInput } from '../../core';
|
||||
import { InputType } from '../../types';
|
||||
import {
|
||||
InputControlDirective,
|
||||
InlineInputComponent,
|
||||
} from '@isa/ui/input-controls';
|
||||
import { logger } from '@isa/core/logging';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'filter-number-range-input',
|
||||
templateUrl: './number-range-input.component.html',
|
||||
styleUrls: ['./number-range-input.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [InlineInputComponent, InputControlDirective, FormsModule],
|
||||
host: { '[class]': "['filter-number-range-input']" },
|
||||
})
|
||||
export class NumberRangeInputComponent {
|
||||
#logger = logger(() => ({ component: 'NumberRangeInputComponent' }));
|
||||
|
||||
#filterService = inject(FilterService);
|
||||
inputKey = input.required<string>();
|
||||
|
||||
input = computed<NumberRangeFilterInput>(() => {
|
||||
const inputs = this.#filterService.inputs();
|
||||
const input = inputs.find(
|
||||
(input) =>
|
||||
input.key === this.inputKey() && input.type === InputType.NumberRange,
|
||||
) as NumberRangeFilterInput;
|
||||
|
||||
if (!input) {
|
||||
const err = new Error(`Input not found for key: ${this.inputKey()}`);
|
||||
this.#logger.error('Input not found', { error: err });
|
||||
}
|
||||
|
||||
return input;
|
||||
});
|
||||
|
||||
minValue = linkedSignal(() => this.input().min);
|
||||
|
||||
// Overall domain bounds coming from the filter input definition
|
||||
overallMinLimit = computed(() => this.input().minValue ?? 0);
|
||||
overallMaxLimit = computed(() => this.input().maxValue ?? Infinity);
|
||||
|
||||
// Allowed bounds for the minimum value input
|
||||
minInputLowerLimit = computed(() => this.overallMinLimit());
|
||||
minInputUpperLimit = computed(() =>
|
||||
Math.min(this.maxValue() ?? Infinity, this.overallMaxLimit()),
|
||||
);
|
||||
|
||||
maxValue = linkedSignal(() => this.input().max);
|
||||
|
||||
// Allowed bounds for the maximum value input
|
||||
maxInputLowerLimit = computed(() =>
|
||||
Math.max(this.minValue() ?? 0, this.overallMinLimit()),
|
||||
);
|
||||
maxInputUpperLimit = computed(() => this.overallMaxLimit());
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
const minValue = this.minValue();
|
||||
const maxValue = this.maxValue();
|
||||
|
||||
const currentMin = this.input().min;
|
||||
const currentMax = this.input().max;
|
||||
|
||||
if (minValue === currentMin && maxValue === currentMax) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#filterService.setInputNumberRangeValue(
|
||||
this.inputKey(),
|
||||
minValue,
|
||||
maxValue,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,14 @@ export enum InputType {
|
||||
Text = 1,
|
||||
Checkbox = 2,
|
||||
DateRange = 128,
|
||||
NumberRange = 4096,
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumeration of search trigger types that indicate how a search was initiated.
|
||||
* Used throughout the filter system to track user interaction patterns and
|
||||
* optimize search behavior based on the trigger source.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Handle different search triggers
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
export * from './lib/checkbox/checkbox-label.directive';
|
||||
export * from './lib/checkbox/checkbox.component';
|
||||
export * from './lib/checkbox/checklist-value.directive';
|
||||
export * from './lib/checkbox/checklist.component';
|
||||
export * from './lib/core/input-control.directive';
|
||||
export * from './lib/dropdown/dropdown.component';
|
||||
export * from './lib/dropdown/dropdown.types';
|
||||
export * from './lib/listbox/listbox-item.directive';
|
||||
export * from './lib/listbox/listbox.directive';
|
||||
export * from './lib/text-field/textarea.component';
|
||||
export * from './lib/text-field/text-field.component';
|
||||
export * from './lib/text-field/text-field-clear.component';
|
||||
export * from './lib/text-field/text-field-container.component';
|
||||
export * from './lib/text-field/text-field-errors.component';
|
||||
export * from './lib/chips/chips.component';
|
||||
export * from './lib/chips/chip-option.component';
|
||||
export * from './lib/checkbox/checkbox-label.directive';
|
||||
export * from './lib/checkbox/checkbox.component';
|
||||
export * from './lib/checkbox/checklist-value.directive';
|
||||
export * from './lib/checkbox/checklist.component';
|
||||
export * from './lib/core/input-control.directive';
|
||||
export * from './lib/dropdown/dropdown.component';
|
||||
export * from './lib/dropdown/dropdown.types';
|
||||
export * from './lib/listbox/listbox-item.directive';
|
||||
export * from './lib/listbox/listbox.directive';
|
||||
export * from './lib/inline-input/inline-input.component';
|
||||
export * from './lib/text-field/textarea.component';
|
||||
export * from './lib/text-field/text-field.component';
|
||||
export * from './lib/text-field/text-field-clear.component';
|
||||
export * from './lib/text-field/text-field-container.component';
|
||||
export * from './lib/text-field/text-field-errors.component';
|
||||
export * from './lib/chips/chips.component';
|
||||
export * from './lib/chips/chip-option.component';
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
@use "./lib/checkbox/checklist";
|
||||
@use "./lib/chips/chips";
|
||||
@use "./lib/dropdown/dropdown";
|
||||
@use "./lib/inline-input/inline-input";
|
||||
@use "./lib/listbox/listbox";
|
||||
@use "./lib/text-field/text-field";
|
||||
@use "./lib/text-field/text-field-clear";
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
.ui-inline-input {
|
||||
display: inline-flex;
|
||||
padding: 0.5rem 0;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
gap: 0.25rem;
|
||||
|
||||
label {
|
||||
@apply text-isa-neutral-900;
|
||||
|
||||
font-family: "Open Sans";
|
||||
font-size: 0.5rem;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 0.75rem; /* 150% */
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
&.ui-inline-input--small input {
|
||||
@apply isa-text-body-2-bold;
|
||||
}
|
||||
|
||||
&.ui-inline-input--medium input {
|
||||
@apply isa-text-body-1-bold;
|
||||
}
|
||||
|
||||
input {
|
||||
@apply outline-none font-bold text-isa-neutral-900;
|
||||
|
||||
&::placeholder {
|
||||
@apply text-isa-neutral-500 font-normal;
|
||||
|
||||
&:hover {
|
||||
@apply font-normal;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
@apply text-isa-neutral-600 font-bold;
|
||||
}
|
||||
}
|
||||
|
||||
&.ng-invalid {
|
||||
@apply text-isa-accent-red;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<ng-content></ng-content>
|
||||
@@ -0,0 +1,39 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
computed,
|
||||
input,
|
||||
contentChild,
|
||||
} from '@angular/core';
|
||||
|
||||
import { InputControlDirective } from '../core/input-control.directive';
|
||||
|
||||
export const InlineInputSize = {
|
||||
Small: 'small',
|
||||
Medium: 'medium',
|
||||
} as const;
|
||||
export type InlineInputSize =
|
||||
(typeof InlineInputSize)[keyof typeof InlineInputSize];
|
||||
|
||||
@Component({
|
||||
selector: 'ui-inline-input',
|
||||
templateUrl: './inline-input.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: { '[class]': "['ui-inline-input', sizeClass()]" },
|
||||
})
|
||||
export class InlineInputComponent {
|
||||
size = input<InlineInputSize>(InlineInputSize.Medium);
|
||||
|
||||
sizeClass = computed(() => {
|
||||
const size = this.size();
|
||||
switch (size) {
|
||||
case InlineInputSize.Small:
|
||||
return 'ui-inline-input--small';
|
||||
case InlineInputSize.Medium:
|
||||
default:
|
||||
return 'ui-inline-input--medium';
|
||||
}
|
||||
});
|
||||
|
||||
inputControl = contentChild.required(InputControlDirective);
|
||||
}
|
||||
@@ -1,140 +1,140 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"importHelpers": false,
|
||||
"target": "ES2022",
|
||||
"module": "es2020",
|
||||
"lib": ["es2020", "dom"],
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@adapter/*": ["apps/isa-app/src/adapter/*/index.ts"],
|
||||
"@cdn/*": ["apps/isa-app/src/cdn/*/index.ts"],
|
||||
"@core/*": ["apps/isa-app/src/core/*/index.ts"],
|
||||
"@core/config": ["libs/core/config/src/index.ts"],
|
||||
"@domain/*": ["apps/isa-app/src/domain/*/index.ts"],
|
||||
"@external/*": ["apps/isa-app/src/external/*/index.ts"],
|
||||
"@generated/swagger/availability-api": [
|
||||
"generated/swagger/availability-api/src/index.ts"
|
||||
],
|
||||
"@generated/swagger/cat-search-api": [
|
||||
"generated/swagger/cat-search-api/src/index.ts"
|
||||
],
|
||||
"@generated/swagger/checkout-api": [
|
||||
"generated/swagger/checkout-api/src/index.ts"
|
||||
],
|
||||
"@generated/swagger/crm-api": ["generated/swagger/crm-api/src/index.ts"],
|
||||
"@generated/swagger/eis-api": ["generated/swagger/eis-api/src/index.ts"],
|
||||
"@generated/swagger/inventory-api": [
|
||||
"generated/swagger/inventory-api/src/index.ts"
|
||||
],
|
||||
"@generated/swagger/isa-api": ["generated/swagger/isa-api/src/index.ts"],
|
||||
"@generated/swagger/oms-api": ["generated/swagger/oms-api/src/index.ts"],
|
||||
"@generated/swagger/print-api": [
|
||||
"generated/swagger/print-api/src/index.ts"
|
||||
],
|
||||
"@generated/swagger/wws-api": ["generated/swagger/wws-api/src/index.ts"],
|
||||
"@hub/*": ["apps/isa-app/src/hub/*/index.ts"],
|
||||
"@isa/catalogue/data-access": ["libs/catalogue/data-access/src/index.ts"],
|
||||
"@isa/checkout/data-access": ["libs/checkout/data-access/src/index.ts"],
|
||||
"@isa/checkout/feature/reward-catalog": [
|
||||
"libs/checkout/feature/reward-catalog/src/index.ts"
|
||||
],
|
||||
"@isa/common/data-access": ["libs/common/data-access/src/index.ts"],
|
||||
"@isa/common/decorators": ["libs/common/decorators/src/index.ts"],
|
||||
"@isa/common/print": ["libs/common/print/src/index.ts"],
|
||||
"@isa/core/config": ["libs/core/config/src/index.ts"],
|
||||
"@isa/core/logging": ["libs/core/logging/src/index.ts"],
|
||||
"@isa/core/notifications": ["libs/core/notifications/src/index.ts"],
|
||||
"@isa/core/storage": ["libs/core/storage/src/index.ts"],
|
||||
"@isa/core/tabs": ["libs/core/tabs/src/index.ts"],
|
||||
"@isa/crm/data-access": ["libs/crm/data-access/src/index.ts"],
|
||||
"@isa/icons": ["libs/icons/src/index.ts"],
|
||||
"@isa/oms/data-access": ["libs/oms/data-access/src/index.ts"],
|
||||
"@isa/oms/feature/return-details": [
|
||||
"libs/oms/feature/return-details/src/index.ts"
|
||||
],
|
||||
"@isa/oms/feature/return-process": [
|
||||
"libs/oms/feature/return-process/src/index.ts"
|
||||
],
|
||||
"@isa/oms/feature/return-review": [
|
||||
"libs/oms/feature/return-review/src/index.ts"
|
||||
],
|
||||
"@isa/oms/feature/return-search": [
|
||||
"libs/oms/feature/return-search/src/index.ts"
|
||||
],
|
||||
"@isa/oms/feature/return-summary": [
|
||||
"libs/oms/feature/return-summary/src/index.ts"
|
||||
],
|
||||
"@isa/oms/shared/product-info": [
|
||||
"libs/oms/shared/product-info/src/index.ts"
|
||||
],
|
||||
"@isa/oms/shared/task-list": ["libs/oms/shared/task-list/src/index.ts"],
|
||||
"@isa/oms/utils/translation": ["libs/oms/utils/translation/src/index.ts"],
|
||||
"@isa/remission/data-access": ["libs/remission/data-access/src/index.ts"],
|
||||
"@isa/remission/feature/remission-list": [
|
||||
"libs/remission/feature/remission-list/src/index.ts"
|
||||
],
|
||||
"@isa/remission/feature/remission-return-receipt-details": [
|
||||
"libs/remission/feature/remission-return-receipt-details/src/index.ts"
|
||||
],
|
||||
"@isa/remission/feature/remission-return-receipt-list": [
|
||||
"libs/remission/feature/remission-return-receipt-list/src/index.ts"
|
||||
],
|
||||
"@isa/remission/shared/product": [
|
||||
"libs/remission/shared/product/src/index.ts"
|
||||
],
|
||||
"@isa/remission/shared/remission-start-dialog": [
|
||||
"libs/remission/shared/remission-start-dialog/src/index.ts"
|
||||
],
|
||||
"@isa/remission/shared/return-receipt-actions": [
|
||||
"libs/remission/shared/return-receipt-actions/src/index.ts"
|
||||
],
|
||||
"@isa/remission/shared/search-item-to-remit-dialog": [
|
||||
"libs/remission/shared/search-item-to-remit-dialog/src/index.ts"
|
||||
],
|
||||
"@isa/shared/filter": ["libs/shared/filter/src/index.ts"],
|
||||
"@isa/shared/product-foramt": ["libs/shared/product-format/src/index.ts"],
|
||||
"@isa/shared/product-image": ["libs/shared/product-image/src/index.ts"],
|
||||
"@isa/shared/product-router-link": [
|
||||
"libs/shared/product-router-link/src/index.ts"
|
||||
],
|
||||
"@isa/shared/scanner": ["libs/shared/scanner/src/index.ts"],
|
||||
"@isa/ui/bullet-list": ["libs/ui/bullet-list/src/index.ts"],
|
||||
"@isa/ui/buttons": ["libs/ui/buttons/src/index.ts"],
|
||||
"@isa/ui/datepicker": ["libs/ui/datepicker/src/index.ts"],
|
||||
"@isa/ui/dialog": ["libs/ui/dialog/src/index.ts"],
|
||||
"@isa/ui/empty-state": ["libs/ui/empty-state/src/index.ts"],
|
||||
"@isa/ui/expandable": ["libs/ui/expandable/src/index.ts"],
|
||||
"@isa/ui/input-controls": ["libs/ui/input-controls/src/index.ts"],
|
||||
"@isa/ui/item-rows": ["libs/ui/item-rows/src/index.ts"],
|
||||
"@isa/ui/label": ["libs/ui/label/src/index.ts"],
|
||||
"@isa/ui/layout": ["libs/ui/layout/src/index.ts"],
|
||||
"@isa/ui/menu": ["libs/ui/menu/src/index.ts"],
|
||||
"@isa/ui/progress-bar": ["libs/ui/progress-bar/src/index.ts"],
|
||||
"@isa/ui/search-bar": ["libs/ui/search-bar/src/index.ts"],
|
||||
"@isa/ui/skeleton-loader": ["libs/ui/skeleton-loader/src/index.ts"],
|
||||
"@isa/ui/toolbar": ["libs/ui/toolbar/src/index.ts"],
|
||||
"@isa/ui/tooltip": ["libs/ui/tooltip/src/index.ts"],
|
||||
"@isa/utils/ean-validation": ["libs/utils/ean-validation/src/index.ts"],
|
||||
"@isa/utils/scroll-position": ["libs/utils/scroll-position/src/index.ts"],
|
||||
"@isa/utils/z-safe-parse": ["libs/utils/z-safe-parse/src/index.ts"],
|
||||
"@modal/*": ["apps/isa-app/src/modal/*/index.ts"],
|
||||
"@page/*": ["apps/isa-app/src/page/*/index.ts"],
|
||||
"@shared/*": ["apps/isa-app/src/shared/*/index.ts"],
|
||||
"@shared/components/*": ["apps/isa-app/src/shared/components/*/index.ts"],
|
||||
"@shared/directives/*": ["apps/isa-app/src/shared/directives/*/index.ts"],
|
||||
"@shared/pipes/*": ["apps/isa-app/src/shared/pipes/*/index.ts"],
|
||||
"@shared/services/*": ["apps/isa-app/src/shared/services/*/index.ts"],
|
||||
"@swagger/*": ["apps/isa-app/src/swagger/*/index.ts"],
|
||||
"@ui/*": ["apps/isa-app/src/ui/*/index.ts"],
|
||||
"@utils/*": ["apps/isa-app/src/utils/*/index.ts"],
|
||||
"packageJson": ["package.json"]
|
||||
},
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"importHelpers": false,
|
||||
"target": "ES2022",
|
||||
"module": "es2020",
|
||||
"lib": ["es2020", "dom"],
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@adapter/*": ["apps/isa-app/src/adapter/*/index.ts"],
|
||||
"@cdn/*": ["apps/isa-app/src/cdn/*/index.ts"],
|
||||
"@core/*": ["apps/isa-app/src/core/*/index.ts"],
|
||||
"@core/config": ["libs/core/config/src/index.ts"],
|
||||
"@domain/*": ["apps/isa-app/src/domain/*/index.ts"],
|
||||
"@external/*": ["apps/isa-app/src/external/*/index.ts"],
|
||||
"@generated/swagger/availability-api": [
|
||||
"generated/swagger/availability-api/src/index.ts"
|
||||
],
|
||||
"@generated/swagger/cat-search-api": [
|
||||
"generated/swagger/cat-search-api/src/index.ts"
|
||||
],
|
||||
"@generated/swagger/checkout-api": [
|
||||
"generated/swagger/checkout-api/src/index.ts"
|
||||
],
|
||||
"@generated/swagger/crm-api": ["generated/swagger/crm-api/src/index.ts"],
|
||||
"@generated/swagger/eis-api": ["generated/swagger/eis-api/src/index.ts"],
|
||||
"@generated/swagger/inventory-api": [
|
||||
"generated/swagger/inventory-api/src/index.ts"
|
||||
],
|
||||
"@generated/swagger/isa-api": ["generated/swagger/isa-api/src/index.ts"],
|
||||
"@generated/swagger/oms-api": ["generated/swagger/oms-api/src/index.ts"],
|
||||
"@generated/swagger/print-api": [
|
||||
"generated/swagger/print-api/src/index.ts"
|
||||
],
|
||||
"@generated/swagger/wws-api": ["generated/swagger/wws-api/src/index.ts"],
|
||||
"@hub/*": ["apps/isa-app/src/hub/*/index.ts"],
|
||||
"@isa/catalogue/data-access": ["libs/catalogue/data-access/src/index.ts"],
|
||||
"@isa/checkout/data-access": ["libs/checkout/data-access/src/index.ts"],
|
||||
"@isa/checkout/feature/reward-catalog": [
|
||||
"libs/checkout/feature/reward-catalog/src/index.ts"
|
||||
],
|
||||
"@isa/common/data-access": ["libs/common/data-access/src/index.ts"],
|
||||
"@isa/common/decorators": ["libs/common/decorators/src/index.ts"],
|
||||
"@isa/common/print": ["libs/common/print/src/index.ts"],
|
||||
"@isa/core/config": ["libs/core/config/src/index.ts"],
|
||||
"@isa/core/logging": ["libs/core/logging/src/index.ts"],
|
||||
"@isa/core/notifications": ["libs/core/notifications/src/index.ts"],
|
||||
"@isa/core/storage": ["libs/core/storage/src/index.ts"],
|
||||
"@isa/core/tabs": ["libs/core/tabs/src/index.ts"],
|
||||
"@isa/crm/data-access": ["libs/crm/data-access/src/index.ts"],
|
||||
"@isa/icons": ["libs/icons/src/index.ts"],
|
||||
"@isa/oms/data-access": ["libs/oms/data-access/src/index.ts"],
|
||||
"@isa/oms/feature/return-details": [
|
||||
"libs/oms/feature/return-details/src/index.ts"
|
||||
],
|
||||
"@isa/oms/feature/return-process": [
|
||||
"libs/oms/feature/return-process/src/index.ts"
|
||||
],
|
||||
"@isa/oms/feature/return-review": [
|
||||
"libs/oms/feature/return-review/src/index.ts"
|
||||
],
|
||||
"@isa/oms/feature/return-search": [
|
||||
"libs/oms/feature/return-search/src/index.ts"
|
||||
],
|
||||
"@isa/oms/feature/return-summary": [
|
||||
"libs/oms/feature/return-summary/src/index.ts"
|
||||
],
|
||||
"@isa/oms/shared/product-info": [
|
||||
"libs/oms/shared/product-info/src/index.ts"
|
||||
],
|
||||
"@isa/oms/shared/task-list": ["libs/oms/shared/task-list/src/index.ts"],
|
||||
"@isa/oms/utils/translation": ["libs/oms/utils/translation/src/index.ts"],
|
||||
"@isa/remission/data-access": ["libs/remission/data-access/src/index.ts"],
|
||||
"@isa/remission/feature/remission-list": [
|
||||
"libs/remission/feature/remission-list/src/index.ts"
|
||||
],
|
||||
"@isa/remission/feature/remission-return-receipt-details": [
|
||||
"libs/remission/feature/remission-return-receipt-details/src/index.ts"
|
||||
],
|
||||
"@isa/remission/feature/remission-return-receipt-list": [
|
||||
"libs/remission/feature/remission-return-receipt-list/src/index.ts"
|
||||
],
|
||||
"@isa/remission/shared/product": [
|
||||
"libs/remission/shared/product/src/index.ts"
|
||||
],
|
||||
"@isa/remission/shared/remission-start-dialog": [
|
||||
"libs/remission/shared/remission-start-dialog/src/index.ts"
|
||||
],
|
||||
"@isa/remission/shared/return-receipt-actions": [
|
||||
"libs/remission/shared/return-receipt-actions/src/index.ts"
|
||||
],
|
||||
"@isa/remission/shared/search-item-to-remit-dialog": [
|
||||
"libs/remission/shared/search-item-to-remit-dialog/src/index.ts"
|
||||
],
|
||||
"@isa/shared/filter": ["libs/shared/filter/src/index.ts"],
|
||||
"@isa/shared/product-foramt": ["libs/shared/product-format/src/index.ts"],
|
||||
"@isa/shared/product-image": ["libs/shared/product-image/src/index.ts"],
|
||||
"@isa/shared/product-router-link": [
|
||||
"libs/shared/product-router-link/src/index.ts"
|
||||
],
|
||||
"@isa/shared/scanner": ["libs/shared/scanner/src/index.ts"],
|
||||
"@isa/ui/bullet-list": ["libs/ui/bullet-list/src/index.ts"],
|
||||
"@isa/ui/buttons": ["libs/ui/buttons/src/index.ts"],
|
||||
"@isa/ui/datepicker": ["libs/ui/datepicker/src/index.ts"],
|
||||
"@isa/ui/dialog": ["libs/ui/dialog/src/index.ts"],
|
||||
"@isa/ui/empty-state": ["libs/ui/empty-state/src/index.ts"],
|
||||
"@isa/ui/expandable": ["libs/ui/expandable/src/index.ts"],
|
||||
"@isa/ui/input-controls": ["libs/ui/input-controls/src/index.ts"],
|
||||
"@isa/ui/item-rows": ["libs/ui/item-rows/src/index.ts"],
|
||||
"@isa/ui/label": ["libs/ui/label/src/index.ts"],
|
||||
"@isa/ui/layout": ["libs/ui/layout/src/index.ts"],
|
||||
"@isa/ui/menu": ["libs/ui/menu/src/index.ts"],
|
||||
"@isa/ui/progress-bar": ["libs/ui/progress-bar/src/index.ts"],
|
||||
"@isa/ui/search-bar": ["libs/ui/search-bar/src/index.ts"],
|
||||
"@isa/ui/skeleton-loader": ["libs/ui/skeleton-loader/src/index.ts"],
|
||||
"@isa/ui/toolbar": ["libs/ui/toolbar/src/index.ts"],
|
||||
"@isa/ui/tooltip": ["libs/ui/tooltip/src/index.ts"],
|
||||
"@isa/utils/ean-validation": ["libs/utils/ean-validation/src/index.ts"],
|
||||
"@isa/utils/scroll-position": ["libs/utils/scroll-position/src/index.ts"],
|
||||
"@isa/utils/z-safe-parse": ["libs/utils/z-safe-parse/src/index.ts"],
|
||||
"@modal/*": ["apps/isa-app/src/modal/*/index.ts"],
|
||||
"@page/*": ["apps/isa-app/src/page/*/index.ts"],
|
||||
"@shared/*": ["apps/isa-app/src/shared/*/index.ts"],
|
||||
"@shared/components/*": ["apps/isa-app/src/shared/components/*/index.ts"],
|
||||
"@shared/directives/*": ["apps/isa-app/src/shared/directives/*/index.ts"],
|
||||
"@shared/pipes/*": ["apps/isa-app/src/shared/pipes/*/index.ts"],
|
||||
"@shared/services/*": ["apps/isa-app/src/shared/services/*/index.ts"],
|
||||
"@swagger/*": ["apps/isa-app/src/swagger/*/index.ts"],
|
||||
"@ui/*": ["apps/isa-app/src/ui/*/index.ts"],
|
||||
"@utils/*": ["apps/isa-app/src/utils/*/index.ts"],
|
||||
"packageJson": ["package.json"]
|
||||
},
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user