From 1e1b7e5cfc51926e811614d04f31df6009154940 Mon Sep 17 00:00:00 2001 From: CalvinChuaYH Date: Thu, 26 Feb 2026 19:03:55 +0800 Subject: [PATCH 1/3] feat(ui): add drag-and-drop reordering to projects form --- .../builder/forms/projects-form.tsx | 281 ++++++++++-------- 1 file changed, 165 insertions(+), 116 deletions(-) diff --git a/apps/frontend/components/builder/forms/projects-form.tsx b/apps/frontend/components/builder/forms/projects-form.tsx index 16ca653a9..3258ec006 100644 --- a/apps/frontend/components/builder/forms/projects-form.tsx +++ b/apps/frontend/components/builder/forms/projects-form.tsx @@ -8,6 +8,22 @@ import { RichTextEditor } from '@/components/ui/rich-text-editor'; import { Project } from '@/components/dashboard/resume-component'; import { Plus, Trash2, Github, Globe } from 'lucide-react'; import { useTranslations } from '@/lib/i18n'; +import { + DndContext, + closestCenter, + PointerSensor, + KeyboardSensor, + useSensor, + useSensors, + DragEndEvent, +} from '@dnd-kit/core'; +import { + arrayMove, + SortableContext, + sortableKeyboardCoordinates, + verticalListSortingStrategy, +} from '@dnd-kit/sortable'; +import { DraggableListItem } from '../draggable-list-item'; interface ProjectsFormProps { data: Project[]; @@ -17,6 +33,30 @@ interface ProjectsFormProps { export const ProjectsForm: React.FC = ({ data, onChange }) => { const { t } = useTranslations(); + // Configure drag-and-drop sensors + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }) + ); + + // Handler for drag end event + const handleDragEnd = (event: DragEndEvent) => { + const { active, over } = event; + + if (!over || active.id === over.id) return; + + const oldIndex = data.findIndex((item) => item.id === active.id); + const newIndex = data.findIndex((item) => item.id === over.id); + + if (oldIndex === -1 || newIndex === -1) return; + + // Reorder the array using arrayMove from @dnd-kit + const reordered = arrayMove(data, oldIndex, newIndex); + onChange(reordered); + }; + const handleAdd = () => { const newId = Math.max(...data.map((d) => d.id), 0) + 1; onChange([ @@ -98,120 +138,8 @@ export const ProjectsForm: React.FC = ({ data, onChange }) => -
- {data.map((item) => ( -
- - -
-
- - handleChange(item.id, 'name', e.target.value)} - placeholder={t('builder.forms.projects.placeholders.projectName')} - className="rounded-none border-black bg-white" - /> -
-
- - handleChange(item.id, 'role', e.target.value)} - placeholder={t('builder.forms.projects.placeholders.role')} - className="rounded-none border-black bg-white" - /> -
-
- - handleChange(item.id, 'years', e.target.value)} - placeholder={t('builder.forms.projects.placeholders.years')} - className="rounded-none border-black bg-white" - /> -
-
- - handleChange(item.id, 'github', e.target.value)} - placeholder={t('builder.forms.projects.placeholders.github')} - className="rounded-none border-black bg-white" - /> -
-
- - handleChange(item.id, 'website', e.target.value)} - placeholder={t('builder.forms.projects.placeholders.website')} - className="rounded-none border-black bg-white" - /> -
-
- -
-
- - -
- {item.description?.map((desc, idx) => ( -
-
- handleDescriptionChange(item.id, idx, html)} - placeholder={t('builder.forms.projects.placeholders.description')} - minHeight="60px" - /> -
- -
- ))} -
-
- ))} - - {data.length === 0 && ( -
+ {data.length === 0 ? ( +

{t('builder.genericItemForm.noEntries', { label: t('resume.sections.projects') })}

@@ -224,8 +152,129 @@ export const ProjectsForm: React.FC = ({ data, onChange }) => {t('builder.forms.projects.addFirstProject')}
- )} -
+ ) : ( + + item.id)} + strategy={verticalListSortingStrategy} + > +
+ {data.map((item) => ( + +
+ + +
+
+ + handleChange(item.id, 'name', e.target.value)} + placeholder={t('builder.forms.projects.placeholders.projectName')} + className="rounded-none border-black bg-white" + /> +
+
+ + handleChange(item.id, 'role', e.target.value)} + placeholder={t('builder.forms.projects.placeholders.role')} + className="rounded-none border-black bg-white" + /> +
+
+ + handleChange(item.id, 'years', e.target.value)} + placeholder={t('builder.forms.projects.placeholders.years')} + className="rounded-none border-black bg-white" + /> +
+
+ + handleChange(item.id, 'github', e.target.value)} + placeholder={t('builder.forms.projects.placeholders.github')} + className="rounded-none border-black bg-white" + /> +
+
+ + handleChange(item.id, 'website', e.target.value)} + placeholder={t('builder.forms.projects.placeholders.website')} + className="rounded-none border-black bg-white" + /> +
+
+ +
+
+ + +
+ {item.description?.map((desc, idx) => ( +
+
+ handleDescriptionChange(item.id, idx, html)} + placeholder={t('builder.forms.projects.placeholders.description')} + minHeight="60px" + /> +
+ +
+ ))} +
+
+
+ ))} +
+
+
+ )}
); }; From d7e73d41ea9113263702c06b2f3a561622bce783 Mon Sep 17 00:00:00 2001 From: Calvin Chua <126031516+CalvinChuaYH@users.noreply.github.com> Date: Thu, 26 Feb 2026 19:37:18 +0800 Subject: [PATCH 2/3] Update apps/frontend/components/builder/forms/projects-form.tsx Co-authored-by: kilo-code-bot[bot] <240665456+kilo-code-bot[bot]@users.noreply.github.com> --- apps/frontend/components/builder/forms/projects-form.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/components/builder/forms/projects-form.tsx b/apps/frontend/components/builder/forms/projects-form.tsx index 3258ec006..ad884c741 100644 --- a/apps/frontend/components/builder/forms/projects-form.tsx +++ b/apps/frontend/components/builder/forms/projects-form.tsx @@ -161,7 +161,7 @@ export const ProjectsForm: React.FC = ({ data, onChange }) =>
{data.map((item) => ( -
+