Skip to content

Commit 7a45753

Browse files
committed
[#1] Play with adding conditional rules
1 parent d28d780 commit 7a45753

File tree

2 files changed

+293
-6
lines changed

2 files changed

+293
-6
lines changed
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
import { Badge } from "@/components/ui/badge";
2+
import { Button } from "@/components/ui/button";
3+
import {
4+
Dialog,
5+
DialogContent,
6+
DialogDescription,
7+
DialogFooter,
8+
DialogHeader,
9+
DialogTitle,
10+
} from "@/components/ui/dialog";
11+
import { Textarea } from "@/components/ui/textarea"; // Import Textarea
12+
import {
13+
Tooltip,
14+
TooltipContent,
15+
TooltipProvider,
16+
TooltipTrigger,
17+
} from "@/components/ui/tooltip";
18+
import type { JSONSchema } from "@/types/jsonSchema"; // Assuming JSONSchema type is available
19+
import { CirclePlus, Info } from "lucide-react";
20+
import type React from "react";
21+
import { useState } from "react";
22+
23+
// Define the structure for a conditional rule
24+
export interface ConditionalRule {
25+
if: JSONSchema;
26+
then: JSONSchema;
27+
else?: JSONSchema;
28+
}
29+
30+
interface AddConditionalRuleButtonProps {
31+
onAddConditionalRule: (rule: ConditionalRule) => void;
32+
variant?: "primary" | "secondary";
33+
}
34+
35+
const AddConditionalRuleButton: React.FC<AddConditionalRuleButtonProps> = ({
36+
onAddConditionalRule,
37+
variant = "primary",
38+
}) => {
39+
const [dialogOpen, setDialogOpen] = useState(false);
40+
const [ifSchemaStr, setIfSchemaStr] = useState("");
41+
const [thenSchemaStr, setThenSchemaStr] = useState("");
42+
const [elseSchemaStr, setElseSchemaStr] = useState("");
43+
const [error, setError] = useState<string | null>(null);
44+
45+
const handleSubmit = (e: React.FormEvent) => {
46+
e.preventDefault();
47+
setError(null);
48+
49+
let ifSchema: JSONSchema;
50+
let thenSchema: JSONSchema;
51+
let elseSchema: JSONSchema | undefined;
52+
53+
if (!ifSchemaStr.trim() || !thenSchemaStr.trim()) {
54+
setError(
55+
"The 'If' and 'Then' schema parts are required and cannot be empty.",
56+
);
57+
return;
58+
}
59+
60+
try {
61+
ifSchema = JSON.parse(ifSchemaStr);
62+
} catch (err) {
63+
setError(
64+
"Invalid JSON in 'If' schema. Please provide a valid JSON object.",
65+
);
66+
return;
67+
}
68+
69+
try {
70+
thenSchema = JSON.parse(thenSchemaStr);
71+
} catch (err) {
72+
setError(
73+
"Invalid JSON in 'Then' schema. Please provide a valid JSON object.",
74+
);
75+
return;
76+
}
77+
78+
if (elseSchemaStr.trim()) {
79+
try {
80+
elseSchema = JSON.parse(elseSchemaStr);
81+
} catch (err) {
82+
setError(
83+
"Invalid JSON in 'Else' schema. Please provide a valid JSON object or leave it empty.",
84+
);
85+
return;
86+
}
87+
}
88+
89+
onAddConditionalRule({
90+
if: ifSchema,
91+
then: thenSchema,
92+
...(elseSchema && { else: elseSchema }),
93+
});
94+
95+
// Reset state
96+
setIfSchemaStr("");
97+
setThenSchemaStr("");
98+
setElseSchemaStr("");
99+
setDialogOpen(false);
100+
};
101+
102+
return (
103+
<>
104+
<Button
105+
onClick={() => setDialogOpen(true)}
106+
variant={variant === "primary" ? "default" : "outline"}
107+
size="sm"
108+
className="flex items-center gap-1.5 group"
109+
>
110+
<CirclePlus
111+
size={16}
112+
className="group-hover:scale-110 transition-transform"
113+
/>
114+
<span>Add Conditional Rule</span>
115+
</Button>
116+
117+
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
118+
<DialogContent className="md:max-w-[800px] max-h-[85vh] w-[95vw] p-4 sm:p-6">
119+
<DialogHeader className="mb-4">
120+
<DialogTitle className="text-xl flex flex-wrap items-center gap-2">
121+
Add Conditional Rule (If/Then/Else)
122+
<Badge variant="secondary" className="text-xs">
123+
Schema Builder
124+
</Badge>
125+
</DialogTitle>
126+
<DialogDescription className="text-sm">
127+
Define a conditional subschema. If the data validates against the
128+
'If' schema, then it must also validate against the 'Then' schema.
129+
Optionally, if it does not validate against 'If', it must validate
130+
against the 'Else' schema.
131+
</DialogDescription>
132+
</DialogHeader>
133+
134+
<form onSubmit={handleSubmit} className="space-y-4">
135+
<div>
136+
<div className="flex flex-wrap items-center gap-2 mb-1.5">
137+
<label htmlFor="ifSchema" className="text-sm font-medium">
138+
If Schema
139+
</label>
140+
<TooltipProvider>
141+
<Tooltip>
142+
<TooltipTrigger asChild>
143+
<Info className="h-4 w-4 text-muted-foreground shrink-0" />
144+
</TooltipTrigger>
145+
<TooltipContent className="max-w-[90vw] sm:max-w-sm">
146+
<p>
147+
A schema that the data must validate against for the
148+
'Then' schema to apply. (Required)
149+
<br />
150+
Example:{" "}
151+
<code>
152+
{'{ "properties": { "country": { "const": "US" } } }'}
153+
</code>
154+
</p>
155+
</TooltipContent>
156+
</Tooltip>
157+
</TooltipProvider>
158+
</div>
159+
<Textarea
160+
id="ifSchema"
161+
value={ifSchemaStr}
162+
onChange={(e) => setIfSchemaStr(e.target.value)}
163+
placeholder='{ "properties": { "field_name": { "type": "string" } } }'
164+
className="font-mono text-sm w-full min-h-[100px]"
165+
required
166+
/>
167+
</div>
168+
169+
<div>
170+
<div className="flex flex-wrap items-center gap-2 mb-1.5">
171+
<label htmlFor="thenSchema" className="text-sm font-medium">
172+
Then Schema
173+
</label>
174+
<TooltipProvider>
175+
<Tooltip>
176+
<TooltipTrigger asChild>
177+
<Info className="h-4 w-4 text-muted-foreground shrink-0" />
178+
</TooltipTrigger>
179+
<TooltipContent className="max-w-[90vw] sm:max-w-sm">
180+
<p>
181+
A schema that applies if the data validates against the
182+
'If' schema. (Required)
183+
<br />
184+
Example: <code>{'{ "required": ["zip_code"] }'}</code>
185+
</p>
186+
</TooltipContent>
187+
</Tooltip>
188+
</TooltipProvider>
189+
</div>
190+
<Textarea
191+
id="thenSchema"
192+
value={thenSchemaStr}
193+
onChange={(e) => setThenSchemaStr(e.target.value)}
194+
placeholder='{ "properties": { "dependent_field": { "type": "number" } } }'
195+
className="font-mono text-sm w-full min-h-[100px]"
196+
required
197+
/>
198+
</div>
199+
200+
<div>
201+
<div className="flex flex-wrap items-center gap-2 mb-1.5">
202+
<label htmlFor="elseSchema" className="text-sm font-medium">
203+
Else Schema (Optional)
204+
</label>
205+
<TooltipProvider>
206+
<Tooltip>
207+
<TooltipTrigger asChild>
208+
<Info className="h-4 w-4 text-muted-foreground shrink-0" />
209+
</TooltipTrigger>
210+
<TooltipContent className="max-w-[90vw] sm:max-w-sm">
211+
<p>
212+
A schema that applies if the data does NOT validate
213+
against the 'If' schema. (Optional)
214+
<br />
215+
Example:{" "}
216+
<code>
217+
{
218+
'{ "properties": { "reason": { "type": "string" } } }'
219+
}
220+
</code>
221+
</p>
222+
</TooltipContent>
223+
</Tooltip>
224+
</TooltipProvider>
225+
</div>
226+
<Textarea
227+
id="elseSchema"
228+
value={elseSchemaStr}
229+
onChange={(e) => setElseSchemaStr(e.target.value)}
230+
placeholder='{ "properties": { "alternative_field": { "type": "boolean" } } }'
231+
className="font-mono text-sm w-full min-h-[100px]"
232+
/>
233+
</div>
234+
235+
{error && (
236+
<p className="text-sm text-red-600 bg-red-100 p-2 rounded-md">
237+
{error}
238+
</p>
239+
)}
240+
241+
<DialogFooter className="mt-6 gap-2 flex-wrap">
242+
<Button
243+
type="button"
244+
variant="outline"
245+
size="sm"
246+
onClick={() => {
247+
setDialogOpen(false);
248+
setError(null); // Clear error on cancel
249+
}}
250+
>
251+
Cancel
252+
</Button>
253+
<Button type="submit" size="sm">
254+
Add Rule
255+
</Button>
256+
</DialogFooter>
257+
</form>
258+
</DialogContent>
259+
</Dialog>
260+
</>
261+
);
262+
};
263+
264+
export default AddConditionalRuleButton;

src/components/SchemaEditor/SchemaVisualEditor.tsx

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import {
66
import type { JSONSchema, NewField } from "@/types/jsonSchema";
77
import { asObjectSchema, isBooleanSchema } from "@/types/jsonSchema";
88
import type React from "react";
9+
import AddConditionalRuleButton, {
10+
type ConditionalRule, // Import ConditionalRule
11+
} from "./AddConditionalRuleButton";
912
import AddFieldButton from "./AddFieldButton";
1013
import SchemaFieldList from "./SchemaFieldList";
1114

@@ -34,8 +37,19 @@ const SchemaVisualEditor: React.FC<SchemaVisualEditorProps> = ({
3437
if (newField.required) {
3538
newSchema = updatePropertyRequired(newSchema, newField.name, true);
3639
}
40+
onChange(newSchema);
41+
};
3742

38-
// Update the schema
43+
// Handle adding a conditional rule (if/then/else)
44+
const handleAddConditionalRule = (rule: ConditionalRule) => {
45+
const newSchema: JSONSchema = {
46+
...asObjectSchema(schema),
47+
if: rule.if,
48+
then: rule.then,
49+
};
50+
if (rule.else) {
51+
newSchema.else = rule.else;
52+
}
3953
onChange(newSchema);
4054
};
4155

@@ -116,24 +130,33 @@ const SchemaVisualEditor: React.FC<SchemaVisualEditorProps> = ({
116130

117131
return (
118132
<div className="p-4 h-full flex flex-col overflow-auto">
119-
<div className="mb-6 flex-shrink-0">
133+
<div className="mb-6 flex-shrink-0 flex flex-wrap gap-2">
134+
{" "}
135+
{/* Added flex-wrap and gap */}
120136
<AddFieldButton onAddField={handleAddField} />
137+
<AddConditionalRuleButton
138+
onAddConditionalRule={handleAddConditionalRule}
139+
/>
121140
</div>
122141

123142
<div className="flex-grow overflow-auto">
124-
{!hasFields ? (
143+
{!hasFields &&
144+
!schema.if /* Also check if conditional rule exists to show fields */ ? (
125145
<div className="text-center py-10 text-muted-foreground">
126-
<p className="mb-3">No fields defined yet</p>
127-
<p className="text-sm">Add your first field to get started</p>
146+
<p className="mb-3">No fields or conditional rules defined yet</p>
147+
<p className="text-sm">
148+
Add your first field or rule to get started
149+
</p>
128150
</div>
129151
) : (
130-
<SchemaFieldList
152+
<SchemaFieldList // SchemaFieldList might need updates to display/edit if/then/else if desired there
131153
schema={schema}
132154
onAddField={handleAddField}
133155
onEditField={handleEditField}
134156
onDeleteField={handleDeleteField}
135157
/>
136158
)}
159+
{/* You might want to add a specific UI section to display/edit the if/then/else rules here */}
137160
</div>
138161
</div>
139162
);

0 commit comments

Comments
 (0)