fix(app): align schedules screen shell with app conventions
Move the New schedule button out of the screen header (the only screen using MenuHeader rightContent) into the content filter row, and match the History screen's full-width layout instead of the 720px centered column. Tighten the shared sm SegmentedControl and Button vertical padding so the status switcher and button sit flush with the host-filter pill; this is a global sm change and also affects settings and the schedule form sheet.
This commit is contained in:
@@ -83,7 +83,7 @@ test.describe("Schedules project target", () => {
|
|||||||
await expect(page).not.toHaveURL(/\/h\//);
|
await expect(page).not.toHaveURL(/\/h\//);
|
||||||
await expect(page.getByTestId("schedules-empty")).toBeVisible();
|
await expect(page.getByTestId("schedules-empty")).toBeVisible();
|
||||||
|
|
||||||
await page.getByTestId("schedules-new").click();
|
await page.getByTestId("schedules-empty-new").click();
|
||||||
await expect(page.getByTestId("schedule-form-sheet")).toBeVisible({ timeout: 10_000 });
|
await expect(page.getByTestId("schedule-form-sheet")).toBeVisible({ timeout: 10_000 });
|
||||||
await expect(page.getByTestId("schedule-cwd-trigger")).toHaveCount(0);
|
await expect(page.getByTestId("schedule-cwd-trigger")).toHaveCount(0);
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ test.describe("Schedules project target", () => {
|
|||||||
port: fakePort,
|
port: fakePort,
|
||||||
});
|
});
|
||||||
await expect(page.getByTestId("schedules-empty")).toBeVisible({ timeout: 30_000 });
|
await expect(page.getByTestId("schedules-empty")).toBeVisible({ timeout: 30_000 });
|
||||||
await page.getByTestId("schedules-new").click();
|
await page.getByTestId("schedules-empty-new").click();
|
||||||
await expect(page.getByTestId("schedule-form-sheet")).toBeVisible({ timeout: 10_000 });
|
await expect(page.getByTestId("schedule-form-sheet")).toBeVisible({ timeout: 10_000 });
|
||||||
|
|
||||||
await page.getByRole("button", { name: /select project/i }).click();
|
await page.getByRole("button", { name: /select project/i }).click();
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ interface SchedulesTableProps {
|
|||||||
rows: ScheduleRowView[];
|
rows: ScheduleRowView[];
|
||||||
/**
|
/**
|
||||||
* The form sheet is owned by the screen (it serves both create and edit and
|
* The form sheet is owned by the screen (it serves both create and edit and
|
||||||
* shares the header's "New schedule" button), so the table delegates edit
|
* shares the screen's "New schedule" button), so the table delegates edit
|
||||||
* upward rather than mounting a second sheet here.
|
* upward rather than mounting a second sheet here.
|
||||||
*/
|
*/
|
||||||
onEditSchedule: (schedule: AggregatedSchedule) => void;
|
onEditSchedule: (schedule: AggregatedSchedule) => void;
|
||||||
@@ -32,9 +32,9 @@ interface SchedulesTableProps {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The schedules list: a single settings-style card of rows across every
|
* The schedules list: a single settings-style card of rows across every
|
||||||
* connected host, in a centered, width-constrained reading column matching the
|
* connected host, in a full-width list matching the History screen. Rows own
|
||||||
* projects list. Rows own their host-scoped mutations (pause/resume/run/delete
|
* their host-scoped mutations (pause/resume/run/delete via the mutations hook +
|
||||||
* via the mutations hook + a destructive confirm) and delegate editing upward.
|
* a destructive confirm) and delegate editing upward.
|
||||||
*/
|
*/
|
||||||
export function SchedulesTable({ rows, onEditSchedule }: SchedulesTableProps): ReactElement {
|
export function SchedulesTable({ rows, onEditSchedule }: SchedulesTableProps): ReactElement {
|
||||||
return (
|
return (
|
||||||
@@ -145,14 +145,9 @@ function SchedulesTableRow({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const CONTENT_MAX_WIDTH = 720;
|
|
||||||
|
|
||||||
const styles = StyleSheet.create((theme) => ({
|
const styles = StyleSheet.create((theme) => ({
|
||||||
// Center the card in a readable column, matching settings and projects.
|
// Full-width list padding matching the History screen.
|
||||||
listContent: {
|
listContent: {
|
||||||
width: "100%",
|
paddingHorizontal: { xs: theme.spacing[3], md: theme.spacing[6] },
|
||||||
maxWidth: CONTENT_MAX_WIDTH,
|
|
||||||
alignSelf: "center",
|
|
||||||
paddingHorizontal: theme.spacing[4],
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
borderRadius: theme.borderRadius.md,
|
borderRadius: theme.borderRadius.md,
|
||||||
},
|
},
|
||||||
sm: {
|
sm: {
|
||||||
paddingVertical: theme.spacing[2],
|
paddingVertical: theme.spacing[1.5],
|
||||||
paddingHorizontal: theme.spacing[3],
|
paddingHorizontal: theme.spacing[3],
|
||||||
borderRadius: theme.borderRadius.md,
|
borderRadius: theme.borderRadius.md,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
gap: theme.spacing[1],
|
gap: theme.spacing[1],
|
||||||
},
|
},
|
||||||
segmentSm: {
|
segmentSm: {
|
||||||
paddingVertical: theme.spacing[2],
|
paddingVertical: theme.spacing[1.5],
|
||||||
paddingHorizontal: theme.spacing[4],
|
paddingHorizontal: theme.spacing[4],
|
||||||
},
|
},
|
||||||
segmentMd: {
|
segmentMd: {
|
||||||
|
|||||||
@@ -154,21 +154,12 @@ function SchedulesScreenContent(): ReactElement {
|
|||||||
}));
|
}));
|
||||||
}, [resolvedRows, selectedHost, statusFilter, hosts.length]);
|
}, [resolvedRows, selectedHost, statusFilter, hosts.length]);
|
||||||
|
|
||||||
const headerAction = useMemo(
|
|
||||||
() => (
|
|
||||||
<Button leftIcon={Plus} onPress={openCreate} size="sm" testID="schedules-new">
|
|
||||||
New schedule
|
|
||||||
</Button>
|
|
||||||
),
|
|
||||||
[openCreate],
|
|
||||||
);
|
|
||||||
|
|
||||||
const showLoadError = isError && schedules.length === 0;
|
const showLoadError = isError && schedules.length === 0;
|
||||||
const showHostFilter = hosts.length > 1;
|
const showHostFilter = hosts.length > 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<MenuHeader title="Schedules" rightContent={headerAction} />
|
<MenuHeader title="Schedules" />
|
||||||
<SchedulesScreenBody
|
<SchedulesScreenBody
|
||||||
rows={visibleRows}
|
rows={visibleRows}
|
||||||
hostErrors={hostErrors}
|
hostErrors={hostErrors}
|
||||||
@@ -263,21 +254,26 @@ function SchedulesScreenBody({
|
|||||||
return (
|
return (
|
||||||
<View style={styles.body}>
|
<View style={styles.body}>
|
||||||
<View style={styles.filterRow}>
|
<View style={styles.filterRow}>
|
||||||
{showHostFilter ? (
|
<View style={styles.filterRowControls}>
|
||||||
<HostFilter
|
{showHostFilter ? (
|
||||||
hosts={hosts}
|
<HostFilter
|
||||||
selectedHost={selectedHost}
|
hosts={hosts}
|
||||||
onSelectHost={onSelectHost}
|
selectedHost={selectedHost}
|
||||||
triggerTestID="schedules-host-filter-trigger"
|
onSelectHost={onSelectHost}
|
||||||
|
triggerTestID="schedules-host-filter-trigger"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<SegmentedControl
|
||||||
|
size="sm"
|
||||||
|
value={statusFilter}
|
||||||
|
onValueChange={onStatusFilterChange}
|
||||||
|
options={STATUS_FILTER_OPTIONS}
|
||||||
|
testID="schedules-status-filter"
|
||||||
/>
|
/>
|
||||||
) : null}
|
</View>
|
||||||
<SegmentedControl
|
<Button leftIcon={Plus} onPress={onCreate} size="sm" testID="schedules-new">
|
||||||
size="sm"
|
New schedule
|
||||||
value={statusFilter}
|
</Button>
|
||||||
onValueChange={onStatusFilterChange}
|
|
||||||
options={STATUS_FILTER_OPTIONS}
|
|
||||||
testID="schedules-status-filter"
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={styles.scroll}
|
style={styles.scroll}
|
||||||
@@ -313,8 +309,6 @@ function ScheduleHostErrorsBanner({ errors }: { errors: ScheduleHostError[] }):
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const CONTENT_MAX_WIDTH = 720;
|
|
||||||
|
|
||||||
const styles = StyleSheet.create((theme) => ({
|
const styles = StyleSheet.create((theme) => ({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@@ -332,16 +326,20 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
padding: theme.spacing[6],
|
padding: theme.spacing[6],
|
||||||
},
|
},
|
||||||
filterRow: {
|
filterRow: {
|
||||||
width: "100%",
|
|
||||||
maxWidth: CONTENT_MAX_WIDTH,
|
|
||||||
alignSelf: "center",
|
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
flexWrap: "wrap",
|
justifyContent: "space-between",
|
||||||
gap: theme.spacing[3],
|
gap: theme.spacing[3],
|
||||||
paddingHorizontal: theme.spacing[4],
|
paddingHorizontal: { xs: theme.spacing[3], md: theme.spacing[6] },
|
||||||
paddingTop: theme.spacing[4],
|
paddingTop: theme.spacing[4],
|
||||||
},
|
},
|
||||||
|
filterRowControls: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: theme.spacing[3],
|
||||||
|
flexShrink: 1,
|
||||||
|
flexWrap: "wrap",
|
||||||
|
},
|
||||||
scroll: {
|
scroll: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minHeight: 0,
|
minHeight: 0,
|
||||||
@@ -352,10 +350,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
paddingBottom: theme.spacing[6],
|
paddingBottom: theme.spacing[6],
|
||||||
},
|
},
|
||||||
errorsBannerWrap: {
|
errorsBannerWrap: {
|
||||||
width: "100%",
|
paddingHorizontal: { xs: theme.spacing[3], md: theme.spacing[6] },
|
||||||
maxWidth: CONTENT_MAX_WIDTH,
|
|
||||||
alignSelf: "center",
|
|
||||||
paddingHorizontal: theme.spacing[4],
|
|
||||||
},
|
},
|
||||||
errorsBanner: {
|
errorsBanner: {
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
@@ -369,10 +364,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
fontSize: theme.fontSize.xs,
|
fontSize: theme.fontSize.xs,
|
||||||
},
|
},
|
||||||
filterEmpty: {
|
filterEmpty: {
|
||||||
width: "100%",
|
paddingHorizontal: { xs: theme.spacing[3], md: theme.spacing[6] },
|
||||||
maxWidth: CONTENT_MAX_WIDTH,
|
|
||||||
alignSelf: "center",
|
|
||||||
paddingHorizontal: theme.spacing[4],
|
|
||||||
paddingVertical: theme.spacing[6],
|
paddingVertical: theme.spacing[6],
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user