import { App, Modal, Notice, Setting, } from 'obsidian'; import { ImmerseTask } from './types'; import ImmersePlugin from './main'; // ============ Quick Add Task Modal ============ export class QuickAddTaskModal extends Modal { plugin: ImmersePlugin; taskText: string = ''; estimatedMinutes: number; selectedList: string = 'work'; scheduledDate: string = ''; scheduledTime: string = ''; reminderMinutes: number = 0; constructor(app: App, plugin: ImmersePlugin) { super(app); this.plugin = plugin; this.estimatedMinutes = plugin.settings.defaultEstimateMinutes; this.reminderMinutes = plugin.settings.defaultReminderMinutes; if (plugin.settings.lists.length > 0) { this.selectedList = plugin.settings.lists[0].id; } } onOpen() { const { contentEl } = this; contentEl.addClass('immerse-modal'); contentEl.createEl('h2', { text: '⚡ Add New Task' }); // Task text input new Setting(contentEl) .setName('Task') .addText(text => { text.setPlaceholder('What do you need to do?') .onChange(value => this.taskText = value); text.inputEl.focus(); text.inputEl.addEventListener('keydown', (e) => { if (e.key === 'Enter' && this.taskText.trim()) { this.submitTask(); } }); }); // Time estimate new Setting(contentEl) .setName('Estimated Time') .setDesc('How long do you think this will take?') .addDropdown(dropdown => { const options: Record = { '5': '5 min', '10': '10 min', '15': '15 min', '20': '20 min', '25': '25 min (1 pomodoro)', '30': '30 min', '45': '45 min', '50': '50 min (2 pomodoros)', '60': '1 hour', '90': '1.5 hours', '120': '2 hours', '180': '3 hours', }; Object.entries(options).forEach(([value, label]) => { dropdown.addOption(value, label); }); dropdown.setValue(this.estimatedMinutes.toString()); dropdown.onChange(value => this.estimatedMinutes = parseInt(value)); }); // List selection new Setting(contentEl) .setName('List') .addDropdown(dropdown => { this.plugin.settings.lists.forEach(list => { dropdown.addOption(list.id, `${list.icon} ${list.name}`); }); dropdown.setValue(this.selectedList); dropdown.onChange(value => this.selectedList = value); }); // Scheduled date new Setting(contentEl) .setName('📅 Scheduled Date') .setDesc('Optional: When do you plan to work on this?') .addText(text => { text.setPlaceholder('YYYY-MM-DD') .setValue(this.scheduledDate) .onChange(value => this.scheduledDate = value); text.inputEl.type = 'date'; }); // Scheduled time new Setting(contentEl) .setName('⏰ Scheduled Time') .setDesc('Optional: What time?') .addText(text => { text.setPlaceholder('HH:mm') .setValue(this.scheduledTime) .onChange(value => this.scheduledTime = value); text.inputEl.type = 'time'; }); // Reminder if (this.plugin.settings.enableReminders) { new Setting(contentEl) .setName('🔔 Reminder') .setDesc('Remind me before the scheduled time') .addDropdown(dropdown => { dropdown.addOption('0', 'No reminder'); dropdown.addOption('5', '5 minutes before'); dropdown.addOption('10', '10 minutes before'); dropdown.addOption('15', '15 minutes before'); dropdown.addOption('30', '30 minutes before'); dropdown.addOption('60', '1 hour before'); dropdown.setValue(this.reminderMinutes.toString()); dropdown.onChange(value => this.reminderMinutes = parseInt(value)); }); } // Buttons const buttonContainer = contentEl.createEl('div', { cls: 'immerse-modal-buttons' }); const cancelBtn = buttonContainer.createEl('button', { text: 'Cancel', cls: 'immerse-btn' }); cancelBtn.addEventListener('click', () => this.close()); const addBtn = buttonContainer.createEl('button', { text: 'Add Task', cls: 'immerse-btn immerse-btn-primary' }); addBtn.addEventListener('click', () => this.submitTask()); } submitTask() { if (this.taskText.trim()) { const task = this.plugin.createTask(this.taskText, this.estimatedMinutes, this.selectedList); // Add scheduling data if provided if (this.scheduledDate) { task.scheduledDate = this.scheduledDate; } if (this.scheduledTime) { task.scheduledTime = this.scheduledTime; } if (this.reminderMinutes > 0 && this.scheduledDate && this.scheduledTime) { task.reminderMinutes = this.reminderMinutes; } this.plugin.addTask(task); new Notice('✅ Task added!'); this.close(); } else { new Notice('Please enter a task description'); } } onClose() { const { contentEl } = this; contentEl.empty(); } } // ============ Edit Task Modal ============ export class EditTaskModal extends Modal { plugin: ImmersePlugin; task: ImmerseTask; constructor(app: App, plugin: ImmersePlugin, task: ImmerseTask) { super(app); this.plugin = plugin; this.task = { ...task }; } onOpen() { const { contentEl } = this; contentEl.addClass('immerse-modal'); contentEl.createEl('h2', { text: '✏️ Edit Task' }); new Setting(contentEl) .setName('Task') .addText(text => text .setValue(this.task.text) .onChange(value => this.task.text = value)); new Setting(contentEl) .setName('Estimated Time') .addDropdown(dropdown => { const options: Record = { '5': '5 min', '10': '10 min', '15': '15 min', '20': '20 min', '25': '25 min', '30': '30 min', '45': '45 min', '50': '50 min', '60': '1 hour', '90': '1.5 hours', '120': '2 hours', '180': '3 hours', }; Object.entries(options).forEach(([value, label]) => { dropdown.addOption(value, label); }); dropdown.setValue(this.task.estimatedMinutes.toString()); dropdown.onChange(value => this.task.estimatedMinutes = parseInt(value)); }); new Setting(contentEl) .setName('List') .addDropdown(dropdown => { this.plugin.settings.lists.forEach(list => { dropdown.addOption(list.id, `${list.icon} ${list.name}`); }); dropdown.setValue(this.task.list); dropdown.onChange(value => this.task.list = value); }); new Setting(contentEl) .setName('Notes') .setDesc('Add any additional details or links') .addTextArea(textarea => { textarea .setValue(this.task.notes) .onChange(value => this.task.notes = value); textarea.inputEl.rows = 4; }); // Scheduled date new Setting(contentEl) .setName('📅 Scheduled Date') .setDesc('Optional: When do you plan to work on this?') .addText(text => { text.setPlaceholder('YYYY-MM-DD') .setValue(this.task.scheduledDate || '') .onChange(value => this.task.scheduledDate = value || undefined); text.inputEl.type = 'date'; }); // Scheduled time new Setting(contentEl) .setName('⏰ Scheduled Time') .setDesc('Optional: What time?') .addText(text => { text.setPlaceholder('HH:mm') .setValue(this.task.scheduledTime || '') .onChange(value => this.task.scheduledTime = value || undefined); text.inputEl.type = 'time'; }); // Reminder if (this.plugin.settings.enableReminders) { new Setting(contentEl) .setName('🔔 Reminder') .setDesc('Remind me before the scheduled time') .addDropdown(dropdown => { dropdown.addOption('0', 'No reminder'); dropdown.addOption('5', '5 minutes before'); dropdown.addOption('10', '10 minutes before'); dropdown.addOption('15', '15 minutes before'); dropdown.addOption('30', '30 minutes before'); dropdown.addOption('60', '1 hour before'); dropdown.setValue((this.task.reminderMinutes || 0).toString()); dropdown.onChange(value => { const minutes = parseInt(value); this.task.reminderMinutes = minutes > 0 ? minutes : undefined; }); }); } // Show actual time if task has been worked on if (this.task.actualMinutes > 0) { new Setting(contentEl) .setName('Time Tracked') .setDesc(`You've worked on this task for ${this.plugin.formatTimeHuman(this.task.actualMinutes)}`); } const buttonContainer = contentEl.createEl('div', { cls: 'immerse-modal-buttons' }); const cancelBtn = buttonContainer.createEl('button', { text: 'Cancel', cls: 'immerse-btn' }); cancelBtn.addEventListener('click', () => this.close()); const saveBtn = buttonContainer.createEl('button', { text: 'Save', cls: 'immerse-btn immerse-btn-primary' }); saveBtn.addEventListener('click', () => { this.plugin.updateTask(this.task.id, this.task); new Notice('✅ Task updated!'); this.close(); }); } onClose() { const { contentEl } = this; contentEl.empty(); } } // ============ Report Modal ============ export class ReportModal extends Modal { plugin: ImmersePlugin; startDate: string; endDate: string; selectedListIds: string[] = []; constructor(app: App, plugin: ImmersePlugin) { super(app); this.plugin = plugin; // Default to last 7 days const today = new Date(); this.endDate = today.toISOString().split('T')[0]; const weekAgo = new Date(today); weekAgo.setDate(weekAgo.getDate() - 7); this.startDate = weekAgo.toISOString().split('T')[0]; } onOpen() { const { contentEl } = this; contentEl.addClass('immerse-modal', 'immerse-report-modal'); contentEl.empty(); // Header const header = contentEl.createEl('div', { cls: 'immerse-report-header' }); header.createEl('h2', { text: '📊 Reports', cls: 'immerse-report-title' }); // Filters section this.renderFilters(contentEl); // Generate button const generateBtn = contentEl.createEl('button', { text: '🔄 Generate Report', cls: 'immerse-btn immerse-btn-primary immerse-report-generate-btn' }); generateBtn.addEventListener('click', () => this.renderReport(contentEl)); // Initial report render this.renderReport(contentEl); } renderFilters(container: Element) { const filtersSection = container.createEl('div', { cls: 'immerse-report-filters' }); // Date range const dateRow = filtersSection.createEl('div', { cls: 'immerse-report-filter-row' }); new Setting(dateRow) .setName('Start Date') .addText(text => { text.setValue(this.startDate) .onChange(value => this.startDate = value); text.inputEl.type = 'date'; }); new Setting(dateRow) .setName('End Date') .addText(text => { text.setValue(this.endDate) .onChange(value => this.endDate = value); text.inputEl.type = 'date'; }); // Quick filters const quickFilters = filtersSection.createEl('div', { cls: 'immerse-report-quick-filters' }); quickFilters.createEl('span', { text: 'Quick select: ', cls: 'immerse-filter-label' }); const filters = [ { label: 'Today', days: 0 }, { label: 'Last 7 days', days: 7 }, { label: 'Last 30 days', days: 30 }, { label: 'Last 90 days', days: 90 }, ]; filters.forEach(filter => { const btn = quickFilters.createEl('button', { text: filter.label, cls: 'immerse-quick-filter-btn' }); btn.addEventListener('click', () => { const today = new Date(); this.endDate = today.toISOString().split('T')[0]; const startDate = new Date(today); startDate.setDate(startDate.getDate() - filter.days); this.startDate = startDate.toISOString().split('T')[0]; this.renderReport(container); }); }); } renderReport(container: Element) { // Remove old report if exists const oldReport = container.querySelector('.immerse-report-content'); if (oldReport) oldReport.remove(); // Generate report data const filters: import('./types').ReportFilters = { startDate: this.startDate, endDate: this.endDate, listIds: this.selectedListIds.length > 0 ? this.selectedListIds : undefined, }; const reportData = this.plugin.generateReport(filters); // Create report content const reportContent = container.createEl('div', { cls: 'immerse-report-content' }); // Check if we have data if (reportData.totalTasks === 0) { reportContent.createEl('div', { text: 'No data available for the selected period. Complete some tasks to see your stats!', cls: 'immerse-no-data-message' }); return; } // Summary stats this.renderSummaryStats(reportContent, reportData); // Time by list (donut chart) if (reportData.timeByList.length > 0) { this.renderTimeByList(reportContent, reportData); } // Productivity insights this.renderInsights(reportContent, reportData); // Daily breakdown (bar chart - simplified text version) if (reportData.dailyBreakdown.length > 0) { this.renderDailyBreakdown(reportContent, reportData); } } renderSummaryStats(container: Element, data: import('./types').ReportData) { const statsGrid = container.createEl('div', { cls: 'immerse-stats-grid' }); const stats = [ { label: 'TASKS DONE', value: data.totalTasks.toString(), icon: '✓' }, { label: 'TASKS PER DAY', value: data.tasksPerDay.toFixed(1), icon: '📅' }, { label: 'HOURS PER DAY', value: data.hoursPerDay.toFixed(1), icon: '⏰' }, { label: 'MINS PER TASK', value: data.minsPerTask.toString(), icon: '⏱️' }, { label: 'DAY STREAK', value: data.currentStreak.toString(), icon: '🔥' }, { label: 'TOTAL HOURS', value: (data.totalMinutes / 60).toFixed(1), icon: '⌚' }, ]; stats.forEach(stat => { const statCard = statsGrid.createEl('div', { cls: 'immerse-stat-card' }); statCard.createEl('div', { text: stat.label, cls: 'immerse-stat-label' }); const valueRow = statCard.createEl('div', { cls: 'immerse-stat-value-row' }); valueRow.createEl('span', { text: stat.icon, cls: 'immerse-stat-icon' }); valueRow.createEl('span', { text: stat.value, cls: 'immerse-stat-value' }); }); } renderTimeByList(container: Element, data: import('./types').ReportData) { const section = container.createEl('div', { cls: 'immerse-report-section' }); section.createEl('h3', { text: 'Time by List', cls: 'immerse-report-section-title' }); const listContainer = section.createEl('div', { cls: 'immerse-time-by-list' }); data.timeByList.forEach(item => { const listItem = listContainer.createEl('div', { cls: 'immerse-list-stat-item' }); // List info const listInfo = listItem.createEl('div', { cls: 'immerse-list-info' }); listInfo.createEl('span', { text: item.listIcon, cls: 'immerse-list-icon' }); listInfo.createEl('span', { text: item.listName, cls: 'immerse-list-name' }); // Progress bar const progressBar = listItem.createEl('div', { cls: 'immerse-list-progress' }); const progress = progressBar.createEl('div', { cls: 'immerse-list-progress-fill' }); progress.style.width = `${item.percentage}%`; progress.style.backgroundColor = item.listColor; // Stats const stats = listItem.createEl('div', { cls: 'immerse-list-stats' }); stats.createEl('span', { text: `${item.taskCount} tasks`, cls: 'immerse-list-stat-text' }); stats.createEl('span', { text: `${this.plugin.formatTimeHuman(item.minutes)}`, cls: 'immerse-list-stat-text' }); stats.createEl('span', { text: `${item.percentage.toFixed(1)}%`, cls: 'immerse-list-stat-percentage' }); }); } renderInsights(container: Element, data: import('./types').ReportData) { const section = container.createEl('div', { cls: 'immerse-report-section' }); section.createEl('h3', { text: 'Productivity Insights', cls: 'immerse-report-section-title' }); const insightsGrid = section.createEl('div', { cls: 'immerse-insights-grid' }); if (data.mostProductiveHour !== undefined) { const card = insightsGrid.createEl('div', { cls: 'immerse-insight-card' }); card.createEl('div', { text: 'MOST PRODUCTIVE HOUR', cls: 'immerse-insight-label' }); card.createEl('div', { text: `${data.mostProductiveHour}:00 - ${data.mostProductiveHour + 1}:00`, cls: 'immerse-insight-value' }); } if (data.mostProductiveDay) { const card = insightsGrid.createEl('div', { cls: 'immerse-insight-card' }); card.createEl('div', { text: 'MOST PRODUCTIVE DAY', cls: 'immerse-insight-label' }); card.createEl('div', { text: data.mostProductiveDay, cls: 'immerse-insight-value' }); } if (data.mostProductiveMonth) { const card = insightsGrid.createEl('div', { cls: 'immerse-insight-card' }); card.createEl('div', { text: 'MOST PRODUCTIVE MONTH', cls: 'immerse-insight-label' }); card.createEl('div', { text: data.mostProductiveMonth, cls: 'immerse-insight-value' }); } } renderDailyBreakdown(container: Element, data: import('./types').ReportData) { const section = container.createEl('div', { cls: 'immerse-report-section' }); section.createEl('h3', { text: 'Daily Breakdown', cls: 'immerse-report-section-title' }); const table = section.createEl('table', { cls: 'immerse-daily-table' }); const thead = table.createEl('thead'); const headerRow = thead.createEl('tr'); headerRow.createEl('th', { text: 'Date' }); headerRow.createEl('th', { text: 'Tasks' }); headerRow.createEl('th', { text: 'Hours' }); headerRow.createEl('th', { text: 'Pomodoros' }); const tbody = table.createEl('tbody'); // Show last 14 days max const recentData = data.dailyBreakdown.slice(-14); recentData.forEach(day => { const row = tbody.createEl('tr'); row.createEl('td', { text: day.date }); row.createEl('td', { text: day.tasks.toString() }); row.createEl('td', { text: day.hours.toFixed(1) }); row.createEl('td', { text: day.pomodoros.toString() }); }); } onClose() { const { contentEl } = this; contentEl.empty(); } }