@@ -8,6 +8,13 @@ import {
88 DialogHeader ,
99 DialogTitle ,
1010} from '@/components/ui/dialog'
11+ import {
12+ Select ,
13+ SelectContent ,
14+ SelectItem ,
15+ SelectTrigger ,
16+ SelectValue ,
17+ } from '@/components/ui/select'
1118import { Plus , Minus } from 'lucide-react'
1219import { Spinner } from '@/components/ui/spinner'
1320import type { Key , KeyType } from '@/services/types'
@@ -23,6 +30,8 @@ type KeyGroup = {
2330 sampleKey : Key
2431 currentFlexNumber : number
2532 hasFlexConflict : boolean
33+ hasNullFlex : boolean
34+ isAtMaxFlex : boolean
2635}
2736
2837type Props = {
@@ -46,6 +55,11 @@ export function FlexMenu({
4655 // Group keys by name and type, with a default count of 3
4756 const [ keyGroups , setKeyGroups ] = useState < Map < string , KeyGroup > > ( new Map ( ) )
4857
58+ // Track user-specified flex for groups with null flex
59+ const [ flexOverrides , setFlexOverrides ] = useState < Map < string , number > > (
60+ new Map ( )
61+ )
62+
4963 // Initialize key groups when selectedKeys changes
5064 useMemo ( ( ) => {
5165 const groups = new Map < string , KeyGroup > ( )
@@ -66,22 +80,27 @@ export function FlexMenu({
6680
6781 const uniqueFlexNumbers = new Set ( flexNumbers )
6882 const hasFlexConflict = uniqueFlexNumbers . size > 1
83+ const hasNullFlex = flexNumbers . length === 0
6984
7085 // Use the first flex number found, or 0 if none
7186 const currentFlexNumber = flexNumbers . length > 0 ? flexNumbers [ 0 ] : 0
87+ const isAtMaxFlex = currentFlexNumber === 3
7288
7389 groups . set ( groupKey , {
7490 keyName : key . keyName ,
7591 keyType : key . keyType ,
76- count : 3 ,
92+ count : isAtMaxFlex ? 0 : 3 ,
7793 sampleKey : key ,
7894 currentFlexNumber,
7995 hasFlexConflict,
96+ hasNullFlex,
97+ isAtMaxFlex,
8098 } )
8199 }
82100 } )
83101
84102 setKeyGroups ( groups )
103+ setFlexOverrides ( new Map ( ) )
85104 } , [ selectedKeys ] )
86105
87106 const incrementCount = ( groupKey : string ) => {
@@ -106,29 +125,42 @@ export function FlexMenu({
106125 } )
107126 }
108127
128+ const handleFlexOverride = ( groupKey : string , value : string ) => {
129+ const flexNumber = parseInt ( value , 10 )
130+ setFlexOverrides ( ( prev ) => {
131+ const newOverrides = new Map ( prev )
132+ newOverrides . set ( groupKey , flexNumber )
133+ return newOverrides
134+ } )
135+ }
136+
137+ const getEffectiveFlexNumber = ( group : KeyGroup , groupKey : string ) => {
138+ if ( group . hasNullFlex ) {
139+ return flexOverrides . get ( groupKey ) ?? null
140+ }
141+ return group . currentFlexNumber
142+ }
143+
109144 const handleCreate = async ( ) => {
110145 setIsCreating ( true )
111146 try {
112147 const createdKeys : Key [ ] = [ ]
113148
114- // Calculate total keys to create across all groups
115- let totalKeysToCreate = 0
116- for ( const group of keyGroups . values ( ) ) {
117- totalKeysToCreate += group . count
118- }
149+ // Create keys for each group (skip max-flex groups)
150+ for ( const [ groupKey , group ] of keyGroups . entries ( ) ) {
151+ if ( group . isAtMaxFlex || group . count === 0 ) continue
152+
153+ const effectiveFlex = getEffectiveFlexNumber ( group , groupKey )
154+ if ( effectiveFlex === null ) continue
119155
120- // Create keys for each group
121- for ( const group of keyGroups . values ( ) ) {
122- // Calculate the new flex number (current + 1)
123- const currentFlexNumber = group . currentFlexNumber ?? 0
124- const newFlexNumber = currentFlexNumber + 1
156+ const newFlexNumber = effectiveFlex + 1
125157
126158 // Create 'count' number of keys with sequence numbers 1, 2, 3, etc.
127159 for ( let i = 1 ; i <= group . count ; i ++ ) {
128160 const newKey = await keyService . createKey ( {
129161 keyName : group . keyName ,
130162 keyType : group . keyType ,
131- keySequenceNumber : i , // Sequence number: 1, 2, 3, etc.
163+ keySequenceNumber : i ,
132164 flexNumber : newFlexNumber ,
133165 rentalObjectCode : group . sampleKey . rentalObjectCode ,
134166 keySystemId : group . sampleKey . keySystemId ,
@@ -165,11 +197,18 @@ export function FlexMenu({
165197 }
166198 }
167199
168- const totalKeysToCreate = Array . from ( keyGroups . values ( ) ) . reduce (
169- ( sum , group ) => sum + group . count ,
200+ // Only count groups that can actually create keys
201+ const totalKeysToCreate = Array . from ( keyGroups . entries ( ) ) . reduce (
202+ ( sum , [ , group ] ) => ( group . isAtMaxFlex ? sum : sum + group . count ) ,
170203 0
171204 )
172205
206+ // Check if any null-flex group is missing a user override
207+ const hasUnresolvedNullFlex = Array . from ( keyGroups . entries ( ) ) . some (
208+ ( [ groupKey , group ] ) =>
209+ group . hasNullFlex && ! group . isAtMaxFlex && ! flexOverrides . has ( groupKey )
210+ )
211+
173212 return (
174213 < Dialog open = { open } onOpenChange = { onOpenChange } >
175214 < DialogContent className = "max-w-4xl max-h-[80vh] overflow-y-auto" >
@@ -195,8 +234,9 @@ export function FlexMenu({
195234 < div className = "font-medium" > { key . keyName } </ div >
196235 < div className = "text-muted-foreground" >
197236 { KeyTypeLabels [ key . keyType ] }
198- { key . flexNumber !== undefined &&
199- ` • Flex ${ key . flexNumber } ` }
237+ { key . flexNumber != null
238+ ? ` • Flex ${ key . flexNumber } `
239+ : ' • Flex saknas' }
200240 { key . keySequenceNumber !== undefined &&
201241 ` • Löpnr: ${ key . keySequenceNumber } ` }
202242 </ div >
@@ -212,7 +252,10 @@ export function FlexMenu({
212252 </ h3 >
213253 < div className = "space-y-3 max-h-[400px] overflow-y-auto" >
214254 { Array . from ( keyGroups . entries ( ) ) . map ( ( [ groupKey , group ] ) => {
215- const newFlexNumber = group . currentFlexNumber + 1
255+ const effectiveFlex = getEffectiveFlexNumber ( group , groupKey )
256+ const newFlexNumber =
257+ effectiveFlex !== null ? effectiveFlex + 1 : null
258+
216259 return (
217260 < div
218261 key = { groupKey }
@@ -225,41 +268,89 @@ export function FlexMenu({
225268 </ div >
226269 { group . hasFlexConflict && (
227270 < div className = "text-xs text-destructive mt-1" >
228- ⚠️ Varning: Valda nycklar har olika flex-nummer
271+ Varning: Valda nycklar har olika flex-nummer
229272 </ div >
230273 ) }
231274 </ div >
232275
233- < div className = "flex items-center justify-between" >
234- < span className = "text-xs text-muted-foreground" >
235- Flex { newFlexNumber } • Löpnr 1-{ group . count }
236- </ span >
237- < div className = "flex items-center gap-2" >
238- < Button
239- type = "button"
240- size = "icon"
241- variant = "outline"
242- className = "h-7 w-7"
243- onClick = { ( ) => decrementCount ( groupKey ) }
244- disabled = { group . count === 0 || isCreating }
245- >
246- < Minus className = "h-3 w-3" />
247- </ Button >
248- < span className = "w-8 text-center font-medium" >
249- { group . count }
250- </ span >
251- < Button
252- type = "button"
253- size = "icon"
254- variant = "outline"
255- className = "h-7 w-7"
256- onClick = { ( ) => incrementCount ( groupKey ) }
257- disabled = { isCreating }
258- >
259- < Plus className = "h-3 w-3" />
260- </ Button >
276+ { /* Max flex warning */ }
277+ { group . isAtMaxFlex && (
278+ < div className = "text-xs text-destructive" >
279+ Kan inte flexa – redan på flex 3
261280 </ div >
262- </ div >
281+ ) }
282+
283+ { /* Null flex - require user to set flex */ }
284+ { group . hasNullFlex && ! group . isAtMaxFlex && (
285+ < div className = "space-y-2" >
286+ < div className = "text-xs text-destructive" >
287+ Flex saknas – ange nuvarande flex innan du flexar
288+ </ div >
289+ < div className = "flex items-center gap-2" >
290+ < span className = "text-xs text-muted-foreground" >
291+ Nuvarande flex:
292+ </ span >
293+ < Select
294+ value = {
295+ flexOverrides . has ( groupKey )
296+ ? String ( flexOverrides . get ( groupKey ) )
297+ : undefined
298+ }
299+ onValueChange = { ( v ) =>
300+ handleFlexOverride ( groupKey , v )
301+ }
302+ >
303+ < SelectTrigger className = "w-20 h-7 text-xs" >
304+ < SelectValue placeholder = "–" />
305+ </ SelectTrigger >
306+ < SelectContent >
307+ < SelectItem value = "1" > 1</ SelectItem >
308+ < SelectItem value = "2" > 2</ SelectItem >
309+ </ SelectContent >
310+ </ Select >
311+ { newFlexNumber !== null && (
312+ < span className = "text-xs text-muted-foreground" >
313+ → Ny flex: { newFlexNumber }
314+ </ span >
315+ ) }
316+ </ div >
317+ </ div >
318+ ) }
319+
320+ { /* Normal group or null-flex with override set - show quantity controls */ }
321+ { ! group . isAtMaxFlex &&
322+ ( ! group . hasNullFlex || flexOverrides . has ( groupKey ) ) && (
323+ < div className = "flex items-center justify-between" >
324+ < span className = "text-xs text-muted-foreground" >
325+ Flex { newFlexNumber } • Löpnr 1-{ group . count }
326+ </ span >
327+ < div className = "flex items-center gap-2" >
328+ < Button
329+ type = "button"
330+ size = "icon"
331+ variant = "outline"
332+ className = "h-7 w-7"
333+ onClick = { ( ) => decrementCount ( groupKey ) }
334+ disabled = { group . count === 0 || isCreating }
335+ >
336+ < Minus className = "h-3 w-3" />
337+ </ Button >
338+ < span className = "w-8 text-center font-medium" >
339+ { group . count }
340+ </ span >
341+ < Button
342+ type = "button"
343+ size = "icon"
344+ variant = "outline"
345+ className = "h-7 w-7"
346+ onClick = { ( ) => incrementCount ( groupKey ) }
347+ disabled = { isCreating }
348+ >
349+ < Plus className = "h-3 w-3" />
350+ </ Button >
351+ </ div >
352+ </ div >
353+ ) }
263354 </ div >
264355 )
265356 } ) }
@@ -277,7 +368,9 @@ export function FlexMenu({
277368 </ Button >
278369 < Button
279370 onClick = { handleCreate }
280- disabled = { isCreating || totalKeysToCreate === 0 }
371+ disabled = {
372+ isCreating || totalKeysToCreate === 0 || hasUnresolvedNullFlex
373+ }
281374 >
282375 { isCreating ? (
283376 < >
0 commit comments