Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

1082: Prefill card form from query params #1089

Merged
merged 4 commits into from
Aug 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions administration/src/bp-modules/cards/AddCardsController.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NonIdealState, Spinner } from '@blueprintjs/core'
import { useContext } from 'react'
import { useNavigate } from 'react-router-dom'
import { useContext, useEffect } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'

import { WhoAmIContext } from '../../WhoAmIProvider'
import { Region } from '../../generated/graphql'
Expand Down
26 changes: 20 additions & 6 deletions administration/src/bp-modules/cards/AddCardsForm.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { useCallback, useContext, useEffect, useRef } from 'react'
import { useCallback, useContext, useEffect, useMemo, useRef } from 'react'
import FlipMove from 'react-flip-move'
import { useSearchParams } from 'react-router-dom'
import styled from 'styled-components'

import { CardBlueprint } from '../../cards/CardBlueprint'
import { Region } from '../../generated/graphql'
import { ProjectConfigContext } from '../../project-configs/ProjectConfigContext'
import CreateCardForm from './AddCardForm'
import CardFormButton from './CardFormButton'
import { getHeaders } from './ImportCardsController'

const FormsWrapper = styled(FlipMove)`
flex-wrap: wrap;
Expand Down Expand Up @@ -42,6 +44,23 @@ type CreateCardsFormProps = {

const CreateCardsForm = ({ region, cardBlueprints, setCardBlueprints }: CreateCardsFormProps) => {
const projectConfig = useContext(ProjectConfigContext)
const [searchParams, setSearchParams] = useSearchParams()

useEffect(() => {
if (cardBlueprints.length === 0) {
const headers = getHeaders(projectConfig)
const cardBlueprint = new CardBlueprint('', projectConfig.card, [region])
headers.forEach(header => {
const value = searchParams.get(header)
if (!value) {
return
}
cardBlueprint.setValue(header, value)
})
setCardBlueprints([cardBlueprint])
setSearchParams()
Copy link
Contributor

Choose a reason for hiding this comment

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

Hm it might be better to clean the searchParams after creating barcodes?
On the other hand f.e. a logout doesn't trigger cleaning.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought it makes sense to remove the searchParams imediately to not accidentally refill the form with values. So I'd keep it.

}
}, [cardBlueprints.length, projectConfig, region, searchParams, setCardBlueprints, setSearchParams])
const bottomRef = useRef<HTMLDivElement>(null)

const scrollToBottom = () => {
Expand All @@ -54,11 +73,6 @@ const CreateCardsForm = ({ region, cardBlueprints, setCardBlueprints }: CreateCa
scrollToBottom()
}, [cardBlueprints, projectConfig.card, region, setCardBlueprints])

useEffect(() => {
// create a form on mount
setCardBlueprints([new CardBlueprint('', projectConfig.card, [region])])
}, [projectConfig.card, region, setCardBlueprints])

const removeCardBlueprint = (oldBlueprint: CardBlueprint) => {
setCardBlueprints(cardBlueprints.filter(blueprint => blueprint !== oldBlueprint))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ const InnerImportCardsController = ({ region }: { region: Region }): ReactElemen
headers.forEach(header => {
const idx = csvHeader.indexOf(header)
if (idx === -1) {
// column is missing in csv
return
}
// column is missing in csv
cardBlueprint.setValue(header, line[idx])
})
return cardBlueprint
Expand Down
72 changes: 25 additions & 47 deletions administration/src/cards/CSVCard.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,48 @@
import { Region } from '../generated/graphql'
import { CardConfig } from '../project-configs/getProjectConfig'
import PlainDate from '../util/PlainDate'
import { CardBlueprint } from './CardBlueprint'

type HeaderMap = {
[header: string]: {
getValue: () => string | null
setValue: (value: string) => void
isValid: () => boolean
}
}

class CSVCard extends CardBlueprint {
private readonly headerMap: HeaderMap

constructor(cardConfig: CardConfig, region: Region) {
super('', cardConfig)

this.headerMap = {
[cardConfig.nameColumnName]: {
getValue: () => this.fullName ?? null,
setValue: value => (this.fullName = value),
isValid: () => this.isFullNameValid(),
},
[cardConfig.expiryColumnName]: {
getValue: () => (this.expirationDate ? this.expirationDate.format('dd.MM.yyyy') : null),
setValue: value => this.setExpirationDate(value),
isValid: () => this.isExpirationDateValid() || this.hasInfiniteLifetime(),
},
}

this.extensions.forEach((extension, idx) => {
const columnName = cardConfig.extensionColumnNames[idx]
if (!columnName) {
extension.setInitialState(region)
return
}

this.headerMap[columnName] = {
getValue: () => extension.toString(),
setValue: (value: string) => extension.fromString(value),
isValid: () => extension.isValid(),
}
})
}

setExpirationDate = (value: string) => {
if (value.length === 0) return null
try {
this.expirationDate = PlainDate.fromCustomFormat(value, 'dd.MM.yyyy')
} catch (error) {
this.expirationDate = null
console.error("Could not parse date from string '" + value + "' with format dd.MM.yyyy.", error)
getValue(key: string): string | null {
switch (key) {
case this.cardConfig.nameColumnName:
return this.fullName
case this.cardConfig.expiryColumnName:
return this.expirationDate ? this.expirationDate.format('dd.MM.yyyy') : null
default:
const extensionIdx = this.cardConfig.extensionColumnNames.indexOf(key)
if (extensionIdx === -1) {
return null
}
return this.extensions[extensionIdx].toString()
}
}

setValue = (header: string, value: string) => {
this.headerMap[header]?.setValue(value)
}

getValue = (header: string) => {
return this.headerMap[header]?.getValue() ?? null
}

isValueValid = (header: string): boolean => {
return this.headerMap[header]?.isValid() ?? false
isValueValid = (key: string): boolean => {
switch (key) {
case this.cardConfig.nameColumnName:
return this.isFullNameValid()
case this.cardConfig.expiryColumnName:
return this.isExpirationDateValid() || this.hasInfiniteLifetime()
default:
const extensionIdx = this.cardConfig.extensionColumnNames.indexOf(key)
if (extensionIdx === -1) {
return false
}
return this.extensions[extensionIdx].isValid()
}
}
}

Expand Down
28 changes: 28 additions & 0 deletions administration/src/cards/CardBlueprint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ export class CardBlueprint {
fullName: string
expirationDate: PlainDate | null
extensions: ExtensionInstance[]
cardConfig: CardConfig

constructor(fullName: string, cardConfig: CardConfig, initParams?: Parameters<CardBlueprint['initialize']>) {
this.cardConfig = cardConfig
this.fullName = fullName
this.expirationDate =
cardConfig.defaultValidity && initParams
Expand All @@ -44,6 +46,23 @@ export class CardBlueprint {
}
}

setValue(key: string, value: string): void {
switch (key) {
case this.cardConfig.nameColumnName:
this.fullName = value
break
case this.cardConfig.expiryColumnName:
this.setExpirationDate(value)
break
default:
const extensionIdx = this.cardConfig.extensionColumnNames.indexOf(key)
if (extensionIdx === -1) {
return
}
this.extensions[extensionIdx].fromString(value)
}
}

initialize(region: Region) {
this.extensions.forEach(ext => {
if (ext instanceof RegionExtension) ext.setInitialState(region)
Expand All @@ -69,6 +88,15 @@ export class CardBlueprint {
return this.expirationDate !== null && this.expirationDate.isAfter(today)
}

setExpirationDate(value: string) {
if (value.length === 0) return
try {
this.expirationDate = PlainDate.fromCustomFormat(value, 'dd.MM.yyyy')
} catch (error) {
console.error("Could not parse date from string '" + value + "' with format dd.MM.yyyy.", error)
}
}

isValid(): boolean {
return (
// Name valid
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ class NuernbergPassIdExtension extends Extension<NuernbergPassIdState, null> {
}

fromString(state: string) {
this.state = { nuernbergPassId: parseInt(state, 10) }
const nuernbergPassId = parseInt(state, 10)
this.state = !isNaN(nuernbergPassId) ? { nuernbergPassId } : null
}

toString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ class NuernbergPassNumberExtension extends Extension<NuernbergPassNumberState, n
}

fromString(state: string) {
this.state = { passNumber: parseInt(state, 10) }
const passNumber = parseInt(state, 10)
this.state = !isNaN(passNumber) ? { passNumber } : null
}

toString() {
Expand Down
2 changes: 1 addition & 1 deletion administration/src/cards/extensions/extensions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { PartialMessage } from '@bufbuild/protobuf'
import { ReactElement } from 'react'

import { CardExtensions } from '../../generated/card_pb'
import AddressExtensions from './AddressFieldExtensons'
import AddressExtensions from './AddressFieldExtensions'
import BavariaCardTypeExtension from './BavariaCardTypeExtension'
import BirthdayExtension from './BirthdayExtension'
import NuernbergPassIdExtension from './NuernbergPassIdExtension'
Expand Down
2 changes: 1 addition & 1 deletion administration/src/project-configs/nuernberg/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import AddressExtensions from '../../cards/extensions/AddressFieldExtensons'
import AddressExtensions from '../../cards/extensions/AddressFieldExtensions'
import BirthdayExtension from '../../cards/extensions/BirthdayExtension'
import NuernbergPassIdExtension from '../../cards/extensions/NuernbergPassIdExtension'
import NuernbergPassNumberExtension from '../../cards/extensions/NuernbergPassNumberExtension'
Expand Down
2 changes: 1 addition & 1 deletion administration/src/project-configs/nuernberg/pdf.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { PDFForm, rgb } from 'pdf-lib'

import AddressExtensions from '../../cards/extensions/AddressFieldExtensons'
import AddressExtensions from '../../cards/extensions/AddressFieldExtensions'
import NuernbergPassIdExtension from '../../cards/extensions/NuernbergPassIdExtension'
import { findExtension } from '../../cards/extensions/extensions'
import { InfoParams } from '../../cards/pdf/PdfTextElement'
Expand Down