
This commit includes the following changes: - Deleted the `package-lock.json` file to streamline dependency management. - Updated the `financials.py` endpoint to return a comprehensive user financial summary, including net balance, total group spending, debts, and credits. - Enhanced the `expense.py` CRUD operations to handle enum values and improve error handling during expense deletion. - Introduced new schemas in `financials.py` for user financial summaries and debt/credit tracking. - Refactored the costs service to improve group balance summary calculations. These changes aim to improve the application's financial tracking capabilities and maintain cleaner dependency management.
861 lines
29 KiB
Vue
861 lines
29 KiB
Vue
<template>
|
|
<Dialog v-model="isOpen" class="chore-detail-sheet">
|
|
<div class="sheet-container">
|
|
<!-- Enhanced Header -->
|
|
<div class="sheet-header">
|
|
<div class="header-content">
|
|
<div class="header-main">
|
|
<div class="chore-icon">
|
|
<BaseIcon :name="getChoreIcon(chore)" class="icon" />
|
|
</div>
|
|
<div class="header-text">
|
|
<Heading :level="2" class="chore-title">
|
|
{{ chore?.name || 'Chore Details' }}
|
|
</Heading>
|
|
<div class="chore-meta">
|
|
<span class="chore-type">{{ chore?.type === 'group' ? 'Group Task' : 'Personal Task'
|
|
}}</span>
|
|
<span class="chore-status" :class="getStatusClass(chore)">
|
|
{{ getStatusText(chore) }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="header-actions">
|
|
<Button variant="ghost" size="sm" @click="toggleFavorite" v-if="chore">
|
|
<BaseIcon :name="isFavorite ? 'heroicons:heart-solid' : 'heroicons:heart-20-solid'"
|
|
:class="{ 'text-error-500': isFavorite }" />
|
|
</Button>
|
|
<Button variant="ghost" size="sm" @click="close" class="close-button">
|
|
<BaseIcon name="heroicons:x-mark-20-solid" class="w-5 h-5" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Actions Bar -->
|
|
<div v-if="chore" class="quick-actions">
|
|
<Button v-if="canComplete" variant="solid" color="success" size="sm" @click="handleComplete"
|
|
:loading="isCompleting" class="quick-action">
|
|
<template #icon-left>
|
|
<BaseIcon name="heroicons:check-20-solid" />
|
|
</template>
|
|
Complete
|
|
</Button>
|
|
<Button v-if="canClaim" variant="solid" color="primary" size="sm" @click="handleClaim"
|
|
:loading="isClaiming" class="quick-action">
|
|
<template #icon-left>
|
|
<BaseIcon name="heroicons:hand-raised-20-solid" />
|
|
</template>
|
|
Claim
|
|
</Button>
|
|
<Button variant="outline" color="neutral" size="sm" @click="startTimer" v-if="!isTimerRunning"
|
|
class="quick-action">
|
|
<template #icon-left>
|
|
<BaseIcon name="heroicons:play-20-solid" />
|
|
</template>
|
|
Start Timer
|
|
</Button>
|
|
<Button variant="solid" color="warning" size="sm" @click="stopTimer" v-if="isTimerRunning"
|
|
class="quick-action">
|
|
<template #icon-left>
|
|
<BaseIcon name="heroicons:pause-20-solid" />
|
|
</template>
|
|
{{ formatTimerDuration(currentTimerDuration) }}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Content Sections with Progressive Disclosure -->
|
|
<div class="sheet-content">
|
|
<!-- Primary Information (Always Visible) -->
|
|
<Card variant="elevated" color="neutral" padding="lg" class="info-section">
|
|
<div class="section-header">
|
|
<h3 class="section-title">Overview</h3>
|
|
<div class="section-badges">
|
|
<span v-if="chore?.frequency" class="frequency-badge">
|
|
{{ getFrequencyLabel(chore.frequency) }}
|
|
</span>
|
|
<span v-if="getDueDateBadge(chore)" class="due-date-badge" :class="getDueDateClass(chore)">
|
|
{{ getDueDateBadge(chore) }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="overview-grid">
|
|
<div class="overview-item">
|
|
<div class="item-icon">
|
|
<BaseIcon name="heroicons:calendar-20-solid" />
|
|
</div>
|
|
<div class="item-content">
|
|
<span class="item-label">Due Date</span>
|
|
<span class="item-value">{{ formatDate(chore?.next_due_date) }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="overview-item">
|
|
<div class="item-icon">
|
|
<BaseIcon name="heroicons:user-20-solid" />
|
|
</div>
|
|
<div class="item-content">
|
|
<span class="item-label">Created by</span>
|
|
<span class="item-value">{{ chore?.creator?.full_name || chore?.creator?.email ||
|
|
'Unknown'
|
|
}}</span>
|
|
</div>
|
|
</div>
|
|
<div class="overview-item">
|
|
<div class="item-icon">
|
|
<BaseIcon name="heroicons:arrow-path-20-solid" />
|
|
</div>
|
|
<div class="item-content">
|
|
<span class="item-label">Frequency</span>
|
|
<span class="item-value">{{ getFrequencyDescription(chore) }}</span>
|
|
</div>
|
|
</div>
|
|
<div v-if="getCurrentAssignment(chore)" class="overview-item">
|
|
<div class="item-icon">
|
|
<BaseIcon name="heroicons:user-circle-20-solid" />
|
|
</div>
|
|
<div class="item-content">
|
|
<span class="item-label">Assigned to</span>
|
|
<span class="item-value">{{ getAssignedUserName(chore) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
|
|
<!-- Description Section (Expandable) -->
|
|
<Card v-if="chore?.description" variant="outlined" color="neutral" padding="lg"
|
|
class="description-section">
|
|
<div class="expandable-section" @click="toggleSection('description')">
|
|
<div class="section-header-expandable">
|
|
<div class="section-header-content">
|
|
<BaseIcon name="heroicons:document-text-20-solid" class="section-icon" />
|
|
<h3 class="section-title">Description</h3>
|
|
</div>
|
|
<BaseIcon
|
|
:name="expandedSections.description ? 'heroicons:chevron-up-20-solid' : 'heroicons:chevron-down-20-solid'"
|
|
class="expand-icon" />
|
|
</div>
|
|
</div>
|
|
<TransitionExpand>
|
|
<div v-if="expandedSections.description" class="section-content">
|
|
<p class="description-text">{{ chore.description }}</p>
|
|
</div>
|
|
</TransitionExpand>
|
|
</Card>
|
|
|
|
<!-- History & Progress Section (Expandable) -->
|
|
<Card variant="outlined" color="neutral" padding="lg" class="history-section">
|
|
<div class="expandable-section" @click="toggleSection('history')">
|
|
<div class="section-header-expandable">
|
|
<div class="section-header-content">
|
|
<BaseIcon name="heroicons:clock-20-solid" class="section-icon" />
|
|
<h3 class="section-title">History & Progress</h3>
|
|
<span class="completion-count">{{ getCompletionCount(chore) }} completions</span>
|
|
</div>
|
|
<BaseIcon
|
|
:name="expandedSections.history ? 'heroicons:chevron-up-20-solid' : 'heroicons:chevron-down-20-solid'"
|
|
class="expand-icon" />
|
|
</div>
|
|
</div>
|
|
<TransitionExpand>
|
|
<div v-if="expandedSections.history" class="section-content">
|
|
<div v-if="chore?.assignments && chore.assignments.length > 0" class="assignments-list">
|
|
<div v-for="assignment in chore.assignments.slice(0, showAllHistory ? undefined : 3)"
|
|
:key="assignment.id" class="assignment-item">
|
|
<div class="assignment-status">
|
|
<div :class="['status-indicator', { 'completed': assignment.is_complete }]">
|
|
<BaseIcon
|
|
:name="assignment.is_complete ? 'heroicons:check-20-solid' : 'heroicons:clock-20-solid'" />
|
|
</div>
|
|
</div>
|
|
<div class="assignment-details">
|
|
<span class="assignment-user">{{ assignment.assigned_user?.full_name ||
|
|
assignment.assigned_user?.email }}</span>
|
|
<div class="assignment-dates">
|
|
<span class="due-date">Due: {{ formatDate(assignment.due_date) }}</span>
|
|
<span v-if="assignment.completed_at" class="completed-date">
|
|
Completed: {{ formatDate(assignment.completed_at) }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<Button v-if="chore.assignments.length > 3 && !showAllHistory" variant="ghost"
|
|
color="primary" size="sm" @click="showAllHistory = true" class="show-more-button">
|
|
Show {{ chore.assignments.length - 3 }} more assignments
|
|
</Button>
|
|
</div>
|
|
<div v-else class="no-history">
|
|
<BaseIcon name="heroicons:calendar-x-mark-20-solid" class="no-history-icon" />
|
|
<p>No completion history yet</p>
|
|
</div>
|
|
</div>
|
|
</TransitionExpand>
|
|
</Card>
|
|
|
|
<!-- Sub-tasks Section (Expandable) -->
|
|
<Card v-if="chore?.child_chores?.length" variant="outlined" color="neutral" padding="lg"
|
|
class="subtasks-section">
|
|
<div class="expandable-section" @click="toggleSection('subtasks')">
|
|
<div class="section-header-expandable">
|
|
<div class="section-header-content">
|
|
<BaseIcon name="heroicons:list-bullet-20-solid" class="section-icon" />
|
|
<h3 class="section-title">Sub-tasks</h3>
|
|
<span class="subtask-count">{{ chore.child_chores.length }} tasks</span>
|
|
</div>
|
|
<BaseIcon
|
|
:name="expandedSections.subtasks ? 'heroicons:chevron-up-20-solid' : 'heroicons:chevron-down-20-solid'"
|
|
class="expand-icon" />
|
|
</div>
|
|
</div>
|
|
<TransitionExpand>
|
|
<div v-if="expandedSections.subtasks" class="section-content">
|
|
<div class="subtasks-list">
|
|
<div v-for="sub in chore.child_chores" :key="sub.id" class="subtask-item">
|
|
<div class="subtask-checkbox">
|
|
<BaseIcon name="heroicons:check-circle-20-solid" v-if="isSubtaskComplete(sub)"
|
|
class="completed-icon" />
|
|
<div v-else class="uncompleted-circle"></div>
|
|
</div>
|
|
<span class="subtask-name" :class="{ 'completed': isSubtaskComplete(sub) }">
|
|
{{ sub.name }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</TransitionExpand>
|
|
</Card>
|
|
|
|
<!-- Scheduling Section (Expandable) -->
|
|
<Card variant="outlined" color="neutral" padding="lg" class="scheduling-section">
|
|
<div class="expandable-section" @click="toggleSection('scheduling')">
|
|
<div class="section-header-expandable">
|
|
<div class="section-header-content">
|
|
<BaseIcon name="heroicons:calendar-days-20-solid" class="section-icon" />
|
|
<h3 class="section-title">Scheduling</h3>
|
|
</div>
|
|
<BaseIcon
|
|
:name="expandedSections.scheduling ? 'heroicons:chevron-up-20-solid' : 'heroicons:chevron-down-20-solid'"
|
|
class="expand-icon" />
|
|
</div>
|
|
</div>
|
|
<TransitionExpand>
|
|
<div v-if="expandedSections.scheduling" class="section-content">
|
|
<div class="scheduling-grid">
|
|
<div class="scheduling-item">
|
|
<span class="scheduling-label">Next Due Date</span>
|
|
<span class="scheduling-value">{{ formatDate(chore?.next_due_date) }}</span>
|
|
</div>
|
|
<div v-if="chore?.last_completed_at" class="scheduling-item">
|
|
<span class="scheduling-label">Last Completed</span>
|
|
<span class="scheduling-value">{{ formatDate(chore.last_completed_at) }}</span>
|
|
</div>
|
|
<div class="scheduling-item">
|
|
<span class="scheduling-label">Created</span>
|
|
<span class="scheduling-value">{{ formatDate(chore?.created_at) }}</span>
|
|
</div>
|
|
<div class="scheduling-item">
|
|
<span class="scheduling-label">Last Updated</span>
|
|
<span class="scheduling-value">{{ formatDate(chore?.updated_at) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</TransitionExpand>
|
|
</Card>
|
|
</div>
|
|
|
|
<!-- Footer Actions -->
|
|
<div class="sheet-footer">
|
|
<div class="footer-actions">
|
|
<Button variant="outline" color="neutral" @click="editChore" v-if="canEdit">
|
|
<template #icon-left>
|
|
<BaseIcon name="heroicons:pencil-20-solid" />
|
|
</template>
|
|
Edit
|
|
</Button>
|
|
<Button variant="outline" color="error" @click="deleteChore" v-if="canDelete">
|
|
<template #icon-left>
|
|
<BaseIcon name="heroicons:trash-20-solid" />
|
|
</template>
|
|
Delete
|
|
</Button>
|
|
<div class="spacer"></div>
|
|
<Button variant="ghost" color="neutral" @click="close">
|
|
Close
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Dialog>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
import { format, isToday, isTomorrow, isPast, isYesterday } from 'date-fns'
|
|
import type { ChoreWithCompletion } from '@/types/chore'
|
|
import BaseIcon from '@/components/BaseIcon.vue'
|
|
import { Dialog, Card, Button, Heading } from '@/components/ui'
|
|
import TransitionExpand from '@/components/ui/TransitionExpand.vue'
|
|
|
|
interface Props {
|
|
modelValue: boolean
|
|
chore: ChoreWithCompletion | null
|
|
}
|
|
|
|
const props = defineProps<Props>()
|
|
const emit = defineEmits<{
|
|
(e: 'update:modelValue', value: boolean): void
|
|
(e: 'complete', chore: ChoreWithCompletion): void
|
|
(e: 'claim', chore: ChoreWithCompletion): void
|
|
(e: 'edit', chore: ChoreWithCompletion): void
|
|
(e: 'delete', chore: ChoreWithCompletion): void
|
|
}>()
|
|
|
|
// State
|
|
const expandedSections = ref({
|
|
description: false,
|
|
history: false,
|
|
subtasks: false,
|
|
scheduling: false
|
|
})
|
|
const showAllHistory = ref(false)
|
|
const isFavorite = ref(false)
|
|
const isCompleting = ref(false)
|
|
const isClaiming = ref(false)
|
|
const isTimerRunning = ref(false)
|
|
const currentTimerDuration = ref(0)
|
|
const timerInterval = ref<number | null>(null)
|
|
|
|
// Computed
|
|
const isOpen = computed({
|
|
get: () => props.modelValue,
|
|
set: (val: boolean) => emit('update:modelValue', val),
|
|
})
|
|
|
|
const canComplete = computed(() => {
|
|
if (!props.chore) return false
|
|
const assignment = getCurrentAssignment(props.chore)
|
|
return assignment && !assignment.is_complete
|
|
})
|
|
|
|
const canClaim = computed(() => {
|
|
if (!props.chore) return false
|
|
return props.chore.type === 'group' && !getCurrentAssignment(props.chore)
|
|
})
|
|
|
|
const canEdit = computed(() => {
|
|
// Add logic based on user permissions
|
|
return true
|
|
})
|
|
|
|
const canDelete = computed(() => {
|
|
// Add logic based on user permissions
|
|
return true
|
|
})
|
|
|
|
// Methods
|
|
function close() {
|
|
emit('update:modelValue', false)
|
|
}
|
|
|
|
function formatDate(dateStr?: string) {
|
|
if (!dateStr) return 'Not set'
|
|
const date = new Date(dateStr)
|
|
|
|
if (isToday(date)) return 'Today'
|
|
if (isTomorrow(date)) return 'Tomorrow'
|
|
if (isYesterday(date)) return 'Yesterday'
|
|
|
|
return format(date, 'MMM d, yyyy')
|
|
}
|
|
|
|
function getChoreIcon(chore?: ChoreWithCompletion | null) {
|
|
if (!chore) return 'heroicons:clipboard-document-list-20-solid'
|
|
|
|
if (chore.type === 'group') return 'heroicons:user-group-20-solid'
|
|
return 'heroicons:user-20-solid'
|
|
}
|
|
|
|
function getStatusClass(chore?: ChoreWithCompletion | null) {
|
|
if (!chore) return ''
|
|
|
|
const assignment = getCurrentAssignment(chore)
|
|
if (assignment?.is_complete) return 'status-completed'
|
|
|
|
if (chore.next_due_date && isPast(new Date(chore.next_due_date))) {
|
|
return 'status-overdue'
|
|
}
|
|
|
|
return 'status-pending'
|
|
}
|
|
|
|
function getStatusText(chore?: ChoreWithCompletion | null) {
|
|
if (!chore) return 'Unknown'
|
|
|
|
const assignment = getCurrentAssignment(chore)
|
|
if (assignment?.is_complete) return 'Completed'
|
|
|
|
if (chore.next_due_date && isPast(new Date(chore.next_due_date))) {
|
|
return 'Overdue'
|
|
}
|
|
|
|
return 'Pending'
|
|
}
|
|
|
|
function getCurrentAssignment(chore?: ChoreWithCompletion | null) {
|
|
if (!chore?.assignments || chore.assignments.length === 0) return null
|
|
return chore.assignments[chore.assignments.length - 1]
|
|
}
|
|
|
|
function getAssignedUserName(chore?: ChoreWithCompletion | null) {
|
|
const assignment = getCurrentAssignment(chore)
|
|
if (!assignment?.assigned_user) return 'Unassigned'
|
|
return assignment.assigned_user.full_name || assignment.assigned_user.email
|
|
}
|
|
|
|
function getFrequencyLabel(frequency?: string) {
|
|
const map: Record<string, string> = {
|
|
one_time: 'One-time',
|
|
daily: 'Daily',
|
|
weekly: 'Weekly',
|
|
monthly: 'Monthly',
|
|
custom: 'Custom'
|
|
}
|
|
return map[frequency || ''] || frequency || 'Unknown'
|
|
}
|
|
|
|
function getFrequencyDescription(chore?: ChoreWithCompletion | null) {
|
|
if (!chore) return 'Unknown'
|
|
|
|
const { frequency, custom_interval_days } = chore
|
|
if (frequency === 'custom' && custom_interval_days) {
|
|
return `Every ${custom_interval_days} days`
|
|
}
|
|
return getFrequencyLabel(frequency)
|
|
}
|
|
|
|
function getDueDateBadge(chore?: ChoreWithCompletion | null) {
|
|
if (!chore?.next_due_date) return null
|
|
|
|
const date = new Date(chore.next_due_date)
|
|
if (isToday(date)) return 'Due Today'
|
|
if (isTomorrow(date)) return 'Due Tomorrow'
|
|
if (isPast(date)) return 'Overdue'
|
|
|
|
return null
|
|
}
|
|
|
|
function getDueDateClass(chore?: ChoreWithCompletion | null) {
|
|
if (!chore?.next_due_date) return ''
|
|
|
|
const date = new Date(chore.next_due_date)
|
|
if (isPast(date)) return 'overdue'
|
|
if (isToday(date)) return 'due-today'
|
|
if (isTomorrow(date)) return 'due-tomorrow'
|
|
|
|
return ''
|
|
}
|
|
|
|
function getCompletionCount(chore?: ChoreWithCompletion | null) {
|
|
if (!chore?.assignments) return 0
|
|
return chore.assignments.filter(a => a.is_complete).length
|
|
}
|
|
|
|
function isSubtaskComplete(subtask: any) {
|
|
// This would need to be implemented based on your subtask completion logic
|
|
return false
|
|
}
|
|
|
|
function toggleSection(section: keyof typeof expandedSections.value) {
|
|
expandedSections.value[section] = !expandedSections.value[section]
|
|
}
|
|
|
|
function toggleFavorite() {
|
|
isFavorite.value = !isFavorite.value
|
|
// Implement favorite logic
|
|
}
|
|
|
|
async function handleComplete() {
|
|
if (!props.chore) return
|
|
|
|
isCompleting.value = true
|
|
try {
|
|
emit('complete', props.chore)
|
|
} finally {
|
|
isCompleting.value = false
|
|
}
|
|
}
|
|
|
|
async function handleClaim() {
|
|
if (!props.chore) return
|
|
|
|
isClaiming.value = true
|
|
try {
|
|
emit('claim', props.chore)
|
|
} finally {
|
|
isClaiming.value = false
|
|
}
|
|
}
|
|
|
|
function editChore() {
|
|
if (props.chore) {
|
|
emit('edit', props.chore)
|
|
}
|
|
}
|
|
|
|
function deleteChore() {
|
|
if (props.chore) {
|
|
emit('delete', props.chore)
|
|
}
|
|
}
|
|
|
|
function startTimer() {
|
|
isTimerRunning.value = true
|
|
currentTimerDuration.value = 0
|
|
timerInterval.value = window.setInterval(() => {
|
|
currentTimerDuration.value += 1
|
|
}, 1000)
|
|
}
|
|
|
|
function stopTimer() {
|
|
isTimerRunning.value = false
|
|
if (timerInterval.value) {
|
|
clearInterval(timerInterval.value)
|
|
timerInterval.value = null
|
|
}
|
|
}
|
|
|
|
function formatTimerDuration(seconds: number) {
|
|
const mins = Math.floor(seconds / 60)
|
|
const secs = seconds % 60
|
|
return `${mins}:${secs.toString().padStart(2, '0')}`
|
|
}
|
|
|
|
// Cleanup timer on unmount
|
|
onUnmounted(() => {
|
|
if (timerInterval.value) {
|
|
clearInterval(timerInterval.value)
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.chore-detail-sheet {
|
|
@apply max-w-2xl mx-auto;
|
|
}
|
|
|
|
.sheet-container {
|
|
@apply flex flex-col h-full max-h-[90vh];
|
|
}
|
|
|
|
/* Header Styles */
|
|
.sheet-header {
|
|
@apply space-y-4 p-6 pb-4;
|
|
@apply border-b border-border-primary;
|
|
}
|
|
|
|
.header-content {
|
|
@apply flex items-start justify-between;
|
|
}
|
|
|
|
.header-main {
|
|
@apply flex items-start gap-4 flex-1;
|
|
}
|
|
|
|
.chore-icon {
|
|
@apply flex-shrink-0 w-12 h-12;
|
|
@apply bg-primary-100 dark:bg-primary-900;
|
|
@apply rounded-lg flex items-center justify-center;
|
|
}
|
|
|
|
.chore-icon .icon {
|
|
@apply w-6 h-6 text-primary-600 dark:text-primary-400;
|
|
}
|
|
|
|
.header-text {
|
|
@apply space-y-2;
|
|
}
|
|
|
|
.chore-title {
|
|
@apply text-xl font-semibold text-text-primary;
|
|
@apply leading-tight;
|
|
}
|
|
|
|
.chore-meta {
|
|
@apply flex items-center gap-3;
|
|
}
|
|
|
|
.chore-type {
|
|
@apply text-sm text-text-secondary;
|
|
}
|
|
|
|
.chore-status {
|
|
@apply text-xs font-medium px-2 py-1 rounded-md;
|
|
}
|
|
|
|
.chore-status.status-completed {
|
|
@apply bg-success-100 text-success-700 dark:bg-success-900/50 dark:text-success-300;
|
|
}
|
|
|
|
.chore-status.status-overdue {
|
|
@apply bg-error-100 text-error-700 dark:bg-error-900/50 dark:text-error-300;
|
|
}
|
|
|
|
.chore-status.status-pending {
|
|
@apply bg-warning-100 text-warning-700 dark:bg-warning-900/50 dark:text-warning-300;
|
|
}
|
|
|
|
.header-actions {
|
|
@apply flex items-center gap-2;
|
|
}
|
|
|
|
.quick-actions {
|
|
@apply flex items-center gap-2 flex-wrap;
|
|
}
|
|
|
|
/* Content Styles */
|
|
.sheet-content {
|
|
@apply flex-1 overflow-y-auto p-6 space-y-4;
|
|
}
|
|
|
|
.info-section {
|
|
@apply space-y-4;
|
|
}
|
|
|
|
.section-header {
|
|
@apply flex items-center justify-between;
|
|
}
|
|
|
|
.section-title {
|
|
@apply text-lg font-semibold text-text-primary;
|
|
}
|
|
|
|
.section-badges {
|
|
@apply flex items-center gap-2;
|
|
}
|
|
|
|
.frequency-badge {
|
|
@apply text-xs font-medium px-2 py-1 rounded-md;
|
|
@apply bg-neutral-100 text-neutral-700 dark:bg-neutral-800 dark:text-neutral-300;
|
|
}
|
|
|
|
.due-date-badge {
|
|
@apply text-xs font-medium px-2 py-1 rounded-md;
|
|
}
|
|
|
|
.due-date-badge.overdue {
|
|
@apply bg-error-100 text-error-700 dark:bg-error-900/50 dark:text-error-300;
|
|
}
|
|
|
|
.due-date-badge.due-today {
|
|
@apply bg-warning-100 text-warning-700 dark:bg-warning-900/50 dark:text-warning-300;
|
|
}
|
|
|
|
.due-date-badge.due-tomorrow {
|
|
@apply bg-primary-100 text-primary-700 dark:bg-primary-900/50 dark:text-primary-300;
|
|
}
|
|
|
|
.overview-grid {
|
|
@apply grid grid-cols-1 md:grid-cols-2 gap-4;
|
|
}
|
|
|
|
.overview-item {
|
|
@apply flex items-center gap-3;
|
|
}
|
|
|
|
.item-icon {
|
|
@apply flex-shrink-0 w-8 h-8;
|
|
@apply bg-surface-elevated rounded-lg;
|
|
@apply flex items-center justify-center;
|
|
}
|
|
|
|
.item-icon svg {
|
|
@apply w-4 h-4 text-text-secondary;
|
|
}
|
|
|
|
.item-content {
|
|
@apply space-y-1;
|
|
}
|
|
|
|
.item-label {
|
|
@apply text-sm text-text-secondary;
|
|
}
|
|
|
|
.item-value {
|
|
@apply text-sm font-medium text-text-primary;
|
|
}
|
|
|
|
/* Expandable Sections */
|
|
.expandable-section {
|
|
@apply cursor-pointer;
|
|
}
|
|
|
|
.section-header-expandable {
|
|
@apply flex items-center justify-between py-2;
|
|
@apply transition-colors duration-micro hover:bg-surface-hover rounded-md;
|
|
}
|
|
|
|
.section-header-content {
|
|
@apply flex items-center gap-3;
|
|
}
|
|
|
|
.section-icon {
|
|
@apply w-5 h-5 text-text-secondary;
|
|
}
|
|
|
|
.expand-icon {
|
|
@apply w-5 h-5 text-text-secondary;
|
|
@apply transition-transform duration-micro;
|
|
}
|
|
|
|
.section-content {
|
|
@apply pt-4;
|
|
}
|
|
|
|
.completion-count,
|
|
.subtask-count {
|
|
@apply text-sm text-text-secondary;
|
|
}
|
|
|
|
/* Description Section */
|
|
.description-text {
|
|
@apply text-sm text-text-primary leading-relaxed;
|
|
@apply whitespace-pre-wrap;
|
|
}
|
|
|
|
/* History Section */
|
|
.assignments-list {
|
|
@apply space-y-3;
|
|
}
|
|
|
|
.assignment-item {
|
|
@apply flex items-start gap-3;
|
|
}
|
|
|
|
.assignment-status {
|
|
@apply flex-shrink-0;
|
|
}
|
|
|
|
.status-indicator {
|
|
@apply w-8 h-8 rounded-full flex items-center justify-center;
|
|
}
|
|
|
|
.status-indicator.completed {
|
|
@apply bg-success-100 text-success-600 dark:bg-success-900/50 dark:text-success-400;
|
|
}
|
|
|
|
.status-indicator:not(.completed) {
|
|
@apply bg-neutral-100 text-neutral-500 dark:bg-neutral-800 dark:text-neutral-400;
|
|
}
|
|
|
|
.assignment-details {
|
|
@apply space-y-1;
|
|
}
|
|
|
|
.assignment-user {
|
|
@apply text-sm font-medium text-text-primary;
|
|
}
|
|
|
|
.assignment-dates {
|
|
@apply space-y-1;
|
|
}
|
|
|
|
.due-date,
|
|
.completed-date {
|
|
@apply text-xs text-text-secondary;
|
|
@apply block;
|
|
}
|
|
|
|
.show-more-button {
|
|
@apply mt-3;
|
|
}
|
|
|
|
.no-history {
|
|
@apply text-center py-8 space-y-3;
|
|
}
|
|
|
|
.no-history-icon {
|
|
@apply w-12 h-12 text-text-secondary mx-auto;
|
|
}
|
|
|
|
/* Subtasks Section */
|
|
.subtasks-list {
|
|
@apply space-y-3;
|
|
}
|
|
|
|
.subtask-item {
|
|
@apply flex items-center gap-3;
|
|
}
|
|
|
|
.subtask-checkbox {
|
|
@apply flex-shrink-0;
|
|
}
|
|
|
|
.completed-icon {
|
|
@apply w-5 h-5 text-success-600 dark:text-success-400;
|
|
}
|
|
|
|
.uncompleted-circle {
|
|
@apply w-5 h-5 rounded-full border-2 border-neutral-300 dark:border-neutral-600;
|
|
}
|
|
|
|
.subtask-name {
|
|
@apply text-sm text-text-primary;
|
|
}
|
|
|
|
.subtask-name.completed {
|
|
@apply line-through text-text-secondary;
|
|
}
|
|
|
|
/* Scheduling Section */
|
|
.scheduling-grid {
|
|
@apply grid grid-cols-1 md:grid-cols-2 gap-4;
|
|
}
|
|
|
|
.scheduling-item {
|
|
@apply space-y-1;
|
|
}
|
|
|
|
.scheduling-label {
|
|
@apply text-sm text-text-secondary;
|
|
@apply block;
|
|
}
|
|
|
|
.scheduling-value {
|
|
@apply text-sm font-medium text-text-primary;
|
|
@apply block;
|
|
}
|
|
|
|
/* Footer */
|
|
.sheet-footer {
|
|
@apply p-6 pt-4;
|
|
@apply border-t border-border-primary;
|
|
}
|
|
|
|
.footer-actions {
|
|
@apply flex items-center gap-3;
|
|
}
|
|
|
|
.spacer {
|
|
@apply flex-1;
|
|
}
|
|
|
|
/* Responsive */
|
|
@media (max-width: 640px) {
|
|
.header-main {
|
|
@apply flex-col gap-3;
|
|
}
|
|
|
|
.overview-grid {
|
|
@apply grid-cols-1;
|
|
}
|
|
|
|
.scheduling-grid {
|
|
@apply grid-cols-1;
|
|
}
|
|
|
|
.quick-actions {
|
|
@apply justify-center;
|
|
}
|
|
}
|
|
</style> |