|
16 | 16 | * under the License. |
17 | 17 | */ |
18 | 18 |
|
19 | | -import { useState, useCallback, useMemo } from "react"; |
| 19 | +import { useState, useCallback } from "react"; |
20 | 20 | import { Box, Button, Card, CardContent, Typography } from "@wso2/oxygen-ui"; |
21 | 21 | import { Plus as Add, FileText } from "@wso2/oxygen-ui-icons-react"; |
22 | | -import { useFieldArray, useFormContext, useWatch } from "react-hook-form"; |
23 | 22 | import { EnvVariableEditor } from "@agent-management-platform/views"; |
24 | 23 | import { EnvBulkImportModal, EnvVariable } from "@agent-management-platform/shared-component"; |
| 24 | +import { CreateAgentFormValues } from "../form/schema"; |
25 | 25 |
|
26 | | -export const EnvironmentVariable = () => { |
27 | | - const { control, formState: { errors }, register, getValues } = useFormContext(); |
28 | | - const { fields, append, remove, replace } = useFieldArray({ control, name: 'env' }); |
29 | | - const watchedEnvValues = useWatch({ control, name: 'env' }); |
30 | | - const [importModalOpen, setImportModalOpen] = useState(false); |
| 26 | +interface EnvironmentVariableProps { |
| 27 | + formData: CreateAgentFormValues; |
| 28 | + setFormData: React.Dispatch<React.SetStateAction<CreateAgentFormValues>>; |
| 29 | +} |
31 | 30 |
|
32 | | - // Memoize envValues to stabilize dependency for useCallback |
33 | | - const envValues = useMemo( |
34 | | - () => (watchedEnvValues || []) as EnvVariable[], |
35 | | - [watchedEnvValues] |
36 | | - ); |
| 31 | +export const EnvironmentVariable = ({ |
| 32 | + formData, |
| 33 | + setFormData, |
| 34 | +}: EnvironmentVariableProps) => { |
| 35 | + const [importModalOpen, setImportModalOpen] = useState(false); |
| 36 | + const envVariables = formData.env || []; |
| 37 | + const isOneEmpty = envVariables.some((e) => !e?.key || !e?.value); |
37 | 38 |
|
38 | | - const isOneEmpty = envValues.some((e) => !e?.key || !e?.value); |
| 39 | + const handleAdd = () => { |
| 40 | + setFormData((prev) => ({ |
| 41 | + ...prev, |
| 42 | + env: [...(prev.env || []), { key: '', value: '' }], |
| 43 | + })); |
| 44 | + }; |
39 | 45 |
|
40 | | - // Handle bulk import - merge imported vars with existing ones, remove empty rows |
41 | | - const handleImport = useCallback((importedVars: EnvVariable[]) => { |
42 | | - // Get current values directly from form to avoid stale closure |
43 | | - const currentEnv = (getValues('env') || []) as EnvVariable[]; |
| 46 | + const handleRemove = (index: number) => { |
| 47 | + setFormData((prev) => ({ |
| 48 | + ...prev, |
| 49 | + env: prev.env?.filter((_, i) => i !== index) || [], |
| 50 | + })); |
| 51 | + }; |
44 | 52 |
|
45 | | - // Filter out rows with no key (value may be intentionally empty) |
46 | | - const nonEmptyExisting = currentEnv.filter((env) => env?.key); |
| 53 | + const handleChange = (index: number, field: 'key' | 'value', value: string) => { |
| 54 | + setFormData((prev) => ({ |
| 55 | + ...prev, |
| 56 | + env: prev.env?.map((item, i) => |
| 57 | + i === index ? { ...item, [field]: value } : item |
| 58 | + ) || [], |
| 59 | + })); |
| 60 | + }; |
47 | 61 |
|
48 | | - // Map existing keys to their values for merging |
49 | | - const existingMap = new Map<string, string>(); |
50 | | - nonEmptyExisting.forEach((env) => { |
51 | | - existingMap.set(env.key, env.value); |
52 | | - }); |
| 62 | + const handleInitialEdit = (field: 'key' | 'value', value: string) => { |
| 63 | + setFormData((prev) => { |
| 64 | + const envList = prev.env || []; |
| 65 | + if (envList.length > 0) { |
| 66 | + return { |
| 67 | + ...prev, |
| 68 | + env: envList.map((item, i) => |
| 69 | + i === 0 ? { ...item, [field]: value } : item |
| 70 | + ), |
| 71 | + }; |
| 72 | + } |
| 73 | + return { |
| 74 | + ...prev, |
| 75 | + env: [{ key: field === 'key' ? value : '', value: field === 'value' ? value : '' }], |
| 76 | + }; |
| 77 | + }); |
| 78 | + }; |
53 | 79 |
|
54 | | - // Merge: imported vars override existing ones with same key |
55 | | - importedVars.forEach((imported) => { |
56 | | - existingMap.set(imported.key, imported.value); |
57 | | - }); |
| 80 | + const handleImport = useCallback((importedVars: EnvVariable[]) => { |
| 81 | + setFormData((prev) => { |
| 82 | + // Filter out rows with no key (value may be intentionally empty) |
| 83 | + const nonEmpty = (prev.env || []).filter((env) => env?.key); |
58 | 84 |
|
59 | | - // Convert map back to array |
60 | | - const mergedEnv = Array.from(existingMap.entries()).map(([key, value]) => ({ key, value })); |
| 85 | + // Build map from existing vars; imported vars override on same key |
| 86 | + const existingMap = new Map<string, string>(nonEmpty.map((env) => [env.key, env.value])); |
| 87 | + importedVars.forEach((v) => existingMap.set(v.key, v.value)); |
61 | 88 |
|
62 | | - // Replace all fields with merged result |
63 | | - replace(mergedEnv); |
64 | | - }, [getValues, replace]); |
| 89 | + return { |
| 90 | + ...prev, |
| 91 | + env: Array.from(existingMap.entries()).map(([key, value]) => ({ key, value })), |
| 92 | + }; |
| 93 | + }); |
| 94 | + }, [setFormData]); |
65 | 95 |
|
66 | | - const handleModalClose = useCallback(() => setImportModalOpen(false), []); |
| 96 | + const handleModalClose = useCallback(() => setImportModalOpen(false), []); |
67 | 97 |
|
68 | | - return ( |
69 | | - <Card variant="outlined"> |
70 | | - <CardContent> |
71 | | - <Box display="flex" flexDirection="row" alignItems="center" gap={1}> |
72 | | - <Typography variant="h5"> |
73 | | - Environment Variables (Optional) |
74 | | - </Typography> |
75 | | - </Box> |
76 | | - <Box display="flex" flexDirection="column" py={2} gap={2}> |
77 | | - {fields.map((field, index) => ( |
78 | | - <EnvVariableEditor |
79 | | - key={field.id} |
80 | | - fieldName="env" |
81 | | - index={index} |
82 | | - fieldId={field.id} |
83 | | - register={register} |
84 | | - errors={errors} |
85 | | - onRemove={() => remove(index)} |
86 | | - /> |
87 | | - ))} |
88 | | - </Box> |
89 | | - <Box display="flex" gap={1}> |
90 | | - <Button |
91 | | - startIcon={<Add fontSize="small" />} |
92 | | - disabled={isOneEmpty} |
93 | | - variant="outlined" |
94 | | - color="primary" |
95 | | - onClick={() => append({ key: '', value: '' })} |
96 | | - > |
97 | | - Add |
98 | | - </Button> |
99 | | - <Button |
100 | | - startIcon={<FileText fontSize="small" />} |
101 | | - variant="outlined" |
102 | | - color="primary" |
103 | | - onClick={() => setImportModalOpen(true)} |
104 | | - > |
105 | | - Import |
106 | | - </Button> |
107 | | - </Box> |
| 98 | + return ( |
| 99 | + <Card variant="outlined"> |
| 100 | + <CardContent> |
| 101 | + <Box display="flex" flexDirection="row" alignItems="center" gap={1}> |
| 102 | + <Typography variant="h5"> |
| 103 | + Environment Variables (Optional) |
| 104 | + </Typography> |
| 105 | + </Box> |
| 106 | + <Box display="flex" flexDirection="column" py={2} gap={2}> |
| 107 | + {envVariables.length ? envVariables.map((item, index) => ( |
| 108 | + <EnvVariableEditor |
| 109 | + key={`env-${index}`} |
| 110 | + index={index} |
| 111 | + keyValue={item.key || ''} |
| 112 | + valueValue={item.value || ''} |
| 113 | + onKeyChange={(value) => handleChange(index, 'key', value)} |
| 114 | + onValueChange={(value) => handleChange(index, 'value', value)} |
| 115 | + onRemove={() => handleRemove(index)} |
| 116 | + /> |
| 117 | + )) : ( |
| 118 | + <EnvVariableEditor |
| 119 | + key="env-0" |
| 120 | + index={0} |
| 121 | + keyValue={envVariables?.[0]?.key || ''} |
| 122 | + valueValue={envVariables?.[0]?.value || ''} |
| 123 | + onKeyChange={(value) => handleInitialEdit('key', value)} |
| 124 | + onValueChange={(value) => handleInitialEdit('value', value)} |
| 125 | + onRemove={() => handleRemove(0)} |
| 126 | + /> |
| 127 | + )} |
| 128 | + </Box> |
| 129 | + <Box display="flex" gap={1}> |
| 130 | + <Button |
| 131 | + startIcon={<Add fontSize="small" />} |
| 132 | + disabled={isOneEmpty} |
| 133 | + variant="outlined" |
| 134 | + color="primary" |
| 135 | + onClick={handleAdd} |
| 136 | + > |
| 137 | + Add |
| 138 | + </Button> |
| 139 | + <Button |
| 140 | + startIcon={<FileText fontSize="small" />} |
| 141 | + variant="outlined" |
| 142 | + color="primary" |
| 143 | + onClick={() => setImportModalOpen(true)} |
| 144 | + > |
| 145 | + Import |
| 146 | + </Button> |
| 147 | + </Box> |
108 | 148 |
|
109 | | - <EnvBulkImportModal |
110 | | - open={importModalOpen} |
111 | | - onClose={handleModalClose} |
112 | | - onImport={handleImport} |
113 | | - /> |
114 | | - </CardContent> |
115 | | - </Card> |
116 | | - ); |
| 149 | + <EnvBulkImportModal |
| 150 | + open={importModalOpen} |
| 151 | + onClose={handleModalClose} |
| 152 | + onImport={handleImport} |
| 153 | + /> |
| 154 | + </CardContent> |
| 155 | + </Card> |
| 156 | + ); |
117 | 157 | }; |
0 commit comments