Skip to content

Commit a4dd095

Browse files
Merge pull request #407 from Bostads-AB-Mimer/feature/mim-1370-gor-releasenotes-till-popup
Feature/mim 1370 gor releasenotes till popup
2 parents d213b19 + 8fed67e commit a4dd095

File tree

6 files changed

+269
-107
lines changed

6 files changed

+269
-107
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { Pin } from 'lucide-react'
2+
import { Badge } from '@/components/ui/v2/Badge'
3+
import {
4+
RELEASE_NOTE_CATEGORY_LABELS,
5+
RELEASE_NOTE_CATEGORY_ICONS,
6+
RELEASE_NOTE_BADGE_STYLES,
7+
RELEASE_NOTE_ICON_STYLES,
8+
formatReleaseNoteDate,
9+
} from '@/data/release-notes'
10+
import type { ReleaseNote } from '@/services/types'
11+
12+
interface ReleaseNoteItemProps {
13+
note: ReleaseNote
14+
/** Optional id attribute for scroll targeting */
15+
id?: string
16+
/** Optional click handler to make the item interactive */
17+
onClick?: () => void
18+
/** Additional className for the container */
19+
className?: string
20+
}
21+
22+
/**
23+
* Shared component for displaying a single release note item.
24+
* Used in both ReleaseNotesCard and ReleaseNotesModal.
25+
*/
26+
export function ReleaseNoteItem({
27+
note,
28+
id,
29+
onClick,
30+
className = '',
31+
}: ReleaseNoteItemProps) {
32+
const Icon = RELEASE_NOTE_CATEGORY_ICONS[note.category]
33+
34+
const content = (
35+
<>
36+
<div
37+
className={`p-2 rounded-full flex-shrink-0 ${RELEASE_NOTE_ICON_STYLES[note.category]}`}
38+
>
39+
<Icon className="h-4 w-4" />
40+
</div>
41+
<div className="flex-1 min-w-0 space-y-1">
42+
<div className="flex items-center gap-2 flex-wrap">
43+
<Badge className={RELEASE_NOTE_BADGE_STYLES[note.category]}>
44+
{RELEASE_NOTE_CATEGORY_LABELS[note.category]}
45+
</Badge>
46+
{note.pinned && <Pin className="h-3 w-3 text-muted-foreground" />}
47+
<span className="text-sm text-muted-foreground">
48+
{formatReleaseNoteDate(note.date)}
49+
</span>
50+
</div>
51+
<p className="font-semibold">{note.title}</p>
52+
<p className="text-sm text-muted-foreground leading-relaxed">
53+
{note.description}
54+
</p>
55+
</div>
56+
</>
57+
)
58+
59+
if (onClick) {
60+
return (
61+
<button
62+
type="button"
63+
id={id}
64+
onClick={onClick}
65+
className={`flex items-start gap-4 w-full text-left cursor-pointer rounded-lg p-2 -m-2 hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors ${className}`}
66+
>
67+
{content}
68+
</button>
69+
)
70+
}
71+
72+
return (
73+
<div id={id} className={`flex items-start gap-4 ${className}`}>
74+
{content}
75+
</div>
76+
)
77+
}

apps/property-tree/src/components/dashboard/ReleaseNotesCard.tsx

Lines changed: 43 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -2,121 +2,36 @@ import { useState, useMemo } from 'react'
22
import { motion, AnimatePresence } from 'framer-motion'
33
import {
44
Newspaper,
5-
Sparkles,
6-
Bug,
7-
Zap,
85
ChevronLeft,
96
ChevronRight,
107
ChevronDown,
118
ChevronUp,
12-
Info,
13-
AlertTriangle,
14-
Pin,
159
} from 'lucide-react'
1610
import {
1711
Card,
1812
CardContent,
1913
CardHeader,
2014
CardTitle,
2115
} from '@/components/ui/v2/Card'
22-
import { Badge } from '@/components/ui/v2/Badge'
23-
import {
24-
RELEASE_NOTES,
25-
RELEASE_NOTE_CATEGORY_LABELS,
26-
} from '@/data/release-notes'
27-
import type { ReleaseNote, ReleaseNoteCategory } from '@/services/types'
16+
import { RELEASE_NOTES, sortReleaseNotesByPinned } from '@/data/release-notes'
17+
import { ReleaseNoteItem } from './ReleaseNoteItem'
18+
import { ReleaseNotesModal } from './ReleaseNotesModal'
19+
import { SupportMessage } from './SupportMessage'
2820

