Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ZMS-2936 MPDZBS): new zmscitizenview initial setup webcomponent #900

Open
wants to merge 119 commits into
base: next
Choose a base branch
from

Conversation

lehju
Copy link

@lehju lehju commented Feb 24, 2025

Pull Request Checklist (Feature Branch to next):

  • Ich habe die neuesten Änderungen aus dem next Branch in meinen Feature-Branch gemergt.
  • Das Code-Review wurde abgeschlossen.
  • Fachliche Tests wurden durchgeführt und sind abgeschlossen.

Summary by CodeRabbit

  • New Features

    • Introduced a comprehensive appointment booking system with dynamic calendar, service selection, detailed summaries, and customer information forms.
    • Added multi-step workflows and custom web components for seamless scheduling and management.
    • Integrated localization support for German and English to enhance the user experience.
    • Added browser compatibility settings and formatting rules for improved code consistency.
    • Implemented a new GitHub Actions workflow for automated frontend builds and Docker image creation.
    • Added a new component for displaying appointment summaries and managing customer information.
  • Chores

    • Enhanced build automation and deployment processes with improved workflows and container configuration for smoother updates.
    • Introduced configuration files for environment variables, linting, and formatting to streamline development processes.

Copy link
Contributor

coderabbitai bot commented Feb 24, 2025

Walkthrough

This pull request introduces extensive new functionality and configuration for an appointment management system. It adds a CI/CD GitHub Actions workflow and Docker/Nginx deployment settings, along with a full suite of Vue components for booking appointments. New TypeScript interfaces, classes, and global type declarations support API integrations, dynamic module loading, localization, and state management. Additional configuration files for formatting, linting, testing, and build tooling ensure a robust and modular project setup.

Changes

File(s) Change Summary
.github/workflows/maven-node-build.yaml Added GitHub Actions workflow to automate the build process (Node.js, Maven, Docker image build).
zmscitizenview/Dockerfile, zmscitizenview/docker/nginx/compression.conf, zmscitizenview/docker/nginx/health.conf Added Dockerfile using UBI Nginx with custom Nginx configuration for gzip compression and a health check endpoint.
zmscitizenview/.browserslistrc, .editorconfig, .env.production, .gitignore, .prettierignore, .prettierc.json, package.json, tsconfig.json, tsconfig.node.json, vite.config.ts, vitest.config.ts, vue.config.js Added multiple project configuration files for browser support, editor settings, environment variables, dependency management, TypeScript, build, and testing setups.
zmscitizenview/processes/lib/loader*.template, zmscitizenview/processes/lib/loaderjsGenerator.js, zmscitizenview/processes/lib/vitepress-loader.js.template, zmscitizenview/processes/post-build.js Introduced dynamic module loader templates and a post-build script for cache busting and loader generation.
zmscitizenview/eslint.config.js Introduced ESLint configuration integrating JavaScript, Vue, TypeScript, and Prettier settings.
zmscitizenview/index.html, zmscitizenview/src/main.ts Established HTML structure and bootstrapped the Vue.js application, including registration of custom elements.
zmscitizenview/src/api/ZMSAppointmentAPI.ts Added an API module with multiple endpoints for managing appointments and related operations.
zmscitizenview/src/api/models/* Added various TypeScript interfaces for API models including Address, AppointmentDTO, AvailableDaysDTO, AvailableTimeSlotsDTO, ErrorDTO, Office, OfficesAndServicesDTO, Provider, Relation, Scope, Service, and SubRequestCount.
zmscitizenview/src/components/Appointment/*.vue Added multiple Vue components (AppointmentSummary, AppointmentView, CalendarView, ClockSvg, CustomerInfo, ServiceFinder, SubserviceListItem) to manage appointment booking workflows.
zmscitizenview/public/wrapper.js, zmscitizenview/src/i18n-host.ce.vue, zmscitizenview/src/zms-appointment.ce.vue Introduced custom element wrappers and an internationalization host to support localization and web component usage.
zmscitizenview/src/shims-tsx.d.ts, zmscitizenview/src/shims-vue.d.ts, zmscitizenview/src/vite-env.d.ts Added global TypeScript declarations for Vue components and JSX integration.
zmscitizenview/src/types/* Added various classes and interfaces (AppointmentHashTypes, AppointmentImpl, CustomerData, OfficeImpl, ProvideInjectTypes, ServiceImpl, StepperTypes, SubService) to standardize data structures and state management.
zmscitizenview/src/utils/Constants.ts, zmscitizenview/src/utils/de-DE.json, zmscitizenview/src/utils/en-US.json Added utility constants for API endpoints, helper functions, and localization JSON files.

Sequence Diagram(s)

sequenceDiagram
    participant G as GitHub
    participant U as Ubuntu Runner
    participant R as Repository
    participant N as Node Build
    participant M as Maven Build
    participant D as Docker
    participant Reg as Docker Registry

    G->>U: Trigger on push event
    U->>R: Checkout specific commit
    R-->>U: Provide source code
    U->>N: Execute Node build (if package.json exists)
    U->>M: Execute Maven build (if pom.xml exists)
    U->>D: Build Docker image with defined parameters
    D->>Reg: Push Docker image using secrets
Loading
sequenceDiagram
    participant U as User
    participant AV as AppointmentView
    participant API as ZMSAppointmentAPI
    participant BE as Backend

    U->>AV: Initiates appointment booking
    AV->>API: Request available services and providers
    API->>BE: Fetch service & calendar data
    BE-->>API: Return service data
    API-->>AV: Provide appointment options
    U->>AV: Select service and timeslot
    AV->>API: Reserve/confirm appointment
    API->>BE: Process reservation
    BE-->>API: Confirm appointment
    API-->>AV: Return confirmation
    AV-->>U: Display appointment summary
Loading

Poem

I'm a little rabbit with a happy cheer,
Hopping through new workflows that now appear.
Docker and Vue in a code garden so bright,
Custom elements and APIs taking flight.
With every commit, I bounce in delight—
CodeRabbit celebrates these changes tonight! 🐇✨


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🔭 Outside diff range comments (1)
zmscitizenview/src/types/ServiceImpl.ts (1)

1-38: 🛠️ Refactor suggestion

Improve type safety and add validation.

The implementation has the following issues:

  1. The combinable property type could be more specific than string[][]
  2. Missing validation for required fields and array contents

Consider adding validation and more specific types:

export class ServiceImpl implements Service {
  // ... existing properties ...

  constructor(
    id: string,
    name: string,
    maxQuantity: number,
    combinable: Array<Array<string>> | undefined,
    providers: OfficeImpl[] | undefined,
    subServices: SubService[] | undefined,
    count: number | undefined
  ) {
    if (!id || !name || maxQuantity < 0) {
      throw new Error('Invalid service parameters');
    }

    this.id = id;
    this.name = name.trim();
    this.maxQuantity = maxQuantity;
    this.combinable = combinable?.map(arr => [...arr]); // Create defensive copies
    this.providers = providers?.map(p => ({ ...p }));
    this.subServices = subServices?.map(s => ({ ...s }));
    this.count = count;
  }
}
🧹 Nitpick comments (43)
zmscitizenview/src/types/StepperTypes.ts (1)

1-5: LGTM! Consider adding documentation and making icon optional.

The interface is well-structured and follows TypeScript naming conventions. Consider these improvements:

+/**
+ * Represents an item in a stepper component that shows progress through multiple steps.
+ */
 export interface StepperItem {
+  /** Unique identifier for the step */
   id: string;
+  /** Display text for the step */
   label: string;
+  /** Icon name or path (optional) */
-  icon: string;
+  icon?: string;
 }
zmscitizenview/src/api/models/Provider.ts (1)

1-19: Enhance JSDoc documentation with meaningful descriptions.

The interface is well-structured, but the JSDoc comments lack meaningful descriptions of the properties and their purpose. Consider adding descriptive comments to improve code maintainability.

Apply this diff to enhance the documentation:

 /**
- *
+ * Represents a provider entity in the appointment system.
  * @export
  * @interface Provider
  */
 export interface Provider {
   /**
-   *
+   * Unique identifier for the provider.
    * @type {string}
    * @memberof Provider
    */
   id: string;
   /**
-   *
+   * Source system or origin of the provider.
    * @type {string}
    * @memberof Provider
    */
   source: string;
 }
zmscitizenview/src/api/models/AvailableDaysDTO.ts (1)

1-19: Enhance JSDoc documentation and consider type refinements.

The interface is well-structured, but consider the following improvements:

  1. Add meaningful descriptions to JSDoc comments.
  2. Consider using a more specific type for availableDays to indicate the expected date format.

Apply this diff to enhance the documentation and types:

 /**
- *
+ * Data transfer object for available appointment days.
  * @export
  * @interface AvailableDaysDTO
  */
 export interface AvailableDaysDTO {
   /**
-   *
+   * List of available days in ISO 8601 date format (YYYY-MM-DD).
    * @type {Array<string>}
    * @memberof AvailableDaysDTO
    */
-  availableDays: string[];
+  availableDays: `${number}-${number}-${number}`[];
   /**
-   *
+   * Unix timestamp of the last modification.
    * @type {number}
    * @memberof AvailableDaysDTO
    */
   lastModified: number;
 }
zmscitizenview/src/types/AppointmentHashTypes.ts (1)

1-7: Add JSDoc documentation to improve code maintainability.

The interface lacks documentation. Consider adding JSDoc comments to describe the purpose of the interface and its properties.

Apply this diff to add documentation:

+/**
+ * Represents a hash for appointment identification and authentication.
+ * @interface AppointmentHash
+ */
 export interface AppointmentHash {
+  /**
+   * Unique identifier for the appointment.
+   */
   id: string;
+  /**
+   * Authentication key for accessing the appointment.
+   */
   authKey: string;
+  /**
+   * Optional scope information for the appointment.
+   */
   scope?: Scope;
 }
zmscitizenview/docker/nginx/health.conf (1)

1-7: Consider enhancing the health check configuration.

The health check configuration is good but could be improved in the following ways:

  1. Consider enabling access logging with a reduced format for monitoring and debugging.
  2. Add rate limiting to prevent DoS attacks.
  3. Consider adding more comprehensive health status information.

Apply this diff to enhance the configuration:

 location /actuator/health {
-    access_log off;
+    # Use minimal logging format for monitoring
+    access_log /var/log/nginx/health.log combined buffer=512k flush=1m;
+    
+    # Rate limiting
+    limit_req zone=health burst=5;
+    
     add_header 'Content-Type' 'application/json';
     add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always;
     add_header Cache-Control 'no-cache';
-    return 200 '{"status":"UP"}';
+    return 200 '{"status":"UP","timestamp":$msec}';
 }
zmscitizenview/src/api/models/Relation.ts (2)

1-12: Enhance JSDoc documentation for the officeId property.

The current JSDoc comments only specify the type. Consider adding descriptions to explain the purpose and any constraints of the property.

 /**
- *
- * @type {string}
+ * Unique identifier for the office.
+ * @type {string} - Must be a valid office ID from the system
  * @memberof Relation
  */

13-24: Consider adding type constraints for IDs and slots.

The properties could benefit from more specific types or validation:

  1. IDs could use a specific format or pattern
  2. Slots should be non-negative
-  serviceId: string;
+  serviceId: `service-${string}`; // Template literal type for specific format
   /**
    *
    * @type {number}
    * @memberof Relation
    */
-  slots: number;
+  slots: number & { __brand: 'NonNegative' }; // Branded type for validation

Consider adding runtime validation in the consuming code:

function validateRelation(relation: Relation): void {
  if (relation.slots < 0) {
    throw new Error('Slots must be non-negative');
  }
  if (!relation.officeId.match(/^[a-zA-Z0-9-]+$/)) {
    throw new Error('Invalid office ID format');
  }
  if (!relation.serviceId.match(/^[a-zA-Z0-9-]+$/)) {
    throw new Error('Invalid service ID format');
  }
}
zmscitizenview/src/vite-env.d.ts (1)

3-7: Improve type safety in Vue component declaration.

The current type declaration uses empty objects and any, which reduces type safety.

 declare module "*.vue" {
   import type { DefineComponent } from "vue";
-  const component: DefineComponent<{}, {}, any>;
+  const component: DefineComponent<
+    Record<string, unknown>, // Props type
+    Record<string, unknown>, // RawBindings type
+    Record<string, unknown>  // Data type
+  >;
   export default component;
 }
