mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 00:01:10 +02:00
Merge pull request #8 from Memo-2023/feat/code-quality
🔧 Improve code quality: Unified ESLint config with optimizations
This commit is contained in:
commit
88c10e4ef7
267 changed files with 5006 additions and 3439 deletions
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"startTime": 1764606551673,
|
||||
"sessionId": "session-1764606551673",
|
||||
"lastActivity": 1764606551673,
|
||||
"startTime": 1764801237027,
|
||||
"sessionId": "session-1764801237027",
|
||||
"lastActivity": 1764801237027,
|
||||
"sessionDuration": 0,
|
||||
"totalTasks": 2,
|
||||
"successfulTasks": 2,
|
||||
"totalTasks": 1,
|
||||
"successfulTasks": 1,
|
||||
"failedTasks": 0,
|
||||
"totalAgents": 0,
|
||||
"activeAgents": 0,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,10 +1,10 @@
|
|||
[
|
||||
{
|
||||
"id": "cmd-swarm-1764606576686",
|
||||
"id": "cmd-swarm-1764801237142",
|
||||
"type": "swarm",
|
||||
"success": true,
|
||||
"duration": 4.44541700000002,
|
||||
"timestamp": 1764606576691,
|
||||
"duration": 5.236916000000008,
|
||||
"timestamp": 1764801237147,
|
||||
"metadata": {}
|
||||
}
|
||||
]
|
||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
|
@ -384,6 +384,6 @@ jobs:
|
|||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.name,
|
||||
repo: context.repo.repo,
|
||||
body
|
||||
});
|
||||
|
|
|
|||
17
apps-archived/maerchenzauber/apps/web/eslint.config.js
Normal file
17
apps-archived/maerchenzauber/apps/web/eslint.config.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// @ts-check
|
||||
import {
|
||||
baseConfig,
|
||||
typescriptConfig,
|
||||
svelteConfig,
|
||||
prettierConfig,
|
||||
} from '@manacore/eslint-config';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', '.svelte-kit/**', 'node_modules/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...svelteConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
17
apps-archived/memoro/apps/web/eslint.config.js
Normal file
17
apps-archived/memoro/apps/web/eslint.config.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// @ts-check
|
||||
import {
|
||||
baseConfig,
|
||||
typescriptConfig,
|
||||
svelteConfig,
|
||||
prettierConfig,
|
||||
} from '@manacore/eslint-config';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', '.svelte-kit/**', 'node_modules/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...svelteConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
17
apps-archived/news/apps/web/eslint.config.js
Normal file
17
apps-archived/news/apps/web/eslint.config.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// @ts-check
|
||||
import {
|
||||
baseConfig,
|
||||
typescriptConfig,
|
||||
svelteConfig,
|
||||
prettierConfig,
|
||||
} from '@manacore/eslint-config';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', '.svelte-kit/**', 'node_modules/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...svelteConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
17
apps-archived/nutriphi/apps/web/eslint.config.js
Normal file
17
apps-archived/nutriphi/apps/web/eslint.config.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// @ts-check
|
||||
import {
|
||||
baseConfig,
|
||||
typescriptConfig,
|
||||
svelteConfig,
|
||||
prettierConfig,
|
||||
} from '@manacore/eslint-config';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', '.svelte-kit/**', 'node_modules/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...svelteConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
17
apps-archived/wisekeep/apps/web/eslint.config.js
Normal file
17
apps-archived/wisekeep/apps/web/eslint.config.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// @ts-check
|
||||
import {
|
||||
baseConfig,
|
||||
typescriptConfig,
|
||||
svelteConfig,
|
||||
prettierConfig,
|
||||
} from '@manacore/eslint-config';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', '.svelte-kit/**', 'node_modules/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...svelteConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
17
apps/calendar/apps/backend/eslint.config.mjs
Normal file
17
apps/calendar/apps/backend/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// @ts-check
|
||||
import {
|
||||
baseConfig,
|
||||
typescriptConfig,
|
||||
nestjsConfig,
|
||||
prettierConfig,
|
||||
} from '@manacore/eslint-config';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', 'node_modules/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...nestjsConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
|
|
@ -10,7 +10,6 @@
|
|||
"preview": "astro preview",
|
||||
"astro": "astro",
|
||||
"type-check": "astro check",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"format": "prettier --write .",
|
||||
"clean": "rm -rf dist .astro node_modules"
|
||||
},
|
||||
|
|
|
|||
17
apps/calendar/apps/web/eslint.config.js
Normal file
17
apps/calendar/apps/web/eslint.config.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// @ts-check
|
||||
import {
|
||||
baseConfig,
|
||||
typescriptConfig,
|
||||
svelteConfig,
|
||||
prettierConfig,
|
||||
} from '@manacore/eslint-config';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', '.svelte-kit/**', 'node_modules/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...svelteConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
|
|
@ -19,7 +19,15 @@
|
|||
};
|
||||
|
||||
// Views to show in selector
|
||||
const visibleViews: CalendarViewType[] = ['day', '5day', 'week', '10day', '14day', 'month', 'year'];
|
||||
const visibleViews: CalendarViewType[] = [
|
||||
'day',
|
||||
'5day',
|
||||
'week',
|
||||
'10day',
|
||||
'14day',
|
||||
'month',
|
||||
'year',
|
||||
];
|
||||
|
||||
// Format title based on view type
|
||||
let title = $derived.by(() => {
|
||||
|
|
@ -30,9 +38,17 @@
|
|||
// Helper to format date range
|
||||
const formatRange = () => {
|
||||
if (rangeStart.getMonth() === rangeEnd.getMonth()) {
|
||||
return format(rangeStart, 'd.', { locale: de }) + ' - ' + format(rangeEnd, 'd. MMMM yyyy', { locale: de });
|
||||
return (
|
||||
format(rangeStart, 'd.', { locale: de }) +
|
||||
' - ' +
|
||||
format(rangeEnd, 'd. MMMM yyyy', { locale: de })
|
||||
);
|
||||
}
|
||||
return format(rangeStart, 'd. MMM', { locale: de }) + ' - ' + format(rangeEnd, 'd. MMM yyyy', { locale: de });
|
||||
return (
|
||||
format(rangeStart, 'd. MMM', { locale: de }) +
|
||||
' - ' +
|
||||
format(rangeEnd, 'd. MMM yyyy', { locale: de })
|
||||
);
|
||||
};
|
||||
|
||||
switch (viewStore.viewType) {
|
||||
|
|
@ -61,14 +77,17 @@
|
|||
|
||||
<header class="calendar-header" class:nav-collapsed={$isNavCollapsed}>
|
||||
<div class="header-left">
|
||||
<button class="today-btn" onclick={() => viewStore.goToToday()}>
|
||||
Heute
|
||||
</button>
|
||||
<button class="today-btn" onclick={() => viewStore.goToToday()}> Heute </button>
|
||||
|
||||
<div class="nav-buttons">
|
||||
<button class="nav-btn" onclick={() => viewStore.goToPrevious()} aria-label="Zurück">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 19l-7-7 7-7"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="nav-btn" onclick={() => viewStore.goToNext()} aria-label="Weiter">
|
||||
|
|
|
|||
|
|
@ -34,7 +34,9 @@
|
|||
);
|
||||
|
||||
// Calculate visible hours range for positioning
|
||||
let firstVisibleHour = $derived(settingsStore.filterHoursEnabled ? settingsStore.dayStartHour : 0);
|
||||
let firstVisibleHour = $derived(
|
||||
settingsStore.filterHoursEnabled ? settingsStore.dayStartHour : 0
|
||||
);
|
||||
let lastVisibleHour = $derived(settingsStore.filterHoursEnabled ? settingsStore.dayEndHour : 24);
|
||||
let totalVisibleHours = $derived(lastVisibleHour - firstVisibleHour);
|
||||
|
||||
|
|
@ -74,12 +76,10 @@
|
|||
|
||||
// Split all-day events by display mode
|
||||
let headerAllDayEvents = $derived(
|
||||
allDayEvents.filter(e => getEventDisplayMode(e) === 'header')
|
||||
allDayEvents.filter((e) => getEventDisplayMode(e) === 'header')
|
||||
);
|
||||
|
||||
let blockAllDayEvents = $derived(
|
||||
allDayEvents.filter(e => getEventDisplayMode(e) === 'block')
|
||||
);
|
||||
let blockAllDayEvents = $derived(allDayEvents.filter((e) => getEventDisplayMode(e) === 'block'));
|
||||
|
||||
// ============================================================================
|
||||
// Drag & Drop State
|
||||
|
|
@ -155,7 +155,10 @@
|
|||
hasMoved = true;
|
||||
const mouseMinutes = getMinutesFromY(e.clientY);
|
||||
const newStartMinutes = snapToGrid(mouseMinutes - dragOffsetMinutes);
|
||||
const clampedMinutes = Math.max(firstVisibleHour * 60, Math.min(newStartMinutes, lastVisibleHour * 60 - 15));
|
||||
const clampedMinutes = Math.max(
|
||||
firstVisibleHour * 60,
|
||||
Math.min(newStartMinutes, lastVisibleHour * 60 - 15)
|
||||
);
|
||||
|
||||
dragPreviewTop = minutesToPercent(clampedMinutes);
|
||||
}
|
||||
|
|
@ -168,10 +171,19 @@
|
|||
|
||||
const mouseMinutes = getMinutesFromY(e.clientY);
|
||||
const newStartMinutes = snapToGrid(mouseMinutes - dragOffsetMinutes);
|
||||
const clampedMinutes = Math.max(firstVisibleHour * 60, Math.min(newStartMinutes, lastVisibleHour * 60 - 30));
|
||||
const clampedMinutes = Math.max(
|
||||
firstVisibleHour * 60,
|
||||
Math.min(newStartMinutes, lastVisibleHour * 60 - 30)
|
||||
);
|
||||
|
||||
const start = typeof draggedEvent.startTime === 'string' ? parseISO(draggedEvent.startTime) : draggedEvent.startTime;
|
||||
const end = typeof draggedEvent.endTime === 'string' ? parseISO(draggedEvent.endTime) : draggedEvent.endTime;
|
||||
const start =
|
||||
typeof draggedEvent.startTime === 'string'
|
||||
? parseISO(draggedEvent.startTime)
|
||||
: draggedEvent.startTime;
|
||||
const end =
|
||||
typeof draggedEvent.endTime === 'string'
|
||||
? parseISO(draggedEvent.endTime)
|
||||
: draggedEvent.endTime;
|
||||
const duration = differenceInMinutes(end, start);
|
||||
|
||||
// Create new start time on same day
|
||||
|
|
@ -262,12 +274,18 @@
|
|||
let newEnd = new Date(resizeOriginalEnd);
|
||||
|
||||
if (resizeEdge === 'top') {
|
||||
const newStartMinutes = Math.max(firstVisibleHour * 60, Math.min(snappedMinutes, origEndMinutes - SNAP_MINUTES));
|
||||
const newStartMinutes = Math.max(
|
||||
firstVisibleHour * 60,
|
||||
Math.min(snappedMinutes, origEndMinutes - SNAP_MINUTES)
|
||||
);
|
||||
newStart = setHours(new Date(viewStore.currentDate), Math.floor(newStartMinutes / 60));
|
||||
newStart = setMinutes(newStart, newStartMinutes % 60);
|
||||
newStart.setSeconds(0, 0);
|
||||
} else {
|
||||
const newEndMinutes = Math.min(lastVisibleHour * 60, Math.max(snappedMinutes, origStartMinutes + SNAP_MINUTES));
|
||||
const newEndMinutes = Math.min(
|
||||
lastVisibleHour * 60,
|
||||
Math.max(snappedMinutes, origStartMinutes + SNAP_MINUTES)
|
||||
);
|
||||
newEnd = setHours(new Date(viewStore.currentDate), Math.floor(newEndMinutes / 60));
|
||||
newEnd = setMinutes(newEnd, newEndMinutes % 60);
|
||||
newEnd.setSeconds(0, 0);
|
||||
|
|
@ -343,7 +361,9 @@
|
|||
if (isDragging || isResizing || hasMoved) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setTimeout(() => { hasMoved = false; }, 100);
|
||||
setTimeout(() => {
|
||||
hasMoved = false;
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
goto(`/?event=${event.id}`);
|
||||
|
|
@ -395,11 +415,7 @@
|
|||
{/each}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="day-column"
|
||||
class:today={isToday(viewStore.currentDate)}
|
||||
bind:this={dayColumnRef}
|
||||
>
|
||||
<div class="day-column" class:today={isToday(viewStore.currentDate)} bind:this={dayColumnRef}>
|
||||
{#each hours as hour}
|
||||
<button
|
||||
class="hour-slot"
|
||||
|
|
@ -430,7 +446,11 @@
|
|||
class:resizing={isBeingResized}
|
||||
class:draft={isDraft}
|
||||
data-event-id={event.id}
|
||||
style={isBeingDragged ? `top: ${dragPreviewTop}%; height: ${dragPreviewHeight}%; background-color: ${calendarsStore.getColor(event.calendarId)};` : isBeingResized ? `top: ${resizePreviewTop}%; height: ${resizePreviewHeight}%; background-color: ${calendarsStore.getColor(event.calendarId)};` : getEventStyle(event)}
|
||||
style={isBeingDragged
|
||||
? `top: ${dragPreviewTop}%; height: ${dragPreviewHeight}%; background-color: ${calendarsStore.getColor(event.calendarId)};`
|
||||
: isBeingResized
|
||||
? `top: ${resizePreviewTop}%; height: ${resizePreviewHeight}%; background-color: ${calendarsStore.getColor(event.calendarId)};`
|
||||
: getEventStyle(event)}
|
||||
onpointerdown={(e) => startDrag(event, e)}
|
||||
onclick={(e) => !isDraft && handleEventClick(event, e)}
|
||||
role="button"
|
||||
|
|
@ -599,7 +619,9 @@
|
|||
overflow: hidden;
|
||||
touch-action: none;
|
||||
user-select: none;
|
||||
transition: box-shadow 150ms ease, opacity 150ms ease;
|
||||
transition:
|
||||
box-shadow 150ms ease,
|
||||
opacity 150ms ease;
|
||||
}
|
||||
|
||||
.event-card:hover {
|
||||
|
|
@ -629,7 +651,8 @@
|
|||
}
|
||||
|
||||
@keyframes pulse-outline {
|
||||
0%, 100% {
|
||||
0%,
|
||||
100% {
|
||||
outline-color: hsl(var(--color-primary));
|
||||
}
|
||||
50% {
|
||||
|
|
|
|||
|
|
@ -45,16 +45,18 @@
|
|||
|
||||
// Filter weekends if option is active
|
||||
let calendarDays = $derived(
|
||||
settingsStore.showOnlyWeekdays ? allCalendarDays.filter((day) => !isWeekend(day)) : allCalendarDays
|
||||
settingsStore.showOnlyWeekdays
|
||||
? allCalendarDays.filter((day) => !isWeekend(day))
|
||||
: allCalendarDays
|
||||
);
|
||||
|
||||
// Week day headers - depends on week start setting
|
||||
const weekDaysFromMonday = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'];
|
||||
const weekDaysFromSunday = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'];
|
||||
let allWeekDays = $derived(settingsStore.weekStartsOn === 1 ? weekDaysFromMonday : weekDaysFromSunday);
|
||||
let weekDays = $derived(
|
||||
settingsStore.showOnlyWeekdays ? allWeekDays.slice(0, 5) : allWeekDays
|
||||
let allWeekDays = $derived(
|
||||
settingsStore.weekStartsOn === 1 ? weekDaysFromMonday : weekDaysFromSunday
|
||||
);
|
||||
let weekDays = $derived(settingsStore.showOnlyWeekdays ? allWeekDays.slice(0, 5) : allWeekDays);
|
||||
|
||||
// Number of columns for grid
|
||||
let columnCount = $derived(settingsStore.showOnlyWeekdays ? 5 : 7);
|
||||
|
|
@ -94,7 +96,7 @@
|
|||
return {
|
||||
destroy() {
|
||||
setDayCellRef(day, null);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -141,8 +143,14 @@
|
|||
const targetDay = getDayFromPoint(e.clientX, e.clientY);
|
||||
|
||||
if (targetDay) {
|
||||
const start = typeof draggedEvent.startTime === 'string' ? new Date(draggedEvent.startTime) : draggedEvent.startTime;
|
||||
const end = typeof draggedEvent.endTime === 'string' ? new Date(draggedEvent.endTime) : draggedEvent.endTime;
|
||||
const start =
|
||||
typeof draggedEvent.startTime === 'string'
|
||||
? new Date(draggedEvent.startTime)
|
||||
: draggedEvent.startTime;
|
||||
const end =
|
||||
typeof draggedEvent.endTime === 'string'
|
||||
? new Date(draggedEvent.endTime)
|
||||
: draggedEvent.endTime;
|
||||
const duration = differenceInMinutes(end, start);
|
||||
|
||||
// Keep the same time, change the date
|
||||
|
|
@ -280,14 +288,13 @@
|
|||
: event.startTime,
|
||||
'HH:mm'
|
||||
)}-{format(
|
||||
typeof event.endTime === 'string'
|
||||
? new Date(event.endTime)
|
||||
: event.endTime,
|
||||
typeof event.endTime === 'string' ? new Date(event.endTime) : event.endTime,
|
||||
'HH:mm'
|
||||
)}</span
|
||||
>
|
||||
{/if}
|
||||
<span class="event-title">{event.title || (isDraft ? '(Neuer Termin)' : '')}</span>
|
||||
<span class="event-title">{event.title || (isDraft ? '(Neuer Termin)' : '')}</span
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
|
|
@ -308,7 +315,6 @@
|
|||
.month-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
}
|
||||
|
||||
.weekday-headers {
|
||||
|
|
@ -408,7 +414,10 @@
|
|||
text-overflow: ellipsis;
|
||||
touch-action: none;
|
||||
user-select: none;
|
||||
transition: transform 150ms ease, box-shadow 150ms ease, opacity 150ms ease;
|
||||
transition:
|
||||
transform 150ms ease,
|
||||
box-shadow 150ms ease,
|
||||
opacity 150ms ease;
|
||||
}
|
||||
|
||||
.event-pill:hover {
|
||||
|
|
@ -429,7 +438,8 @@
|
|||
}
|
||||
|
||||
@keyframes pulse-outline {
|
||||
0%, 100% {
|
||||
0%,
|
||||
100% {
|
||||
outline-color: hsl(var(--color-primary));
|
||||
}
|
||||
50% {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,9 @@
|
|||
|
||||
// Get date-fns locale based on current app locale
|
||||
const dateLocales = { de, en: enUS, fr, es, it };
|
||||
let currentDateLocale = $derived(dateLocales[$locale?.substring(0, 2) as keyof typeof dateLocales] || de);
|
||||
let currentDateLocale = $derived(
|
||||
dateLocales[$locale?.substring(0, 2) as keyof typeof dateLocales] || de
|
||||
);
|
||||
|
||||
// Generate days based on view range, optionally filtering weekends
|
||||
let allDays = $derived(
|
||||
|
|
@ -55,7 +57,9 @@
|
|||
);
|
||||
|
||||
// Calculate visible hours range for positioning
|
||||
let firstVisibleHour = $derived(settingsStore.filterHoursEnabled ? settingsStore.dayStartHour : 0);
|
||||
let firstVisibleHour = $derived(
|
||||
settingsStore.filterHoursEnabled ? settingsStore.dayStartHour : 0
|
||||
);
|
||||
let lastVisibleHour = $derived(settingsStore.filterHoursEnabled ? settingsStore.dayEndHour : 24);
|
||||
let totalVisibleHours = $derived(lastVisibleHour - firstVisibleHour);
|
||||
|
||||
|
|
@ -125,16 +129,16 @@
|
|||
|
||||
// Split all-day events by display mode
|
||||
function getHeaderAllDayEventsForDay(day: Date) {
|
||||
return getAllDayEventsForDay(day).filter(e => getEventDisplayMode(e) === 'header');
|
||||
return getAllDayEventsForDay(day).filter((e) => getEventDisplayMode(e) === 'header');
|
||||
}
|
||||
|
||||
function getBlockAllDayEventsForDay(day: Date) {
|
||||
return getAllDayEventsForDay(day).filter(e => getEventDisplayMode(e) === 'block');
|
||||
return getAllDayEventsForDay(day).filter((e) => getEventDisplayMode(e) === 'block');
|
||||
}
|
||||
|
||||
// Check if there are any all-day events to show in header
|
||||
let hasAnyHeaderAllDayEvents = $derived(
|
||||
days.some(day => getHeaderAllDayEventsForDay(day).length > 0)
|
||||
days.some((day) => getHeaderAllDayEventsForDay(day).length > 0)
|
||||
);
|
||||
|
||||
function getEventStyle(event: any) {
|
||||
|
|
@ -162,7 +166,9 @@
|
|||
if (isDragging || isResizing || hasMoved) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setTimeout(() => { hasMoved = false; }, 100);
|
||||
setTimeout(() => {
|
||||
hasMoved = false;
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
goto(`/?event=${event.id}`);
|
||||
|
|
@ -248,7 +254,10 @@
|
|||
const newMinutes = getMinutesFromY(e.clientY) - dragOffsetMinutes;
|
||||
|
||||
// Clamp to valid range (firstVisibleHour to lastVisibleHour)
|
||||
const clampedMinutes = Math.max(firstVisibleHour * 60, Math.min(lastVisibleHour * 60 - 15, newMinutes));
|
||||
const clampedMinutes = Math.max(
|
||||
firstVisibleHour * 60,
|
||||
Math.min(lastVisibleHour * 60 - 15, newMinutes)
|
||||
);
|
||||
|
||||
// Update preview
|
||||
dragPreviewTop = minutesToPercent(clampedMinutes);
|
||||
|
|
@ -268,8 +277,14 @@
|
|||
return;
|
||||
}
|
||||
|
||||
const start = typeof draggedEvent.startTime === 'string' ? parseISO(draggedEvent.startTime) : draggedEvent.startTime;
|
||||
const end = typeof draggedEvent.endTime === 'string' ? parseISO(draggedEvent.endTime) : draggedEvent.endTime;
|
||||
const start =
|
||||
typeof draggedEvent.startTime === 'string'
|
||||
? parseISO(draggedEvent.startTime)
|
||||
: draggedEvent.startTime;
|
||||
const end =
|
||||
typeof draggedEvent.endTime === 'string'
|
||||
? parseISO(draggedEvent.endTime)
|
||||
: draggedEvent.endTime;
|
||||
const duration = differenceInMinutes(end, start);
|
||||
|
||||
// Calculate new start time
|
||||
|
|
@ -336,17 +351,24 @@
|
|||
|
||||
hasMoved = true;
|
||||
const currentMinutes = getMinutesFromY(e.clientY);
|
||||
const originalStartMinutes = resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes();
|
||||
const originalStartMinutes =
|
||||
resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes();
|
||||
const originalEndMinutes = resizeOriginalEnd.getHours() * 60 + resizeOriginalEnd.getMinutes();
|
||||
|
||||
if (resizeEdge === 'bottom') {
|
||||
// Resize from bottom - change end time
|
||||
const newEndMinutes = Math.max(originalStartMinutes + 15, Math.min(lastVisibleHour * 60, currentMinutes));
|
||||
const newEndMinutes = Math.max(
|
||||
originalStartMinutes + 15,
|
||||
Math.min(lastVisibleHour * 60, currentMinutes)
|
||||
);
|
||||
const newDuration = newEndMinutes - originalStartMinutes;
|
||||
resizePreviewHeight = (newDuration / (totalVisibleHours * 60)) * 100;
|
||||
} else {
|
||||
// Resize from top - change start time
|
||||
const newStartMinutes = Math.max(firstVisibleHour * 60, Math.min(originalEndMinutes - 15, currentMinutes));
|
||||
const newStartMinutes = Math.max(
|
||||
firstVisibleHour * 60,
|
||||
Math.min(originalEndMinutes - 15, currentMinutes)
|
||||
);
|
||||
const newDuration = originalEndMinutes - newStartMinutes;
|
||||
resizePreviewTop = minutesToPercent(newStartMinutes);
|
||||
resizePreviewHeight = (newDuration / (totalVisibleHours * 60)) * 100;
|
||||
|
|
@ -367,20 +389,27 @@
|
|||
}
|
||||
|
||||
const currentMinutes = getMinutesFromY(e.clientY);
|
||||
const originalStartMinutes = resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes();
|
||||
const originalStartMinutes =
|
||||
resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes();
|
||||
const originalEndMinutes = resizeOriginalEnd.getHours() * 60 + resizeOriginalEnd.getMinutes();
|
||||
|
||||
let newStart = resizeOriginalStart;
|
||||
let newEnd = resizeOriginalEnd;
|
||||
|
||||
if (resizeEdge === 'bottom') {
|
||||
const newEndMinutes = Math.max(originalStartMinutes + 15, Math.min(lastVisibleHour * 60, currentMinutes));
|
||||
const newEndMinutes = Math.max(
|
||||
originalStartMinutes + 15,
|
||||
Math.min(lastVisibleHour * 60, currentMinutes)
|
||||
);
|
||||
const newHours = Math.floor(newEndMinutes / 60);
|
||||
const newMins = newEndMinutes % 60;
|
||||
newEnd = setHours(new Date(resizeOriginalEnd), newHours);
|
||||
newEnd = setMinutes(newEnd, newMins);
|
||||
} else {
|
||||
const newStartMinutes = Math.max(firstVisibleHour * 60, Math.min(originalEndMinutes - 15, currentMinutes));
|
||||
const newStartMinutes = Math.max(
|
||||
firstVisibleHour * 60,
|
||||
Math.min(originalEndMinutes - 15, currentMinutes)
|
||||
);
|
||||
const newHours = Math.floor(newStartMinutes / 60);
|
||||
const newMins = newStartMinutes % 60;
|
||||
newStart = setHours(new Date(resizeOriginalStart), newHours);
|
||||
|
|
@ -434,7 +463,11 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<div class="multi-day-view" class:compact={columnClass === 'compact'} class:very-compact={columnClass === 'very-compact'}>
|
||||
<div
|
||||
class="multi-day-view"
|
||||
class:compact={columnClass === 'compact'}
|
||||
class:very-compact={columnClass === 'very-compact'}
|
||||
>
|
||||
<!-- All-day events row (only shown when there are header-mode all-day events) -->
|
||||
{#if hasAnyHeaderAllDayEvents}
|
||||
<div class="all-day-row">
|
||||
|
|
@ -461,7 +494,9 @@
|
|||
<div class="time-gutter"></div>
|
||||
{#each days as day}
|
||||
<div class="day-header" class:today={isToday(day)}>
|
||||
<span class="day-name">{format(day, columnClass === 'very-compact' ? 'EEEEE' : 'EEE', { locale: de })}</span>
|
||||
<span class="day-name"
|
||||
>{format(day, columnClass === 'very-compact' ? 'EEEEE' : 'EEE', { locale: de })}</span
|
||||
>
|
||||
<span class="day-number" class:today={isToday(day)}>{format(day, 'd')}</span>
|
||||
</div>
|
||||
{/each}
|
||||
|
|
@ -553,10 +588,12 @@
|
|||
{/each}
|
||||
|
||||
<!-- Drag preview ghost (for cross-day dragging) -->
|
||||
{#if isDragging && draggedEvent && dragTargetDay && isSameDay(day, dragTargetDay) && !getEventsForDay(day).some(e => e.id === draggedEvent.id)}
|
||||
{#if isDragging && draggedEvent && dragTargetDay && isSameDay(day, dragTargetDay) && !getEventsForDay(day).some((e) => e.id === draggedEvent.id)}
|
||||
<div
|
||||
class="event-card drag-ghost"
|
||||
style="top: {dragPreviewTop}%; height: {dragPreviewHeight}%; background-color: {calendarsStore.getColor(draggedEvent.calendarId)};"
|
||||
style="top: {dragPreviewTop}%; height: {dragPreviewHeight}%; background-color: {calendarsStore.getColor(
|
||||
draggedEvent.calendarId
|
||||
)};"
|
||||
>
|
||||
{#if columnClass !== 'very-compact'}
|
||||
<span class="event-time">{formatEventTime(draggedEvent.startTime)}</span>
|
||||
|
|
@ -795,7 +832,9 @@
|
|||
padding: 2px 4px;
|
||||
border-radius: var(--radius-sm);
|
||||
overflow: hidden;
|
||||
transition: box-shadow 0.15s ease, opacity 0.15s ease;
|
||||
transition:
|
||||
box-shadow 0.15s ease,
|
||||
opacity 0.15s ease;
|
||||
touch-action: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
|
@ -826,7 +865,8 @@
|
|||
}
|
||||
|
||||
@keyframes pulse-outline {
|
||||
0%, 100% {
|
||||
0%,
|
||||
100% {
|
||||
outline-color: hsl(var(--color-primary));
|
||||
}
|
||||
50% {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,9 @@
|
|||
|
||||
// Get date-fns locale based on current app locale
|
||||
const dateLocales = { de, en: enUS, fr, es, it };
|
||||
let currentDateLocale = $derived(dateLocales[$locale?.substring(0, 2) as keyof typeof dateLocales] || de);
|
||||
let currentDateLocale = $derived(
|
||||
dateLocales[$locale?.substring(0, 2) as keyof typeof dateLocales] || de
|
||||
);
|
||||
|
||||
// Generate days of the week, optionally filtering weekends
|
||||
let allDays = $derived(
|
||||
|
|
@ -61,7 +63,9 @@
|
|||
);
|
||||
|
||||
// Calculate visible hours range for positioning
|
||||
let firstVisibleHour = $derived(settingsStore.filterHoursEnabled ? settingsStore.dayStartHour : 0);
|
||||
let firstVisibleHour = $derived(
|
||||
settingsStore.filterHoursEnabled ? settingsStore.dayStartHour : 0
|
||||
);
|
||||
let lastVisibleHour = $derived(settingsStore.filterHoursEnabled ? settingsStore.dayEndHour : 24);
|
||||
let totalVisibleHours = $derived(lastVisibleHour - firstVisibleHour);
|
||||
|
||||
|
|
@ -124,16 +128,16 @@
|
|||
|
||||
// Split all-day events by display mode
|
||||
function getHeaderAllDayEventsForDay(day: Date) {
|
||||
return getAllDayEventsForDay(day).filter(e => getEventDisplayMode(e) === 'header');
|
||||
return getAllDayEventsForDay(day).filter((e) => getEventDisplayMode(e) === 'header');
|
||||
}
|
||||
|
||||
function getBlockAllDayEventsForDay(day: Date) {
|
||||
return getAllDayEventsForDay(day).filter(e => getEventDisplayMode(e) === 'block');
|
||||
return getAllDayEventsForDay(day).filter((e) => getEventDisplayMode(e) === 'block');
|
||||
}
|
||||
|
||||
// Check if there are any all-day events to show in header
|
||||
let hasAnyHeaderAllDayEvents = $derived(
|
||||
days.some(day => getHeaderAllDayEventsForDay(day).length > 0)
|
||||
days.some((day) => getHeaderAllDayEventsForDay(day).length > 0)
|
||||
);
|
||||
|
||||
function getEventStyle(event: any) {
|
||||
|
|
@ -162,7 +166,9 @@
|
|||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// Reset hasMoved after a short delay to allow for the next clean click
|
||||
setTimeout(() => { hasMoved = false; }, 100);
|
||||
setTimeout(() => {
|
||||
hasMoved = false;
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
goto(`/?event=${event.id}`);
|
||||
|
|
@ -248,7 +254,10 @@
|
|||
const newMinutes = getMinutesFromY(e.clientY) - dragOffsetMinutes;
|
||||
|
||||
// Clamp to valid range (firstVisibleHour to lastVisibleHour)
|
||||
const clampedMinutes = Math.max(firstVisibleHour * 60, Math.min(lastVisibleHour * 60 - 15, newMinutes));
|
||||
const clampedMinutes = Math.max(
|
||||
firstVisibleHour * 60,
|
||||
Math.min(lastVisibleHour * 60 - 15, newMinutes)
|
||||
);
|
||||
|
||||
// Update preview
|
||||
dragPreviewTop = minutesToPercent(clampedMinutes);
|
||||
|
|
@ -268,8 +277,14 @@
|
|||
return;
|
||||
}
|
||||
|
||||
const start = typeof draggedEvent.startTime === 'string' ? parseISO(draggedEvent.startTime) : draggedEvent.startTime;
|
||||
const end = typeof draggedEvent.endTime === 'string' ? parseISO(draggedEvent.endTime) : draggedEvent.endTime;
|
||||
const start =
|
||||
typeof draggedEvent.startTime === 'string'
|
||||
? parseISO(draggedEvent.startTime)
|
||||
: draggedEvent.startTime;
|
||||
const end =
|
||||
typeof draggedEvent.endTime === 'string'
|
||||
? parseISO(draggedEvent.endTime)
|
||||
: draggedEvent.endTime;
|
||||
const duration = differenceInMinutes(end, start);
|
||||
|
||||
// Calculate new start time
|
||||
|
|
@ -336,17 +351,24 @@
|
|||
|
||||
hasMoved = true;
|
||||
const currentMinutes = getMinutesFromY(e.clientY);
|
||||
const originalStartMinutes = resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes();
|
||||
const originalStartMinutes =
|
||||
resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes();
|
||||
const originalEndMinutes = resizeOriginalEnd.getHours() * 60 + resizeOriginalEnd.getMinutes();
|
||||
|
||||
if (resizeEdge === 'bottom') {
|
||||
// Resize from bottom - change end time
|
||||
const newEndMinutes = Math.max(originalStartMinutes + 15, Math.min(lastVisibleHour * 60, currentMinutes));
|
||||
const newEndMinutes = Math.max(
|
||||
originalStartMinutes + 15,
|
||||
Math.min(lastVisibleHour * 60, currentMinutes)
|
||||
);
|
||||
const newDuration = newEndMinutes - originalStartMinutes;
|
||||
resizePreviewHeight = (newDuration / (totalVisibleHours * 60)) * 100;
|
||||
} else {
|
||||
// Resize from top - change start time
|
||||
const newStartMinutes = Math.max(firstVisibleHour * 60, Math.min(originalEndMinutes - 15, currentMinutes));
|
||||
const newStartMinutes = Math.max(
|
||||
firstVisibleHour * 60,
|
||||
Math.min(originalEndMinutes - 15, currentMinutes)
|
||||
);
|
||||
const newDuration = originalEndMinutes - newStartMinutes;
|
||||
resizePreviewTop = minutesToPercent(newStartMinutes);
|
||||
resizePreviewHeight = (newDuration / (totalVisibleHours * 60)) * 100;
|
||||
|
|
@ -367,20 +389,27 @@
|
|||
}
|
||||
|
||||
const currentMinutes = getMinutesFromY(e.clientY);
|
||||
const originalStartMinutes = resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes();
|
||||
const originalStartMinutes =
|
||||
resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes();
|
||||
const originalEndMinutes = resizeOriginalEnd.getHours() * 60 + resizeOriginalEnd.getMinutes();
|
||||
|
||||
let newStart = resizeOriginalStart;
|
||||
let newEnd = resizeOriginalEnd;
|
||||
|
||||
if (resizeEdge === 'bottom') {
|
||||
const newEndMinutes = Math.max(originalStartMinutes + 15, Math.min(lastVisibleHour * 60, currentMinutes));
|
||||
const newEndMinutes = Math.max(
|
||||
originalStartMinutes + 15,
|
||||
Math.min(lastVisibleHour * 60, currentMinutes)
|
||||
);
|
||||
const newHours = Math.floor(newEndMinutes / 60);
|
||||
const newMins = newEndMinutes % 60;
|
||||
newEnd = setHours(new Date(resizeOriginalEnd), newHours);
|
||||
newEnd = setMinutes(newEnd, newMins);
|
||||
} else {
|
||||
const newStartMinutes = Math.max(firstVisibleHour * 60, Math.min(originalEndMinutes - 15, currentMinutes));
|
||||
const newStartMinutes = Math.max(
|
||||
firstVisibleHour * 60,
|
||||
Math.min(originalEndMinutes - 15, currentMinutes)
|
||||
);
|
||||
const newHours = Math.floor(newStartMinutes / 60);
|
||||
const newMins = newStartMinutes % 60;
|
||||
newStart = setHours(new Date(resizeOriginalStart), newHours);
|
||||
|
|
@ -563,10 +592,12 @@
|
|||
{/each}
|
||||
|
||||
<!-- Drag preview ghost (for cross-day dragging) -->
|
||||
{#if isDragging && draggedEvent && dragTargetDay && isSameDay(day, dragTargetDay) && !getEventsForDay(day).some(e => e.id === draggedEvent.id)}
|
||||
{#if isDragging && draggedEvent && dragTargetDay && isSameDay(day, dragTargetDay) && !getEventsForDay(day).some((e) => e.id === draggedEvent.id)}
|
||||
<div
|
||||
class="event-card drag-ghost"
|
||||
style="top: {dragPreviewTop}%; height: {dragPreviewHeight}%; background-color: {calendarsStore.getColor(draggedEvent.calendarId)};"
|
||||
style="top: {dragPreviewTop}%; height: {dragPreviewHeight}%; background-color: {calendarsStore.getColor(
|
||||
draggedEvent.calendarId
|
||||
)};"
|
||||
>
|
||||
<span class="event-time">{formatEventTime(draggedEvent.startTime)}</span>
|
||||
<span class="event-title">{draggedEvent.title}</span>
|
||||
|
|
@ -764,7 +795,9 @@
|
|||
cursor: grab;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
transition: box-shadow 0.15s ease, opacity 0.15s ease;
|
||||
transition:
|
||||
box-shadow 0.15s ease,
|
||||
opacity 0.15s ease;
|
||||
touch-action: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
|
@ -801,7 +834,8 @@
|
|||
}
|
||||
|
||||
@keyframes pulse-outline {
|
||||
0%, 100% {
|
||||
0%,
|
||||
100% {
|
||||
outline-color: hsl(var(--color-primary));
|
||||
}
|
||||
50% {
|
||||
|
|
|
|||
|
|
@ -104,11 +104,9 @@
|
|||
|
||||
// Get calendar info for the event
|
||||
let calendarName = $derived(
|
||||
event ? calendarsStore.calendars.find(c => c.id === event!.calendarId)?.name : undefined
|
||||
);
|
||||
let calendarColor = $derived(
|
||||
event ? calendarsStore.getColor(event.calendarId) : '#3b82f6'
|
||||
event ? calendarsStore.calendars.find((c) => c.id === event!.calendarId)?.name : undefined
|
||||
);
|
||||
let calendarColor = $derived(event ? calendarsStore.getColor(event.calendarId) : '#3b82f6');
|
||||
|
||||
// Format recurrence rule to human readable text
|
||||
function formatRecurrence(rule: string): string {
|
||||
|
|
@ -120,9 +118,18 @@
|
|||
const days = rule.match(/BYDAY=([A-Z,]+)/)?.[1];
|
||||
if (days) {
|
||||
const dayMap: Record<string, string> = {
|
||||
MO: 'Mo', TU: 'Di', WE: 'Mi', TH: 'Do', FR: 'Fr', SA: 'Sa', SU: 'So'
|
||||
MO: 'Mo',
|
||||
TU: 'Di',
|
||||
WE: 'Mi',
|
||||
TH: 'Do',
|
||||
FR: 'Fr',
|
||||
SA: 'Sa',
|
||||
SU: 'So',
|
||||
};
|
||||
const translatedDays = days.split(',').map(d => dayMap[d] || d).join(', ');
|
||||
const translatedDays = days
|
||||
.split(',')
|
||||
.map((d) => dayMap[d] || d)
|
||||
.join(', ');
|
||||
return `Wöchentlich (${translatedDays})`;
|
||||
}
|
||||
}
|
||||
|
|
@ -153,20 +160,35 @@
|
|||
{#if !isEditing}
|
||||
<button class="btn btn-ghost" onclick={() => (isEditing = true)}>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"
|
||||
/>
|
||||
</svg>
|
||||
Bearbeiten
|
||||
</button>
|
||||
<button class="btn btn-ghost text-destructive" onclick={handleDelete}>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
Löschen
|
||||
</button>
|
||||
{/if}
|
||||
<button class="btn btn-ghost btn-close" onclick={onClose} aria-label="Schließen">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -194,7 +216,12 @@
|
|||
<div class="detail-row">
|
||||
<span class="detail-icon">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<div class="detail-content">
|
||||
|
|
@ -208,7 +235,12 @@
|
|||
<div class="detail-row">
|
||||
<span class="detail-icon">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<div class="detail-content">
|
||||
|
|
@ -223,8 +255,18 @@
|
|||
<div class="detail-row">
|
||||
<span class="detail-icon">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<div class="detail-content">
|
||||
|
|
@ -257,15 +299,30 @@
|
|||
<div class="detail-row">
|
||||
<span class="detail-icon">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<div class="detail-content">
|
||||
<span class="detail-label">Videokonferenz</span>
|
||||
<a href={event.metadata.conferenceUrl} target="_blank" rel="noopener noreferrer" class="detail-link">
|
||||
<a
|
||||
href={event.metadata.conferenceUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="detail-link"
|
||||
>
|
||||
Beitreten
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -277,15 +334,30 @@
|
|||
<div class="detail-row">
|
||||
<span class="detail-icon">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<div class="detail-content">
|
||||
<span class="detail-label">Link</span>
|
||||
<a href={event.metadata.url} target="_blank" rel="noopener noreferrer" class="detail-link">
|
||||
<a
|
||||
href={event.metadata.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="detail-link"
|
||||
>
|
||||
{new URL(event.metadata.url).hostname}
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -297,7 +369,12 @@
|
|||
<div class="detail-row">
|
||||
<span class="detail-icon">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 6h16M4 12h16M4 18h7"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<div class="detail-content">
|
||||
|
|
@ -312,7 +389,12 @@
|
|||
<div class="detail-row">
|
||||
<span class="detail-icon">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<div class="detail-content">
|
||||
|
|
@ -322,8 +404,17 @@
|
|||
<div class="attendee">
|
||||
<span class="attendee-name">{attendee.name || attendee.email}</span>
|
||||
{#if attendee.status}
|
||||
<span class="attendee-status" class:accepted={attendee.status === 'accepted'} class:declined={attendee.status === 'declined'} class:tentative={attendee.status === 'tentative'}>
|
||||
{attendee.status === 'accepted' ? '✓' : attendee.status === 'declined' ? '✗' : '?'}
|
||||
<span
|
||||
class="attendee-status"
|
||||
class:accepted={attendee.status === 'accepted'}
|
||||
class:declined={attendee.status === 'declined'}
|
||||
class:tentative={attendee.status === 'tentative'}
|
||||
>
|
||||
{attendee.status === 'accepted'
|
||||
? '✓'
|
||||
: attendee.status === 'declined'
|
||||
? '✗'
|
||||
: '?'}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
<script lang="ts">
|
||||
import { calendarsStore } from '$lib/stores/calendars.svelte';
|
||||
import { settingsStore } from '$lib/stores/settings.svelte';
|
||||
import type { CalendarEvent, CreateEventInput, UpdateEventInput, LocationDetails } from '@calendar/shared';
|
||||
import type {
|
||||
CalendarEvent,
|
||||
CreateEventInput,
|
||||
UpdateEventInput,
|
||||
LocationDetails,
|
||||
} from '@calendar/shared';
|
||||
import { format, addMinutes, parseISO } from 'date-fns';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -96,13 +101,16 @@
|
|||
|
||||
// Build location details if any field is filled
|
||||
const locationDetails: LocationDetails | undefined =
|
||||
(locationStreet.trim() || locationPostalCode.trim() || locationCity.trim() || locationCountry.trim())
|
||||
locationStreet.trim() ||
|
||||
locationPostalCode.trim() ||
|
||||
locationCity.trim() ||
|
||||
locationCountry.trim()
|
||||
? {
|
||||
street: locationStreet.trim() || undefined,
|
||||
postalCode: locationPostalCode.trim() || undefined,
|
||||
city: locationCity.trim() || undefined,
|
||||
country: locationCountry.trim() || undefined,
|
||||
}
|
||||
street: locationStreet.trim() || undefined,
|
||||
postalCode: locationPostalCode.trim() || undefined,
|
||||
city: locationCity.trim() || undefined,
|
||||
country: locationCountry.trim() || undefined,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
// Build metadata
|
||||
|
|
@ -253,9 +261,15 @@
|
|||
<button
|
||||
type="button"
|
||||
class="flex items-center gap-1 text-sm text-primary hover:text-primary/80 transition-colors self-start"
|
||||
onclick={() => showLocationDetails = !showLocationDetails}
|
||||
onclick={() => (showLocationDetails = !showLocationDetails)}
|
||||
>
|
||||
<svg class="w-4 h-4 transition-transform" class:rotate-90={showLocationDetails} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg
|
||||
class="w-4 h-4 transition-transform"
|
||||
class:rotate-90={showLocationDetails}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
{showLocationDetails ? 'Adressdetails ausblenden' : 'Adressdetails hinzufügen'}
|
||||
|
|
@ -331,7 +345,11 @@
|
|||
>
|
||||
Abbrechen
|
||||
</button>
|
||||
<button type="submit" class="px-4 py-2 rounded-lg font-medium text-primary-foreground bg-primary hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" disabled={submitting || !title.trim() || !calendarId}>
|
||||
<button
|
||||
type="submit"
|
||||
class="px-4 py-2 rounded-lg font-medium text-primary-foreground bg-primary hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
disabled={submitting || !title.trim() || !calendarId}
|
||||
>
|
||||
{mode === 'create' ? 'Erstellen' : 'Speichern'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -280,7 +280,10 @@
|
|||
|
||||
// Build location details if any field is filled
|
||||
const locationDetails: LocationDetails | undefined =
|
||||
locationStreet.trim() || locationPostalCode.trim() || locationCity.trim() || locationCountry.trim()
|
||||
locationStreet.trim() ||
|
||||
locationPostalCode.trim() ||
|
||||
locationCity.trim() ||
|
||||
locationCountry.trim()
|
||||
? {
|
||||
street: locationStreet.trim() || undefined,
|
||||
postalCode: locationPostalCode.trim() || undefined,
|
||||
|
|
@ -337,235 +340,313 @@
|
|||
aria-modal="true"
|
||||
aria-label="Termin erstellen"
|
||||
>
|
||||
<form onsubmit={handleSubmit}>
|
||||
<!-- Header -->
|
||||
<div class="overlay-header">
|
||||
<span class="header-title">Neuer Termin</span>
|
||||
<button type="button" class="close-btn" onclick={onClose} aria-label="Schließen">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
<form onsubmit={handleSubmit}>
|
||||
<!-- Header -->
|
||||
<div class="overlay-header">
|
||||
<span class="header-title">Neuer Termin</span>
|
||||
<button type="button" class="close-btn" onclick={onClose} aria-label="Schließen">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Scrollable content -->
|
||||
<div class="overlay-content">
|
||||
<!-- Title input -->
|
||||
<div class="form-group">
|
||||
<input
|
||||
type="text"
|
||||
class="title-input"
|
||||
value={title}
|
||||
oninput={handleTitleChange}
|
||||
bind:this={titleInputRef}
|
||||
placeholder="Titel hinzufügen"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Scrollable content -->
|
||||
<div class="overlay-content">
|
||||
<!-- Title input -->
|
||||
<div class="form-group">
|
||||
<!-- Time display under title -->
|
||||
<div class="time-display">
|
||||
<svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
{format(draftStart(), 'EEEE, d. MMMM yyyy', { locale: de })}
|
||||
{#if !isAllDay}
|
||||
· {displayStartTime} – {displayEndTime}
|
||||
{:else}
|
||||
· Ganztägig
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Calendar select -->
|
||||
<div class="form-row">
|
||||
<div class="row-icon">
|
||||
<div
|
||||
class="calendar-dot"
|
||||
style="background-color: {calendarsStore.getColor(calendarId)}"
|
||||
></div>
|
||||
</div>
|
||||
<div class="row-content">
|
||||
<label class="field-label">Kalender</label>
|
||||
<select class="field-select" value={calendarId} onchange={handleCalendarChange}>
|
||||
{#each calendarsStore.calendars as cal}
|
||||
<option value={cal.id}>{cal.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- All day toggle -->
|
||||
<div class="form-row clickable" onclick={handleAllDayToggle}>
|
||||
<div class="row-icon">
|
||||
<svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="row-content toggle-content">
|
||||
<span>Ganztägig</span>
|
||||
<input
|
||||
type="text"
|
||||
class="title-input"
|
||||
value={title}
|
||||
oninput={handleTitleChange}
|
||||
bind:this={titleInputRef}
|
||||
placeholder="Titel hinzufügen"
|
||||
type="checkbox"
|
||||
checked={isAllDay}
|
||||
class="toggle-checkbox"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
onchange={handleAllDayToggle}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Time display under title -->
|
||||
<div class="time-display">
|
||||
<svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span>
|
||||
{format(draftStart(), 'EEEE, d. MMMM yyyy', { locale: de })}
|
||||
{#if !isAllDay}
|
||||
· {displayStartTime} – {displayEndTime}
|
||||
{:else}
|
||||
· Ganztägig
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Calendar select -->
|
||||
<div class="form-row">
|
||||
<div class="row-icon">
|
||||
<div class="calendar-dot" style="background-color: {calendarsStore.getColor(calendarId)}"></div>
|
||||
</div>
|
||||
<!-- All-day display mode -->
|
||||
{#if isAllDay}
|
||||
<div class="form-row sub-row">
|
||||
<div class="row-icon"></div>
|
||||
<div class="row-content">
|
||||
<label class="field-label">Kalender</label>
|
||||
<select class="field-select" value={calendarId} onchange={handleCalendarChange}>
|
||||
{#each calendarsStore.calendars as cal}
|
||||
<option value={cal.id}>{cal.name}</option>
|
||||
{/each}
|
||||
<label class="field-label">Anzeigeart</label>
|
||||
<select class="field-select" bind:value={allDayDisplayMode}>
|
||||
<option value="default">Standard (aus Einstellungen)</option>
|
||||
<option value="header">In Kopfzeile</option>
|
||||
<option value="block">Als Tagesblock</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- All day toggle -->
|
||||
<div class="form-row clickable" onclick={handleAllDayToggle}>
|
||||
<div class="row-icon">
|
||||
<svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="row-content toggle-content">
|
||||
<span>Ganztägig</span>
|
||||
<input type="checkbox" checked={isAllDay} class="toggle-checkbox" onclick={(e) => e.stopPropagation()} onchange={handleAllDayToggle} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- All-day display mode -->
|
||||
{#if isAllDay}
|
||||
<div class="form-row sub-row">
|
||||
<div class="row-icon"></div>
|
||||
<div class="row-content">
|
||||
<label class="field-label">Anzeigeart</label>
|
||||
<select class="field-select" bind:value={allDayDisplayMode}>
|
||||
<option value="default">Standard (aus Einstellungen)</option>
|
||||
<option value="header">In Kopfzeile</option>
|
||||
<option value="block">Als Tagesblock</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Start date/time -->
|
||||
<div class="form-row">
|
||||
<div class="row-icon">
|
||||
<svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="row-content datetime-row">
|
||||
<div class="datetime-field">
|
||||
<label class="field-label">Beginn</label>
|
||||
<input type="date" class="field-input" value={startDateStr} onchange={handleStartDateChange} />
|
||||
</div>
|
||||
{#if !isAllDay}
|
||||
<div class="datetime-field time-field">
|
||||
<label class="field-label">Uhrzeit</label>
|
||||
<input type="time" class="field-input" value={startTimeStr} onchange={handleStartTimeChange} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- End date/time -->
|
||||
<div class="form-row">
|
||||
<div class="row-icon">
|
||||
<svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="row-content datetime-row">
|
||||
<div class="datetime-field">
|
||||
<label class="field-label">Ende</label>
|
||||
<input type="date" class="field-input" value={endDateStr} onchange={handleEndDateChange} />
|
||||
</div>
|
||||
{#if !isAllDay}
|
||||
<div class="datetime-field time-field">
|
||||
<label class="field-label">Uhrzeit</label>
|
||||
<input type="time" class="field-input" value={endTimeStr} onchange={handleEndTimeChange} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Location -->
|
||||
<div class="form-row">
|
||||
<div class="row-icon">
|
||||
<svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="row-content">
|
||||
<input
|
||||
type="text"
|
||||
class="field-input full"
|
||||
bind:value={location}
|
||||
placeholder="Ort hinzufügen"
|
||||
<!-- Start date/time -->
|
||||
<div class="form-row">
|
||||
<div class="row-icon">
|
||||
<svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
<!-- Toggle for address details -->
|
||||
<button
|
||||
type="button"
|
||||
class="address-toggle"
|
||||
onclick={() => (showLocationDetails = !showLocationDetails)}
|
||||
>
|
||||
<svg class="toggle-chevron" class:rotated={showLocationDetails} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
{showLocationDetails ? 'Adressdetails ausblenden' : 'Adressdetails hinzufügen'}
|
||||
</button>
|
||||
</div>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="row-content datetime-row">
|
||||
<div class="datetime-field">
|
||||
<label class="field-label">Beginn</label>
|
||||
<input
|
||||
type="date"
|
||||
class="field-input"
|
||||
value={startDateStr}
|
||||
onchange={handleStartDateChange}
|
||||
/>
|
||||
</div>
|
||||
{#if !isAllDay}
|
||||
<div class="datetime-field time-field">
|
||||
<label class="field-label">Uhrzeit</label>
|
||||
<input
|
||||
type="time"
|
||||
class="field-input"
|
||||
value={startTimeStr}
|
||||
onchange={handleStartTimeChange}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Location details (expandable) -->
|
||||
{#if showLocationDetails}
|
||||
<div class="form-row sub-row">
|
||||
<div class="row-icon"></div>
|
||||
<div class="row-content address-details-form">
|
||||
<div class="address-field">
|
||||
<label class="field-label">Straße</label>
|
||||
<!-- End date/time -->
|
||||
<div class="form-row">
|
||||
<div class="row-icon">
|
||||
<svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="row-content datetime-row">
|
||||
<div class="datetime-field">
|
||||
<label class="field-label">Ende</label>
|
||||
<input
|
||||
type="date"
|
||||
class="field-input"
|
||||
value={endDateStr}
|
||||
onchange={handleEndDateChange}
|
||||
/>
|
||||
</div>
|
||||
{#if !isAllDay}
|
||||
<div class="datetime-field time-field">
|
||||
<label class="field-label">Uhrzeit</label>
|
||||
<input
|
||||
type="time"
|
||||
class="field-input"
|
||||
value={endTimeStr}
|
||||
onchange={handleEndTimeChange}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Location -->
|
||||
<div class="form-row">
|
||||
<div class="row-icon">
|
||||
<svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="row-content">
|
||||
<input
|
||||
type="text"
|
||||
class="field-input full"
|
||||
bind:value={location}
|
||||
placeholder="Ort hinzufügen"
|
||||
/>
|
||||
<!-- Toggle for address details -->
|
||||
<button
|
||||
type="button"
|
||||
class="address-toggle"
|
||||
onclick={() => (showLocationDetails = !showLocationDetails)}
|
||||
>
|
||||
<svg
|
||||
class="toggle-chevron"
|
||||
class:rotated={showLocationDetails}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
{showLocationDetails ? 'Adressdetails ausblenden' : 'Adressdetails hinzufügen'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Location details (expandable) -->
|
||||
{#if showLocationDetails}
|
||||
<div class="form-row sub-row">
|
||||
<div class="row-icon"></div>
|
||||
<div class="row-content address-details-form">
|
||||
<div class="address-field">
|
||||
<label class="field-label">Straße</label>
|
||||
<input
|
||||
type="text"
|
||||
class="field-input"
|
||||
bind:value={locationStreet}
|
||||
placeholder="Musterstraße 123"
|
||||
/>
|
||||
</div>
|
||||
<div class="address-row">
|
||||
<div class="address-field postal">
|
||||
<label class="field-label">PLZ</label>
|
||||
<input
|
||||
type="text"
|
||||
class="field-input"
|
||||
bind:value={locationStreet}
|
||||
placeholder="Musterstraße 123"
|
||||
bind:value={locationPostalCode}
|
||||
placeholder="12345"
|
||||
/>
|
||||
</div>
|
||||
<div class="address-row">
|
||||
<div class="address-field postal">
|
||||
<label class="field-label">PLZ</label>
|
||||
<input
|
||||
type="text"
|
||||
class="field-input"
|
||||
bind:value={locationPostalCode}
|
||||
placeholder="12345"
|
||||
/>
|
||||
</div>
|
||||
<div class="address-field city">
|
||||
<label class="field-label">Stadt</label>
|
||||
<input
|
||||
type="text"
|
||||
class="field-input"
|
||||
bind:value={locationCity}
|
||||
placeholder="Musterstadt"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="address-field">
|
||||
<label class="field-label">Land</label>
|
||||
<div class="address-field city">
|
||||
<label class="field-label">Stadt</label>
|
||||
<input
|
||||
type="text"
|
||||
class="field-input"
|
||||
bind:value={locationCountry}
|
||||
placeholder="Deutschland"
|
||||
bind:value={locationCity}
|
||||
placeholder="Musterstadt"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Description -->
|
||||
<div class="form-row">
|
||||
<div class="row-icon">
|
||||
<svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="row-content">
|
||||
<textarea
|
||||
class="field-textarea"
|
||||
bind:value={description}
|
||||
placeholder="Beschreibung hinzufügen"
|
||||
rows="3"
|
||||
></textarea>
|
||||
<div class="address-field">
|
||||
<label class="field-label">Land</label>
|
||||
<input
|
||||
type="text"
|
||||
class="field-input"
|
||||
bind:value={locationCountry}
|
||||
placeholder="Deutschland"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Actions (sticky footer) -->
|
||||
<div class="overlay-actions">
|
||||
<button type="button" class="btn-ghost" onclick={onClose}>
|
||||
Abbrechen
|
||||
</button>
|
||||
<button type="submit" class="btn-primary" disabled={submitting || !title.trim()}>
|
||||
{submitting ? 'Speichern...' : 'Speichern'}
|
||||
</button>
|
||||
<!-- Description -->
|
||||
<div class="form-row">
|
||||
<div class="row-icon">
|
||||
<svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 6h16M4 12h16M4 18h7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="row-content">
|
||||
<textarea
|
||||
class="field-textarea"
|
||||
bind:value={description}
|
||||
placeholder="Beschreibung hinzufügen"
|
||||
rows="3"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Actions (sticky footer) -->
|
||||
<div class="overlay-actions">
|
||||
<button type="button" class="btn-ghost" onclick={onClose}> Abbrechen </button>
|
||||
<button type="submit" class="btn-primary" disabled={submitting || !title.trim()}>
|
||||
{submitting ? 'Speichern...' : 'Speichern'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
@ -576,7 +657,9 @@
|
|||
background: hsl(var(--color-surface));
|
||||
border: 1px solid hsl(var(--color-border));
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2), 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||
box-shadow:
|
||||
0 20px 60px rgba(0, 0, 0, 0.2),
|
||||
0 4px 16px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1001;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
|||
|
|
@ -84,7 +84,9 @@ export const eventsStore = {
|
|||
// Include draft event if it exists and is on this day
|
||||
if (includeDraft && draftEvent) {
|
||||
const draftStart =
|
||||
typeof draftEvent.startTime === 'string' ? parseISO(draftEvent.startTime) : draftEvent.startTime;
|
||||
typeof draftEvent.startTime === 'string'
|
||||
? parseISO(draftEvent.startTime)
|
||||
: draftEvent.startTime;
|
||||
if (isSameDay(date, draftStart)) {
|
||||
result.push(draftEvent);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,7 +106,10 @@ export const viewStore = {
|
|||
|
||||
// Load view type from settings or localStorage (for backwards compatibility)
|
||||
const savedView = localStorage.getItem('calendar-view-type');
|
||||
if (savedView && ['day', '5day', 'week', '10day', '14day', 'month', 'year', 'agenda'].includes(savedView)) {
|
||||
if (
|
||||
savedView &&
|
||||
['day', '5day', 'week', '10day', '14day', 'month', 'year', 'agenda'].includes(savedView)
|
||||
) {
|
||||
viewType = savedView as CalendarViewType;
|
||||
} else {
|
||||
// Use default view from settings
|
||||
|
|
|
|||
|
|
@ -225,7 +225,10 @@
|
|||
class:sidebar-mode={isSidebarMode && !isCollapsed}
|
||||
class:floating-mode={!isSidebarMode && !isCollapsed}
|
||||
>
|
||||
<div class="content-wrapper" class:calendar-expanded={settingsStore.sidebarCollapsed && $page.url.pathname === '/'}>
|
||||
<div
|
||||
class="content-wrapper"
|
||||
class:calendar-expanded={settingsStore.sidebarCollapsed && $page.url.pathname === '/'}
|
||||
>
|
||||
{@render children()}
|
||||
</div>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -107,7 +107,12 @@
|
|||
title="Sidebar ausblenden"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 19l-7-7 7-7m8 14l-7-7 7-7" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M11 19l-7-7 7-7m8 14l-7-7 7-7"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
|
|
@ -135,16 +140,22 @@
|
|||
title="Sidebar einblenden"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="fab-new-event"
|
||||
onclick={handleNewEvent}
|
||||
title="Neuer Termin"
|
||||
>
|
||||
<button class="fab-new-event" onclick={handleNewEvent} title="Neuer Termin">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 4v16m8-8H4"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -186,10 +197,7 @@
|
|||
|
||||
<!-- Event Detail Modal -->
|
||||
{#if modalEventId}
|
||||
<EventDetailModal
|
||||
eventId={modalEventId}
|
||||
onClose={handleEventModalClose}
|
||||
/>
|
||||
<EventDetailModal eventId={modalEventId} onClose={handleEventModalClose} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@
|
|||
const groups: Map<string, typeof currentEvents> = new Map();
|
||||
|
||||
for (const event of currentEvents) {
|
||||
const start = typeof event.startTime === 'string' ? parseISO(event.startTime) : event.startTime;
|
||||
const start =
|
||||
typeof event.startTime === 'string' ? parseISO(event.startTime) : event.startTime;
|
||||
const dateKey = format(start, 'yyyy-MM-dd');
|
||||
|
||||
if (!groups.has(dateKey)) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,12 @@
|
|||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { theme } from '$lib/stores/theme';
|
||||
import { userSettings } from '$lib/stores/user-settings.svelte';
|
||||
import { settingsStore, type WeekStartDay, type TimeFormat, type AllDayDisplayMode } from '$lib/stores/settings.svelte';
|
||||
import {
|
||||
settingsStore,
|
||||
type WeekStartDay,
|
||||
type TimeFormat,
|
||||
type AllDayDisplayMode,
|
||||
} from '$lib/stores/settings.svelte';
|
||||
import { calendarsStore } from '$lib/stores/calendars.svelte';
|
||||
import { toast } from '$lib/stores/toast';
|
||||
import { setLocale, supportedLocales, type SupportedLocale } from '$lib/i18n';
|
||||
|
|
@ -177,7 +182,11 @@
|
|||
<input type="color" class="color-input" bind:value={newCalendarColor} />
|
||||
</div>
|
||||
<div class="calendar-form-actions">
|
||||
<button type="button" class="btn btn-ghost" onclick={() => (showNewCalendarForm = false)}>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost"
|
||||
onclick={() => (showNewCalendarForm = false)}
|
||||
>
|
||||
Abbrechen
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary" disabled={!newCalendarName.trim()}>
|
||||
|
|
@ -206,7 +215,11 @@
|
|||
<input type="color" name="color" class="color-input" value={calendar.color} />
|
||||
</div>
|
||||
<div class="calendar-form-actions">
|
||||
<button type="button" class="btn btn-ghost" onclick={() => (editingCalendar = null)}>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost"
|
||||
onclick={() => (editingCalendar = null)}
|
||||
>
|
||||
Abbrechen
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary"> Speichern </button>
|
||||
|
|
@ -435,11 +448,14 @@
|
|||
<input
|
||||
type="checkbox"
|
||||
checked={settingsStore.filterHoursEnabled}
|
||||
onchange={() => settingsStore.set('filterHoursEnabled', !settingsStore.filterHoursEnabled)}
|
||||
onchange={() =>
|
||||
settingsStore.set('filterHoursEnabled', !settingsStore.filterHoursEnabled)}
|
||||
/>
|
||||
<div class="toggle-info">
|
||||
<span class="setting-label">Stunden filtern</span>
|
||||
<span class="setting-description">Nur bestimmte Stunden in der Tages-/Wochenansicht anzeigen</span>
|
||||
<span class="setting-description"
|
||||
>Nur bestimmte Stunden in der Tages-/Wochenansicht anzeigen</span
|
||||
>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
|
@ -448,7 +464,9 @@
|
|||
<div class="setting-item hour-range-setting">
|
||||
<div class="setting-info">
|
||||
<span class="setting-label">Sichtbare Stunden</span>
|
||||
<span class="setting-description">Zeitbereich der in der Kalenderansicht angezeigt wird</span>
|
||||
<span class="setting-description"
|
||||
>Zeitbereich der in der Kalenderansicht angezeigt wird</span
|
||||
>
|
||||
</div>
|
||||
<div class="hour-range-inputs">
|
||||
<div class="hour-input-group">
|
||||
|
|
@ -526,7 +544,9 @@
|
|||
>
|
||||
{#each durationOptions as duration}
|
||||
<option value={duration}>
|
||||
{duration >= 60 ? `${duration / 60} Stunde${duration > 60 ? 'n' : ''}` : `${duration} Minuten`}
|
||||
{duration >= 60
|
||||
? `${duration / 60} Stunde${duration > 60 ? 'n' : ''}`
|
||||
: `${duration} Minuten`}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
"dev:landing": "pnpm --filter @calendar/landing dev",
|
||||
"dev:mobile": "pnpm --filter @calendar/mobile dev",
|
||||
"build": "turbo run build",
|
||||
"lint": "turbo run lint",
|
||||
"clean": "turbo run clean",
|
||||
"db:push": "pnpm --filter @calendar/backend db:push",
|
||||
"db:studio": "pnpm --filter @calendar/backend db:studio",
|
||||
|
|
|
|||
|
|
@ -11,8 +11,7 @@
|
|||
"./constants": "./src/constants/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"type-check": "tsc --noEmit",
|
||||
"lint": "eslint src"
|
||||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "~5.9.2"
|
||||
|
|
|
|||
|
|
@ -60,7 +60,15 @@ export interface UpdateCalendarInput {
|
|||
/**
|
||||
* Calendar view types
|
||||
*/
|
||||
export type CalendarViewType = 'day' | '5day' | 'week' | '10day' | '14day' | 'month' | 'year' | 'agenda';
|
||||
export type CalendarViewType =
|
||||
| 'day'
|
||||
| '5day'
|
||||
| 'week'
|
||||
| '10day'
|
||||
| '14day'
|
||||
| 'month'
|
||||
| 'year'
|
||||
| 'agenda';
|
||||
|
||||
/**
|
||||
* Default calendar colors
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { RecurrencePattern, RecurrenceFrequency, Weekday } from '../types/recurrence';
|
||||
import { addDays, addWeeks, addMonths, addYears } from './date';
|
||||
import { addDays, addMonths, addYears } from './date';
|
||||
|
||||
/**
|
||||
* Parse an RFC 5545 RRULE string to a RecurrencePattern object
|
||||
|
|
@ -46,13 +46,14 @@ export function parseRRule(rrule: string): RecurrencePattern | null {
|
|||
pattern.count = parseInt(value, 10);
|
||||
break;
|
||||
|
||||
case 'UNTIL':
|
||||
case 'UNTIL': {
|
||||
// Parse UNTIL date (format: YYYYMMDD or YYYYMMDDTHHMMSSZ)
|
||||
const year = parseInt(value.substring(0, 4), 10);
|
||||
const month = parseInt(value.substring(4, 6), 10) - 1;
|
||||
const day = parseInt(value.substring(6, 8), 10);
|
||||
pattern.until = new Date(year, month, day, 23, 59, 59);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -188,7 +189,7 @@ export function generateOccurrences(
|
|||
rangeStart: Date,
|
||||
rangeEnd: Date,
|
||||
exceptions: string[] = [],
|
||||
maxOccurrences: number = 365
|
||||
maxOccurrences = 365
|
||||
): Date[] {
|
||||
const occurrences: Date[] = [];
|
||||
const exceptionsSet = new Set(exceptions);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ COPY pnpm-lock.yaml ./
|
|||
|
||||
# Copy shared packages
|
||||
COPY packages/shared-errors ./packages/shared-errors
|
||||
COPY packages/shared-nestjs-auth ./packages/shared-nestjs-auth
|
||||
|
||||
# Copy chat backend
|
||||
COPY apps/chat/apps/backend ./apps/chat/apps/backend
|
||||
|
|
@ -24,6 +25,9 @@ RUN pnpm install --frozen-lockfile
|
|||
WORKDIR /app/packages/shared-errors
|
||||
RUN pnpm build
|
||||
|
||||
WORKDIR /app/packages/shared-nestjs-auth
|
||||
RUN pnpm build
|
||||
|
||||
# Build the backend
|
||||
WORKDIR /app/apps/chat/apps/backend
|
||||
RUN pnpm build
|
||||
|
|
|
|||
17
apps/chat/apps/backend/eslint.config.mjs
Normal file
17
apps/chat/apps/backend/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// @ts-check
|
||||
import {
|
||||
baseConfig,
|
||||
typescriptConfig,
|
||||
nestjsConfig,
|
||||
prettierConfig,
|
||||
} from '@manacore/eslint-config';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', 'node_modules/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...nestjsConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common';
|
||||
import { isOk } from '@manacore/shared-errors';
|
||||
import { ChatService } from './chat.service';
|
||||
import { ChatCompletionDto, ChatCompletionResponseDto } from './dto/chat-completion.dto';
|
||||
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
|
||||
import { type ChatService } from './chat.service';
|
||||
import { type ChatCompletionDto, type ChatCompletionResponseDto } from './dto/chat-completion.dto';
|
||||
import { JwtAuthGuard, CurrentUser, type CurrentUserData } from '@manacore/shared-nestjs-auth';
|
||||
|
||||
@Controller('chat')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { Injectable, Inject, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { type ConfigService } from '@nestjs/config';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { type AsyncResult, ok, err, ValidationError, ServiceError } from '@manacore/shared-errors';
|
||||
import { GoogleGenerativeAI } from '@google/generative-ai';
|
||||
import { DATABASE_CONNECTION } from '../db/database.module';
|
||||
import { type Database } from '../db/connection';
|
||||
import { models, type Model } from '../db/schema/models.schema';
|
||||
import { ChatCompletionDto, ChatCompletionResponseDto } from './dto/chat-completion.dto';
|
||||
import { type ChatCompletionDto, type ChatCompletionResponseDto } from './dto/chat-completion.dto';
|
||||
|
||||
@Injectable()
|
||||
export class ChatService {
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ import {
|
|||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { isOk } from '@manacore/shared-errors';
|
||||
import { ConversationService } from './conversation.service';
|
||||
import { type ConversationService } from './conversation.service';
|
||||
import { type Conversation } from '../db/schema/conversations.schema';
|
||||
import { type Message } from '../db/schema/messages.schema';
|
||||
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
|
||||
import { JwtAuthGuard, CurrentUser, type CurrentUserData } from '@manacore/shared-nestjs-auth';
|
||||
|
||||
@Controller('conversations')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { drizzle } from 'drizzle-orm/postgres-js';
|
|||
import * as schema from './schema';
|
||||
|
||||
// Use require for postgres to avoid ESM/CommonJS interop issues
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
|
||||
const postgres = require('postgres');
|
||||
|
||||
let connection: ReturnType<typeof postgres> | null = null;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Module, Global, OnModuleDestroy } from '@nestjs/common';
|
||||
import { Module, Global, type OnModuleDestroy } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { getDb, closeConnection, type Database } from './connection';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Body, Controller, Delete, Get, Param, Post, UseGuards } from '@nestjs/common';
|
||||
import { isOk } from '@manacore/shared-errors';
|
||||
import { DocumentService } from './document.service';
|
||||
import { type DocumentService } from './document.service';
|
||||
import { type Document } from '../db/schema/documents.schema';
|
||||
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
|
||||
import { JwtAuthGuard, CurrentUser, type CurrentUserData } from '@manacore/shared-nestjs-auth';
|
||||
|
||||
@Controller('documents')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Controller, Get, Param } from '@nestjs/common';
|
||||
import { isOk } from '@manacore/shared-errors';
|
||||
import { ModelService } from './model.service';
|
||||
import { type ModelService } from './model.service';
|
||||
import { type Model } from '../db/schema/models.schema';
|
||||
|
||||
// Models are publicly accessible - no auth required to list available models
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Body, Controller, Delete, Get, Param, Patch, Post, UseGuards } from '@nestjs/common';
|
||||
import { isOk } from '@manacore/shared-errors';
|
||||
import { SpaceService } from './space.service';
|
||||
import { type SpaceService } from './space.service';
|
||||
import { type Space, type SpaceMember } from '../db/schema/spaces.schema';
|
||||
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
|
||||
import { JwtAuthGuard, CurrentUser, type CurrentUserData } from '@manacore/shared-nestjs-auth';
|
||||
|
||||
@Controller('spaces')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Body, Controller, Delete, Get, Param, Patch, Post, UseGuards } from '@nestjs/common';
|
||||
import { isOk } from '@manacore/shared-errors';
|
||||
import { TemplateService } from './template.service';
|
||||
import { type TemplateService } from './template.service';
|
||||
import { type Template } from '../db/schema/templates.schema';
|
||||
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
|
||||
import { JwtAuthGuard, CurrentUser, type CurrentUserData } from '@manacore/shared-nestjs-auth';
|
||||
|
||||
@Controller('templates')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
|
|
|
|||
12
apps/chat/apps/mobile/eslint.config.mjs
Normal file
12
apps/chat/apps/mobile/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// @ts-check
|
||||
import { baseConfig, typescriptConfig, reactConfig, prettierConfig } from '@manacore/eslint-config';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', '.expo/**', 'node_modules/**', 'android/**', 'ios/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...reactConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
|
|
@ -12,8 +12,8 @@
|
|||
"build:preview": "eas build --profile preview",
|
||||
"build:prod": "eas build --profile production",
|
||||
"prebuild": "expo prebuild",
|
||||
"lint": "eslint \"**/*.{js,jsx,ts,tsx}\" && prettier -c \"**/*.{js,jsx,ts,tsx,json}\"",
|
||||
"format": "eslint \"**/*.{js,jsx,ts,tsx}\" --fix && prettier \"**/*.{js,jsx,ts,tsx,json}\" --write",
|
||||
"lint": "eslint .",
|
||||
"format": "eslint . --fix",
|
||||
"web": "expo start --web"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
@ -49,7 +49,7 @@
|
|||
"@typescript-eslint/eslint-plugin": "^7.7.0",
|
||||
"@typescript-eslint/parser": "^7.7.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-config-universe": "^12.0.1",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-tailwindcss": "^0.5.11",
|
||||
|
|
|
|||
17
apps/chat/apps/web/eslint.config.js
Normal file
17
apps/chat/apps/web/eslint.config.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// @ts-check
|
||||
import {
|
||||
baseConfig,
|
||||
typescriptConfig,
|
||||
svelteConfig,
|
||||
prettierConfig,
|
||||
} from '@manacore/eslint-config';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', '.svelte-kit/**', 'node_modules/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...svelteConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
|
|
@ -10,7 +10,8 @@
|
|||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"type-check": "echo 'Skipping type-check: @chat/web needs shared-ui component fixes'"
|
||||
"type-check": "echo 'Skipping type-check: @chat/web needs shared-ui component fixes'",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^6.0.0",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
"private": true,
|
||||
"description": "Chat project - AI chat application with mobile, web and landing",
|
||||
"scripts": {
|
||||
"dev": "turbo run dev",
|
||||
"lint": "turbo run lint"
|
||||
"dev": "turbo run dev"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,5 +7,6 @@
|
|||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {}
|
||||
}
|
||||
|
|
|
|||
17
apps/contacts/apps/backend/eslint.config.mjs
Normal file
17
apps/contacts/apps/backend/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// @ts-check
|
||||
import {
|
||||
baseConfig,
|
||||
typescriptConfig,
|
||||
nestjsConfig,
|
||||
prettierConfig,
|
||||
} from '@manacore/eslint-config';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', 'node_modules/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...nestjsConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
17
apps/contacts/apps/web/eslint.config.js
Normal file
17
apps/contacts/apps/web/eslint.config.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// @ts-check
|
||||
import {
|
||||
baseConfig,
|
||||
typescriptConfig,
|
||||
svelteConfig,
|
||||
prettierConfig,
|
||||
} from '@manacore/eslint-config';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', '.svelte-kit/**', 'node_modules/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...svelteConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
|
|
@ -43,8 +43,7 @@
|
|||
<div class="flex gap-2">
|
||||
{#each ['light', 'dark', 'system'] as mode}
|
||||
<button
|
||||
class="px-4 py-2 text-sm font-medium rounded-lg transition-colors {theme.mode ===
|
||||
mode
|
||||
class="px-4 py-2 text-sm font-medium rounded-lg transition-colors {theme.mode === mode
|
||||
? 'bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]'
|
||||
: 'bg-[hsl(var(--muted))] hover:bg-[hsl(var(--muted))]/80 text-[hsl(var(--foreground))]'}"
|
||||
onclick={() => theme.setMode(mode as 'light' | 'dark' | 'system')}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
"dev:landing": "pnpm --filter @contacts/landing dev",
|
||||
"dev:mobile": "pnpm --filter @contacts/mobile dev",
|
||||
"build": "turbo run build",
|
||||
"lint": "turbo run lint",
|
||||
"clean": "turbo run clean",
|
||||
"db:push": "pnpm --filter @contacts/backend db:push",
|
||||
"db:studio": "pnpm --filter @contacts/backend db:studio",
|
||||
|
|
|
|||
17
apps/maerchenzauber/apps/backend/eslint.config.mjs
Normal file
17
apps/maerchenzauber/apps/backend/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// @ts-check
|
||||
import {
|
||||
baseConfig,
|
||||
typescriptConfig,
|
||||
nestjsConfig,
|
||||
prettierConfig,
|
||||
} from '@manacore/eslint-config';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', 'node_modules/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...nestjsConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
12
apps/maerchenzauber/apps/mobile/eslint.config.mjs
Normal file
12
apps/maerchenzauber/apps/mobile/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// @ts-check
|
||||
import { baseConfig, typescriptConfig, reactConfig, prettierConfig } from '@manacore/eslint-config';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', '.expo/**', 'node_modules/**', 'android/**', 'ios/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...reactConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
2
apps/manacore/apps/mobile/app-env.d.ts
vendored
2
apps/manacore/apps/mobile/app-env.d.ts
vendored
|
|
@ -1,2 +1,2 @@
|
|||
// @ts-ignore
|
||||
// @ts-expect-error nativewind types
|
||||
/// <reference types="nativewind/types" />
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Stack } from 'expo-router';
|
||||
import { ScrollView, Text, View, TouchableOpacity, Image, Alert } from 'react-native';
|
||||
import { ScrollView, Text, View, TouchableOpacity, Alert } from 'react-native';
|
||||
import { FontAwesome5 } from '@expo/vector-icons';
|
||||
|
||||
import { Container } from '~/components/Container';
|
||||
import { useTheme } from '../../utils/themeContext';
|
||||
import { supabase } from '../../utils/supabase';
|
||||
|
||||
// Definiere die Mana-Pakete
|
||||
const manaPacks = [
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { Stack, useRouter } from 'expo-router';
|
||||
import { ScrollView, Text, View, TouchableOpacity, Pressable } from 'react-native';
|
||||
import { ScrollView, Text, View, TouchableOpacity } from 'react-native';
|
||||
import { FontAwesome5 } from '@expo/vector-icons';
|
||||
|
||||
import { Container } from '~/components/Container';
|
||||
import DashboardStats from '../../components/DashboardStats';
|
||||
import { useTheme } from '../../utils/themeContext';
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ export default function OrganizationDetails() {
|
|||
}
|
||||
|
||||
fetchOrganizationDetails();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [initialOrgName, orgId]);
|
||||
|
||||
const fetchOrganizationDetails = async () => {
|
||||
|
|
@ -94,14 +95,6 @@ export default function OrganizationDetails() {
|
|||
.eq('user_id', session.user.id)
|
||||
.eq('organization_id', orgId);
|
||||
|
||||
// Supabase gibt die Daten in einem anderen Format zurück als erwartet
|
||||
// Definiere den korrekten Typ für die Benutzerrolle
|
||||
interface UserRoleWithRoles {
|
||||
roles: {
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
|
||||
if (currentUserRolesError) throw currentUserRolesError;
|
||||
|
||||
// Finde die höchste Rolle
|
||||
|
|
@ -314,7 +307,7 @@ export default function OrganizationDetails() {
|
|||
{/* Lösch-Bestätigungsmodal */}
|
||||
<Modal
|
||||
animationType="fade"
|
||||
transparent={true}
|
||||
transparent
|
||||
visible={showDeleteModal}
|
||||
onRequestClose={cancelDelete}
|
||||
>
|
||||
|
|
@ -339,8 +332,7 @@ export default function OrganizationDetails() {
|
|||
<Text
|
||||
className={`mb-6 text-center text-base ${isDarkMode ? 'text-gray-300' : 'text-gray-600'}`}
|
||||
>
|
||||
Möchtest du die Organisation "{orgName}" wirklich löschen? Diese Aktion kann nicht
|
||||
rückgängig gemacht werden.
|
||||
{`Möchtest du die Organisation "${orgName}" wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.`}
|
||||
</Text>
|
||||
|
||||
<View className="flex-row justify-between">
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ export default function Organizations() {
|
|||
<Text className="text-sm font-semibold text-white">Neue Organisation</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<OrganizationList hideTitle={true} ref={organizationListRef} />
|
||||
<OrganizationList hideTitle ref={organizationListRef} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -7,14 +7,13 @@ import {
|
|||
ScrollView,
|
||||
TextInput,
|
||||
Alert,
|
||||
ActivityIndicator,
|
||||
} from 'react-native';
|
||||
import { Stack } from 'expo-router';
|
||||
import { FontAwesome5 } from '@expo/vector-icons';
|
||||
import { Container } from '~/components/Container';
|
||||
import { useTheme, ThemeMode } from '~/utils/themeContext';
|
||||
import { useTheme, type ThemeMode } from '~/utils/themeContext';
|
||||
import { supabase } from '../../utils/supabase';
|
||||
import { Session } from '@supabase/supabase-js';
|
||||
import { type Session } from '@supabase/supabase-js';
|
||||
|
||||
interface Profile {
|
||||
id: string;
|
||||
|
|
|
|||
|
|
@ -5,11 +5,9 @@ import {
|
|||
Text,
|
||||
View,
|
||||
TouchableOpacity,
|
||||
TextInput,
|
||||
Alert,
|
||||
ActivityIndicator,
|
||||
Modal,
|
||||
Pressable,
|
||||
} from 'react-native';
|
||||
import { FontAwesome5 } from '@expo/vector-icons';
|
||||
|
||||
|
|
@ -27,16 +25,12 @@ export default function TeamDetails() {
|
|||
}>();
|
||||
const { isDarkMode } = useTheme();
|
||||
const [teamName, setTeamName] = useState(initialTeamName || '');
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [newTeamName, setNewTeamName] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [deletingTeam, setDeletingTeam] = useState(false);
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialTeamName) {
|
||||
setTeamName(initialTeamName);
|
||||
setNewTeamName(initialTeamName);
|
||||
}
|
||||
}, [initialTeamName]);
|
||||
|
||||
|
|
@ -44,47 +38,6 @@ export default function TeamDetails() {
|
|||
router.push('/teams');
|
||||
};
|
||||
|
||||
const startEditing = () => {
|
||||
setIsEditing(true);
|
||||
setNewTeamName(teamName);
|
||||
};
|
||||
|
||||
const cancelEditing = () => {
|
||||
setIsEditing(false);
|
||||
};
|
||||
|
||||
const updateTeamName = async () => {
|
||||
if (!newTeamName.trim()) {
|
||||
Alert.alert('Fehler', 'Der Teamname darf nicht leer sein.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (newTeamName.trim() === teamName) {
|
||||
setIsEditing(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const { error } = await supabase
|
||||
.from('teams')
|
||||
.update({ name: newTeamName.trim() })
|
||||
.eq('id', teamId);
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
setTeamName(newTeamName.trim());
|
||||
setIsEditing(false);
|
||||
Alert.alert('Erfolg', 'Der Teamname wurde erfolgreich aktualisiert.');
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Aktualisieren des Teamnamens:', error);
|
||||
Alert.alert('Fehler', 'Es ist ein Fehler beim Aktualisieren des Teamnamens aufgetreten.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteTeam = () => {
|
||||
console.log('Delete team button clicked, teamId:', teamId);
|
||||
// Modal öffnen statt Alert anzeigen
|
||||
|
|
@ -111,7 +64,7 @@ export default function TeamDetails() {
|
|||
console.log('Checking for dependencies...');
|
||||
|
||||
// 1. Prüfe auf credit_transactions
|
||||
const { data: txData, error: txCheckError } = await supabase
|
||||
const { data: txData } = await supabase
|
||||
.from('credit_transactions')
|
||||
.select('id')
|
||||
.eq('team_id', teamId);
|
||||
|
|
@ -119,7 +72,7 @@ export default function TeamDetails() {
|
|||
console.log('Credit transactions:', txData);
|
||||
|
||||
// 2. Prüfe auf team_members
|
||||
const { data: memberData, error: memberCheckError } = await supabase
|
||||
const { data: memberData } = await supabase
|
||||
.from('team_members')
|
||||
.select('user_id')
|
||||
.eq('team_id', teamId);
|
||||
|
|
@ -127,7 +80,7 @@ export default function TeamDetails() {
|
|||
console.log('Team members:', memberData);
|
||||
|
||||
// 3. Prüfe auf user_roles
|
||||
const { data: roleData, error: roleCheckError } = await supabase
|
||||
const { data: roleData } = await supabase
|
||||
.from('user_roles')
|
||||
.select('id')
|
||||
.eq('team_id', teamId);
|
||||
|
|
@ -240,7 +193,7 @@ export default function TeamDetails() {
|
|||
{/* Lösch-Bestätigungsmodal */}
|
||||
<Modal
|
||||
animationType="fade"
|
||||
transparent={true}
|
||||
transparent
|
||||
visible={showDeleteModal}
|
||||
onRequestClose={cancelDelete}
|
||||
>
|
||||
|
|
@ -265,8 +218,7 @@ export default function TeamDetails() {
|
|||
<Text
|
||||
className={`mb-6 text-center text-base ${isDarkMode ? 'text-gray-300' : 'text-gray-600'}`}
|
||||
>
|
||||
Möchtest du das Team "{teamName}" wirklich löschen? Diese Aktion kann nicht rückgängig
|
||||
gemacht werden.
|
||||
{`Möchtest du das Team "${teamName}" wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.`}
|
||||
</Text>
|
||||
|
||||
<View className="flex-row justify-between">
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export default function Teams() {
|
|||
<Text className="text-sm font-semibold text-white">Neues Team</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<TeamList hideTitle={true} />
|
||||
<TeamList hideTitle />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export default function NotFoundScreen() {
|
|||
<>
|
||||
<Stack.Screen options={{ title: 'Oops!' }} />
|
||||
<Container>
|
||||
<Text className={styles.title}>This screen doesn't exist.</Text>
|
||||
<Text className={styles.title}>This screen does not exist.</Text>
|
||||
<Link href="/" className={styles.link}>
|
||||
<Text className={styles.linkText}>Go to home screen!</Text>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { Stack, useRouter, useSegments } from 'expo-router';
|
|||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||
import { ThemeProvider } from '~/utils/themeContext';
|
||||
import { supabase } from '../utils/supabase';
|
||||
import { Session } from '@supabase/supabase-js';
|
||||
import { type Session } from '@supabase/supabase-js';
|
||||
|
||||
export const unstable_settings = {
|
||||
// Ensure that reloading on `/modal` keeps a back button present.
|
||||
|
|
@ -54,6 +54,7 @@ function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||
// leite ihn zur Hauptseite um
|
||||
router.replace('/');
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [session, segments, isLoading]);
|
||||
|
||||
// Zeige nichts während des Ladens
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import {
|
|||
Platform,
|
||||
} from 'react-native';
|
||||
import { Stack, useRouter, useLocalSearchParams } from 'expo-router';
|
||||
import { supabase } from '../../utils/supabase';
|
||||
import { useTheme } from '../../utils/themeContext';
|
||||
|
||||
export default function ResetPasswordScreen() {
|
||||
|
|
@ -32,6 +31,7 @@ export default function ResetPasswordScreen() {
|
|||
return;
|
||||
}
|
||||
verifyToken();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const verifyToken = async () => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert } from 'react-native';
|
||||
import { supabase } from '../utils/supabase';
|
||||
import { Session } from '@supabase/supabase-js';
|
||||
import { type Session } from '@supabase/supabase-js';
|
||||
|
||||
interface Profile {
|
||||
id: string;
|
||||
|
|
@ -21,6 +21,7 @@ export default function Account({ session }: { session: Session }) {
|
|||
|
||||
useEffect(() => {
|
||||
if (session) getProfile();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [session]);
|
||||
|
||||
async function getProfile() {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,8 @@
|
|||
import React, { useState } from 'react';
|
||||
import {
|
||||
Alert,
|
||||
StyleSheet,
|
||||
View,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
Text,
|
||||
Image,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import { Alert, StyleSheet, View, TextInput, TouchableOpacity, Text, Platform } from 'react-native';
|
||||
import { FontAwesome5 } from '@expo/vector-icons';
|
||||
import { supabase } from '../utils/supabase';
|
||||
import { useTheme, useThemeColors, lightColors, darkColors } from '../utils/themeContext';
|
||||
import { useTheme, useThemeColors } from '../utils/themeContext';
|
||||
|
||||
export default function Auth() {
|
||||
const { isDarkMode } = useTheme();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { forwardRef } from 'react';
|
||||
import { Text, TouchableOpacity, TouchableOpacityProps, View } from 'react-native';
|
||||
import { Text, TouchableOpacity, type TouchableOpacityProps, type View } from 'react-native';
|
||||
|
||||
type ButtonProps = {
|
||||
title: string;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
ScrollView,
|
||||
} from 'react-native';
|
||||
import { supabase } from '../utils/supabase';
|
||||
import { Session } from '@supabase/supabase-js';
|
||||
import { type Session } from '@supabase/supabase-js';
|
||||
import { useTheme } from '../utils/themeContext';
|
||||
|
||||
interface UserRole {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
ScrollView,
|
||||
} from 'react-native';
|
||||
import { supabase } from '../utils/supabase';
|
||||
import { Session } from '@supabase/supabase-js';
|
||||
import { type Session } from '@supabase/supabase-js';
|
||||
import { useTheme } from '../utils/themeContext';
|
||||
import { useRouter } from 'expo-router';
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ interface CreateTeamProps {
|
|||
}
|
||||
|
||||
export default function CreateTeam({ onTeamCreated }: CreateTeamProps) {
|
||||
const router = useRouter();
|
||||
const _router = useRouter();
|
||||
const [session, setSession] = useState<Session | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [organizations, setOrganizations] = useState<Organization[]>([]);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { View, Text, TouchableOpacity, ActivityIndicator } from 'react-native';
|
|||
import { FontAwesome5 } from '@expo/vector-icons';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { supabase } from '../utils/supabase';
|
||||
import { Session } from '@supabase/supabase-js';
|
||||
import { type Session } from '@supabase/supabase-js';
|
||||
import { useTheme, lightColors, darkColors } from '../utils/themeContext';
|
||||
|
||||
export default function DashboardStats() {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { forwardRef } from 'react';
|
|||
import { Pressable, StyleSheet } from 'react-native';
|
||||
|
||||
export const HeaderButton = forwardRef<typeof Pressable, { onPress?: () => void }>(
|
||||
({ onPress }, ref) => {
|
||||
({ onPress }, _ref) => {
|
||||
return (
|
||||
<Pressable onPress={onPress}>
|
||||
{({ pressed }) => (
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'rea
|
|||
import { View, Text, FlatList, TouchableOpacity, ActivityIndicator, Alert } from 'react-native';
|
||||
import { FontAwesome5 } from '@expo/vector-icons';
|
||||
import { supabase } from '../utils/supabase';
|
||||
import { Session } from '@supabase/supabase-js';
|
||||
import { type Session } from '@supabase/supabase-js';
|
||||
import { useTheme } from '../utils/themeContext';
|
||||
import { useRouter } from 'expo-router';
|
||||
|
||||
|
|
@ -67,6 +67,7 @@ const OrganizationList = forwardRef<OrganizationListRef, OrganizationListProps>(
|
|||
});
|
||||
|
||||
return () => subscription.unsubscribe();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
async function fetchUserOrganizations(userId: string) {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
ActivityIndicator,
|
||||
} from 'react-native';
|
||||
import { supabase } from '../utils/supabase';
|
||||
import { Session } from '@supabase/supabase-js';
|
||||
import { type Session } from '@supabase/supabase-js';
|
||||
import { useTheme } from '../utils/themeContext';
|
||||
|
||||
interface User {
|
||||
|
|
@ -464,133 +464,3 @@ export default function SendMana() {
|
|||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: 'white',
|
||||
borderRadius: 10,
|
||||
padding: 20,
|
||||
marginBottom: 20,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
elevation: 3,
|
||||
},
|
||||
header: {
|
||||
fontSize: 22,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 20,
|
||||
color: '#333',
|
||||
},
|
||||
creditInfo: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#f0f8ff',
|
||||
padding: 15,
|
||||
borderRadius: 8,
|
||||
marginBottom: 20,
|
||||
},
|
||||
creditLabel: {
|
||||
fontSize: 16,
|
||||
fontWeight: '500',
|
||||
color: '#333',
|
||||
},
|
||||
creditAmount: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
color: '#0055FF',
|
||||
marginLeft: 10,
|
||||
},
|
||||
formGroup: {
|
||||
marginBottom: 15,
|
||||
},
|
||||
label: {
|
||||
fontSize: 16,
|
||||
marginBottom: 8,
|
||||
fontWeight: '500',
|
||||
color: '#333',
|
||||
},
|
||||
input: {
|
||||
height: 50,
|
||||
borderWidth: 1,
|
||||
borderColor: '#ddd',
|
||||
borderRadius: 8,
|
||||
paddingHorizontal: 15,
|
||||
backgroundColor: '#f9f9f9',
|
||||
fontSize: 16,
|
||||
},
|
||||
searchContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
searchInput: {
|
||||
flex: 1,
|
||||
height: 50,
|
||||
borderWidth: 1,
|
||||
borderColor: '#ddd',
|
||||
borderRadius: 8,
|
||||
paddingHorizontal: 15,
|
||||
backgroundColor: '#f9f9f9',
|
||||
fontSize: 16,
|
||||
},
|
||||
searchButton: {
|
||||
backgroundColor: '#0055FF',
|
||||
height: 50,
|
||||
paddingHorizontal: 15,
|
||||
borderRadius: 8,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginLeft: 10,
|
||||
},
|
||||
searchButtonText: {
|
||||
color: 'white',
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
userCard: {
|
||||
backgroundColor: '#f0f8ff',
|
||||
padding: 15,
|
||||
borderRadius: 8,
|
||||
marginBottom: 20,
|
||||
borderLeftWidth: 4,
|
||||
borderLeftColor: '#0055FF',
|
||||
},
|
||||
userCardTitle: {
|
||||
fontSize: 14,
|
||||
color: '#666',
|
||||
marginBottom: 5,
|
||||
},
|
||||
userCardEmail: {
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
color: '#333',
|
||||
},
|
||||
userCardName: {
|
||||
fontSize: 14,
|
||||
color: '#666',
|
||||
marginTop: 5,
|
||||
},
|
||||
sendButton: {
|
||||
backgroundColor: '#0055FF',
|
||||
height: 50,
|
||||
borderRadius: 8,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginTop: 10,
|
||||
},
|
||||
sendButtonText: {
|
||||
color: 'white',
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
disabledButton: {
|
||||
backgroundColor: '#ccc',
|
||||
},
|
||||
notLoggedIn: {
|
||||
fontSize: 16,
|
||||
color: '#666',
|
||||
textAlign: 'center',
|
||||
padding: 20,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
} from 'react-native';
|
||||
import { FontAwesome5 } from '@expo/vector-icons';
|
||||
import { supabase } from '../utils/supabase';
|
||||
import { Session } from '@supabase/supabase-js';
|
||||
import { type Session } from '@supabase/supabase-js';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { useTheme } from '../utils/themeContext';
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@ import {
|
|||
} from 'react-native';
|
||||
import { FontAwesome5 } from '@expo/vector-icons';
|
||||
import { supabase } from '../utils/supabase';
|
||||
import { Session } from '@supabase/supabase-js';
|
||||
import { useLocalSearchParams } from 'expo-router';
|
||||
import { type Session } from '@supabase/supabase-js';
|
||||
import { useTheme } from '../utils/themeContext';
|
||||
|
||||
interface TeamMember {
|
||||
|
|
@ -45,7 +44,7 @@ export default function TeamMembers({ teamId }: TeamMembersProps) {
|
|||
const [members, setMembers] = useState<TeamMember[]>([]);
|
||||
const [newMemberEmail, setNewMemberEmail] = useState('');
|
||||
const [inviting, setInviting] = useState(false);
|
||||
const [userRole, setUserRole] = useState<string | null>(null);
|
||||
const [_userRole, setUserRole] = useState<string | null>(null);
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
const { isDarkMode } = useTheme();
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
|
@ -95,6 +94,7 @@ export default function TeamMembers({ teamId }: TeamMembersProps) {
|
|||
});
|
||||
|
||||
return () => subscription.unsubscribe();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [teamId]);
|
||||
|
||||
async function checkUserRole(userId: string, teamId: string) {
|
||||
|
|
|
|||
20
apps/manacore/apps/mobile/eslint.config.mjs
Normal file
20
apps/manacore/apps/mobile/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// @ts-check
|
||||
import { baseConfig, typescriptConfig, reactConfig, prettierConfig } from '@manacore/eslint-config';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: [
|
||||
'dist/**',
|
||||
'.expo/**',
|
||||
'node_modules/**',
|
||||
'android/**',
|
||||
'ios/**',
|
||||
'metro.config.js',
|
||||
'tailwind.config.js',
|
||||
],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...reactConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
|
|
@ -10,8 +10,8 @@
|
|||
"build:preview": "eas build --profile preview",
|
||||
"build:prod": "eas build --profile production",
|
||||
"prebuild": "expo prebuild",
|
||||
"lint": "eslint \"**/*.{js,jsx,ts,tsx}\" && prettier -c \"**/*.{js,jsx,ts,tsx,json}\"",
|
||||
"format": "eslint \"**/*.{js,jsx,ts,tsx}\" --fix && prettier \"**/*.{js,jsx,ts,tsx,json}\" --write",
|
||||
"lint": "eslint .",
|
||||
"format": "eslint . --fix",
|
||||
"web": "expo start --web --port 19006",
|
||||
"web:dev": "expo start --web --port 19006"
|
||||
},
|
||||
|
|
@ -47,7 +47,7 @@
|
|||
"@types/react-dom": "^19.2.3",
|
||||
"@typescript-eslint/eslint-plugin": "^7.7.0",
|
||||
"@typescript-eslint/parser": "^7.7.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-config-universe": "^12.0.1",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-tailwindcss": "^0.5.11",
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ interface StorageData {
|
|||
|
||||
class HybridStorageService {
|
||||
private memoryStorage: StorageData = {};
|
||||
private isAsyncStorageAvailable: boolean = false;
|
||||
private isAsyncStorageAvailable = false;
|
||||
|
||||
constructor() {
|
||||
// Check if we're in an environment where AsyncStorage is available
|
||||
|
|
@ -34,7 +34,7 @@ class HybridStorageService {
|
|||
if (this.isAsyncStorageAvailable) {
|
||||
await this.syncFromAsyncStorage();
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
console.warn('AsyncStorage not available, falling back to memory storage');
|
||||
this.isAsyncStorageAvailable = false;
|
||||
}
|
||||
|
|
|
|||
17
apps/manacore/apps/web/eslint.config.js
Normal file
17
apps/manacore/apps/web/eslint.config.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// @ts-check
|
||||
import {
|
||||
baseConfig,
|
||||
typescriptConfig,
|
||||
svelteConfig,
|
||||
prettierConfig,
|
||||
} from '@manacore/eslint-config';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', '.svelte-kit/**', 'node_modules/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...svelteConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
|
|
@ -33,12 +33,16 @@
|
|||
|
||||
// Navigation position handler
|
||||
async function handleNavPositionChange(position: NavPosition) {
|
||||
await userSettings.updateGlobal({ nav: { ...userSettings.globalSettings.nav, desktopPosition: position } });
|
||||
await userSettings.updateGlobal({
|
||||
nav: { ...userSettings.globalSettings.nav, desktopPosition: position },
|
||||
});
|
||||
}
|
||||
|
||||
// Sidebar collapsed handler
|
||||
async function handleSidebarChange(collapsed: boolean) {
|
||||
await userSettings.updateGlobal({ nav: { ...userSettings.globalSettings.nav, sidebarCollapsed: collapsed } });
|
||||
await userSettings.updateGlobal({
|
||||
nav: { ...userSettings.globalSettings.nav, sidebarCollapsed: collapsed },
|
||||
});
|
||||
}
|
||||
|
||||
// Theme mode handler
|
||||
|
|
@ -48,7 +52,9 @@
|
|||
|
||||
// Color scheme handler
|
||||
async function handleColorSchemeChange(colorScheme: string) {
|
||||
await userSettings.updateGlobal({ theme: { ...userSettings.globalSettings.theme, colorScheme } });
|
||||
await userSettings.updateGlobal({
|
||||
theme: { ...userSettings.globalSettings.theme, colorScheme },
|
||||
});
|
||||
}
|
||||
|
||||
// Locale handler
|
||||
|
|
@ -202,11 +208,14 @@
|
|||
<div class="flex items-center justify-between py-3 border-b border-border">
|
||||
<div>
|
||||
<p class="font-medium">Position (Desktop)</p>
|
||||
<p class="text-sm text-muted-foreground">Position der Navigation auf großen Bildschirmen</p>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Position der Navigation auf großen Bildschirmen
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
class="px-4 py-2 text-sm font-medium rounded-lg transition-colors {userSettings.globalSettings.nav.desktopPosition === 'top'
|
||||
class="px-4 py-2 text-sm font-medium rounded-lg transition-colors {userSettings
|
||||
.globalSettings.nav.desktopPosition === 'top'
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'bg-surface-hover hover:bg-surface-hover/80'}"
|
||||
onclick={() => handleNavPositionChange('top')}
|
||||
|
|
@ -214,7 +223,8 @@
|
|||
Oben
|
||||
</button>
|
||||
<button
|
||||
class="px-4 py-2 text-sm font-medium rounded-lg transition-colors {userSettings.globalSettings.nav.desktopPosition === 'bottom'
|
||||
class="px-4 py-2 text-sm font-medium rounded-lg transition-colors {userSettings
|
||||
.globalSettings.nav.desktopPosition === 'bottom'
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'bg-surface-hover hover:bg-surface-hover/80'}"
|
||||
onclick={() => handleNavPositionChange('bottom')}
|
||||
|
|
@ -230,13 +240,16 @@
|
|||
<p class="text-sm text-muted-foreground">Standard-Zustand der Sidebar</p>
|
||||
</div>
|
||||
<button
|
||||
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors {userSettings.globalSettings.nav.sidebarCollapsed
|
||||
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors {userSettings
|
||||
.globalSettings.nav.sidebarCollapsed
|
||||
? 'bg-primary'
|
||||
: 'bg-gray-200 dark:bg-gray-700'}"
|
||||
onclick={() => handleSidebarChange(!userSettings.globalSettings.nav.sidebarCollapsed)}
|
||||
onclick={() =>
|
||||
handleSidebarChange(!userSettings.globalSettings.nav.sidebarCollapsed)}
|
||||
>
|
||||
<span
|
||||
class="inline-block h-4 w-4 transform rounded-full bg-white transition-transform {userSettings.globalSettings.nav.sidebarCollapsed
|
||||
class="inline-block h-4 w-4 transform rounded-full bg-white transition-transform {userSettings
|
||||
.globalSettings.nav.sidebarCollapsed
|
||||
? 'translate-x-6'
|
||||
: 'translate-x-1'}"
|
||||
></span>
|
||||
|
|
@ -257,7 +270,8 @@
|
|||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
class="px-3 py-2 text-sm font-medium rounded-lg transition-colors {userSettings.globalSettings.theme.mode === 'light'
|
||||
class="px-3 py-2 text-sm font-medium rounded-lg transition-colors {userSettings
|
||||
.globalSettings.theme.mode === 'light'
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'bg-surface-hover hover:bg-surface-hover/80'}"
|
||||
onclick={() => handleThemeModeChange('light')}
|
||||
|
|
@ -265,7 +279,8 @@
|
|||
Hell
|
||||
</button>
|
||||
<button
|
||||
class="px-3 py-2 text-sm font-medium rounded-lg transition-colors {userSettings.globalSettings.theme.mode === 'dark'
|
||||
class="px-3 py-2 text-sm font-medium rounded-lg transition-colors {userSettings
|
||||
.globalSettings.theme.mode === 'dark'
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'bg-surface-hover hover:bg-surface-hover/80'}"
|
||||
onclick={() => handleThemeModeChange('dark')}
|
||||
|
|
@ -273,7 +288,8 @@
|
|||
Dunkel
|
||||
</button>
|
||||
<button
|
||||
class="px-3 py-2 text-sm font-medium rounded-lg transition-colors {userSettings.globalSettings.theme.mode === 'system'
|
||||
class="px-3 py-2 text-sm font-medium rounded-lg transition-colors {userSettings
|
||||
.globalSettings.theme.mode === 'system'
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'bg-surface-hover hover:bg-surface-hover/80'}"
|
||||
onclick={() => handleThemeModeChange('system')}
|
||||
|
|
@ -289,14 +305,10 @@
|
|||
<p class="text-sm text-muted-foreground">Akzentfarbe der Benutzeroberfläche</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
{#each [
|
||||
{ id: 'ocean', label: 'Ozean', color: 'bg-blue-500' },
|
||||
{ id: 'forest', label: 'Wald', color: 'bg-green-500' },
|
||||
{ id: 'sunset', label: 'Sonnenuntergang', color: 'bg-orange-500' },
|
||||
{ id: 'lavender', label: 'Lavendel', color: 'bg-purple-500' }
|
||||
] as scheme}
|
||||
{#each [{ id: 'ocean', label: 'Ozean', color: 'bg-blue-500' }, { id: 'forest', label: 'Wald', color: 'bg-green-500' }, { id: 'sunset', label: 'Sonnenuntergang', color: 'bg-orange-500' }, { id: 'lavender', label: 'Lavendel', color: 'bg-purple-500' }] as scheme}
|
||||
<button
|
||||
class="w-8 h-8 rounded-full transition-all {scheme.color} {userSettings.globalSettings.theme.colorScheme === scheme.id
|
||||
class="w-8 h-8 rounded-full transition-all {scheme.color} {userSettings
|
||||
.globalSettings.theme.colorScheme === scheme.id
|
||||
? 'ring-2 ring-offset-2 ring-primary'
|
||||
: 'hover:scale-110'}"
|
||||
onclick={() => handleColorSchemeChange(scheme.id)}
|
||||
|
|
@ -319,15 +331,10 @@
|
|||
<p class="text-sm text-muted-foreground">Sprache der Benutzeroberfläche</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
{#each [
|
||||
{ id: 'de', label: 'DE' },
|
||||
{ id: 'en', label: 'EN' },
|
||||
{ id: 'fr', label: 'FR' },
|
||||
{ id: 'es', label: 'ES' },
|
||||
{ id: 'it', label: 'IT' }
|
||||
] as lang}
|
||||
{#each [{ id: 'de', label: 'DE' }, { id: 'en', label: 'EN' }, { id: 'fr', label: 'FR' }, { id: 'es', label: 'ES' }, { id: 'it', label: 'IT' }] as lang}
|
||||
<button
|
||||
class="px-3 py-2 text-sm font-medium rounded-lg transition-colors {userSettings.globalSettings.locale === lang.id
|
||||
class="px-3 py-2 text-sm font-medium rounded-lg transition-colors {userSettings
|
||||
.globalSettings.locale === lang.id
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'bg-surface-hover hover:bg-surface-hover/80'}"
|
||||
onclick={() => handleLocaleChange(lang.id)}
|
||||
|
|
@ -342,7 +349,9 @@
|
|||
|
||||
{#if userSettings.syncing}
|
||||
<div class="mt-4 flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
|
||||
<div
|
||||
class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"
|
||||
></div>
|
||||
<span>Einstellungen werden synchronisiert...</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,17 @@
|
|||
// @ts-check
|
||||
import eslint from '@eslint/js';
|
||||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
||||
import globals from 'globals';
|
||||
import tseslint from 'typescript-eslint';
|
||||
import {
|
||||
baseConfig,
|
||||
typescriptConfig,
|
||||
nestjsConfig,
|
||||
prettierConfig,
|
||||
} from '@manacore/eslint-config';
|
||||
|
||||
export default tseslint.config(
|
||||
{
|
||||
ignores: ['eslint.config.mjs'],
|
||||
},
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
eslintPluginPrettierRecommended,
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.jest,
|
||||
},
|
||||
sourceType: 'commonjs',
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-floating-promises': 'warn',
|
||||
'@typescript-eslint/no-unsafe-argument': 'warn'
|
||||
},
|
||||
},
|
||||
);
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', 'node_modules/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...nestjsConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
/* eslint-env node */
|
||||
const { defineConfig } = require('eslint/config');
|
||||
const expoConfig = require('eslint-config-expo/flat');
|
||||
|
||||
module.exports = defineConfig([
|
||||
expoConfig,
|
||||
{
|
||||
ignores: ['dist/*'],
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'react/display-name': 'off',
|
||||
},
|
||||
},
|
||||
]);
|
||||
12
apps/manadeck/apps/mobile/eslint.config.mjs
Normal file
12
apps/manadeck/apps/mobile/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// @ts-check
|
||||
import { baseConfig, typescriptConfig, reactConfig, prettierConfig } from '@manacore/eslint-config';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', '.expo/**', 'node_modules/**', 'android/**', 'ios/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...reactConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
|
|
@ -12,8 +12,8 @@
|
|||
"build:preview": "eas build --profile preview",
|
||||
"build:prod": "eas build --profile production",
|
||||
"prebuild": "expo prebuild",
|
||||
"lint": "eslint \"**/*.{js,jsx,ts,tsx}\" && prettier -c \"**/*.{js,jsx,ts,tsx,json}\"",
|
||||
"format": "eslint \"**/*.{js,jsx,ts,tsx}\" --fix && prettier \"**/*.{js,jsx,ts,tsx,json}\" --write",
|
||||
"lint": "eslint .",
|
||||
"format": "eslint . --fix",
|
||||
"web": "expo start --web"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
|||
17
apps/manadeck/apps/web/eslint.config.js
Normal file
17
apps/manadeck/apps/web/eslint.config.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// @ts-check
|
||||
import {
|
||||
baseConfig,
|
||||
typescriptConfig,
|
||||
svelteConfig,
|
||||
prettierConfig,
|
||||
} from '@manacore/eslint-config';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', '.svelte-kit/**', 'node_modules/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...svelteConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
|
|
@ -9,7 +9,8 @@
|
|||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^7.0.0",
|
||||
|
|
|
|||
|
|
@ -43,8 +43,7 @@
|
|||
<div class="flex gap-2">
|
||||
{#each ['light', 'dark', 'system'] as mode}
|
||||
<button
|
||||
class="px-4 py-2 text-sm font-medium rounded-lg transition-colors {theme.mode ===
|
||||
mode
|
||||
class="px-4 py-2 text-sm font-medium rounded-lg transition-colors {theme.mode === mode
|
||||
? 'bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]'
|
||||
: 'bg-[hsl(var(--muted))] hover:bg-[hsl(var(--muted))]/80 text-[hsl(var(--foreground))]'}"
|
||||
onclick={() => theme.setMode(mode as 'light' | 'dark' | 'system')}
|
||||
|
|
|
|||
12
apps/memoro/apps/mobile/eslint.config.mjs
Normal file
12
apps/memoro/apps/mobile/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// @ts-check
|
||||
import { baseConfig, typescriptConfig, reactConfig, prettierConfig } from '@manacore/eslint-config';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', '.expo/**', 'node_modules/**', 'android/**', 'ios/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...reactConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
17
apps/nutriphi/apps/backend/eslint.config.mjs
Normal file
17
apps/nutriphi/apps/backend/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// @ts-check
|
||||
import {
|
||||
baseConfig,
|
||||
typescriptConfig,
|
||||
nestjsConfig,
|
||||
prettierConfig,
|
||||
} from '@manacore/eslint-config';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', 'node_modules/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...nestjsConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
12
apps/nutriphi/apps/mobile/eslint.config.mjs
Normal file
12
apps/nutriphi/apps/mobile/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// @ts-check
|
||||
import { baseConfig, typescriptConfig, reactConfig, prettierConfig } from '@manacore/eslint-config';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', '.expo/**', 'node_modules/**', 'android/**', 'ios/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...reactConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
17
apps/picture/apps/backend/eslint.config.mjs
Normal file
17
apps/picture/apps/backend/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// @ts-check
|
||||
import {
|
||||
baseConfig,
|
||||
typescriptConfig,
|
||||
nestjsConfig,
|
||||
prettierConfig,
|
||||
} from '@manacore/eslint-config';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', 'node_modules/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...nestjsConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
|
|
@ -10,7 +10,6 @@
|
|||
"preview": "astro preview",
|
||||
"astro": "astro",
|
||||
"type-check": "astro check",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"format": "prettier --write .",
|
||||
"clean": "rm -rf dist .astro node_modules"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
/* eslint-env node */
|
||||
const { defineConfig } = require('eslint/config');
|
||||
const expoConfig = require('eslint-config-expo/flat');
|
||||
|
||||
module.exports = defineConfig([
|
||||
expoConfig,
|
||||
{
|
||||
ignores: ['dist/*'],
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'react/display-name': 'off',
|
||||
},
|
||||
},
|
||||
]);
|
||||
12
apps/picture/apps/mobile/eslint.config.mjs
Normal file
12
apps/picture/apps/mobile/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// @ts-check
|
||||
import { baseConfig, typescriptConfig, reactConfig, prettierConfig } from '@manacore/eslint-config';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', '.expo/**', 'node_modules/**', 'android/**', 'ios/**'],
|
||||
},
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...reactConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
|
|
@ -11,8 +11,8 @@
|
|||
"build:preview": "eas build --profile preview",
|
||||
"build:prod": "eas build --profile production",
|
||||
"prebuild": "expo prebuild",
|
||||
"lint": "eslint \"**/*.{js,jsx,ts,tsx}\" && prettier -c \"**/*.{js,jsx,ts,tsx,json}\"",
|
||||
"format": "eslint \"**/*.{js,jsx,ts,tsx}\" --fix && prettier \"**/*.{js,jsx,ts,tsx,json}\" --write",
|
||||
"lint": "eslint .",
|
||||
"format": "eslint . --fix",
|
||||
"type-check": "echo 'Skipping type-check: @picture/mobile needs theme system migration'",
|
||||
"postinstall": "patch-package && ./scripts/build-workspace-deps.sh"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,41 +1,17 @@
|
|||
import prettier from 'eslint-config-prettier';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { includeIgnoreFile } from '@eslint/compat';
|
||||
import js from '@eslint/js';
|
||||
import svelte from 'eslint-plugin-svelte';
|
||||
import { defineConfig } from 'eslint/config';
|
||||
import globals from 'globals';
|
||||
import ts from 'typescript-eslint';
|
||||
import svelteConfig from './svelte.config.js';
|
||||
// @ts-check
|
||||
import {
|
||||
baseConfig,
|
||||
typescriptConfig,
|
||||
svelteConfig,
|
||||
prettierConfig,
|
||||
} from '@manacore/eslint-config';
|
||||
|
||||
const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
|
||||
|
||||
export default defineConfig(
|
||||
includeIgnoreFile(gitignorePath),
|
||||
js.configs.recommended,
|
||||
...ts.configs.recommended,
|
||||
...svelte.configs.recommended,
|
||||
prettier,
|
||||
...svelte.configs.prettier,
|
||||
export default [
|
||||
{
|
||||
languageOptions: {
|
||||
globals: { ...globals.browser, ...globals.node },
|
||||
},
|
||||
rules: {
|
||||
// typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
|
||||
// see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
|
||||
'no-undef': 'off',
|
||||
},
|
||||
ignores: ['dist/**', '.svelte-kit/**', 'node_modules/**'],
|
||||
},
|
||||
{
|
||||
files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
extraFileExtensions: ['.svelte'],
|
||||
parser: ts.parser,
|
||||
svelteConfig,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
...baseConfig,
|
||||
...typescriptConfig,
|
||||
...svelteConfig,
|
||||
...prettierConfig,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -43,8 +43,7 @@
|
|||
<div class="flex gap-2">
|
||||
{#each ['light', 'dark', 'system'] as mode}
|
||||
<button
|
||||
class="px-4 py-2 text-sm font-medium rounded-lg transition-colors {theme.mode ===
|
||||
mode
|
||||
class="px-4 py-2 text-sm font-medium rounded-lg transition-colors {theme.mode === mode
|
||||
? 'bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]'
|
||||
: 'bg-[hsl(var(--muted))] hover:bg-[hsl(var(--muted))]/80 text-[hsl(var(--foreground))]'}"
|
||||
onclick={() => theme.setMode(mode as 'light' | 'dark' | 'system')}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@
|
|||
"build:mobile": "pnpm --filter @picture/mobile build:prod",
|
||||
"build:web": "pnpm --filter @picture/web build",
|
||||
"build:landing": "pnpm --filter @picture/landing build",
|
||||
"lint": "pnpm run --recursive lint",
|
||||
"type-check": "pnpm run --recursive type-check",
|
||||
"clean": "pnpm run --recursive clean && rm -rf node_modules",
|
||||
"android": "expo run:android",
|
||||
"ios": "expo run:ios"
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
|||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { ComponentInfo } from '../types';
|
||||
import { type ComponentInfo } from '../types';
|
||||
import { getComponentsPath, ensureDir } from './paths';
|
||||
|
||||
/**
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue