feat(#37-41): Add domains, ideas, relationships, agents, widgets schema
Schema additions for issues #37-41: New models: - Domain (#37): Life domains (work, marriage, homelab, etc.) - Idea (#38): Brain dumps with pgvector embeddings - Relationship (#39): Generic entity linking (blocks, depends_on) - Agent (#40): ClawdBot agent tracking with metrics - AgentSession (#40): Conversation session tracking - WidgetDefinition (#41): HUD widget registry - UserLayout (#41): Per-user dashboard configuration Updated models: - Task, Event, Project: Added domainId foreign key - User, Workspace: Added new relations New enums: - IdeaStatus: CAPTURED, PROCESSING, ACTIONABLE, ARCHIVED, DISCARDED - RelationshipType: BLOCKS, BLOCKED_BY, DEPENDS_ON, etc. - AgentStatus: IDLE, WORKING, WAITING, ERROR, TERMINATED - EntityType: Added IDEA, DOMAIN Migration: 20260129182803_add_domains_ideas_agents_widgets
This commit is contained in:
111
apps/web/src/lib/utils/date-format.test.ts
Normal file
111
apps/web/src/lib/utils/date-format.test.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
formatDate,
|
||||
formatTime,
|
||||
getDateGroupLabel,
|
||||
isPastTarget,
|
||||
isApproachingTarget,
|
||||
} from "./date-format";
|
||||
|
||||
describe("date-format utils", () => {
|
||||
describe("formatDate", () => {
|
||||
it("should format date in readable format", () => {
|
||||
// Use explicit time to avoid timezone issues
|
||||
const date = new Date("2026-01-29T12:00:00");
|
||||
const result = formatDate(date);
|
||||
expect(result).toMatch(/Jan/);
|
||||
expect(result).toMatch(/2026/);
|
||||
// Note: Day might be 28 or 29 depending on timezone
|
||||
expect(result).toMatch(/\d{1,2}/);
|
||||
});
|
||||
|
||||
it("should handle invalid dates", () => {
|
||||
const result = formatDate(new Date("invalid"));
|
||||
expect(result).toBe("Invalid Date");
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatTime", () => {
|
||||
it("should format time in 12-hour format", () => {
|
||||
const date = new Date("2026-01-29T14:30:00");
|
||||
const result = formatTime(date);
|
||||
expect(result).toMatch(/\d{1,2}:\d{2} [AP]M/i);
|
||||
});
|
||||
|
||||
it("should handle invalid time", () => {
|
||||
const result = formatTime(new Date("invalid"));
|
||||
expect(result).toBe("Invalid Time");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getDateGroupLabel", () => {
|
||||
const today = new Date("2026-01-28T12:00:00");
|
||||
|
||||
it("should return 'Today' for today's date", () => {
|
||||
const date = new Date("2026-01-28T14:00:00");
|
||||
const result = getDateGroupLabel(date, today);
|
||||
expect(result).toBe("Today");
|
||||
});
|
||||
|
||||
it("should return 'Tomorrow' for tomorrow's date", () => {
|
||||
const date = new Date("2026-01-29T10:00:00");
|
||||
const result = getDateGroupLabel(date, today);
|
||||
expect(result).toBe("Tomorrow");
|
||||
});
|
||||
|
||||
it("should return 'This Week' for dates within 7 days", () => {
|
||||
const date = new Date("2026-02-02T10:00:00");
|
||||
const result = getDateGroupLabel(date, today);
|
||||
expect(result).toBe("This Week");
|
||||
});
|
||||
|
||||
it("should return 'Next Week' for dates 7-14 days out", () => {
|
||||
const date = new Date("2026-02-08T10:00:00");
|
||||
const result = getDateGroupLabel(date, today);
|
||||
expect(result).toBe("Next Week");
|
||||
});
|
||||
|
||||
it("should return 'Later' for dates beyond 2 weeks", () => {
|
||||
const date = new Date("2026-03-15T10:00:00");
|
||||
const result = getDateGroupLabel(date, today);
|
||||
expect(result).toBe("Later");
|
||||
});
|
||||
});
|
||||
|
||||
describe("isPastTarget", () => {
|
||||
const now = new Date("2026-01-28T12:00:00");
|
||||
|
||||
it("should return true for past dates", () => {
|
||||
const pastDate = new Date("2026-01-27T10:00:00");
|
||||
expect(isPastTarget(pastDate, now)).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for future dates", () => {
|
||||
const futureDate = new Date("2026-01-29T10:00:00");
|
||||
expect(isPastTarget(futureDate, now)).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false for current time", () => {
|
||||
expect(isPastTarget(now, now)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isApproachingTarget", () => {
|
||||
const now = new Date("2026-01-28T12:00:00");
|
||||
|
||||
it("should return true for dates within 24 hours", () => {
|
||||
const soonDate = new Date("2026-01-29T10:00:00");
|
||||
expect(isApproachingTarget(soonDate, now)).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for dates beyond 24 hours", () => {
|
||||
const laterDate = new Date("2026-01-30T14:00:00");
|
||||
expect(isApproachingTarget(laterDate, now)).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false for past dates", () => {
|
||||
const pastDate = new Date("2026-01-27T10:00:00");
|
||||
expect(isApproachingTarget(pastDate, now)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
69
apps/web/src/lib/utils/date-format.ts
Normal file
69
apps/web/src/lib/utils/date-format.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Date formatting utilities
|
||||
* Provides PDA-friendly date formatting and grouping
|
||||
*/
|
||||
|
||||
import { format, isToday, isTomorrow, differenceInDays, isBefore } from "date-fns";
|
||||
|
||||
/**
|
||||
* Format a date in a readable format
|
||||
*/
|
||||
export function formatDate(date: Date): string {
|
||||
try {
|
||||
return format(date, "MMM d, yyyy");
|
||||
} catch (error) {
|
||||
return "Invalid Date";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format time in 12-hour format
|
||||
*/
|
||||
export function formatTime(date: Date): string {
|
||||
try {
|
||||
return format(date, "h:mm a");
|
||||
} catch (error) {
|
||||
return "Invalid Time";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a PDA-friendly label for date grouping
|
||||
* Returns: "Today", "Tomorrow", "This Week", "Next Week", "Later"
|
||||
*/
|
||||
export function getDateGroupLabel(date: Date, referenceDate: Date = new Date()): string {
|
||||
if (isToday(date)) {
|
||||
return "Today";
|
||||
}
|
||||
|
||||
if (isTomorrow(date)) {
|
||||
return "Tomorrow";
|
||||
}
|
||||
|
||||
const daysUntil = differenceInDays(date, referenceDate);
|
||||
|
||||
if (daysUntil >= 0 && daysUntil <= 7) {
|
||||
return "This Week";
|
||||
}
|
||||
|
||||
if (daysUntil > 7 && daysUntil <= 14) {
|
||||
return "Next Week";
|
||||
}
|
||||
|
||||
return "Later";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a date has passed (PDA-friendly: "target passed" instead of "overdue")
|
||||
*/
|
||||
export function isPastTarget(targetDate: Date, now: Date = new Date()): boolean {
|
||||
return isBefore(targetDate, now);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a date is approaching (within 24 hours)
|
||||
*/
|
||||
export function isApproachingTarget(targetDate: Date, now: Date = new Date()): boolean {
|
||||
const hoursUntil = differenceInDays(targetDate, now);
|
||||
return hoursUntil >= 0 && hoursUntil <= 1;
|
||||
}
|
||||
Reference in New Issue
Block a user