2921
const ITEMS_PER_PAGE = 3
3022

31-
const categoryIcons: Record<ReleaseNoteCategory, React.ElementType> = {
32-
feature: Sparkles,
33-
fix: Bug,
34-
improvement: Zap,
35-
info: Info,
36-
warning: AlertTriangle,
37-
}
38-
39-
const categoryBadgeStyles: Record<ReleaseNoteCategory, string> = {
40-
feature: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300',
41-
fix: 'bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300',
42-
improvement:
43-
'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300',
44-
info: 'bg-sky-100 text-sky-700 dark:bg-sky-900/30 dark:text-sky-300',
45-
warning:
46-
'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300',
47-
}
48-
49-
const categoryIconStyles: Record<ReleaseNoteCategory, string> = {
50-
feature: 'bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400',
51-
fix: 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-400',
52-
improvement:
53-
'bg-green-100 text-green-600 dark:bg-green-900/30 dark:text-green-400',
54-
info: 'bg-sky-100 text-sky-600 dark:bg-sky-900/30 dark:text-sky-400',
55-
warning:
56-
'bg-amber-100 text-amber-600 dark:bg-amber-900/30 dark:text-amber-400',
57-
}
58-
59-
function formatDate(dateString: string): string {
60-
return new Date(dateString).toLocaleDateString('sv-SE', {
61-
year: 'numeric',
62-
month: 'short',
63-
day: 'numeric',
64-
})
65-
}
66-
67-
interface ReleaseNoteItemProps {
68-
note: ReleaseNote
69-
index: number
70-
}
71-
72-
function ReleaseNoteItem({ note, index }: ReleaseNoteItemProps) {
73-
const Icon = categoryIcons[note.category]
74-
75-
return (
76-
<motion.div
77-
initial={{ opacity: 0, y: 10 }}
78-
animate={{ opacity: 1, y: 0 }}
79-
exit={{ opacity: 0, y: -10 }}
80-
transition={{ delay: index * 0.05 }}
81-
>
82-
<div className="flex items-start gap-4">
83-
<div
84-
className={`p-2 rounded-full flex-shrink-0 ${categoryIconStyles[note.category]}`}
85-
>
86-
<Icon className="h-4 w-4" />
87-
</div>
88-
<div className="flex-1 min-w-0 space-y-1">
89-
<div className="flex items-center gap-2 flex-wrap">
90-
<Badge className={categoryBadgeStyles[note.category]}>
91-
{RELEASE_NOTE_CATEGORY_LABELS[note.category]}
92-
</Badge>
93-
{note.pinned && <Pin className="h-3 w-3 text-muted-foreground" />}
94-
<span className="text-sm text-muted-foreground">
95-
{formatDate(note.date)}
96-
</span>
97-
</div>
98-
<p className="font-semibold">{note.title}</p>
99-
<p className="text-sm text-muted-foreground leading-relaxed">
100-
{note.description}
101-
</p>
102-
</div>
103-
</div>
104-
</motion.div>
105-
)
106-
}
107-
10823
export function ReleaseNotesCard() {
10924
const [page, setPage] = useState(0)
11025
const [isCollapsed, setIsCollapsed] = useState(false)
26+
const [isModalOpen, setIsModalOpen] = useState(false)
27+
const [scrollToNoteId, setScrollToNoteId] = useState<string | undefined>()
28+
29+
const openModal = (noteId?: string) => {
30+
setScrollToNoteId(noteId)
31+
setIsModalOpen(true)
32+
}
11133

112-
// Sort notes: pinned first, then by date
113-
const sortedNotes = useMemo(() => {
114-
return [...RELEASE_NOTES].sort((a, b) => {
115-
if (a.pinned && !b.pinned) return -1
116-
if (!a.pinned && b.pinned) return 1
117-
return 0 // Keep original order (already sorted by date)
118-
})
119-
}, [])
34+
const sortedNotes = useMemo(() => sortReleaseNotesByPinned(RELEASE_NOTES), [])
12035

12136
const totalPages = Math.ceil(sortedNotes.length / ITEMS_PER_PAGE)
12237
const startIndex = page * ITEMS_PER_PAGE
@@ -131,7 +46,10 @@ export function ReleaseNotesCard() {
13146
return (
13247
<Card>
13348
<CardHeader className="flex flex-row items-center justify-between">
134-
<CardTitle className="flex items-center gap-2">
49+
<CardTitle
50+
className="flex items-center gap-2 cursor-pointer hover:text-primary transition-colors"
51+
onClick={() => openModal()}
52+
>
13553
<Newspaper className="h-5 w-5 text-primary" />
13654
Nyheter och uppdateringar
13755
</CardTitle>
@@ -167,17 +85,23 @@ export function ReleaseNotesCard() {
16785
className="space-y-6"
16886
>
16987
{visibleNotes.map((note, index) => (
170-
<ReleaseNoteItem key={note.id} note={note} index={index} />
88+
<motion.div
89+
key={note.id}
90+
initial={{ opacity: 0, y: 10 }}
91+
animate={{ opacity: 1, y: 0 }}
92+
exit={{ opacity: 0, y: -10 }}
93+
transition={{ delay: index * 0.05 }}
94+
>
95+
<ReleaseNoteItem
96+
note={note}
97+
onClick={() => openModal(note.id)}
98+
/>
99+
</motion.div>
171100
))}
172101
</motion.div>
173102
</AnimatePresence>
174103
<div className="mt-6 pt-4 border-t text-center">
175-
<p className="text-sm text-muted-foreground">
176-
Har du frågor eller behöver hjälp? Tveka inte att höra av dig
177-
till <span className="font-semibold text-primary">David</span>{' '}
178-
eller <span className="font-semibold text-primary">Lina</span>{' '}
179-
- vi finns här för att stötta dig!
180-
</p>
104+
<SupportMessage />
181105
</div>
182106
{/* Pagination */}
183107
{totalPages > 1 && (
@@ -203,10 +127,24 @@ export function ReleaseNotesCard() {
203127
</button>
204128
</div>
205129
)}
130+
{/* View all link */}
131+
<div className="flex justify-center mt-4">
132+
<button
133+
onClick={() => openModal()}
134+
className="text-sm text-primary hover:underline"
135+
>
136+
Visa alla nyheter →
137+
</button>
138+
</div>
206139
</CardContent>
207140
</motion.div>
208141
)}
209142
</AnimatePresence>
143+
<ReleaseNotesModal
144+
open={isModalOpen}
145+
onOpenChange={setIsModalOpen}
146+
scrollToNoteId={scrollToNoteId}
147+
/>
210148
</Card>
211149
)
212150
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { useEffect, useMemo } from 'react'
2+
import { Newspaper } from 'lucide-react'
3+
import {
4+
Dialog,
5+
DialogContent,
6+
DialogHeader,
7+
DialogTitle,
8+
} from '@/components/ui/v2/Dialog'
9+
import { RELEASE_NOTES, sortReleaseNotesByPinned } from '@/data/release-notes'
10+
import { ReleaseNoteItem } from './ReleaseNoteItem'
11+
import { SupportMessage } from './SupportMessage'
12+
13+
/** Delay in ms to wait for modal render before scrolling */
14+
const SCROLL_DELAY_MS = 100
15+
16+
interface ReleaseNotesModalProps {
17+
open: boolean
18+
onOpenChange: (open: boolean) => void
19+
scrollToNoteId?: string
20+
}
21+
22+
export function ReleaseNotesModal({
23+
open,
24+
onOpenChange,
25+
scrollToNoteId,
26+
}: ReleaseNotesModalProps) {
27+
const sortedNotes = useMemo(() => sortReleaseNotesByPinned(RELEASE_NOTES), [])
28+
29+
useEffect(() => {
30+
if (open && scrollToNoteId) {
31+
const timeout = setTimeout(() => {
32+
const element = document.getElementById(scrollToNoteId)
33+
if (element) {
34+
element.scrollIntoView({
35+
behavior: 'smooth',
36+
block: 'center',
37+
})
38+
}
39+
}, SCROLL_DELAY_MS)
40+
return () => clearTimeout(timeout)
41+
}
42+
}, [open, scrollToNoteId])
43+
44+
return (
45+
<Dialog open={open} onOpenChange={onOpenChange}>
46+
<DialogContent className="max-w-2xl max-h-[85vh] flex flex-col">
47+
<DialogHeader>
48+
<DialogTitle className="flex items-center gap-2">
49+
<Newspaper className="h-5 w-5 text-primary" />
50+
Nyheter och uppdateringar
51+
</DialogTitle>
52+
</DialogHeader>
53+
<div className="flex-1 overflow-y-auto space-y-6 pr-2 -mr-2">
54+
{sortedNotes.map((note) => (
55+
<ReleaseNoteItem key={note.id} note={note} id={note.id} />
56+
))}
57+
</div>
58+
<div className="pt-4 border-t text-center">
59+
<SupportMessage />
60+
</div>
61+
</DialogContent>
62+
</Dialog>
63+
)
64+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* Shared support message component displayed in release notes sections.
3+
*/
4+
export function SupportMessage() {
5+
return (
6+
<p className="text-sm text-muted-foreground">
7+
Har du frågor eller behöver hjälp? Tveka inte att höra av dig till{' '}
8+
<span className="font-semibold text-primary">David</span> eller{' '}
9+
<span className="font-semibold text-primary">Lina</span> - vi finns här
10+
för att stötta dig!
11+
</p>
12+
)
13+
}

apps/property-tree/src/data/release-notes/2026-03-04-v2.0.0.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"id": "rn-2026-03-04-v1100-1",
44
"date": "2026-03-04",
55
"title": "Massutskick av SMS och e-post",
6-
"description": "Det går nu att skicka SMS och e-post till flera hyresgäster samtidigt direkt från hyreskontraktssidan. Man kan välja enskilda mottagare eller flera. Det går även att skicka SMS/Mail från kundkortet.",
6+
"description": "Det går nu att skicka SMS och e-post till flera hyresgäster samtidigt direkt från hyreskontraktssidan. Man kan välja enskilda mottagare eller flera. Det går även att skicka SMS/Mejl från kundkortet.",
77
"category": "feature"
88
},
99
{
@@ -18,7 +18,7 @@
1818
"date": "2026-03-04",
1919
"title": "Ändrat metod för SMS- och e-postutskick",
2020
"description": "Ändrat metod för att göra sms och epost-utskick i syfte att komma åt bugg med att meddelanden inte levererats.",
21-
"category": "fix"
21+
"category": "improvement"
2222
},
2323
{
2424
"id": "rn-2026-03-04-v1100-4",
@@ -33,5 +33,12 @@
3333
"title": "Byta bilplats för sökande med bostad i område eller fastighet med utökade regler",
3434
"description": "På Mimer.nu går det nu går att välja att byta bilplats också för sökande med bostad i område eller fastighet med utökade regler. Tidigare har den typen av sökande behövt gå via kundtjänst för att byta bilplats.",
3535
"category": "improvement"
36+
},
37+
{
38+
"id": "rn-2026-03-04-v1100-6",
39+
"date": "2026-03-04",
40+
"title": "Klickbara nyheter med popup",
41+
"description": "Det går nu att klicka på nyheter och uppdateringar på startsidan för att öppna en popup som visar alla nyheter. Klicka på rubriken, en enskild nyhet eller 'Visa alla nyheter' för att se hela listan.",
42+
"category": "improvement"
3643
}
3744
]

0 commit comments

Comments
 (0)