1
- import { useQuery } from "@apollo/client" ;
1
+ import classNames from "classnames" ;
2
+ import { clone } from "lodash" ;
2
3
import { FC , useCallback , useMemo , useState } from "react" ;
3
4
import { useNavigate } from "react-router-dom" ;
4
5
import { Handle , Position } from "reactflow" ;
5
- import { AnimatedButton } from "../../components/button" ;
6
+ import { ActionButton , AnimatedButton } from "../../components/button" ;
6
7
import { Card , ExpandableCard } from "../../components/card" ;
7
8
import { EmptyMessage } from "../../components/common" ;
9
+ import { createDropdownItem , Dropdown } from "../../components/dropdown" ;
8
10
import { IGraphCardProps } from "../../components/graph/graph" ;
9
11
import { Icons } from "../../components/icons" ;
12
+ import { Input , InputWithlabel , Label } from "../../components/input" ;
10
13
import { Loading } from "../../components/loading" ;
11
14
import { InternalPage } from "../../components/page" ;
12
15
import { InternalRoutes } from "../../config/routes" ;
13
- import { DatabaseType , GetStorageUnitsDocument , GetStorageUnitsQuery , GetStorageUnitsQueryVariables , StorageUnit } from "../../generated/graphql" ;
16
+ import { DatabaseType , RecordInput , StorageUnit , useAddStorageUnitMutation , useGetStorageUnitsQuery } from "../../generated/graphql" ;
17
+ import { notify } from "../../store/function" ;
14
18
import { useAppSelector } from "../../store/hooks" ;
15
- import { getDatabaseStorageUnitLabel } from "../../utils/functions" ;
19
+ import { getDatabaseStorageUnitLabel , isNoSQL } from "../../utils/functions" ;
16
20
17
21
const StorageUnitCard : FC < { unit : StorageUnit } > = ( { unit } ) => {
18
22
const [ expanded , setExpanded ] = useState ( false ) ;
@@ -80,9 +84,14 @@ const StorageUnitCard: FC<{ unit: StorageUnit }> = ({ unit }) => {
80
84
81
85
export const StorageUnitPage : FC = ( ) => {
82
86
const navigate = useNavigate ( ) ;
87
+ const [ create , setCreate ] = useState ( false ) ;
88
+ const [ storageUnitName , setStorageUnitName ] = useState ( "" ) ;
89
+ const [ fields , setFields ] = useState < RecordInput [ ] > ( [ { Key : "" , Value : "" } ] ) ;
90
+ const [ error , setError ] = useState < string > ( ) ;
83
91
const schema = useAppSelector ( state => state . database . schema ) ;
84
92
const current = useAppSelector ( state => state . auth . current ) ;
85
- const { loading, data } = useQuery < GetStorageUnitsQuery , GetStorageUnitsQueryVariables > ( GetStorageUnitsDocument , {
93
+ const [ addStorageUnit , ] = useAddStorageUnitMutation ( ) ;
94
+ const { loading, data } = useGetStorageUnitsQuery ( {
86
95
variables : {
87
96
type : current ?. Type as DatabaseType ,
88
97
schema,
@@ -99,6 +108,82 @@ export const StorageUnitPage: FC = () => {
99
108
] ;
100
109
} , [ current ] ) ;
101
110
111
+ const handleCreate = useCallback ( ( ) => {
112
+ setCreate ( ! create ) ;
113
+ } , [ create ] ) ;
114
+
115
+ const handleSubmit = useCallback ( ( ) => {
116
+ if ( storageUnitName . length === 0 ) {
117
+ return setError ( "Name is required" ) ;
118
+ }
119
+ if ( fields . some ( field => field . Key . length === 0 || field . Value . length === 0 ) ) {
120
+ return setError ( "Fields cannot be empty" ) ;
121
+ }
122
+ setError ( undefined ) ;
123
+ addStorageUnit ( {
124
+ variables : {
125
+ type : current ?. Type as DatabaseType ,
126
+ schema,
127
+ storageUnit : storageUnitName ,
128
+ fields,
129
+ } ,
130
+ onCompleted ( ) {
131
+ notify ( `${ getDatabaseStorageUnitLabel ( current ?. Type , true ) } ${ storageUnitName } created successfully!` , "success" ) ;
132
+ setStorageUnitName ( "" ) ;
133
+ setFields ( [ ] ) ;
134
+ } ,
135
+ onError ( e ) {
136
+ notify ( e . message , "error" ) ;
137
+ } ,
138
+ } ) ;
139
+ } , [ addStorageUnit , current ?. Type , fields , schema , storageUnitName ] ) ;
140
+
141
+ const handleAddField = useCallback ( ( ) => {
142
+ setFields ( f => [ ...f , { Key : "" , Value : "" } ] ) ;
143
+ } , [ ] ) ;
144
+
145
+ const handleFieldValueChange = useCallback ( ( type : "Key" | "Value" , index : number , value : string ) => {
146
+ setFields ( f => {
147
+ const newF = clone ( f ) ;
148
+ newF [ index ] [ type ] = value ;
149
+ return newF ;
150
+ } ) ;
151
+ } , [ ] ) ;
152
+
153
+ const handleRemove = useCallback ( ( index : number ) => {
154
+ if ( fields . length <= 1 ) {
155
+ return ;
156
+ }
157
+ setFields ( f => {
158
+ const newF = clone ( f ) ;
159
+ newF . splice ( index , 1 ) ;
160
+ return newF ;
161
+ } )
162
+ } , [ fields . length ] ) ;
163
+
164
+ const storageUnitTypesDropdownItems = useMemo ( ( ) => {
165
+ if ( current ?. Type == null || isNoSQL ( current . Type ) ) {
166
+ return [ ] ;
167
+ }
168
+ let items : string [ ] = [ ] ;
169
+
170
+ switch ( current . Type ) {
171
+ case DatabaseType . MariaDb :
172
+ items = [ "VARCHAR" , "INT" , "TEXT" , "DATE" , "BOOLEAN" ] ;
173
+ break ;
174
+ case DatabaseType . MySql :
175
+ items = [ "VARCHAR" , "INT" , "TEXT" , "DATE" , "BOOLEAN" ] ;
176
+ break ;
177
+ case DatabaseType . Postgres :
178
+ items = [ "VARCHAR" , "INT" , "TEXT" , "DATE" , "BOOLEAN" , "UUID" , "JSONB" ] ;
179
+ break ;
180
+ case DatabaseType . Sqlite3 :
181
+ items = [ "TEXT" , "INTEGER" , "REAL" , "BLOB" , "NUMERIC" ] ;
182
+ break ;
183
+ }
184
+ return items . map ( item => createDropdownItem ( item ) ) ;
185
+ } , [ current ?. Type ] ) ;
186
+
102
187
if ( loading ) {
103
188
return < InternalPage routes = { routes } >
104
189
< Loading />
@@ -112,10 +197,57 @@ export const StorageUnitPage: FC = () => {
112
197
{
113
198
data != null && (
114
199
data . StorageUnit . length === 0
115
- ? < EmptyMessage icon = { Icons . SadSmile } label = { `No ${ getDatabaseStorageUnitLabel ( current ?. Type ) } found` } />
116
- : data . StorageUnit . map ( unit => (
117
- < StorageUnitCard key = { unit . Name } unit = { unit } />
118
- ) )
200
+ ? < >
201
+ < EmptyMessage icon = { Icons . SadSmile } label = { `No ${ getDatabaseStorageUnitLabel ( current ?. Type ) } found` } />
202
+ </ >
203
+ : < >
204
+ < ExpandableCard className = "overflow-visible" icon = { {
205
+ bgClassName : "bg-teal-500" ,
206
+ component : Icons . Add ,
207
+ } } isExpanded = { create } tag = { < div className = "text-red-700 dark:text-red-400 text-xs" >
208
+ { error }
209
+ </ div > } >
210
+ < div className = "flex grow flex-col justify-between my-2 text-neutral-800 dark:text-neutral-100" >
211
+ Create a { getDatabaseStorageUnitLabel ( current ?. Type , true ) }
212
+ < AnimatedButton className = "self-end" icon = { Icons . Add } label = "Create" onClick = { handleCreate } />
213
+ </ div >
214
+ < div className = "flex grow flex-col justify-between my-2 gap-4" >
215
+ < div className = "flex flex-col gap-2" >
216
+ < InputWithlabel label = "Name" value = { storageUnitName } setValue = { setStorageUnitName } />
217
+ < div className = "flex gap-2 justify-between" >
218
+ < Label label = "Field Name" />
219
+ < Label label = "Value" />
220
+ < div className = "w-14" />
221
+ </ div >
222
+ {
223
+ fields . map ( ( field , index ) => (
224
+ < div className = "flex gap-2" >
225
+ < Input inputProps = { { className : "w-1/2" } } value = { field . Key } setValue = { ( value ) => handleFieldValueChange ( "Key" , index , value ) } placeholder = "Enter field name" />
226
+ < Dropdown className = "w-1/2" items = { storageUnitTypesDropdownItems } value = { createDropdownItem ( field . Value ) }
227
+ onChange = { ( item ) => handleFieldValueChange ( "Value" , index , item . id ) } />
228
+ < div className = "flex items-end mb-2" >
229
+ < ActionButton disabled = { fields . length === 1 } containerClassName = "w-6 h-6" icon = { Icons . Delete } className = { classNames ( {
230
+ "stroke-red-500 dark:stroke-red-400" : fields . length > 1 ,
231
+ "stroke-neutral-300 dark:stroke-neutral-600" : fields . length === 1 ,
232
+ } ) } onClick = { ( ) => handleRemove ( index ) } />
233
+ </ div >
234
+ </ div >
235
+ ) )
236
+ }
237
+ < AnimatedButton className = "self-end" icon = { Icons . Add } label = "Add field" onClick = { handleAddField } />
238
+ </ div >
239
+ < div className = "flex items-center justify-between" >
240
+ < AnimatedButton icon = { Icons . Cancel } label = "Cancel" onClick = { handleCreate } />
241
+ < AnimatedButton labelClassName = "text-green-600 dark:text-green-300"
242
+ iconClassName = "stroke-green-600 dark:stroke-green-300" icon = { Icons . Add }
243
+ label = "Submit" onClick = { handleSubmit } />
244
+ </ div >
245
+ </ div >
246
+ </ ExpandableCard >
247
+ { data . StorageUnit . map ( unit => (
248
+ < StorageUnitCard key = { unit . Name } unit = { unit } />
249
+ ) ) }
250
+ </ >
119
251
)
120
252
}
121
253
</ InternalPage >
0 commit comments