Compare commits

..

26 commits

Author SHA1 Message Date
Markus Johansen
47434c80d9 🐛 Fjernet ubrukt import av kommentartekstfelt 2023-08-11 10:27:52 +02:00
Amalie
518d1214f5
Opprydding backend (#35)
*  Backend støtter mulighet for å slette innmeldte feilmeldinger

Co-authored-by: Sindre Kjelsrud <sindre.kjelsrud@nav.no>

*  Knapp i redigeringsverktøy for sletting av feil

* 🐛 Fikset routing bug, Delete was not allowed, og feil endepunkt i frontend

* ♻️  Sørger for at http-statuskodene matcher request

* ♻️  Rydder opp i response-meldinger

---------

Co-authored-by: Sindre Kjelsrud <sindre.kjelsrud@nav.no>
Co-authored-by: Markus Johansen <markus.aleksander.rakil.johansen@nav.no>
Co-authored-by: Markus A. R. Johansen <90006516+J0hans1@users.noreply.github.com>
2023-08-11 09:57:27 +02:00
Markus A. R. Johansen
5c1a7d173b
Redigering av kommentarer (#36)
Co-authored-by: Sindre Kjelsrud <sindre.kjelsrud@nav.no>
2023-08-11 09:55:18 +02:00
Markus A. R. Johansen
900422a4e0
Slett feilmeldinger (#34)
*  Backend støtter mulighet for å slette innmeldte feilmeldinger

*  Knapp i redigeringsverktøy for sletting av feil

* 🐛 Fikset routing bug, Delete was not allowed, og feil endepunkt i frontend

* ️ Refresher mainpage ved sletting av feil

---------

Co-authored-by: Amalie Mansåker <amalie.erdal.mansaker@nav.no>
Co-authored-by: Sindre Kjelsrud <sindre.kjelsrud@nav.no>
2023-08-11 09:53:09 +02:00
Markus Johansen
df3ce139c2 🗃️ Tømmer databasen før presentasjon
Co-authored-by: Sindre Kjelsrud <sindre.kjelsrud@nav.no>
Co-authored-by: Amalie Mansåker <amalie.erdal.mansaker@nav.no>
2023-08-09 14:42:30 +02:00
Sindre Kjelsrud
8e8f6e8a92 📝 Legger til dokumentasjon for prosjektet
Co-authored-by: Markus A. R. Johansen <markus.aleksander.rakil.johansen@nav.no>
Co-authored-by: Amalie Erdal Mansåker <amalie.erdal.mansaker@nav.no>
2023-08-07 16:36:10 +02:00
Markus A. R. Johansen
35d623cd96
Frontend opprydding (#33)
* se forrige commit

* 🎨📝 Kommentar komponenter i egen fil, og dokumentering av modal, kommentar, header, feilmeldingsinnhold og kortkonteiner

* 📝 Dokumentert redigeringsverktøy, skillelinje og tagbar

* 📝♻️  Forenklet henting av alle feil og dokumenterte resterende frontend
2023-08-07 12:35:55 +02:00
Sindre Kjelsrud
d75e646140 🐛 Fikser oppdatering av feil-bug, manglet aktorid
Co-authored-by: Amalie Erdal Mansåker <amalie.erdal.mansaker@nav.no>
2023-08-07 12:00:28 +02:00
Amalie Mansåker
8b5cc0c565 🔥 Cleanup 2023-08-07 10:32:47 +02:00
Amalie Mansåker
d97900e872 🐛 Dato som vises på et feilkort stemmer nå med når feilmelding er opprettet 2023-08-07 08:56:20 +02:00
dependabot[bot]
2cb5a308ab
Bump tough-cookie and @cypress/request in /frontend (#32)
Bumps [tough-cookie](https://github.com/salesforce/tough-cookie) and [@cypress/request](https://github.com/cypress-io/request). These dependencies needed to be updated together.

Updates `tough-cookie` from 2.5.0 to 4.1.3
- [Release notes](https://github.com/salesforce/tough-cookie/releases)
- [Changelog](https://github.com/salesforce/tough-cookie/blob/master/CHANGELOG.md)
- [Commits](https://github.com/salesforce/tough-cookie/compare/v2.5.0...v4.1.3)

Updates `@cypress/request` from 2.88.11 to 2.88.12
- [Release notes](https://github.com/cypress-io/request/releases)
- [Changelog](https://github.com/cypress-io/request/blob/master/CHANGELOG.md)
- [Commits](https://github.com/cypress-io/request/compare/v2.88.11...v2.88.12)

---
updated-dependencies:
- dependency-name: tough-cookie
  dependency-type: indirect
- dependency-name: "@cypress/request"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-04 15:20:20 +02:00
Amalie Mansåker
1934a94d32 ♻️ Lager hjelpefunksjon for å skrive localdatetimes i januar
Co-authored-by: Hege Haavaldsen <hege.haavaldsen@nav.no>
2023-08-04 15:18:47 +02:00
Amalie Mansåker
e8d27f85f2 ♻️ Refaktorere tester og kode
Co-authored-by: Hege Haavaldsen <hege.haavaldsen@nav.no>
2023-08-04 15:04:01 +02:00
Sindre Kjelsrud
164c554cbc 🔀 Merge branch 'main' of github.com:navikt/helse-sprik 2023-08-04 12:50:24 +02:00
Sindre Kjelsrud
75c3bf6cbc Legger til testing av søkefunksjonalitet 2023-08-04 12:48:36 +02:00
Sindre Kjelsrud
0ca843f384 Legger til testing av landingside
(glemte selve filen ved forrige commit...)
2023-08-04 12:48:00 +02:00
Sindre Kjelsrud
de91e13727 Legger til testing for å lage feilmelding 2023-08-04 12:46:58 +02:00
Markus A. R. Johansen
13a3ee415d
Aktor id backend (#31)
* 🚧 Frontend sender AktørID til backend

WIP: Database og backend takler ikke lange tall (må finne alternativ datatype til int)


* 🐛 Endret datatype på aktorid i database til BIGINT

* 🍱 Lagring av valgfri aktørid i database

*  Aktørid vises på feilmeldinginnhold i frontend

* 🎨 Fjernet ubrukte logs, rettet grammatikk, fikset typing på aktorid frontend

---------

Co-authored-by: Amalie Mansåker <amalie.erdal.mansaker@nav.no>
2023-08-04 12:41:24 +02:00
Sindre Kjelsrud
3962f3818c 🍱 Legger til cypress-axe 2023-08-04 12:18:47 +02:00
Sindre Kjelsrud
2fb4a478bd Oppdaterer landingsside tester 2023-08-04 12:15:18 +02:00
Sindre Kjelsrud
f303081d4e ️ Forbedrer semantikk ved å legge til Heading1 og aria-label på svg 2023-08-04 12:10:42 +02:00
Sindre Kjelsrud
ebd9e0105b 🐛 Sender kommentar ved oppdatering av feil, fikset 404
Co-authored-by: Markus A. R. Johansen <markus.aleksander.rakil.johansen@nav.no>
2023-08-03 15:32:27 +02:00
Markus A. R. Johansen
dabf031091
Persistent kommentar i frontend (#29)
* 🐛 Fikset build error pga ubrukt state

Co-authored-by: Sindre Kjelsrud <sindre.kjelsrud@nav.no>

*  Notatlapper er persistente på frontend feilkort

Co-authored-by: Sindre Kjelsrud <sindre.kjelsrud@nav.no>

---------

Co-authored-by: Sindre Kjelsrud <sindre.kjelsrud@nav.no>
2023-08-03 15:18:07 +02:00
Markus Johansen
255541de6c 🐛 Fikset build error pga ubrukt state
Co-authored-by: Sindre Kjelsrud <sindre.kjelsrud@nav.no>
2023-08-03 15:00:38 +02:00
Markus Johansen
75212e1f47 Lagt til mulighet for å legge til kategorier og aktørID på innmelding 2023-08-03 14:53:10 +02:00
Sid
65cd2cb6ae
Fiks kommentar backend (#27)
* 🚧 Påbegynt kommentarhåndtering

WIP: Kommentarer vises ikke i frontend

Co-authored-by: Amalie Erdal Mansåker <amalie.erdal.mansaker@nav.no>

* 💄🐛 Bruker switch til å endre om sak haster i redigering

BUG: La også til kommentar-field for å unngå BAD_REQUEST på Posting av nye feil

* 💄 Gjør små styling forbedringer, forbedret UU ved å lage skillelinjer i forms

* ️ La til heading på Switch i redigeringsskjema

*  Saker som meldes inn kan merkes som haster fra innmelding (#24)

Co-authored-by: Sid <93219711+SindreKjelsrud@users.noreply.github.com>

* Kommentarer frontend (#26)

* 💄 Laget mulighet for å skrive inn og submitte kommentar

* 💄 Gjorde kommentar til en "notat-klistrelapp"

* 🚑 Frontend bruker oppdaterkommentar endepunktet

* 🚧 Påbegynt kommentarhåndtering

WIP: Kommentarer vises ikke i frontend

Co-authored-by: Amalie Erdal Mansåker <amalie.erdal.mansaker@nav.no>

* 🔥 Fjerner ubrukte felt

Co-authored-by: Sindre Kjelsrud <sindre.kjelsrud@nav.no>

*  Kommentarer til feilmeldinger vises i frontend

Vises foreløpig ikke riktig i frontend, men det vises

Co-authored-by: Sindre Kjelsrud <sindre.kjelsrud@nav.no>

---------

Co-authored-by: Amalie Erdal Mansåker <amalie.erdal.mansaker@nav.no>
Co-authored-by: Markus Johansen <markus.aleksander.rakil.johansen@nav.no>
Co-authored-by: Markus A. R. Johansen <90006516+J0hans1@users.noreply.github.com>
2023-08-03 12:43:41 +02:00
43 changed files with 1322 additions and 557 deletions

View file

@ -1,53 +0,0 @@
# Dependencies
## Frontend
- Typescript
> Javascript med typeing
- Next.js
> React-basert frontend rammeverk
- TailwindCSS
> CSS rammeverk bestående av små utility-klasser
- Axios
> HTTP klient-rammeverk/bibliotek
- Aksel
> NAVs design system som inkluderer ikoner, komponenter etc.
- useSWR
> Bibliotek som forenkler datafetching over Axios
## Backend
- Kotlin
> Moderne Java basert språk
- HikariCP
> Database connection pool som etablerer forbindelser mellom database og backend
- PostgreSQL
> SQL database rammeverk
- Ktor
> Asynkron HTTP server ~ RESTAPI
- JUnit
> Testrammeverk for backend i Kotlin
- Flyway
> Databasemigrering rammeverk
- Exposed
> ORM for å skrive SQL-spørringer i Kotlin
- Logback
> Loggføringsbibliotek
## Build
- yarn
- gradle

452
Dokumentasjon.md Normal file
View file

@ -0,0 +1,452 @@
# Sprik
## Introduksjon
Som en del av forarbeidet til utviklingen av Sprik ble det gjort omfattende dokumentasjon av behovet for en ny løsning, og hvordan en eventuell løsning ville være hensiktsmessig å utforme.
Dokumentasjonen er resultatet av brukerintervjuer, innsiktsarbeid, prototyping og funksjonalitetsprioritering.
Dokumentasjonen beskriver teknologien bak applikasjonen, referater fra innsiktsarbeidet, lenker til prototypeskissering i Figma, ROS og definerer problemet som Sprik skal løse.
## Innholdsfortegnelse
[Prosjekt plan](#prosjekt-plan)
- [Bakgrunnsinfo](#bakgrunnsinfo)
- [Problemdefinisjon](#problemdefinisjon)
- [Objective / Mål](#objective--mål)
- [Key Results](#key-results)
[Teknologier](#teknologier)
- [React & Typescript](#react--typescript)
- [Aksel & TailwindCSS](#aksel--tailwindcss)
- [Axios & Ktor](#axios--ktor)
- [Kotlin](#kotlin)
- [PostegreSQL & Flyway](#postgresql--flyway)
- [HikariCP & Exposed](#hikaricp--exposed)
- [JUnit & Cypress](#junit--cypress)
- [Yarn & Gradle](#yarn--gradle)
[Kartlegging](#kartlegging)
- [Brukerintervjuer/tester](#brukerintervjuertester)
- [Utviklingsteam](#utviklingsteam)
- [Utvikler #1](#utvikler-1)
- [Utvikler #2](#utvikler-2)
- [Fagperson](#fagperson)
- [Saksbehandlere](#saksbehandlere)
- [Saksbehandler #1](#saksbehandler-1)
- [Saksbehandler #2](#saksbehandler-2)
- [Saksbehandler #3](#saksbehandler-3)
- [Saksbehandler #4](#saksbehandler-4)
- [Saksbehandler #5](#saksbehandler-5)
- [Brukerhistorier og ønsker](#brukerhistorier-og-ønsker)
- [Funksjonalitet basert på ønsker og problemløsning (MoSCoW)](#funksjonalitet-basert-på-ønsker-og-problemløsning-moscow)
[Jus & Ros](#jus--ros)
[Design](#design)
## Prosjekt plan
### Bakgrunnsinfo
Når saksbehandlere oppdager feil eller avvik i sykepengeløsningen Speil må dette meldes til utviklingsteamet.
Vår oppgave var å få innsikt i utfordringer ved dagens løsning, behovene til saksbehandlere og utviklingsteamet, samt påbegynne en løsning basert på innsiktsarbeidet.
#### Utfordringer med dagens løsning
- Dagens løsning for kommunikasjon mellom saksbehandlere og teamet er både treig og lang. Saksbehandler → Teams → Coach → slack → Teams
- Det er ikke tilstrekkelig tilgangskontroll for å verne om brukere (personvern), under innmelding av saker. Saker relatert til kode 6 og 7 brukere er veldig utsatte i dagens løsning da de kan formidles mot mange som ikke har tjenstlig behov for informasjonen.
- De fleste saksbehandlere har ikke direkte mulighet til å melde ifra om feil og feature requests hvilket kan føre til at mange feil går under radaren.
- Uoversiktlig presentasjon og formidling av saker fører til at saker rapporteres inn gjentatte ganger (dobbelt arbeid), at de kan glemmes bort, og i verste fall gå uløst.
- Det er et utydelig skille mellom feature-requests og innmeldte feil i dagens løsning.
- Det er vanskelig å se hvem som er egnet til å besvare
### Problemdefinisjon
Applikasjonens formål er i henhold til oppgaveteksten:
> Lage en applikasjon der saksbehandlere kan melde inn feil/mangler/ønsker. Og potensielt en visning over hva som er meldt inn.
>
> Feil med speil skal kunne gi både saksbehandlere og utviklere en raskere og bedre flyt i kommunikasjonen. Dette vil føre til at vi utviklere kan oppdage og rette opp i feil raskere. Det vil forhåpentligvis også føre til en bedre saksbehandleropplevelse.
>
### Objective / Mål
"Lage et fantastisk produkt som har ekstremt stor verdi for de fine saksbehandlerne og strålende utviklerne."
Dette mener vi at vi har fått til når:
- 80% av saksbehandlere klarer å melde inn feil (KR1)
- 75% av brukere synes Sprik er mer oversiktlig enn dagens løsninger (KR2)
- Ingen uten tjenstlig behov har tilgang til persondata i innmeldte saker (KR3)
#### Key Results
*KR1 og KR2:*
- Ikke hatt brukertester med saksbehandlere der de får prøve å bruke applikasjonene selv grunnet mangel på tid. Derfor har vi ikke målbare tall.
*KR3:*
- Har foreløpig ikke implementert tilgangskontroll.
*Fokus i sommer:*
- Viktige saker første fire uker:
- Få på plass basic funksjonalitet
- Forstå hva tjenesten innebærer og illustrere dette i prototype
- Få deploya applikasjonen i devmiljø
- Viktige saker siste fire uker:
- Ha en brukervennlig applikasjon
- Et utvalg av brukere har begynt å teste/ta i bruk applikasjonen
- Ha en overføringsklar dokumentasjonsbase
- Implementere alle must-haves
## Teknologier
### React & Typescript
Frontend applikasjonen bygges med React på TypeScript
### Aksel & TailwindCSS
Applikasjonen er hovedsaklig bygget av komponentbiblioteket til NAVs designsystem Aksel, men egen styling gjøres gjennom utility-first rammeverket TailwindCSS. Tailwind sine små utilityklasser er kompatible med Aksel og gjør at styling går svært fort. Ikoner og farger er også hentet fra Aksel.
### Axios & Ktor
Axios benyttes som HTTP klient-rammeverk for kommunikasjon med endepunkter i backend. Ktor er en asynkron HTTP server, som brukes som et HTTP API. Ktor hører til Kotlin språket.
### Kotlin
Kotlin er et moderne javabasert språk. Applikasjonens backend er skrevet i Kotlin.
### PostgreSQL & Flyway
Databasen er skrevet i PostgreSQL og i backenden brukes Flyway rammeverket for migrering av databasen slik at en enkelt kan gjøre endringer på databasen
### HikariCP & Exposed
HikariCP danner en database connection pool mellom DB og backend. Exposed er jetbrains sin SQL-ORM for Kotlin, som brukes til å gjøre spørringer mot databasen. Sammen utgjør de kommunikasjonen mellom Backend og database.
### JUnit & Cypress
JUnit er et Kotlin kompatibelt testrammeverk, og Cypress er et testrammeverk for frontend som kan utføre både komponenttesting og ende-til-ende testing. Cypress-axe er en pakke for cypress som kan brukes til UU-testing.
### Yarn & Gradle
Yarn og Gradle er dependency-management verktøyene for frontend og backend applikasjonene.
## Kartlegging
### Brukerintervjuer/tester
#### Utviklingsteam
##### Utvikler #1
- *Hva anser du som en utfordring ved å bruke dagens løsning (slack)?*
- Utfordring at mange av saksbehandlerne ikke er på slack
- Teams = møk
- Delayed rapportering fordi det er en lang vei
- Gjentakende rapportering av samme feil
- Noen kan vurdere det som meldes inn før det går videre til teamet.
- Usikker på om redteam er nødvendig i kommunikasjonsprosessen
- Tråd kommunikasjonen er tungvinnt
- Ting vi har avklart drukner i slack
- *Er det noe du er fornøyd med rundt dagens løsning?*
- Redteam løsningen vi har i dag fungerer ganske bra, med tanke på å rotere på hvem som er redteam
- *Hva slags arbeidsflyt ønsker du å ha med ny løsning?*
- Varslinger nice, ellers lite preferanser
- *Hva er spesielt viktig for saksbehandlere/utviklere/jurister/designer/… å få ut av en slik plattform.*
- Vanskelig å fange opp hvor stor grad en feil skjer.
- Jetbrains har en issue tracker med voting
- De kan se hvilke feil som er meldt inn og saksbehandlere skal kunne vote opp spesiellt relevante cases for å se “hvor skoen trykker i systemet”
- *Hvilke data ønsker du skal være presentert om en sak i Sprik?*
- Greit å ha med innmelder av en sak
- Greit at utvikler kan labele saker mtp app og sann
- Kunne generere lapper i Trello.
- *Hvordan kunne du sett for deg at dataen presenteres i sprik?*
- Enkelt kunne skille feil og feature requests
- Enkle detaljer
- Sortere etter traction etc
- Kanskje tenk gallery view, eller feed.
- Type feil: Feilinfo, handlingsfeil, grensesnittsfeil etc.
##### Utvikler #2
- *Feature requests*
- Vanskelig å sørge for at saksbehandlere ikke blir demotiverte dersom endringene ikke skjer.
- Feature voting side hvor man kan vote opp feature forslag
- Lar utviklere se hvor skoen trykker
- Veldig delte meninger
- *Hvilke behov ønsker du at applikasjonen skal dekke? (forstå hva folk vil bruke det til)*
- “Jeg bruker appen når jeg er redteam”
- Enkel måte å få vite om noe er galt, vite om noe haster eller ikke”
- Ikke en stor blob med tekst men kategorisert og organisert.
- Voting er bra for å se hva som er veldig aktuellt.
- Hashtags/emneknagger/tags for å organisere i type feil → ser antall feil av en type.
- *Hvilken funksjonalitet er det viktig for deg at er med i applikasjon?*
- “Feilen kommer lett frem”, “beskrivelse av casen” hadde vært enkelt med en kort tittel på problemet.
- Oversikt over hva som er meldt inn tidligere
- *Hvilken funksjonalitet tenker du har mest verdi for deg? Altså hva er viktigst først?*
- Å kunne kategoriesere hvilke feil man har
- Få inntrykk av hvilke features som eksisterer samt muligheten til å komme med tilbakemelding.
- *Hva anser du som en utfordring ved å bruke dagens løsning (Slack)?*
- Ikke god oversikt over hva som er meldt ifra før
- Vanskelig å se hvem som passer til å svare på spm
- *Hva er den største av disse utfordringene?*
- Vanskelig å ha oversikt over hva som er meldt inn (enklere med status, men tungvinnt).
- Mistenker dobbelt arbeid
- *Er det noe du er fornøyd med rundt dagens løsning?*
- Mulighet til å ta kontakt med saksbehandlere hvis det trengs (ved feks ønske om flere opplysninger)
- Trådsvar - nais to have, ikke kritisk
- *Hva slags arbeidsflyt ønsker du å ha med ny løsning?*
- Er en del felter som ville gjort det enklere (Dette går under data også)
- Skjermbilde
- Tags
- AktørID
- Egendefinerte tags
- Kan være en utfordring med flere måter å definere et begrep (grunnbeløp vs G vs 6G)
- Tildele til personer
- *Hva er spesielt viktig for saksbehandlere/utviklere/jurister/designer/… å få ut av en slik plattform.*
- Unngå dobbeltarbeid
- Sortere traction eller hvor kritisk det er
- *Hvilke data ønsker du skal være presentert om en sak i Sprik*
- +aktøride
- +skjermbilde
- +tags
- +tittel,
- +status
- +innmeldt saksbehandler
- statusflagg?
- forklarende bilde?
- Saksnummer?
- Beskrivelse?
- innsender?
- mer?
- *Hvordan kunne du sett for deg at dataen presenteres i sprik?*
- Galleri-visning
- *Syntes du at redteam ordningen som et vaktlag skal implementeres videre? har du noe forslag*
- Red team har tatt lang tid å få til å fungere
- Er ikke nødvendigvis beste løsning
- Handler om at man skal rullere for å få noen til å ta ansvar
##### Fagperson
- *Hva er din opplevelse av dagens løsning?*
- Skille mellom innspill og feil kan være litt uklart
- Det er bra med direkte kontakt (fine med meldingstjenesten)
- En del som meldes inn flere ganger
- En del saksbehandlere man ikke har kontakt med
- *Hvorfor fungerer ikke dagens løsning?*
- Ikke kontakt med alle saksbehandlere ~ saksbehandler-kanalen kan bli litt uoversiktelig
- Viktig-info kanalen funker fint ettersom det kun kommer enveis-meldinger
- Er nok en del feil som glipper (går under radaren/ikke blir meldt inn)
- *Hva er de største utfordringene i dagens løsning?*
- Går under radaren
- Hvorfor? Travel hverdag
- Slack ikke optimal til oppfølging
- *Hva fungerer godt i dagens løsning?*
- Funker bra at det er en direkte kontakt mellom saksbehandlere og utviklingsteam (ikke erstatte dette)
- Gir ett forhold til brukeren og løsninga si
- Mange ting som blir fiksa fortløpende
- *Hva er det viktigste for deg som (utvikler/saksbehandler/…) å oppnå med plattformen?*
- Hjelpe dem med å jobbe med de riktige tingene
- Slippe å bruke unødvendig tid
- *Hva tenker du er formålet med Sprik?*
- Å fjerne gapet mellom saksbehandlere og utviklingsteam
- Gapet = de som ikke er i Slack, ikke alle tør å poste i Slack (fører til lite kontakt)
- Få flere informerte saksbehandlere
- *ukategorisert*
- en ting som kunne vært kult hvis vi skal jobbe med målinger, kunne vært kult med temperaturmålinger for saksbehandling (hvordan trives du med å jobbe i speil, får du hjelp når du trenger det, hvordan har du det, hvordan er motivasjonen, målt over tid for å påvirke prouktet)
- hvordan gjøres: brukt en vanlig helsesjekk i starten for å sjekke om de vil bruke denne, evt. spesiallaget noe i speil der det kommer en boble i speil eksempelvis hver fredag som tar 30sek å svare på
- kunne forbedret dagens løsning
- vanlig spørsmål og svar (q&a forum)
- hvordan gjør jeg dette og dette
- løsning: faq side
- tips
- intervjue de som ikke er i slack av saksbehandlere (ikke like mye innsikt, mer realistisk brukeropplevelse)
- rammeverk å tenke på når vi skal arbeide:
- kjapt å lage → tar lang tid å lage (x akse)
- lav verdi ^ høy verdi (y akse)
- lapper med forslag man plasserer på grafen
- prioriterer kjapt å lage med høy verdi
- redteam fungerer?
- funker, men må kanskje gjøres på en annen måte ved ny løsning
- det de blir tagga i blir oftest løst, men er fortsatt ting som kan forsvinne pga. mengden meldinger
- ikke alle i redteam er like på, eksempelvis jurister, til å svare på saker
#### Saksbehandlere
##### Saksbehandler #1
- *Hva skal vi kalle hvert element (lapp, feilmelding, feil)?*
- Innmeldte feil (med parantes på)
- *Hva syntes du om statuslappene?*
- Likte statuslabelsa
- Dropdown var veldig intuitivt
- *For å holde oversikt over egne saker, syntes du det funker å bare bruke filtrering for dette? (evt. bruke egne farger for lapper eller labels)*
- filtrering enten høyere eller venstre side, mer naturlig på venstresiden
- egne saker → filtrer etter initialer
- filtrer automatisk etter kronologisk tid, tidligst først
- egen farger for egne saker kan bli litt mye
- *Hva tenker du om å se feil på denne måten? (oversiktelig?)*
- Førsteinntrykk er mye, men etterhvert veldig oversiktelig
- “melde inn feil/funksjonalitetsønsker”-knapper legges høyere
- *Hva er det første du ser?*
- Mye informasjon, mye tekst, må bruke litt tid for å forstå hva det egt er
- “Hva er søkefeltet til”-spørsmål
##### Saksbehandler #2
- *Hvordan syntes du det er å jobbe med speil idag?*
- *Kan du fortelle litt om hvordan du bruker Speil i dag?*
- Skjønte ikke helt spørsmålet her
- *Hvordan går du frem når du finner feil i Speil i dag?*
- Tar kontakt med coacher eller super
- De melder videre
- eller kan melde fra i support-kanal på teams
- *Hvordan synes du feilinnmelding angående Speil fungerer i dag?*
- Slack ble fjernet som forstyrrende element i arbeid
- mener de fortsatt burde hatt slack, lavere terskel for å melde inn
- tungvinnt å gå via coacher
- La kanskje til henne en mening om lang vei??
##### Saksbehandler #3
- *Hvordan syntes du det er å jobbe med speil idag?*
- *Kan du fortelle litt om hvordan du bruker Speil i dag?*
- *Hvordan går du frem når du finner feil i Speil i dag?*
- Går til coach som melder til utvikler
- Svarer som de gjør fordi hovedproblemet med å jobbe med sykepenger fordi det er for mange som ikke kan sykepenger
- omfattende domene
- Noen er raske fordi vi har produksjonstall
- Lang tid å skaffe domene kunnskap
- Mange tror de kan sette seg ned og bare gjøre ting, det er mye å sette seg inn i
- *Hvordan synes du feilinnmelding angående Speil fungerer i dag?*
- Det er så som så
- tror at utfordringen er at noen enheter er flinke til å bruke faglige ressurser rundt seg, samtidig som andre bruker support rollen
- Bruker ikke mye tid der, men drukner i repetetive innmeldinger.
- brukerutbetaling skaper mange feil → større konsekvens
- Siden det er så komplisert det vi jobber med (sykepenger) → fordi ting gjøres automatisk
##### Saksbehandler #4
- *Hva er det viktigste for deg i en ny løsning for feilinnmelding?*
- det må være toveiskommunikasjon
- å melde inn er ikke nok så lenge man ikke får noe respons
##### Saksbehandler #5
- *Hvordan synes du feilinnmelding angående Speil fungerer i dag?*
- *Er det noe som fungerer godt?*
- *Er det noe som fungerer mindre godt?*
- ordinær opplæring innenfor fag og systemer, fungerte ikke fordi ingen har kunnskaper om speil
- er veldig misfornøyd med oversikten over benken min
### Brukerhistorier og ønsker
| Ønsker | Intervjuobjekt | Prioritet | Brukerhistorie |
| - | - | - | - |
| Kunne se helt tydelig, uten å grave i diskusjonstråd, hva status og konklusjon er for en sak. | Saksbehandler | | - Jeg vil se behandlingsstatus på en sak <br /> - Jeg ønsker å enkelt kunne finne frem konklusjonen for en lukket sak |
| Ønsker type nyhets feed eller gallery view | Utvikler | | - Jeg vil se en ryddig og organisert visning av innmeldte saker |
| Ryddig oversikt over innmeldte saker | Utvikler | | - Jeg vil se en ryddig og organisert visning av innmeldte saker |
| Voting er bra for å se hva som er aktuelt | Utvikler | | - Jeg vil se hvor aktuell en sak er |
| Måle “traction” på ulike saker for å fange opp i hvor stor grad en feil skjer. Foreslår et voting system. Hvor trykker skoen i systemet. Fint om utviklere kan sortere etter traction | Utvikler | Største ønske | - Jeg vil se hvor akutell en sak er <br /> - Jeg ønsker å sortere etter hvor aktuell en sak er |
| Se om en sak er rapportert tidligere evt behandlet (unngå gjentakende rapportering) | Saksbehandler | Største ønske | - Jeg vil sjekke om en sak er rapportert inn tidligere <br /> - Jeg vil se behandlingsstatus på en sak |
| Tydelig skille mellom rapporterte feil og feature requests | Utvikler | | - Jeg vil se tydelig skille mellom feature requests for en sak jeg "følger" |
| Bli varslet om aktivitet på en sak/post (diskusjon, status endringer, etc.). Da slipper man å overvåke. | Saksbehandler | | - Jeg vil varsles ved endringer/aktiviteter for en sak jeg "følger" |
| Kunne være i direkte kontakt med utviklere | Saksbehandler | | - Jeg ønsker å ha direkte kontakt med saksbehandler/utvikler |
| Utvikler skal kunne varsle saksbehandler dersom mer informasjon om sak trengs for å løse den. | Saksbehandler | | - Jeg ønsker å ha direkte kontakt med saksbehandler/utvikler |
| Kunne kontakte saksbehandler dersom det trengs for ytterligere info om saken | Utvikler | | - Jeg ønsker å ha idrekte kontakt med saksbehandler/utvikler |
| Kunne søke opp saker på nøkkelord | Saksbehandler | Største ønske | - Jeg ønsker å kunne søke opp saker på nøkkelord og tagger |
| Generere lapper på en sak i Trello | Utvikler | Nice to have | - Jeg ønsker å lage en lapp på en sak i Trello |
| Mulighet til å initiere samtale relatert til posten. Utviklere og saksbehandlre kan ha en dialog som kan organiseres i en tråd festet til en post. | Saksbehandler | | - Jeg ønsker å lese/skrive i diskusjonstråd for en sak |
| Trådsvar | Utvikler | Nice to have | - Jeg ønsker å lese/skrive i diskusjonstråd for en sak |
| Kunne se en diskusjonstråd eller annen aktivtet/varsler rundt en sak. | Saksbehandler | | - Jeg ønsker å lese/skrive i diskusjonstråd for en sak <br /> - Jeg vil varsles ved endringer/aktiviteter for en sak jeg "følger" |
| En sak skal kunne innehold skjermbilder, beskrivelse, aktør-id og dato(er) | Saksbehandler | | - Jeg ønsker å opprette/lese en sak som inneholder tittel, beskrivelse, skjermbilder, datoer, innmelder, feiltype (grensesnitt, handling, logik) og behandlingsstatus |
| Innmelder må være felt på en sak, type feil (grensesnitt, handlingsfeil, verdifeil) | Utvikler | | - Jeg ønsker å opprette/lese en sak som inneholder tittel, beskrivelse, skjermbilder, datoer, innmelder, feiltype (grensesnitt, handling, logik) og behandlingsstatus |
| Feilen kommer lett frem med en beskrivelse og tittel | Utvikler | | - Jeg ønsker å opprette/lese en sak som inneholder tittel, beskrivelse, skjermbilder, datoer, innmelder, feiltype (grensesnitt, handling, logik) og behandlingsstatus |
| Sak burde ha aktørid, mulighet for skjermbilde opplastning, tags, tittel, status. innmelder (saksbehandler) | Utvikler | | - Jeg ønsker å opprette/lese en sak som inneholder tittel, beskrivelse, skjermbilder, datoer, innmelder, feiltype (grensesnitt, handling, logik) og behandlingsstatus |
| Oversikt over hva som støttes i Speil. Funksjonalitetsoversikt (kommunikasjonskanal for ny funksjonalitet i Speil) | Saksbehandler | | - Jeg ønsker å se hvilken funksjonalitet som Speil har |
| Få inntrykk av hvilke features som eksisterer samt. muligheten til å komme med tilbakemelding | Utvikler | Største ønske | - Jeg ønsker å se hvilken ufnksjonalitet som Speil har |
| Se om saken haster å løse | Utvikler | | - Jeg ønsker å se om en sak haster å løse |
| Utviklere skal kunne gi en label til en sak ifht. relatert app | Utvikler | | Jeg ønsker å tildele en sak en emneknagg |
| Kategoriere hvilke typer feil som finnes | Utvikler | Største ønske | - Jeg ønsker å tildele en sak en emneknagg |
| Hashtags/emneknagger|tags for å organisere i type feil -> ser antall feil av en type. Disse kan være egendefinerte | Utvikler | | - Jeg ønsker å tildele en sak en emneknagg <br /> - Jeg ønsker å se antall aktive saker på en emneknagg |
| Ha et "beredskapsteam" (redteam), som svarer raskt | Saksbehandler | REDTEAM | |
| Låse en løst tråd??? | Saksbehandler | | |
| Vurdere en innmeldt sak før den sendes videre til utviklingsteam - for å unngå gjentakende rapportering | Utvikler | |
### Funksjonalitet basert på ønsker og problemløsning (MoSCoW)
| Funksjonalitet | Implementasjonsdetaljer | MoSCoW | Status |
| - | - | - | - |
| Oppdatere innmeldt feil | | Must have (dev) | Done (mangler aktorid) |
| Saksbehandlere skal kunne melde inn saker | • Legge til en god beskrivelse av saken (~ juss) <br /> ◦ Skjermbilder, aktør-id, datoer <br /> ◦ Se en oversikt over meldte saker (egne saker? ~ juss) | Must have (dev) | Done |
| Kunne se innmeldte feil | | Must have (dev) | Done |
| Teamet skal kunne gi beskjed om at saker er løst | | Must have (dev) | Done |
| Enkelt se konklusjon av en sak | | Must have (dev) | Done |
| Søkefunksjonalitet | | Must have (dev) | Done |
| Redigere innmeldt feil | | Should have | Done |
| Filtrering av saker etter type feil og lables | | Should have | Started frontend |
| Gi labels/kategorisering av/til feil | | Should have | Not started |
| Kunne oppdage at noe er meldt inn tidligere | | Could have | Not started |
| Kunne stemme på saker for å måle "hvor skoen trykker i systemet" | | Could have | Not started |
| Trådsvar | | Could have | Not started |
| Oversikt over kjente feil / løsninger | | Could have | Not started |
| Opprette Trello lapper direkte fra appen på en rapportert sak | | Will not have | Not started |
| Formidle støttet funksjonalitet i Speil | | Will not have | Not started |
| Komme med tilbakemelding på støttet funksjonalitet i Speil | | Will not have | Not started |
| FAQ side om Speil | | Will not have | Not started |
| Sortering av saker etter traction | | Will not have | Not started |
| Ved jobbing med målinger hadde det vært kult med temperaturmålings for saksbehandling (spørsmål rundt trivsel målt over tid for å påvirke produktet) | | Will not have | Not started |
| Direkte kontakt mellom de to partene | | Must have (prod) | Not started |
## Juss & ROS
Hensyn å ta:
- Hvem har tjenstlig behov for å se innmeldte feil?
- Tilgangskontroll
- Logging og sporing
- Skal alle kunne se alle felter av innmeldte feil?
- Skal kun RedTeam se saker?
- Personvernhensyn
- Akørid må nesten med både for å kunne sjekke en sak, men også for å logge hvordan en brukers info er behandlet
- Ha info-bokser for å minne på at personopplysninger ikke skal deles
- Kan ha en pop-up som kommer når man trykker "meld inn sak" som ber innmelder dobbeltsjekke personopplysninger
- Deling av personopplysninger i skjermbilde?
- Færre muligheter for å skrive fritekst kan redusere sannsynligheten for å dele for mye, men kan bli vanskelig med standard kategorier som er dekkende
- Spesielle hensyn å ta ang. kode 6 og 7?
> ROS er påbegynt og registert i TryggNok
## Design
- Applikasjonen er skissert i Figma.
- Figma prosjektet finner du [her](https://www.figma.com/files/810213623608415105/team/1256163063148444981).
- Underveis i designprosessen har UX/UI-Designer i PO-Helse gitt råd.
- Prototypen er raffinert gjennom flere iterasjoner med brukerintervjuer av fagfolk, utviklere og saksbehandlere.
- Prototypen bygges på designsystemet Aksel sine tokens og komponenter.

View file

@ -1,13 +0,0 @@
package no.nav.helse.sprik
import kotlinx.serialization.*
@Serializable
data class Test (
var ord: String,
var tall: Int
) {
override fun toString(): String {
return "Test(ord='$ord', tall=$tall)"
}
}

View file

@ -1,5 +1,6 @@
package no.nav.helse.sprik.db
import no.nav.helse.sprik.db.FeilmeldingTable.aktorid
import no.nav.helse.sprik.db.FeilmeldingTable.arbeidsstatus
import no.nav.helse.sprik.db.FeilmeldingTable.beskrivelse
import no.nav.helse.sprik.db.FeilmeldingTable.dato
@ -9,6 +10,7 @@ import no.nav.helse.sprik.db.FeilmeldingTable.kommentar
import no.nav.helse.sprik.db.FeilmeldingTable.tittel
import no.nav.helse.sprik.modell.Feilmelding
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.like
import org.jetbrains.exposed.sql.transactions.transaction
@ -22,19 +24,23 @@ class FeilmeldingRepository {
it[FeilmeldingTable.dato] = feilmelding.dato
it[FeilmeldingTable.arbeidsstatus] = feilmelding.arbeidsstatus
it[FeilmeldingTable.haster] = feilmelding.haster
feilmelding.aktorid?.also { aktorid ->
it[FeilmeldingTable.aktorid] = aktorid
}
}
}
}
}
private fun radTilFeilmelding(rad: ResultRow) = Feilmelding(
internal fun radTilFeilmelding(rad: ResultRow) = Feilmelding(
id = rad[id],
tittel = rad[tittel],
beskrivelse = rad[beskrivelse],
dato = rad[dato],
arbeidsstatus = rad[arbeidsstatus],
haster = rad[haster],
kommentar = rad[kommentar]
kommentar = rad[kommentar],
aktorid = rad[aktorid]
)
fun hentAlleFeilmeldinger(): List<Feilmelding> = transaction {
@ -42,8 +48,7 @@ class FeilmeldingRepository {
}
fun hentSokteFeilmeldinger(sokeord: String): List<Feilmelding> = transaction {
val sok = "%${sokeord.lowercase()}%"
val sok = "%${sokeord.lowercase().trim()}%"
FeilmeldingTable.select(
(FeilmeldingTable.tittel.lowerCase() like sok)
or (FeilmeldingTable.beskrivelse.lowerCase() like sok)
@ -52,14 +57,12 @@ class FeilmeldingRepository {
}
fun oppdaterFeilmelding(feilmelding: Feilmelding) = transaction {
val id = feilmelding.id
if (id != null) {
FeilmeldingTable.update({ FeilmeldingTable.id eq id }) {
it[FeilmeldingTable.tittel] = feilmelding.tittel
it[FeilmeldingTable.beskrivelse] = feilmelding.beskrivelse
it[FeilmeldingTable.arbeidsstatus] = feilmelding.arbeidsstatus
it[FeilmeldingTable.haster] = feilmelding.haster
}
checkNotNull(feilmelding.id) { "Id kan ikke være null når vi skal oppdatere feilmelding" }
FeilmeldingTable.update({ FeilmeldingTable.id eq feilmelding.id }) {
it[FeilmeldingTable.tittel] = feilmelding.tittel
it[FeilmeldingTable.beskrivelse] = feilmelding.beskrivelse
it[FeilmeldingTable.arbeidsstatus] = feilmelding.arbeidsstatus
it[FeilmeldingTable.haster] = feilmelding.haster
}
}
@ -68,4 +71,8 @@ class FeilmeldingRepository {
it[FeilmeldingTable.kommentar] = kommentar
}
}
fun slettFeilmelding(id: Int) = transaction {
FeilmeldingTable.deleteWhere { FeilmeldingTable.id eq id }
}
}

View file

@ -11,4 +11,5 @@ object FeilmeldingTable : Table("feilmelding") {
val arbeidsstatus = integer("arbeidsstatus")
val haster = bool("haster")
val kommentar = text("kommentar")
val aktorid = long("aktorid")
}

View file

@ -13,14 +13,15 @@ import java.time.LocalDateTime
* Objekt for feilmeldinger i Sprik
* */
@Serializable
class Feilmelding(
data class Feilmelding(
val id: Int?,
val tittel: String,
val beskrivelse: String,
val dato: LocalDateTime, //Krever en spesiallaget serialiserer i KotlinX
val arbeidsstatus: Int,
val haster: Boolean,
val kommentar: String?
val kommentar: String?,
val aktorid: Long?
//val bilde: String,
) {
override fun toString(): String {

View file

@ -0,0 +1,4 @@
import kotlinx.serialization.Serializable
@Serializable
class InnkommendeKommentar(val id: Int, val kommentar: String)

View file

@ -1,5 +1,6 @@
package no.nav.helse.sprik.plugins
import InnkommendeKommentar
import io.ktor.http.*
import io.ktor.server.routing.*
import io.ktor.server.response.*
@ -27,6 +28,7 @@ fun configureRouting(): ApplicationEngine = embeddedServer(CIO, applicationEngin
allowMethod(HttpMethod.Get)
allowMethod(HttpMethod.Post)
allowMethod(HttpMethod.Put)
allowMethod(HttpMethod.Delete)
allowNonSimpleContentTypes = true
}
install(ContentNegotiation) {
@ -47,28 +49,42 @@ fun configureRouting(): ApplicationEngine = embeddedServer(CIO, applicationEngin
get("/isready"){
call.respondText("READY")
}
get("/api/test") {
call.respondText("test")
get("/api/hentallefeil"){
val feilmeldinger = feilmeldingRepository.hentAlleFeilmeldinger()
call.respond(status = HttpStatusCode.OK, message = feilmeldinger)
}
get("/api/hentsok/{sokestreng}"){
val sokestreng = call.parameters["sokestreng"]
?: return@get call.respond(HttpStatusCode.BadRequest, "Søkestreng må være definert")
val sokeresultat = feilmeldingRepository.hentSokteFeilmeldinger(sokestreng)
call.respond(status = HttpStatusCode.OK, message = sokeresultat)
}
post("/api/nyfeil") {
val feilmelding = call.receive<Feilmelding>()
feilmeldingRepository.lagre(feilmelding)
call.respond(status = HttpStatusCode.Created, message = "Feilmelding motatt og sendt til database")
}
get("/api/hentallefeil"){
val testMelding = feilmeldingRepository.hentAlleFeilmeldinger()
call.respond(status = HttpStatusCode.Created, message = testMelding)
}
get("/api/hentsok/{sokestreng}"){
val sokestreng = call.parameters["sokestreng"]
?: return@get call.respond(HttpStatusCode.BadRequest, "Sokestreng må være definert")
val sokeresultat = feilmeldingRepository.hentSokteFeilmeldinger(sokestreng)
call.respond(status = HttpStatusCode.Created, message = sokeresultat)
call.respond(status = HttpStatusCode.Created, message = "Feilmelding motatt og lagret")
}
put("/api/oppdaterfeil") {
val oppdatertFeilmelding = call.receive<Feilmelding>()
feilmeldingRepository.oppdaterFeilmelding(oppdatertFeilmelding)
call.respond(status = HttpStatusCode.Created, message = "Feilmelding oppdatert")
call.respond(status = HttpStatusCode.OK, message = "Feilmelding oppdatert")
}
put("/api/oppdaterkommentar") {
val innkommendeKommentar = call.receive<InnkommendeKommentar>()
feilmeldingRepository.oppdaterKommentar(innkommendeKommentar.id, innkommendeKommentar.kommentar)
call.respond(status = HttpStatusCode.OK, message = "Kommentar oppdatert")
}
delete("api/slettfeilmelding/{id}") {
val id = call.parameters["id"]
checkNotNull(id) {"Id kan ikke være null"}
feilmeldingRepository.slettFeilmelding(id.toInt())
call.respond(status = HttpStatusCode.OK, message = "Feilmelding slettet")
}
delete("api/slettfeilmelding/{id}") {
val id = call.parameters["id"]
checkNotNull(id) {"Id kan ikke være null"}
feilmeldingRepository.slettFeilmelding(id.toInt())
call.respond(status = HttpStatusCode.Created, message = "Feilmelding slettet")
}
}
}
@ -76,6 +92,4 @@ fun configureRouting(): ApplicationEngine = embeddedServer(CIO, applicationEngin
connector {
port = 8080
}
})
})

View file

@ -0,0 +1 @@
ALTER TABLE feilmelding ADD aktorid INT

View file

@ -0,0 +1,2 @@
ALTER TABLE feilmelding
ALTER COLUMN aktorid TYPE BIGINT

View file

@ -0,0 +1 @@
TRUNCATE feilmelding

View file

@ -22,10 +22,11 @@ fun main() {
private fun oppretteMockData() {
val feilmeldingRepository = FeilmeldingRepository()
feilmeldingRepository.lagre(Feilmelding(null, "Mangel på hensyn til tariffoppgjør", "Det har vært ett tariffoppgjør og speil sier sykepengene må tilbakekreves, noe som er feil. (sier vi.. har ikke domenekunnskap)", LocalDateTime.of(2023, 1, 1, 8, 0, 0), 0, false, null))
feilmeldingRepository.lagre(Feilmelding(null, "Speil sier NAV må tilbakekreve sykepenger på feil grunnlag", "Beskrivelse Test2", LocalDateTime.of(2023, 2, 1, 8, 0, 0), 1, true, null))
feilmeldingRepository.lagre(Feilmelding(null, "Feil A", "Lorem Ipsum", LocalDateTime.of(2023, 3, 1, 8, 0, 0), 2, false, null))
feilmeldingRepository.lagre(Feilmelding(null, "Feil B", "Lorem Ipsum", LocalDateTime.of(2023, 4, 1, 8, 0, 0), 1, true, null))
feilmeldingRepository.lagre(Feilmelding(null, "Feil C", "Lorem Ipsum", LocalDateTime.of(2023, 5, 1, 8, 0, 0), 1, true, null))
feilmeldingRepository.lagre(Feilmelding(null, "Feil D", "Lorem Ipsum", LocalDateTime.of(2023, 6, 1, 8, 0, 0), 0, false, null))
feilmeldingRepository.lagre(Feilmelding(null, "Mangel på hensyn til tariffoppgjør", "Det har vært ett tariffoppgjør og speil sier sykepengene må tilbakekreves, noe som er feil. (sier vi.. har ikke domenekunnskap)", LocalDateTime.of(2023, 1, 1, 8, 0, 0), 0, false, null, null))
feilmeldingRepository.lagre(Feilmelding(null, "Speil sier NAV må tilbakekreve sykepenger på feil grunnlag", "Beskrivelse Test2", LocalDateTime.of(2023, 2, 1, 8, 0, 0), 1, true, null, null))
feilmeldingRepository.lagre(Feilmelding(null, "Feil A", "Lorem Ipsum", LocalDateTime.of(2023, 3, 1, 8, 0, 0), 2, false, null, null))
feilmeldingRepository.lagre(Feilmelding(null, "Feil B", "Lorem Ipsum", LocalDateTime.of(2023, 4, 1, 8, 0, 0), 1, true, null, null))
feilmeldingRepository.lagre(Feilmelding(null, "Feil C", "Lorem Ipsum", LocalDateTime.of(2023, 5, 1, 8, 0, 0), 1, true, null, null))
feilmeldingRepository.lagre(Feilmelding(null, "Feil D", "Lorem Ipsum", LocalDateTime.of(2023, 6, 1, 8, 0, 0), 0, false, null, null))
feilmeldingRepository.oppdaterKommentar(1, "Test kommentar")
}

View file

@ -1,5 +0,0 @@
package no.nav.helse.sprik
class ApplicationTest {
}

View file

@ -0,0 +1,7 @@
package no.nav.helse.sprik
import java.time.LocalDateTime
fun Int.januar(år: Int = 2023, time: Int = 8, minutt: Int = 0) = LocalDateTime.of(år, 1,this, time, minutt)
val Int.januar get() = this.januar()

View file

@ -8,20 +8,30 @@ import no.nav.helse.sprik.modell.Feilmelding
import org.jetbrains.exposed.sql.deleteAll
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.*
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import java.time.LocalDateTime
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNull
import org.jetbrains.exposed.sql.Database as ExposedDatabase
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class FeilmeldingTest {
private val database = Database(dbconfig()).configureFlyway()
private val feilmeldingRepository = FeilmeldingRepository()
private companion object {
val feilmelding = Feilmelding(
null,
"Test",
"Testesen",
1.januar,
0,
true,
null,
null
)
}
fun getId() = transaction {
FeilmeldingTable.selectAll().single()[FeilmeldingTable.id]
@ -32,12 +42,6 @@ class FeilmeldingTest {
ExposedDatabase.connect(database.dataSource)
}
@BeforeEach
fun lagreFeilmelding() {
val feilmelding = Feilmelding(null, "Test", "Testesen", LocalDateTime.of(2023,1,1,8,0), 0, true, null)
feilmeldingRepository.lagre(feilmelding)
}
@AfterEach
fun wipe() {
transaction {
@ -45,9 +49,9 @@ class FeilmeldingTest {
}
}
@Test
fun `Sett opp testdatabasen riktig`(){
feilmeldingRepository.lagre(feilmelding)
transaction {
assertEquals(1, FeilmeldingTable.selectAll().map {
it
@ -56,60 +60,94 @@ class FeilmeldingTest {
}
@Test
fun `Lagrer feilmelding i databasen`() {
fun `Lagrer feilmelding uten aktørid i databasen`() {
feilmeldingRepository.lagre(feilmelding)
transaction {
val actual = FeilmeldingTable.selectAll().single()
assertEquals("Test", actual[FeilmeldingTable.tittel])
assertEquals("Testesen", actual[FeilmeldingTable.beskrivelse])
assertEquals(LocalDateTime.of(2023, 1, 1, 8, 0), actual[FeilmeldingTable.dato])
assertEquals(1, actual[FeilmeldingTable.id])
val forventetFeilmelding = Feilmelding(
getId(),
"Test",
"Testesen",
1.januar,
0,
true,
null,
null
)
val faktiskFeilmelding = feilmeldingRepository.radTilFeilmelding(FeilmeldingTable.selectAll().single())
assertEquals(forventetFeilmelding, faktiskFeilmelding)
}
}
@Test
fun `Lagrer feilmelding med aktørid i databasen`() {
feilmeldingRepository.lagre(feilmelding.copy(aktorid = 12345678))
transaction {
val forventetFeilmelding = Feilmelding(
getId(),
"Test",
"Testesen",
1.januar,
0,
true,
null,
12345678
)
val faktiskFeilmelding = feilmeldingRepository.radTilFeilmelding(FeilmeldingTable.selectAll().single())
assertEquals(forventetFeilmelding, faktiskFeilmelding)
}
}
@Test
fun `Henter alle feilmeldinger i databasen`() {
feilmeldingRepository.lagre(feilmelding)
feilmeldingRepository.lagre(feilmelding)
transaction {
val resultat: List<Feilmelding> = feilmeldingRepository.hentAlleFeilmeldinger()
val actual = FeilmeldingTable.selectAll()
assertEquals(actual.map { it }.size, resultat.size)
assertEquals("Test", resultat[0].tittel)
assertEquals("Testesen", resultat[0].beskrivelse)
assertEquals(LocalDateTime.of(2023, 1, 1, 8, 0), resultat[0].dato)
val forventet = FeilmeldingTable.selectAll().map { it }
assertEquals(forventet.size, resultat.size)
}
}
@Test
fun `Henter feilmeldinger som matcher søk`() {
feilmeldingRepository.lagre(feilmelding)
val sokeresultat: List<Feilmelding> = feilmeldingRepository.hentSokteFeilmeldinger("Test")
assertEquals(1, sokeresultat.size)
assertEquals("Test", sokeresultat[0].tittel)
assertEquals("Testesen", sokeresultat[0].beskrivelse)
}
@Test
fun `Henter feilmeldinger som har søkestreng som substreng`() {
fun `Henter alle feilmeldinger som har søkestreng som substreng`() {
feilmeldingRepository.lagre(feilmelding)
feilmeldingRepository.lagre(feilmelding.copy(tittel = "Heste"))
val sokeresultat: List<Feilmelding> = feilmeldingRepository.hentSokteFeilmeldinger("este")
assertEquals(1, sokeresultat.size)
assertEquals("Test", sokeresultat[0].tittel)
assertEquals("Testesen", sokeresultat[0].beskrivelse)
assertEquals(2, sokeresultat.size)
}
@Test
fun `Finner ingen feilmeldinger som matcher søk`() {
feilmeldingRepository.lagre(feilmelding)
val sokeresultat: List<Feilmelding> = feilmeldingRepository.hentSokteFeilmeldinger("abrakadabra")
assertEquals(0, sokeresultat.size)
}
@Test
fun `Søk er ikke case sensitivt`() {
feilmeldingRepository.lagre(feilmelding)
val sokeresultat: List<Feilmelding> = feilmeldingRepository.hentSokteFeilmeldinger("test")
assertEquals(1, sokeresultat.size)
assertEquals("Test", sokeresultat[0].tittel)
assertEquals("Testesen", sokeresultat[0].beskrivelse)
}
@Test
fun `Søk er ikke mellomrom-sensitiv`() {
feilmeldingRepository.lagre(feilmelding)
val sokeresultat: List<Feilmelding> = feilmeldingRepository.hentSokteFeilmeldinger("Test ")
assertEquals(1, sokeresultat.size)
}
@Test
fun `Oppdaterer en feilmelding`() {
val oppdatertFeilmelding = Feilmelding(getId(), "Oppdatert", "Oppdatert feil", LocalDateTime.of(2023, 1, 1, 8, 0), 1, false, null)
feilmeldingRepository.lagre(feilmelding)
val oppdatertFeilmelding = Feilmelding(getId(), "Oppdatert", "Oppdatert feil", 1.januar, 1, false, null, null)
feilmeldingRepository.oppdaterFeilmelding(oppdatertFeilmelding)
val actualOppdatert = transaction { FeilmeldingTable.selectAll().single() }
assertEquals("Oppdatert", actualOppdatert[FeilmeldingTable.tittel])
@ -119,10 +157,43 @@ class FeilmeldingTest {
}
@Test
fun `Oppdaterer feilmeldingskommentar`() {
feilmeldingRepository.oppdaterKommentar(getId(), "Feilen fikses nå!")
val actual = transaction { FeilmeldingTable.selectAll().single() }
assertEquals("Feilen fikses nå!", actual[FeilmeldingTable.kommentar])
fun `Prøver å oppdatere feilmelding uten id`() {
feilmeldingRepository.lagre(feilmelding)
val oppdatertFeilmelding = Feilmelding(null, "Oppdatert", "Oppdatert feil", 1.januar, 1, false, null, null)
assertThrows<IllegalStateException> {
feilmeldingRepository.oppdaterFeilmelding(oppdatertFeilmelding)
}
}
@Test
fun `Kommentar er tom når feilmelding opprettes`() {
feilmeldingRepository.lagre(feilmelding)
val initiellKommentar = transaction { FeilmeldingTable.selectAll().single()[FeilmeldingTable.kommentar] }
assertNull(initiellKommentar)
}
@Test
fun `Oppdaterer feilmeldingskommentar`() {
feilmeldingRepository.lagre(feilmelding)
feilmeldingRepository.oppdaterKommentar(getId(), "Feilen fikses nå!")
val oppdatertKommentar = transaction { FeilmeldingTable.selectAll().single()[FeilmeldingTable.kommentar] }
assertEquals("Feilen fikses nå!", oppdatertKommentar)
}
@Test
fun `Ny kommentar skal overskrive gammel`() {
feilmeldingRepository.lagre(feilmelding)
feilmeldingRepository.oppdaterKommentar(getId(), "Initiell kommentar")
feilmeldingRepository.oppdaterKommentar(getId(), "Oppdatert kommentar")
val oppdatertKommentar = transaction { FeilmeldingTable.selectAll().single()[FeilmeldingTable.kommentar] }
assertEquals("Oppdatert kommentar", oppdatertKommentar)
}
@Test
fun `Feilmelding slettes basert på id`() {
feilmeldingRepository.lagre(feilmelding)
feilmeldingRepository.slettFeilmelding(getId())
val resultat = transaction { FeilmeldingTable.selectAll().map { it }.size }
assertEquals(0, resultat)
}
}

View file

@ -1,9 +1,20 @@
import { defineConfig } from "cypress";
import { plugin } from "./cypress/plugins/index";
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
},
viewportHeight: 1300,
viewportWidth: 1800,
requestTimeout: 10000,
defaultCommandTimeout: 10000,
video: false,
retries: {
runMode: 2,
},
});
e2e: {
//@ts-ignore
setupNodeEvents(on, config) {
return plugin(on, config);
},
baseUrl: "http://localhost:5173",
},
});

View file

@ -0,0 +1,26 @@
const fyllFeilmeldingInputs = () => {
cy.getByTestId('tittel-inputfelt').type('Cypress-test Tittel')
cy.getByTestId('beskrivelse-inputfelt').type('Cypress-test Beskrivelse')
cy.getByTestId('switch-toggle').click()
}
describe('Klarer å melde inn feil', () => {
context('Fyller ut felt, melder inn og går tilbake til hovedside', () => {
beforeEach(() => {
cy.viewport(1920, 1080)
cy.visit('http://localhost:5173/')
})
it('sjekker axe', () => {
cy.checkPageA11y()
})
it('Fyller ut felt og melder inn feil som haster', () => {
cy.contains('Meld inn feil').click()
fyllFeilmeldingInputs()
cy.contains('Meld inn feil').click()
})
})
})

View file

@ -0,0 +1,21 @@
describe('Landingsside loader ordentlig', () => {
context('Loader landingsside i riktig dimensjon', () => {
beforeEach(() => {
cy.viewport(1920, 1080)
cy.visit('http://localhost:5173/')
})
it('sjekker axe', () => {
cy.checkPageA11y()
})
it('bør loade liste med feil', () => {
cy.contains('Speil sier NAV må tilbakekreve sykepenger på feil grunnlag')
cy.contains('Feil A')
cy.contains('Feil B')
cy.contains('Feil C')
cy.contains('Feil D')
cy.contains('Mangel på hensyn til tariffoppgjør')
})
})
})

View file

@ -0,0 +1,16 @@
describe('Sjekker søkefunksjonalitet', () => {
context('Loader landingsside i riktig dimensjon', () => {
beforeEach(() => {
cy.viewport(1920, 1080)
cy.visit('http://localhost:5173/')
})
it('sjekker axe', () => {
cy.checkPageA11y()
})
it('Klarer å søke etter en spesifik feil: "Mangel på hensyn til tariffoppgjør"', () => {
cy.getByTestId('soke-inputfelt').type('Man')
})
})
})

View file

@ -1,43 +0,0 @@
import { slowCypressDown } from 'cypress-slow-down'
// Notat: Husk å kjør opp frontend før tester
slowCypressDown() // gjør at testene ikke kjører dritfort
describe('Landingsside loader ordentlig', () => {
context('Resolution er 1080p', () => {
beforeEach(() => {
cy.viewport(1920, 1080)
})
it('passes', () => {
cy.visit('http://localhost:5173/')
})
})
})
describe('Klarer å navigere frem og tilbake til/fra "Meld inn feil"-side', () => {
context('Går til "Meld inn feil"-siden', () => {
beforeEach(() => {
cy.viewport(1920, 1080)
})
it('passes', () => {
cy.visit('http://localhost:5173/')
cy.contains('Meld inn feil').click()
})
})
context('Går tilbake til hovedsiden', () => {
beforeEach(() => {
cy.viewport(1920, 1080)
})
it('passes', () => {
cy.visit('http://localhost:5173/nyfeil')
cy.contains('Gå tilbake til hovedmenyen').click()
})
})
})

View file

@ -0,0 +1,34 @@
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
* @type {Cypress.PluginConfig}
*/
// eslint-disable-next-line no-unused-vars
export const plugin: Cypress.PluginConfig = (on: Cypress.PluginEvents) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
on("task", {
log(message: string) {
console.log(message);
return null;
},
table(message: string) {
console.table(message);
return null;
},
});
};

View file

@ -1,3 +1,4 @@
import 'cypress-axe'
/// <reference types="cypress" />
// ***********************************************
// This example commands.ts shows you how to
@ -24,14 +25,23 @@
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
//
// declare global {
// namespace Cypress {
// interface Chainable {
// login(email: string, password: string): Chainable<void>
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
// }
// }
// }
Cypress.Commands.add("getByTestId", (selector, ...args) => {
return cy.get(`[data-testid=${selector}]`, ...args);
});
Cypress.Commands.add("checkPageA11y", () => {
cy.injectAxe();
cy.configureAxe({
rules: [
// {
// id: "svg-img-alt",
// enabled: false,
// },
// Skrur av fordi checkA11y ikke vet at div er en gyldig children av <dl>-elementer
],
});
cy.checkA11y(
undefined
);
});

View file

@ -17,4 +17,16 @@
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')
// require('./commands')
declare global {
namespace Cypress {
interface Chainable {
getByTestId(
selector: string,
...rest: any
): Chainable<JQuery<HTMLElement>>;
checkPageA11y(): Chainable<JQuery<HTMLElement>>;
}
}
}

View file

@ -32,6 +32,8 @@
"@typescript-eslint/parser": "^5.61.0",
"@vitejs/plugin-react": "^4.0.1",
"cypress": "^12.17.2",
"cypress-axe": "^1.4.0",
"cypress-slow-down": "^1.2.1",
"eslint": "^8.44.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.1",
@ -652,11 +654,10 @@
}
},
"node_modules/@cypress/request": {
"version": "2.88.11",
"resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.11.tgz",
"integrity": "sha512-M83/wfQ1EkspjkE2lNWNV5ui2Cv7UCv1swW1DqljahbzLVWltcsexQh8jYtuS/vzFXP+HySntGM83ZXA9fn17w==",
"version": "2.88.12",
"resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.12.tgz",
"integrity": "sha512-tOn+0mDZxASFM+cuAP9szGUGPI1HwWVSvdzm7V4cCsPdFTx6qMj29CwaQmRAMIEhORIUBFBsYROYJcveK4uOjA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"aws-sign2": "~0.7.0",
"aws4": "^1.8.0",
@ -673,7 +674,7 @@
"performance-now": "^2.1.0",
"qs": "~6.10.3",
"safe-buffer": "^5.1.2",
"tough-cookie": "~2.5.0",
"tough-cookie": "^4.1.3",
"tunnel-agent": "^0.6.0",
"uuid": "^8.3.2"
},
@ -2035,6 +2036,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/axe-core": {
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.2.tgz",
"integrity": "sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g==",
"dev": true,
"peer": true,
"engines": {
"node": ">=4"
}
},
"node_modules/axios": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
@ -2693,6 +2704,34 @@
"node": "^14.0.0 || ^16.0.0 || >=18.0.0"
}
},
"node_modules/cypress-axe": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/cypress-axe/-/cypress-axe-1.4.0.tgz",
"integrity": "sha512-Ut7NKfzjyKm0BEbt2WxuKtLkIXmx6FD2j0RwdvO/Ykl7GmB/qRQkwbKLk3VP35+83hiIr8GKD04PDdrTK5BnyA==",
"dev": true,
"engines": {
"node": ">=10"
},
"peerDependencies": {
"axe-core": "^3 || ^4",
"cypress": "^10 || ^11 || ^12"
}
},
"node_modules/cypress-plugin-config": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/cypress-plugin-config/-/cypress-plugin-config-1.2.1.tgz",
"integrity": "sha512-z+bQ7oyfDKun51HiCVNBOR+g38/nYRJ7zVdCZT2/9UozzE8P4iA1zF/yc85ePZLy5NOj/0atutoUPBBR5SqjSQ==",
"dev": true
},
"node_modules/cypress-slow-down": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/cypress-slow-down/-/cypress-slow-down-1.2.1.tgz",
"integrity": "sha512-Pd+nESR+Ca8I+mLGbBrPVMEFvJBWxkJcEdcIUDxSBnMoWI00hiIKxzEgVqCv5c6Oap2OPpnrPLbJBwveCNKLig==",
"dev": true,
"dependencies": {
"cypress-plugin-config": "^1.0.0"
}
},
"node_modules/cypress/node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
@ -5200,8 +5239,7 @@
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
"dev": true,
"license": "MIT"
"dev": true
},
"node_modules/pump": {
"version": "3.0.0",
@ -5240,6 +5278,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
"dev": true
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@ -5486,6 +5530,12 @@
"node": ">=0.10.0"
}
},
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
"dev": true
},
"node_modules/resolve": {
"version": "1.22.2",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
@ -6403,17 +6453,27 @@
}
},
"node_modules/tough-cookie": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
"integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"psl": "^1.1.28",
"punycode": "^2.1.1"
"psl": "^1.1.33",
"punycode": "^2.1.1",
"universalify": "^0.2.0",
"url-parse": "^1.5.3"
},
"engines": {
"node": ">=0.8"
"node": ">=6"
}
},
"node_modules/tough-cookie/node_modules/universalify": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
"dev": true,
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/trim-newlines": {
@ -6584,6 +6644,16 @@
"punycode": "^2.1.0"
}
},
"node_modules/url-parse": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
"dev": true,
"dependencies": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
}
},
"node_modules/use-sync-external-store": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",

View file

@ -34,6 +34,7 @@
"@typescript-eslint/parser": "^5.61.0",
"@vitejs/plugin-react": "^4.0.1",
"cypress": "^12.17.2",
"cypress-axe": "^1.4.0",
"cypress-slow-down": "^1.2.1",
"eslint": "^8.44.0",
"eslint-plugin-react-hooks": "^4.6.0",

View file

@ -3,9 +3,9 @@ import { Button, Heading } from "@navikt/ds-react"
/**
* Inkluderer en knapp for opplastning i finder/explorer.
* Inkluderer et felt for drag and drop opplastning av bilder
* @returns `Drag and drop` komponent for filopplastning av skjermbilder
* BildeOpplastningskomponentet brukes for å laste opp skjermbilder av en feil i Speil.
* Komponentet er ment for å støtte både drag-and-drop og vanlig opplasting av bilder.
* @TODO: Implementer funksjonalitet for å laste opp bildefiler -> API og backend
*/
const BildeOpplastning = () => {
return(
@ -13,20 +13,16 @@ const BildeOpplastning = () => {
<Heading size="xsmall">
Skjermbilder
</Heading>
<div className="
h-48 p-5 bg-bg-subtle hover:bg-surface-selected border-2 border-blue-500
rounded-lg border-dashed flex flex-col items-center justify-center text-center gap-2
">
<div className="h-48 p-5 bg-bg-subtle hover:bg-surface-selected border-2 border-blue-500 rounded-lg border-dashed flex flex-col items-center justify-center text-center gap-2">
<FileImageIcon
fontSize="3.5rem"
className="text-blue-500"
/>
<p className="text-surface-neutral">Dra og slipp skjermbilder her!</p>
<p className="text-surface-neutral">
Dra og slipp skjermbilder her!
</p>
</div>
<Button
variant="secondary"
icon={<UploadIcon />}
>
<Button variant="secondary" icon={<UploadIcon />}>
Last opp skjermbilder
</Button>
</div>

View file

@ -7,36 +7,39 @@ import FeilkortHeader from "./FeilkortHeader";
import RedigeringsVerktoy from "./RedigeringsVerktoy";
import FeilmeldingsInnhold from "./FeilmeldingsInnhold";
/**
* En konteiner som inneholder all informasjon og funksjonalitet for å vise og interagere med en feilmelding.
* @param tittel
* @param beskrivelse
* @param dato
* @returns JSX komponent som beskriver innholdet i feilmeldinger.
*/
interface IFeilKort extends IFeilmelding {
key: number
reset: () => void
}
/**
* En konteiner som inneholder all informasjon og funksjonalitet for å vise og interagere med en feilmelding.
* Komponentet rendres hovedsiden, og mappes ut fra en liste med feilmeldinger i KortKonteiner.
* Du kan trykke en feilmelding for å åpne en modal som viser mer informasjon om feilmeldingen, samt mulighet for å redigere.
* @param id er feilmeldingens unike id
* @param tittel
* @param beskrivelse
* @param dato Dato for når feilen ble meldt inn
* @param haster Boolean som beskriver om feilen haster eller ikke.
* @param reset funksjon som kalles når en feilmelding endres. Denne funksjonen kalles for å oppdatere feilmeldingene som vises hovedsiden.
*/
const FeilKort = (props: IFeilKort) => {
const [visModal, setVisModal] = useState<boolean>(false)
const [redigeringsmodus, setRedigeringsmodus] = useState(false)
useEffect(() => {
Modal.setAppElement(document.getElementById('root'));
}, []);
return(
<>
<div
key={props.key}
key={props.id}
className="
bg-bg-default border border-border-default p-7 rounded-lg
hover:bg-bg-subtle hover:border-border-strong hover:shadow-md duration-100
active:bg-surface-active"
onClick={() => setVisModal(true)}
>
>
<FeilkortHeader
id={props.id}
tittel={props.tittel}
@ -44,41 +47,43 @@ const FeilKort = (props: IFeilKort) => {
dato={props.dato}
haster={props.haster}
arbeidsstatus={props.arbeidsstatus}
/>
kommentar={props.kommentar}
/>
</div>
<FeilModal
open={visModal}
setOpen={setVisModal}
>
<FeilModal open={visModal} setOpen={setVisModal}>
{redigeringsmodus ?
<RedigeringsVerktoy
id={props.id}
tittel={props.tittel}
beskrivelse={props.beskrivelse}
dato={props.dato}
haster={props.haster}
arbeidsstatus={props.arbeidsstatus}
setRedigeringsmodus={setRedigeringsmodus}
setVisModal={setVisModal}
reset={props.reset}
id={props.id}
tittel={props.tittel}
beskrivelse={props.beskrivelse}
dato={props.dato}
haster={props.haster}
arbeidsstatus={props.arbeidsstatus}
setRedigeringsmodus={setRedigeringsmodus}
setVisModal={setVisModal}
reset={props.reset}
kommentar={props.kommentar}
aktorId={props.aktorId}
/>
:
:
<FeilmeldingsInnhold
id={props.id}
tittel={props.tittel}
beskrivelse={props.beskrivelse}
dato={props.dato}
haster={props.haster}
arbeidsstatus={props.arbeidsstatus}
setVisModal={setVisModal}
setRedigeringsmodus={setRedigeringsmodus}
reset={props.reset}
id={props.id}
tittel={props.tittel}
beskrivelse={props.beskrivelse}
dato={props.dato}
haster={props.haster}
arbeidsstatus={props.arbeidsstatus}
setVisModal={setVisModal}
setRedigeringsmodus={setRedigeringsmodus}
reset={props.reset}
kommentar={props.kommentar}
aktorId={props.aktorId}
>
<p>Her kommer det content</p>
<p>aktorId: {props.aktorId}</p>
</FeilmeldingsInnhold>
}
</FeilModal>
</>
)
}
export default FeilKort
export default FeilKort

View file

@ -8,10 +8,16 @@ interface modalInterface {
children: React.ReactNode
}
/**
* Modal som brukes til å vise feilmeldinger i fullvisningsmodus med feilinnhold og redigeringsverktøy
* @param open boolean som beskriver om modalen skal være åpen eller ikke
* @param setOpen setter open
* @param children innholdet i modalen
*/
const FeilModal = (props: modalInterface) => {
return(
<Modal
className="w-3/5 min-w-2/4 p-5"
className="w-3/5 p-5"
open={props.open}
aria-label={" modal"}
onClose={() => props.setOpen(false)}

View file

@ -3,14 +3,8 @@ import { IFeilmelding } from "../interface";
import TagBar from "./TagBar";
/**
* FeilkortHeader er komponent som beskriver ikke-sensitiv informasjon om feilmeldingen og vises for alle forsiden.
* FeilkortHeaderen er en del av FeilKort-komponenten og FullvisningsKort-komponenten.
* Midlertidig implementerer komponentet @requires IFeilmelding for props, men dette endres når IFeilmelding utvides i fremtiden for støtte av flere typer feilmeldinger.
* @param tittel
* @param beskrivelse
* @param haster
* @param dato
* @returns JSX komponent som skal vise nødvendig informasjon for å forstå en feil.
* Headeren til et Feilkort, inneholder lite sensitiv informasjon som er beskrivende for feilen (tittel, beskrivelse, dato, haster, arbeidsstatus).
* Komponentet er en del av FeilKort, og er det du kan se når du er hovedsiden.
*/
export const FeilkortHeader = (props: IFeilmelding) => {
return(
@ -20,7 +14,7 @@ export const FeilkortHeader = (props: IFeilmelding) => {
<p className="text-text-subtle mb-4">{props.dato.toDateString()}</p>
<p>{props.beskrivelse}</p>
</div>
<TagBar haster={props.haster} arbeidsstatus={props.arbeidsstatus}/>
<TagBar haster={props.haster} arbeidsstatus={props.arbeidsstatus}/>
</div>
)
}

View file

@ -1,23 +1,31 @@
import { ChatElipsisIcon, PencilIcon, XMarkIcon } from "@navikt/aksel-icons"
import { Button, Heading, TextField } from "@navikt/ds-react"
import { PencilIcon, XMarkIcon } from "@navikt/aksel-icons"
import { Button } from "@navikt/ds-react"
import { FeilmeldingsInnholdInterface } from "../interface"
import FeilkortHeader from "./FeilkortHeader"
import { useState } from "react"
import Skillelinje from "./Skillelinje"
import axios from "axios"
import { Kommentar } from "./Kommentar"
/**
* FeilmeldingsInnhold er et komponent som viser det fulle innholdet til en feilmelding.
* Komponentet er en del av FeilKort, og er det du kan se når du trykker et Feilkort.
* Tilgangen til å vise FeilmeldingsInnhold er skal begrenses til saksbehandlere, utviklere og fagfolk med tjenestlig behov.
*/
const FeilmeldingsInnhold = (props: FeilmeldingsInnholdInterface) => {
const [kommentar, setKommentar] = useState("")
const [kommentarfelt, setKommentarfelt] = useState("")
//kommentar kan være null eller undefined når den kommer fra databasen, derfor må den sjekkes og omgjøres til en tom string om det er tilfellet
const [kommentar, setKommentar] = useState(props.kommentar != (null || undefined) ? props.kommentar : "")
const [kommentarfelt, setKommentarfelt] = useState("")
/**
* Endrer Feilmeldingsobjektet i databasen og setter en ny kommentar den
*/
const oppdaterkommentar = async() => {
setKommentar(kommentarfelt)
const payload = {
id: props.id,
tittel: kommentar,
kommentar: kommentarfelt,
}
await axios.put("/api/oppdaterkommentar", payload, {
headers: {
'Content-Type': 'application/json'
@ -27,10 +35,16 @@ const FeilmeldingsInnhold = (props: FeilmeldingsInnholdInterface) => {
}).catch((error) => {
console.log(error);
})
//TODO: fiks så kommentar oppdateres uten å måtte skjule modalen.
props.setVisModal(false)
props.reset()
}
const fullUpdate = async() => {
setKommentar(kommentarfelt)
oppdaterkommentar()
}
return(
<>
<div className="flex justify-between ">
@ -40,7 +54,9 @@ const FeilmeldingsInnhold = (props: FeilmeldingsInnholdInterface) => {
beskrivelse={props.beskrivelse}
dato={props.dato}
haster={props.haster}
arbeidsstatus={props.arbeidsstatus} />
arbeidsstatus={props.arbeidsstatus}
kommentar={props.kommentar}
/>
<div className="flex gap-4 items-start">
<Button
variant="tertiary"
@ -62,68 +78,15 @@ const FeilmeldingsInnhold = (props: FeilmeldingsInnholdInterface) => {
</div>
<Skillelinje/>
{props.children}
{kommentar.length === 0 ?
<KommentarTekstfelt
kommentarfelt={kommentarfelt}
setKommentarfelt={setKommentarfelt}
oppdaterKommentar={() => oppdaterkommentar()}
/>
:
<Kommentar
tekst={kommentar}
/>
}
<Kommentar
tekst={kommentar}
kommentarfelt={kommentarfelt}
setKommentarfelt={setKommentarfelt}
oppdaterKommentar={fullUpdate}
/>
</>
)
}
export default FeilmeldingsInnhold;
interface Ikommentar {
setKommentarfelt: (val: string) => void
oppdaterKommentar: () => void
}
interface kommentarTekstfeltInterface extends Ikommentar{
kommentarfelt: string,
}
interface kommentarInterface {
tekst: string
}
const KommentarTekstfelt = (props: kommentarTekstfeltInterface) => {
return(
<div className="flex items-end gap-12 w-full mt-4 h-fit">
<TextField
className="grow"
label="Skriv inn din kommentar til feilen"
value={props.kommentarfelt}
onChange={e => props.setKommentarfelt(e.target.value)}
>
</TextField>
<Button
variant="secondary"
icon={<ChatElipsisIcon/>}
onClick={() => props.oppdaterKommentar()}
>
Legg til kommentar
</Button>
</div>
)
}
const Kommentar = (props: kommentarInterface) => {
return(
<>
<Skillelinje/>
<div className="p-5 bg-bg-subtle rounded-lg w-2/3 my-4">
<div className="flex justify-between items-center">
<Heading size="medium">Notat</Heading>
<Button variant="tertiary" icon={<PencilIcon/>}></Button>
</div>
<p className="break-words">{props.tekst}</p>
</div>
</>
)
}

View file

@ -1,14 +1,70 @@
import { Accordion, Checkbox, CheckboxGroup, Radio, RadioGroup } from "@navikt/ds-react"
import { useState } from "react"
import React, { useState } from "react"
/**
* Filtermenyen er en komponent som inneholder alle filterene som kan brukes for å filtrere feil.
* Komponentet rendres hovedsiden.
* Foreløpig holder den filter for kategorier, status, prioritet og mine innmeldinger.
*/
const Filtermeny = () => {
const [visningstype, settVisningstype] = useState(false)
const [prioritet, settPrioritet] = useState(false)
return (
<div className="bg-bg-default w-500">
<Accordion>
<KategoriFilter/>
<StatusFilter/>
<PrioritetFilter/>
<MineInnmeldinger/>
<FilterModul
tittel="Kategorier"
beskrivelse="Denne løsningen er litt mer avansert enn den andre filtreringen. UFERDIG!!!!!!!!!!"
>
</FilterModul>
<FilterModul
tittel="Status"
beskrivelse="
Ved å filtrere etter status forskjellig innmeldte feil
kan du enkelt oversikt over hvilke feil som ikke er påbegynte,
hvilke som utredes av utviklingsteamet og løste feil."
>
<CheckboxGroup legend="Hvilke statusflagg ønsker du å vise?">
<Checkbox value="Ikke påbegynt">Velg alle</Checkbox>
<Checkbox value="Ikke påbegynt">Ikke påbegynt</Checkbox>
<Checkbox value="Jobbes med">Jobbes med</Checkbox>
<Checkbox value="Ferdig med">Ferdig med</Checkbox>
</CheckboxGroup>
</FilterModul>
<FilterModul
tittel="Prioritet"
beskrivelse="
Saker som haster å løse kan merkes med et haster flagg.
For å raskt finne ut av hvilke saker som løses raskt,
kan man velge å kun vise hastesaker."
>
<RadioGroup
legend="Velg visningstype"
value={prioritet}
onChange={(nyPrioritet: any) => settPrioritet(nyPrioritet)}
>
<Radio value={false}>Alle feil</Radio>
<Radio value={true}>Kun feil som haster</Radio>
</RadioGroup>
</FilterModul>
<FilterModul
tittel="Mine innmeldinger"
beskrivelse="Som saksbehandler kan det være nyttig å finne tilbake til feil man har meldt inn for å sjekke status eller konklusjon."
>
<RadioGroup
legend="Velg visningstype"
value={visningstype}
onChange={(nyVisningstype: any) => settVisningstype(nyVisningstype)}
>
<Radio value={false}>Alle feil</Radio>
<Radio value={true}>Kun egne feil</Radio>
</RadioGroup>
</FilterModul>
</Accordion>
</div>
)
@ -16,99 +72,31 @@ const Filtermeny = () => {
export default Filtermeny
const KategoriFilter = () => {
return (
/**
* Filtermodul utgjør en modul for de forskjellige filterene som kan brukes i Filtermenyen.
* Komponentet bygger aksel sin Accordion komponent og enkelt elementer i denne.
* Komponentet kan derfor ikke brukes utenfor en Accordion.
* @param tittel Tittelen filteret
* @param beskrivelse Beskrivelse av hvordan kan brukes
* @param children Innholdet i en Filtermodul, som er kontrollkomponenter for filteret
* @returns
*/
const FilterModul = (props: ItemInterface) => {
return(
<Accordion.Item>
<Accordion.Header>
Kategori
{props.tittel}
</Accordion.Header>
<Accordion.Content>
<p>
Denne løsningen er litt mer avansert enn den andre filtreringen. UFERDIG!!!!!!!!!!!
</p>
<p>{props.beskrivelse}</p>
<br/>
{props.children}
</Accordion.Content>
</Accordion.Item>
)
}
const StatusFilter = () => {
return(
<Accordion.Item>
<Accordion.Header>
Status
</Accordion.Header>
<Accordion.Content>
<p>
Ved å filtrere etter status forskjellig innmeldte feil
kan du enkelt oversikt over hvilke feil som ikke er påbegynte,
hvilke som utredes av utviklingsteamet og løste feil.
</p>
<br/>
<CheckboxGroup
legend="Hvilke statusflagg ønsker du å vise?"
>
<Checkbox value="Ikke påbegynt">Velg alle</Checkbox>
<Checkbox value="Ikke påbegynt">Ikke påbegynt</Checkbox>
<Checkbox value="Jobbes med">Jobbes med</Checkbox>
<Checkbox value="Ferdig med">Ferdig med</Checkbox>
</CheckboxGroup>
</Accordion.Content>
</Accordion.Item>
)
}
const PrioritetFilter = () => {
const [verdi, settVerdi] = useState(false)
return(
<Accordion.Item>
<Accordion.Header>
Prioritet
</Accordion.Header>
<Accordion.Content>
<p>
Saker som haster å løse kan merkes med et haster flagg.
For å raskt finne ut av hvilke saker som løses raskt,
kan man velge å kun vise hastesaker.
</p>
<br/>
<RadioGroup
legend="Velg visningstype"
value={verdi}
onChange={(nyVerdi: any) => settVerdi(nyVerdi)}
>
<Radio value={false}>Alle feil</Radio>
<Radio value={true}>Kun feil som haster</Radio>
</RadioGroup>
</Accordion.Content>
</Accordion.Item>
)
}
const MineInnmeldinger = () => {
const [verdi, settVerdi] = useState(false)
return(
<Accordion.Item>
<Accordion.Header>
Mine innmeldinger
</Accordion.Header>
<Accordion.Content>
<p>
Som saksbehandler kan det være nyttig å finne tilbake til feil man har meldt inn for å sjekke status eller konklusjon.
</p>
<br/>
<RadioGroup
legend="Velg visningstype"
value={verdi}
onChange={(nyVerdi: any) => settVerdi(nyVerdi)}
>
<Radio value={false}>Alle feil</Radio>
<Radio value={true}>Kun egne feil</Radio>
</RadioGroup>
</Accordion.Content>
</Accordion.Item>
)
interface ItemInterface {
tittel: string
beskrivelse: string
children: React.ReactNode
}

View file

@ -1,20 +1,14 @@
import { MenuGridIcon } from "@navikt/aksel-icons"
import { InternalHeader, Dropdown } from "@navikt/ds-react"
import { InternalHeader } from "@navikt/ds-react"
/**
* Headeren til applikasjonen, inneholder logo og lenke til hovedsiden.
*/
const Header = () => {
return(
<InternalHeader>
<InternalHeader.Title href="/#home">
Sprik
</InternalHeader.Title>
<Dropdown>
<InternalHeader.Button
as={Dropdown.Toggle}
>
<MenuGridIcon title="MenuGridIconer og oppslagsverk" />
</InternalHeader.Button>
{/* <Dropdown.Menu /> */}
</Dropdown>
</InternalHeader>
)
}

View file

@ -0,0 +1,73 @@
import { ChatElipsisIcon, PencilIcon } from "@navikt/aksel-icons"
import { TextField, Button, Heading } from "@navikt/ds-react"
import Skillelinje from "./Skillelinje"
import { useState } from "react"
interface kommentarInterface {
kommentarfelt: string,
setKommentarfelt: (val: string) => void
oppdaterKommentar: () => void
tekst: string
}
/**
* Kommentartekstfeltet er et tekstfelt med en knapp som poster en kommentar til en feil.
*/
export const KommentarTekstfelt = (props: kommentarInterface) => {
return (
<div className="flex items-end gap-12 w-full mt-4 h-fit">
<TextField
className="grow"
label="Skriv inn din kommentar til feilen"
value={props.kommentarfelt}
onChange={e => props.setKommentarfelt(e.target.value)}
>
</TextField>
<Button
variant="secondary"
icon={<ChatElipsisIcon />}
onClick={() => props.oppdaterKommentar()}
>
Legg til kommentar
</Button>
</div>
)
}
/**
* Kommentar er en komponent som viser en kommentar til en feil.
* Komentaren kan beskrive konklusjonen til en feil, eller være en oppdatering statusen til en feil.
* @param tekst
*/
export const Kommentar = (props: kommentarInterface) => {
const [redigerKommentar, setRedigerKommentar] = useState(false)
return (
<>
<Skillelinje />
{props.tekst.length === 0 || redigerKommentar ?
<KommentarTekstfelt
kommentarfelt={props.kommentarfelt}
setKommentarfelt={props.setKommentarfelt}
oppdaterKommentar={props.oppdaterKommentar}
tekst={props.tekst}
/>
:
<div className="flex flex-col gap-3 p-5 bg-bg-subtle rounded-lg w-2/3 my-4">
<div className="flex justify-between items-center">
<Heading size="medium">Kommentar</Heading>
<Button
variant="tertiary"
icon={<PencilIcon />}
onClick={() => {
setRedigerKommentar(true);
console.log(redigerKommentar);
}}
></Button>
</div>
<p className="break-words">{props.tekst}</p>
</div>
}
</>
)
}

View file

@ -13,17 +13,18 @@ interface IKortKonteiner {
const KortKonteiner = (props: IKortKonteiner) => {
return (
<div className="grid grid-cols-2 gap-6">
{props.feilmeldinger.map((feilMelding) => (
<FeilKort
key={props.feilmeldinger.indexOf(feilMelding)}
id={feilMelding.id}
tittel={feilMelding.tittel}
beskrivelse={feilMelding.beskrivelse}
dato={new Date()}
haster={feilMelding.haster}
dato={new Date(feilMelding.dato)}
arbeidsstatus={feilMelding.arbeidsstatus}
haster={feilMelding.haster}
reset={props.reset}
kommentar={feilMelding.kommentar}
aktorId={feilMelding.aktorid}
/>
))
}

View file

@ -1,4 +1,4 @@
import { FloppydiskIcon, XMarkIcon } from "@navikt/aksel-icons"
import { FloppydiskIcon, TrashIcon, XMarkIcon } from "@navikt/aksel-icons"
import { TextField, Textarea, RadioGroup, Radio, Button, Switch, Heading } from "@navikt/ds-react"
import { useState } from "react"
import { FeilmeldingsInnholdInterface } from "../interface"
@ -9,12 +9,18 @@ interface redigeringsInterface extends FeilmeldingsInnholdInterface {
reset: () => void
}
/**
* Redigeringsverktøy er et skjema som lar brukeren redigere en innmeldt feil.
* Statusflagg som arbeidsstatus og haster flagg kan endres.
* Tittel og beskrivelse kan også endres
*/
const RedigeringsVerktoy = (props: redigeringsInterface) => {
const [tittel, setTittel] = useState(props.tittel)
const [beskrivelse, setBeskrivelse] = useState(props.beskrivelse)
const [arbeidsstatus, setArbeidsstatus] = useState(props.arbeidsstatus)
const [haster, setHaster] = useState(props.haster)
const lagreEndringer = async() => {
props.setVisModal(false)
props.setRedigeringsmodus(false)
@ -25,10 +31,12 @@ const RedigeringsVerktoy = (props: redigeringsInterface) => {
beskrivelse: beskrivelse,
dato: props.dato.toISOString().replace('Z', ''),
arbeidsstatus: arbeidsstatus,
haster: haster
haster: haster,
kommentar: props.kommentar,
aktorid: props.aktorId
}
await axios.put(`/api/oppdaterfeil/${props.id}`, payload, {
await axios.put(`/api/oppdaterfeil`, payload, {
headers: {
'Content-Type': 'application/json'
}
@ -43,11 +51,10 @@ const RedigeringsVerktoy = (props: redigeringsInterface) => {
return (
<div className="flex flex-col gap-12 items-center px-12">
<div className="flex flex-col gap-6 w-full">
<Heading className="" size="large">
Rediger feil
</Heading>
<Heading className="" size="large">
Rediger feil
</Heading>
<TextField
label="Tittel"
value={tittel}
@ -95,8 +102,32 @@ const RedigeringsVerktoy = (props: redigeringsInterface) => {
>
Avbryt
</Button>
<SlettFeilKnapp setVisModal={props.setVisModal} reset={props.reset} id={props.id}/>
</div>
</div>
)
}
export default RedigeringsVerktoy;
const SlettFeilKnapp = (props: {
id : number
reset: () => void
setVisModal: (visModal: boolean) => void
}) => {
const SlettFeil = async () => {
await axios.delete(`/api/slettfeilmelding/${props.id}`)
props.reset()
props.setVisModal(false)
}
return(
<Button
variant="danger"
icon={<TrashIcon/>}
onClick={SlettFeil}
>
Slett feil
</Button>
)
}

View file

@ -1,5 +1,8 @@
/**
* Skillelinjer som brukes for å dele opp innholdet i et komponent/skjema i mindre deler
* Forbedrer lesbarheten til skjemaet
*/
const Skillelinje = () => {
return (
<div className="h-1 bg-gray-200 my-3 rounded-lg w-full"></div>

View file

@ -4,33 +4,31 @@ interface TagBarInterface {
haster: boolean
arbeidsstatus: number
}
/**
* @param arbeidsstatus er en kode som beskriver om feilen er fikset, jobbes med eller ikke påbegynt. være 0, 1 eller 2.
* @returns Tag komponent med riktig farge og tekst basert arbeidsstatus-kode
*/
const toggleArbeidsstatus = (arbeidsstatus: number) => {
switch (arbeidsstatus) {
case 0:
return <Tag variant="neutral">Ikke påbegynt</Tag>;
case 1:
return <Tag variant="info">Feilen jobbes med</Tag>;
case 2:
return <Tag variant="success">Feilen er fikset</Tag>;
default:
throw new Error("Ikke gyldig arbeidsstatus-kode. Koden må være 0, 1 eller 2");
}
}
/**
* Komponentet er en bar (vanrett linje) som inneholder to statusflagg: "arbeidsstatus" og "Haster".
* Komponentet er en del av FeilKortHeader
* @param haster er en boolean som beskriver om feilen haster eller ikke.
* @param arbeidsstatus
* @param arbeidsstatus er en kode som beskriver om feilen er fikset, jobbes med eller ikke påbegynt. være 0, 1 eller 2.
*/
const TagBar = (props: TagBarInterface) => {
/**
* Funksjonen tar inn
* @param arbeidsstatus
* @returns
*/
const toggleArbeidsstatus = (arbeidsstatus: number) => {
switch (arbeidsstatus) {
case 0:
return <Tag variant="neutral">Ikke påbegynt</Tag>;
case 1:
return <Tag variant="info">Feilen jobbes med</Tag>;
case 2:
return <Tag variant="success">Feilen er fikset</Tag>;
default:
throw new Error("Ikke gyldig arbeidsstatus-kode. Koden må være 0, 1 eller 2");
}
}
return (
<div className="flex gap-3 mt-4">
{toggleArbeidsstatus(props.arbeidsstatus)}

View file

@ -1,6 +1,6 @@
import KortKonteiner from "./components/KortKonteiner";
import "@navikt/ds-css";
import { Button, Search } from "@navikt/ds-react";
import { Button, Heading, Search } from "@navikt/ds-react";
import Header from "./components/Header";
import { PlusIcon } from "@navikt/aksel-icons";
import Filtermeny from "./components/Filtermeny";
@ -9,39 +9,28 @@ import axios from "axios";
import { useEffect, useState } from "react";
import { Feilmelding } from "./interface";
/**
* Hovedsiden til applikasjonen, viser alle innmeldte feil.
* Tilbyr søkefelt og filtreringsmuligheter
* Tilbyr også navigering til feilinnmeldingssiden
*/
export default function Home() {
const navigate = useNavigate()
const [feilmeldinger, setFeilmeldinger] = useState<Feilmelding[]>([]);
/**
* Henter alle feilmeldinger fra backend.
* Bruker endepunktet /api/hentallefeil.
*/
const hentAlleFeil = async () => {
console.log("hentAlleFeil");
await axios.get("/api/hentallefeil")
.then(data => data.data)
.then(feil => {
setFeilmeldinger(
feil.map((jsonFeilmelding: any) => new Feilmelding(jsonFeilmelding))
);
})
const hentAlleFeil = async () => {
const { data } = await axios.get("/api/hentallefeil")
setFeilmeldinger(data)
}
// Sørger for at hentAlleFeil() kun kjører når komponentet laster inn
useEffect(() => {
hentAlleFeil();
}, [])
/**
* Oppdaterer viste feilmeldinger ved endring i søkefelt.
* Kontakter endepunktet /api/hentsok/.
* @param soketekst
*/
//oppdaterer visningen av feilmeldinger når søkefeltet endres
const handleSearch = async (soketekst: string) => {
// Ved tomt søkefelt hentes alle feilmeldinger
if (soketekst === "") {
hentAlleFeil()
return
@ -50,14 +39,17 @@ export default function Home() {
setFeilmeldinger(data)
}
return (
<main className="flex flex-col h-screen">
<Header/>
<div className="flex grow">
<Filtermeny/>
<div className="grow bg-bg-subtle px-32 py-8 flex flex-col gap-10">
<Heading level="1" size="xlarge">Innmeldte feil</Heading>
<div className="flex gap-12 items-end">
<Search
data-testid="soke-inputfelt"
label="Søkefelt"
description="Søk gjennom innmeldte feil (nøkkelord, tags, status)"
hideLabel={false}
@ -65,7 +57,7 @@ export default function Home() {
/>
<Button
className="w-64 h-min"
icon={<PlusIcon/>}
icon={<PlusIcon aria-label="PlusIcon"/>}
onClick={() => navigate("nyfeil")}
>
Meld inn feil

View file

@ -1,15 +1,12 @@
/*
* I denne filen kan vi legge interfaces som skal brukes over flere steder!
*/
export interface IFeilmelding {
id: number,
tittel: string,
beskrivelse: string
dato: Date
haster: boolean
arbeidsstatus: number
haster: boolean
kommentar?: string
aktorId?: number
}
export interface FeilmeldingsInnholdInterface extends IFeilmelding {
@ -19,26 +16,30 @@ export interface FeilmeldingsInnholdInterface extends IFeilmelding {
reset: () => void
}
/**
* En klasse som implementerer IFeilmelding interfacet.
* Brukes for å mappe JSON objekter til en klasse ved henting av data fra backend
*/
export class Feilmelding implements IFeilmelding {
id: number = 0
tittel: string = "default tittel"
beskrivelse: string = "default beskrivelse"
dato: Date = new Date()
haster: boolean = false
arbeidsstatus: number = 0
haster: boolean = false
kommentar?: string = undefined
aktorid?: number = undefined
/**
* Typescript 2.1 syntax som lar deg sende inn et JSON object og mappe det til class.
* https://stackoverflow.com/questions/14142071/typescript-and-field-initializers
*/
public constructor(
fields: {
id: number
tittel: string
beskrivelse: string
dato: Date
haster: boolean
arbeidsstatus: number
haster: boolean
kommentar: string
aktorId: number
}) {
if (fields) Object.assign(this, fields);
}

View file

@ -5,6 +5,7 @@ import './index.css'
import Home from './index'
import Feil from './pages/feil'
const router = createBrowserRouter([
{
path: "/",

View file

@ -1,7 +1,7 @@
import "@navikt/ds-css";
import { ArrowLeftIcon, BugIcon } from "@navikt/aksel-icons";
import { Alert, Button, Heading, Switch, TextField, Textarea } from "@navikt/ds-react";
import { Alert, Button, Chips, Heading, Switch, TextField, Textarea } from "@navikt/ds-react";
import axios from "axios";
import { useState } from "react";
import BildeOpplastning from "../components/BildeOpplastning";
@ -9,24 +9,37 @@ import Header from "../components/Header";
import { useNavigate } from "react-router-dom";
import Skillelinje from "../components/Skillelinje";
/**
* Siden for å melde inn feil i speil.
*/
export default function Feil() {
const [tittel, setTittel] = useState("");
const [beskrivelse, setBeskrivelse] = useState("");
const [status, setStatus] = useState(0)
const [haster, setHaster] = useState(false)
const handleSubmit = () => {
const [valgteTags, setValgteTags] = useState([] as string[]);
const [aktorid, setAktorid] = useState<number|null>(null);
const navigate = useNavigate()
const tags = [
"Utbetaling",
"Inntekt",
"Speil",
"Annet"
];
//Sender feil til databasen
const meldInnFeil = () => {
const payload = {
id: null,
tittel: tittel,
beskrivelse: beskrivelse,
dato: new Date().toISOString().replace('Z', ''), // Litt wack fix, burde endres
dato: new Date().toISOString().replace('Z', ''),
arbeidsstatus: 0,
haster: haster,
kommentar: null
kommentar: null,
aktorid: aktorid ? aktorid : null,
//TODO: kategorier: valgteTags
}
axios.post("/api/nyfeil", payload, {
headers: {
'Content-Type': 'application/json'
@ -38,6 +51,7 @@ export default function Feil() {
})
}
//Håndterer alerts som vises etter at feil er lagt til i databasen eller feiler i å bli lagt til
const handleAlerts = () => {
if (status === 201) {
console.log("Feil lagt til i database");
@ -53,17 +67,13 @@ export default function Feil() {
}
}
// TODO: clear data fra felter
const navigate = useNavigate()
return (
<main className="flex flex-col h-screen">
<Header/>
<div className="flex grow">
<div className="w-1/4 bg-bg-subtle"></div>
<div className="flex flex-col justify-center gap-8 p-8 px-16 grow">
<div className="flex items">
<div className=" bg-bg-subtle grow"></div>
<div className="w-3/5 flex flex-col justify-center gap-8 px-16 py-8">
<div className=" flex flex-col gap-2 justify-center">
<BugIcon
title="Insekts ikon"
@ -78,32 +88,65 @@ export default function Feil() {
</div>
<div className="w-1/2 flex flex-col gap-4 justify-center">
<TextField
data-testid="tittel-inputfelt"
label="Tittel"
onChange={e => setTittel(e.target.value)}
/>
<Textarea
data-testid="beskrivelse-inputfelt"
label="Beskrivelse"
description="Detaljert beskrivelse av problemet"
onChange={e => setBeskrivelse(e.target.value)}
/>
<Skillelinje/>
<BildeOpplastning/>
<Skillelinje/>
<Heading size="xsmall">
Haster det å fikse feilen?
</Heading>
<Switch
<Switch
data-testid="switch-toggle"
onClick={() => setHaster(!haster)}
>
Saken Haster
</Switch>
<Skillelinje/>
<TextField
label="Aktør-ID (valgfritt)"
description="Legg ved aktør-ID om det er relevant"
onChange={e => setAktorid(parseInt(e.target.value))}
/>
<Skillelinje/>
<Heading size="xsmall">
Velg kategorier som passer
</Heading>
<Chips>
{tags.map((c) => (
<Chips.Toggle
selected={valgteTags.includes(c)}
key={c}
onClick={() =>
setValgteTags(
valgteTags.includes(c)
? valgteTags.filter((x) => x !== c)
: [...valgteTags, c]
)
}
>
{c}
</Chips.Toggle>
))}
</Chips>
</div>
<div className="w-1/2 flex flex-col gap-2 justify-center">
<div className="w-1/2 flex flex-col gap-2 justify-center mt-8">
{status != 0 ? handleAlerts() : <></>}
<Button
onClick={handleSubmit}
onClick={meldInnFeil}
variant="primary"
>
Meld inn feil
@ -117,7 +160,8 @@ export default function Feil() {
</Button>
</div>
</div>
<div className="w-1/4 bg-bg-subtle"></div>
<div className=" bg-bg-subtle grow"></div>
</div>
</main>
)

View file

@ -250,9 +250,9 @@
integrity sha512-hBI9tfBtuPIi885ZsZ32IMEU/5nlZH/KOVYJCOh7gyMxaVLGmLedYqFN6Ui1LXkI8JlC8IsuC0rF0btcRZKd5g==
"@cypress/request@^2.88.11":
version "2.88.11"
resolved "https://registry.npmjs.org/@cypress/request/-/request-2.88.11.tgz"
integrity sha512-M83/wfQ1EkspjkE2lNWNV5ui2Cv7UCv1swW1DqljahbzLVWltcsexQh8jYtuS/vzFXP+HySntGM83ZXA9fn17w==
version "2.88.12"
resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.12.tgz#ba4911431738494a85e93fb04498cb38bc55d590"
integrity sha512-tOn+0mDZxASFM+cuAP9szGUGPI1HwWVSvdzm7V4cCsPdFTx6qMj29CwaQmRAMIEhORIUBFBsYROYJcveK4uOjA==
dependencies:
aws-sign2 "~0.7.0"
aws4 "^1.8.0"
@ -269,7 +269,7 @@
performance-now "^2.1.0"
qs "~6.10.3"
safe-buffer "^5.1.2"
tough-cookie "~2.5.0"
tough-cookie "^4.1.3"
tunnel-agent "^0.6.0"
uuid "^8.3.2"
@ -1457,14 +1457,19 @@ csstype@^3.0.2:
resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz"
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
cypress-axe@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/cypress-axe/-/cypress-axe-1.4.0.tgz"
integrity sha512-Ut7NKfzjyKm0BEbt2WxuKtLkIXmx6FD2j0RwdvO/Ykl7GmB/qRQkwbKLk3VP35+83hiIr8GKD04PDdrTK5BnyA==
cypress-plugin-config@^1.0.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/cypress-plugin-config/-/cypress-plugin-config-1.2.1.tgz#aa7eaa55ab5ce5e186ab7d0e37cc7e42bfb609b4"
resolved "https://registry.npmjs.org/cypress-plugin-config/-/cypress-plugin-config-1.2.1.tgz"
integrity sha512-z+bQ7oyfDKun51HiCVNBOR+g38/nYRJ7zVdCZT2/9UozzE8P4iA1zF/yc85ePZLy5NOj/0atutoUPBBR5SqjSQ==
cypress-slow-down@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/cypress-slow-down/-/cypress-slow-down-1.2.1.tgz#d10c75c2f79240812f4e58824e09d057a2d0e24d"
resolved "https://registry.npmjs.org/cypress-slow-down/-/cypress-slow-down-1.2.1.tgz"
integrity sha512-Pd+nESR+Ca8I+mLGbBrPVMEFvJBWxkJcEdcIUDxSBnMoWI00hiIKxzEgVqCv5c6Oap2OPpnrPLbJBwveCNKLig==
dependencies:
cypress-plugin-config "^1.0.0"
@ -3030,9 +3035,9 @@ proxy-from-env@^1.1.0:
resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
psl@^1.1.28:
psl@^1.1.33:
version "1.9.0"
resolved "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==
pump@^3.0.0:
@ -3055,6 +3060,11 @@ qs@~6.10.3:
dependencies:
side-channel "^1.0.4"
querystringify@^2.1.1:
version "2.2.0"
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
@ -3188,6 +3198,11 @@ require-from-string@^2.0.2:
resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
requires-port@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz"
@ -3625,13 +3640,15 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"
tough-cookie@~2.5.0:
version "2.5.0"
resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz"
integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
tough-cookie@^4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf"
integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==
dependencies:
psl "^1.1.28"
psl "^1.1.33"
punycode "^2.1.1"
universalify "^0.2.0"
url-parse "^1.5.3"
trim-newlines@^4.0.2:
version "4.1.1"
@ -3699,6 +3716,11 @@ typescript@^5.0.2:
resolved "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz"
integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==
universalify@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==
universalify@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz"
@ -3724,6 +3746,14 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"
url-parse@^1.5.3:
version "1.5.10"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
dependencies:
querystringify "^2.1.1"
requires-port "^1.0.0"
use-sync-external-store@^1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"

View file

@ -1,4 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1