diff --git a/.changeset/cold-showers-camp.md b/.changeset/cold-showers-camp.md new file mode 100644 index 000000000..690a539e9 --- /dev/null +++ b/.changeset/cold-showers-camp.md @@ -0,0 +1,10 @@ +--- +'@o2s/blocks.notification-summary': major ++'@o2s/blocks.ticket-summary': major +'@o2s/integrations.mocked': minor +'@o2s/api-harmonization': minor +'@o2s/framework': minor +'@o2s/frontend': minor +--- + +add NotificationSummary and TicketSummary blocks diff --git a/.changeset/plain-moons-notice.md b/.changeset/plain-moons-notice.md new file mode 100644 index 000000000..4c919061b --- /dev/null +++ b/.changeset/plain-moons-notice.md @@ -0,0 +1,13 @@ +--- +'@o2s/blocks.notification-summary': minor +'@o2s/blocks.ticket-summary': minor +'@o2s/integrations.mocked': minor +'@o2s/blocks.ticket-list': minor +'@o2s/utils.frontend': minor +'@o2s/api-harmonization': minor +'@o2s/framework': minor +'@o2s/frontend': minor +'@o2s/ui': minor +--- + +conditionally render button in InfoCard for improved layout diff --git a/apps/api-harmonization/package.json b/apps/api-harmonization/package.json index 00e69107d..9f3186241 100644 --- a/apps/api-harmonization/package.json +++ b/apps/api-harmonization/package.json @@ -36,6 +36,7 @@ "@o2s/blocks.invoice-list": "*", "@o2s/blocks.notification-details": "*", "@o2s/blocks.notification-list": "*", + "@o2s/blocks.notification-summary": "*", "@o2s/blocks.order-details": "*", "@o2s/blocks.order-list": "*", "@o2s/blocks.orders-summary": "*", @@ -48,6 +49,7 @@ "@o2s/blocks.ticket-details": "*", "@o2s/blocks.ticket-list": "*", "@o2s/blocks.ticket-recent": "*", + "@o2s/blocks.ticket-summary": "*", "@o2s/blocks.user-account": "*", "@o2s/configs.integrations": "*", "@o2s/framework": "*", diff --git a/apps/api-harmonization/src/app.module.ts b/apps/api-harmonization/src/app.module.ts index f9ef4d660..f80a93e0e 100644 --- a/apps/api-harmonization/src/app.module.ts +++ b/apps/api-harmonization/src/app.module.ts @@ -34,6 +34,7 @@ import * as FeaturedServiceList from '@o2s/blocks.featured-service-list/api-harm import * as InvoiceList from '@o2s/blocks.invoice-list/api-harmonization'; import * as NotificationDetails from '@o2s/blocks.notification-details/api-harmonization'; import * as NotificationList from '@o2s/blocks.notification-list/api-harmonization'; +import * as NotificationSummary from '@o2s/blocks.notification-summary/api-harmonization'; import * as OrderDetails from '@o2s/blocks.order-details/api-harmonization'; import * as OrderList from '@o2s/blocks.order-list/api-harmonization'; import * as OrdersSummary from '@o2s/blocks.orders-summary/api-harmonization'; @@ -46,11 +47,13 @@ import * as SurveyJsForm from '@o2s/blocks.surveyjs-form/api-harmonization'; import * as TicketDetails from '@o2s/blocks.ticket-details/api-harmonization'; import * as TicketList from '@o2s/blocks.ticket-list/api-harmonization'; import * as TicketRecent from '@o2s/blocks.ticket-recent/api-harmonization'; +import * as TicketSummary from '@o2s/blocks.ticket-summary/api-harmonization'; import * as UserAccount from '@o2s/blocks.user-account/api-harmonization'; +// BLOCK IMPORT + import { configuration } from '@o2s/api-harmonization/config/configuration'; -// BLOCK IMPORT import { AppConfig } from './app.config'; import { AppService } from './app.service'; import { ContextHeadersMiddleware } from './middleware/context-headers.middleware'; @@ -133,6 +136,8 @@ export const AuthModuleBaseModule = AuthModule.Module.register(AppConfig); ArticleSearch.Module.register(AppConfig), FeaturedServiceList.Module.register(AppConfig), ArticleList.Module.register(AppConfig), + NotificationSummary.Module.register(AppConfig), + TicketSummary.Module.register(AppConfig), // BLOCK REGISTER ], providers: [ diff --git a/apps/api-harmonization/src/modules/page/page.model.ts b/apps/api-harmonization/src/modules/page/page.model.ts index 73fd1b1f8..1abbaf72e 100644 --- a/apps/api-harmonization/src/modules/page/page.model.ts +++ b/apps/api-harmonization/src/modules/page/page.model.ts @@ -10,6 +10,7 @@ import * as FeaturedServiceList from '@o2s/blocks.featured-service-list/api-harm import * as BlockInvoiceList from '@o2s/blocks.invoice-list/api-harmonization'; import * as NotificationDetails from '@o2s/blocks.notification-details/api-harmonization'; import * as NotificationList from '@o2s/blocks.notification-list/api-harmonization'; +import * as NotificationSummary from '@o2s/blocks.notification-summary/api-harmonization'; import * as OrderDetails from '@o2s/blocks.order-details/api-harmonization'; import * as OrderList from '@o2s/blocks.order-list/api-harmonization'; import * as OrdersSummary from '@o2s/blocks.orders-summary/api-harmonization'; @@ -22,8 +23,11 @@ import * as Surveyjs from '@o2s/blocks.surveyjs-form/api-harmonization'; import * as TicketDetails from '@o2s/blocks.ticket-details/api-harmonization'; import * as TicketList from '@o2s/blocks.ticket-list/api-harmonization'; import * as TicketRecent from '@o2s/blocks.ticket-recent/api-harmonization'; +import * as TicketSummary from '@o2s/blocks.ticket-summary/api-harmonization'; import * as UserAccount from '@o2s/blocks.user-account/api-harmonization'; +// BLOCK IMPORT + export class Init { locales!: { value: string; @@ -73,6 +77,8 @@ export class PageData { export type Blocks = // BLOCK REGISTER + | TicketSummary.Model.TicketSummaryBlock['__typename'] + | NotificationSummary.Model.NotificationSummaryBlock['__typename'] | ArticleList.Model.ArticleListBlock['__typename'] | Category.Model.CategoryBlock['__typename'] | Article.Model.ArticleBlock['__typename'] diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 1889d9749..6752c71ff 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -25,6 +25,7 @@ "@o2s/blocks.invoice-list": "*", "@o2s/blocks.notification-details": "*", "@o2s/blocks.notification-list": "*", + "@o2s/blocks.notification-summary": "*", "@o2s/blocks.order-details": "*", "@o2s/blocks.order-list": "*", "@o2s/blocks.orders-summary": "*", @@ -37,6 +38,7 @@ "@o2s/blocks.ticket-details": "*", "@o2s/blocks.ticket-list": "*", "@o2s/blocks.ticket-recent": "*", + "@o2s/blocks.ticket-summary": "*", "@o2s/blocks.user-account": "*", "@o2s/framework": "*", "@o2s/integrations.mocked": "*", diff --git a/apps/frontend/src/blocks/renderBlocks.tsx b/apps/frontend/src/blocks/renderBlocks.tsx index a3f6702a4..aec5ca9c2 100644 --- a/apps/frontend/src/blocks/renderBlocks.tsx +++ b/apps/frontend/src/blocks/renderBlocks.tsx @@ -11,6 +11,7 @@ import * as FeaturedServiceList from '@o2s/blocks.featured-service-list/frontend import * as InvoiceList from '@o2s/blocks.invoice-list/frontend'; import * as NotificationDetails from '@o2s/blocks.notification-details/frontend'; import * as NotificationList from '@o2s/blocks.notification-list/frontend'; +import * as NotificationSummary from '@o2s/blocks.notification-summary/frontend'; import * as OrderDetails from '@o2s/blocks.order-details/frontend'; import * as OrderList from '@o2s/blocks.order-list/frontend'; import * as OrdersSummary from '@o2s/blocks.orders-summary/frontend'; @@ -23,7 +24,9 @@ import * as SurveyJsForm from '@o2s/blocks.surveyjs-form/frontend'; import * as TicketDetails from '@o2s/blocks.ticket-details/frontend'; import * as TicketList from '@o2s/blocks.ticket-list/frontend'; import * as TickeRecent from '@o2s/blocks.ticket-recent/frontend'; +import * as TicketSummary from '@o2s/blocks.ticket-summary/frontend'; import * as UserAccount from '@o2s/blocks.user-account/frontend'; +// BLOCK IMPORT import { getLocale } from 'next-intl/server'; import { draftMode } from 'next/headers'; import React from 'react'; @@ -34,7 +37,6 @@ import { Container } from '@o2s/ui/components/Container'; import { auth } from '@/auth'; -// BLOCK IMPORT import { routing } from '@/i18n'; import { onSignOut } from '../actions/signOut'; @@ -133,6 +135,10 @@ const renderBlock = (typename: string, blockProps: BlockProps) => { return ; case 'FeaturedServiceListBlock': return ; + case 'NotificationSummaryBlock': + return ; + case 'TicketSummaryBlock': + return ; // BLOCK REGISTER default: return null; diff --git a/package-lock.json b/package-lock.json index 6553de69a..c576613e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -72,6 +72,7 @@ "@o2s/blocks.invoice-list": "*", "@o2s/blocks.notification-details": "*", "@o2s/blocks.notification-list": "*", + "@o2s/blocks.notification-summary": "*", "@o2s/blocks.order-details": "*", "@o2s/blocks.order-list": "*", "@o2s/blocks.orders-summary": "*", @@ -84,6 +85,7 @@ "@o2s/blocks.ticket-details": "*", "@o2s/blocks.ticket-list": "*", "@o2s/blocks.ticket-recent": "*", + "@o2s/blocks.ticket-summary": "*", "@o2s/blocks.user-account": "*", "@o2s/configs.integrations": "*", "@o2s/framework": "*", @@ -462,6 +464,7 @@ "@o2s/blocks.invoice-list": "*", "@o2s/blocks.notification-details": "*", "@o2s/blocks.notification-list": "*", + "@o2s/blocks.notification-summary": "*", "@o2s/blocks.order-details": "*", "@o2s/blocks.order-list": "*", "@o2s/blocks.orders-summary": "*", @@ -474,6 +477,7 @@ "@o2s/blocks.ticket-details": "*", "@o2s/blocks.ticket-list": "*", "@o2s/blocks.ticket-recent": "*", + "@o2s/blocks.ticket-summary": "*", "@o2s/blocks.user-account": "*", "@o2s/framework": "*", "@o2s/integrations.mocked": "*", @@ -12881,6 +12885,10 @@ "resolved": "packages/blocks/notification-list", "link": true }, + "node_modules/@o2s/blocks.notification-summary": { + "resolved": "packages/blocks/notification-summary", + "link": true + }, "node_modules/@o2s/blocks.order-details": { "resolved": "packages/blocks/order-details", "link": true @@ -12929,6 +12937,10 @@ "resolved": "packages/blocks/ticket-recent", "link": true }, + "node_modules/@o2s/blocks.ticket-summary": { + "resolved": "packages/blocks/ticket-summary", + "link": true + }, "node_modules/@o2s/blocks.user-account": { "resolved": "packages/blocks/user-account", "link": true @@ -47712,6 +47724,83 @@ "tailwindcss": "^4" } }, + "packages/blocks/notification-summary": { + "name": "@o2s/blocks.notification-summary", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "@o2s/configs.integrations": "*", + "@o2s/framework": "*", + "@o2s/ui": "*", + "@o2s/utils.api-harmonization": "*", + "@o2s/utils.frontend": "*", + "@o2s/utils.logger": "*" + }, + "devDependencies": { + "@o2s/eslint-config": "*", + "@o2s/prettier-config": "*", + "@o2s/typescript-config": "*", + "concurrently": "^9.1.2", + "dotenv-cli": "^8.0.0", + "eslint": "^9.27.0", + "prettier": "^3.5.3", + "tsc-alias": "^1.8.16", + "typescript": "^5.8.3" + }, + "peerDependencies": { + "@nestjs/axios": "^4", + "@nestjs/common": "^11", + "@nestjs/config": "^4.0.2", + "@nestjs/core": "^11", + "@types/react": "^19", + "@types/react-dom": "^19", + "next": "^15.3.2", + "next-intl": "^4.1.0", + "react": "^19", + "react-dom": "^19", + "rxjs": "^7", + "tailwindcss": "^4" + } + }, + "packages/blocks/notification-summary/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "packages/blocks/notification-summary/node_modules/dotenv-cli": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-8.0.0.tgz", + "integrity": "sha512-aLqYbK7xKOiTMIRf1lDPbI+Y+Ip/wo5k3eyp6ePysVaSqbyxjyK3dK35BTxG+rmd7djf5q2UPs4noPNH+cj0Qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.6", + "dotenv": "^16.3.0", + "dotenv-expand": "^10.0.0", + "minimist": "^1.2.6" + }, + "bin": { + "dotenv": "cli.js" + } + }, + "packages/blocks/notification-summary/node_modules/dotenv-expand": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", + "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, "packages/blocks/order-details": { "name": "@o2s/blocks.order-details", "version": "1.1.0", @@ -48171,6 +48260,83 @@ "tailwindcss": "^4" } }, + "packages/blocks/ticket-summary": { + "name": "@o2s/blocks.ticket-summary", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "@o2s/configs.integrations": "*", + "@o2s/framework": "*", + "@o2s/ui": "*", + "@o2s/utils.api-harmonization": "*", + "@o2s/utils.frontend": "*", + "@o2s/utils.logger": "*" + }, + "devDependencies": { + "@o2s/eslint-config": "*", + "@o2s/prettier-config": "*", + "@o2s/typescript-config": "*", + "concurrently": "^9.1.2", + "dotenv-cli": "^8.0.0", + "eslint": "^9.27.0", + "prettier": "^3.5.3", + "tsc-alias": "^1.8.16", + "typescript": "^5.8.3" + }, + "peerDependencies": { + "@nestjs/axios": "^4", + "@nestjs/common": "^11", + "@nestjs/config": "^4.0.2", + "@nestjs/core": "^11", + "@types/react": "^19", + "@types/react-dom": "^19", + "next": "^15.3.2", + "next-intl": "^4.1.0", + "react": "^19", + "react-dom": "^19", + "rxjs": "^7", + "tailwindcss": "^4" + } + }, + "packages/blocks/ticket-summary/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "packages/blocks/ticket-summary/node_modules/dotenv-cli": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-8.0.0.tgz", + "integrity": "sha512-aLqYbK7xKOiTMIRf1lDPbI+Y+Ip/wo5k3eyp6ePysVaSqbyxjyK3dK35BTxG+rmd7djf5q2UPs4noPNH+cj0Qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.6", + "dotenv": "^16.3.0", + "dotenv-expand": "^10.0.0", + "minimist": "^1.2.6" + }, + "bin": { + "dotenv": "cli.js" + } + }, + "packages/blocks/ticket-summary/node_modules/dotenv-expand": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", + "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, "packages/blocks/user-account": { "name": "@o2s/blocks.user-account", "version": "1.1.0", diff --git a/packages/blocks/notification-summary/.gitignore b/packages/blocks/notification-summary/.gitignore new file mode 100644 index 000000000..29986a380 --- /dev/null +++ b/packages/blocks/notification-summary/.gitignore @@ -0,0 +1,57 @@ +# compiled output +/dist +/node_modules +/build + +# Logs +logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local + +# temp directory +.temp +.tmp + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +/tsconfig.tsbuildinfo diff --git a/packages/blocks/notification-summary/.prettierrc.mjs b/packages/blocks/notification-summary/.prettierrc.mjs new file mode 100644 index 000000000..93b66d398 --- /dev/null +++ b/packages/blocks/notification-summary/.prettierrc.mjs @@ -0,0 +1,25 @@ +import apiConfig from "@o2s/prettier-config/api.mjs"; +import frontendConfig from "@o2s/prettier-config/frontend.mjs"; + +/** + * @see https://prettier.io/docs/en/configuration.html + * @type {import("prettier").Config} + */ +const config = { + overrides: [ + { + files: "./src/api-harmonization/**/*", + options: apiConfig, + }, + { + files: "./src/frontend/**/*", + options: frontendConfig, + }, + { + files: "./src/sdk/**/*", + options: frontendConfig, + }, + ], +}; + +export default config; diff --git a/packages/blocks/notification-summary/eslint.config.mjs b/packages/blocks/notification-summary/eslint.config.mjs new file mode 100644 index 000000000..223f2af08 --- /dev/null +++ b/packages/blocks/notification-summary/eslint.config.mjs @@ -0,0 +1,18 @@ +import { config as apiConfig } from '@o2s/eslint-config/api'; +import { config as frontendConfig } from '@o2s/eslint-config/frontend-block'; +import { defineConfig } from 'eslint/config'; + +export default defineConfig([ + { + files: ['src/api-harmonization/**/*'], + extends: [apiConfig], + }, + { + files: ['src/frontend/**/*'], + extends: [frontendConfig], + }, + { + files: ['src/sdk/**/*'], + extends: [frontendConfig], + }, +]); diff --git a/packages/blocks/notification-summary/lint-staged.config.mjs b/packages/blocks/notification-summary/lint-staged.config.mjs new file mode 100644 index 000000000..ff4483ccb --- /dev/null +++ b/packages/blocks/notification-summary/lint-staged.config.mjs @@ -0,0 +1,4 @@ +export default { + '*.{js,jsx,ts,tsx,css,scss}': ['prettier --write'], + '*.{js,jsx,ts,tsx}': () => 'tsc --noEmit', +}; diff --git a/packages/blocks/notification-summary/package.json b/packages/blocks/notification-summary/package.json new file mode 100644 index 000000000..0eca3687b --- /dev/null +++ b/packages/blocks/notification-summary/package.json @@ -0,0 +1,55 @@ +{ + "name": "@o2s/blocks.notification-summary", + "version": "0.0.1", + "private": false, + "license": "MIT", + "description": "Displays a dynamic NotificationSummary showing notification counts grouped by priority.", + "exports": { + "./api-harmonization": "./dist/api-harmonization/api-harmonization/index.js", + "./frontend": "./dist/frontend/frontend/index.js", + "./sdk": "./dist/sdk/sdk/index.js", + "./client": "./dist/api-harmonization/api-harmonization/notification-summary.client.js" + }, + "files": [ + "dist" + ], + "scripts": { + "dev": "tsc --build tsconfig.json && (concurrently \"tsc --build tsconfig.json -w\" \"tsc-alias -w\")", + "build": "tsc --build tsconfig.json && tsc-alias", + "lint": "tsc --noEmit && eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,css,scss,json}\"" + }, + "dependencies": { + "@o2s/framework": "*", + "@o2s/utils.logger": "*", + "@o2s/ui": "*", + "@o2s/utils.api-harmonization": "*", + "@o2s/utils.frontend": "*", + "@o2s/configs.integrations": "*" + }, + "devDependencies": { + "dotenv-cli": "^8.0.0", + "@o2s/eslint-config": "*", + "@o2s/prettier-config": "*", + "@o2s/typescript-config": "*", + "concurrently": "^9.1.2", + "eslint": "^9.27.0", + "prettier": "^3.5.3", + "tsc-alias": "^1.8.16", + "typescript": "^5.8.3" + }, + "peerDependencies": { + "@types/react": "^19", + "@types/react-dom": "^19", + "react": "^19", + "react-dom": "^19", + "tailwindcss": "^4", + "@nestjs/axios": "^4", + "@nestjs/common": "^11", + "@nestjs/config": "^4.0.2", + "@nestjs/core": "^11", + "rxjs": "^7", + "next": "^15.3.2", + "next-intl": "^4.1.0" + } +} diff --git a/packages/blocks/notification-summary/src/api-harmonization/index.ts b/packages/blocks/notification-summary/src/api-harmonization/index.ts new file mode 100644 index 000000000..ed4175b19 --- /dev/null +++ b/packages/blocks/notification-summary/src/api-harmonization/index.ts @@ -0,0 +1,8 @@ +export const URL = '/blocks/notification-summary'; + +export { NotificationSummaryBlockModule as Module } from './notification-summary.module'; +export { NotificationSummaryService as Service } from './notification-summary.service'; +export { NotificationSummaryController as Controller } from './notification-summary.controller'; + +export * as Model from './notification-summary.model'; +export * as Request from './notification-summary.request'; diff --git a/packages/blocks/notification-summary/src/api-harmonization/notification-summary.client.ts b/packages/blocks/notification-summary/src/api-harmonization/notification-summary.client.ts new file mode 100644 index 000000000..9a08dc6e3 --- /dev/null +++ b/packages/blocks/notification-summary/src/api-harmonization/notification-summary.client.ts @@ -0,0 +1,4 @@ +export const URL = '/blocks/notification-summary'; + +export * as Model from './notification-summary.model'; +export * as Request from './notification-summary.request'; diff --git a/packages/blocks/notification-summary/src/api-harmonization/notification-summary.controller.ts b/packages/blocks/notification-summary/src/api-harmonization/notification-summary.controller.ts new file mode 100644 index 000000000..d5b83058d --- /dev/null +++ b/packages/blocks/notification-summary/src/api-harmonization/notification-summary.controller.ts @@ -0,0 +1,25 @@ +import { Controller, Get, Headers, Query, UseInterceptors } from '@nestjs/common'; + +import { Models } from '@o2s/utils.api-harmonization'; +import { LoggerService } from '@o2s/utils.logger'; + +import { Auth } from '@o2s/framework/modules'; + +import { URL } from './'; +import { GetNotificationSummaryBlockQuery } from './notification-summary.request'; +import { NotificationSummaryService } from './notification-summary.service'; + +@Controller(URL) +@UseInterceptors(LoggerService) +export class NotificationSummaryController { + constructor(protected readonly service: NotificationSummaryService) {} + + @Get() + @Auth.Decorators.Roles({ roles: [] }) + getNotificationSummaryBlock( + @Headers() headers: Models.Headers.AppHeaders, + @Query() query: GetNotificationSummaryBlockQuery, + ) { + return this.service.getNotificationSummaryBlock(query, headers); + } +} diff --git a/packages/blocks/notification-summary/src/api-harmonization/notification-summary.mapper.ts b/packages/blocks/notification-summary/src/api-harmonization/notification-summary.mapper.ts new file mode 100644 index 000000000..73848f20f --- /dev/null +++ b/packages/blocks/notification-summary/src/api-harmonization/notification-summary.mapper.ts @@ -0,0 +1,34 @@ +import { CMS } from '@o2s/configs.integrations'; + +import { Notifications } from '@o2s/framework/modules'; + +import { NotificationSummaryBlock, NotificationSummaryInfoCard } from './notification-summary.model'; + +export const mapNotificationSummary = ( + cms: CMS.Model.NotificationSummaryBlock.NotificationSummaryBlock, + notifications: Notifications.Model.Notifications, + _locale: string, +): NotificationSummaryBlock => { + const priorityCounts = notifications.data.reduce( + (acc, notification) => { + acc[notification.priority] = (acc[notification.priority] || 0) + 1; + return acc; + }, + {} as Record, + ); + + const infoCards: NotificationSummaryInfoCard[] = cms.infoCards.map((card) => ({ + title: card.title, + icon: card.icon, + value: priorityCounts[card.variant] ?? card.value, + description: card.description, + variant: card.variant, + })); + + return { + __typename: 'NotificationSummaryBlock', + id: cms.id, + layout: cms.layout, + infoCards, + }; +}; diff --git a/packages/blocks/notification-summary/src/api-harmonization/notification-summary.model.ts b/packages/blocks/notification-summary/src/api-harmonization/notification-summary.model.ts new file mode 100644 index 000000000..8ee35f323 --- /dev/null +++ b/packages/blocks/notification-summary/src/api-harmonization/notification-summary.model.ts @@ -0,0 +1,16 @@ +import { Models } from '@o2s/utils.api-harmonization'; + +import { Notifications } from '@o2s/framework/modules'; + +export class NotificationSummaryInfoCard { + title!: string; + icon?: string; + value!: number; + description?: string; + variant!: Notifications.Model.NotificationPriority; +} +export class NotificationSummaryBlock extends Models.Block.Block { + __typename!: 'NotificationSummaryBlock'; + layout?: 'vertical' | 'horizontal'; + infoCards!: NotificationSummaryInfoCard[]; +} diff --git a/packages/blocks/notification-summary/src/api-harmonization/notification-summary.module.ts b/packages/blocks/notification-summary/src/api-harmonization/notification-summary.module.ts new file mode 100644 index 000000000..6e7ddd413 --- /dev/null +++ b/packages/blocks/notification-summary/src/api-harmonization/notification-summary.module.ts @@ -0,0 +1,29 @@ +import { DynamicModule, Module } from '@nestjs/common'; +import { CMS, Notifications } from '@o2s/configs.integrations'; + +import * as Framework from '@o2s/framework/modules'; + +import { NotificationSummaryController } from './notification-summary.controller'; +import { NotificationSummaryService } from './notification-summary.service'; + +@Module({}) +export class NotificationSummaryBlockModule { + static register(_config: Framework.ApiConfig): DynamicModule { + return { + module: NotificationSummaryBlockModule, + providers: [ + NotificationSummaryService, + { + provide: CMS.Service, + useExisting: Framework.CMS.Service, + }, + { + provide: Notifications.Service, + useExisting: Framework.Notifications.Service, + }, + ], + controllers: [NotificationSummaryController], + exports: [NotificationSummaryService], + }; + } +} diff --git a/packages/blocks/notification-summary/src/api-harmonization/notification-summary.request.ts b/packages/blocks/notification-summary/src/api-harmonization/notification-summary.request.ts new file mode 100644 index 000000000..bb6d72272 --- /dev/null +++ b/packages/blocks/notification-summary/src/api-harmonization/notification-summary.request.ts @@ -0,0 +1,5 @@ +import { CMS } from '@o2s/framework/modules'; + +export class GetNotificationSummaryBlockQuery implements Omit { + id!: string; +} diff --git a/packages/blocks/notification-summary/src/api-harmonization/notification-summary.service.ts b/packages/blocks/notification-summary/src/api-harmonization/notification-summary.service.ts new file mode 100644 index 000000000..b1959cce1 --- /dev/null +++ b/packages/blocks/notification-summary/src/api-harmonization/notification-summary.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@nestjs/common'; +import { CMS, Notifications } from '@o2s/configs.integrations'; +import { Observable, forkJoin, map } from 'rxjs'; + +import { Models } from '@o2s/utils.api-harmonization'; + +import { mapNotificationSummary } from './notification-summary.mapper'; +import { NotificationSummaryBlock } from './notification-summary.model'; +import { GetNotificationSummaryBlockQuery } from './notification-summary.request'; + +@Injectable() +export class NotificationSummaryService { + constructor( + private readonly cmsService: CMS.Service, + private readonly notificationService: Notifications.Service, + ) {} + + getNotificationSummaryBlock( + query: GetNotificationSummaryBlockQuery, + headers: Models.Headers.AppHeaders, + ): Observable { + const cms = this.cmsService.getNotificationSummaryBlock({ ...query, locale: headers['x-locale'] }); + const notifications = this.notificationService.getNotificationList({ + limit: 1000, + offset: 0, + locale: headers['x-locale'], + }); + + return forkJoin([notifications, cms]).pipe( + map(([notifications, cms]) => mapNotificationSummary(cms, notifications, headers['x-locale'])), + ); + } +} diff --git a/packages/blocks/notification-summary/src/frontend/NotificationSummary.client.stories.tsx b/packages/blocks/notification-summary/src/frontend/NotificationSummary.client.stories.tsx new file mode 100644 index 000000000..9008fd8c3 --- /dev/null +++ b/packages/blocks/notification-summary/src/frontend/NotificationSummary.client.stories.tsx @@ -0,0 +1,108 @@ +import type { Meta, StoryObj } from '@storybook/nextjs'; + +import { NotificationSummaryPure } from './NotificationSummary.client'; + +const meta = { + title: 'Blocks/NotificationSummary', + component: NotificationSummaryPure, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + routing: { + locales: ['en', 'de', 'pl'], + defaultLocale: 'en', + pathnames: { + '/login': { + en: '/sign-in', + de: '/einloggen', + pl: '/logowanie', + }, + }, + }, + __typename: 'NotificationSummaryBlock', + id: 'notification-summary-1', + layout: 'horizontal', + infoCards: [ + { + title: 'High Priority', + icon: 'AlertCircle', + value: 12, + description: 'High priority notifications', + variant: 'HIGH', + }, + { + title: 'Medium Priority', + icon: 'Info', + value: 5, + description: 'Medium priority notifications', + variant: 'MEDIUM', + }, + ], + accessToken: + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSmFuZSBEb2UiLCJlbWFpbCI6ImphbmVAZXhhbXBsZS5jb20iLCJyb2xlIjoic2VsZnNlcnZpY2Vfb3JnX2FkbWluIiwiY3VzdG9tZXIiOnsiaWQiOiJjdXN0LTAwMSIsInJvbGVzIjpbInNlbGZzZXJ2aWNlX29yZ191c2VyIiwic2VsZnNlcnZpY2Vfb3JnX3VzZXIiLCJzZWxmc2VydmljZV9vcmdfYWRtaW4iXSwibmFtZSI6IkFjbWUgQ29ycG9yYXRpb24ifSWiaWF0IjoxNzU2MzI0NTg0fQ.wFAXi1DbgN67z8xQcqZdGz9YeAolbim3lecVIzV2rv0', + locale: 'en', + }, +}; + +export const Vertical: Story = { + args: { + ...Default.args, + layout: 'vertical', + }, +}; + +export const AllPriorities: Story = { + args: { + ...Default.args, + infoCards: [ + { + title: 'Critical Priority', + icon: 'AlertTriangle', + value: 3, + description: 'Critical priority notifications', + variant: 'CRITICAL', + }, + { + title: 'High Priority', + icon: 'AlertCircle', + value: 12, + description: 'High priority notifications', + variant: 'HIGH', + }, + { + title: 'Medium Priority', + icon: 'Info', + value: 5, + description: 'Medium priority notifications', + variant: 'MEDIUM', + }, + { + title: 'Low Priority', + icon: 'Bell', + value: 8, + description: 'Low priority notifications', + variant: 'LOW', + }, + ], + }, +}; + +export const SinglePriority: Story = { + args: { + ...Default.args, + infoCards: [ + { + title: 'High Priority', + icon: 'AlertCircle', + value: 12, + description: 'High priority notifications', + variant: 'HIGH', + }, + ], + }, +}; diff --git a/packages/blocks/notification-summary/src/frontend/NotificationSummary.client.tsx b/packages/blocks/notification-summary/src/frontend/NotificationSummary.client.tsx new file mode 100644 index 000000000..fd0a21cb2 --- /dev/null +++ b/packages/blocks/notification-summary/src/frontend/NotificationSummary.client.tsx @@ -0,0 +1,65 @@ +'use client'; + +import React from 'react'; + +import { Mappings } from '@o2s/utils.frontend'; + +import { cn } from '@o2s/ui/lib/utils'; + +import { InfoCard } from '@o2s/ui/components/Cards/InfoCard'; +import { DynamicIcon, DynamicIconProps } from '@o2s/ui/components/DynamicIcon'; +import { RichText } from '@o2s/ui/components/RichText'; + +import { Typography } from '@o2s/ui/elements/typography'; + +import { NotificationSummaryPureProps } from './NotificationSummary.types'; + +export const NotificationSummaryPure: React.FC = ({ ...component }) => { + const { infoCards, layout } = component; + + if (!infoCards || infoCards.length === 0) { + return null; + } + + const isVertical = layout === 'vertical'; + const containerClass = cn( + 'w-full gap-6', + isVertical ? 'flex flex-col' : 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4', + ); + + return ( +
+ {infoCards.map((infoCard, index) => { + const colorClass = + infoCard.variant && + Mappings.NotificationSummary.notificationSummaryVariants[ + infoCard.variant as keyof typeof Mappings.NotificationSummary.notificationSummaryVariants + ]; + + return ( + + {infoCard.value} + + } + description={ + infoCard.description ? ( +
+ +
+ ) : undefined + } + icon={ + infoCard.icon ? ( + + ) : undefined + } + /> + ); + })} +
+ ); +}; diff --git a/packages/blocks/notification-summary/src/frontend/NotificationSummary.renderer.tsx b/packages/blocks/notification-summary/src/frontend/NotificationSummary.renderer.tsx new file mode 100644 index 000000000..38e942229 --- /dev/null +++ b/packages/blocks/notification-summary/src/frontend/NotificationSummary.renderer.tsx @@ -0,0 +1,31 @@ +import { useLocale } from 'next-intl'; +import React, { Suspense } from 'react'; + +import { Loading } from '@o2s/ui/components/Loading'; + +import { NotificationSummary } from './NotificationSummary.server'; +import { NotificationSummaryRendererProps } from './NotificationSummary.types'; + +export const NotificationSummaryRenderer: React.FC = ({ + id, + accessToken, + routing, +}) => { + const locale = useLocale(); + + return ( + +
+ + +
+ + } + > + +
+ ); +}; diff --git a/packages/blocks/notification-summary/src/frontend/NotificationSummary.server.tsx b/packages/blocks/notification-summary/src/frontend/NotificationSummary.server.tsx new file mode 100644 index 000000000..8d9e4ef3a --- /dev/null +++ b/packages/blocks/notification-summary/src/frontend/NotificationSummary.server.tsx @@ -0,0 +1,29 @@ +import dynamic from 'next/dynamic'; +import React from 'react'; + +import { sdk } from '../sdk'; + +import { NotificationSummaryProps } from './NotificationSummary.types'; + +export const NotificationSummaryDynamic = dynamic(() => + import('./NotificationSummary.client').then((module) => module.NotificationSummaryPure), +); + +export const NotificationSummary: React.FC = async ({ id, accessToken, locale, routing }) => { + try { + const data = await sdk.blocks.getNotificationSummary( + { + id, + }, + { 'x-locale': locale }, + accessToken, + ); + + return ( + + ); + } catch (error) { + console.error('Error fetching NotificationSummary block', error); + return null; + } +}; diff --git a/packages/blocks/notification-summary/src/frontend/NotificationSummary.types.ts b/packages/blocks/notification-summary/src/frontend/NotificationSummary.types.ts new file mode 100644 index 000000000..4ec2c0620 --- /dev/null +++ b/packages/blocks/notification-summary/src/frontend/NotificationSummary.types.ts @@ -0,0 +1,16 @@ +import { defineRouting } from 'next-intl/routing'; + +import { Model } from '../api-harmonization/notification-summary.client'; + +export interface NotificationSummaryProps { + id: string; + accessToken?: string; + locale: string; + routing: ReturnType; +} + +export type NotificationSummaryPureProps = NotificationSummaryProps & Model.NotificationSummaryBlock; + +export type NotificationSummaryRendererProps = Omit & { + slug: string[]; +}; diff --git a/packages/blocks/notification-summary/src/frontend/index.ts b/packages/blocks/notification-summary/src/frontend/index.ts new file mode 100644 index 000000000..51342dba0 --- /dev/null +++ b/packages/blocks/notification-summary/src/frontend/index.ts @@ -0,0 +1,5 @@ +export { NotificationSummaryPure as Client } from './NotificationSummary.client'; +export { NotificationSummary as Server } from './NotificationSummary.server'; +export { NotificationSummaryRenderer as Renderer } from './NotificationSummary.renderer'; + +export * as Types from './NotificationSummary.types'; diff --git a/packages/blocks/notification-summary/src/sdk/index.ts b/packages/blocks/notification-summary/src/sdk/index.ts new file mode 100644 index 000000000..4e2bd516a --- /dev/null +++ b/packages/blocks/notification-summary/src/sdk/index.ts @@ -0,0 +1,28 @@ +// this unused import is necessary for TypeScript to properly resolve API methods +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { Models } from '@o2s/utils.api-harmonization'; + +import { extendSdk, getSdk } from '@o2s/framework/sdk'; + +import { notificationSummary } from './notification-summary'; + +const API_URL = + (typeof window === 'undefined' ? process.env.NEXT_PUBLIC_API_URL_INTERNAL : process.env.NEXT_PUBLIC_API_URL) || + process.env.NEXT_PUBLIC_API_URL; + +const internalSdk = getSdk({ + apiUrl: API_URL!, + logger: { + // @ts-expect-error missing types + level: process.env.NEXT_PUBLIC_LOG_LEVEL, + // @ts-expect-error missing types + format: process.env.NEXT_PUBLIC_LOG_FORMAT, + colorsEnabled: process.env.NEXT_PUBLIC_LOG_COLORS_ENABLED === 'true', + }, +}); + +export const sdk = extendSdk(internalSdk, { + blocks: { + getNotificationSummary: notificationSummary(internalSdk).blocks.getNotificationSummary, + }, +}); diff --git a/packages/blocks/notification-summary/src/sdk/notification-summary.ts b/packages/blocks/notification-summary/src/sdk/notification-summary.ts new file mode 100644 index 000000000..a03da4b44 --- /dev/null +++ b/packages/blocks/notification-summary/src/sdk/notification-summary.ts @@ -0,0 +1,32 @@ +import { Models } from '@o2s/utils.api-harmonization'; +import { Utils } from '@o2s/utils.frontend'; + +import { Sdk } from '@o2s/framework/sdk'; + +import { Model, Request, URL } from '../api-harmonization/notification-summary.client'; + +const API_URL = URL; + +export const notificationSummary = (sdk: Sdk) => ({ + blocks: { + getNotificationSummary: ( + query: Request.GetNotificationSummaryBlockQuery, + headers: Models.Headers.AppHeaders, + authorization?: string, + ): Promise => + sdk.makeRequest({ + method: 'get', + url: `${API_URL}`, + headers: { + ...Utils.Headers.getApiHeaders(), + ...headers, + ...(authorization + ? { + Authorization: `Bearer ${authorization}`, + } + : {}), + }, + params: query, + }), + }, +}); diff --git a/packages/blocks/notification-summary/tsconfig.api.json b/packages/blocks/notification-summary/tsconfig.api.json new file mode 100644 index 000000000..0f9f79f8e --- /dev/null +++ b/packages/blocks/notification-summary/tsconfig.api.json @@ -0,0 +1,14 @@ +{ + "extends": "@o2s/typescript-config/api.json", + "compilerOptions": { + "outDir": "./dist/api-harmonization", + "rootDir": "./src", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "composite": true, + "declaration": true, + "declarationMap": true, + "baseUrl": "./src/api-harmonization", + }, + "include": ["src/api-harmonization"] +} diff --git a/packages/blocks/notification-summary/tsconfig.frontend.json b/packages/blocks/notification-summary/tsconfig.frontend.json new file mode 100644 index 000000000..057957eec --- /dev/null +++ b/packages/blocks/notification-summary/tsconfig.frontend.json @@ -0,0 +1,22 @@ +{ + "extends": "@o2s/typescript-config/frontend.json", + "compilerOptions": { + "outDir": "./dist/frontend", + "rootDir": "./src", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "composite": true, + "declaration": true, + "declarationMap": true, + "noEmit": false, + "jsx": "react", + "baseUrl": "./src/frontend" + }, + "include": [ + "src/frontend", + "src/api-harmonization/notification-summary.client.ts", + "src/api-harmonization/notification-summary.model.ts", + "src/api-harmonization/notification-summary.request.ts", + "src/sdk" + ] +} diff --git a/packages/blocks/notification-summary/tsconfig.json b/packages/blocks/notification-summary/tsconfig.json new file mode 100644 index 000000000..c3031c1dd --- /dev/null +++ b/packages/blocks/notification-summary/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "@o2s/typescript-config/base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "composite": true, + "declaration": true, + "declarationMap": true, + "baseUrl": "./src", + }, + "references": [ + { "path": "./tsconfig.frontend.json" }, + { "path": "./tsconfig.api.json" }, + { "path": "./tsconfig.sdk.json" } + ] +} diff --git a/packages/blocks/notification-summary/tsconfig.sdk.json b/packages/blocks/notification-summary/tsconfig.sdk.json new file mode 100644 index 000000000..7740fa9b2 --- /dev/null +++ b/packages/blocks/notification-summary/tsconfig.sdk.json @@ -0,0 +1,19 @@ +{ + "extends": "@o2s/typescript-config/api.json", + "compilerOptions": { + "outDir": "./dist/sdk", + "rootDir": "./src", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "composite": true, + "declaration": true, + "declarationMap": true, + "baseUrl": "./src/sdk" + }, + "include": [ + "src/sdk", + "src/api-harmonization/notification-summary.client.ts", + "src/api-harmonization/notification-summary.model.ts", + "src/api-harmonization/notification-summary.request.ts" + ] +} diff --git a/packages/blocks/ticket-list/src/frontend/TicketList.renderer.tsx b/packages/blocks/ticket-list/src/frontend/TicketList.renderer.tsx index 1c2253fdf..7fe099af5 100644 --- a/packages/blocks/ticket-list/src/frontend/TicketList.renderer.tsx +++ b/packages/blocks/ticket-list/src/frontend/TicketList.renderer.tsx @@ -19,9 +19,8 @@ export const TicketListRenderer: React.FC = ({ +
-
} > diff --git a/packages/blocks/ticket-summary/.gitignore b/packages/blocks/ticket-summary/.gitignore new file mode 100644 index 000000000..29986a380 --- /dev/null +++ b/packages/blocks/ticket-summary/.gitignore @@ -0,0 +1,57 @@ +# compiled output +/dist +/node_modules +/build + +# Logs +logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local + +# temp directory +.temp +.tmp + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +/tsconfig.tsbuildinfo diff --git a/packages/blocks/ticket-summary/.prettierrc.mjs b/packages/blocks/ticket-summary/.prettierrc.mjs new file mode 100644 index 000000000..93b66d398 --- /dev/null +++ b/packages/blocks/ticket-summary/.prettierrc.mjs @@ -0,0 +1,25 @@ +import apiConfig from "@o2s/prettier-config/api.mjs"; +import frontendConfig from "@o2s/prettier-config/frontend.mjs"; + +/** + * @see https://prettier.io/docs/en/configuration.html + * @type {import("prettier").Config} + */ +const config = { + overrides: [ + { + files: "./src/api-harmonization/**/*", + options: apiConfig, + }, + { + files: "./src/frontend/**/*", + options: frontendConfig, + }, + { + files: "./src/sdk/**/*", + options: frontendConfig, + }, + ], +}; + +export default config; diff --git a/packages/blocks/ticket-summary/eslint.config.mjs b/packages/blocks/ticket-summary/eslint.config.mjs new file mode 100644 index 000000000..223f2af08 --- /dev/null +++ b/packages/blocks/ticket-summary/eslint.config.mjs @@ -0,0 +1,18 @@ +import { config as apiConfig } from '@o2s/eslint-config/api'; +import { config as frontendConfig } from '@o2s/eslint-config/frontend-block'; +import { defineConfig } from 'eslint/config'; + +export default defineConfig([ + { + files: ['src/api-harmonization/**/*'], + extends: [apiConfig], + }, + { + files: ['src/frontend/**/*'], + extends: [frontendConfig], + }, + { + files: ['src/sdk/**/*'], + extends: [frontendConfig], + }, +]); diff --git a/packages/blocks/ticket-summary/lint-staged.config.mjs b/packages/blocks/ticket-summary/lint-staged.config.mjs new file mode 100644 index 000000000..ff4483ccb --- /dev/null +++ b/packages/blocks/ticket-summary/lint-staged.config.mjs @@ -0,0 +1,4 @@ +export default { + '*.{js,jsx,ts,tsx,css,scss}': ['prettier --write'], + '*.{js,jsx,ts,tsx}': () => 'tsc --noEmit', +}; diff --git a/packages/blocks/ticket-summary/package.json b/packages/blocks/ticket-summary/package.json new file mode 100644 index 000000000..e06055130 --- /dev/null +++ b/packages/blocks/ticket-summary/package.json @@ -0,0 +1,55 @@ +{ + "name": "@o2s/blocks.ticket-summary", + "version": "0.0.1", + "private": false, + "license": "MIT", + "description": "Displays a dynamic TicketSummary showing ticket counts grouped by status.", + "exports": { + "./api-harmonization": "./dist/api-harmonization/api-harmonization/index.js", + "./frontend": "./dist/frontend/frontend/index.js", + "./sdk": "./dist/sdk/sdk/index.js", + "./client": "./dist/api-harmonization/api-harmonization/ticket-summary.client.js" + }, + "files": [ + "dist" + ], + "scripts": { + "dev": "tsc --build tsconfig.json && (concurrently \"tsc --build tsconfig.json -w\" \"tsc-alias -w\")", + "build": "tsc --build tsconfig.json && tsc-alias", + "lint": "tsc --noEmit && eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,css,scss,json}\"" + }, + "dependencies": { + "@o2s/framework": "*", + "@o2s/utils.logger": "*", + "@o2s/ui": "*", + "@o2s/utils.api-harmonization": "*", + "@o2s/utils.frontend": "*", + "@o2s/configs.integrations": "*" + }, + "devDependencies": { + "dotenv-cli": "^8.0.0", + "@o2s/eslint-config": "*", + "@o2s/prettier-config": "*", + "@o2s/typescript-config": "*", + "concurrently": "^9.1.2", + "eslint": "^9.27.0", + "prettier": "^3.5.3", + "tsc-alias": "^1.8.16", + "typescript": "^5.8.3" + }, + "peerDependencies": { + "@types/react": "^19", + "@types/react-dom": "^19", + "react": "^19", + "react-dom": "^19", + "tailwindcss": "^4", + "@nestjs/axios": "^4", + "@nestjs/common": "^11", + "@nestjs/config": "^4.0.2", + "@nestjs/core": "^11", + "rxjs": "^7", + "next": "^15.3.2", + "next-intl": "^4.1.0" + } +} diff --git a/packages/blocks/ticket-summary/src/api-harmonization/index.ts b/packages/blocks/ticket-summary/src/api-harmonization/index.ts new file mode 100644 index 000000000..a096ea7b6 --- /dev/null +++ b/packages/blocks/ticket-summary/src/api-harmonization/index.ts @@ -0,0 +1,8 @@ +export const URL = '/blocks/ticket-summary'; + +export { TicketSummaryBlockModule as Module } from './ticket-summary.module'; +export { TicketSummaryService as Service } from './ticket-summary.service'; +export { TicketSummaryController as Controller } from './ticket-summary.controller'; + +export * as Model from './ticket-summary.model'; +export * as Request from './ticket-summary.request'; diff --git a/packages/blocks/ticket-summary/src/api-harmonization/ticket-summary.client.ts b/packages/blocks/ticket-summary/src/api-harmonization/ticket-summary.client.ts new file mode 100644 index 000000000..443da3522 --- /dev/null +++ b/packages/blocks/ticket-summary/src/api-harmonization/ticket-summary.client.ts @@ -0,0 +1,4 @@ +export const URL = '/blocks/ticket-summary'; + +export * as Model from './ticket-summary.model'; +export * as Request from './ticket-summary.request'; diff --git a/packages/blocks/ticket-summary/src/api-harmonization/ticket-summary.controller.ts b/packages/blocks/ticket-summary/src/api-harmonization/ticket-summary.controller.ts new file mode 100644 index 000000000..1c05b56fd --- /dev/null +++ b/packages/blocks/ticket-summary/src/api-harmonization/ticket-summary.controller.ts @@ -0,0 +1,22 @@ +import { Controller, Get, Headers, Query, UseInterceptors } from '@nestjs/common'; + +import { Models } from '@o2s/utils.api-harmonization'; +import { LoggerService } from '@o2s/utils.logger'; + +import { Auth } from '@o2s/framework/modules'; + +import { URL } from './'; +import { GetTicketSummaryBlockQuery } from './ticket-summary.request'; +import { TicketSummaryService } from './ticket-summary.service'; + +@Controller(URL) +@UseInterceptors(LoggerService) +export class TicketSummaryController { + constructor(protected readonly service: TicketSummaryService) {} + + @Get() + @Auth.Decorators.Roles({ roles: [] }) + getTicketSummaryBlock(@Headers() headers: Models.Headers.AppHeaders, @Query() query: GetTicketSummaryBlockQuery) { + return this.service.getTicketSummaryBlock(query, headers); + } +} diff --git a/packages/blocks/ticket-summary/src/api-harmonization/ticket-summary.mapper.ts b/packages/blocks/ticket-summary/src/api-harmonization/ticket-summary.mapper.ts new file mode 100644 index 000000000..c8e2da418 --- /dev/null +++ b/packages/blocks/ticket-summary/src/api-harmonization/ticket-summary.mapper.ts @@ -0,0 +1,32 @@ +import { CMS, Tickets } from '@o2s/configs.integrations'; + +import { TicketSummaryBlock, TicketSummaryInfoCard } from './ticket-summary.model'; + +export const mapTicketSummary = ( + cms: CMS.Model.TicketSummaryBlock.TicketSummaryBlock, + tickets: Tickets.Model.Tickets, + _locale: string, +): TicketSummaryBlock => { + const statusCounts = tickets.data.reduce( + (acc, ticket) => { + acc[ticket.status] = (acc[ticket.status] || 0) + 1; + return acc; + }, + {} as Record, + ); + + const infoCards: TicketSummaryInfoCard[] = cms.infoCards.map((card) => ({ + title: card.title, + icon: card.icon, + value: card.variant ? (statusCounts[card.variant] ?? card.value) : card.value, + description: card.description, + variant: card.variant, + })); + + return { + __typename: 'TicketSummaryBlock', + id: cms.id, + layout: cms.layout, + infoCards, + }; +}; diff --git a/packages/blocks/ticket-summary/src/api-harmonization/ticket-summary.model.ts b/packages/blocks/ticket-summary/src/api-harmonization/ticket-summary.model.ts new file mode 100644 index 000000000..93bbc8ab4 --- /dev/null +++ b/packages/blocks/ticket-summary/src/api-harmonization/ticket-summary.model.ts @@ -0,0 +1,15 @@ +import { Models } from '@o2s/utils.api-harmonization'; + +export class TicketSummaryInfoCard { + title!: string; + icon?: string; + value!: number; + description?: string; + variant?: 'OPEN' | 'IN_PROGRESS' | 'CLOSED'; +} + +export class TicketSummaryBlock extends Models.Block.Block { + __typename!: 'TicketSummaryBlock'; + layout?: 'vertical' | 'horizontal'; + infoCards!: TicketSummaryInfoCard[]; +} diff --git a/packages/blocks/ticket-summary/src/api-harmonization/ticket-summary.module.ts b/packages/blocks/ticket-summary/src/api-harmonization/ticket-summary.module.ts new file mode 100644 index 000000000..c8b0d1255 --- /dev/null +++ b/packages/blocks/ticket-summary/src/api-harmonization/ticket-summary.module.ts @@ -0,0 +1,29 @@ +import { DynamicModule, Module } from '@nestjs/common'; +import { CMS, Tickets } from '@o2s/configs.integrations'; + +import * as Framework from '@o2s/framework/modules'; + +import { TicketSummaryController } from './ticket-summary.controller'; +import { TicketSummaryService } from './ticket-summary.service'; + +@Module({}) +export class TicketSummaryBlockModule { + static register(_config: Framework.ApiConfig): DynamicModule { + return { + module: TicketSummaryBlockModule, + providers: [ + TicketSummaryService, + { + provide: CMS.Service, + useExisting: Framework.CMS.Service, + }, + { + provide: Tickets.Service, + useExisting: Framework.Tickets.Service, + }, + ], + controllers: [TicketSummaryController], + exports: [TicketSummaryService], + }; + } +} diff --git a/packages/blocks/ticket-summary/src/api-harmonization/ticket-summary.request.ts b/packages/blocks/ticket-summary/src/api-harmonization/ticket-summary.request.ts new file mode 100644 index 000000000..1ec6bc136 --- /dev/null +++ b/packages/blocks/ticket-summary/src/api-harmonization/ticket-summary.request.ts @@ -0,0 +1,5 @@ +import { CMS } from '@o2s/framework/modules'; + +export class GetTicketSummaryBlockQuery implements Omit { + id!: string; +} diff --git a/packages/blocks/ticket-summary/src/api-harmonization/ticket-summary.service.ts b/packages/blocks/ticket-summary/src/api-harmonization/ticket-summary.service.ts new file mode 100644 index 000000000..fe77c41f2 --- /dev/null +++ b/packages/blocks/ticket-summary/src/api-harmonization/ticket-summary.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@nestjs/common'; +import { CMS, Tickets } from '@o2s/configs.integrations'; +import { Observable, forkJoin, map } from 'rxjs'; + +import { Models } from '@o2s/utils.api-harmonization'; + +import { mapTicketSummary } from './ticket-summary.mapper'; +import { TicketSummaryBlock } from './ticket-summary.model'; +import { GetTicketSummaryBlockQuery } from './ticket-summary.request'; + +@Injectable() +export class TicketSummaryService { + constructor( + private readonly cmsService: CMS.Service, + private readonly ticketService: Tickets.Service, + ) {} + + getTicketSummaryBlock( + query: GetTicketSummaryBlockQuery, + headers: Models.Headers.AppHeaders, + ): Observable { + const cms = this.cmsService.getTicketSummaryBlock({ ...query, locale: headers['x-locale'] }); + const tickets = this.ticketService.getTicketList({ + limit: 1000, + offset: 0, + locale: headers['x-locale'], + }); + + return forkJoin([tickets, cms]).pipe( + map(([tickets, cms]) => mapTicketSummary(cms, tickets, headers['x-locale'])), + ); + } +} diff --git a/packages/blocks/ticket-summary/src/frontend/TicketSummary.client.stories.tsx b/packages/blocks/ticket-summary/src/frontend/TicketSummary.client.stories.tsx new file mode 100644 index 000000000..02fe5cf15 --- /dev/null +++ b/packages/blocks/ticket-summary/src/frontend/TicketSummary.client.stories.tsx @@ -0,0 +1,79 @@ +import type { Meta, StoryObj } from '@storybook/nextjs'; + +import { TicketSummaryPure } from './TicketSummary.client'; + +const meta = { + title: 'Blocks/TicketSummary', + component: TicketSummaryPure, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + routing: { + locales: ['en', 'de', 'pl'], + defaultLocale: 'en', + pathnames: { + '/login': { + en: '/sign-in', + de: '/einloggen', + pl: '/logowanie', + }, + }, + }, + __typename: 'TicketSummaryBlock', + id: 'ticket-summary-1', + layout: 'horizontal', + infoCards: [ + { + title: 'Under consideration', + icon: 'Clock', + value: 35, + description: 'Tickets under consideration', + variant: 'OPEN', + }, + { + title: 'New response', + icon: 'AlertCircle', + value: 36, + description: 'New response tickets', + variant: 'IN_PROGRESS', + }, + { + title: 'Resolved', + icon: 'CheckCircle', + value: 34, + description: 'Resolved tickets', + variant: 'CLOSED', + }, + ], + accessToken: + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSmFuZSBEb2UiLCJlbWFpbCI6ImphbmVAZXhhbXBsZS5jb20iLCJyb2xlIjoic2VsZnNlcnZpY2Vfb3JnX2FkbWluIiwiY3VzdG9tZXIiOnsiaWQiOiJjdXN0LTAwMSIsInJvbGVzIjpbInNlbGZzZXJ2aWNlX29yZ191c2VyIiwic2VsZnNlcnZpY2Vfb3JnX3VzZXIiLCJzZWxmc2VydmljZV9vcmdfYWRtaW4iXSwibmFtZSI6IkFjbWUgQ29ycG9yYXRpb24ifSWiaWF0IjoxNzU2MzI0NTg0fQ.wFAXi1DbgN67z8xQcqZdGz9YeAolbim3lecVIzV2rv0', + locale: 'en', + }, +}; + +export const Vertical: Story = { + args: { + ...Default.args, + layout: 'vertical', + }, +}; + +export const SingleStatus: Story = { + args: { + ...Default.args, + infoCards: [ + { + title: 'New response', + icon: 'AlertCircle', + value: 36, + description: 'New response tickets', + variant: 'IN_PROGRESS', + }, + ], + }, +}; diff --git a/packages/blocks/ticket-summary/src/frontend/TicketSummary.client.tsx b/packages/blocks/ticket-summary/src/frontend/TicketSummary.client.tsx new file mode 100644 index 000000000..b34f0e0da --- /dev/null +++ b/packages/blocks/ticket-summary/src/frontend/TicketSummary.client.tsx @@ -0,0 +1,62 @@ +'use client'; + +import React from 'react'; + +import { Mappings } from '@o2s/utils.frontend'; + +import { cn } from '@o2s/ui/lib/utils'; + +import { InfoCard } from '@o2s/ui/components/Cards/InfoCard'; +import { DynamicIcon, DynamicIconProps } from '@o2s/ui/components/DynamicIcon'; +import { RichText } from '@o2s/ui/components/RichText'; + +import { Typography } from '@o2s/ui/elements/typography'; + +import { TicketSummaryPureProps } from './TicketSummary.types'; + +export const TicketSummaryPure: React.FC = ({ ...component }) => { + const { infoCards, layout } = component; + + if (!infoCards || infoCards.length === 0) { + return null; + } + + const isVertical = layout === 'vertical'; + const containerClass = cn('w-full gap-6', isVertical ? 'flex flex-col' : 'grid grid-cols-1 md:grid-cols-3'); + + return ( +
+ {infoCards.map((infoCard, index) => { + const colorClass = + infoCard.variant && + Mappings.TicketSummary.ticketSummaryVariants[ + infoCard.variant as keyof typeof Mappings.TicketSummary.ticketSummaryVariants + ]; + + return ( + + {infoCard.value} + + } + description={ + infoCard.description ? ( +
+ +
+ ) : undefined + } + icon={ + infoCard.icon ? ( + + ) : undefined + } + /> + ); + })} +
+ ); +}; diff --git a/packages/blocks/ticket-summary/src/frontend/TicketSummary.renderer.tsx b/packages/blocks/ticket-summary/src/frontend/TicketSummary.renderer.tsx new file mode 100644 index 000000000..12a14090c --- /dev/null +++ b/packages/blocks/ticket-summary/src/frontend/TicketSummary.renderer.tsx @@ -0,0 +1,27 @@ +import { useLocale } from 'next-intl'; +import React, { Suspense } from 'react'; + +import { Loading } from '@o2s/ui/components/Loading'; + +import { TicketSummary } from './TicketSummary.server'; +import { TicketSummaryRendererProps } from './TicketSummary.types'; + +export const TicketSummaryRenderer: React.FC = ({ id, accessToken, routing }) => { + const locale = useLocale(); + + return ( + +
+ + +
+ + } + > + +
+ ); +}; diff --git a/packages/blocks/ticket-summary/src/frontend/TicketSummary.server.tsx b/packages/blocks/ticket-summary/src/frontend/TicketSummary.server.tsx new file mode 100644 index 000000000..4b7f3c251 --- /dev/null +++ b/packages/blocks/ticket-summary/src/frontend/TicketSummary.server.tsx @@ -0,0 +1,27 @@ +import dynamic from 'next/dynamic'; +import React from 'react'; + +import { sdk } from '../sdk'; + +import { TicketSummaryProps } from './TicketSummary.types'; + +export const TicketSummaryDynamic = dynamic(() => + import('./TicketSummary.client').then((module) => module.TicketSummaryPure), +); + +export const TicketSummary: React.FC = async ({ id, accessToken, locale, routing }) => { + try { + const data = await sdk.blocks.getTicketSummary( + { + id, + }, + { 'x-locale': locale }, + accessToken, + ); + + return ; + } catch (error) { + console.error('Error fetching TicketSummary block', error); + return null; + } +}; diff --git a/packages/blocks/ticket-summary/src/frontend/TicketSummary.types.ts b/packages/blocks/ticket-summary/src/frontend/TicketSummary.types.ts new file mode 100644 index 000000000..aa8b7dd90 --- /dev/null +++ b/packages/blocks/ticket-summary/src/frontend/TicketSummary.types.ts @@ -0,0 +1,16 @@ +import { defineRouting } from 'next-intl/routing'; + +import { Model } from '../api-harmonization/ticket-summary.client'; + +export interface TicketSummaryProps { + id: string; + accessToken?: string; + locale: string; + routing: ReturnType; +} + +export type TicketSummaryPureProps = TicketSummaryProps & Model.TicketSummaryBlock; + +export type TicketSummaryRendererProps = Omit & { + slug: string[]; +}; diff --git a/packages/blocks/ticket-summary/src/frontend/index.ts b/packages/blocks/ticket-summary/src/frontend/index.ts new file mode 100644 index 000000000..211ee5e77 --- /dev/null +++ b/packages/blocks/ticket-summary/src/frontend/index.ts @@ -0,0 +1,5 @@ +export { TicketSummaryPure as Client } from './TicketSummary.client'; +export { TicketSummary as Server } from './TicketSummary.server'; +export { TicketSummaryRenderer as Renderer } from './TicketSummary.renderer'; + +export * as Types from './TicketSummary.types'; diff --git a/packages/blocks/ticket-summary/src/sdk/index.ts b/packages/blocks/ticket-summary/src/sdk/index.ts new file mode 100644 index 000000000..40756ff99 --- /dev/null +++ b/packages/blocks/ticket-summary/src/sdk/index.ts @@ -0,0 +1,28 @@ +// this unused import is necessary for TypeScript to properly resolve API methods +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { Models } from '@o2s/utils.api-harmonization'; + +import { extendSdk, getSdk } from '@o2s/framework/sdk'; + +import { ticketSummary } from './ticket-summary'; + +const API_URL = + (typeof window === 'undefined' ? process.env.NEXT_PUBLIC_API_URL_INTERNAL : process.env.NEXT_PUBLIC_API_URL) || + process.env.NEXT_PUBLIC_API_URL; + +const internalSdk = getSdk({ + apiUrl: API_URL!, + logger: { + // @ts-expect-error missing types + level: process.env.NEXT_PUBLIC_LOG_LEVEL, + // @ts-expect-error missing types + format: process.env.NEXT_PUBLIC_LOG_FORMAT, + colorsEnabled: process.env.NEXT_PUBLIC_LOG_COLORS_ENABLED === 'true', + }, +}); + +export const sdk = extendSdk(internalSdk, { + blocks: { + getTicketSummary: ticketSummary(internalSdk).blocks.getTicketSummary, + }, +}); diff --git a/packages/blocks/ticket-summary/src/sdk/ticket-summary.ts b/packages/blocks/ticket-summary/src/sdk/ticket-summary.ts new file mode 100644 index 000000000..72d776d91 --- /dev/null +++ b/packages/blocks/ticket-summary/src/sdk/ticket-summary.ts @@ -0,0 +1,32 @@ +import { Models } from '@o2s/utils.api-harmonization'; +import { Utils } from '@o2s/utils.frontend'; + +import { Sdk } from '@o2s/framework/sdk'; + +import { Model, Request, URL } from '../api-harmonization/ticket-summary.client'; + +const API_URL = URL; + +export const ticketSummary = (sdk: Sdk) => ({ + blocks: { + getTicketSummary: ( + query: Request.GetTicketSummaryBlockQuery, + headers: Models.Headers.AppHeaders, + authorization?: string, + ): Promise => + sdk.makeRequest({ + method: 'get', + url: `${API_URL}`, + headers: { + ...Utils.Headers.getApiHeaders(), + ...headers, + ...(authorization + ? { + Authorization: `Bearer ${authorization}`, + } + : {}), + }, + params: query, + }), + }, +}); diff --git a/packages/blocks/ticket-summary/tsconfig.api.json b/packages/blocks/ticket-summary/tsconfig.api.json new file mode 100644 index 000000000..0f9f79f8e --- /dev/null +++ b/packages/blocks/ticket-summary/tsconfig.api.json @@ -0,0 +1,14 @@ +{ + "extends": "@o2s/typescript-config/api.json", + "compilerOptions": { + "outDir": "./dist/api-harmonization", + "rootDir": "./src", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "composite": true, + "declaration": true, + "declarationMap": true, + "baseUrl": "./src/api-harmonization", + }, + "include": ["src/api-harmonization"] +} diff --git a/packages/blocks/ticket-summary/tsconfig.frontend.json b/packages/blocks/ticket-summary/tsconfig.frontend.json new file mode 100644 index 000000000..88c5d5556 --- /dev/null +++ b/packages/blocks/ticket-summary/tsconfig.frontend.json @@ -0,0 +1,22 @@ +{ + "extends": "@o2s/typescript-config/frontend.json", + "compilerOptions": { + "outDir": "./dist/frontend", + "rootDir": "./src", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "composite": true, + "declaration": true, + "declarationMap": true, + "noEmit": false, + "jsx": "react", + "baseUrl": "./src/frontend" + }, + "include": [ + "src/frontend", + "src/api-harmonization/ticket-summary.client.ts", + "src/api-harmonization/ticket-summary.model.ts", + "src/api-harmonization/ticket-summary.request.ts", + "src/sdk" + ] +} diff --git a/packages/blocks/ticket-summary/tsconfig.json b/packages/blocks/ticket-summary/tsconfig.json new file mode 100644 index 000000000..c3031c1dd --- /dev/null +++ b/packages/blocks/ticket-summary/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "@o2s/typescript-config/base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "composite": true, + "declaration": true, + "declarationMap": true, + "baseUrl": "./src", + }, + "references": [ + { "path": "./tsconfig.frontend.json" }, + { "path": "./tsconfig.api.json" }, + { "path": "./tsconfig.sdk.json" } + ] +} diff --git a/packages/blocks/ticket-summary/tsconfig.sdk.json b/packages/blocks/ticket-summary/tsconfig.sdk.json new file mode 100644 index 000000000..b377825f2 --- /dev/null +++ b/packages/blocks/ticket-summary/tsconfig.sdk.json @@ -0,0 +1,19 @@ +{ + "extends": "@o2s/typescript-config/api.json", + "compilerOptions": { + "outDir": "./dist/sdk", + "rootDir": "./src", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "composite": true, + "declaration": true, + "declarationMap": true, + "baseUrl": "./src/sdk" + }, + "include": [ + "src/sdk", + "src/api-harmonization/ticket-summary.client.ts", + "src/api-harmonization/ticket-summary.model.ts", + "src/api-harmonization/ticket-summary.request.ts" + ] +} diff --git a/packages/framework/src/modules/cms/cms.model.ts b/packages/framework/src/modules/cms/cms.model.ts index 8138efa0f..290df3772 100644 --- a/packages/framework/src/modules/cms/cms.model.ts +++ b/packages/framework/src/modules/cms/cms.model.ts @@ -33,3 +33,6 @@ export * as CategoryListBlock from './models/blocks/category-list.model'; export * as QuickLinksBlock from './models/blocks/quick-links.model'; export * as ArticleSearchBlock from './models/blocks/article-search.model'; export * as FeaturedServiceListBlock from './models/blocks/featured-service-list.model'; +export * as NotificationSummaryBlock from './models/blocks/notification-summary.model'; +export * as TicketSummaryBlock from './models/blocks/ticket-summary.model'; +// BLOCK IMPORT diff --git a/packages/framework/src/modules/cms/models/blocks/notification-summary.model.ts b/packages/framework/src/modules/cms/models/blocks/notification-summary.model.ts new file mode 100644 index 000000000..cd3f98202 --- /dev/null +++ b/packages/framework/src/modules/cms/models/blocks/notification-summary.model.ts @@ -0,0 +1,16 @@ +import { NotificationPriority } from '@/modules/notifications/notifications.model'; + +import { Block } from '@/utils/models'; + +export class NotificationSummaryInfoCard { + title!: string; + icon?: string; + value!: number; + description?: string; + variant!: NotificationPriority; +} + +export class NotificationSummaryBlock extends Block.Block { + layout?: 'vertical' | 'horizontal'; + infoCards!: NotificationSummaryInfoCard[]; +} diff --git a/packages/framework/src/modules/cms/models/blocks/ticket-summary.model.ts b/packages/framework/src/modules/cms/models/blocks/ticket-summary.model.ts new file mode 100644 index 000000000..c48b178c5 --- /dev/null +++ b/packages/framework/src/modules/cms/models/blocks/ticket-summary.model.ts @@ -0,0 +1,16 @@ +import { TicketStatus } from '@/modules/tickets/tickets.model'; + +import { Block } from '@/utils/models'; + +export class TicketSummaryInfoCard { + title!: string; + icon?: string; + value!: number; + description?: string; + variant?: TicketStatus; +} + +export class TicketSummaryBlock extends Block.Block { + layout?: 'vertical' | 'horizontal'; + infoCards!: TicketSummaryInfoCard[]; +} diff --git a/packages/integrations/mocked/src/modules/cms/cms.service.ts b/packages/integrations/mocked/src/modules/cms/cms.service.ts index 95b7ca4c4..f156998bc 100644 --- a/packages/integrations/mocked/src/modules/cms/cms.service.ts +++ b/packages/integrations/mocked/src/modules/cms/cms.service.ts @@ -12,6 +12,7 @@ import { mapInvoiceDetailsBlock } from './mappers/blocks/cms.invoice-details.map import { mapInvoiceListBlock } from './mappers/blocks/cms.invoice-list.mapper'; import { mapNotificationDetailsBlock } from './mappers/blocks/cms.notification-details.mapper'; import { mapNotificationListBlock } from './mappers/blocks/cms.notification-list.mapper'; +import { mapNotificationSummaryBlock } from './mappers/blocks/cms.notification-summary.mapper'; import { mapOrderDetailsBlock } from './mappers/blocks/cms.order-details.mapper'; import { mapOrderListBlock } from './mappers/blocks/cms.order-list.mapper'; import { mapOrdersSummaryBlock } from './mappers/blocks/cms.orders-summary.mapper'; @@ -25,6 +26,7 @@ import { mapSurveyJsBlock } from './mappers/blocks/cms.surveyjs-block.mapper'; import { mapTicketDetailsBlock } from './mappers/blocks/cms.ticket-details.mapper'; import { mapTicketListBlock } from './mappers/blocks/cms.ticket-list.mapper'; import { mapTicketRecentBlock } from './mappers/blocks/cms.ticket-recent.mapper'; +import { mapTicketSummaryBlock } from './mappers/blocks/cms.ticket-summary.mapper'; import { mapUserAccountBlock } from './mappers/blocks/cms.user-account.mapper'; import { mapAppConfig } from './mappers/cms.app-config.mapper'; import { mapCategoryBlock } from './mappers/cms.category.mapper'; @@ -100,6 +102,10 @@ export class CmsService implements CMS.Service { return of(mapNotificationDetailsBlock(_options.locale)).pipe(responseDelay()); } + getNotificationSummaryBlock(_options: CMS.Request.GetCmsEntryParams) { + return of(mapNotificationSummaryBlock(_options.locale)).pipe(responseDelay()); + } + getInvoiceListBlock(options: CMS.Request.GetCmsEntryParams) { return of(mapInvoiceListBlock(options.locale)).pipe(responseDelay()); } @@ -187,4 +193,8 @@ export class CmsService implements CMS.Service { getFeaturedServiceListBlock(options: CMS.Request.GetCmsEntryParams) { return of(mapFeaturedServiceListBlock(options.locale)).pipe(responseDelay()); } + + getTicketSummaryBlock(options: CMS.Request.GetCmsEntryParams) { + return of(mapTicketSummaryBlock(options.locale)).pipe(responseDelay()); + } } diff --git a/packages/integrations/mocked/src/modules/cms/mappers/blocks/cms.notification-summary.mapper.ts b/packages/integrations/mocked/src/modules/cms/mappers/blocks/cms.notification-summary.mapper.ts new file mode 100644 index 000000000..84bb5232b --- /dev/null +++ b/packages/integrations/mocked/src/modules/cms/mappers/blocks/cms.notification-summary.mapper.ts @@ -0,0 +1,119 @@ +import { CMS } from '@o2s/framework/modules'; + +const MOCK_NOTIFICATION_SUMMARY_BLOCK_EN: CMS.Model.NotificationSummaryBlock.NotificationSummaryBlock = { + id: 'notification-summary-1', + layout: 'horizontal', + infoCards: [ + { + title: 'Critical Priority', + icon: 'AlertCircle', + description: 'Critical priority notifications', + variant: 'CRITICAL', + value: 0, + }, + { + title: 'High Priority', + icon: 'AlertCircle', + description: 'High priority notifications', + variant: 'HIGH', + value: 0, + }, + { + title: 'Medium Priority', + icon: 'AlertCircle', + description: 'Medium priority notifications', + variant: 'MEDIUM', + value: 0, + }, + { + title: 'Low Priority', + icon: 'AlertCircle', + description: 'Low priority notifications', + variant: 'LOW', + value: 0, + }, + ], +}; + +const MOCK_NOTIFICATION_SUMMARY_BLOCK_DE: CMS.Model.NotificationSummaryBlock.NotificationSummaryBlock = { + id: 'notification-summary-1', + layout: 'horizontal', + infoCards: [ + { + title: 'Kritische Priorität', + icon: 'AlertCircle', + description: 'Benachrichtigungen mit kritischer Priorität', + variant: 'CRITICAL', + value: 0, + }, + { + title: 'Hohe Priorität', + icon: 'AlertCircle', + description: 'Benachrichtigungen mit hoher Priorität', + variant: 'HIGH', + value: 0, + }, + { + title: 'Mittlere Priorität', + icon: 'AlertCircle', + description: 'Benachrichtigungen mit mittlerer Priorität', + variant: 'MEDIUM', + value: 0, + }, + { + title: 'Niedrige Priorität', + icon: 'AlertCircle', + description: 'Benachrichtigungen mit niedriger Priorität', + variant: 'LOW', + value: 0, + }, + ], +}; + +const MOCK_NOTIFICATION_SUMMARY_BLOCK_PL: CMS.Model.NotificationSummaryBlock.NotificationSummaryBlock = { + id: 'notification-summary-1', + layout: 'horizontal', + infoCards: [ + { + title: 'Krytyczny priorytet', + icon: 'AlertCircle', + description: 'Powiadomienia o krytycznym priorytecie', + variant: 'CRITICAL', + value: 0, + }, + { + title: 'Wysoki priorytet', + icon: 'AlertCircle', + description: 'Powiadomienia o wysokim priorytecie', + variant: 'HIGH', + value: 0, + }, + { + title: 'Średni priorytet', + icon: 'AlertCircle', + description: 'Powiadomienia o średnim priorytecie', + variant: 'MEDIUM', + value: 0, + }, + { + title: 'Niski priorytet', + icon: 'AlertCircle', + description: 'Powiadomienia o niskim priorytecie', + variant: 'LOW', + value: 0, + }, + ], +}; + +export const mapNotificationSummaryBlock = ( + locale: string, +): CMS.Model.NotificationSummaryBlock.NotificationSummaryBlock => { + switch (locale) { + case 'de': + return MOCK_NOTIFICATION_SUMMARY_BLOCK_DE; + case 'pl': + return MOCK_NOTIFICATION_SUMMARY_BLOCK_PL; + default: + return MOCK_NOTIFICATION_SUMMARY_BLOCK_EN; + } +}; diff --git a/packages/integrations/mocked/src/modules/cms/mappers/blocks/cms.ticket-summary.mapper.ts b/packages/integrations/mocked/src/modules/cms/mappers/blocks/cms.ticket-summary.mapper.ts new file mode 100644 index 000000000..a1534201d --- /dev/null +++ b/packages/integrations/mocked/src/modules/cms/mappers/blocks/cms.ticket-summary.mapper.ts @@ -0,0 +1,96 @@ +import { CMS } from '@o2s/framework/modules'; + +const MOCK_TICKET_SUMMARY_BLOCK_EN: CMS.Model.TicketSummaryBlock.TicketSummaryBlock = { + id: 'ticket-summary-1', + layout: 'horizontal', + infoCards: [ + { + title: 'Under consideration', + icon: 'Clock', + description: 'Tickets under consideration', + variant: 'OPEN', + value: 0, + }, + { + title: 'New response', + icon: 'AlertCircle', + description: 'New response tickets', + variant: 'IN_PROGRESS', + value: 0, + }, + { + title: 'Resolved', + icon: 'CheckCircle', + description: 'Resolved tickets', + variant: 'CLOSED', + value: 0, + }, + ], +}; + +const MOCK_TICKET_SUMMARY_BLOCK_DE: CMS.Model.TicketSummaryBlock.TicketSummaryBlock = { + id: 'ticket-summary-1', + layout: 'horizontal', + infoCards: [ + { + title: 'In Bearbeitung', + icon: 'Clock', + description: 'Tickets in Bearbeitung', + variant: 'OPEN', + value: 0, + }, + { + title: 'Neue Antwort', + icon: 'AlertCircle', + description: 'Tickets mit neuer Antwort', + variant: 'IN_PROGRESS', + value: 0, + }, + { + title: 'Gelöst', + icon: 'CheckCircle', + description: 'Gelöste Tickets', + variant: 'CLOSED', + value: 0, + }, + ], +}; + +const MOCK_TICKET_SUMMARY_BLOCK_PL: CMS.Model.TicketSummaryBlock.TicketSummaryBlock = { + id: 'ticket-summary-1', + layout: 'horizontal', + infoCards: [ + { + title: 'W rozpatrzeniu', + icon: 'Clock', + description: 'Zgłoszenia w rozpatrzeniu', + variant: 'OPEN', + value: 0, + }, + { + title: 'Nowa odpowiedź', + icon: 'AlertCircle', + description: 'Zgłoszenia z nową odpowiedzią', + variant: 'IN_PROGRESS', + value: 0, + }, + { + title: 'Rozwiązane', + icon: 'CheckCircle', + description: 'Rozwiązane zgłoszenia', + variant: 'CLOSED', + value: 0, + }, + ], +}; + +export const mapTicketSummaryBlock = (locale: string): CMS.Model.TicketSummaryBlock.TicketSummaryBlock => { + switch (locale) { + case 'de': + return MOCK_TICKET_SUMMARY_BLOCK_DE; + case 'pl': + return MOCK_TICKET_SUMMARY_BLOCK_PL; + default: + return MOCK_TICKET_SUMMARY_BLOCK_EN; + } +}; diff --git a/packages/integrations/mocked/src/modules/cms/mappers/mocks/pages/notification-list.page.ts b/packages/integrations/mocked/src/modules/cms/mappers/mocks/pages/notification-list.page.ts index b06989c62..24262f849 100644 --- a/packages/integrations/mocked/src/modules/cms/mappers/mocks/pages/notification-list.page.ts +++ b/packages/integrations/mocked/src/modules/cms/mappers/mocks/pages/notification-list.page.ts @@ -23,6 +23,10 @@ export const PAGE_NOTIFICATION_LIST_EN: CMS.Model.Page.Page = { __typename: 'OneColumnTemplate', slots: { main: [ + { + __typename: 'NotificationSummaryBlock', + id: 'notification-summary-1', + }, { __typename: 'NotificationListBlock', id: 'notification-list-1', @@ -61,6 +65,10 @@ export const PAGE_NOTIFICATION_LIST_DE: CMS.Model.Page.Page = { __typename: 'OneColumnTemplate', slots: { main: [ + { + __typename: 'NotificationSummaryBlock', + id: 'notification-summary-1', + }, { __typename: 'NotificationListBlock', id: 'notification-list-1', @@ -99,6 +107,10 @@ export const PAGE_NOTIFICATION_LIST_PL: CMS.Model.Page.Page = { __typename: 'OneColumnTemplate', slots: { main: [ + { + __typename: 'NotificationSummaryBlock', + id: 'notification-summary-1', + }, { __typename: 'NotificationListBlock', id: 'notification-list-1', diff --git a/packages/integrations/mocked/src/modules/cms/mappers/mocks/pages/ticket-list.page.ts b/packages/integrations/mocked/src/modules/cms/mappers/mocks/pages/ticket-list.page.ts index 1298c78c1..46d305848 100644 --- a/packages/integrations/mocked/src/modules/cms/mappers/mocks/pages/ticket-list.page.ts +++ b/packages/integrations/mocked/src/modules/cms/mappers/mocks/pages/ticket-list.page.ts @@ -23,6 +23,10 @@ export const PAGE_TICKET_LIST_EN: CMS.Model.Page.Page = { __typename: 'OneColumnTemplate', slots: { main: [ + { + __typename: 'TicketSummaryBlock', + id: 'ticket-summary-1', + }, { __typename: 'TicketListBlock', id: 'ticket-list-1', @@ -61,6 +65,10 @@ export const PAGE_TICKET_LIST_DE: CMS.Model.Page.Page = { __typename: 'OneColumnTemplate', slots: { main: [ + { + __typename: 'TicketSummaryBlock', + id: 'ticket-summary-1', + }, { __typename: 'TicketListBlock', id: 'ticket-list-1', @@ -99,6 +107,10 @@ export const PAGE_TICKET_LIST_PL: CMS.Model.Page.Page = { __typename: 'OneColumnTemplate', slots: { main: [ + { + __typename: 'TicketSummaryBlock', + id: 'ticket-summary-1', + }, { __typename: 'TicketListBlock', id: 'ticket-list-1', diff --git a/packages/ui/src/components/Cards/InfoCard/InfoCard.tsx b/packages/ui/src/components/Cards/InfoCard/InfoCard.tsx index 0610528c0..ce0cc34c3 100644 --- a/packages/ui/src/components/Cards/InfoCard/InfoCard.tsx +++ b/packages/ui/src/components/Cards/InfoCard/InfoCard.tsx @@ -29,7 +29,7 @@ export const InfoCard: React.FC> = ({ title, value, desc description )} -
{button}
+ {button &&
{button}
} diff --git a/packages/utils/frontend/src/mappings/index.ts b/packages/utils/frontend/src/mappings/index.ts index b03975b6b..a4e261113 100644 --- a/packages/utils/frontend/src/mappings/index.ts +++ b/packages/utils/frontend/src/mappings/index.ts @@ -1,6 +1,8 @@ export * as InvoiceBadge from './invoice-badge'; export * as NotificationBadge from './notification-badge'; +export * as NotificationSummary from './notification-summary'; export * as OrderBadge from './order-badge'; export * as ServicesBadge from './services-badge'; export * as StatusOrder from './status-order'; export * as TicketBadge from './ticket-badge'; +export * as TicketSummary from './ticket-summary'; diff --git a/packages/utils/frontend/src/mappings/notification-summary.ts b/packages/utils/frontend/src/mappings/notification-summary.ts new file mode 100644 index 000000000..8f3ccbdae --- /dev/null +++ b/packages/utils/frontend/src/mappings/notification-summary.ts @@ -0,0 +1,8 @@ +export type NotificationSummaryVariant = 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW'; + +export const notificationSummaryVariants: Record = { + CRITICAL: 'text-destructive', + HIGH: 'text-destructive', + MEDIUM: 'text-muted-foreground', + LOW: 'text-muted-foreground', +}; diff --git a/packages/utils/frontend/src/mappings/ticket-summary.ts b/packages/utils/frontend/src/mappings/ticket-summary.ts new file mode 100644 index 000000000..ba8186f38 --- /dev/null +++ b/packages/utils/frontend/src/mappings/ticket-summary.ts @@ -0,0 +1,7 @@ +export type TicketSummaryVariant = 'OPEN' | 'IN_PROGRESS' | 'CLOSED'; + +export const ticketSummaryVariants: Record = { + OPEN: 'text-muted-foreground', + IN_PROGRESS: 'text-muted-foreground', + CLOSED: 'text-muted-foreground', +}; diff --git a/turbo/generators/config.ts b/turbo/generators/config.ts index b0b87809f..af2fcbc18 100644 --- a/turbo/generators/config.ts +++ b/turbo/generators/config.ts @@ -201,7 +201,7 @@ export default function generator(plop: PlopTypes.NodePlopAPI): void { type: 'modify', path: 'apps/api-harmonization/src/app.module.ts', pattern: /(\/\/ BLOCK IMPORT)/g, - template: `import * as {{pascalCase name}} from '@dxp/blocks.{{kebabCase name}}/api-harmonization';\n// BLOCK IMPORT`, + template: `import * as {{pascalCase name}} from '@o2s/blocks.{{kebabCase name}}/api-harmonization';\n// BLOCK IMPORT`, }, { type: 'modify', @@ -213,7 +213,7 @@ export default function generator(plop: PlopTypes.NodePlopAPI): void { type: 'modify', path: 'apps/api-harmonization/src/modules/page/page.model.ts', pattern: /(\/\/ BLOCK IMPORT)/g, - template: `import * as {{pascalCase name}} from '@dxp/blocks.{{kebabCase name}}/api-harmonization';\n// BLOCK IMPORT`, + template: `import * as {{pascalCase name}} from '@o2s/blocks.{{kebabCase name}}/api-harmonization';\n// BLOCK IMPORT`, }, { type: 'modify', @@ -252,7 +252,7 @@ export default function generator(plop: PlopTypes.NodePlopAPI): void { type: 'modify', path: 'apps/frontend/src/blocks/renderBlocks.tsx', pattern: /(\/\/ BLOCK IMPORT)/g, - template: `import * as {{pascalCase name}} from '@dxp/blocks.{{kebabCase name}}/frontend';\n// BLOCK IMPORT`, + template: `import * as {{pascalCase name}} from '@o2s/blocks.{{kebabCase name}}/frontend';\n// BLOCK IMPORT`, }, { type: 'modify',