✨ Trip: attachments, archived QoL, empty lists placeholders
This commit is contained in:
parent
0a2d0d5c6f
commit
85495f55e8
@ -1,5 +1,5 @@
|
|||||||
<section class="mt-4" [class.prettyprint]="isPrinting">
|
<section class="mt-4" [class.prettyprint]="isPrinting">
|
||||||
<div class="p-4 print:p-0 flex flex-wrap items-center justify-between">
|
<div class="p-4 print:p-0 flex items-center justify-between">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<p-button text icon="pi pi-chevron-left" class="print:hidden" (click)="back()" severity="secondary" />
|
<p-button text icon="pi pi-chevron-left" class="print:hidden" (click)="back()" severity="secondary" />
|
||||||
<div class="flex flex-col max-w-[55vw] md:max-w-full">
|
<div class="flex flex-col max-w-[55vw] md:max-w-full">
|
||||||
@ -10,12 +10,24 @@
|
|||||||
trip?.days?.length }} {{ trip?.days!.length > 1 ? 'days' :
|
trip?.days?.length }} {{ trip?.days!.length > 1 ? 'days' :
|
||||||
'day'}}</span>
|
'day'}}</span>
|
||||||
<span
|
<span
|
||||||
class="bg-gray-100 text-gray-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded min-w-fit dark:bg-gray-400">{{
|
class="bg-gray-100 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded min-w-fit dark:bg-gray-400">{{
|
||||||
(totalPrice | number:'1.0-2') || '-' }} {{ trip?.currency }}</span>
|
(totalPrice | number:'1.0-2') || '-' }} {{ trip?.currency }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="hidden print:flex flex-col items-center">
|
||||||
|
<img src="favicon.png" class="size-20">
|
||||||
|
<div class="flex gap-2 items-center text-xs text-gray-500"><i class="pi pi-github"></i>itskovacs/trip</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2 print:hidden">
|
||||||
|
<div class="flex">
|
||||||
|
<p-button (click)="openMenuTripActionsItems($event)" severity="secondary" text icon="pi pi-ellipsis-h" />
|
||||||
|
<p-menu #menuTripActions [model]="menuTripActionsItems" [popup]="true" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@if (trip?.archived) {
|
@if (trip?.archived) {
|
||||||
<div class="mx-auto p-4 mt-4 md:mt-0 w-full md:w-fit text-orange-800 rounded-md bg-orange-50"
|
<div class="mx-auto p-4 mt-4 md:mt-0 w-full md:w-fit text-orange-800 rounded-md bg-orange-50"
|
||||||
[class.prettyprint]="isPrinting">
|
[class.prettyprint]="isPrinting">
|
||||||
@ -28,27 +40,12 @@
|
|||||||
<p-button text icon="pi pi-box" label="Restore" severity="success" (click)="openUnarchiveTripModal()" />
|
<p-button text icon="pi pi-box" label="Restore" severity="success" (click)="openUnarchiveTripModal()" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
|
|
||||||
<div class="hidden print:flex flex-col items-center">
|
|
||||||
<img src="favicon.png" class="size-20">
|
|
||||||
<div class="flex gap-2 items-center text-xs text-gray-500"><i class="pi pi-github"></i>itskovacs/trip</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-2 print:hidden">
|
|
||||||
@if (!trip?.archived) {
|
|
||||||
<div class="flex">
|
|
||||||
<p-button (click)="menuTripActions.toggle($event)" severity="secondary" text icon="pi pi-ellipsis-h" />
|
|
||||||
<p-menu #menuTripActions [model]="menuTripActionsItems" [popup]="true" />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (isArchivalReviewDisplayed) {
|
@if (isArchivalReviewDisplayed) {
|
||||||
<div
|
<div
|
||||||
class="m-4 whitespace-pre-line text-gray-800 dark:text-gray-200 max-h-[600px] overflow-y-auto p-4 rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800">
|
class="m-4 whitespace-pre-line text-gray-800 dark:text-gray-200 max-h-[600px] overflow-y-auto p-4 rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800">
|
||||||
{{ trip?.archival_review }}</div>
|
{{ trip?.archival_review }}</div>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="p-4 print:px-1 grid lg:grid-cols-3 gap-4 print:block" [class.prettyprint]="isPrinting">
|
<section class="p-4 print:px-1 grid lg:grid-cols-3 gap-4 print:block" [class.prettyprint]="isPrinting">
|
||||||
@ -320,8 +317,8 @@
|
|||||||
<div class="relative rounded-md shadow p-4">
|
<div class="relative rounded-md shadow p-4">
|
||||||
<p class="font-bold mb-1">Place</p>
|
<p class="font-bold mb-1">Place</p>
|
||||||
<div class="text-sm text-gray-500 truncate">{{ selectedItem.place.name }}</div>
|
<div class="text-sm text-gray-500 truncate">{{ selectedItem.place.name }}</div>
|
||||||
<div class="absolute top-2 right-2"><p-button severity="help" text icon="pi pi-pencil"
|
<div class="absolute top-2 right-2"><p-button severity="help" pTooltip="Edit place details" text
|
||||||
(click)="editPlace(selectedItem.place)" /></div>
|
icon="pi pi-pencil" (click)="editPlace(selectedItem.place)" /></div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,8 +392,8 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="bg-white rounded py-2 absolute top-1/2 -translate-y-1/2 left-0 hidden group-hover:block slide-x dark:bg-surface-900">
|
class="bg-white rounded py-2 absolute top-1/2 -translate-y-1/2 left-0 hidden group-hover:block slide-x dark:bg-surface-900">
|
||||||
<p-button [icon]="collapsedTripPlaces ? 'pi pi-chevron-down' : 'pi pi-chevron-up'" text
|
<p-button [icon]="isCollapsedTripPlaces ? 'pi pi-chevron-down' : 'pi pi-chevron-up'" text
|
||||||
(click)="collapsedTripPlaces = !collapsedTripPlaces" />
|
(click)="isCollapsedTripPlaces = !isCollapsedTripPlaces" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -414,7 +411,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (!collapsedTripPlaces) {
|
@if (!isCollapsedTripPlaces) {
|
||||||
<div [class.max-h-64!]="!isExpanded" class="max-h-[340px] overflow-y-auto">
|
<div [class.max-h-64!]="!isExpanded" class="max-h-[340px] overflow-y-auto">
|
||||||
@defer {
|
@defer {
|
||||||
@for (p of places; track p.id) {
|
@for (p of places; track p.id) {
|
||||||
@ -475,15 +472,15 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="bg-white rounded py-2 absolute top-1/2 -translate-y-1/2 left-0 hidden group-hover:block slide-x dark:bg-surface-900">
|
class="bg-white rounded py-2 absolute top-1/2 -translate-y-1/2 left-0 hidden group-hover:block slide-x dark:bg-surface-900">
|
||||||
<p-button [icon]="collapsedTripDays ? 'pi pi-chevron-down' : 'pi pi-chevron-up'" text
|
<p-button [icon]="isCollapsedTripDays ? 'pi pi-chevron-down' : 'pi pi-chevron-up'" text
|
||||||
(click)="collapsedTripDays = !collapsedTripDays" />
|
(click)="isCollapsedTripDays = !isCollapsedTripDays" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p-button icon="pi pi-plus" [disabled]="trip?.archived" (click)="addDay()" text />
|
<p-button icon="pi pi-plus" [disabled]="trip?.archived" (click)="addDay()" text />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (!collapsedTripDays) {
|
@if (!isCollapsedTripDays) {
|
||||||
<div [class.max-h-64!]="!isExpanded" class="max-h-[340px] overflow-y-auto">
|
<div [class.max-h-64!]="!isExpanded" class="max-h-[340px] overflow-y-auto">
|
||||||
@defer {
|
@defer {
|
||||||
@for (d of trip?.days; track d.id) {
|
@for (d of trip?.days; track d.id) {
|
||||||
@ -568,9 +565,9 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<p-dialog header="Share" [draggable]="false" [dismissableMask]="true" [modal]="true" [(visible)]="shareDialogVisible"
|
<p-dialog header="Share" [draggable]="false" [dismissableMask]="true" [modal]="true" [(visible)]="isShareDialogVisible"
|
||||||
[style]="{ width: '25rem' }">
|
[style]="{ width: '25rem' }">
|
||||||
@if (shareDialogVisible) {
|
@if (isShareDialogVisible) {
|
||||||
<ng-container>
|
<ng-container>
|
||||||
@if (trip?.shared) {
|
@if (trip?.shared) {
|
||||||
<div class="flex items-center flex-col gap-2">
|
<div class="flex items-center flex-col gap-2">
|
||||||
@ -596,10 +593,10 @@
|
|||||||
|
|
||||||
<p-menu #menuTripPacking [model]="menuTripPackingItems" attachTo="body" [popup]="true" />
|
<p-menu #menuTripPacking [model]="menuTripPackingItems" attachTo="body" [popup]="true" />
|
||||||
<p-dialog header="Packing list" [draggable]="false" [dismissableMask]="true" [modal]="true"
|
<p-dialog header="Packing list" [draggable]="false" [dismissableMask]="true" [modal]="true"
|
||||||
[(visible)]="packingDialogVisible" styleClass="w-[95%] md:w-[70%] lg:w-[50%]">
|
[(visible)]="isPackingDialogVisible" styleClass="w-[95%] md:w-[70%] lg:w-[50%]">
|
||||||
<section class="md:max-w-3/4 md:mx-auto max-h-[80%] md:max-h-[600px]">
|
<section class="md:max-w-3/4 md:mx-auto max-h-[80%] md:max-h-[600px]">
|
||||||
<div class="flex items-center justify-center gap-4">
|
<div class="flex items-center justify-center gap-4">
|
||||||
<p-button (click)="addPackingItem()" icon="pi pi-plus" label="Add item" text />
|
<p-button (click)="addPackingItem()" [disabled]="trip?.archived" icon="pi pi-plus" label="Add item" text />
|
||||||
<p-button (click)="menuTripPacking.toggle($event)" icon="pi pi-ellipsis-h" text />
|
<p-button (click)="menuTripPacking.toggle($event)" icon="pi pi-ellipsis-h" text />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -621,39 +618,52 @@
|
|||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
class="md:opacity-0 absolute right-0 top-1/2 -translate-y-1/2 md:group-hover:opacity-100 bg-white md:bg-gray-100 rounded">
|
class="md:opacity-0 absolute right-0 top-1/2 -translate-y-1/2 md:group-hover:opacity-100 bg-white md:bg-gray-100 rounded">
|
||||||
<p-button size="small" text icon="pi pi-trash" (click)="deletePackingItem(item)" severity="danger" />
|
<p-button size="small" [disabled]="trip?.archived" text icon="pi pi-trash" (click)="deletePackingItem(item)"
|
||||||
|
severity="danger" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
} @empty {
|
||||||
|
<div class="flex flex-col items-center justify-center mt-4 text-center select-none">
|
||||||
|
<i style="font-size: 3rem" class="pi pi-inbox mb-2 text-gray-300 dark:text-gray-700"></i>
|
||||||
|
<p class="text-sm text-gray-500 dark:text-gray-400">No item</p>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</p-dialog>
|
</p-dialog>
|
||||||
|
|
||||||
<p-dialog header="Checklist" [draggable]="false" [dismissableMask]="true" [modal]="true"
|
<p-dialog header="Checklist" [draggable]="false" [dismissableMask]="true" [modal]="true"
|
||||||
[(visible)]="checklistDialogVisible" styleClass="w-[95%] md:w-[50%] lg:w-[30%]">
|
[(visible)]="isChecklistDialogVisible" styleClass="w-[95%] md:w-[50%] lg:w-[30%]">
|
||||||
<section class="p-4 max-w-full max-h-[80%] md:max-h-[600px]">
|
<section class="p-4 max-w-full max-h-[80%] md:max-h-[600px]">
|
||||||
<div class="flex items-center justify-center gap-4">
|
<div class="flex items-center justify-center gap-4">
|
||||||
<p-button (click)="addChecklistItem()" icon="pi pi-plus" label="Add item" text />
|
<p-button (click)="addChecklistItem()" [disabled]="trip?.archived" icon="pi pi-plus" label="Add item" text />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid md:grid-cols-2 xl:grid-cols-3 gap-2 mt-2 pb-4">
|
<div class="grid md:grid-cols-2 xl:grid-cols-3 gap-2 mt-2 pb-4">
|
||||||
@for (item of checklistItems; track item.id) {
|
@for (item of checklistItems; track item.id) {
|
||||||
<div class="relative group flex items-center gap-3 rounded-md p-2 hover:bg-gray-100 dark:hover:bg-white/5">
|
<div
|
||||||
|
class="relative group flex items-center gap-3 rounded-md py-1 min-w-0 hover:bg-gray-100 dark:hover:bg-white/5">
|
||||||
<label [for]="item.id" [pTooltip]="item.text" [class.line-through]="item.checked"
|
<label [for]="item.id" [pTooltip]="item.text" [class.line-through]="item.checked"
|
||||||
class="flex items-center gap-2 w-full cursor-pointer">
|
class="flex items-center gap-2 w-full cursor-pointer">
|
||||||
<p-checkbox (onChange)="onCheckChecklistItem($event, item.id)" [binary]="true" [inputId]="item.id.toString()"
|
<p-checkbox (onChange)="onCheckChecklistItem($event, item.id)" [binary]="true"
|
||||||
[(ngModel)]="item.checked" />
|
[disabled]="!!this.trip?.archived" [inputId]="item.id.toString()" [(ngModel)]="item.checked" />
|
||||||
<div class="pr-6 md:pr-0 truncate select-none flex-1">
|
<div class="truncate select-none">
|
||||||
<span>{{ item.text }}</span>
|
<span>{{ item.text }}</span>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
class="md:opacity-0 absolute right-0 top-1/2 -translate-y-1/2 md:group-hover:opacity-100 bg-white md:bg-gray-100 rounded">
|
class="md:opacity-0 absolute right-0 top-1/2 -translate-y-1/2 md:group-hover:opacity-100 bg-white md:bg-transparent rounded">
|
||||||
<p-button size="small" text icon="pi pi-trash" (click)="deleteChecklistItem(item)" severity="danger" />
|
<p-button size="small" text icon="pi pi-trash" [disabled]="trip?.archived" (click)="deleteChecklistItem(item)"
|
||||||
|
severity="danger" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
} @empty {
|
||||||
|
<div class="col-span-full flex flex-col items-center justify-center mt-4 text-center select-none">
|
||||||
|
<i style="font-size: 3rem" class="pi pi-inbox mb-2 text-gray-300 dark:text-gray-700"></i>
|
||||||
|
<p class="text-sm text-gray-500 dark:text-gray-400">No item</p>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -685,16 +695,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
} @empty {
|
||||||
|
<div class="col-span-full flex flex-col items-center justify-center mt-4 text-center select-none">
|
||||||
|
<i style="font-size: 3rem" class="pi pi-inbox mb-2 text-gray-300 dark:text-gray-700"></i>
|
||||||
|
<p class="text-sm text-gray-500 dark:text-gray-400">No item</p>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</p-dialog>
|
</p-dialog>
|
||||||
|
|
||||||
<p-dialog header="Members" [draggable]="false" [dismissableMask]="true" [modal]="true"
|
<p-dialog header="Members" [draggable]="false" [dismissableMask]="true" [modal]="true"
|
||||||
[(visible)]="membersDialogVisible" styleClass="w-[95%] md:w-[40%] lg:w-[30%]">
|
[(visible)]="isMembersDialogVisible" styleClass="w-[95%] md:w-[40%] lg:w-[30%]">
|
||||||
<section class="p-4 max-w-full max-h-[80%] md:max-h-[600px]">
|
<section class="p-4 max-w-full max-h-[80%] md:max-h-[600px]">
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<p-button (click)="addMember()" icon="pi pi-plus" label="Member" text />
|
<p-button (click)="addMember()" [disabled]="trip?.archived" icon="pi pi-plus" label="Member" text />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="divide-y divide-gray-100 mt-4 pb-4">
|
<div class="divide-y divide-gray-100 mt-4 pb-4">
|
||||||
@ -738,7 +753,8 @@
|
|||||||
m.balance || '-' }} {{ trip?.currency }}</span>
|
m.balance || '-' }} {{ trip?.currency }}</span>
|
||||||
|
|
||||||
@if (m.invited_at) {
|
@if (m.invited_at) {
|
||||||
<p-button text (click)="deleteMember(m.user)" icon="pi pi-trash" severity="danger" />
|
<p-button text (click)="deleteMember(m.user)" [disabled]="trip?.archived" icon="pi pi-trash"
|
||||||
|
severity="danger" />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -747,6 +763,52 @@
|
|||||||
</section>
|
</section>
|
||||||
</p-dialog>
|
</p-dialog>
|
||||||
|
|
||||||
|
<p-dialog header="Attachments" [draggable]="false" [dismissableMask]="true" [modal]="true"
|
||||||
|
[(visible)]="isAttachmentsDialogVisible" styleClass="w-[95%] md:w-[70%] lg:w-[50%]">
|
||||||
|
<section class="md:max-w-3/4 md:mx-auto max-h-[80%] md:max-h-[600px]">
|
||||||
|
<div class="flex items-center justify-center gap-4">
|
||||||
|
<p-button (click)="fileUpload.click()" [disabled]="trip?.archived" icon="pi pi-file-import" label="Add document"
|
||||||
|
text />
|
||||||
|
<input type="file" style="display: none;" (change)="onFileUploadInputChange($event)" #fileUpload>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid md:grid-cols-2 xl:grid-cols-3 gap-2 mt-2 pb-4">
|
||||||
|
@for (attachment of trip?.attachments; track attachment.id) {
|
||||||
|
<div (click)="downloadAttachment(attachment)"
|
||||||
|
class="group relative cursor-pointer flex items-center gap-3 rounded-lg border border-gray-200 bg-white hover:bg-gray-100 dark:hover:bg-white/5 p-3 transition-all dark:border-gray-800 dark:bg-gray-950">
|
||||||
|
<div
|
||||||
|
class="group relative flex h-10 w-10 shrink-0 cursor-pointer items-center justify-center rounded-md bg-red-100 transition-colors dark:bg-red-900">
|
||||||
|
<i class="pi pi-file text-red-600 transition-opacity group-hover:opacity-0 dark:text-red-400"></i>
|
||||||
|
<i
|
||||||
|
class="pi pi-arrow-down absolute -translate-y-4 text-red-600 opacity-0 transition-none group-hover:translate-y-0 group-hover:opacity-100 group-hover:transition-all group-hover:duration-300 dark:text-red-400"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<div class="truncate font-mono text-md text-gray-900 dark:text-gray-100">{{ attachment.filename }}</div>
|
||||||
|
<div class="flex items-center gap-2 mt-1 text-xs font-mono text-gray-500 dark:text-gray-400">
|
||||||
|
<span>{{ attachment.file_size | fileSize }}</span>
|
||||||
|
<span
|
||||||
|
class="bg-gray-100 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded min-w-fit dark:bg-gray-400">{{
|
||||||
|
attachment.uploaded_by }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="md:opacity-0 absolute right-2 top-1/2 -translate-y-1/2 translate-x-2 group-hover:translate-x-0 md:group-hover:opacity-100 group-hover:transition-all group-hover:duration-300 bg-white md:bg-transparent rounded">
|
||||||
|
<p-button text icon="pi pi-trash" [disabled]="trip?.archived"
|
||||||
|
(click)="deleteAttachment(attachment.id); $event.stopPropagation()" severity="danger" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
} @empty {
|
||||||
|
<div class="col-span-full flex flex-col items-center justify-center mt-4 text-center select-none">
|
||||||
|
<i style="font-size: 3rem" class="pi pi-inbox mb-2 text-gray-300 dark:text-gray-700"></i>
|
||||||
|
<p class="text-sm text-gray-500 dark:text-gray-400">No attachments</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</p-dialog>
|
||||||
|
|
||||||
@if (isPrinting) {
|
@if (isPrinting) {
|
||||||
<section class="hidden print:block">
|
<section class="hidden print:block">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { AfterViewInit, Component } from '@angular/core';
|
import { AfterViewInit, Component, ViewChild } from '@angular/core';
|
||||||
import { ApiService } from '../../services/api.service';
|
import { ApiService } from '../../services/api.service';
|
||||||
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { ButtonModule } from 'primeng/button';
|
import { ButtonModule } from 'primeng/button';
|
||||||
@ -17,6 +17,7 @@ import {
|
|||||||
PackingItem,
|
PackingItem,
|
||||||
ChecklistItem,
|
ChecklistItem,
|
||||||
TripMember,
|
TripMember,
|
||||||
|
TripAttachment,
|
||||||
} from '../../types/trip';
|
} from '../../types/trip';
|
||||||
import { Place } from '../../types/poi';
|
import { Place } from '../../types/poi';
|
||||||
import { createMap, placeToMarker, createClusterGroup, tripDayMarker, gpxToPolyline } from '../../shared/map';
|
import { createMap, placeToMarker, createClusterGroup, tripDayMarker, gpxToPolyline } from '../../shared/map';
|
||||||
@ -32,7 +33,7 @@ import { UtilsService } from '../../services/utils.service';
|
|||||||
import { TripCreateModalComponent } from '../../modals/trip-create-modal/trip-create-modal.component';
|
import { TripCreateModalComponent } from '../../modals/trip-create-modal/trip-create-modal.component';
|
||||||
import { AsyncPipe, CommonModule, DecimalPipe } from '@angular/common';
|
import { AsyncPipe, CommonModule, DecimalPipe } from '@angular/common';
|
||||||
import { MenuItem } from 'primeng/api';
|
import { MenuItem } from 'primeng/api';
|
||||||
import { MenuModule } from 'primeng/menu';
|
import { Menu, MenuModule } from 'primeng/menu';
|
||||||
import { LinkifyPipe } from '../../shared/linkify.pipe';
|
import { LinkifyPipe } from '../../shared/linkify.pipe';
|
||||||
import { PlaceCreateModalComponent } from '../../modals/place-create-modal/place-create-modal.component';
|
import { PlaceCreateModalComponent } from '../../modals/place-create-modal/place-create-modal.component';
|
||||||
import { Settings } from '../../types/settings';
|
import { Settings } from '../../types/settings';
|
||||||
@ -49,6 +50,7 @@ import { calculateDistanceBetween } from '../../shared/haversine';
|
|||||||
import { orderByPipe } from '../../shared/order-by.pipe';
|
import { orderByPipe } from '../../shared/order-by.pipe';
|
||||||
import { TripNotesModalComponent } from '../../modals/trip-notes-modal/trip-notes-modal.component';
|
import { TripNotesModalComponent } from '../../modals/trip-notes-modal/trip-notes-modal.component';
|
||||||
import { TripArchiveModalComponent } from '../../modals/trip-archive-modal/trip-archive-modal.component';
|
import { TripArchiveModalComponent } from '../../modals/trip-archive-modal/trip-archive-modal.component';
|
||||||
|
import { FileSizePipe } from '../../shared/filesize.pipe';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-trip',
|
selector: 'app-trip',
|
||||||
@ -72,11 +74,13 @@ import { TripArchiveModalComponent } from '../../modals/trip-archive-modal/trip-
|
|||||||
MultiSelectModule,
|
MultiSelectModule,
|
||||||
CheckboxModule,
|
CheckboxModule,
|
||||||
orderByPipe,
|
orderByPipe,
|
||||||
|
FileSizePipe,
|
||||||
],
|
],
|
||||||
templateUrl: './trip.component.html',
|
templateUrl: './trip.component.html',
|
||||||
styleUrls: ['./trip.component.scss'],
|
styleUrls: ['./trip.component.scss'],
|
||||||
})
|
})
|
||||||
export class TripComponent implements AfterViewInit {
|
export class TripComponent implements AfterViewInit {
|
||||||
|
@ViewChild('menuTripActions') menuTripActions!: Menu;
|
||||||
tripSharedURL$?: Observable<string>;
|
tripSharedURL$?: Observable<string>;
|
||||||
statuses: TripStatus[] = [];
|
statuses: TripStatus[] = [];
|
||||||
trip?: Trip;
|
trip?: Trip;
|
||||||
@ -87,21 +91,22 @@ export class TripComponent implements AfterViewInit {
|
|||||||
isPrinting = false;
|
isPrinting = false;
|
||||||
isArchivalReviewDisplayed = false;
|
isArchivalReviewDisplayed = false;
|
||||||
|
|
||||||
|
totalPrice = 0;
|
||||||
isMapFullscreen = false;
|
isMapFullscreen = false;
|
||||||
isMapFullscreenDays = false;
|
isMapFullscreenDays = false;
|
||||||
totalPrice = 0;
|
isCollapsedTripDays = false;
|
||||||
collapsedTripDays = false;
|
isCollapsedTripPlaces = false;
|
||||||
collapsedTripPlaces = false;
|
isShareDialogVisible = false;
|
||||||
shareDialogVisible = false;
|
isPackingDialogVisible = false;
|
||||||
packingDialogVisible = false;
|
isMembersDialogVisible = false;
|
||||||
|
isAttachmentsDialogVisible = false;
|
||||||
|
isChecklistDialogVisible = false;
|
||||||
isExpanded = false;
|
isExpanded = false;
|
||||||
isFilteringMode = false;
|
isFilteringMode = false;
|
||||||
packingList: PackingItem[] = [];
|
packingList: PackingItem[] = [];
|
||||||
dispPackingList: Record<string, PackingItem[]> = {};
|
dispPackingList: Record<string, PackingItem[]> = {};
|
||||||
checklistDialogVisible = false;
|
|
||||||
checklistItems: ChecklistItem[] = [];
|
checklistItems: ChecklistItem[] = [];
|
||||||
dispchecklist: ChecklistItem[] = [];
|
dispchecklist: ChecklistItem[] = [];
|
||||||
membersDialogVisible = false;
|
|
||||||
tripMembers: TripMember[] = [];
|
tripMembers: TripMember[] = [];
|
||||||
|
|
||||||
map?: L.Map;
|
map?: L.Map;
|
||||||
@ -112,86 +117,7 @@ export class TripComponent implements AfterViewInit {
|
|||||||
tripMapAntLayer?: L.FeatureGroup;
|
tripMapAntLayer?: L.FeatureGroup;
|
||||||
tripMapAntLayerDayID?: number;
|
tripMapAntLayerDayID?: number;
|
||||||
|
|
||||||
readonly menuTripActionsItems: MenuItem[] = [
|
menuTripActionsItems: MenuItem[] = [];
|
||||||
{
|
|
||||||
label: 'Lists',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: 'Checklist',
|
|
||||||
icon: 'pi pi-list-check',
|
|
||||||
command: () => {
|
|
||||||
this.openChecklist();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Packing list',
|
|
||||||
icon: 'pi pi-briefcase',
|
|
||||||
command: () => {
|
|
||||||
this.openPackingList();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Collaboration',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: 'Members',
|
|
||||||
icon: 'pi pi-users',
|
|
||||||
command: () => {
|
|
||||||
this.openMembersDialog();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Share',
|
|
||||||
icon: 'pi pi-share-alt',
|
|
||||||
command: () => {
|
|
||||||
this.shareDialogVisible = true;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Trip',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: 'Pretty Print',
|
|
||||||
icon: 'pi pi-print',
|
|
||||||
command: () => {
|
|
||||||
this.togglePrint();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Notes',
|
|
||||||
icon: 'pi pi-info-circle',
|
|
||||||
command: () => {
|
|
||||||
this.openTripNotesModal();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Archive',
|
|
||||||
icon: 'pi pi-box',
|
|
||||||
command: () => {
|
|
||||||
this.openArchiveTripModal();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Edit',
|
|
||||||
icon: 'pi pi-pencil',
|
|
||||||
command: () => {
|
|
||||||
this.editTrip();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Delete',
|
|
||||||
icon: 'pi pi-trash',
|
|
||||||
command: () => {
|
|
||||||
this.deleteTrip();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
readonly menuTripTableActionsItems: MenuItem[] = [
|
readonly menuTripTableActionsItems: MenuItem[] = [
|
||||||
{
|
{
|
||||||
label: 'Actions',
|
label: 'Actions',
|
||||||
@ -913,6 +839,12 @@ export class TripComponent implements AfterViewInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleArchiveTrip() {
|
||||||
|
if (!this.trip) return;
|
||||||
|
if (this.trip.archived) this.openUnarchiveTripModal();
|
||||||
|
else this.openArchiveTripModal();
|
||||||
|
}
|
||||||
|
|
||||||
openArchiveTripModal() {
|
openArchiveTripModal() {
|
||||||
if (!this.trip) return;
|
if (!this.trip) return;
|
||||||
const currentArchiveStatus = this.trip?.archived;
|
const currentArchiveStatus = this.trip?.archived;
|
||||||
@ -1455,7 +1387,7 @@ export class TripComponent implements AfterViewInit {
|
|||||||
.subscribe({
|
.subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.trip!.shared = false;
|
this.trip!.shared = false;
|
||||||
this.shareDialogVisible = false;
|
this.isShareDialogVisible = false;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -1481,7 +1413,7 @@ export class TripComponent implements AfterViewInit {
|
|||||||
label: `Quick Paste (${this.utilsService.packingListToCopy.length})`,
|
label: `Quick Paste (${this.utilsService.packingListToCopy.length})`,
|
||||||
icon: 'pi pi-copy',
|
icon: 'pi pi-copy',
|
||||||
command: () => this.pastePackingList(),
|
command: () => this.pastePackingList(),
|
||||||
disabled: !this.utilsService.packingListToCopy.length,
|
disabled: this.trip?.archived || !this.utilsService.packingListToCopy.length,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -1501,7 +1433,7 @@ export class TripComponent implements AfterViewInit {
|
|||||||
this.computeDispPackingList();
|
this.computeDispPackingList();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.packingDialogVisible = true;
|
this.isPackingDialogVisible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
addPackingItem() {
|
addPackingItem() {
|
||||||
@ -1677,7 +1609,7 @@ export class TripComponent implements AfterViewInit {
|
|||||||
this.computeDispChecklistList();
|
this.computeDispChecklistList();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.checklistDialogVisible = true;
|
this.isChecklistDialogVisible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
addChecklistItem() {
|
addChecklistItem() {
|
||||||
@ -1784,7 +1716,7 @@ export class TripComponent implements AfterViewInit {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.membersDialogVisible = true;
|
this.isMembersDialogVisible = true;
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1874,7 +1806,7 @@ export class TripComponent implements AfterViewInit {
|
|||||||
'1024px': '70vw',
|
'1024px': '70vw',
|
||||||
'640px': '90vw',
|
'640px': '90vw',
|
||||||
},
|
},
|
||||||
data: this.trip?.notes,
|
data: this.trip,
|
||||||
})!;
|
})!;
|
||||||
|
|
||||||
modal.onClose.pipe(take(1)).subscribe({
|
modal.onClose.pipe(take(1)).subscribe({
|
||||||
@ -1889,4 +1821,152 @@ export class TripComponent implements AfterViewInit {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openMenuTripActionsItems(event: any) {
|
||||||
|
const lists = {
|
||||||
|
label: 'Lists',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Attachments',
|
||||||
|
icon: 'pi pi-paperclip',
|
||||||
|
command: () => {
|
||||||
|
this.openAttachmentsModal();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Checklist',
|
||||||
|
icon: 'pi pi-list-check',
|
||||||
|
command: () => {
|
||||||
|
this.openChecklist();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Packing list',
|
||||||
|
icon: 'pi pi-briefcase',
|
||||||
|
command: () => {
|
||||||
|
this.openPackingList();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const collaboration = {
|
||||||
|
label: 'Collaboration',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Members',
|
||||||
|
icon: 'pi pi-users',
|
||||||
|
command: () => {
|
||||||
|
this.openMembersDialog();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Share',
|
||||||
|
icon: 'pi pi-share-alt',
|
||||||
|
command: () => {
|
||||||
|
this.isShareDialogVisible = true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const actions = {
|
||||||
|
label: 'Trip',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Pretty Print',
|
||||||
|
icon: 'pi pi-print',
|
||||||
|
command: () => {
|
||||||
|
this.togglePrint();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Notes',
|
||||||
|
icon: 'pi pi-info-circle',
|
||||||
|
command: () => {
|
||||||
|
this.openTripNotesModal();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: this.trip?.archived ? 'Unarchive' : 'Archive',
|
||||||
|
icon: 'pi pi-box',
|
||||||
|
command: () => {
|
||||||
|
this.toggleArchiveTrip();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Edit',
|
||||||
|
icon: 'pi pi-pencil',
|
||||||
|
disabled: this.trip?.archived,
|
||||||
|
command: () => {
|
||||||
|
this.editTrip();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Delete',
|
||||||
|
icon: 'pi pi-trash',
|
||||||
|
disabled: this.trip?.archived,
|
||||||
|
command: () => {
|
||||||
|
this.deleteTrip();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
this.menuTripActionsItems = [lists, collaboration, actions];
|
||||||
|
this.menuTripActions.toggle(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
openAttachmentsModal() {
|
||||||
|
if (!this.trip) return;
|
||||||
|
this.isAttachmentsDialogVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onFileUploadInputChange(event: Event) {
|
||||||
|
if (!this.trip) return;
|
||||||
|
const input = event.target as HTMLInputElement;
|
||||||
|
if (!input.files?.length) return;
|
||||||
|
|
||||||
|
const formdata = new FormData();
|
||||||
|
formdata.append('file', input.files[0]);
|
||||||
|
|
||||||
|
this.apiService
|
||||||
|
.postTripAttachment(this.trip?.id, formdata)
|
||||||
|
.pipe(take(1))
|
||||||
|
.subscribe({
|
||||||
|
next: (attachment) => (this.trip!.attachments = [...this.trip!.attachments!, attachment]),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadAttachment(attachment: TripAttachment) {
|
||||||
|
if (!this.trip) return;
|
||||||
|
this.apiService
|
||||||
|
.downloadTripAttachment(this.trip.id, attachment.id)
|
||||||
|
.pipe(take(1))
|
||||||
|
.subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
const blob = new Blob([data], { type: 'application/pdf' });
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const anchor = document.createElement('a');
|
||||||
|
anchor.download = attachment.filename;
|
||||||
|
anchor.href = url;
|
||||||
|
|
||||||
|
document.body.appendChild(anchor);
|
||||||
|
anchor.click();
|
||||||
|
|
||||||
|
document.body.removeChild(anchor);
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteAttachment(attachmentId: number) {
|
||||||
|
if (!this.trip) return;
|
||||||
|
this.apiService
|
||||||
|
.deleteTripAttachment(this.trip.id, attachmentId)
|
||||||
|
.pipe(take(1))
|
||||||
|
.subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.trip!.attachments = this.trip?.attachments?.filter((att) => att.id != attachmentId);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user