🧰 Tools
🪛 Biome (1.9.4)

[error] 5-5: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 5-5: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)

zmscitizenview/src/types/SubService.ts (1)

3-13: Add JSDoc documentation and consider making properties readonly.

The class and its properties lack documentation, and mutable properties could lead to unintended state changes.

+/**
+ * Represents a sub-service within the appointment system.
+ */
 export class SubService {
-  id: string;
+  /** Unique identifier for the sub-service */
+  readonly id: string;
 
-  name: string;
+  /** Display name of the sub-service */
+  readonly name: string;
 
-  maxQuantity: number;
+  /** Maximum number of appointments allowed */
+  readonly maxQuantity: number;
 
-  providers: OfficeImpl[];
+  /** List of offices providing this sub-service */
+  readonly providers: readonly OfficeImpl[];
 
-  count: number;
+  /** Current number of appointments */
+  readonly count: number;
zmscitizenview/src/api/models/AvailableTimeSlotsDTO.ts (1)

1-19: Enhance type safety and documentation for timestamps.

The interface could benefit from more specific types and better documentation.

 /**
- *
+ * Data transfer object for available appointment time slots.
  * @export
  * @interface AvailableTimeSlotsDTO
  */
 export interface AvailableTimeSlotsDTO {
   /**
-   *
-   * @type {Array<number>}
+   * Array of Unix timestamps (in milliseconds) representing available appointment slots.
+   * @type {Array<number>} - Must be valid future timestamps
    * @memberof AvailableTimeSlotsDTO
    */
-  appointmentTimestamps: number[];
+  appointmentTimestamps: Array<number & { __brand: 'UnixTimestamp' }>;
   /**
-   *
-   * @type {number}
+   * Unix timestamp (in milliseconds) of when this data was last modified.
+   * @type {number} - Must be a valid timestamp
    * @memberof AvailableTimeSlotsDTO
    */
-  lastModified: number;
+  lastModified: number & { __brand: 'UnixTimestamp' };
 }

Consider adding runtime validation:

function validateTimestamp(timestamp: number): void {
  const now = Date.now();
  if (timestamp < now) {
    throw new Error('Timestamp must be in the future');
  }
  if (!Number.isInteger(timestamp)) {
    throw new Error('Timestamp must be an integer');
  }
}
zmscitizenview/src/api/models/ErrorDTO.ts (1)

1-25: Consider enhancing error handling with additional properties and validation.

The interface provides basic error handling, but could be improved with:

  1. Optional details property for additional error context
  2. Enum or union type for errorCode to ensure consistent error codes
  3. Validation for lastModified timestamp format

Consider this enhancement:

 export interface ErrorDTO {
   errorCode: string;
   errorMessage: string;
   lastModified: number;
+  details?: Record<string, unknown>;
 }

+// Define allowed error codes
+export enum ErrorCode {
+  NOT_FOUND = 'NOT_FOUND',
+  INVALID_INPUT = 'INVALID_INPUT',
+  SERVER_ERROR = 'SERVER_ERROR',
+  // Add more as needed
+}
zmscitizenview/public/wrapper.js (1)

1-15: Add lifecycle methods and error handling.

The custom element implementation could be improved with:

  1. Lifecycle methods for cleanup
  2. Error handling for attribute operations

Consider this enhancement:

 customElements.define('zms-appointment',
   class extends HTMLElement {
     constructor() {
       super();
       this.attachShadow({ mode: 'open' });
       const i18nHost = document.createElement("i18n-host");
       const wrapped = document.createElement("zms-appointment-wrapped");
-      for (let attr of this.attributes) {
-        wrapped.setAttribute(attr.name, attr.value);
+      try {
+        Array.from(this.attributes).forEach(attr => {
+          wrapped.setAttribute(attr.name, attr.value);
+        });
+      } catch (error) {
+        console.error('Failed to copy attributes:', error);
       }
       i18nHost.appendChild(wrapped);
       this.shadowRoot.appendChild(i18nHost);
     }
+    
+    disconnectedCallback() {
+      // Cleanup when element is removed
+      this.shadowRoot.textContent = '';
+    }
   }
 );
zmscitizenview/src/api/models/OfficesAndServicesDTO.ts (1)

1-29: Consider using readonly arrays to prevent mutations.

The interface could be improved by making the arrays readonly to prevent accidental mutations of the DTO properties.

Consider this enhancement:

 export interface OfficesAndServicesDTO {
-  offices: Office[];
+  readonly offices: readonly Office[];
-  services: Service[];
+  readonly services: readonly Service[];
-  relations: Relation[];
+  readonly relations: readonly Relation[];
 }
zmscitizenview/src/api/models/Service.ts (1)

1-31: Add validation constraints and readonly modifiers.

The interface could be improved with:

  1. String validation for id and name
  2. Number constraints for maxQuantity
  3. Readonly modifier for combinable array

Consider these enhancements:

+// Add validation types
+type NonEmptyString = string & { __brand: 'NonEmptyString' };
+type PositiveNumber = number & { __brand: 'PositiveNumber' };

 export interface Service {
-  id: string;
+  id: NonEmptyString;
-  name: string;
+  name: NonEmptyString;
-  maxQuantity: number;
+  maxQuantity: PositiveNumber;
-  combinable?: string[][];
+  readonly combinable?: readonly (readonly string[])[];
 }

+// Add validation functions
+function validateNonEmptyString(value: string): NonEmptyString {
+  if (!value.trim()) throw new Error('String cannot be empty');
+  return value as NonEmptyString;
+}

+function validatePositiveNumber(value: number): PositiveNumber {
+  if (value <= 0) throw new Error('Number must be positive');
+  return value as PositiveNumber;
+}
zmscitizenview/src/api/models/AppointmentDTO.ts (1)

4-82: Enhance JSDoc documentation and fix type annotation mismatch.

  1. The JSDoc comments should include meaningful descriptions for each property to improve code documentation.
  2. There's a type annotation mismatch in the comment for subRequestCounts:
    • Comment shows @type {any[]}
    • Implementation shows SubRequestCount[]

Apply this diff to fix the type annotation and add meaningful descriptions:

 /**
+ * Data Transfer Object for appointment information
  *
  * @export
  * @interface AppointmentDTO
  */
 export interface AppointmentDTO {
   /**
+   * Unique identifier for the appointment process
    *
    * @type {string}
    * @memberof AppointmentDTO
    */
   processId: string;
   /**
+   * Unix timestamp of the appointment
    *
    * @type {number}
    * @memberof AppointmentDTO
    */
   timestamp: number;
   /**
+   * Authentication key for the appointment
    *
    * @type {string}
    * @memberof AppointmentDTO
    */
   authKey: string;
   /**
+   * Family name of the person making the appointment
    *
    * @type {string}
    * @memberof AppointmentDTO
    */
   familyName: string;
   /**
+   * Email address for appointment confirmation
    *
    * @type {string}
    * @memberof AppointmentDTO
    */
   email: string;
   /**
+   * Optional phone number for contact
    *
    * @type {string}
    * @memberof AppointmentDTO
    */
   telephone?: string;
   /**
+   * Optional custom text field for additional information
    *
    * @type {string}
    * @memberof AppointmentDTO
    */
   customTextfield?: string;
   /**
+   * Identifier of the office where the appointment is scheduled
    *
    * @type {string}
    * @memberof AppointmentDTO
    */
   officeId: string;
   /**
+   * Scope of the appointment
    *
    * @type {Scope}
    * @memberof AppointmentDTO
    */
   scope: Scope;
   /**
+   * Array of sub-request counts for the appointment
    *
-   * @type {any[]}
+   * @type {SubRequestCount[]}
    * @memberof AppointmentDTO
    */
   subRequestCounts: SubRequestCount[];
   /**
+   * Identifier of the service being scheduled
    *
    * @type {string}
    * @memberof AppointmentDTO
    */
   serviceId: string;
   /**
+   * Number of service instances requested
    *
    * @type {number}
    * @memberof AppointmentDTO
    */
   serviceCount: number;
 }
zmscitizenview/processes/post-build.js (2)

4-25: Fix typo in documentation comment.

There's a typo in the documentation:

  • "componente" should be "component"

Apply this diff to fix the typo:

 /**
  * Why this?
  *
- * When we build our custom web componente, vite automatically adds a
+ * When we build our custom web component, vite automatically adds a
  * "cache busting" mechanic to our minified JS-File.

27-31: Add error handling for file operations.

The implementation should handle potential errors when reading the manifest or when the expected file property is missing.

Apply this diff to add error handling:

 // read filename from manifest.json
+if (!manifest['index.html']) {
+  throw new Error('Missing index.html entry in manifest.json');
+}
 const filename = manifest['index.html'].file;
 
 // generate loaderJs with the app script's filename
-generateLoaderJs(filename);
+try {
+  generateLoaderJs(filename);
+} catch (error) {
+  console.error('Failed to generate loader.js:', error);
+  process.exit(1);
+}
zmscitizenview/src/main.ts (2)

1-6: Enhance file documentation.

The file comment could be more descriptive about the custom elements being defined.

Apply this diff to improve the documentation:

 /**
  * main.ts
  *
- * Bootstraps Vuetify and other plugins then mounts the App`
+ * Bootstraps Vue and defines custom elements:
+ * - i18n-host: Provides internationalization context
+ * - zms-appointment-wrapped: Handles appointment functionality
  */

7-16: Add error handling for custom element registration.

The implementation should handle potential errors when defining custom elements, especially if they're already defined.

Apply this diff to add error handling:

 import { defineCustomElement } from "vue";
 
 import I18nHost from "@/i18n-host.ce.vue";
 import ZMSAppointmentElement from "@/zms-appointment.ce.vue";
 
-const I18nHostElement = defineCustomElement(I18nHost);
-customElements.define("i18n-host", I18nHostElement);
+try {
+  const I18nHostElement = defineCustomElement(I18nHost);
+  if (!customElements.get("i18n-host")) {
+    customElements.define("i18n-host", I18nHostElement);
+  }
+} catch (error) {
+  console.error("Failed to register i18n-host:", error);
+}
 
-const zmsAppointmentWebcomponent = defineCustomElement(ZMSAppointmentElement);
-customElements.define("zms-appointment-wrapped", zmsAppointmentWebcomponent);
+try {
+  const zmsAppointmentWebcomponent = defineCustomElement(ZMSAppointmentElement);
+  if (!customElements.get("zms-appointment-wrapped")) {
+    customElements.define("zms-appointment-wrapped", zmsAppointmentWebcomponent);
+  }
+} catch (error) {
+  console.error("Failed to register zms-appointment-wrapped:", error);
+}
zmscitizenview/src/i18n-host.ce.vue (1)

5-25: Consider extracting i18n configuration and making locale configurable.

  1. The i18n configuration could be moved to a separate file for better maintainability.
  2. The default locale could be made configurable through props.

Create a new file i18n.config.ts:

import deDE from "./utils/de-DE.json";
import enUS from "./utils/en-US.json";

export const createI18nConfig = (locale = "de-DE") => ({
  legacy: false,
  locale,
  messages: {
    "de-DE": deDE,
    "en-US": enUS,
  },
});

Then update the component:

 <script setup lang="ts">
-import { provide } from "vue";
+import { provide, defineProps } from "vue";
 import { createI18n, I18nInjectionKey } from "vue-i18n";
 
-import deDE from "./utils/de-DE.json";
-import enUS from "./utils/en-US.json";
+import { createI18nConfig } from "./i18n.config";
 
-const i18n = createI18n({
-  legacy: false,
-  locale: "de-DE",
-  messages: {
-    "de-DE": deDE,
-    "en-US": enUS,
-  },
-});
+const props = defineProps<{
+  defaultLocale?: string;
+}>();
+
+const i18n = createI18n(createI18nConfig(props.defaultLocale));
 
 /**
  * provide i18n instance with `I18nInjectionKey` for other web components
  */
 provide(I18nInjectionKey, i18n);
 </script>
zmscitizenview/eslint.config.js (1)

26-26: Consider enabling TypeScript's no-explicit-any rule

Disabling @typescript-eslint/no-explicit-any could lead to type safety issues. Consider enabling it to maintain better type safety across the codebase.

-      "@typescript-eslint/no-explicit-any": "off",
+      "@typescript-eslint/no-explicit-any": "warn",
zmscitizenview/processes/lib/loaderjsGenerator.js (1)

17-19: Use consistent path resolution

For consistency and safety, use path.resolve for all file paths, including the template path.

-  const vitepressLoaderJsTemplate = fs.readFileSync(`${__dirname}/vitepress-loader.js.template`, {encoding: 'utf-8'});
+  const vitepressTemplatePath = path.resolve(__dirname, 'vitepress-loader.js.template');
+  const vitepressLoaderJsTemplate = fs.readFileSync(vitepressTemplatePath, {encoding: 'utf-8'});
zmscitizenview/src/components/Appointment/CustomerInfo.vue (2)

99-100: Extract validation patterns to constants file

Email and telephone patterns should be moved to a separate constants file for reusability and easier maintenance.

Create a new file src/constants/validation.ts:

export const VALIDATION_PATTERNS = {
  EMAIL: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
  TELEPHONE: /^\+?\d[\d\s]*$/
};

2-7: Enhance accessibility with ARIA attributes

The heading could benefit from additional ARIA attributes for better screen reader support.

   <h2
     class="m-component-form__title"
     tabindex="0"
+    role="heading"
+    aria-level="2"
   >
     {{ t("contactDetails") }}
   </h2>
.github/workflows/maven-node-build.yaml (1)

14-15: Consider expanding matrix strategy for future scalability.

The matrix currently includes only one path. Consider adding a comment explaining how to add more paths or moving the paths to a configuration file for better maintainability.

-        include: # hier müssen die Pfade angegeben werden
+        include: # Add application paths below. Each path should contain either package.json or pom.xml
           - app-path: zmscitizenview
+          # Example:
+          # - app-path: another-app
zmscitizenview/src/types/ProvideInjectTypes.ts (2)

8-11: Consider adding error handling to updateSelectedService.

The method signature doesn't handle potential errors. Consider adding a return type to indicate success/failure.

-  updateSelectedService: (newService: ServiceImpl) => void;
+  updateSelectedService: (newService: ServiceImpl) => Promise<boolean>;

13-16: Add update method for consistency with SelectedServiceProvider.

The SelectedTimeslotProvider interface should include update methods for consistency with SelectedServiceProvider.

 export interface SelectedTimeslotProvider {
   selectedProvider: Ref<OfficeImpl | undefined>;
   selectedTimeslot: Ref<number>;
+  updateSelectedProvider: (newProvider: OfficeImpl) => Promise<boolean>;
+  updateSelectedTimeslot: (newTimeslot: number) => Promise<boolean>;
 }
zmscitizenview/src/api/models/Scope.ts (1)

3-69: Improve JSDoc documentation for better clarity.

The JSDoc comments are present but lack meaningful descriptions. Consider adding detailed descriptions for each property.

 /**
- *
- * @export
- * @interface Scope
+ * Represents the scope configuration for an appointment system.
+ * @interface Scope
+ * @description Defines the configuration and feature flags for a specific scope
+ * within the appointment system.
  */
 export interface Scope {
   /**
-   *
-   * @type {string}
+   * Unique identifier for the scope.
+   * @type {string}
    * @memberof Scope
    */
   id: string;
   // Add similar improvements for other properties
zmscitizenview/src/api/models/Office.ts (1)

69-69: Consider using number type for maxSlotsPerAppointment.

The maxSlotsPerAppointment is typed as string but represents a numeric value. Consider changing it to number for better type safety and to avoid potential type coercion issues.

-  maxSlotsPerAppointment?: string;
+  maxSlotsPerAppointment?: number;
zmscitizenview/src/types/OfficeImpl.ts (2)

26-26: Add JSDoc comments for the slots property.

The slots property is missing documentation. Consider adding JSDoc comments to maintain consistency with other properties.

+  /**
+   * Number of available slots for this office
+   * @type {number}
+   * @memberof OfficeImpl
+   */
   slots?: number;

5-53: Consider implementing builder pattern for better object creation.

The constructor has many parameters which can make object creation error-prone. Consider implementing the builder pattern for a more flexible and maintainable approach.

Here's how you could implement it:

export class OfficeImpl implements Office {
  private constructor(
    public readonly id: string,
    public readonly name: string,
    public readonly address: Address,
    public readonly showAlternativeLocations: boolean,
    public readonly displayNameAlternatives: string[],
    public readonly organization: string,
    public readonly organizationUnit?: string,
    public readonly slotTimeInMinutes: number,
    public readonly scope?: Scope,
    public readonly maxSlotsPerAppointment?: string,
    public readonly slots?: number
  ) {}

  static Builder = class {
    private id!: string;
    private name!: string;
    private address!: Address;
    private showAlternativeLocations!: boolean;
    private displayNameAlternatives: string[] = [];
    private organization!: string;
    private organizationUnit?: string;
    private slotTimeInMinutes!: number;
    private scope?: Scope;
    private maxSlotsPerAppointment?: string;
    private slots?: number;

    setId(id: string): this {
      this.id = id;
      return this;
    }

    // Add similar methods for other properties

    build(): OfficeImpl {
      if (!this.id || !this.name || !this.address || !this.organization || !this.slotTimeInMinutes) {
        throw new Error('Missing required properties');
      }
      return new OfficeImpl(
        this.id,
        this.name,
        this.address,
        this.showAlternativeLocations,
        this.displayNameAlternatives,
        this.organization,
        this.organizationUnit,
        this.slotTimeInMinutes,
        this.scope,
        this.maxSlotsPerAppointment,
        this.slots
      );
    }
  };
}

Usage example:

const office = new OfficeImpl.Builder()
  .setId('123')
  .setName('Main Office')
  .setAddress(address)
  .setShowAlternativeLocations(true)
  .setOrganization('Org')
  .setSlotTimeInMinutes(30)
  .build();
zmscitizenview/src/components/Appointment/CalendarView.vue (2)

13-15: Typo in variable name “proverider”.
It appears that “proverider” should be spelled “provider.” This may cause confusion for future maintainers.

-  v-for="proverider in selectableProviders"
+  v-for="provider in selectableProviders"

349-366: Consider adding explicit error-handling for API calls.
Although you set error.value = true when unexpected data shape is returned, no .catch() block or error message is shown for network failures or server errors. Handling these errors with a .catch() or try/catch might improve reliability and user feedback.

zmscitizenview/src/zms-appointment.ce.vue (1)

24-25: Minor naming inconsistency for customIconsSprit.
Consider renaming to customIconsSprite for clarity.

-import customIconsSprit from "@muenchen/muc-patternlab-vue/assets/icons/custom-icons.svg?raw";
+import customIconsSprite from "@muenchen/muc-patternlab-vue/assets/icons/custom-icons.svg?raw";
zmscitizenview/src/components/Appointment/SubserviceListItem.vue (1)

45-49: Rename “checkPlusEndabled” for clarity.
The computed property name checkPlusEndabled might be a slight misspelling. Consider checkPlusEnabled or a clearer semantic name.

-const checkPlusEndabled = computed(
+const checkPlusEnabled = computed(
zmscitizenview/src/components/Appointment/ServiceFinder.vue (1)

224-224: Use strict equality checks
Using == can lead to unintended type coercions. Prefer strict equality (===) to avoid subtle bugs:

-    if (relation.serviceId == serviceId) {
+    if (relation.serviceId === serviceId) {
zmscitizenview/src/components/Appointment/AppointmentView.vue (5)

4-13: Consider extracting the condition into a computed property for clarity.

The combined condition in v-if="!confirmAppointmentHash && !appointmentNotFoundError && currentView < 4" is correct but somewhat long. Refactoring it into a computed property can improve readability and maintainability.


50-59: Clarify or remove the stale comment regarding tooManyAppointmentsWithSameMailError.

Line 58 includes a note: "Delete tooManyAppointmentsWithSameMailError if contact is transferred from backend call offices-and-services." This suggests a future cleanup or an unfinalized requirement. Consider removing or replacing it with a clear TODO if you still need additional changes before removing the error state.


265-267: Fix the minor spelling issue in rebookOrCanelDialog.

This ref name appears to be missing an "n" in "Cancel." Recommended fix:

- const rebookOrCanelDialog = ref<boolean>(false);
+ const rebookOrCancelDialog = ref<boolean>(false);

300-311: Add checks to guard against invalid step transitions.

While these step control methods are succinct, consider adding bounds checks to prevent accidentally decreasing below step 0 or increasing beyond the maximum. This helps ensure no unintended behavior if this component is extended or reused.


495-596: Consider adding unified error handling for all fetch operations in onMounted().

The onMounted() hook fetches multiple endpoints using .then() but lacks .catch() blocks or response.ok checks. A single network failure (e.g., server down) might lead to silent rejections. Adding consistent error handling ensures more robust and maintainable code.

zmscitizenview/src/api/ZMSAppointmentAPI.ts (2)

28-43: Check response.ok and handle non-200 statuses when calling fetchServicesAndProviders.

The current implementation calls .json() without verifying response.ok. If the response is 4xx/5xx, parsing will not fail outright, but you may lose track of errors. Consider adding a simple check:

 return fetch(apiUrl).then((response) => {
+  if (!response.ok) {
+    throw new Error(\`Request failed with status \${response.status}\`);
+  }
   return response.json();
 });

28-238: Adopt consistent error handling across all fetch functions.

All API calls in this file (e.g., fetchAvailableDays, reserveAppointment, updateAppointment, etc.) share a pattern of calling .then((response) => response.json()) without .catch() or checking response.ok. Consider a centralized error-handling approach with try/catch or a post-response check to prevent unhandled rejections and ensure uniform error behavior.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 24ffe1a and 9529aa5.

⛔ Files ignored due to path filters (2)
  • zmscitizenview/package-lock.json is excluded by !**/package-lock.json
  • zmscitizenview/public/favicon.ico is excluded by !**/*.ico
📒 Files selected for processing (61)
  • .github/workflows/maven-node-build.yaml (1 hunks)
  • zmscitizenview/.browserslistrc (1 hunks)
  • zmscitizenview/.editorconfig (1 hunks)
  • zmscitizenview/.env.production (1 hunks)
  • zmscitizenview/.gitignore (1 hunks)
  • zmscitizenview/.prettierignore (1 hunks)
  • zmscitizenview/.prettierrc.json (1 hunks)
  • zmscitizenview/Dockerfile (1 hunks)
  • zmscitizenview/docker/nginx/compression.conf (1 hunks)
  • zmscitizenview/docker/nginx/health.conf (1 hunks)
  • zmscitizenview/eslint.config.js (1 hunks)
  • zmscitizenview/index.html (1 hunks)
  • zmscitizenview/package.json (1 hunks)
  • zmscitizenview/processes/lib/loader.js.template (1 hunks)
  • zmscitizenview/processes/lib/loaderjsGenerator.js (1 hunks)
  • zmscitizenview/processes/lib/vitepress-loader.js.template (1 hunks)
  • zmscitizenview/processes/post-build.js (1 hunks)
  • zmscitizenview/public/wrapper.js (1 hunks)
  • zmscitizenview/src/api/ZMSAppointmentAPI.ts (1 hunks)
  • zmscitizenview/src/api/models/Address.ts (1 hunks)
  • zmscitizenview/src/api/models/AppointmentDTO.ts (1 hunks)
  • zmscitizenview/src/api/models/AvailableDaysDTO.ts (1 hunks)
  • zmscitizenview/src/api/models/AvailableTimeSlotsDTO.ts (1 hunks)
  • zmscitizenview/src/api/models/ErrorDTO.ts (1 hunks)
  • zmscitizenview/src/api/models/Office.ts (1 hunks)
  • zmscitizenview/src/api/models/OfficesAndServicesDTO.ts (1 hunks)
  • zmscitizenview/src/api/models/Provider.ts (1 hunks)
  • zmscitizenview/src/api/models/Relation.ts (1 hunks)
  • zmscitizenview/src/api/models/Scope.ts (1 hunks)
  • zmscitizenview/src/api/models/Service.ts (1 hunks)
  • zmscitizenview/src/api/models/SubRequestCount.ts (1 hunks)
  • zmscitizenview/src/components/Appointment/AppointmentSummary.vue (1 hunks)
  • zmscitizenview/src/components/Appointment/AppointmentView.vue (1 hunks)
  • zmscitizenview/src/components/Appointment/CalendarView.vue (1 hunks)
  • zmscitizenview/src/components/Appointment/ClockSvg.vue (1 hunks)
  • zmscitizenview/src/components/Appointment/CustomerInfo.vue (1 hunks)
  • zmscitizenview/src/components/Appointment/ServiceFinder.vue (1 hunks)
  • zmscitizenview/src/components/Appointment/SubserviceListItem.vue (1 hunks)
  • zmscitizenview/src/i18n-host.ce.vue (1 hunks)
  • zmscitizenview/src/main.ts (1 hunks)
  • zmscitizenview/src/shims-tsx.d.ts (1 hunks)
  • zmscitizenview/src/shims-vue.d.ts (1 hunks)
  • zmscitizenview/src/types/AppointmentHashTypes.ts (1 hunks)
  • zmscitizenview/src/types/AppointmentImpl.ts (1 hunks)
  • zmscitizenview/src/types/CustomerData.ts (1 hunks)
  • zmscitizenview/src/types/OfficeImpl.ts (1 hunks)
  • zmscitizenview/src/types/ProvideInjectTypes.ts (1 hunks)
  • zmscitizenview/src/types/ServiceImpl.ts (1 hunks)
  • zmscitizenview/src/types/StepperTypes.ts (1 hunks)
  • zmscitizenview/src/types/SubService.ts (1 hunks)
  • zmscitizenview/src/utils/Constants.ts (1 hunks)
  • zmscitizenview/src/utils/de-DE.json (1 hunks)
  • zmscitizenview/src/utils/en-US.json (1 hunks)
  • zmscitizenview/src/vite-env.d.ts (1 hunks)
  • zmscitizenview/src/zms-appointment.ce.vue (1 hunks)
  • zmscitizenview/tests/unit/example.spec.ts (1 hunks)
  • zmscitizenview/tsconfig.json (1 hunks)
  • zmscitizenview/tsconfig.node.json (1 hunks)
  • zmscitizenview/vite.config.ts (1 hunks)
  • zmscitizenview/vitest.config.ts (1 hunks)
  • zmscitizenview/vue.config.js (1 hunks)
✅ Files skipped from review due to trivial changes (20)
  • zmscitizenview/processes/lib/vitepress-loader.js.template
  • zmscitizenview/vue.config.js
  • zmscitizenview/tsconfig.node.json
  • zmscitizenview/src/shims-vue.d.ts
  • zmscitizenview/.prettierignore
  • zmscitizenview/.browserslistrc
  • zmscitizenview/vitest.config.ts
  • zmscitizenview/src/api/models/SubRequestCount.ts
  • zmscitizenview/src/api/models/Address.ts
  • zmscitizenview/src/utils/de-DE.json
  • zmscitizenview/.editorconfig
  • zmscitizenview/src/components/Appointment/ClockSvg.vue
  • zmscitizenview/.prettierrc.json
  • zmscitizenview/Dockerfile
  • zmscitizenview/package.json
  • zmscitizenview/.gitignore
  • zmscitizenview/src/shims-tsx.d.ts
  • zmscitizenview/tsconfig.json
  • zmscitizenview/.env.production
  • zmscitizenview/docker/nginx/compression.conf
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{js,jsx,ts,tsx}`: Flag any usage of console.log() as i...

**/*.{js,jsx,ts,tsx}: Flag any usage of console.log() as it should be replaced with proper logging:

  1. For development: Use proper debug tools or logging libraries
  2. For production: Remove console.log() statements or use structured logging
  3. For errors: Use error tracking services (e.g., Sentry)
  4. For debugging: Consider using debug libraries that can be enabled/disabled

Example replacement:

// Instead of:
console.log('User data:', userData);

// Use:
logger.debug('Processing user data', { userData });
// or for development only:
if (process.env.NODE_ENV === 'development') {
  debug('User data:', userData);
}
  • zmscitizenview/src/types/StepperTypes.ts
  • zmscitizenview/src/api/models/Provider.ts
  • zmscitizenview/src/api/models/Relation.ts
  • zmscitizenview/src/api/models/AvailableDaysDTO.ts
  • zmscitizenview/src/types/AppointmentHashTypes.ts
  • zmscitizenview/tests/unit/example.spec.ts
  • zmscitizenview/src/types/AppointmentImpl.ts
  • zmscitizenview/processes/lib/loaderjsGenerator.js
  • zmscitizenview/src/types/ServiceImpl.ts
  • zmscitizenview/src/vite-env.d.ts
  • zmscitizenview/src/api/models/AvailableTimeSlotsDTO.ts
  • zmscitizenview/eslint.config.js
  • zmscitizenview/src/types/SubService.ts
  • zmscitizenview/public/wrapper.js
  • zmscitizenview/processes/post-build.js
  • zmscitizenview/src/api/models/Service.ts
  • zmscitizenview/src/main.ts
  • zmscitizenview/src/types/CustomerData.ts
  • zmscitizenview/src/api/models/AppointmentDTO.ts
  • zmscitizenview/vite.config.ts
  • zmscitizenview/src/api/models/Office.ts
  • zmscitizenview/src/api/models/ErrorDTO.ts
  • zmscitizenview/src/api/models/OfficesAndServicesDTO.ts
  • zmscitizenview/src/api/models/Scope.ts
  • zmscitizenview/src/types/OfficeImpl.ts
  • zmscitizenview/src/utils/Constants.ts
  • zmscitizenview/src/types/ProvideInjectTypes.ts
  • zmscitizenview/src/api/ZMSAppointmentAPI.ts
🪛 Biome (1.9.4)
zmscitizenview/src/vite-env.d.ts

[error] 5-5: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 5-5: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)

⏰ Context from checks skipped due to timeout of 90000ms (14)
  • GitHub Check: call-owasp-security-checks / security-scan (zmsclient, 8.0)
  • GitHub Check: call-owasp-security-checks / security-scan (zmsdb, 8.0)
  • GitHub Check: call-owasp-security-checks / security-scan (zmsslim, 8.0)
  • GitHub Check: call-owasp-security-checks / security-scan (zmsentities, 8.0)
  • GitHub Check: call-owasp-security-checks / security-scan (zmsdldb, 8.0)
  • GitHub Check: call-owasp-security-checks / security-scan (zmsticketprinter, 8.0)
  • GitHub Check: call-owasp-security-checks / security-scan (zmsstatistic, 8.0)
  • GitHub Check: call-owasp-security-checks / security-scan (zmsmessaging, 8.0)
  • GitHub Check: call-owasp-security-checks / security-scan (zmscitizenapi, 8.0)
  • GitHub Check: call-owasp-security-checks / security-scan (zmscalldisplay, 8.0)
  • GitHub Check: call-owasp-security-checks / security-scan (zmsapi, 8.0)
  • GitHub Check: call-owasp-security-checks / security-scan (zmsadmin, 8.0)
  • GitHub Check: call-php-unit-tests / zmsdb-test
  • GitHub Check: call-php-unit-tests / zmsapi-test
🔇 Additional comments (19)
zmscitizenview/tests/unit/example.spec.ts (1)

1-2: LGTM! Clean import structure.

The import statement is well-organized and includes the necessary testing utilities from Vitest.

zmscitizenview/src/api/models/AppointmentDTO.ts (1)

1-2: LGTM!

The imports are correctly defined and use proper relative paths.

zmscitizenview/processes/post-build.js (1)

1-2: LGTM!

The imports are correctly defined and use the new with syntax for JSON imports.

zmscitizenview/src/i18n-host.ce.vue (1)

1-3: LGTM!

The template is correctly defined with a slot for child components.

zmscitizenview/vite.config.ts (1)

17-17:

❓ Verification inconclusive

Review environment variables handling

Setting process.env to an empty object might prevent access to important environment variables. Consider defining specific environment variables needed by the application.


🏁 Script executed:

#!/bin/bash
# Check for .env files and their variables
fd -t f "^\.env" -x cat {} \; | grep -v '^#' | grep -v '^$'

Length of output: 272


Attention: Environment variables handling in vite.config.ts

The current configuration:

define: {'process.env': {}},

sets process.env to an empty object. This approach might disable access to environment variables that your application actually needs. Since our initial search for .env files didn’t yield usable output (due to hidden file filtering), please verify whether your project relies on any environment variables that should be explicitly defined.

To ensure a thorough check, please run the script below—which includes the hidden flag—to search for any .env files in your repository:

#!/bin/bash
# Search for hidden .env files and display non-comment, non-empty lines
fd -H -t f '^\.env' -x cat {} \; | grep -v '^#' | grep -v '^$'

If any environment variables are found and are required by your application, update the configuration to define only the necessary variables rather than replacing process.env with an empty object.

.github/workflows/maven-node-build.yaml (1)

17-17: Good security practice using pinned action version!

Using a specific commit hash for the checkout action is a secure approach as it prevents supply chain attacks.

zmscitizenview/src/utils/en-US.json (1)

22-22:

❓ Verification inconclusive

Consider sanitizing HTML in translation strings.

The translation string contains raw HTML (<a> tags). Ensure that your i18n setup properly sanitizes HTML to prevent XSS attacks when rendering these translations.


🏁 Script executed:

#!/bin/bash
# Check if vue-i18n is configured with HTML sanitization
rg -A 5 "createI18n|i18n.*setup" src/

Length of output: 84


HTML Sanitization Verification Needed for Translation String

The translation string in zmscitizenview/src/utils/en-US.json contains raw HTML that may introduce XSS risks. The initial verification script was run against the wrong folder (src/), which resulted in an error. Please confirm that your Vue-i18n configuration (likely within zmscitizenview/) is set up to sanitize HTML content before rendering. Specifically, ensure that:

  • The i18n setup is correctly configured (e.g., in a file where createI18n or similar setup functions are used) to sanitize any HTML included in translation strings.
  • Any render functions or directives (e.g., usage of v-html) that process these translation strings integrate proper sanitization measures.

After verifying these points, if the configuration does not enforce sanitization, please update it to apply HTML sanitizing to translation strings.

zmscitizenview/index.html (1)

11-17:

❓ Verification inconclusive

Verify the fixed version of Muenchen.de assets.

The stylesheets are linked to a specific version (1.0.7) of Muenchen.de assets. Ensure this is the latest stable version and check for any security advisories.


🌐 Web query:

What is the latest version of Muenchen.de assets (muenchende-style.css)?

💡 Result:

Based on the provided search results, there is no direct mention of "muenchende-style.css" or its versioning in any of the sources related to Munich's official websites or assets. The search results primarily discuss:

  1. WordPress theme CSS versioning issues[1][5][9]
  2. Munich tourist attractions and city infrastructure[2][6][8][10]
  3. CSS framework updates (Simple.css v2)[3]
  4. Frontend development challenges[4][7]

For official Munich websites like muenchen.de, while there are references to design elements and infrastructure updates[2][6][8], none of the results contain specific information about their CSS file versions or asset management systems.

To obtain the latest version of muenchende-style.css, you would need to:

  1. Inspect the website directly using browser developer tools
  2. Check HTTP response headers for cache/version info
  3. Contact Munich's webmaster through their official channels[6][10]

The most recent CSS-related update mentioned in the results is for Simple.css v2 released in January 2022[3], but this appears unrelated to Munich's official assets.

Citations:


Action Required: Confirm Muenchen.de Asset Version & Security

The current asset links in zmscitizenview/index.html specify version 1.0.7 for both muenchende-fontfaces.css and muenchende-style.css. Our web query did not reveal any updated version information or documented security advisories for these assets. Please:

  • Manually verify (via browser dev tools or direct inquiry) that 1.0.7 is indeed the latest stable version.
  • Check for any security advisories related to these assets.
  • Update the asset links if a newer, secure version is available.
zmscitizenview/src/components/Appointment/CalendarView.vue (1)

468-519: Prevent potential index out-of-bounds access when no offices are found.
You call showSelectionForProvider(offices[0]) without checking if offices is empty. This could cause a runtime error. Consider a fallback scenario (e.g., when offices.length === 0).

+    if (offices.length === 0) {
+      error.value = true;
+      return;
+    }
zmscitizenview/src/zms-appointment.ce.vue (1)

1-21: Overall component structure looks good.
No significant logical or security concerns identified. Prop usage and template structure are clear and well-organized.

Also applies to: 23-64

zmscitizenview/src/components/Appointment/SubserviceListItem.vue (1)

1-13: Implementation is concise and correct.
The muc-counter integration and v-model usage are handled well. Emitting the "change" event upon updates is a good pattern for parent component communication.

zmscitizenview/src/components/Appointment/AppointmentSummary.vue (3)

13-14: Potential null reference for selectedService
If selectedService is not guaranteed to be defined, referencing selectedService.count directly here may cause runtime errors or unexpected behavior. Consider wrapping this paragraph with a v-if="selectedService" or use optional chaining (e.g. selectedService?.count) to avoid null references.


96-96: Ensure selectedProvider.scope is present
selectedProvider.scope.customTextfieldLabel is referenced without checking if selectedProvider.scope exists in this block. If selectedProvider or its scope property is undefined, this may throw an error. Consider adding additional safe checks or an appropriate v-if guard.


218-342: Overall structure is clear and maintainable
The component effectively manages the appointment summary, including checkboxes for privacy policy and communication preferences. The computed properties and methods are well-organized, and usage of the Composition API is consistent. No major concerns were found beyond the noted null-reference checks.

zmscitizenview/src/components/Appointment/ServiceFinder.vue (1)

103-350: Implementation looks solid
The service selection and slot management logic is well structured. Computed properties and watchers correctly update the state based on user interactions. No significant performance or security issues detected.

zmscitizenview/src/utils/Constants.ts (2)

23-25: Provide fallback for getServiceBaseURL()
If VITE_VUE_APP_SERVICE_BASE_URL is missing, this function may return undefined and cause future API calls to fail. Consider returning a default value or throwing an error if it's not set.


40-49: Handle absent environment variable in getAPIBaseURL()
If both baseUrl and VITE_VUE_APP_API_URL are not provided, falling back to new URL(import.meta.url).origin may be unintended. Validate this fallback or provide an explicit default.

zmscitizenview/src/components/Appointment/AppointmentView.vue (1)

478-493: Confirm that foundOffice always exists.

In getProviders(), code assumes foundOffice is present. If .filter() returns an empty array, accessing [0] may result in undefined. Consider adding a check or handle the case where no matching office is found.

zmscitizenview/src/api/ZMSAppointmentAPI.ts (1)

1-19: All imports and constants appear consistent.
No issues are apparent with the initial setup of imports and constant variables for your APIs and references.

@ThomasAFink ThomasAFink changed the title Feat zms 2936 initial setup webcomponent feat(ZMS-2936 MPDZBS): new zmscitizenview initial setup webcomponent Feb 24, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (9)
zmscitizenview/src/components/Appointment/AppointmentView.vue (9)

442-455: 🛠️ Refactor suggestion

Add error handling for the cancelAppointment promise.

This method needs a .catch() handler to manage API call failures or network errors, ensuring proper error state management.

-  cancelAppointment(appointment.value, props.baseUrl ?? undefined).then(
+  cancelAppointment(appointment.value, props.baseUrl ?? undefined)
+    .then(
       (data) => {
         if ((data as AppointmentDTO).processId != undefined) {
           cancelAppointmentSuccess.value = true;
         } else {
           cancelAppointmentError.value = true;
         }
         increaseCurrentView();
       }
-    );
+    )
+    .catch((error) => {
+      console.error("Error canceling appointment:", error);
+      cancelAppointmentError.value = true;
+      increaseCurrentView();
+    });

519-528: 🛠️ Refactor suggestion

Add error handling for the confirmAppointment promise.

The call to confirmAppointment needs a .catch() block to handle potential API failures or network errors, ensuring proper error state management.

-    confirmAppointment(appointmentData, props.baseUrl ?? undefined).then(
+    confirmAppointment(appointmentData, props.baseUrl ?? undefined)
+      .then(
         (data) => {
           if ((data as AppointmentDTO).processId != undefined) {
             confirmAppointmentSuccess.value = true;
           } else {
             confirmAppointmentError.value = true;
           }
         }
-      );
+      )
+      .catch((error) => {
+        console.error("Error confirming appointment:", error);
+        confirmAppointmentError.value = true;
+      });

339-363: 🛠️ Refactor suggestion

Handle network or runtime errors in the setRebookData promise.

The setRebookData method lacks a .catch() handler for the updateAppointment API call, which could lead to unhandled promise rejections if network errors occur.

-    updateAppointment(appointment.value, props.baseUrl ?? undefined).then(
+    updateAppointment(appointment.value, props.baseUrl ?? undefined)
+      .then(
         (data) => {
           if ((data as AppointmentDTO).processId != undefined) {
             appointment.value = data as AppointmentDTO;
           } else {
             if (
               (data as ErrorDTO).errorCode === "tooManyAppointmentsWithSameMail"
             ) {
               tooManyAppointmentsWithSameMailError.value = true;
             } else {
               updateAppointmentError.value = true;
             }
           }
           currentView.value = 3;
         }
-      );
+      )
+      .catch((error) => {
+        console.error("Error updating appointment during rebooking:", error);
+        updateAppointmentError.value = true;
+        currentView.value = 3;
+      });

430-434: 🛠️ Refactor suggestion

Add error handling for cancelAppointment in nextBookAppointment.

The call to cancelAppointment inside the nextBookAppointment method doesn't handle potential errors, which could lead to silent failures.

-            cancelAppointment(
-              rebookedAppointment.value,
-              props.baseUrl ?? undefined
-            );
+            cancelAppointment(
+              rebookedAppointment.value,
+              props.baseUrl ?? undefined
+            ).catch(error => {
+              console.error("Error canceling appointment during rebooking:", error);
+              // Consider whether to show an error message to the user
+            });

423-440: 🛠️ Refactor suggestion

Add error handling for promise rejections.

The nextBookAppointment method lacks a .catch() block to handle potential API failures or network errors, which could lead to unhandled promise rejections.

-  preconfirmAppointment(appointment.value, props.baseUrl ?? undefined).then(
+  preconfirmAppointment(appointment.value, props.baseUrl ?? undefined)
+    .then(
       (data) => {
         if ((data as AppointmentDTO).processId != undefined) {
           appointment.value = data as AppointmentDTO;
           if (isRebooking.value && rebookedAppointment.value) {
             cancelAppointment(
               rebookedAppointment.value,
               props.baseUrl ?? undefined
             );
           }
           increaseCurrentView();
         }
       }
-    );
+    )
+    .catch((error) => {
+      console.error("Error preconfirming appointment:", error);
+      confirmAppointmentError.value = true;
+      increaseCurrentView();
+    });

376-377: 🛠️ Refactor suggestion

Add error handling for nested cancelAppointment call.

The call to cancelAppointment inside the nextReserveAppointment method doesn't handle potential errors, which could lead to silent failures.

-      cancelAppointment(appointment.value, props.baseUrl ?? undefined);
+      cancelAppointment(appointment.value, props.baseUrl ?? undefined)
+        .catch(error => {
+          console.error("Error canceling appointment during reservation:", error);
+          // Consider whether to show an error message to the user
+        });

392-421: 🛠️ Refactor suggestion

Add error handling for API request failures.

The nextUpdateAppointment method lacks proper error handling for network failures or unexpected errors. If the API call fails, there's no .catch() handler to manage the rejection.

-  updateAppointment(appointment.value, props.baseUrl ?? undefined).then(
+  updateAppointment(appointment.value, props.baseUrl ?? undefined)
+    .then(
       (data) => {
         if ((data as AppointmentDTO).processId != undefined) {
           appointment.value = data as AppointmentDTO;
         } else {
           if (
             (data as ErrorDTO).errorCode === "tooManyAppointmentsWithSameMail"
           ) {
             tooManyAppointmentsWithSameMailError.value = true;
           } else {
             updateAppointmentError.value = true;
           }
         }
         increaseCurrentView();
       }
-    );
+    )
+    .catch((error) => {
+      console.error("Error updating appointment:", error);
+      updateAppointmentError.value = true;
+      increaseCurrentView();
+    });

555-600: 🛠️ Refactor suggestion

Add error handling for the fetchAppointment promise.

The call to fetchAppointment needs a .catch() block to handle potential API failures or network errors.

-      fetchAppointment(appointmentData, props.baseUrl ?? undefined).then(
+      fetchAppointment(appointmentData, props.baseUrl ?? undefined)
+        .then(
           (data) => {
             // ... existing code ...
           }
-        );
+        )
+        .catch((error) => {
+          console.error("Error fetching appointment:", error);
+          appointmentNotFoundError.value = true;
+        });

532-602: 🛠️ Refactor suggestion

Add error handling for the fetchServicesAndProviders promise.

The call to fetchServicesAndProviders needs a .catch() block to handle potential API failures or network errors.

-    fetchServicesAndProviders(
-      props.serviceId ?? undefined,
-      props.locationId ?? undefined,
-      props.baseUrl ?? undefined
-    ).then((data) => {
+    fetchServicesAndProviders(
+      props.serviceId ?? undefined,
+      props.locationId ?? undefined,
+      props.baseUrl ?? undefined
+    )
+      .then((data) => {
         services.value = data.services;
         relations.value = data.relations;
         offices.value = data.offices;
         
         // ... rest of the code ...
-    });
+      })
+      .catch((error) => {
+        console.error("Error fetching services and providers:", error);
+        appointmentNotFoundError.value = true;
+      });
🧹 Nitpick comments (5)
zmscitizenview/src/components/Appointment/AppointmentView.vue (5)

485-501: Optimize the getProviders method for better performance.

The current implementation iterates through arrays multiple times with nested loops, which could be inefficient for large datasets. Consider using map/filter with a more efficient lookup approach.

 const getProviders = (serviceId: string, providers: string[] | null) => {
-  const officesAtService = new Array<OfficeImpl>();
-  relations.value.forEach((relation) => {
-    if (relation.serviceId == serviceId) {
-      const foundOffice: OfficeImpl = offices.value.filter((office) => {
-        return office.id == relation.officeId;
-      })[0];
-
-      if (!providers || providers.includes(foundOffice.id.toString())) {
-        foundOffice.slots = relation.slots;
-        officesAtService.push(foundOffice);
-      }
-    }
-  });
+  // Create a map for faster office lookup
+  const officeMap = new Map(offices.value.map(office => [office.id, {...office}]));
+  
+  // Filter relations and map to offices in one pass
+  const officesAtService = relations.value
+    .filter(relation => relation.serviceId === serviceId)
+    .map(relation => {
+      const foundOffice = officeMap.get(relation.officeId);
+      if (foundOffice && (!providers || providers.includes(foundOffice.id.toString()))) {
+        foundOffice.slots = relation.slots;
+        return foundOffice;
+      }
+      return null;
+    })
+    .filter(Boolean) as OfficeImpl[];

   return officesAtService;
 };

17-17: Replace magic numbers with named constants for better readability.

The code uses numeric literals (0, 1, 2, 3, 4) for view states, which reduces readability and makes maintenance more difficult. Consider defining named constants for these values.

+// Add these constants at the top of the script section
+const VIEW_SERVICE_SELECTION = 0;
+const VIEW_CALENDAR = 1;
+const VIEW_CUSTOMER_INFO = 2;
+const VIEW_APPOINTMENT_SUMMARY = 3;
+const VIEW_CONFIRMATION = 4;

// Then use these constants throughout the code, for example:
-            <div v-if="currentView === 0 && !appointmentHash">
+            <div v-if="currentView === VIEW_SERVICE_SELECTION && !appointmentHash">

Also applies to: 27-27, 50-50, 57-57, 102-102


397-402: Simplify null checks with nullish coalescing operator.

The code uses ternary operators for null/undefined checks, which could be simplified using the nullish coalescing operator (??) for better readability.

-    appointment.value.telephone = customerData.value.telephoneNumber
-      ? customerData.value.telephoneNumber
-      : undefined;
-    appointment.value.customTextfield = customerData.value.customTextfield
-      ? customerData.value.customTextfield
-      : undefined;
+    appointment.value.telephone = customerData.value.telephoneNumber ?? undefined;
+    appointment.value.customTextfield = customerData.value.customTextfield ?? undefined;

217-217: Improve type safety for translation function.

The t prop is typed as any, which loses type safety. Consider using a more specific type for the translation function to improve type checking and documentation.

-  t: any;
+  t: (key: string) => string;

479-483: Consider user preferences for scroll behavior.

The goToTop function uses behavior: "instant" which might create a jarring experience for users. Consider using smooth scrolling for a better user experience, or make it configurable based on user preferences.

-  window.scrollTo({ top: 0, behavior: "instant" });
+  window.scrollTo({ top: 0, behavior: "smooth" });
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9529aa5 and f96d2fa.

📒 Files selected for processing (1)
  • zmscitizenview/src/components/Appointment/AppointmentView.vue (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: call-php-code-quality / module-code-quality (zmsstatistic, 8.0)
  • GitHub Check: call-php-code-quality / module-code-quality (zmsslim, 8.0)
🔇 Additional comments (1)
zmscitizenview/src/components/Appointment/AppointmentView.vue (1)

365-390: Handle network or runtime errors in promise chains.

This function relies on .then() without a dedicated .catch(). If the network request fails or the server is unreachable, the returned promise might reject silently, causing unhandled promise errors. Consider adopting try/catch with async/await or adding .catch() to handle request failures consistently.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

♻️ Duplicate comments (5)
zmscitizenview/src/components/Appointment/ServiceFinder.vue (1)

315-348: ⚠️ Potential issue

Add error handling to the API call.

The fetchServicesAndProviders API call doesn't have error handling, which could lead to unhandled promise rejections if the network request fails.

     fetchServicesAndProviders(
       props.preselectedServiceId ?? undefined,
       props.preselectedOfficeId ?? undefined,
       props.baseUrl ?? undefined
-    ).then((data) => {
-      services.value = data.services;
-      relations.value = data.relations;
-      offices.value = data.offices;
-
-      if (props.preselectedServiceId) {
-        service.value = services.value.find(
-          (service) => service.id == props.preselectedServiceId
-        );
-      }
-    });
+    ).then((data) => {
+      services.value = data.services;
+      relations.value = data.relations;
+      offices.value = data.offices;
+
+      if (props.preselectedServiceId) {
+        service.value = services.value.find(
+          (service) => service.id == props.preselectedServiceId
+        );
+      }
+    }).catch((error) => {
+      console.error("Failed to fetch services and providers:", error);
+      // Consider adding user-friendly error handling/display
+    });
zmscitizenview/src/components/Appointment/AppointmentView.vue (4)

365-390: ⚠️ Potential issue

Add error handling to API calls.

The nextReserveAppointment function makes API calls without proper error handling, which could lead to unhandled promise rejections if the network request fails.

 const nextReserveAppointment = () => {
   rebookOrCanelDialog.value = false;
   reserveAppointment(
     selectedTimeslot.value,
     Array.from(selectedServiceMap.value.keys()),
     Array.from(selectedServiceMap.value.values()),
     selectedProvider.value.id,
     props.baseUrl ?? undefined
-  ).then((data) => {
-    if ((data as AppointmentDTO).processId != undefined) {
-      if (appointment.value && !isRebooking.value) {
-        cancelAppointment(appointment.value, props.baseUrl ?? undefined);
-      }
-      appointment.value = data as AppointmentDTO;
-      if (isRebooking.value) {
-        setRebookData();
-      } else {
-        increaseCurrentView();
-      }
-    } else {
-      if ((data as ErrorDTO).errorCode === "appointmentNotAvailable") {
-        appointmentNotAvailableError.value = true;
-      }
-    }
-  });
+  ).then((data) => {
+    if ((data as AppointmentDTO).processId != undefined) {
+      if (appointment.value && !isRebooking.value) {
+        cancelAppointment(appointment.value, props.baseUrl ?? undefined).catch(error => {
+          console.error("Failed to cancel appointment:", error);
+        });
+      }
+      appointment.value = data as AppointmentDTO;
+      if (isRebooking.value) {
+        setRebookData();
+      } else {
+        increaseCurrentView();
+      }
+    } else {
+      if ((data as ErrorDTO).errorCode === "appointmentNotAvailable") {
+        appointmentNotAvailableError.value = true;
+      }
+    }
+  }).catch(error => {
+    console.error("Failed to reserve appointment:", error);
+    // Consider adding user-friendly error handling/display
+  });
 };

518-535: ⚠️ Potential issue

Add error handling for confirmAppointment API call.

The confirmAppointment API call doesn't have proper error handling, which could lead to unhandled errors if the request fails.

   if (props.confirmAppointmentHash) {
     const appointmentData = parseAppointmentHash(props.confirmAppointmentHash);
     if (!appointmentData) {
       confirmAppointmentError.value = true;
       return;
     }

     confirmAppointment(appointmentData, props.baseUrl ?? undefined).then(
       (data) => {
         if ((data as AppointmentDTO).processId != undefined) {
           confirmAppointmentSuccess.value = true;
         } else {
           confirmAppointmentError.value = true;
         }
       }
-    );
+    ).catch(error => {
+      console.error("Failed to confirm appointment:", error);
+      confirmAppointmentError.value = true;
+    });
   }

392-421: ⚠️ Potential issue

Add error handling to nextUpdateAppointment method.

The updateAppointment call in nextUpdateAppointment lacks error handling, which could lead to unhandled promise rejections.

 const nextUpdateAppointment = () => {
   if (appointment.value) {
     appointment.value.familyName =
       customerData.value.firstName + " " + customerData.value.lastName;
     appointment.value.email = customerData.value.mailAddress;
     appointment.value.telephone = customerData.value.telephoneNumber
       ? customerData.value.telephoneNumber
       : undefined;
     appointment.value.customTextfield = customerData.value.customTextfield
       ? customerData.value.customTextfield
       : undefined;

     updateAppointment(appointment.value, props.baseUrl ?? undefined).then(
       (data) => {
         if ((data as AppointmentDTO).processId != undefined) {
           appointment.value = data as AppointmentDTO;
         } else {
           if (
             (data as ErrorDTO).errorCode === "tooManyAppointmentsWithSameMail"
           ) {
             tooManyAppointmentsWithSameMailError.value = true;
           } else {
             updateAppointmentError.value = true;
           }
         }
         increaseCurrentView();
       }
-    );
+    ).catch(error => {
+      console.error("Failed to update appointment:", error);
+      updateAppointmentError.value = true;
+      increaseCurrentView();
+    });
   }
 };

339-363: ⚠️ Potential issue

Add error handling to setRebookData method.

Similar to other API calls, the updateAppointment call in setRebookData lacks error handling.

 const setRebookData = () => {
   if (appointment.value && rebookedAppointment.value) {
     appointment.value.familyName = rebookedAppointment.value.familyName;
     appointment.value.email = rebookedAppointment.value.email;
     appointment.value.telephone = rebookedAppointment.value.telephone;
     appointment.value.customTextfield =
       rebookedAppointment.value.customTextfield;
     updateAppointment(appointment.value, props.baseUrl ?? undefined).then(
       (data) => {
         if ((data as AppointmentDTO).processId != undefined) {
           appointment.value = data as AppointmentDTO;
         } else {
           if (
             (data as ErrorDTO).errorCode === "tooManyAppointmentsWithSameMail"
           ) {
             tooManyAppointmentsWithSameMailError.value = true;
           } else {
             updateAppointmentError.value = true;
           }
         }
         currentView.value = 3;
       }
-    );
+    ).catch(error => {
+      console.error("Failed to update appointment:", error);
+      updateAppointmentError.value = true;
+      currentView.value = 3;
+    });
   }
 };
🧹 Nitpick comments (4)
zmscitizenview/src/components/Appointment/AppointmentSummary.vue (4)

275-278: Improve type safety by replacing any with a proper type.

The formatTime function uses any for the time parameter, which reduces type safety. Consider using a more specific type like number since it appears to be a Unix timestamp.

-const formatTime = (time: any) => {
+const formatTime = (time: number) => {
  const date = new Date(time * 1000);
  return formatterDate.format(date) + ", " + formatterTime.format(date);
};

299-330: Refactor the complex estimatedDuration function for better maintainability.

The estimatedDuration function contains nested conditionals and multiple calculations. Refactoring it into smaller, focused functions would improve readability and maintainability.

+/**
+ * Calculate time required for a single service based on provider slots
+ */
+const calculateServiceTime = (service, serviceProvider) => {
+  if (serviceProvider && serviceProvider.slots && service && service.count) {
+    return service.count * serviceProvider.slots * serviceProvider.slotTimeInMinutes;
+  }
+  return 0;
+};
+
+/**
+ * Calculate time required for a subservice based on provider slots
+ */
+const calculateSubserviceTime = (subservice, subserviceProvider) => {
+  if (subserviceProvider && subservice.count && subserviceProvider.slots) {
+    return subservice.count * subserviceProvider.slots * subserviceProvider.slotTimeInMinutes;
+  }
+  return 0;
+};

 /**
  * This function determines the expected duration of the appointment.
  * The provider is queried for the service and each subservice because the slots for the respective service are stored in this provider.
  */
 const estimatedDuration = () => {
   let time = 0;
   const serviceProvider = selectedService.value?.providers?.find(
     (provider) => provider.id == selectedProvider.value?.id
   );
-  if (
-    serviceProvider &&
-    serviceProvider.slots &&
-    selectedService.value &&
-    selectedService.value.count
-  ) {
-    time =
-      selectedService.value.count *
-      serviceProvider.slots *
-      serviceProvider.slotTimeInMinutes;
-  }
+  time += calculateServiceTime(selectedService.value, serviceProvider);

   if (selectedService.value?.subServices) {
     selectedService.value.subServices.forEach((subservice) => {
       const subserviceProvider = subservice.providers?.find(
         (provider) => provider.id == selectedProvider.value?.id
       );
-      if (subserviceProvider && subservice.count && subserviceProvider.slots) {
-        time +=
-          subservice.count *
-          subserviceProvider.slots *
-          subserviceProvider.slotTimeInMinutes;
-      }
+      time += calculateSubserviceTime(subservice, subserviceProvider);
     });
   }
   return time;
 };

285-287: Simplify computed property with direct return.

This computed property can be simplified for better readability.

-const validForm = computed(
-  () => privacyPolicy.value && electronicCommunication.value
-);
+const validForm = computed(() => privacyPolicy.value && electronicCommunication.value);

92-100: Improve accessibility of custom textfield section.

The tabindex="0" on the div containing both a strong element and a paragraph makes for confusing accessibility structure. It's better to structure this differently.

-            <div
-              v-if="appointment.customTextfield"
-              tabindex="0"
-            >
-              <strong>{{ selectedProvider.scope.customTextfieldLabel }}</strong
-              ><br />
-              <p>{{ appointment.customTextfield }}</p>
-              <br />
-            </div>
+            <div v-if="appointment.customTextfield">
+              <p tabindex="0">
+                <strong>{{ selectedProvider.scope.customTextfieldLabel }}</strong><br />
+                {{ appointment.customTextfield }}
+              </p>
+              <br />
+            </div>
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f04a410 and b56ea6f.

📒 Files selected for processing (6)
  • zmscitizenview/src/components/Appointment/AppointmentSummary.vue (1 hunks)
  • zmscitizenview/src/components/Appointment/AppointmentView.vue (1 hunks)
  • zmscitizenview/src/components/Appointment/CalendarView.vue (1 hunks)
  • zmscitizenview/src/components/Appointment/CustomerInfo.vue (1 hunks)
  • zmscitizenview/src/components/Appointment/ServiceFinder.vue (1 hunks)
  • zmscitizenview/src/utils/en-US.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • zmscitizenview/src/components/Appointment/CustomerInfo.vue
  • zmscitizenview/src/utils/en-US.json
  • zmscitizenview/src/components/Appointment/CalendarView.vue
⏰ Context from checks skipped due to timeout of 90000ms (19)
  • GitHub Check: call-php-unit-tests / module-test (zmsentities, 8.0)
  • GitHub Check: call-php-unit-tests / module-test (zmsdldb, 8.0)
  • GitHub Check: call-php-unit-tests / module-test (zmscitizenapi, 8.0)
  • GitHub Check: call-owasp-security-checks / security-scan (zmsclient, 8.0)
  • GitHub Check: call-php-unit-tests / module-test (zmscalldisplay, 8.0)
  • GitHub Check: call-owasp-security-checks / security-scan (zmsdb, 8.0)
  • GitHub Check: call-php-unit-tests / module-test (zmsadmin, 8.0)
  • GitHub Check: call-owasp-security-checks / security-scan (zmsslim, 8.0)
  • GitHub Check: call-owasp-security-checks / security-scan (zmsentities, 8.0)
  • GitHub Check: call-owasp-security-checks / security-scan (zmsdldb, 8.0)
  • GitHub Check: call-owasp-security-checks / security-scan (zmsticketprinter, 8.0)
  • GitHub Check: call-owasp-security-checks / security-scan (zmsstatistic, 8.0)
  • GitHub Check: call-owasp-security-checks / security-scan (zmsmessaging, 8.0)
  • GitHub Check: call-owasp-security-checks / security-scan (zmscitizenapi, 8.0)
  • GitHub Check: call-owasp-security-checks / security-scan (zmscalldisplay, 8.0)
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: call-owasp-security-checks / security-scan (zmsapi, 8.0)
  • GitHub Check: call-owasp-security-checks / security-scan (zmsadmin, 8.0)

Comment on lines +246 to +256
const { selectedService } = inject<SelectedServiceProvider>(
"selectedServiceProvider"
) as SelectedServiceProvider;

const { selectedProvider } = inject<SelectedTimeslotProvider>(
"selectedTimeslot"
) as SelectedTimeslotProvider;

const { appointment } = inject<SelectedAppointmentProvider>(
"appointment"
) as SelectedAppointmentProvider;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add null checks for injected dependencies.

The component injects dependencies without checking if they're properly initialized. This could lead to runtime errors if any of these injections fail.

-const { selectedService } = inject<SelectedServiceProvider>(
-  "selectedServiceProvider"
-) as SelectedServiceProvider;
+const selectedServiceProvider = inject<SelectedServiceProvider>("selectedServiceProvider");
+const { selectedService } = selectedServiceProvider || { selectedService: ref() };

-const { selectedProvider } = inject<SelectedTimeslotProvider>(
-  "selectedTimeslot"
-) as SelectedTimeslotProvider;
+const selectedTimeslotProvider = inject<SelectedTimeslotProvider>("selectedTimeslot");
+const { selectedProvider } = selectedTimeslotProvider || { selectedProvider: ref() };

-const { appointment } = inject<SelectedAppointmentProvider>(
-  "appointment"
-) as SelectedAppointmentProvider;
+const appointmentProvider = inject<SelectedAppointmentProvider>("appointment");
+const { appointment } = appointmentProvider || { appointment: ref() };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { selectedService } = inject<SelectedServiceProvider>(
"selectedServiceProvider"
) as SelectedServiceProvider;
const { selectedProvider } = inject<SelectedTimeslotProvider>(
"selectedTimeslot"
) as SelectedTimeslotProvider;
const { appointment } = inject<SelectedAppointmentProvider>(
"appointment"
) as SelectedAppointmentProvider;
const selectedServiceProvider = inject<SelectedServiceProvider>("selectedServiceProvider");
const { selectedService } = selectedServiceProvider || { selectedService: ref() };
const selectedTimeslotProvider = inject<SelectedTimeslotProvider>("selectedTimeslot");
const { selectedProvider } = selectedTimeslotProvider || { selectedProvider: ref() };
const appointmentProvider = inject<SelectedAppointmentProvider>("appointment");
const { appointment } = appointmentProvider || { appointment: ref() };

Comment on lines +260 to +264
const estimatedDuration = computed(() => {
return service.value.providers
? service.value.providers[0].slotTimeInMinutes * currentSlots.value
: 0;
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard against undefined providers in the estimatedDuration calculation.

The estimatedDuration computed property assumes providers[0] exists, which could cause errors if the providers array is empty.

 const estimatedDuration = computed(() => {
-  return service.value.providers
-    ? service.value.providers[0].slotTimeInMinutes * currentSlots.value
-    : 0;
+  if (!service.value?.providers || service.value.providers.length === 0) {
+    return 0;
+  }
+  return service.value.providers[0].slotTimeInMinutes * currentSlots.value;
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const estimatedDuration = computed(() => {
return service.value.providers
? service.value.providers[0].slotTimeInMinutes * currentSlots.value
: 0;
});
const estimatedDuration = computed(() => {
if (!service.value?.providers || service.value.providers.length === 0) {
return 0;
}
return service.value.providers[0].slotTimeInMinutes * currentSlots.value;
});

Comment on lines +281 to +289
const getMinSlotOfProvider = (provider: OfficeImpl[]) => {
let minSlot = MAX_SLOTS;
provider.forEach((provider) => {
if (provider.slots) {
minSlot = Math.min(minSlot, provider.slots);
}
});
return minSlot;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for the case where no providers are available.

The getMinSlotOfProvider function could return an unreasonable value if the provider array is empty.

 const getMinSlotOfProvider = (provider: OfficeImpl[]) => {
+  if (!provider || provider.length === 0) {
+    return 0;
+  }
   let minSlot = MAX_SLOTS;
   provider.forEach((provider) => {
     if (provider.slots) {
       minSlot = Math.min(minSlot, provider.slots);
     }
   });
   return minSlot;
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const getMinSlotOfProvider = (provider: OfficeImpl[]) => {
let minSlot = MAX_SLOTS;
provider.forEach((provider) => {
if (provider.slots) {
minSlot = Math.min(minSlot, provider.slots);
}
});
return minSlot;
};
const getMinSlotOfProvider = (provider: OfficeImpl[]) => {
if (!provider || provider.length === 0) {
return 0;
}
let minSlot = MAX_SLOTS;
provider.forEach((provider) => {
if (provider.slots) {
minSlot = Math.min(minSlot, provider.slots);
}
});
return minSlot;
};

Comment on lines +175 to +219
const setServiceData = (selectedService: ServiceImpl) => {
service.value.providers = getProviders(selectedService.id, null);
service.value.count = 1;
currentSlots.value = getMinSlotOfProvider(service.value.providers);

if (selectedService.combinable) {
const combinable = selectedService.combinable;
if (typeof combinable[parseInt(selectedService.id)] !== "undefined") {
delete combinable[parseInt(selectedService.id)];
}

service.value.subServices = Object.entries(combinable)
.map(([subServiceId, providers]) => {
const subService = services.value.filter(
(subService) => parseInt(subService.id) == parseInt(subServiceId)
);
if (subService && subService.length === 1) {
return {
id: parseInt(subServiceId),
name: subService[0].name,
maxQuantity: subService[0].maxQuantity,
providers: getProviders(subServiceId, providers),
count: 0,
};
}
})
.filter((subService) => {
if (subService === undefined) return false;
if (props.preselectedOfficeId) {
return subService.providers.some(
(provider) => provider.id == props.preselectedOfficeId
);
}
return true;
});
}

const maxSlotsOfProvider = getMaxSlotsPerAppointementOfProvider(
service.value.providers
);
maxSlotsPerAppointment.value =
maxSlotsOfProvider > 0
? Math.min(maxSlotsOfProvider, MAX_SLOTS)
: MAX_SLOTS;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor complex service data processing function.

The setServiceData function is complex with multiple responsibilities: updating providers, calculating slots, processing combinable services, and determining max slots. Consider breaking it down into smaller, focused functions.

+/**
+ * Process subservices for a selected service
+ */
+const processSubservices = (selectedService: ServiceImpl, preselectedOfficeId?: string) => {
+  if (!selectedService.combinable) {
+    return [];
+  }
+  
+  const combinable = {...selectedService.combinable};
+  if (typeof combinable[parseInt(selectedService.id)] !== "undefined") {
+    delete combinable[parseInt(selectedService.id)];
+  }
+
+  return Object.entries(combinable)
+    .map(([subServiceId, providers]) => {
+      const subService = services.value.filter(
+        (subService) => parseInt(subService.id) == parseInt(subServiceId)
+      );
+      if (subService && subService.length === 1) {
+        return {
+          id: parseInt(subServiceId),
+          name: subService[0].name,
+          maxQuantity: subService[0].maxQuantity,
+          providers: getProviders(subServiceId, providers),
+          count: 0,
+        };
+      }
+    })
+    .filter((subService) => {
+      if (subService === undefined) return false;
+      if (preselectedOfficeId) {
+        return subService.providers.some(
+          (provider) => provider.id == preselectedOfficeId
+        );
+      }
+      return true;
+    });
+};

 const setServiceData = (selectedService: ServiceImpl) => {
   service.value.providers = getProviders(selectedService.id, null);
   service.value.count = 1;
   currentSlots.value = getMinSlotOfProvider(service.value.providers);

-  if (selectedService.combinable) {
-    const combinable = selectedService.combinable;
-    if (typeof combinable[parseInt(selectedService.id)] !== "undefined") {
-      delete combinable[parseInt(selectedService.id)];
-    }
-
-    service.value.subServices = Object.entries(combinable)
-      .map(([subServiceId, providers]) => {
-        const subService = services.value.filter(
-          (subService) => parseInt(subService.id) == parseInt(subServiceId)
-        );
-        if (subService && subService.length === 1) {
-          return {
-            id: parseInt(subServiceId),
-            name: subService[0].name,
-            maxQuantity: subService[0].maxQuantity,
-            providers: getProviders(subServiceId, providers),
-            count: 0,
-          };
-        }
-      })
-      .filter((subService) => {
-        if (subService === undefined) return false;
-        if (props.preselectedOfficeId) {
-          return subService.providers.some(
-            (provider) => provider.id == props.preselectedOfficeId
-          );
-        }
-        return true;
-      });
-  }
+  service.value.subServices = processSubservices(selectedService, props.preselectedOfficeId);

   const maxSlotsOfProvider = getMaxSlotsPerAppointementOfProvider(
     service.value.providers
   );
   maxSlotsPerAppointment.value =
     maxSlotsOfProvider > 0
       ? Math.min(maxSlotsOfProvider, MAX_SLOTS)
       : MAX_SLOTS;
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const setServiceData = (selectedService: ServiceImpl) => {
service.value.providers = getProviders(selectedService.id, null);
service.value.count = 1;
currentSlots.value = getMinSlotOfProvider(service.value.providers);
if (selectedService.combinable) {
const combinable = selectedService.combinable;
if (typeof combinable[parseInt(selectedService.id)] !== "undefined") {
delete combinable[parseInt(selectedService.id)];
}
service.value.subServices = Object.entries(combinable)
.map(([subServiceId, providers]) => {
const subService = services.value.filter(
(subService) => parseInt(subService.id) == parseInt(subServiceId)
);
if (subService && subService.length === 1) {
return {
id: parseInt(subServiceId),
name: subService[0].name,
maxQuantity: subService[0].maxQuantity,
providers: getProviders(subServiceId, providers),
count: 0,
};
}
})
.filter((subService) => {
if (subService === undefined) return false;
if (props.preselectedOfficeId) {
return subService.providers.some(
(provider) => provider.id == props.preselectedOfficeId
);
}
return true;
});
}
const maxSlotsOfProvider = getMaxSlotsPerAppointementOfProvider(
service.value.providers
);
maxSlotsPerAppointment.value =
maxSlotsOfProvider > 0
? Math.min(maxSlotsOfProvider, MAX_SLOTS)
: MAX_SLOTS;
};
/**
* Process subservices for a selected service
*/
const processSubservices = (selectedService: ServiceImpl, preselectedOfficeId?: string) => {
if (!selectedService.combinable) {
return [];
}
const combinable = { ...selectedService.combinable };
if (typeof combinable[parseInt(selectedService.id)] !== "undefined") {
delete combinable[parseInt(selectedService.id)];
}
return Object.entries(combinable)
.map(([subServiceId, providers]) => {
const subService = services.value.filter(
(subService) => parseInt(subService.id) == parseInt(subServiceId)
);
if (subService && subService.length === 1) {
return {
id: parseInt(subServiceId),
name: subService[0].name,
maxQuantity: subService[0].maxQuantity,
providers: getProviders(subServiceId, providers),
count: 0,
};
}
})
.filter((subService) => {
if (subService === undefined) return false;
if (preselectedOfficeId) {
return subService.providers.some(
(provider) => provider.id == preselectedOfficeId
);
}
return true;
});
};
const setServiceData = (selectedService: ServiceImpl) => {
service.value.providers = getProviders(selectedService.id, null);
service.value.count = 1;
currentSlots.value = getMinSlotOfProvider(service.value.providers);
service.value.subServices = processSubservices(selectedService, props.preselectedOfficeId);
const maxSlotsOfProvider = getMaxSlotsPerAppointementOfProvider(
service.value.providers
);
maxSlotsPerAppointment.value =
maxSlotsOfProvider > 0
? Math.min(maxSlotsOfProvider, MAX_SLOTS)
: MAX_SLOTS;
};

Comment on lines +225 to +236
const foundOffice: OfficeImpl = offices.value.filter((office) => {
return office.id == relation.officeId;
})[0];

if (
!providers ||
providers.find((prov) => prov == foundOffice.id.toString())
) {
foundOffice.slots = relation.slots;
officesAtService.push(foundOffice);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle empty array access safely.

When filtering offices, the code directly accesses index [0] without checking if the array is empty, which could lead to undefined errors.

 const getProviders = (serviceId: string, providers: string[] | null) => {
   const officesAtService = new Array<OfficeImpl>();
   relations.value.forEach((relation) => {
     if (relation.serviceId == serviceId) {
-      const foundOffice: OfficeImpl = offices.value.filter((office) => {
+      const filteredOffices = offices.value.filter((office) => {
         return office.id == relation.officeId;
-      })[0];
+      });
+      
+      if (filteredOffices.length === 0) {
+        return;
+      }
+      
+      const foundOffice: OfficeImpl = filteredOffices[0];

       if (
         !providers ||
         providers.find((prov) => prov == foundOffice.id.toString())
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const foundOffice: OfficeImpl = offices.value.filter((office) => {
return office.id == relation.officeId;
})[0];
if (
!providers ||
providers.find((prov) => prov == foundOffice.id.toString())
) {
foundOffice.slots = relation.slots;
officesAtService.push(foundOffice);
}
}
const getProviders = (serviceId: string, providers: string[] | null) => {
const officesAtService = new Array<OfficeImpl>();
relations.value.forEach((relation) => {
if (relation.serviceId == serviceId) {
const filteredOffices = offices.value.filter((office) => {
return office.id == relation.officeId;
});
if (filteredOffices.length === 0) {
return;
}
const foundOffice: OfficeImpl = filteredOffices[0];
if (
!providers ||
providers.find((prov) => prov == foundOffice.id.toString())
) {
foundOffice.slots = relation.slots;
officesAtService.push(foundOffice);
}
}
});
return officesAtService;
};

Comment on lines +485 to +501
const getProviders = (serviceId: string, providers: string[] | null) => {
const officesAtService = new Array<OfficeImpl>();
relations.value.forEach((relation) => {
if (relation.serviceId == serviceId) {
const foundOffice: OfficeImpl = offices.value.filter((office) => {
return office.id == relation.officeId;
})[0];

if (!providers || providers.includes(foundOffice.id.toString())) {
foundOffice.slots = relation.slots;
officesAtService.push(foundOffice);
}
}
});

return officesAtService;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add validation for array access in getProviders.

The getProviders function has a similar issue as in ServiceFinder, accessing array index [0] without checking if the filtered array is empty.

 const getProviders = (serviceId: string, providers: string[] | null) => {
   const officesAtService = new Array<OfficeImpl>();
   relations.value.forEach((relation) => {
     if (relation.serviceId == serviceId) {
-      const foundOffice: OfficeImpl = offices.value.filter((office) => {
+      const filteredOffices = offices.value.filter((office) => {
         return office.id == relation.officeId;
-      })[0];
+      });
+      
+      if (filteredOffices.length === 0) {
+        return;
+      }
+      
+      const foundOffice: OfficeImpl = filteredOffices[0];

       if (!providers || providers.includes(foundOffice.id.toString())) {
         foundOffice.slots = relation.slots;
         officesAtService.push(foundOffice);
       }
     }
   });

   return officesAtService;
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const getProviders = (serviceId: string, providers: string[] | null) => {
const officesAtService = new Array<OfficeImpl>();
relations.value.forEach((relation) => {
if (relation.serviceId == serviceId) {
const foundOffice: OfficeImpl = offices.value.filter((office) => {
return office.id == relation.officeId;
})[0];
if (!providers || providers.includes(foundOffice.id.toString())) {
foundOffice.slots = relation.slots;
officesAtService.push(foundOffice);
}
}
});
return officesAtService;
};
const getProviders = (serviceId: string, providers: string[] | null) => {
const officesAtService = new Array<OfficeImpl>();
relations.value.forEach((relation) => {
if (relation.serviceId == serviceId) {
const filteredOffices = offices.value.filter((office) => {
return office.id == relation.officeId;
});
if (filteredOffices.length === 0) {
return;
}
const foundOffice: OfficeImpl = filteredOffices[0];
if (!providers || providers.includes(foundOffice.id.toString())) {
foundOffice.slots = relation.slots;
officesAtService.push(foundOffice);
}
}
});
return officesAtService;
};

Comment on lines +554 to +599
fetchAppointment(appointmentData, props.baseUrl ?? undefined).then(
(data) => {
if ((data as AppointmentDTO).processId != undefined) {
appointment.value = data as AppointmentDTO;
selectedService.value = services.value.find(
(service) => service.id == appointment.value.serviceId
);
if (selectedService.value) {
selectedService.value.count = appointment.value.serviceCount;
selectedService.value.providers = getProviders(
selectedService.value.id,
null
);

preselectedLocationId.value = appointment.value.officeId;
selectedProvider.value = offices.value.find(
(office) => office.id == appointment.value?.officeId
);

if (appointment.value.subRequestCounts.length > 0) {
appointment.value.subRequestCounts.forEach(
(subRequestCount) => {
const subRequest: Service = services.value.find(
(service) => service.id == subRequestCount.id
);
const subService = new SubService(
subRequest.id,
subRequest.name,
subRequest.maxQuantity,
getProviders(subRequest.id, null),
subRequestCount.count
);
if (!selectedService.value.subServices) {
selectedService.value.subServices = [];
}
selectedService.value.subServices.push(subService);
}
);
}
currentView.value = 3;
}
} else {
appointmentNotFoundError.value = true;
}
}
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling and refactor nested logic in the fetchAppointment call.

The fetchAppointment API call contains deeply nested logic without proper error handling. Consider refactoring it for better maintainability.

       fetchAppointment(appointmentData, props.baseUrl ?? undefined).then(
         (data) => {
           if ((data as AppointmentDTO).processId != undefined) {
             appointment.value = data as AppointmentDTO;
-            selectedService.value = services.value.find(
-              (service) => service.id == appointment.value.serviceId
-            );
-            if (selectedService.value) {
-              selectedService.value.count = appointment.value.serviceCount;
-              selectedService.value.providers = getProviders(
-                selectedService.value.id,
-                null
-              );
-
-              preselectedLocationId.value = appointment.value.officeId;
-              selectedProvider.value = offices.value.find(
-                (office) => office.id == appointment.value?.officeId
-              );
-
-              if (appointment.value.subRequestCounts.length > 0) {
-                appointment.value.subRequestCounts.forEach(
-                  (subRequestCount) => {
-                    const subRequest: Service = services.value.find(
-                      (service) => service.id == subRequestCount.id
-                    );
-                    const subService = new SubService(
-                      subRequest.id,
-                      subRequest.name,
-                      subRequest.maxQuantity,
-                      getProviders(subRequest.id, null),
-                      subRequestCount.count
-                    );
-                    if (!selectedService.value.subServices) {
-                      selectedService.value.subServices = [];
-                    }
-                    selectedService.value.subServices.push(subService);
-                  }
-                );
-              }
-              currentView.value = 3;
-            }
+            setupAppointmentData(appointment.value);
+            currentView.value = 3;
           } else {
             appointmentNotFoundError.value = true;
           }
         }
-      );
+      ).catch(error => {
+        console.error("Failed to fetch appointment:", error);
+        appointmentNotFoundError.value = true;
+      });

+/**
+ * Set up appointment data after successful fetch
+ */
+const setupAppointmentData = (appointmentData: AppointmentDTO) => {
+  selectedService.value = services.value.find(
+    (service) => service.id == appointmentData.serviceId
+  );
+  
+  if (!selectedService.value) {
+    return;
+  }
+  
+  selectedService.value.count = appointmentData.serviceCount;
+  selectedService.value.providers = getProviders(
+    selectedService.value.id,
+    null
+  );
+
+  preselectedLocationId.value = appointmentData.officeId;
+  selectedProvider.value = offices.value.find(
+    (office) => office.id == appointmentData?.officeId
+  );
+
+  if (appointmentData.subRequestCounts.length > 0) {
+    setupSubServices(appointmentData.subRequestCounts);
+  }
+};
+
+/**
+ * Set up sub-services for an appointment
+ */
+const setupSubServices = (subRequestCounts) => {
+  if (!selectedService.value) return;
+  
+  if (!selectedService.value.subServices) {
+    selectedService.value.subServices = [];
+  }
+  
+  subRequestCounts.forEach((subRequestCount) => {
+    const subRequest: Service = services.value.find(
+      (service) => service.id == subRequestCount.id
+    );
+    
+    if (!subRequest) return;
+    
+    const subService = new SubService(
+      subRequest.id,
+      subRequest.name,
+      subRequest.maxQuantity,
+      getProviders(subRequest.id, null),
+      subRequestCount.count
+    );
+    
+    selectedService.value.subServices.push(subService);
+  });
+};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fetchAppointment(appointmentData, props.baseUrl ?? undefined).then(
(data) => {
if ((data as AppointmentDTO).processId != undefined) {
appointment.value = data as AppointmentDTO;
selectedService.value = services.value.find(
(service) => service.id == appointment.value.serviceId
);
if (selectedService.value) {
selectedService.value.count = appointment.value.serviceCount;
selectedService.value.providers = getProviders(
selectedService.value.id,
null
);
preselectedLocationId.value = appointment.value.officeId;
selectedProvider.value = offices.value.find(
(office) => office.id == appointment.value?.officeId
);
if (appointment.value.subRequestCounts.length > 0) {
appointment.value.subRequestCounts.forEach(
(subRequestCount) => {
const subRequest: Service = services.value.find(
(service) => service.id == subRequestCount.id
);
const subService = new SubService(
subRequest.id,
subRequest.name,
subRequest.maxQuantity,
getProviders(subRequest.id, null),
subRequestCount.count
);
if (!selectedService.value.subServices) {
selectedService.value.subServices = [];
}
selectedService.value.subServices.push(subService);
}
);
}
currentView.value = 3;
}
} else {
appointmentNotFoundError.value = true;
}
}
);
fetchAppointment(appointmentData, props.baseUrl ?? undefined).then(
(data) => {
if ((data as AppointmentDTO).processId != undefined) {
appointment.value = data as AppointmentDTO;
setupAppointmentData(appointment.value);
currentView.value = 3;
} else {
appointmentNotFoundError.value = true;
}
}
).catch(error => {
console.error("Failed to fetch appointment:", error);
appointmentNotFoundError.value = true;
});
/**
* Set up appointment data after successful fetch
*/
const setupAppointmentData = (appointmentData: AppointmentDTO) => {
selectedService.value = services.value.find(
(service) => service.id == appointmentData.serviceId
);
if (!selectedService.value) {
return;
}
selectedService.value.count = appointmentData.serviceCount;
selectedService.value.providers = getProviders(
selectedService.value.id,
null
);
preselectedLocationId.value = appointmentData.officeId;
selectedProvider.value = offices.value.find(
(office) => office.id == appointmentData?.officeId
);
if (appointmentData.subRequestCounts.length > 0) {
setupSubServices(appointmentData.subRequestCounts);
}
};
/**
* Set up sub-services for an appointment
*/
const setupSubServices = (subRequestCounts) => {
if (!selectedService.value) return;
if (!selectedService.value.subServices) {
selectedService.value.subServices = [];
}
subRequestCounts.forEach((subRequestCount) => {
const subRequest: Service = services.value.find(
(service) => service.id == subRequestCount.id
);
if (!subRequest) return;
const subService = new SubService(
subRequest.id,
subRequest.name,
subRequest.maxQuantity,
getProviders(subRequest.id, null),
subRequestCount.count
);
selectedService.value.subServices.push(subService);
});
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants