Repository: digitalfabrik/entitlementcard
Pull request #2167: 1753: Add prime minister badge to applications view
Author: login: seluianova
Labels: name: tested
Created: 2025-05-08T17:03:33Z | Merged: 2025-05-23T15:08:12Z

### Short Description
Add prime minister badge to applications view
<img width="987" alt="image" src="https://github.com/user-attachments/assets/3280fd95-a535-47f8-85e3-52586b8cf4f9" />

### Proposed Changes
- add check if application has goldenCardHonoredByMinisterPresidentEntitlement type
- add corresponding metadata for the new label

### Side Effects
no

### Testing
1. Create application Goldene Ehrenamtskarte --> Ich bin Inhaber:in des Ehrenzeichens für Verdienste im Ehrenamt des Bayerischen Ministerpräsidenten
2. Login as region admin, check applications view

### Resolved Issues
Fixes: #1753



Diff:
diff --git a/administration/src/bp-modules/applications/ApplicationCard.tsx b/administration/src/bp-modules/applications/ApplicationCard.tsx
index 901d0782a2..b60af59898 100644
--- a/administration/src/bp-modules/applications/ApplicationCard.tsx
+++ b/administration/src/bp-modules/applications/ApplicationCard.tsx
@@ -21,10 +21,11 @@ import formatDateWithTimezone from '../../util/formatDate'
 import getApiBaseUrl from '../../util/getApiBaseUrl'
 import { useAppToaster } from '../AppToaster'
 import type { Application } from './ApplicationsOverview'
+import type { JsonField } from './JsonFieldView'
 import JsonFieldView, { findValue } from './JsonFieldView'
-import type { GeneralJsonField, JsonField } from './JsonFieldView'
 import NoteDialogController from './NoteDialogController'
-import PreVerifiedQuickIndicator, { PreVerifiedQuickIndicatorType } from './PreVerifiedQuickIndicator'
+import { getPreVerifiedEntitlementType } from './PreVerifiedEntitlementType'
+import PreVerifiedQuickIndicator from './PreVerifiedQuickIndicator'
 import VerificationsQuickIndicator from './VerificationsQuickIndicator'
 import VerificationsView from './VerificationsView'
 import { printAwareCss } from './constants'
