Project Editor
Form component for creating and editing projects with dual-API pattern
Live Example
Create New Project
Fill in the form to create a new project. The editor uses the dual-API pattern (Tastypie for write, GET API for refresh).
Edit Existing Project
Select a project from the autocomplete dropdown to edit. The editor will load the current data and allow you to update it.
Select a project to edit:
Editor in Drawer
Open the editor in a side drawer for a better user experience. This pattern is useful when you want to keep the context visible.
Create Project
Project Editor
A form component for creating new projects or editing existing ones. Implements the dual-API pattern (Tastypie for writes, GET API for refresh).
Usage
Create New Project
import { ProjectEditor } from '@/components/sgerp/editors/project-editor';
import { useState } from 'react';
function MyComponent() {
const [showEditor, setShowEditor] = useState(false);
return (
<>
<button onClick={() => setShowEditor(true)}>
Create Project
</button>
{showEditor && (
<ProjectEditor
onSuccess={(project) => {
console.log('Created:', project);
setShowEditor(false);
}}
onCancel={() => setShowEditor(false)}
/>
)}
</>
);
}
Edit Existing Project (by ID)
import { ProjectEditor } from '@/components/sgerp/editors/project-editor';
function MyComponent() {
const projectId = 123; // The project you want to edit
return (
<ProjectEditor
projectId={projectId}
onSuccess={(project) => {
console.log('Updated:', project);
}}
onCancel={() => {
// Handle cancel
}}
/>
);
}
Edit with Initial Data (No Fetch)
When you already have the project data (e.g., from a table), pass it directly to avoid unnecessary fetching:
import { ProjectEditor } from '@/components/sgerp/editors/project-editor';
import type { Project } from 'sgerp-frontend-lib/lib/sgerp/types/sharing/project';
function MyComponent({ project }: { project: Project }) {
return (
<ProjectEditor
projectId={project.id}
initialData={project}
onSuccess={(updatedProject) => {
console.log('Updated:', updatedProject);
}}
onCancel={() => {
// Handle cancel
}}
/>
);
}
Features
- Create Mode: When
projectIdis not provided, creates a new project - Edit Mode: When
projectIdis provided, loads and updates existing project - Dual-API Pattern: Writes to Tastypie API, refreshes from GET API for consistency
- Form Validation: Required field validation (name is required)
- Error Handling: Displays error messages from API
- Loading States: Shows loading indicator while fetching/saving
- Collection Integration: Automatically updates the project collection on success
Props
| Prop | Type | Required | Description |
|---|---|---|---|
projectId | number | No | ID of project to edit (omit for create mode) |
initialData | Project | No | Pre-loaded project data to avoid fetching. When provided, editor uses this data immediately without API call |
onSuccess | (project: Project) => void | No | Callback when save succeeds with fresh project data |
onCancel | () => void | No | Callback when user cancels editing |
Form Fields
Required Fields
- Name: Project name (string, required)
Optional Fields
- Timezone: Timezone for date/time calculations (dropdown, defaults to UTC)
- External ID: External identifier for imports (string)
- Latitude: Default map location latitude (number)
- Longitude: Default map location longitude (number)
Dual-API Implementation
The editor follows the dual-API pattern documented in CLAUDE.md:
- Write Operation: POST/PATCH to
/api/v2/project/(Tastypie API) - Refresh Operation: GET from
/api/v2/microservices/get?model=project&id={id}(GET API) - Collection Update: Updates
api.collections.projectwith fresh data
This ensures all data in collections maintains consistent format from the GET API.
Example: With Autocomplete Selection
Use SGERPAutocomplete to select a project to edit:
import { ProjectEditor } from '@/components/sgerp/editors/project-editor';
import { SGERPAutocomplete } from '@/components/sgerp/sgerp-autocomplete';
import { useState } from 'react';
function MyComponent() {
const [editingProject, setEditingProject] = useState<number | null>(null);
return (
<div>
{editingProject === null ? (
<div className="space-y-4">
<p>Select a project to edit:</p>
<SGERPAutocomplete
collectionName="project"
onSelectionChange={(id) => id && setEditingProject(id)}
renderLabel={(project) => project.name}
placeholder="Search projects..."
/>
</div>
) : (
<ProjectEditor
projectId={editingProject}
onSuccess={(project) => {
console.log('Updated:', project);
setEditingProject(null);
}}
onCancel={() => setEditingProject(null)}
/>
)}
</div>
);
}
Example: In a Drawer
A drawer pattern provides a better user experience by keeping context visible:
import { ProjectEditor } from '@/components/sgerp/editors/project-editor';
import { useState } from 'react';
function MyComponent() {
const [drawerOpen, setDrawerOpen] = useState(false);
return (
<>
<button onClick={() => setDrawerOpen(true)}>
Create Project
</button>
{/* Drawer */}
{drawerOpen && (
<div className="fixed inset-0 z-50 overflow-hidden">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/50"
onClick={() => setDrawerOpen(false)}
/>
{/* Drawer */}
<div className="absolute right-0 top-0 h-full w-full max-w-md bg-background shadow-xl">
<div className="flex h-full flex-col">
<div className="border-b px-6 py-4">
<h2 className="text-lg font-semibold">Create Project</h2>
</div>
<div className="flex-1 overflow-y-auto px-6 py-4">
<ProjectEditor
onSuccess={(project) => {
console.log('Created:', project);
setDrawerOpen(false);
}}
onCancel={() => setDrawerOpen(false)}
/>
</div>
</div>
</div>
</div>
)}
</>
);
}
Example: In a Dialog
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { ProjectEditor } from '@/components/sgerp/editors/project-editor';
import { useState } from 'react';
function MyComponent() {
const [open, setOpen] = useState(false);
return (
<>
<button onClick={() => setOpen(true)}>
Create Project
</button>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Create New Project</DialogTitle>
</DialogHeader>
<ProjectEditor
onSuccess={(project) => {
console.log('Created:', project);
setOpen(false);
}}
onCancel={() => setOpen(false)}
/>
</DialogContent>
</Dialog>
</>
);
}
Error Handling
The editor handles various error scenarios:
- Network Errors: Displays error message from API response
- Validation Errors: Browser validation for required fields
- Loading Errors: Shows error if project fails to load in edit mode
<ProjectEditor
projectId={123}
onSuccess={(project) => {
// Success toast
toast.success(`Updated: ${project.name}`);
}}
onCancel={() => {
// User cancelled
}}
/>
Styling
The editor uses Tailwind CSS classes and inherits theme colors. All form elements are styled consistently with the design system.
Related
- Dual API Architecture - Understanding the dual-API pattern
- Project Collection - API reference for projects
- Collections - Working with collections