feat: timeline UI overhaul with project/resource panel redesign, quick filters, and API improvements
Redesigned timeline project and resource panels with expanded detail views, added quick filter toolbar, improved drag handling, and enhanced vacation/entitlement router logic. Includes e2e test updates and minor API fixes. Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -1,25 +1,35 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test.describe("Timeline", () => {
|
||||
test.describe.configure({ mode: "serial" });
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.addInitScript(() => {
|
||||
localStorage.setItem("planarchy_theme", JSON.stringify({ mode: "dark" }));
|
||||
});
|
||||
await page.goto("/auth/signin");
|
||||
await page.fill('input[type="email"]', "admin@planarchy.dev");
|
||||
await page.fill('input[type="password"]', "admin123");
|
||||
await page.click('button[type="submit"]');
|
||||
await expect(page).toHaveURL(/\/resources/);
|
||||
await expect(page).toHaveURL(/\/(dashboard|resources)/);
|
||||
await page.goto("/timeline");
|
||||
});
|
||||
|
||||
test("loads and displays the timeline", async ({ page }) => {
|
||||
await expect(page.locator("text=Resource view")).toBeVisible();
|
||||
await expect(page.locator("text=Project view")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: /All Clients|Clients:/ })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: /All Chapters|Chapters:/ })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: /All people|People:/ })).toBeVisible();
|
||||
// Timeline canvas should be visible
|
||||
await expect(page.locator(".overflow-auto")).toBeVisible();
|
||||
await expect(page.locator("div.app-surface.relative.flex-1.overflow-auto")).toBeVisible();
|
||||
});
|
||||
|
||||
test("can switch between resource and project view", async ({ page }) => {
|
||||
await page.click("text=Project view");
|
||||
await expect(page.locator("text=0 projects").or(page.locator("text=/\\d+ projects/"))).toBeVisible();
|
||||
await expect(
|
||||
page.locator("text=0 projects").or(page.locator("text=/\\d+ projects/")),
|
||||
).toBeVisible();
|
||||
await page.click("text=Resource view");
|
||||
await expect(page.locator("text=/\\d+ resources/")).toBeVisible();
|
||||
});
|
||||
@@ -34,8 +44,10 @@ test.describe("Timeline", () => {
|
||||
|
||||
test("filter panel opens and closes", async ({ page }) => {
|
||||
await page.locator("button", { hasText: "Filter" }).click();
|
||||
await expect(page.locator("text=Chapters")).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Filters" })).toBeVisible();
|
||||
await expect(page.getByPlaceholder("Search projects…")).toBeVisible();
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(page.getByRole("heading", { name: "Filters" })).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("shows placeholder bars for unassigned allocations", async ({ page }) => {
|
||||
@@ -44,7 +56,7 @@ test.describe("Timeline", () => {
|
||||
await page.waitForSelector(".overflow-auto", { state: "visible" });
|
||||
// Check that the timeline loaded (resource rows or empty state visible)
|
||||
await expect(
|
||||
page.locator("text=resources").or(page.locator("text=No allocations"))
|
||||
page.locator(".app-toolbar").getByText(/\d+ resources · \d+ allocations/),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -55,10 +67,59 @@ test.describe("Timeline", () => {
|
||||
|
||||
// Try to find and click a placeholder bar (dashed border style)
|
||||
const placeholderBar = page.locator("[style*='dashed']").first();
|
||||
if (await placeholderBar.count() > 0) {
|
||||
if ((await placeholderBar.count()) > 0) {
|
||||
await placeholderBar.click();
|
||||
await expect(page.locator("text=Fill Placeholder").or(page.locator("text=Assign Resource"))).toBeVisible();
|
||||
await expect(
|
||||
page.locator("text=Fill Placeholder").or(page.locator("text=Assign Resource")),
|
||||
).toBeVisible();
|
||||
await page.keyboard.press("Escape");
|
||||
}
|
||||
});
|
||||
|
||||
test("resource and project views keep tooltips opaque in dark mode and support right click", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.waitForSelector(".overflow-auto", { state: "visible" });
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const heatmapTooltip = page
|
||||
.locator("div.fixed.pointer-events-none.rounded-xl.border.border-gray-800")
|
||||
.first();
|
||||
const allocationPopoverField = page.getByText("Hours / day");
|
||||
|
||||
const resourceHoverTarget = page.locator(".relative.overflow-hidden.touch-none").first();
|
||||
await resourceHoverTarget.hover({ position: { x: 120, y: 20 } });
|
||||
await expect(heatmapTooltip).toBeVisible();
|
||||
await expect
|
||||
.poll(async () => {
|
||||
return heatmapTooltip.evaluate((element) => getComputedStyle(element).backgroundColor);
|
||||
})
|
||||
.toBe("rgba(3, 7, 18, 0.96)");
|
||||
|
||||
const resourceAllocation = page
|
||||
.locator(
|
||||
"div.absolute.rounded-md.flex.items-stretch.overflow-hidden.transition-all.duration-75",
|
||||
)
|
||||
.first();
|
||||
await resourceAllocation.click({ button: "right" });
|
||||
await expect(allocationPopoverField).toBeVisible();
|
||||
await page.mouse.click(40, 40);
|
||||
|
||||
await page.getByText("Project view").click();
|
||||
await expect(page.getByText(/projects/)).toBeVisible();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const projectHoverTarget = page.locator(".relative.overflow-hidden.touch-none").first();
|
||||
await projectHoverTarget.hover({ position: { x: 120, y: 20 } });
|
||||
await expect(heatmapTooltip).toBeVisible();
|
||||
await expect
|
||||
.poll(async () => {
|
||||
return heatmapTooltip.evaluate((element) => getComputedStyle(element).backgroundColor);
|
||||
})
|
||||
.toBe("rgba(3, 7, 18, 0.96)");
|
||||
|
||||
const projectAllocation = page.locator("div[style*='top: 2px'][style*='bottom: 2px']").nth(1);
|
||||
await projectAllocation.click({ button: "right" });
|
||||
await expect(allocationPopoverField).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user