Compare commits
2 Commits
7ebebe5d11
...
866d2e9fff
| Author | SHA1 | Date | |
|---|---|---|---|
|
866d2e9fff
|
|||
|
381012db98
|
@@ -2,6 +2,7 @@
|
|||||||
import { X, Sparkles, Wind, Bath, UtensilsCrossed, CookingPot, WashingMachine,
|
import { X, Sparkles, Wind, Bath, UtensilsCrossed, CookingPot, WashingMachine,
|
||||||
Flower2, Droplets, Leaf, ShoppingCart, Trash2, Shirt, Brush } from 'lucide-svelte';
|
Flower2, Droplets, Leaf, ShoppingCart, Trash2, Shirt, Brush } from 'lucide-svelte';
|
||||||
import ProfilePicture from '$lib/components/cospend/ProfilePicture.svelte';
|
import ProfilePicture from '$lib/components/cospend/ProfilePicture.svelte';
|
||||||
|
import Toggle from '$lib/components/Toggle.svelte';
|
||||||
|
|
||||||
const USERS = ['anna', 'alexander'];
|
const USERS = ['anna', 'alexander'];
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
/** @type {string[]} */
|
/** @type {string[]} */
|
||||||
let selectedTags = $state(task?.tags ? [...task.tags] : []);
|
let selectedTags = $state(task?.tags ? [...task.tags] : []);
|
||||||
let difficulty = $state(task?.difficulty || '');
|
let difficulty = $state(task?.difficulty || '');
|
||||||
|
let refreshMode = $state(task?.refreshMode || 'completion');
|
||||||
let isRecurring = $state(task?.isRecurring || false);
|
let isRecurring = $state(task?.isRecurring || false);
|
||||||
let frequencyType = $state(task?.frequency?.type || 'weekly');
|
let frequencyType = $state(task?.frequency?.type || 'weekly');
|
||||||
let customDays = $state(task?.frequency?.customDays || 7);
|
let customDays = $state(task?.frequency?.customDays || 7);
|
||||||
@@ -121,6 +123,7 @@
|
|||||||
tags: selectedTags,
|
tags: selectedTags,
|
||||||
difficulty: difficulty || undefined,
|
difficulty: difficulty || undefined,
|
||||||
isRecurring,
|
isRecurring,
|
||||||
|
refreshMode: isRecurring ? refreshMode : undefined,
|
||||||
frequency: isRecurring ? {
|
frequency: isRecurring ? {
|
||||||
type: frequencyType,
|
type: frequencyType,
|
||||||
customDays: frequencyType === 'custom' ? customDays : undefined
|
customDays: frequencyType === 'custom' ? customDays : undefined
|
||||||
@@ -297,10 +300,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field-row">
|
<div class="field-row">
|
||||||
<label class="checkbox-label">
|
<Toggle bind:checked={isRecurring} label="Wiederkehrend" accentColor="var(--nord10)" />
|
||||||
<input type="checkbox" bind:checked={isRecurring} />
|
|
||||||
Wiederkehrend
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if isRecurring}
|
{#if isRecurring}
|
||||||
@@ -321,6 +321,33 @@
|
|||||||
<input id="customDays" type="number" bind:value={customDays} min="1" max="365" />
|
<input id="customDays" type="number" bind:value={customDays} min="1" max="365" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Nächstes Fälligkeitsdatum berechnen ab</label>
|
||||||
|
<div class="refresh-mode-buttons">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="refresh-btn"
|
||||||
|
class:selected={refreshMode === 'completion'}
|
||||||
|
onclick={() => refreshMode = 'completion'}
|
||||||
|
>
|
||||||
|
Erledigung
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="refresh-btn"
|
||||||
|
class:selected={refreshMode === 'planned'}
|
||||||
|
onclick={() => refreshMode = 'planned'}
|
||||||
|
>
|
||||||
|
Geplantes Datum
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<span class="hint">
|
||||||
|
{refreshMode === 'completion'
|
||||||
|
? 'Intervall startet ab dem Zeitpunkt der Erledigung'
|
||||||
|
: 'Intervall startet ab dem geplanten Fälligkeitsdatum (holt auf bei Verspätung)'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
@@ -608,16 +635,39 @@
|
|||||||
color: var(--nord12);
|
color: var(--nord12);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Refresh mode buttons ── */
|
||||||
|
.refresh-mode-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.4rem;
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
}
|
||||||
|
.refresh-btn {
|
||||||
|
all: unset;
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0.4rem 0.5rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1.5px solid var(--color-border, #ddd);
|
||||||
|
transition: all 120ms;
|
||||||
|
color: var(--color-text-secondary, #777);
|
||||||
|
}
|
||||||
|
.refresh-btn:hover {
|
||||||
|
border-color: var(--nord10);
|
||||||
|
background: rgba(94, 129, 172, 0.06);
|
||||||
|
}
|
||||||
|
.refresh-btn.selected {
|
||||||
|
border-color: var(--nord10);
|
||||||
|
background: rgba(94, 129, 172, 0.1);
|
||||||
|
color: var(--nord10);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.field-row {
|
.field-row {
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
}
|
}
|
||||||
.checkbox-label {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-actions {
|
.form-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export interface ITask {
|
|||||||
assignees: string[];
|
assignees: string[];
|
||||||
tags: string[];
|
tags: string[];
|
||||||
difficulty?: 'low' | 'medium' | 'high';
|
difficulty?: 'low' | 'medium' | 'high';
|
||||||
|
refreshMode?: 'completion' | 'planned';
|
||||||
isRecurring: boolean;
|
isRecurring: boolean;
|
||||||
frequency?: {
|
frequency?: {
|
||||||
type: 'daily' | 'weekly' | 'biweekly' | 'monthly' | 'custom';
|
type: 'daily' | 'weekly' | 'biweekly' | 'monthly' | 'custom';
|
||||||
@@ -45,6 +46,11 @@ const TaskSchema = new mongoose.Schema(
|
|||||||
type: String,
|
type: String,
|
||||||
enum: ['low', 'medium', 'high']
|
enum: ['low', 'medium', 'high']
|
||||||
},
|
},
|
||||||
|
refreshMode: {
|
||||||
|
type: String,
|
||||||
|
enum: ['completion', 'planned'],
|
||||||
|
default: 'completion'
|
||||||
|
},
|
||||||
isRecurring: {
|
isRecurring: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: true,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
|
|||||||
if (!auth?.user?.nickname) throw error(401, 'Not logged in');
|
if (!auth?.user?.nickname) throw error(401, 'Not logged in');
|
||||||
|
|
||||||
const data = await request.json();
|
const data = await request.json();
|
||||||
const { title, description, assignees, tags, difficulty, isRecurring, frequency, nextDueDate } = data;
|
const { title, description, assignees, tags, difficulty, refreshMode, isRecurring, frequency, nextDueDate } = data;
|
||||||
|
|
||||||
if (!title?.trim()) throw error(400, 'Title is required');
|
if (!title?.trim()) throw error(400, 'Title is required');
|
||||||
if (!nextDueDate) throw error(400, 'Due date is required');
|
if (!nextDueDate) throw error(400, 'Due date is required');
|
||||||
@@ -35,6 +35,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
|
|||||||
assignees: assignees || [],
|
assignees: assignees || [],
|
||||||
tags: tags || [],
|
tags: tags || [],
|
||||||
difficulty: difficulty || undefined,
|
difficulty: difficulty || undefined,
|
||||||
|
refreshMode: isRecurring ? (refreshMode || 'completion') : undefined,
|
||||||
isRecurring: !!isRecurring,
|
isRecurring: !!isRecurring,
|
||||||
frequency: isRecurring ? frequency : undefined,
|
frequency: isRecurring ? frequency : undefined,
|
||||||
nextDueDate: new Date(nextDueDate),
|
nextDueDate: new Date(nextDueDate),
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export const PUT: RequestHandler = async ({ params, request, locals }) => {
|
|||||||
if (!auth?.user?.nickname) throw error(401, 'Not logged in');
|
if (!auth?.user?.nickname) throw error(401, 'Not logged in');
|
||||||
|
|
||||||
const data = await request.json();
|
const data = await request.json();
|
||||||
const { title, description, assignees, tags, difficulty, isRecurring, frequency, nextDueDate, active } = data;
|
const { title, description, assignees, tags, difficulty, refreshMode, isRecurring, frequency, nextDueDate, active } = data;
|
||||||
|
|
||||||
await dbConnect();
|
await dbConnect();
|
||||||
|
|
||||||
@@ -20,6 +20,7 @@ export const PUT: RequestHandler = async ({ params, request, locals }) => {
|
|||||||
if (assignees !== undefined) task.assignees = assignees;
|
if (assignees !== undefined) task.assignees = assignees;
|
||||||
if (tags !== undefined) task.tags = tags;
|
if (tags !== undefined) task.tags = tags;
|
||||||
if (difficulty !== undefined) task.difficulty = difficulty || undefined;
|
if (difficulty !== undefined) task.difficulty = difficulty || undefined;
|
||||||
|
if (refreshMode !== undefined) task.refreshMode = refreshMode || 'completion';
|
||||||
if (isRecurring !== undefined) {
|
if (isRecurring !== undefined) {
|
||||||
task.isRecurring = isRecurring;
|
task.isRecurring = isRecurring;
|
||||||
task.frequency = isRecurring ? frequency : undefined;
|
task.frequency = isRecurring ? frequency : undefined;
|
||||||
|
|||||||
@@ -55,8 +55,15 @@ export const POST: RequestHandler = async ({ params, request, locals }) => {
|
|||||||
task.lastCompletedBy = completedFor;
|
task.lastCompletedBy = completedFor;
|
||||||
|
|
||||||
if (task.isRecurring && task.frequency) {
|
if (task.isRecurring && task.frequency) {
|
||||||
// Reset from NOW (completion time), not from the original due date
|
// 'planned': calculate from the original due date (catches up if overdue)
|
||||||
task.nextDueDate = getNextDueDate(now, task.frequency.type, task.frequency.customDays);
|
// 'completion' (default): calculate from now
|
||||||
|
const baseDate = task.refreshMode === 'planned' ? task.nextDueDate : now;
|
||||||
|
let next = getNextDueDate(baseDate, task.frequency.type, task.frequency.customDays);
|
||||||
|
// If planned mode produced a date in the past, keep advancing until it's in the future
|
||||||
|
while (task.refreshMode === 'planned' && next <= now) {
|
||||||
|
next = getNextDueDate(next, task.frequency.type, task.frequency.customDays);
|
||||||
|
}
|
||||||
|
task.nextDueDate = next;
|
||||||
} else {
|
} else {
|
||||||
// One-off task: deactivate
|
// One-off task: deactivate
|
||||||
task.active = false;
|
task.active = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user