feat: make Salesforce WriteToObjectSettings entries editable with save/cancel functionality (#22440)

* feat: make Salesforce WriteToObjectSettings entries editable with save/cancel functionality

- Add editingRows and editingData state to track which rows are being edited
- Implement startEditing, cancelEditing, and saveEditing functions for row-level edit management
- Replace read-only existing entries with editable fields when in edit mode
- Add save/cancel buttons with appropriate icons (check/x) for each row
- Include field name validation to prevent duplicate entries
- Maintain existing delete functionality alongside new edit capabilities
- Update action column width to accommodate save/cancel buttons

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: address Code Rabbit review feedback for WriteToObjectSettings

- Fix spread operator usage to use editData instead of prev[key]
- Add validation for empty field names
- Implement single-row editing for better UX

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Joe Au-Yeung
2025-07-17 10:48:54 -04:00
committed by GitHub
co-authored by Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent 1878802213
commit 292073eb4a
@@ -71,6 +71,10 @@ const WriteToObjectSettings = ({
checkboxFieldValueOptions[0]
);
const [whenToWriteSelectedOption, setWhenToWriteSelectedOption] = useState(whenToWriteToRecordOptions[0]);
const [editingRows, setEditingRows] = useState<Record<string, boolean>>({});
const [editingData, setEditingData] = useState<Record<string, z.infer<typeof writeToRecordEntrySchema>>>(
{}
);
const [newOnWriteToRecordEntry, setNewOnWriteToRecordEntry] = useState<
z.infer<typeof writeToRecordEntrySchema>
>({
@@ -80,6 +84,63 @@ const WriteToObjectSettings = ({
whenToWrite: whenToWriteSelectedOption.value,
});
const startEditing = (key: string) => {
Object.keys(editingRows).forEach((rowKey) => {
if (editingRows[rowKey] && rowKey !== key) {
cancelEditing(rowKey);
}
});
setEditingRows((prev) => ({ ...prev, [key]: true }));
setEditingData((prev) => ({
...prev,
[key]: {
field: key,
fieldType: writeToObjectData[key].fieldType,
value: writeToObjectData[key].value,
whenToWrite: writeToObjectData[key].whenToWrite,
},
}));
};
const cancelEditing = (key: string) => {
setEditingRows((prev) => ({ ...prev, [key]: false }));
setEditingData((prev) => {
const newData = { ...prev };
delete newData[key];
return newData;
});
};
const saveEditing = (key: string) => {
const editData = editingData[key];
if (!editData) return;
if (!editData.field.trim()) {
showToast("Field name cannot be empty", "error");
return;
}
if (editData.field !== key && Object.keys(writeToObjectData).includes(editData.field.trim())) {
showToast("Field already exists", "error");
return;
}
const newWriteToObjectData = { ...writeToObjectData };
if (editData.field !== key) {
delete newWriteToObjectData[key];
}
newWriteToObjectData[editData.field.trim()] = {
fieldType: editData.fieldType,
value: editData.value,
whenToWrite: editData.whenToWrite,
};
updateWriteToObjectData(newWriteToObjectData);
cancelEditing(key);
};
return (
<>
<Section.SubSectionHeader icon="star" labelFor="write-to-object-settings" title={optionLabel}>
@@ -98,77 +159,213 @@ const WriteToObjectSettings = ({
<div className="flex-1">{t("field_type")}</div>
<div className="flex-1">{t("value")}</div>
<div className="flex-1">{t("when_to_write")}</div>
<div className="w-10" />
<div className="w-20" />
</div>
<Section.SubSectionNested>
{Object.keys(writeToObjectData).map((key) => (
<div className="flex items-center gap-2" key={key}>
<div className="flex-1">
<InputField value={key} readOnly size="sm" className="w-full" />
</div>
<div className="flex-1">
<Select
size="sm"
className="w-full"
value={fieldTypeOptions.find(
(option) => option.value === writeToObjectData[key].fieldType
{Object.keys(writeToObjectData).map((key) => {
const isEditing = editingRows[key];
const editData = editingData[key];
return (
<div className="flex items-center gap-2" key={key}>
<div className="flex-1">
{isEditing ? (
<InputField
value={editData?.field || key}
onChange={(e) =>
setEditingData((prev) => ({
...prev,
[key]: { ...editData, field: e.target.value },
}))
}
size="sm"
className="w-full"
/>
) : (
<InputField value={key} readOnly size="sm" className="w-full" />
)}
isDisabled={true}
/>
</div>
<div className="flex-1">
{writeToObjectData[key].fieldType === SalesforceFieldType.DATE ? (
<Select
size="sm"
className="w-full"
value={dateFieldValueOptions.find(
(option) => option.value === writeToObjectData[key].value
)}
isDisabled={true}
/>
) : writeToObjectData[key].fieldType === SalesforceFieldType.CHECKBOX ? (
<Select
size="sm"
className="w-full"
value={checkboxFieldValueOptions.find(
(option) => option.value === writeToObjectData[key].value
)}
isDisabled={true}
/>
) : (
<InputField
value={writeToObjectData[key].value as string}
readOnly
size="sm"
className="w-full"
/>
)}
</div>
<div className="flex-1">
<Select
size="sm"
className="w-full"
value={whenToWriteToRecordOptions.find(
(option) => option.value === writeToObjectData[key].whenToWrite
</div>
<div className="flex-1">
{isEditing ? (
<Select
size="sm"
className="w-full"
options={fieldTypeOptions}
value={fieldTypeOptions.find((option) => option.value === editData?.fieldType)}
onChange={(e) => {
if (e) {
setEditingData((prev) => ({
...prev,
[key]: {
...editData,
fieldType: e.value,
...(e.value === SalesforceFieldType.DATE && {
value: dateFieldValueOptions[0].value,
}),
...(e.value === SalesforceFieldType.CHECKBOX && {
value: checkboxFieldValueOptions[0].value,
}),
},
}));
}
}}
/>
) : (
<Select
size="sm"
className="w-full"
value={fieldTypeOptions.find(
(option) => option.value === writeToObjectData[key].fieldType
)}
isDisabled={true}
/>
)}
isDisabled={true}
/>
</div>
<div className="flex-1">
{isEditing ? (
editData?.fieldType === SalesforceFieldType.DATE ? (
<Select
size="sm"
className="w-full"
options={dateFieldValueOptions}
value={dateFieldValueOptions.find((option) => option.value === editData.value)}
onChange={(e) => {
if (e) {
setEditingData((prev) => ({
...prev,
[key]: { ...editData, value: e.value },
}));
}
}}
/>
) : editData?.fieldType === SalesforceFieldType.CHECKBOX ? (
<Select
size="sm"
className="w-full"
options={checkboxFieldValueOptions}
value={checkboxFieldValueOptions.find((option) => option.value === editData.value)}
onChange={(e) => {
if (e) {
setEditingData((prev) => ({
...prev,
[key]: { ...editData, value: e.value },
}));
}
}}
/>
) : (
<InputField
value={(editData?.value as string) || ""}
onChange={(e) =>
setEditingData((prev) => ({
...prev,
[key]: { ...editData, value: e.target.value },
}))
}
size="sm"
className="w-full"
/>
)
) : writeToObjectData[key].fieldType === SalesforceFieldType.DATE ? (
<Select
size="sm"
className="w-full"
value={dateFieldValueOptions.find(
(option) => option.value === writeToObjectData[key].value
)}
isDisabled={true}
/>
) : writeToObjectData[key].fieldType === SalesforceFieldType.CHECKBOX ? (
<Select
size="sm"
className="w-full"
value={checkboxFieldValueOptions.find(
(option) => option.value === writeToObjectData[key].value
)}
isDisabled={true}
/>
) : (
<InputField
value={writeToObjectData[key].value as string}
readOnly
size="sm"
className="w-full"
/>
)}
</div>
<div className="flex-1">
{isEditing ? (
<Select
size="sm"
className="w-full"
options={whenToWriteToRecordOptions}
value={whenToWriteToRecordOptions.find(
(option) => option.value === editData?.whenToWrite
)}
onChange={(e) => {
if (e) {
setEditingData((prev) => ({
...prev,
[key]: { ...editData, whenToWrite: e.value },
}));
}
}}
/>
) : (
<Select
size="sm"
className="w-full"
value={whenToWriteToRecordOptions.find(
(option) => option.value === writeToObjectData[key].whenToWrite
)}
isDisabled={true}
/>
)}
</div>
<div className="flex w-20 justify-center gap-1">
{isEditing ? (
<>
<Button
size="sm"
StartIcon="check"
variant="icon"
color="primary"
onClick={() => saveEditing(key)}
/>
<Button
size="sm"
StartIcon="x"
variant="icon"
color="secondary"
onClick={() => cancelEditing(key)}
/>
</>
) : (
<>
<Button
size="sm"
StartIcon="pencil"
variant="icon"
color="minimal"
onClick={() => startEditing(key)}
/>
<Button
size="sm"
StartIcon="x"
variant="icon"
color="minimal"
onClick={() => {
const newObject = { ...writeToObjectData };
delete newObject[key];
updateWriteToObjectData(newObject);
}}
/>
</>
)}
</div>
</div>
<div className="flex w-10 justify-center">
<Button
size="sm"
StartIcon="x"
variant="icon"
color="minimal"
onClick={() => {
const newObject = writeToObjectData;
delete writeToObjectData[key];
updateWriteToObjectData(newObject);
}}
/>
</div>
</div>
))}
);
})}
<div className="flex gap-2">
<div className="flex-1">
<InputField
@@ -268,7 +465,7 @@ const WriteToObjectSettings = ({
}}
/>
</div>
<div className="w-10" />
<div className="w-20" />
</div>
</Section.SubSectionNested>
<Button