@@ -105,38 +106,14 @@ type RightElementProps = {
 }
 
 const RightElement = ({ jsonField, application }: RightElementProps): ReactElement => {
-  const isJuleicaEntitlementType = (): boolean => {
-    const applicationDetails = findValue(jsonField, 'applicationDetails', 'Array') ?? jsonField
-    const blueCardJuleicaEntitlement = findValue(applicationDetails, 'blueCardJuleicaEntitlement', 'Array')
-    return !!blueCardJuleicaEntitlement
-  }
-
-  const isPreVerifiedByOrganization = (): boolean => {
-    const applicationDetails = findValue(jsonField, 'applicationDetails', 'Array') ?? jsonField
-    const workAtOrganizationsEntitlement =
-      findValue(applicationDetails, 'blueCardWorkAtOrganizationsEntitlement', 'Array')?.value ?? []
-
-    return workAtOrganizationsEntitlement.some(
-      (entitlement: GeneralJsonField) =>
-        Array.isArray(entitlement.value) &&
-        entitlement.value.some(
-          (organizationField: GeneralJsonField) =>
-            organizationField.name === 'isAlreadyVerified' && organizationField.value === true
-        )
-    )
-  }
-
-  const isPreVerified = isJuleicaEntitlementType() || isPreVerifiedByOrganization()
+  const applicationDetails = findValue(jsonField, 'applicationDetails', 'Array') ?? jsonField
+  const preVerifiedEntitlementType = getPreVerifiedEntitlementType(applicationDetails)
 
   return (
     <RightElementContainer>
       {!!application.note && application.note.trim() && <Icon icon='annotation' intent='none' />}
-      {isPreVerified ? (
-        <PreVerifiedQuickIndicator
-          type={
-            isJuleicaEntitlementType() ? PreVerifiedQuickIndicatorType.Juleica : PreVerifiedQuickIndicatorType.Verein360
-          }
-        />
+      {preVerifiedEntitlementType !== undefined ? (
+        <PreVerifiedQuickIndicator type={preVerifiedEntitlementType} />
       ) : (
         <VerificationsQuickIndicator verifications={application.verifications} />
       )}
diff --git a/administration/src/bp-modules/applications/PreVerifiedEntitlementType.ts b/administration/src/bp-modules/applications/PreVerifiedEntitlementType.ts
new file mode 100644
index 0000000000..a35083110e
--- /dev/null
+++ b/administration/src/bp-modules/applications/PreVerifiedEntitlementType.ts
@@ -0,0 +1,31 @@
+import { GeneralJsonField, type JsonField, findValue } from './JsonFieldView'
+
+export const preVerifiedEntitlements = {
+  Juleica: 'blueCardJuleicaEntitlement',
+  Verein360: 'blueCardWorkAtOrganizationsEntitlement',
+  HonoredByMinisterPresident: 'goldenCardHonoredByMinisterPresidentEntitlement',
+} as const
+
+export type PreVerifiedEntitlementType = (typeof preVerifiedEntitlements)[keyof typeof preVerifiedEntitlements]
+
+const verein360Matcher = (entitlement?: GeneralJsonField): boolean => {
+  const entitlements = entitlement !== undefined && Array.isArray(entitlement.value) ? entitlement.value : []
+  return entitlements.some(
+    (item: GeneralJsonField) =>
+      Array.isArray(item.value) &&
+      item.value.some((subItem: GeneralJsonField) => subItem.name === 'isAlreadyVerified' && subItem.value === true)
+  )
+}
+
+const hasPreVerifiedEntitlement = (
+  applicationDetails: JsonField<'Array'>,
+  field: PreVerifiedEntitlementType
+): boolean => {
+  const entitlement = findValue(applicationDetails, field, 'Array')
+  return field === preVerifiedEntitlements.Verein360 ? verein360Matcher(entitlement) : entitlement !== undefined
+}
+
+export const getPreVerifiedEntitlementType = (
+  applicationDetails: JsonField<'Array'>
+): PreVerifiedEntitlementType | undefined =>
+  Object.values(preVerifiedEntitlements).find(type => hasPreVerifiedEntitlement(applicationDetails, type))
diff --git a/administration/src/bp-modules/applications/PreVerifiedQuickIndicator.tsx b/administration/src/bp-modules/applications/PreVerifiedQuickIndicator.tsx
index ee2763576b..7e40f960b5 100644
--- a/administration/src/bp-modules/applications/PreVerifiedQuickIndicator.tsx
+++ b/administration/src/bp-modules/applications/PreVerifiedQuickIndicator.tsx
@@ -3,35 +3,36 @@ import React, { memo } from 'react'
 import { useTranslation } from 'react-i18next'
 import styled from 'styled-components'
 
+import { PreVerifiedEntitlementType, preVerifiedEntitlements } from './PreVerifiedEntitlementType'
 import { UnFocusedDiv } from './VerificationsQuickIndicator'
 import VerificationIndicator from './components/VerificationIndicator'
 import { VerificationStatus } from './constants'
 
-export enum PreVerifiedQuickIndicatorType {
-  Juleica,
-  Verein360,
-}
-
 type PreVerifiedLabelMetaData = {
   backgroundColor: string
   fontColor: string
   labelText: string
 }
 
-const preVerifiedLabelMetaData: Record<PreVerifiedQuickIndicatorType, PreVerifiedLabelMetaData> = {
-  [PreVerifiedQuickIndicatorType.Juleica]: {
+const preVerifiedLabelMetaData: Record<PreVerifiedEntitlementType, PreVerifiedLabelMetaData> = {
+  [preVerifiedEntitlements.Juleica]: {
     backgroundColor: '#bfd4f2',
     fontColor: '#000000',
     labelText: 'Juleica',
   },
-  [PreVerifiedQuickIndicatorType.Verein360]: {
+  [preVerifiedEntitlements.Verein360]: {
     backgroundColor: '#0366D6',
     fontColor: '#ffffff',
     labelText: 'Verein360',
   },
+  [preVerifiedEntitlements.HonoredByMinisterPresident]: {
+    backgroundColor: '#FBCA01',
+    fontColor: '#000000',
+    labelText: 'Ehrenzeichen',
+  },
 }
 
-const PreVerifiedLabel = styled.span<{ type: PreVerifiedQuickIndicatorType }>`
+const PreVerifiedLabel = styled.span<{ type: PreVerifiedEntitlementType }>`
   padding: 6px 12px;
   font-size: 14px;
   font-weight: 600;
@@ -44,7 +45,7 @@ const PreVerifiedLabel = styled.span<{ type: PreVerifiedQuickIndicatorType }>`
   white-space: nowrap;
 `
 
-const PreVerifiedQuickIndicator = memo(({ type }: { type: PreVerifiedQuickIndicatorType }) => {
+const PreVerifiedQuickIndicator = memo(({ type }: { type: PreVerifiedEntitlementType }) => {
   const { t } = useTranslation('applicationsOverview')
   return (
     <Tooltip
diff --git a/administration/src/bp-modules/applications/__tests__/PreVerifiedQuickIndicator.test.tsx b/administration/src/bp-modules/applications/__tests__/PreVerifiedQuickIndicator.test.tsx
index dc34ba2c93..e9c993a18f 100644
--- a/administration/src/bp-modules/applications/__tests__/PreVerifiedQuickIndicator.test.tsx
+++ b/administration/src/bp-modules/applications/__tests__/PreVerifiedQuickIndicator.test.tsx
@@ -1,7 +1,8 @@
 import React from 'react'
 
 import { renderWithTranslation } from '../../../testing/render'
-import PreVerifiedQuickIndicator, { PreVerifiedQuickIndicatorType } from '../PreVerifiedQuickIndicator'
+import { PreVerifiedEntitlementType, preVerifiedEntitlements } from '../PreVerifiedEntitlementType'
+import PreVerifiedQuickIndicator from '../PreVerifiedQuickIndicator'
 
 jest.mock('@blueprintjs/core', () => ({
   ...jest.requireActual('@blueprintjs/core'),
@@ -9,18 +10,14 @@ jest.mock('@blueprintjs/core', () => ({
 }))
 
 describe('PreVerifiedQuickIndicator', () => {
-  it('should show juleica label if the particular type was set', async () => {
-    const { getByText } = renderWithTranslation(
-      <PreVerifiedQuickIndicator type={PreVerifiedQuickIndicatorType.Juleica} />
-    )
-    expect(getByText('Juleica')).toBeTruthy()
-  })
-
-  it('should show verein360 label if the particular type was set', async () => {
-    const { getByText } = renderWithTranslation(
-      <PreVerifiedQuickIndicator type={PreVerifiedQuickIndicatorType.Verein360} />
-    )
+  const cases: [string, PreVerifiedEntitlementType][] = [
+    ['Juleica', preVerifiedEntitlements.Juleica],
+    ['Verein360', preVerifiedEntitlements.Verein360],
+    ['Ehrenzeichen', preVerifiedEntitlements.HonoredByMinisterPresident],
+  ]
 
-    expect(getByText('Verein360')).toBeTruthy()
+  it.each(cases)('should show %s label when type is %s', (expectedLabel, type) => {
+    const { getByText } = renderWithTranslation(<PreVerifiedQuickIndicator type={type} />)
+    expect(getByText(expectedLabel)).toBeInTheDocument()
   })
 })


Reviews:
user: login: andrew8er
body: Some suggestions.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T11:48:07Z
user: login: andrew8er
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T16:43:52Z
user: login: f1sh1918
body: It works nicely @seluianova 👍 
I found the code quite hard to understand tbh and added some suggestions to simplify that. I may have overseen sth but afaics i can see everything works with my suggestions.
With these suggestions the code should be reduced by 5 lines and i find it easier to understand. Even feel free to reject this
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T07:53:05Z
user: login: f1sh1918
body: Looks good 👍 
Tested on chrome
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T14:51:21Z

Review comments:
diff_hunk: @@ -0,0 +1,34 @@
+import { GeneralJsonField, type JsonField, findValue } from './JsonFieldView'
+
+export const enum PreVerifiedEntitlementType {
+  Juleica = 'blueCardJuleicaEntitlement',
+  Verein360 = 'blueCardWorkAtOrganizationsEntitlement',
+  HonoredByMinisterPresident = 'goldenCardHonoredByMinisterPresidentEntitlement',
+}
path: administration/src/bp-modules/applications/PreVerifiedEntitlementType.tsx
user: login: andrew8er
body: 🔧 Since enums are a bit problematic in Typescript, I would rather use an object with an associated type here:
```ts
export const preVerifiedEntitlements = {
  Juleica: 'blueCardJuleicaEntitlement',
  Verein360: 'blueCardWorkAtOrganizationsEntitlement',
  HonoredByMinisterPresident: 'goldenCardHonoredByMinisterPresidentEntitlement',
} as const
/** Values of {preVerifiedEntitlements} */
export type PreVerifiedEntitlementType = typeof preVerifiedEntitlements[keyof typeof preVerifiedEntitlements]
``` 

You can the refer to the actual values with e.g. `preVerifiedEntitlements.Juleica`.
created_at: 2025-05-13T11:39:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,34 @@
+import { GeneralJsonField, type JsonField, findValue } from './JsonFieldView'
+
+export const enum PreVerifiedEntitlementType {
+  Juleica = 'blueCardJuleicaEntitlement',
+  Verein360 = 'blueCardWorkAtOrganizationsEntitlement',
+  HonoredByMinisterPresident = 'goldenCardHonoredByMinisterPresidentEntitlement',
+}
+
+const hasPreVerifiedEntitlement = (
+  applicationDetails: JsonField<'Array'>,
+  field: PreVerifiedEntitlementType
+): boolean => {
+  const entitlement = findValue(applicationDetails, field, 'Array')
+  if (field === PreVerifiedEntitlementType.Verein360) {
+    const entitlements = entitlement?.value ?? []
+    return entitlements.some(
+      (item: GeneralJsonField) =>
+        Array.isArray(item.value) &&
+        item.value.some((subItem: GeneralJsonField) => subItem.name === 'isAlreadyVerified' && subItem.value === true)
+    )
+  }
+  return !!entitlement
path: administration/src/bp-modules/applications/PreVerifiedEntitlementType.tsx
user: login: andrew8er
body: 🔧 I would prefer a more explicit form like `entitlement != undefined` here, as this better conveys the actual test. The double bang relies on JavaScript's implicit type conversion and this should be avoided like the plague.
created_at: 2025-05-13T11:43:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,34 @@
+import { GeneralJsonField, type JsonField, findValue } from './JsonFieldView'
+
+export const enum PreVerifiedEntitlementType {
+  Juleica = 'blueCardJuleicaEntitlement',
+  Verein360 = 'blueCardWorkAtOrganizationsEntitlement',
+  HonoredByMinisterPresident = 'goldenCardHonoredByMinisterPresidentEntitlement',
+}
+
+const hasPreVerifiedEntitlement = (
+  applicationDetails: JsonField<'Array'>,
+  field: PreVerifiedEntitlementType
+): boolean => {
+  const entitlement = findValue(applicationDetails, field, 'Array')
+  if (field === PreVerifiedEntitlementType.Verein360) {
+    const entitlements = entitlement?.value ?? []
+    return entitlements.some(
+      (item: GeneralJsonField) =>
+        Array.isArray(item.value) &&
+        item.value.some((subItem: GeneralJsonField) => subItem.name === 'isAlreadyVerified' && subItem.value === true)
+    )
+  }
+  return !!entitlement
+}
+
+export const getPreVerifiedEntitlementType = (
+  applicationDetails: JsonField<'Array'>
+): PreVerifiedEntitlementType | undefined => {
+  const entitlementTypes: PreVerifiedEntitlementType[] = [
+    PreVerifiedEntitlementType.Juleica,
+    PreVerifiedEntitlementType.Verein360,
+    PreVerifiedEntitlementType.HonoredByMinisterPresident,
+  ]
path: administration/src/bp-modules/applications/PreVerifiedEntitlementType.tsx
user: login: andrew8er
body: 🔧 When using an object instead of an enum as suggested above, this object becomes redundant:

```ts
export const getPreVerifiedEntitlementType = (
  applicationDetails: JsonField<'Array'>
): PreVerifiedEntitlementType | undefined =>
  Object.values(preVerifiedEntitlements).find(type => hasPreVerifiedEntitlement(applicationDetails, type))
```
created_at: 2025-05-13T11:45:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,36 @@
+import { GeneralJsonField, type JsonField, findValue } from './JsonFieldView'
+
+export const preVerifiedEntitlements = {
+  Juleica: 'blueCardJuleicaEntitlement',
+  Verein360: 'blueCardWorkAtOrganizationsEntitlement',
+  HonoredByMinisterPresident: 'goldenCardHonoredByMinisterPresidentEntitlement',
+} as const
+
+export type PreVerifiedEntitlementType = (typeof preVerifiedEntitlements)[keyof typeof preVerifiedEntitlements]
+
+type MatcherFn = (entitlement?: GeneralJsonField) => boolean
+
+const defaultMatcher: MatcherFn = entitlement => entitlement !== undefined
+
+const verein360Matcher: MatcherFn = entitlement => {
+  const entitlements = Array.isArray(entitlement?.value) ? entitlement.value : []
path: administration/src/bp-modules/applications/PreVerifiedEntitlementType.tsx
user: login: f1sh1918
body: ```suggestion
  const entitlements = entitlement !== undefined && Array.isArray(entitlement.value) ? entitlement.value : []
```
🔧  Lacking undefined check
created_at: 2025-05-21T07:14:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,36 @@
+import { GeneralJsonField, type JsonField, findValue } from './JsonFieldView'
+
+export const preVerifiedEntitlements = {
+  Juleica: 'blueCardJuleicaEntitlement',
+  Verein360: 'blueCardWorkAtOrganizationsEntitlement',
+  HonoredByMinisterPresident: 'goldenCardHonoredByMinisterPresidentEntitlement',
+} as const
+
+export type PreVerifiedEntitlementType = (typeof preVerifiedEntitlements)[keyof typeof preVerifiedEntitlements]
+
+type MatcherFn = (entitlement?: GeneralJsonField) => boolean
+
+const defaultMatcher: MatcherFn = entitlement => entitlement !== undefined
+
+const verein360Matcher: MatcherFn = entitlement => {
+  const entitlements = Array.isArray(entitlement?.value) ? entitlement.value : []
+  return entitlements.some(
+    (item: GeneralJsonField) =>
+      Array.isArray(item.value) &&
+      item.value.some((subItem: GeneralJsonField) => subItem.name === 'isAlreadyVerified' && subItem.value === true)
+  )
+}
+
+const hasPreVerifiedEntitlement = (
+  applicationDetails: JsonField<'Array'>,
+  field: PreVerifiedEntitlementType
+): boolean => {
+  const entitlement = findValue(applicationDetails, field, 'Array')
+  const matcher = field === preVerifiedEntitlements.Verein360 ? verein360Matcher : defaultMatcher
path: administration/src/bp-modules/applications/PreVerifiedEntitlementType.tsx
user: login: f1sh1918
body: ❓ i find the code very hard to understand. Is there any reason why we need this `MatcherFn` and `defaultMatcher`?
Shouldn't this also work with a one liner?
```
return field === preVerifiedEntitlements.Verein360 ? verein360Matcher(entitlement) : entitlement !== undefined
```
created_at: 2025-05-21T07:20:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,36 @@
+import { GeneralJsonField, type JsonField, findValue } from './JsonFieldView'
+
+export const preVerifiedEntitlements = {
+  Juleica: 'blueCardJuleicaEntitlement',
+  Verein360: 'blueCardWorkAtOrganizationsEntitlement',
+  HonoredByMinisterPresident: 'goldenCardHonoredByMinisterPresidentEntitlement',
+} as const
+
+export type PreVerifiedEntitlementType = (typeof preVerifiedEntitlements)[keyof typeof preVerifiedEntitlements]
+
+type MatcherFn = (entitlement?: GeneralJsonField) => boolean
+
+const defaultMatcher: MatcherFn = entitlement => entitlement !== undefined
+
+const verein360Matcher: MatcherFn = entitlement => {
path: administration/src/bp-modules/applications/PreVerifiedEntitlementType.tsx
user: login: f1sh1918
body: ```suggestion
const verein360Matcher = (entitlement?: GeneralJsonField): boolean => {
```
🔧 i think this improves readability
created_at: 2025-05-21T07:47:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,36 @@
+import { GeneralJsonField, type JsonField, findValue } from './JsonFieldView'
+
+export const preVerifiedEntitlements = {
+  Juleica: 'blueCardJuleicaEntitlement',
+  Verein360: 'blueCardWorkAtOrganizationsEntitlement',
+  HonoredByMinisterPresident: 'goldenCardHonoredByMinisterPresidentEntitlement',
+} as const
+
+export type PreVerifiedEntitlementType = (typeof preVerifiedEntitlements)[keyof typeof preVerifiedEntitlements]
+
+type MatcherFn = (entitlement?: GeneralJsonField) => boolean
path: administration/src/bp-modules/applications/PreVerifiedEntitlementType.tsx
user: login: f1sh1918
body: 🔧 not needed if the other suggestions are used
created_at: 2025-05-21T07:47:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,36 @@
+import { GeneralJsonField, type JsonField, findValue } from './JsonFieldView'
+
+export const preVerifiedEntitlements = {
+  Juleica: 'blueCardJuleicaEntitlement',
+  Verein360: 'blueCardWorkAtOrganizationsEntitlement',
+  HonoredByMinisterPresident: 'goldenCardHonoredByMinisterPresidentEntitlement',
+} as const
+
+export type PreVerifiedEntitlementType = (typeof preVerifiedEntitlements)[keyof typeof preVerifiedEntitlements]
+
+type MatcherFn = (entitlement?: GeneralJsonField) => boolean
+
+const defaultMatcher: MatcherFn = entitlement => entitlement !== undefined
path: administration/src/bp-modules/applications/PreVerifiedEntitlementType.tsx
user: login: f1sh1918
body: 🔧 not needed if my suggestions are used
created_at: 2025-05-21T07:48:20Z
author_association: CONTRIBUTOR

PR comments:
user: login: seluianova
created_at: 2025-05-20T12:32:49Z
body: @andrew8er thanks for the feedback, I have removed the enum, please have a look now
author_association: CONTRIBUTOR
user: login: seluianova
created_at: 2025-05-23T13:53:17Z
body: @f1sh1918 I agree with your suggestions, pls have a look now
author_association: CONTRIBUTOR
user: login: connium
created_at: 2025-06-25T09:58:02Z
body: 🧪 Tested
- **Browser/Device**: Firefox 140.0, 137.0
- ✅ Applications with an "Ehrenzeichen des Ministerpräsidenten" are shown as pre-verified (green checkmark)
- ✅ Applications with an "Ehrenzeichen des Ministerpräsidenten" show a chip with title "Ehrenzeichen"

author_association: NONE