diff --git a/test_cases/actions.md b/test_cases/actions.md index f371cfcd..30c5e870 100644 --- a/test_cases/actions.md +++ b/test_cases/actions.md @@ -128,38 +128,42 @@ Functionality is the same process for submission as an “Individual” action, ## Creating -1. Actions can only be created by admins in this system. Sign in as an [admin](authentication.md#validating-admin-sign-in), navigate to the “Admin” tab and press the “+” button on the “Action and Announcement Editor” dropdown. +1. Actions can only be created by admins in this system. Sign in as an [admin](authentication.md#validating-admin-sign-in), navigate to the “Admin” tab and press the “+” button on the “Action / Announcement / Peer Eval / Break Period” dropdown. ![Create Action Button](images/createactionbutton.png) -2. Doing so will bring up the action creation modal, which contains the functionality for creating the following “Action Targets”: Individual, Team, Coach, Admin, Student Announcement, Coach Announcement, Peer evaluation, and Break Period. By default, with no “Action Target” selected, all of the action fields are visible and ready to be added, however each “Action Target” has its own specific set of required fields and the modal should change accordingly with each specific “Action Target”. Verify this by briefly clicking through action targets. +2. Doing so will bring up the action creation modal, which contains the functionality for creating the following “Type”: Individual Action, Team Action, Coach Action, Admin, Student Announcement, Coach Announcement, Peer evaluation, and Break Period. After choosing your Type, the modal will update accordingly. Verify this by briefly clicking through types. + ![Type Action Selection](images/typeactionselection.png) ![Create Action Modal](images/createactionmodal.png) -3. For this test case we will be creating a simple individual action for students to complete. Select “Individual” in the “Action Target” dropdown and fill out the fields from “Action Title” to “Due Date/Announcement End Date with unique/easily checkable information. +3. For this test case we will be creating a simple individual action for students to complete. Select “Individual Action” in the “Type” dropdown and fill out the fields from “Action Title” to the bottom. ![Create Action Modal 2](images/createactionmodal2.png) 4. In the Html section include a simple html that can capture user input like below. ![Create Action Modal 3](images/createactionmodal3.png) -5. For now we can leave the file submission fields blank and proceed with submitting and creating the action. +5. Click the Preview button on the bottom to view what your action or announcement will look like. + ![Preview Action Modal](images/previewactionmodal.png) -6. Once the action has been created we should be able to see it in the same “Acton and Announcement Editor” dropdown at the very bottom of the list since its the newest created. +6. For now we can leave the file submission fields blank and proceed with submitting and creating the action. + +7. Once the action has been created we should be able to see it in the same “Action / Announcement / Peer Eval / Break Period” dropdown. ![Create Action Modal 4](images/createactionmodal4.png) -7. Additionally, the freshly created action should be visible in the dashboard to the admin and all of the semester-related students and coaches +8. Additionally, the freshly created action should be visible in the dashboard to the admin and all of the semester-related students and coaches. ![Create Action 1](images/createaction1.png) ![Create Action 2](images/createaction2.png) -8. Like all other individual actions, this action can only be completed by students, student submissions are not visible to other students, and the action is only completed once every student has completed it. +9. Like all other individual actions, this action can only be completed by students, student submissions are not visible to other students, and the action is only completed once every student has completed it. ## Editing -1. In this application actions cannot be hard deleted in this system but we can still [deactivate](#deactivating) and more importantly edit actions to reuse and repurpose data. Actions can only be edited by [admins](authentication.md#validating-admin-sign-in) and like creation, editing of actions is in the “Admin” tab under the “Action and Announcement Editor” dropdown with edit buttons next to each action. +1. In this application actions cannot be hard deleted in this system but we can still [deactivate](#deactivating) and more importantly edit actions to reuse and repurpose data. Actions can only be edited by [admins](authentication.md#validating-admin-sign-in) and like creation, editing of actions is in the “Admin” tab under the “Action / Announcement / Peer Eval / Break Period” dropdown with edit buttons next to each action. ![Editing Actions 1](images/editingactions1.png) 2. Clicking on the edit button will bring up an action editor modal and it should display the correct action and its details. ![Editing Actions 2](images/editingactions2.png) -3. Modify any of the fields with new information and press the submit button when done to finish editing the action. +3. Modify any of the fields with new information and press the submit button when done to finish editing the action. Note: type cannot be change; you will need to deactivate the action and create a new one. 4. A confirmation popup should appear afterwards and the information modified should be correctly displayed in the “Action and Announcement Editor” dropdown and every other instance of the action in the application. diff --git a/test_cases/images/createaction1.png b/test_cases/images/createaction1.png index b7529275..17aa0a04 100644 Binary files a/test_cases/images/createaction1.png and b/test_cases/images/createaction1.png differ diff --git a/test_cases/images/createaction2.png b/test_cases/images/createaction2.png index 164e32a1..0f5cbce4 100644 Binary files a/test_cases/images/createaction2.png and b/test_cases/images/createaction2.png differ diff --git a/test_cases/images/createactionbutton.png b/test_cases/images/createactionbutton.png index be5c261c..49df2738 100644 Binary files a/test_cases/images/createactionbutton.png and b/test_cases/images/createactionbutton.png differ diff --git a/test_cases/images/createactionmodal.png b/test_cases/images/createactionmodal.png index aae0e5a1..3b36e0d8 100644 Binary files a/test_cases/images/createactionmodal.png and b/test_cases/images/createactionmodal.png differ diff --git a/test_cases/images/createactionmodal2.png b/test_cases/images/createactionmodal2.png index 341c3cc2..9e6ed015 100644 Binary files a/test_cases/images/createactionmodal2.png and b/test_cases/images/createactionmodal2.png differ diff --git a/test_cases/images/createactionmodal3.png b/test_cases/images/createactionmodal3.png index 15175ed5..e3df112f 100644 Binary files a/test_cases/images/createactionmodal3.png and b/test_cases/images/createactionmodal3.png differ diff --git a/test_cases/images/createactionmodal4.png b/test_cases/images/createactionmodal4.png index 4326583f..ce296919 100644 Binary files a/test_cases/images/createactionmodal4.png and b/test_cases/images/createactionmodal4.png differ diff --git a/test_cases/images/editingactions2.png b/test_cases/images/editingactions2.png index 9d2a1b3f..cc69e6ab 100644 Binary files a/test_cases/images/editingactions2.png and b/test_cases/images/editingactions2.png differ diff --git a/test_cases/images/previewactionmodal.png b/test_cases/images/previewactionmodal.png new file mode 100644 index 00000000..1782a415 Binary files /dev/null and b/test_cases/images/previewactionmodal.png differ diff --git a/test_cases/images/typeactionselection.png b/test_cases/images/typeactionselection.png new file mode 100644 index 00000000..f53788cd Binary files /dev/null and b/test_cases/images/typeactionselection.png differ diff --git a/ui/src/components/Tabs/AdminTab/ActionEditor/ActionPanel.js b/ui/src/components/Tabs/AdminTab/ActionEditor/ActionPanel.js index d8206b17..aa991c18 100644 --- a/ui/src/components/Tabs/AdminTab/ActionEditor/ActionPanel.js +++ b/ui/src/components/Tabs/AdminTab/ActionEditor/ActionPanel.js @@ -13,7 +13,6 @@ import { useMemo, useState } from "react"; import { Message } from "semantic-ui-react"; import InnerHTML from "dangerously-set-html-content"; - const short_desc = "short_desc"; const file_types = "file_types"; const action_target = "action_target"; @@ -22,7 +21,7 @@ const page_html = "page_html"; const start_date = "start_date"; const TYPE_HELP = { - individual:` + individual: ` Assigns the same action to each student in the semester group. Completion (green) requires submission by every team member. Student team members can submit actions even if they've previously done so or another team member has submitted. Only coaches, admins, and the submitting student’s team can view submitted actions. All users can see action status and submission time/date.

How to fill this out: @@ -35,8 +34,7 @@ const TYPE_HELP = { Important: If you do not require a form to be filled out (which you would create in the HTML Field), you must request at least one file upload instead. `, - team: - `Assigns the same action to each team in the semester group. Completion (green) requires submission by any team member. Student team members can submit actions even if they've previously done so or another team member has submitted. Only coaches, admins, and the submitting student’s team can view submitted actions. All users can see action status and submission time/date. + team: `Assigns the same action to each team in the semester group. Completion (green) requires submission by any team member. Student team members can submit actions even if they've previously done so or another team member has submitted. Only coaches, admins, and the submitting student’s team can view submitted actions. All users can see action status and submission time/date.

How to fill this out:
    @@ -49,7 +47,7 @@ const TYPE_HELP = {
Important: If you do not require a form to be filled out (which you would create in the HTML Field), you must request at least one file upload instead. `, - coach:` + coach: ` Assigns the same action to each coach in the semester group. Completion (green) requires submission by any coach for the corresponding team. Only coaches, admins can view submitted actions.

How to fill this out: @@ -63,7 +61,7 @@ const TYPE_HELP = { Important: If you do not require a form to be filled out (which you would create in the HTML Field), you must request at least one file upload instead. `, - peer_evaluation:` + peer_evaluation: ` Assigns a peer-evaluation activity. Students complete a structured form; submissions are visible to coaches and admins. All users can see action status and submission time/date.

How to fill this out: @@ -73,7 +71,7 @@ const TYPE_HELP = {
  • Use the Question Builder to create items (e.g., table ratings, mood ratings, feedback, peer feedback), then copy the generated HTML into the HTML Field.
  • `, - student_announcement:` + student_announcement: ` Announcement visible to students. No submissions are required; this is informational only.

    How to fill this out: @@ -83,7 +81,7 @@ const TYPE_HELP = {
  • Enter the announcement content in the HTML Field (you can use basic HTML for formatting).
  • `, - coach_announcement:` + coach_announcement: ` Announcement visible to coaches. No submissions are required; this is informational only.

    How to fill this out: @@ -93,7 +91,7 @@ const TYPE_HELP = {
  • Enter the announcement content in the HTML Field (you can use basic HTML for formatting).
  • `, - break_period:` + break_period: ` Blocks out a break period where actions are paused/limited. Displays informational text during the specified range.

    How to fill this out: @@ -105,7 +103,7 @@ const TYPE_HELP = { `, }; const HTML_HELP = { - individual: ` + individual: `

    Individual action: writing instructions & building a simple form

    Use this area to describe the action and (optionally) include a small HTML form students will fill out @@ -113,27 +111,36 @@ const HTML_HELP = {

    Example form (copy/paste and then edit labels/fields):
    -<div>
    -  <h2>Week 1 Artifacts, Tasks, and Deliverables</h2>
    +  <h2>Submit Project Proposal</h2>
       <form class="ui form" action="/db/submitAction" method="POST" enctype="multipart/form-data">
         <ul>
    -      <li>Hold a project kick-off meeting with your sponsor this week or next week.</li>
    -      <li>Gather enough detail to write the project synopsis (due in a future action).</li>
    -      <li>Complete a team social event (off-campus if possible).</li>
    -      <div class="required field"> <!-- Marked 'required' to enforce submission --> 
    -        <label for="Social_Event">Social Event - time, date, and place</label>
    -        <input required name="Social_Event" type="text">
    +      <li>Project title and team members</li>
    +      <li>Background and motivation</li>
    +      <li>Project goals and objectives</li>
    +      <div class="required field">
    +        <label for="Team_Members">Team Members</label>
    +        <input required name="Team_Members" type="text" placeholder="List all team members">
           </div>
    -      <li>Decide on a professional team name (clever is OK, keep it appropriate).</li>
    +        <div class="required field">
    +          <label>Will you be using the project name as your team name?</label>
    +          <div style="display:flex; gap:1rem; align-items:center; margin-top:0.5rem;">
    +            <div>
    +              <input type="radio" id="Yes" name="Same_Project_Name" value="Yes" required>
    +              <label for="Yes">Yes</label>
    +            </div>
    +            <div>
    +              <input type="radio" id="No" name="Same_Project_Name" value="No" required>
    +              <label for="No">No</label>
    +            </div>
    +          </div>
    +        </div>
    +      <li>Decide on a professional team name if it's not the project name(clever is OK, keep it appropriate).</li>
           <div> <!-- Optional field (no 'required' attribute) --> 
             <label for="Team_Name">Team Name</label>
             <input required name="Team_Name" type="text">
           </div>
         </ul>
    -  </form>
    -</div>
    -    
    - + </form>

    Tip: The required attribute on an input and/or wrapping it in class="required field" makes that question mandatory before submission. @@ -146,10 +153,8 @@ const HTML_HELP = { Use this section to describe what the entire team must complete. You can include optional HTML inputs if teams need to record values instead of uploading a document.

    - Example form (copy/paste and then edit labels/fields):
    -<div>
       <h2>Week 1 Artifacts, Tasks, and Deliverables</h2>
       <form class="ui form" action="/db/submitAction" method="POST" enctype="multipart/form-data">
         <ul>
    @@ -160,15 +165,26 @@ const HTML_HELP = {
             <label for="Social_Event">Social Event - time, date, and place</label>
             <input required name="Social_Event" type="text">
           </div>
    -      <li>Decide on a professional team name (clever is OK, keep it appropriate).</li>
    +        <div class="required field">
    +          <label>Will you be using the project name as your team name?</label>
    +          <div style="display:flex; gap:1rem; align-items:center; margin-top:0.5rem;">
    +            <div>
    +              <input type="radio" id="Yes" name="Same_Project_Name" value="Yes" required>
    +              <label for="Yes">Yes</label>
    +            </div>
    +            <div>
    +              <input type="radio" id="No" name="Same_Project_Name" value="No" required>
    +              <label for="No">No</label>
    +            </div>
    +          </div>
    +        </div>
    +      <li>Decide on a professional team name if not using the project name(clever is OK, keep it appropriate).</li>
           <div> <!-- Optional field (no 'required' attribute) --> 
             <label for="Team_Name">Team Name</label>
             <input required name="Team_Name" type="text">
           </div>
         </ul>
    -  </form>
    -</div>
    -    
    + </form>

    Tip: Keep questions short and use textareas for longer team responses.

    `, @@ -183,15 +199,29 @@ const HTML_HELP = { <form class="ui form" action="/db/submitAction" method="POST"> <h3>Team Review Summary</h3> <div class="required field"> <!-- Required overall score --> - <label for="Score">Overall Score (1-5)</label> - <input required name="Score" type="number" min="1" max="5"> + <div class="required field"> + <label>Overall Score</label> + <div style="display:flex; gap:1rem; align-items:center; margin-top:0.5rem;"> + <div> + <input type="radio" id="1" name="Score" value="1" required> + <label for="1">1</label> + </div> + <div> + <input type="radio" id="2" name="Score" value="2" required> + <label for="2">2</label> + </div> + <div> + <input type="radio" id="3" name="Score" value="3" required> + <label for="3">3</label> + </div> + </div> + </div> </div> <div> <!-- Optional written feedback --> <label for="Feedback">Comments / Notes</label> <textarea name="Feedback"></textarea> </div> -</form> - +</form> `, peer_evaluation: ` @@ -247,46 +277,50 @@ const HTML_HELP = { `, }; - export default function ActionPanel(props) { const [open, setOpen] = useState(true); const [errors, setErrors] = useState([]); // track action form errors - - let initialState = useMemo(() => ({ - action_id: props.actionData?.action_id || "", - action_title: props.actionData?.action_title || "", - semester: props.actionData?.semester || "", - action_target: props.actionData?.action_target || "", - date_deleted: props.actionData?.date_deleted || "", - short_desc: props.actionData?.short_desc || "", - start_date: props.actionData?.start_date || "", - due_date: props.actionData?.due_date || "", - page_html: props.actionData?.page_html || "", - file_types: props.actionData?.file_types || "", - file_size: props.actionData?.file_size - ? humanFileSize(props.actionData?.file_size, false, 0) - : "", - }), [props.actionData]); - + + let initialState = useMemo( + () => ({ + action_id: props.actionData?.action_id || "", + action_title: props.actionData?.action_title || "", + semester: props.actionData?.semester || "", + action_target: props.actionData?.action_target || "", + date_deleted: props.actionData?.date_deleted || "", + short_desc: props.actionData?.short_desc || "", + start_date: props.actionData?.start_date || "", + due_date: props.actionData?.due_date || "", + page_html: props.actionData?.page_html || "", + file_types: props.actionData?.file_types || "", + file_size: props.actionData?.file_size + ? humanFileSize(props.actionData?.file_size, false, 0) + : "", + }), + [props.actionData], + ); + let submissionModalMessages = props.create - ? { - SUCCESS: "The action has been created.", - FAIL: "We were unable to create your action.", - SUBMISSON_ERROR: "There were invalid inputs. Please try again.", - } - : { - SUCCESS: "The action has been Edited.", - FAIL: "We were unable to receive your edits.", - SUBMISSON_ERROR: "There were invalid inputs. Please try again.", - }; + ? { + SUCCESS: "The action has been created.", + FAIL: "We were unable to create your action.", + SUBMISSON_ERROR: "There were invalid inputs. Please try again.", + } + : { + SUCCESS: "The action has been Edited.", + FAIL: "We were unable to receive your edits.", + SUBMISSON_ERROR: "There were invalid inputs. Please try again.", + }; let semesterMap = {}; - + for (let i = 0; i < props.semesterData.length; i++) { const semester = props.semesterData[i]; semesterMap[semester.semester_id] = semester.name; } - - const [selectedType, setSelectedType] = useState(initialState.action_target || ""); + + const [selectedType, setSelectedType] = useState( + initialState.action_target || "", + ); const isEdit = !props.create; let submitRoute = props.create @@ -302,27 +336,33 @@ export default function ActionPanel(props) { options: DROPDOWN_ITEMS.actionTarget, required: true, disabled: isEdit, - }]; - if (selectedType) { - formFieldArray.push({ - type: "note", - name: "type_help", - content: ( - -
    - - ), - }); - } - console.log('selectedType =', selectedType, 'ACTION_TARGETS =', ACTION_TARGETS); - formFieldArray.push( + }, + ]; + if (selectedType) { + formFieldArray.push({ + type: "note", + name: "type_help", + content: ( + +
    + + ), + }); + } + console.log( + "selectedType =", + selectedType, + "ACTION_TARGETS =", + ACTION_TARGETS, + ); + formFieldArray.push( { type: "input", label: "Action Title", @@ -353,67 +393,68 @@ export default function ActionPanel(props) { placeHolder: "Start Date", name: "start_date", required: true, - }, - ); - if ( - selectedType === ACTION_TARGETS.coach_announcement || - selectedType === ACTION_TARGETS.student_announcement || - selectedType === (ACTION_TARGETS.break_period || 'break_period') - ) { - formFieldArray.push({ - type: "date", - label: "End Date", - placeHolder: "End Date", - name: "due_date", - required: true, - }); -} else { - // Other actions — Due Dates - formFieldArray.push( - { + }, + ); + if ( + selectedType === ACTION_TARGETS.coach_announcement || + selectedType === ACTION_TARGETS.student_announcement || + selectedType === (ACTION_TARGETS.break_period || "break_period") + ) { + formFieldArray.push({ + type: "date", + label: "End Date", + placeHolder: "End Date", + name: "due_date", + required: true, + }); + } else { + // Other actions — Due Dates + formFieldArray.push({ type: "date", label: "Due Date", placeHolder: "Due Date", name: "due_date", required: true, - } - ); -} -// I can remove if -if (selectedType === ACTION_TARGETS.individual || + }); + } + // I can remove if + if ( + selectedType === ACTION_TARGETS.individual || selectedType === ACTION_TARGETS.team || selectedType === ACTION_TARGETS.coach || selectedType === ACTION_TARGETS.coach_announcement || - selectedType === ACTION_TARGETS.student_announcement || + selectedType === ACTION_TARGETS.student_announcement || selectedType === ACTION_TARGETS.peer_evaluation ) { - formFieldArray.push({ - type: "note", - name: "peer_eval_note", - content: ( - - Instructions -
    - - ), - }); -} -// if (selectedType === ACTION_TARGETS.peer_evaluation) { -// // Peer evaluation — add note about form builder -// formFieldArray.push( -// { -// type: "note", -// label: "Note: For peer evaluations, the HTML editor will be replaced with a form builder in the future.", -// } -// ); -// } -// Now continue adding the rest of the fields INSIDE the array -formFieldArray.push( + formFieldArray.push({ + type: "note", + name: "peer_eval_note", + content: ( + + Instructions +
    + + ), + }); + } + // if (selectedType === ACTION_TARGETS.peer_evaluation) { + // // Peer evaluation — add note about form builder + // formFieldArray.push( + // { + // type: "note", + // label: "Note: For peer evaluations, the HTML editor will be replaced with a form builder in the future.", + // } + // ); + // } + // Now continue adding the rest of the fields INSIDE the array + formFieldArray.push( // PLANNING: When the action is a peer-eval, we would replace textArea with our fourm buider // Or add a taggle to switch bettwen the html and the form builder { @@ -443,7 +484,7 @@ formFieldArray.push( placeHolder: "Active", name: "date_deleted", }, -); + ); // validation for the action form const validateForm = (data) => { @@ -539,7 +580,6 @@ formFieldArray.push( if (isEdit) data.action_target = initialState.action_target; return data; - }; //Processing to be done before data is sent to the backend. @@ -571,7 +611,7 @@ formFieldArray.push( return formData; } }; - + if (props.isOpenCallback) { return ( ( -
    - {/* Optional header area */} -
    -

    {data.action_title || "Untitled"}

    - {data.short_desc && ( -

    {data.short_desc}

    - )} - {/* Show which type and dates for clarity */} -
    -
    Type: {data.action_target || "—"}
    - {data.start_date && ( -
    Start: {data.start_date}
    - )} - {data.due_date && ( -
    Due: {data.due_date}
    - )} - {data.announcement_end_date && ( -
    End: {data.announcement_end_date}
    - )} -
    -
    - - {/* Render the HTML exactly as it will appear */} -
    - No HTML content yet.

    "} /> -
    -
    - ), - }} + enabled: true, + title: "Action/Announcement Preview", + render: (data) => ( +
    + {/* Optional header area */} +
    +

    + {data.action_title || "Untitled"} +

    + {data.short_desc && ( +

    {data.short_desc}

    + )} + {/* Show which type and dates for clarity */} +
    +
    + Type: {data.action_target || "—"} +
    + {data.start_date && ( +
    + Start: {data.start_date} +
    + )} + {data.due_date && ( +
    + Due: {data.due_date} +
    + )} + {data.announcement_end_date && ( +
    + End: {data.announcement_end_date} +
    + )} +
    +
    + + {/* Render the HTML exactly as it will appear */} +
    + No HTML content yet.

    "} + /> +
    +
    + ), + }} /> ); } else { diff --git a/ui/src/components/shared/editors/DatabaseTableEditor.js b/ui/src/components/shared/editors/DatabaseTableEditor.js index b40f5021..701c8baa 100644 --- a/ui/src/components/shared/editors/DatabaseTableEditor.js +++ b/ui/src/components/shared/editors/DatabaseTableEditor.js @@ -43,7 +43,7 @@ export default function DatabaseTableEditor(props) { const [open, setOpen] = React.useState(false); const [errors, setErrors] = useState(props.errors); const [errorSubmitted, setErrorSubmitted] = useState(false); // enable dynamic error updates after first submission - const[showPreview,setShowPreview]=useState(false); + const [showPreview, setShowPreview] = useState(false); const formRef = useRef(null); // maintain the current form data in the case of submission error @@ -153,7 +153,10 @@ export default function DatabaseTableEditor(props) { let cleanedFormData = { ...formData }; if (cleanedFormData.file_types) { - cleanedFormData.file_types = cleanedFormData.file_types.replace(/\s+/g, ""); + cleanedFormData.file_types = cleanedFormData.file_types.replace( + /\s+/g, + "", + ); } // data to be sent to backend @@ -291,14 +294,20 @@ export default function DatabaseTableEditor(props) { * other editor.js files, will contain the name of the column being queried from the db. * */ let fieldComponents = []; - const requiresTypeFirst = formFieldArray.some(f => f.name === "action_target"); + const requiresTypeFirst = formFieldArray.some( + (f) => f.name === "action_target", + ); for (let i = 0; i < formFieldArray.length; i++) { let field = formFieldArray[i]; // if no action_target/type is chosen yet, hide all fields except the Type dropdown - if (requiresTypeFirst && !formData.action_target && field.name !== "action_target") { - continue; -} + if ( + requiresTypeFirst && + !formData.action_target && + field.name !== "action_target" + ) { + continue; + } if (!field.hidden) { switch (field.type) { case "input": @@ -451,12 +460,12 @@ export default function DatabaseTableEditor(props) { disabled={field.loading || field.disabled} value={formData[field.name] || field.nullValue} name={field.name} - onChange={handleChange} + onChange={handleChange} /> - + , ); break; - } else if (field.name === "semester_group") { + } else if (field.name === "semester_group") { // Semester is conditionally required (only for students) const isRequired = formData.type === "student"; fieldComponents.push( @@ -625,13 +634,11 @@ export default function DatabaseTableEditor(props) { , ); break; - case "note": { + case "note": { // render whatever content ActionPanel passes if (field.content) { fieldComponents.push( - - {field.content} - + {field.content}, ); } break; @@ -669,8 +676,8 @@ export default function DatabaseTableEditor(props) { } const isProjectLocked = formFieldArray.some( - (field) => field.name === "synopsis" && field.disabled -); + (field) => field.name === "synopsis" && field.disabled, + ); const modalActions = () => { let mock = false; @@ -684,7 +691,7 @@ export default function DatabaseTableEditor(props) { { key: "Close", content: "Close", - } + }, ]; } @@ -695,18 +702,18 @@ export default function DatabaseTableEditor(props) { content: "Cancel", onClick: (event) => handleCancel(event), color: "grey", - } + }, ]; } - const actions = [ + const actions = [ { key: "cancel", content: "Cancel", onClick: (event) => handleCancel(event), color: "grey", }, - ... (props.preview?.enabled && !!formData.action_target + ...(props.preview?.enabled && !!formData.action_target ? [ { key: "preview", @@ -716,8 +723,7 @@ export default function DatabaseTableEditor(props) { onClick: openPreview, }, ] - : [] - ), + : []), { key: "submit", @@ -745,27 +751,30 @@ export default function DatabaseTableEditor(props) { const openPreview = (e) => { e?.stopPropagation?.(); + e?.preventDefault?.(); + suppressParentCloseRef.current = true; setShowPreview(true); }; const closePreview = (e) => { e?.stopPropagation?.(); - e?.preventDefault?.(); - suppressParentCloseRef.current = true; setShowPreview(false); - setTimeout(() => { suppressParentCloseRef.current = false; }, 100); + setTimeout(() => { + suppressParentCloseRef.current = false; + }, 300); }; const handleParentClose = (e) => { // ignore closes coming from preview teardown if (showPreview || suppressParentCloseRef.current) { e?.stopPropagation?.(); + e?.preventDefault?.(); return; } setOpen(false); props.isOpenCallback?.(false); }; - + if (props.isOpenCallback) { return ( <> @@ -806,7 +815,6 @@ export default function DatabaseTableEditor(props) { ), }} actions={modalActions()} - /> {/* { suppressParentCloseRef.current = true; closeSubmissionModal(); setTimeout(() => { suppressParentCloseRef.current = false; }, 100);}} + onClose={() => { + suppressParentCloseRef.current = true; + closeSubmissionModal(); + setTimeout(() => { + suppressParentCloseRef.current = false; + }, 100); + }} dimmer="false" mountNode={document.body} /> {/* PREVIEW MODAL (needed in isOpenCallback branch too) */} {props.preview?.enabled && ( {props.preview.render?.(formData)}
    }} - actions={[ - { key: "close", content: "Close", onClick: (e) => { e.stopPropagation(); closePreview(e);}} - ]} - /> + className="stacked" + open={showPreview} + size="large" + onClose={closePreview} + closeOnEscape + closeOnDimmerClick={false} + dimmer={false} + mountNode={document.body} + header={props.preview.title ?? "Preview"} + style={{ + position: "fixed", + top: "50%", + left: "20%", + transform: "translate(-50%,-50%)", + margin: 0, + zIndex: 2000, + }} + content={{ + content: ( +
    + {props.preview.render?.(formData)} +
    + ), + }} + actions={[ + { + key: "close", + content: "Close", + onClick: (e) => { + e.stopPropagation(); + closePreview(e); + }, + }, + ]} + /> )} - ); } else { return ( @@ -856,7 +895,7 @@ export default function DatabaseTableEditor(props) { closeOnEscape={false} className={"sticky"} trigger={trigger} - onClose={ handleParentClose } + onClose={handleParentClose} onOpen={() => { setOpen(true); }} @@ -894,17 +933,30 @@ export default function DatabaseTableEditor(props) { size="large" onClose={closePreview} closeOnEscape - closeOnDimmerClick = {false} + closeOnDimmerClick={false} header={props.preview.title ?? "Preview"} content={{ content: ( -
    +
    {props.preview.render?.(formData)}
    ), }} actions={[ - { key: "close", content: "Close", onClick: (e) => { e.stopPropagation(); closePreview(e);}} + { + key: "close", + content: "Close", + onClick: (e) => { + e.stopPropagation(); + closePreview(e); + }, + }, ]} /> )} @@ -914,7 +966,13 @@ export default function DatabaseTableEditor(props) { size="tiny" open={!!submissionModalOpen} {...generateModalFields()} - onClose={() => { suppressParentCloseRef.current = true; closeSubmissionModal(); setTimeout(() => { suppressParentCloseRef.current = false; }, 100);}} + onClose={() => { + suppressParentCloseRef.current = true; + closeSubmissionModal(); + setTimeout(() => { + suppressParentCloseRef.current = false; + }, 100); + }} dimmer="false" mountNode={document.body} /> diff --git a/ui/src/css/components/modal.css b/ui/src/css/components/modal.css index 6b484a56..0523986e 100644 --- a/ui/src/css/components/modal.css +++ b/ui/src/css/components/modal.css @@ -300,7 +300,67 @@ body.dark-mode .ui.toggle.checkbox label:after { } .ui.modal { - border-radius: 10px; - box-shadow: 0 0 25px rgba(0, 0, 0, 0.2); - padding: 12px; + max-height: calc(100vh - 4rem); + overflow-y: visible; +} + +.ui.modal.stacked-modal { + z-index: 1000 !important; +} + +.ui.modal.stacked { + z-index: 1001 !important; +} + +.ui.modal.stacked[dimmer="false"] { + background: transparent !important; +} + +/* Keep parent modal visible when child modal opens */ +.ui.dimmer.modals.page.transition.visible.active { + display: flex !important; + align-items: center; + justify-content: center; +} + +/* Ensure both modals are visible when stacked */ +.ui.page.modals.dimmer.transition.visible.active > .ui.modal { + display: block !important; +} + +/* Stack multiple modals properly */ +.ui.page.modals.dimmer.transition.visible.active > .ui.modal:first-child { + z-index: 1000 !important; +} + +.ui.page.modals.dimmer.transition.visible.active > .ui.modal:nth-child(2) { + z-index: 1001 !important; +} + +.ui.page.modals.dimmer.transition.visible.active > .ui.modal:nth-child(3) { + z-index: 1002 !important; +} + +/* Darken the dimmer for stacked modals */ +.ui.page.modals.dimmer.transition.visible.active:has(.ui.modal.stacked) { + background-color: rgba(0, 0, 0, 0.75) !important; +} + +body.dark-mode + .ui.page.modals.dimmer.transition.visible.active:has(.ui.modal.stacked) { + background-color: rgba(0, 0, 0, 0.9) !important; +} + +.ui.modal .content { + overflow-y: visible; +} + +.ui.modal .ui.dropdown .menu { + max-height: 50vh; + overflow-y: auto; +} + +body.dimmed, +body.dimmed > .pusher { + overflow: hidden; }