feat(FN-2298): merge fusion/fn-2298
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useCallback, useMemo } from "react";
|
||||
import { Plus, Zap, Globe, Folder } from "lucide-react";
|
||||
import { Plus, Zap, Globe, Folder, X } from "lucide-react";
|
||||
import type { Routine, RoutineCreateInput } from "@fusion/core";
|
||||
import {
|
||||
fetchRoutines,
|
||||
@@ -250,11 +250,31 @@ export function ScheduledTasksModal({ onClose, addToast, projectId }: ScheduledT
|
||||
return (
|
||||
<div className="modal-overlay open" onClick={handleOverlayClick}>
|
||||
<div className="modal modal-lg" role="dialog" aria-modal="true" aria-labelledby="schedules-modal-title">
|
||||
<div className="modal-header">
|
||||
<h3 id="schedules-modal-title">Automations</h3>
|
||||
<div className="modal-header-actions">
|
||||
{/* Scope selector */}
|
||||
<div className="scheduling-scope-selector" role="group" aria-label="Scheduling scope">
|
||||
<div className="modal-header scheduling-modal-header">
|
||||
<div className="scheduling-header-main-row">
|
||||
<div className="detail-title-row">
|
||||
<Zap size={20} className="icon-triage" />
|
||||
<h3 id="schedules-modal-title">Automations</h3>
|
||||
</div>
|
||||
<div className="modal-header-actions">
|
||||
{isShowingList && (
|
||||
<button
|
||||
className="btn btn-primary btn-sm"
|
||||
onClick={() => setRoutineView("create")}
|
||||
aria-label="Create new automation"
|
||||
>
|
||||
<Plus size={14} />
|
||||
New Automation
|
||||
</button>
|
||||
)}
|
||||
<button className="modal-close" onClick={onClose} aria-label="Close">
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="scheduling-header-scope-row" role="group" aria-label="Scheduling scope">
|
||||
<div className="scheduling-scope-selector">
|
||||
<button
|
||||
type="button"
|
||||
className={`scope-btn${activeScope === "global" ? " active" : ""}`}
|
||||
@@ -276,19 +296,6 @@ export function ScheduledTasksModal({ onClose, addToast, projectId }: ScheduledT
|
||||
Project
|
||||
</button>
|
||||
</div>
|
||||
{isShowingList && (
|
||||
<button
|
||||
className="btn btn-primary btn-sm"
|
||||
onClick={() => setRoutineView("create")}
|
||||
aria-label="Create new automation"
|
||||
>
|
||||
<Plus size={14} />
|
||||
New Automation
|
||||
</button>
|
||||
)}
|
||||
<button className="modal-close" onClick={onClose} aria-label="Close">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ vi.mock("lucide-react", () => ({
|
||||
Globe: () => <span data-testid="icon-globe">Global</span>,
|
||||
Folder: () => <span data-testid="icon-folder">Project</span>,
|
||||
Layers: () => <span data-testid="icon-layers">Layers</span>,
|
||||
X: () => <span data-testid="icon-x-close">Close</span>,
|
||||
}));
|
||||
|
||||
vi.mock("@fusion/core", () => ({}));
|
||||
@@ -130,6 +131,25 @@ describe("ScheduledTasksModal", () => {
|
||||
expect(screen.getByText("New Automation")).toBeDefined();
|
||||
});
|
||||
|
||||
it("renders scope controls in a dedicated header row", async () => {
|
||||
mockFetchRoutines.mockResolvedValue([makeRoutine({ name: "Scoped Routine" })]);
|
||||
const { container } = render(<ScheduledTasksModal onClose={onClose} addToast={addToast} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Scoped Routine")).toBeDefined();
|
||||
});
|
||||
|
||||
const headerActions = container.querySelector(".scheduling-header-main-row .modal-header-actions");
|
||||
const scopeRow = container.querySelector(".scheduling-header-scope-row");
|
||||
const scopeSelector = container.querySelector(".scheduling-scope-selector");
|
||||
|
||||
expect(headerActions).toBeTruthy();
|
||||
expect(scopeRow).toBeTruthy();
|
||||
expect(scopeSelector).toBeTruthy();
|
||||
expect(scopeRow?.contains(scopeSelector as Node)).toBe(true);
|
||||
expect(headerActions?.contains(scopeSelector as Node)).toBe(false);
|
||||
});
|
||||
|
||||
it("uses routine APIs with global scope by default", async () => {
|
||||
render(<ScheduledTasksModal onClose={onClose} addToast={addToast} />);
|
||||
|
||||
|
||||
@@ -17054,30 +17054,57 @@ html .column.drag-over * {
|
||||
max-height: 70vh;
|
||||
}
|
||||
|
||||
/* Scheduling modal header */
|
||||
.scheduling-modal-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.scheduling-header-main-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.scheduling-header-main-row .detail-title-row {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.scheduling-header-scope-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding-top: var(--space-sm);
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
/* Scheduling scope selector */
|
||||
.scheduling-scope-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
gap: var(--space-xs);
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 2px;
|
||||
padding: var(--space-xs);
|
||||
}
|
||||
|
||||
.scope-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
gap: var(--space-xs);
|
||||
padding: var(--space-xs) var(--space-md);
|
||||
font-size: var(--text-xs, 12px);
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s, color 0.15s;
|
||||
transition: background-color var(--transition-fast), color var(--transition-fast);
|
||||
}
|
||||
|
||||
.scope-btn:hover {
|
||||
@@ -17087,12 +17114,12 @@ html .column.drag-over * {
|
||||
|
||||
.scope-btn.active {
|
||||
background: var(--todo);
|
||||
color: white;
|
||||
color: var(--cta-text);
|
||||
}
|
||||
|
||||
.scope-btn:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: var(--focus-ring);
|
||||
box-shadow: var(--focus-ring-strong);
|
||||
}
|
||||
|
||||
/* Schedule form scope toggle */
|
||||
@@ -17768,15 +17795,30 @@ html .column.drag-over * {
|
||||
padding-right: var(--space-lg);
|
||||
}
|
||||
|
||||
.scheduling-scope-selector {
|
||||
flex-shrink: 0;
|
||||
.scheduling-header-main-row {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.modal-header-actions {
|
||||
.scheduling-modal-header .modal-header-actions {
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.scheduling-header-scope-row {
|
||||
padding-top: var(--space-sm);
|
||||
}
|
||||
|
||||
.scheduling-scope-selector {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.scope-btn {
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.detail-tabs {
|
||||
padding: 0 var(--space-lg);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user