@@ -3,26 +3,55 @@ import { LoadZoneSchema } from '@/schemas/generator/v1/loadZone'
33import { useGeneratorStore } from '@/store/generator/useGeneratorStore'
44import { LoadZoneData } from '@/types/testOptions'
55import { zodResolver } from '@hookform/resolvers/zod'
6- import { Text , Link as RadixLink , Button } from '@radix-ui/themes'
6+ import {
7+ Text ,
8+ Link as RadixLink ,
9+ Button ,
10+ Switch ,
11+ Flex ,
12+ Callout ,
13+ Tooltip ,
14+ } from '@radix-ui/themes'
715import { useCallback , useEffect } from 'react'
8- import { FormProvider , useForm } from 'react-hook-form'
16+ import {
17+ FormProvider ,
18+ useForm ,
19+ useFieldArray ,
20+ useFormContext ,
21+ } from 'react-hook-form'
22+ import { LoadZoneRow } from './LoadZoneRow'
23+ import { FieldGroup } from '@/components/Form'
24+ import {
25+ findUnusedLoadZone ,
26+ getRemainingPercentage ,
27+ LOAD_ZONES_REGIONS_OPTIONS ,
28+ } from './LoadZones.utils'
29+ import { Cross1Icon } from '@radix-ui/react-icons'
930
1031export function LoadZones ( ) {
11- const { distribution, loadZones } = useGeneratorStore (
12- ( store ) => store . loadZones
13- )
32+ const loadZones = useGeneratorStore ( ( store ) => store . loadZones )
1433 const setLoadZones = useGeneratorStore ( ( store ) => store . setLoadZones )
1534
1635 const formMethods = useForm < LoadZoneData > ( {
1736 resolver : zodResolver ( LoadZoneSchema ) ,
1837 shouldFocusError : false ,
19- defaultValues : {
20- distribution,
21- loadZones,
22- } ,
38+ defaultValues : loadZones ,
39+ } )
40+
41+ const {
42+ handleSubmit,
43+ watch,
44+ control,
45+ setValue,
46+ formState : { errors } ,
47+ } = formMethods
48+
49+ const { append, remove, fields } = useFieldArray < LoadZoneData > ( {
50+ control,
51+ name : 'loadZones' ,
2352 } )
2453
25- const { handleSubmit , watch } = formMethods
54+ const { distribution , loadZones : usedLoadZones } = watch ( )
2655
2756 const handleOpenDocs = ( event : React . MouseEvent ) => {
2857 event . preventDefault ( )
@@ -31,8 +60,14 @@ export function LoadZones() {
3160 )
3261 }
3362
34- function handleAddLoadZone ( ) {
35- // TODO: Implement
63+ function handleAddLoadZone ( event : React . MouseEvent ) {
64+ event . preventDefault ( )
65+
66+ append ( {
67+ id : crypto . randomUUID ( ) ,
68+ loadZone : findUnusedLoadZone ( usedLoadZones ) ,
69+ percent : getRemainingPercentage ( usedLoadZones ) ,
70+ } )
3671 }
3772
3873 const onSubmit = useCallback (
@@ -48,6 +83,20 @@ export function LoadZones() {
4883 return ( ) => subscription . unsubscribe ( )
4984 } , [ watch , handleSubmit , onSubmit ] )
5085
86+ // evenly distribute load zones if distribution is set to "even"
87+ useEffect ( ( ) => {
88+ if ( distribution !== 'even' ) return
89+
90+ const basePercent = Math . floor ( 100 / fields . length )
91+ const remainder = 100 % fields . length
92+
93+ fields . forEach ( ( _ , index ) => {
94+ // ensure only integers are used
95+ const percent = index < remainder ? basePercent + 1 : basePercent
96+ setValue ( `loadZones.${ index } .percent` , percent )
97+ } )
98+ } , [ distribution , fields , setValue ] )
99+
51100 return (
52101 < FormProvider { ...formMethods } >
53102 < form onSubmit = { handleSubmit ( onSubmit ) } >
@@ -59,6 +108,25 @@ export function LoadZones() {
59108 </ RadixLink >
60109 .
61110 </ Text >
111+
112+ < FieldGroup name = "distribution" label = "Distribution" errors = { errors } >
113+ < Text size = "2" >
114+ < Flex gap = "2" align = "center" >
115+ Even
116+ < Switch
117+ name = "distribution"
118+ checked = { distribution === 'manual' }
119+ onCheckedChange = { ( checked ) => {
120+ setValue ( 'distribution' , checked ? 'manual' : 'even' )
121+ } }
122+ />
123+ Manual
124+ </ Flex >
125+ </ Text >
126+ </ FieldGroup >
127+
128+ { errors . loadZones ?. root && < LoadZonePercentageError /> }
129+
62130 < Table . Root size = "1" variant = "surface" layout = "fixed" >
63131 < Table . Header >
64132 < Table . Row >
@@ -73,11 +141,31 @@ export function LoadZones() {
73141 </ Table . Header >
74142
75143 < Table . Body >
144+ { fields . map ( ( field , index ) => (
145+ < LoadZoneRow
146+ key = { field . id }
147+ field = { field }
148+ index = { index }
149+ remove = { remove }
150+ />
151+ ) ) }
152+
76153 < Table . Row >
77154 < Table . RowHeaderCell colSpan = { 7 } justify = "center" >
78- < Button variant = "ghost" onClick = { handleAddLoadZone } >
79- Add new load zone
80- </ Button >
155+ < Tooltip
156+ content = "All available load zones are already in use"
157+ hidden = { fields . length !== LOAD_ZONES_REGIONS_OPTIONS . length }
158+ >
159+ < Button
160+ variant = "ghost"
161+ onClick = { handleAddLoadZone }
162+ disabled = {
163+ fields . length === LOAD_ZONES_REGIONS_OPTIONS . length
164+ }
165+ >
166+ Add new load zone
167+ </ Button >
168+ </ Tooltip >
81169 </ Table . RowHeaderCell >
82170 </ Table . Row >
83171 </ Table . Body >
@@ -86,3 +174,19 @@ export function LoadZones() {
86174 </ FormProvider >
87175 )
88176}
177+
178+ function LoadZonePercentageError ( ) {
179+ const {
180+ formState : { errors } ,
181+ } = useFormContext < LoadZoneData > ( )
182+
183+ return (
184+ < Callout . Root variant = "soft" color = "tomato" mb = "3" >
185+ < Callout . Icon >
186+ < Cross1Icon />
187+ </ Callout . Icon >
188+
189+ < Callout . Text > { errors . loadZones ?. root ?. message } </ Callout . Text >
190+ </ Callout . Root >
191+ )
192+ }
0 commit comments