Skip to content

[CORE-152] Add form flow editor#13

Merged
elvisdragonmao merged 32 commits intomainfrom
feat/CORE-152-add-form-flow-editor
Feb 17, 2026
Merged

[CORE-152] Add form flow editor#13
elvisdragonmao merged 32 commits intomainfrom
feat/CORE-152-add-form-flow-editor

Conversation

@Kyle9410-Chen
Copy link
Member

Type of changes

  • Feature

Purpose

  • Add form flow editor

Additional Information

  • Add a new shared component, popover.

@Kyle9410-Chen Kyle9410-Chen self-assigned this Feb 4, 2026
@Kyle9410-Chen Kyle9410-Chen marked this pull request as ready for review February 4, 2026 10:09
Copilot AI review requested due to automatic review settings February 4, 2026 10:09
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a comprehensive form flow editor feature with visual workflow management capabilities. The implementation includes a new Popover shared component and extensive updates to form editing functionality.

Changes:

  • Adds a visual form flow editor with node-based workflow representation including START, END, SECTION, and CONDITION node types
  • Introduces a new Popover shared component using Radix UI primitives for contextual menus
  • Adds development proxy configuration for API requests and wraps the app with ToastProvider

Reviewed changes

Copilot reviewed 24 out of 35 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
vite.config.ts Adds proxy configuration for API requests to development server
package.json / pnpm-lock.yaml Adds @radix-ui/react-popover dependency
src/App.tsx Wraps application with ToastProvider for global toast notifications
src/shared/components/index.ts Exports new Popover component
src/shared/components/Popover/* New Popover component implementation with animations
src/shared/components/Select/* Adds "text" variant for inline select styling
src/features/form/components/AdminFormDetailPages/EditPage.tsx Implements complete flow editor with node management logic
src/features/form/components/AdminFormDetailPages/components/FormEditor/* FlowRenderer and Arrow components for visual workflow
src/features/form/components/AdminFormDetailPages/components/SectionEditor/* Various question editor components moved to subdirectory
src/features/form/components/AdminFormDetailPages/types/workflow.ts Type definitions for workflow nodes
src/features/dashboard/components/AdminSettingsPage.tsx Updates orgSlug to uppercase and adds debug logging
src/features/dashboard/components/ComponentsDemo.tsx Adds Popover demo
src/features/form/components/AdminFormsPage.tsx Adds commented import statement
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

<div>Section Edit Page Content</div>
<button onClick={() => handleEditForm("test")}>Edit Form</button>
<h2>表單結構</h2>
<blockquote className={styles.description}>點擊區塊已新增或編輯條件與問題</blockquote>
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a typo in the Chinese text. The word "已" should be "以" (meaning "to" or "in order to"). The current text reads "點擊區塊已新增或編輯條件與問題" which should be "點擊區塊以新增或編輯條件與問題" (Click on blocks to add or edit conditions and questions).

Suggested change
<blockquote className={styles.description}>點擊區塊已新增或編輯條件與問題</blockquote>
<blockquote className={styles.description}>點擊區塊以新增或編輯條件與問題</blockquote>

Copilot uses AI. Check for mistakes.
import type { NodeItem } from "./types/workflow";

const postProcessNodes = (nodes: NodeItem[]): NodeItem[] => {
console.log("Post-processing nodes:", nodes);
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The console.log statement should be removed before merging to production. Debug logging statements should not be committed to the codebase.

Suggested change
console.log("Post-processing nodes:", nodes);

Copilot uses AI. Check for mistakes.
server: {
proxy: {
"/api": {
target: "https://dev.core-system.sdc.nycu.club",
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The proxy configuration includes a hardcoded development URL "https://dev.core-system.sdc.nycu.club". This should be moved to an environment variable to allow different environments (dev, staging, production) to be configured without code changes. Consider using an environment variable like "VITE_API_BASE_URL" instead.

Copilot uses AI. Check for mistakes.
content={
<div className={styles.popoverContent}>
{node.type === "SECTION" && <Button variant="secondary">編輯</Button>}
{node.type != "END" && node.type != "CONDITION" && (
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent comparison operators are used: line 104 uses != while line 109 uses !==. For consistency and best practices in JavaScript/TypeScript, always use strict equality operators (=== and !==) instead of loose equality operators (== and !=).

Suggested change
{node.type != "END" && node.type != "CONDITION" && (
{node.type !== "END" && node.type !== "CONDITION" && (

Copilot uses AI. Check for mistakes.
Comment on lines +103 to +108
{node.type === "SECTION" && <Button variant="secondary">編輯</Button>}
{node.type != "END" && node.type != "CONDITION" && (
<Button variant="secondary" onClick={onAddSection}>
新增區域
</Button>
)}
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "編輯" (Edit) button on line 103 does not have an onClick handler. This appears to be a placeholder button that does nothing when clicked, which could confuse users. Either add the appropriate handler or remove this button until the functionality is implemented.

Suggested change
{node.type === "SECTION" && <Button variant="secondary">編輯</Button>}
{node.type != "END" && node.type != "CONDITION" && (
<Button variant="secondary" onClick={onAddSection}>
新增區域
</Button>
)}
{node.type != "END" && node.type != "CONDITION" && (
<Button variant="secondary" onClick={onAddSection}>
新增區域
</Button>
)}

Copilot uses AI. Check for mistakes.
if (nodeToMerge && nodeToDelete.type === "CONDITION" && nodeToDelete.nextTrue !== nodeToDelete.mergeId && nodeToDelete.nextFalse !== nodeToDelete.mergeId) {
setToastOpen(true);
setToastTitle("無法刪除條件節點");
setToastDescription("請確保只有一個可辨識的分支路後再嘗試刪除。");
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a typo in the error message. "可辨識的分支路" should be "可辨識的分支路徑" (with the complete word "路徑" meaning "path"). The current text is incomplete.

Suggested change
setToastDescription("請確保只有一個可辨識的分支路後再嘗試刪除。");
setToastDescription("請確保只有一個可辨識的分支路徑後再嘗試刪除。");

Copilot uses AI. Check for mistakes.
export const AdminSettingsPage = () => {
// NOTE: current routes are hard-coded to /orgs/sdc/*
const orgSlug = "sdc";
const orgSlug = "SDC";
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The orgSlug value has been changed from lowercase "sdc" to uppercase "SDC". This may cause issues if the API is case-sensitive, as all the hardcoded routes throughout the codebase use lowercase "sdc" (e.g., "/orgs/sdc/forms"). If this change is intentional, the associated comment on line 43 should be updated to reflect that routes use lowercase but the API expects uppercase. Otherwise, this should be reverted to "sdc" to maintain consistency.

Suggested change
const orgSlug = "SDC";
const orgSlug = "sdc";

Copilot uses AI. Check for mistakes.
);
}, [nodes]);

const renderedIds = new Set<string>();
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The renderedIds Set is declared inside the component but outside the renderNode function. This means it persists across renders but doesn't get reset when the component re-renders. This could cause nodes to not be rendered properly after a state update. The Set should be initialized inside a useMemo hook or the renderNode function should be restructured to handle this correctly.

Copilot uses AI. Check for mistakes.
return;
}

if (!nodeToDelete) return;
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is redundant code - the variable nodeToDelete is checked twice for null/undefined. The check on line 260 is unnecessary since the same check already exists on line 252 with an early return. This second check can be removed.

Suggested change
if (!nodeToDelete) return;

Copilot uses AI. Check for mistakes.
Comment on lines +268 to +271
return { ...node, nextFalse: nodeToDelete.next || (nodeToDelete.mergeId === nodeToDelete.nextTrue ? nodeToDelete.nextFalse : nodeToDelete.nextFalse) };
}
if (node.nextTrue === id) {
return { ...node, nextTrue: nodeToDelete.next || (nodeToDelete.mergeId === nodeToDelete.nextTrue ? nodeToDelete.nextFalse : nodeToDelete.nextFalse) };
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition logic on lines 268, 270, and 271 is complex and appears to have duplicated ternary expressions. The expression (nodeToDelete.mergeId === nodeToDelete.nextTrue ? nodeToDelete.nextFalse : nodeToDelete.nextFalse) evaluates to nodeToDelete.nextFalse in both cases, making the ternary operator redundant. This suggests either a logic error or unnecessary complexity that should be simplified to just nodeToDelete.next || nodeToDelete.nextFalse.

Suggested change
return { ...node, nextFalse: nodeToDelete.next || (nodeToDelete.mergeId === nodeToDelete.nextTrue ? nodeToDelete.nextFalse : nodeToDelete.nextFalse) };
}
if (node.nextTrue === id) {
return { ...node, nextTrue: nodeToDelete.next || (nodeToDelete.mergeId === nodeToDelete.nextTrue ? nodeToDelete.nextFalse : nodeToDelete.nextFalse) };
return { ...node, nextFalse: nodeToDelete.next || nodeToDelete.nextFalse };
}
if (node.nextTrue === id) {
return { ...node, nextTrue: nodeToDelete.next || nodeToDelete.nextFalse };

Copilot uses AI. Check for mistakes.
@elvisdragonmao elvisdragonmao merged commit b2869fe into main Feb 17, 2026
2 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants