feat: Add Focus Tools (ADHD-friendly productivity features)
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
- Add Focus Tools dashboard with collapsible help section - Implement Quick Wins page for tasks under 15 minutes - Add Single Task Focus mode to reduce overwhelm - Create Body Doubling page for virtual co-working - Add visual timer, break reminders, and energy tracking - Implement hyperfocus protection alerts - Add ADHD settings panel with customizable options - Include full English and German translations - Fix larger touch targets CSS to not break button layouts - Add Playwright tests for Focus Tools features
This commit is contained in:
+174
-1
@@ -1,8 +1,22 @@
|
||||
import { sql } from "drizzle-orm";
|
||||
import { pgTable, text, varchar, timestamp, integer, boolean, json } from "drizzle-orm/pg-core";
|
||||
import { pgTable, text, varchar, timestamp, integer, boolean, json, date } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
// ADHD Settings Type
|
||||
export interface ADHDSettings {
|
||||
reducedAnimations: boolean;
|
||||
largerTargets: boolean;
|
||||
breakReminderInterval: number; // minutes
|
||||
singleTaskModeDefault: boolean;
|
||||
positiveMessagingLevel: 'minimal' | 'normal' | 'high';
|
||||
hyperfocusProtection: boolean;
|
||||
hyperfocusMaxMinutes: number;
|
||||
visualTimerEnabled: boolean;
|
||||
quickWinThreshold: number; // minutes - tasks under this are "quick wins"
|
||||
energyCheckInsEnabled: boolean;
|
||||
}
|
||||
|
||||
export const users = pgTable("users", {
|
||||
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
||||
username: text("username").notNull().unique(),
|
||||
@@ -36,6 +50,24 @@ export const users = pgTable("users", {
|
||||
work: { start: "09:00", end: "17:00", days: [1, 2, 3, 4, 5] },
|
||||
personal: { start: "18:00", end: "22:00", days: [1, 2, 3, 4, 5, 0, 6] } // Mon-Fri Evening + Weekend
|
||||
}),
|
||||
|
||||
// ADHD Mode Fields
|
||||
adhdMode: boolean("adhd_mode").notNull().default(false),
|
||||
adhdSettings: json("adhd_settings").$type<ADHDSettings>().default({
|
||||
reducedAnimations: false,
|
||||
largerTargets: true,
|
||||
breakReminderInterval: 45,
|
||||
singleTaskModeDefault: false,
|
||||
positiveMessagingLevel: 'normal',
|
||||
hyperfocusProtection: true,
|
||||
hyperfocusMaxMinutes: 90,
|
||||
visualTimerEnabled: true,
|
||||
quickWinThreshold: 10,
|
||||
energyCheckInsEnabled: true,
|
||||
}),
|
||||
lastBreakAt: timestamp("last_break_at"),
|
||||
currentEnergyLevel: text("current_energy_level").default("medium"), // 'low' | 'medium' | 'high'
|
||||
todayEnergyCheckedIn: boolean("today_energy_checked_in").default(false),
|
||||
});
|
||||
|
||||
export const systemSettings = pgTable("system_settings", {
|
||||
@@ -116,6 +148,8 @@ export const insertUserSchema = createInsertSchema(users).pick({
|
||||
language: true,
|
||||
workHours: true,
|
||||
availability: true,
|
||||
adhdMode: true,
|
||||
adhdSettings: true,
|
||||
});
|
||||
|
||||
export const registerSchema = insertUserSchema;
|
||||
@@ -351,3 +385,142 @@ export const insertTaskTimeLogSchema = createInsertSchema(taskTimeLogs).omit({
|
||||
|
||||
export type InsertTaskTimeLog = z.infer<typeof insertTaskTimeLogSchema>;
|
||||
export type TaskTimeLog = typeof taskTimeLogs.$inferSelect;
|
||||
|
||||
// ============================================
|
||||
// ADHD FEATURE TABLES
|
||||
// ============================================
|
||||
|
||||
// Energy Logs - Track daily energy levels
|
||||
export const energyLogs = pgTable("energy_logs", {
|
||||
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
||||
userId: varchar("user_id").references(() => users.id).notNull(),
|
||||
energyLevel: text("energy_level").notNull(), // 'low' | 'medium' | 'high'
|
||||
notes: text("notes"),
|
||||
loggedAt: timestamp("logged_at").defaultNow(),
|
||||
});
|
||||
|
||||
export const insertEnergyLogSchema = createInsertSchema(energyLogs).omit({
|
||||
id: true,
|
||||
loggedAt: true,
|
||||
});
|
||||
|
||||
export type InsertEnergyLog = z.infer<typeof insertEnergyLogSchema>;
|
||||
export type EnergyLog = typeof energyLogs.$inferSelect;
|
||||
|
||||
// Break Logs - Track when users take breaks
|
||||
export const breakLogs = pgTable("break_logs", {
|
||||
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
||||
userId: varchar("user_id").references(() => users.id).notNull(),
|
||||
breakType: text("break_type").notNull(), // 'stretch' | 'water' | 'walk' | 'eyes' | 'snack' | 'breathe' | 'custom'
|
||||
durationMinutes: integer("duration_minutes").notNull().default(5),
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
});
|
||||
|
||||
export const insertBreakLogSchema = createInsertSchema(breakLogs).omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type InsertBreakLog = z.infer<typeof insertBreakLogSchema>;
|
||||
export type BreakLog = typeof breakLogs.$inferSelect;
|
||||
|
||||
// Body Doubling Sessions
|
||||
export const bodyDoublingSessions = pgTable("body_doubling_sessions", {
|
||||
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
||||
hostId: varchar("host_id").references(() => users.id).notNull(),
|
||||
title: text("title").notNull(),
|
||||
sessionType: text("session_type").notNull().default("focus"), // 'focus' | 'brainstorm' | 'admin' | 'creative'
|
||||
startsAt: timestamp("starts_at").notNull(),
|
||||
durationMinutes: integer("duration_minutes").notNull().default(50),
|
||||
maxParticipants: integer("max_participants").notNull().default(5),
|
||||
isPublic: boolean("is_public").notNull().default(true),
|
||||
status: text("status").notNull().default("scheduled"), // 'scheduled' | 'active' | 'completed' | 'cancelled'
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
});
|
||||
|
||||
export const insertBodyDoublingSessionSchema = createInsertSchema(bodyDoublingSessions).omit({
|
||||
id: true,
|
||||
status: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type InsertBodyDoublingSession = z.infer<typeof insertBodyDoublingSessionSchema>;
|
||||
export type BodyDoublingSession = typeof bodyDoublingSessions.$inferSelect;
|
||||
|
||||
// Session Participants
|
||||
export const sessionParticipants = pgTable("session_participants", {
|
||||
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
||||
sessionId: varchar("session_id").references(() => bodyDoublingSessions.id).notNull(),
|
||||
userId: varchar("user_id").references(() => users.id).notNull(),
|
||||
joinedAt: timestamp("joined_at").defaultNow(),
|
||||
leftAt: timestamp("left_at"),
|
||||
});
|
||||
|
||||
export const insertSessionParticipantSchema = createInsertSchema(sessionParticipants).omit({
|
||||
id: true,
|
||||
joinedAt: true,
|
||||
leftAt: true,
|
||||
});
|
||||
|
||||
export type InsertSessionParticipant = z.infer<typeof insertSessionParticipantSchema>;
|
||||
export type SessionParticipant = typeof sessionParticipants.$inferSelect;
|
||||
|
||||
// Daily Challenges for ADHD motivation
|
||||
export const dailyChallenges = pgTable("daily_challenges", {
|
||||
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
||||
userId: varchar("user_id").references(() => users.id).notNull(),
|
||||
challengeType: text("challenge_type").notNull(), // 'complete_tasks' | 'take_breaks' | 'quick_wins' | 'focus_time' | 'use_timer'
|
||||
title: text("title").notNull(),
|
||||
description: text("description"),
|
||||
target: integer("target").notNull(),
|
||||
progress: integer("progress").notNull().default(0),
|
||||
xpReward: integer("xp_reward").notNull(),
|
||||
challengeDate: date("challenge_date").notNull(),
|
||||
completedAt: timestamp("completed_at"),
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
});
|
||||
|
||||
export const insertDailyChallengeSchema = createInsertSchema(dailyChallenges).omit({
|
||||
id: true,
|
||||
progress: true,
|
||||
completedAt: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type InsertDailyChallenge = z.infer<typeof insertDailyChallengeSchema>;
|
||||
export type DailyChallenge = typeof dailyChallenges.$inferSelect;
|
||||
|
||||
// Focus Sessions - Track focused work periods
|
||||
export const focusSessions = pgTable("focus_sessions", {
|
||||
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
||||
userId: varchar("user_id").references(() => users.id).notNull(),
|
||||
taskId: varchar("task_id").references(() => tasks.id),
|
||||
sessionType: text("session_type").notNull().default("pomodoro"), // 'pomodoro' | 'five_minute' | 'deep_focus' | 'visual_timer'
|
||||
plannedMinutes: integer("planned_minutes").notNull(),
|
||||
actualMinutes: integer("actual_minutes"),
|
||||
wasCompleted: boolean("was_completed").default(false),
|
||||
startedAt: timestamp("started_at").defaultNow(),
|
||||
endedAt: timestamp("ended_at"),
|
||||
});
|
||||
|
||||
export const insertFocusSessionSchema = createInsertSchema(focusSessions).omit({
|
||||
id: true,
|
||||
actualMinutes: true,
|
||||
wasCompleted: true,
|
||||
startedAt: true,
|
||||
endedAt: true,
|
||||
});
|
||||
|
||||
export type InsertFocusSession = z.infer<typeof insertFocusSessionSchema>;
|
||||
export type FocusSession = typeof focusSessions.$inferSelect;
|
||||
|
||||
// Encouragements/Affirmations shown to users
|
||||
export const encouragementLogs = pgTable("encouragement_logs", {
|
||||
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
||||
userId: varchar("user_id").references(() => users.id).notNull(),
|
||||
encouragementType: text("encouragement_type").notNull(), // 'task_complete' | 'returning' | 'struggling' | 'streak_broken' | 'milestone'
|
||||
message: text("message").notNull(),
|
||||
shownAt: timestamp("shown_at").defaultNow(),
|
||||
});
|
||||
|
||||
export type EncouragementLog = typeof encouragementLogs.$inferSelect;
|
||||
|
||||
Reference in New Issue
Block a user