diff --git a/documentation/docs/data-table-custom-actions.md b/documentation/docs/data-table-custom-actions.md new file mode 100644 index 0000000..54b3ffd --- /dev/null +++ b/documentation/docs/data-table-custom-actions.md @@ -0,0 +1,369 @@ +--- +id: data-table-custom-actions +title: Data Table Custom Actions +sidebar_label: Data Table Custom Actions +--- + +# Implementing Edit Actions with Modal Forms in Xest Data Table + +This guide demonstrates how to create an edit action with a modal form to update records in Xest Data Table. + +## 1. Create the Edit Component + +First, create a separate component for the edit functionality: + +```typescript:src/components/EditRecordAction/index.tsx +interface EditRecordProps { + record: { + id: number; + name: string; + email: string; + // ... other record fields + }; + onUpdate: () => void; +} + +function EditRecordAction({ record, onUpdate }: EditRecordProps) { + const [open, setOpen] = useState(false); + const [formData, setFormData] = useState({ + name: record.name, + email: record.email, + }); + + const handleUpdate = async () => { + try { + await updateRecord(record.id, formData); + toast.success("Record updated successfully"); + setOpen(false); + onUpdate(); // Refresh table data + } catch (error) { + toast.error("Failed to update record"); + } + }; + + return ( + + + + + + + Edit Record + +
+
+ + setFormData(prev => ({ + ...prev, + name: e.target.value + }))} + /> +
+
+ + setFormData(prev => ({ + ...prev, + email: e.target.value + }))} + /> +
+
+ + + +
+
+ ); +} +``` + +## 2. Add Form Validation + +Use Zod to validate the form data: + +```typescript:src/components/EditRecordAction/validation.ts +const editSchema = z.object({ + name: z.string().min(1, "Name is required"), + email: z.string().email("Invalid email address") +}); + +// In your component: +const handleUpdate = async () => { + const result = editSchema.safeParse(formData); + if (!result.success) { + toast.error(result.error.issues[0].message); + return; + } + + try { + await updateRecord(record.id, formData); + // ... rest of the code + } catch (error) { + // ... error handling + } +}; +``` + +## 3. Integrate with Xest Data Table + +Add the edit action to your table columns: + +```typescript:src/components/YourDataTable/index.tsx +import { useTable } from '@xest-ui/data-table'; + +function YourDataTable() { + const { data: { refresh } } = useTable(); + + const columns: Col[] = [ + // ... other columns + { + title: "Actions", + render: (record) => ( + + ), + } + ]; + + return ( + + + + ); +} +``` + +## 4. Add Loading States + +Improve user experience by adding loading states: + +```typescript +function EditRecordAction({ record, onUpdate }: EditRecordProps) { + const [isLoading, setIsLoading] = useState(false); + + const handleUpdate = async () => { + setIsLoading(true); + try { + await updateRecord(record.id, formData); + toast.success("Record updated successfully"); + setOpen(false); + onUpdate(); + } catch (error) { + toast.error("Failed to update record"); + } finally { + setIsLoading(false); + } + }; + + return ( + + {/* ... dialog content ... */} + + + + + ); +} +``` + +## 5. Handle API Integration + +Create a service function for the update operation: + +```typescript:src/services/records/updateRecord.ts +interface UpdateRecordData { + name: string; + email: string; +} + +export async function updateRecord(id: number, data: UpdateRecordData) { + const response = await fetch(`/api/records/${id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }); + + if (!response.ok) { + throw new Error('Failed to update record'); + } + + return response.json(); +} +``` + +## Best Practices + +1. **Form State Management** + + - Initialize form with current record data + - Handle form changes efficiently + - Validate before submission + +2. **Error Handling** + + - Display validation errors clearly + - Show API error messages to users + - Maintain form state on error + +3. **UX Considerations** + + - Show loading states during submission + - Disable form while submitting + - Close modal on successful update + - Refresh table data after update + +4. **Modal Management** + - Handle modal open/close states + - Reset form when modal closes + - Confirm before closing with unsaved changes + +## Complete Example + +Here's a full implementation combining all the above concepts: + +```typescript:src/components/EditRecordAction/index.tsx +import { useState, useEffect } from 'react'; +import { z } from 'zod'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Loader2, Pencil } from 'lucide-react'; +import { toast } from 'react-hot-toast'; +import { updateRecord } from '@/services/records/updateRecord'; + +const editSchema = z.object({ + name: z.string().min(1, "Name is required"), + email: z.string().email("Invalid email address") +}); + +interface EditRecordProps { + record: { + id: number; + name: string; + email: string; + }; + onUpdate: () => void; +} + +export function EditRecordAction({ record, onUpdate }: EditRecordProps) { + const [open, setOpen] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [formData, setFormData] = useState({ + name: record.name, + email: record.email, + }); + + // Reset form when modal closes + useEffect(() => { + if (!open) { + setFormData({ + name: record.name, + email: record.email, + }); + } + }, [open, record]); + + const handleUpdate = async () => { + const result = editSchema.safeParse(formData); + if (!result.success) { + toast.error(result.error.issues[0].message); + return; + } + + setIsLoading(true); + try { + await updateRecord(record.id, formData); + toast.success("Record updated successfully"); + setOpen(false); + onUpdate(); + } catch (error) { + toast.error("Failed to update record"); + } finally { + setIsLoading(false); + } + }; + + return ( + + + + + + + Edit Record + +
+
+ + setFormData(prev => ({ + ...prev, + name: e.target.value + }))} + disabled={isLoading} + /> +
+
+ + setFormData(prev => ({ + ...prev, + email: e.target.value + }))} + disabled={isLoading} + /> +
+
+ + + +
+
+ ); +} +``` diff --git a/documentation/sidebars.js b/documentation/sidebars.js index 02f7cc6..510ab1e 100644 --- a/documentation/sidebars.js +++ b/documentation/sidebars.js @@ -85,7 +85,7 @@ module.exports = { type: "category", label: "Data Table", collapsed: true, - items: ["data-table-react"], + items: ["data-table-react", "data-table-custom-actions"], }, /* todo