refac
This commit is contained in:
@@ -109,6 +109,7 @@ class CalendarModel(BaseModel):
|
|||||||
name: str
|
name: str
|
||||||
color: Optional[str] = None
|
color: Optional[str] = None
|
||||||
is_default: bool = False
|
is_default: bool = False
|
||||||
|
is_system: bool = False
|
||||||
|
|
||||||
data: Optional[dict] = None
|
data: Optional[dict] = None
|
||||||
meta: Optional[dict] = None
|
meta: Optional[dict] = None
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ async def get_calendars(request: Request, user: UserModel = Depends(get_verified
|
|||||||
name='Scheduled Tasks',
|
name='Scheduled Tasks',
|
||||||
color='#8b5cf6',
|
color='#8b5cf6',
|
||||||
is_default=False,
|
is_default=False,
|
||||||
|
is_system=True,
|
||||||
created_at=now,
|
created_at=now,
|
||||||
updated_at=now,
|
updated_at=now,
|
||||||
)
|
)
|
||||||
@@ -360,12 +361,21 @@ async def update_calendar(
|
|||||||
@router.delete('/{calendar_id}/delete')
|
@router.delete('/{calendar_id}/delete')
|
||||||
async def delete_calendar(request: Request, calendar_id: str, user: UserModel = Depends(get_verified_user)):
|
async def delete_calendar(request: Request, calendar_id: str, user: UserModel = Depends(get_verified_user)):
|
||||||
await check_calendar_permission(request, user)
|
await check_calendar_permission(request, user)
|
||||||
|
|
||||||
|
# Block deletion of the virtual Scheduled Tasks calendar
|
||||||
|
if calendar_id == SCHEDULED_TASKS_CALENDAR_ID:
|
||||||
|
raise HTTPException(status_code=400, detail='System calendars cannot be deleted')
|
||||||
|
|
||||||
cal = await _check_calendar_access(calendar_id, user, 'write')
|
cal = await _check_calendar_access(calendar_id, user, 'write')
|
||||||
|
|
||||||
# Only owner/admin can delete
|
# Only owner/admin can delete
|
||||||
if cal.user_id != user.id and user.role != 'admin':
|
if cal.user_id != user.id and user.role != 'admin':
|
||||||
raise HTTPException(status_code=403, detail='Only owner can delete calendar')
|
raise HTTPException(status_code=403, detail='Only owner can delete calendar')
|
||||||
|
|
||||||
|
# Block deletion of default calendar
|
||||||
|
if cal.is_default:
|
||||||
|
raise HTTPException(status_code=400, detail='Default calendar cannot be deleted')
|
||||||
|
|
||||||
result = await Calendars.delete_calendar_by_id(calendar_id)
|
result = await Calendars.delete_calendar_by_id(calendar_id)
|
||||||
if not result:
|
if not result:
|
||||||
raise HTTPException(status_code=500, detail='Failed to delete')
|
raise HTTPException(status_code=500, detail='Failed to delete')
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export type CalendarModel = {
|
|||||||
name: string;
|
name: string;
|
||||||
color: string | null;
|
color: string | null;
|
||||||
is_default: boolean;
|
is_default: boolean;
|
||||||
|
is_system: boolean;
|
||||||
data: Record<string, any> | null;
|
data: Record<string, any> | null;
|
||||||
meta: Record<string, any> | null;
|
meta: Record<string, any> | null;
|
||||||
access_grants: any[];
|
access_grants: any[];
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from 'svelte';
|
import { getContext } from 'svelte';
|
||||||
import type { CalendarModel } from '$lib/apis/calendar';
|
import type { CalendarModel } from '$lib/apis/calendar';
|
||||||
|
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
@@ -9,8 +10,30 @@
|
|||||||
export let currentDate: Date = new Date();
|
export let currentDate: Date = new Date();
|
||||||
export let onToggle: (id: string) => void = () => {};
|
export let onToggle: (id: string) => void = () => {};
|
||||||
export let onCreateCalendar: () => void = () => {};
|
export let onCreateCalendar: () => void = () => {};
|
||||||
|
export let onDeleteCalendar: (id: string) => void = () => {};
|
||||||
export let onDateSelect: (date: Date) => void = () => {};
|
export let onDateSelect: (date: Date) => void = () => {};
|
||||||
|
|
||||||
|
// Delete confirmation state
|
||||||
|
let showDeleteConfirm = false;
|
||||||
|
let deleteTargetCalendar: CalendarModel | null = null;
|
||||||
|
|
||||||
|
function isDeletable(cal: CalendarModel): boolean {
|
||||||
|
return !cal.is_default && !cal.is_system;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDeleteClick(e: MouseEvent, cal: CalendarModel) {
|
||||||
|
e.stopPropagation();
|
||||||
|
deleteTargetCalendar = cal;
|
||||||
|
showDeleteConfirm = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmDelete() {
|
||||||
|
if (deleteTargetCalendar) {
|
||||||
|
onDeleteCalendar(deleteTargetCalendar.id);
|
||||||
|
}
|
||||||
|
deleteTargetCalendar = null;
|
||||||
|
}
|
||||||
|
|
||||||
// Mini calendar state
|
// Mini calendar state
|
||||||
$: miniMonth = currentDate.getMonth();
|
$: miniMonth = currentDate.getMonth();
|
||||||
$: miniYear = currentDate.getFullYear();
|
$: miniYear = currentDate.getFullYear();
|
||||||
@@ -68,6 +91,14 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<ConfirmDialog
|
||||||
|
bind:show={showDeleteConfirm}
|
||||||
|
title={$i18n.t('Delete Calendar')}
|
||||||
|
message={$i18n.t('This will permanently delete the calendar "{{name}}" and all its events. This action cannot be undone.', { name: deleteTargetCalendar?.name ?? '' })}
|
||||||
|
confirmLabel={$i18n.t('Delete')}
|
||||||
|
onConfirm={confirmDelete}
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<!-- Mini Month Calendar -->
|
<!-- Mini Month Calendar -->
|
||||||
<div>
|
<div>
|
||||||
@@ -148,27 +179,55 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#each calendars as cal (cal.id)}
|
{#each calendars as cal (cal.id)}
|
||||||
<button
|
<div class="group flex items-center w-full">
|
||||||
class="flex items-center gap-2 px-2 py-1 rounded-lg text-xs transition
|
<button
|
||||||
hover:bg-gray-50 dark:hover:bg-gray-800/50 w-full text-left"
|
class="flex items-center gap-2 px-2 py-1 rounded-lg text-xs transition
|
||||||
on:click={() => onToggle(cal.id)}
|
hover:bg-gray-50 dark:hover:bg-gray-800/50 flex-1 text-left min-w-0"
|
||||||
>
|
on:click={() => onToggle(cal.id)}
|
||||||
<span
|
|
||||||
class="shrink-0 size-2.5 rounded-full transition-opacity"
|
|
||||||
style="background-color: {cal.color || '#3b82f6'}; opacity: {visibleCalendarIds.has(
|
|
||||||
cal.id
|
|
||||||
)
|
|
||||||
? '1'
|
|
||||||
: '0.25'};"
|
|
||||||
></span>
|
|
||||||
<span
|
|
||||||
class="truncate flex-1 {visibleCalendarIds.has(cal.id)
|
|
||||||
? ''
|
|
||||||
: 'text-gray-400 dark:text-gray-500'}"
|
|
||||||
>
|
>
|
||||||
{cal.name}
|
<span
|
||||||
</span>
|
class="shrink-0 size-2.5 rounded-full transition-opacity"
|
||||||
</button>
|
style="background-color: {cal.color || '#3b82f6'}; opacity: {visibleCalendarIds.has(
|
||||||
|
cal.id
|
||||||
|
)
|
||||||
|
? '1'
|
||||||
|
: '0.25'};"
|
||||||
|
></span>
|
||||||
|
<span
|
||||||
|
class="truncate flex-1 {visibleCalendarIds.has(cal.id)
|
||||||
|
? ''
|
||||||
|
: 'text-gray-400 dark:text-gray-500'}"
|
||||||
|
>
|
||||||
|
{cal.name}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if isDeletable(cal)}
|
||||||
|
<button
|
||||||
|
class="shrink-0 p-1 rounded-lg opacity-0 group-hover:opacity-100
|
||||||
|
hover:bg-red-50 dark:hover:bg-red-900/20
|
||||||
|
text-gray-400 hover:text-red-500 dark:hover:text-red-400
|
||||||
|
transition-all duration-150"
|
||||||
|
title={$i18n.t('Delete calendar')}
|
||||||
|
on:click={(e) => handleDeleteClick(e, cal)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
class="size-3"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import {
|
import {
|
||||||
getCalendars,
|
getCalendars,
|
||||||
getCalendarEvents,
|
getCalendarEvents,
|
||||||
|
deleteCalendar,
|
||||||
type CalendarModel,
|
type CalendarModel,
|
||||||
type CalendarEventModel
|
type CalendarEventModel
|
||||||
} from '$lib/apis/calendar';
|
} from '$lib/apis/calendar';
|
||||||
@@ -111,6 +112,21 @@
|
|||||||
visibleCalendarIds = next;
|
visibleCalendarIds = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleDeleteCalendar(id: string) {
|
||||||
|
try {
|
||||||
|
const result = await deleteCalendar(localStorage.token, id);
|
||||||
|
if (result) {
|
||||||
|
toast.success($i18n.t('Calendar deleted'));
|
||||||
|
await loadCalendars();
|
||||||
|
await refresh();
|
||||||
|
} else {
|
||||||
|
toast.error($i18n.t('Failed to delete calendar'));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
toast.error(`${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleCreateEvent(e: CustomEvent<{ start_at: number }>) {
|
function handleCreateEvent(e: CustomEvent<{ start_at: number }>) {
|
||||||
editEvent = null;
|
editEvent = null;
|
||||||
defaultStartAt = e.detail.start_at;
|
defaultStartAt = e.detail.start_at;
|
||||||
@@ -334,6 +350,7 @@
|
|||||||
{visibleCalendarIds}
|
{visibleCalendarIds}
|
||||||
{currentDate}
|
{currentDate}
|
||||||
onToggle={toggleCalendar}
|
onToggle={toggleCalendar}
|
||||||
|
onDeleteCalendar={handleDeleteCalendar}
|
||||||
onDateSelect={handleDateSelect}
|
onDateSelect={handleDateSelect}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user