✨ Trip: export to ics or csv
This commit is contained in:
parent
f5f76f8a7b
commit
96d0fadf23
93
src/src/app/components/trip/csv.ts
Normal file
93
src/src/app/components/trip/csv.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import { FlattenedTripItem } from '../../types/trip';
|
||||||
|
import { UtilsService } from '../../services/utils.service';
|
||||||
|
|
||||||
|
export function generateTripICSFile(
|
||||||
|
tripItems: FlattenedTripItem[],
|
||||||
|
tripName: string = 'Trip Calendar',
|
||||||
|
utilsService: UtilsService,
|
||||||
|
): void {
|
||||||
|
const now = new Date();
|
||||||
|
const tsz = now.toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z';
|
||||||
|
|
||||||
|
let icsContent = [
|
||||||
|
'BEGIN:VCALENDAR',
|
||||||
|
'VERSION:2.0',
|
||||||
|
'PRODID:-//Trip//Trip Calendar//EN',
|
||||||
|
'CALSCALE:GREGORIAN',
|
||||||
|
'METHOD:PUBLISH',
|
||||||
|
`X-WR-CALNAME:${tripName}`,
|
||||||
|
'X-WR-TIMEZONE:Europe/Paris',
|
||||||
|
].join('\r\n');
|
||||||
|
|
||||||
|
if (tripItems.some((i) => !i.td_date))
|
||||||
|
utilsService.toast('warn', 'Caution', 'You have date-less days, they will not be included in your export');
|
||||||
|
|
||||||
|
tripItems.forEach((item, index) => {
|
||||||
|
if (!item.td_date) return;
|
||||||
|
|
||||||
|
const eventDate = item.td_date;
|
||||||
|
const eventTime = item.time ?? '00:00';
|
||||||
|
const [year, month, day] = eventDate.split('-');
|
||||||
|
const [hours, minutes] = eventTime.split(':');
|
||||||
|
const dtStart = `${year}${month}${day}T${hours.padStart(2, '0')}${minutes.padStart(2, '0')}00`;
|
||||||
|
|
||||||
|
const startDateTime = new Date(`${eventDate}T${eventTime}`);
|
||||||
|
const nextItemSameDay = tripItems.slice(index + 1).find((i) => i.td_date === item.td_date && i.time);
|
||||||
|
|
||||||
|
const endDateTime = nextItemSameDay?.time
|
||||||
|
? new Date(`${nextItemSameDay.td_date}T${nextItemSameDay.time}`)
|
||||||
|
: new Date(startDateTime.getTime() + 60 * 60 * 1000);
|
||||||
|
const dtEnd = endDateTime.toISOString().replace(/[-:]/g, '').split('.')[0];
|
||||||
|
|
||||||
|
const eventDescription: string[] = [];
|
||||||
|
if (item.comment) eventDescription.push(`Comment: ${item.comment}`);
|
||||||
|
if (item.place?.name) eventDescription.push(`Place: ${item.place.name}`);
|
||||||
|
|
||||||
|
const lat = item.lat ?? item.place?.lat;
|
||||||
|
const lng = item.lng ?? item.place?.lng;
|
||||||
|
if (lat && lng) {
|
||||||
|
eventDescription.push(`Coordinates: ${lat}, ${lng}`);
|
||||||
|
eventDescription.push(`GMaps: https://www.google.com/maps?q=${lat},${lng}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.price) eventDescription.push(`Price: ${item.price}€`);
|
||||||
|
|
||||||
|
const description = eventDescription.join('\\n').replace(/\n/g, '\\n');
|
||||||
|
const location = item.place?.name ?? (lat && lng ? `${lat}, ${lng}` : '');
|
||||||
|
const geo = lat && lng ? `GEO:${lat};${lng}` : '';
|
||||||
|
const uid = `Trip-${tripName.replace(/[^a-zA-Z0-9-_]/g, '_')}-item-${item.id}-${tsz}`;
|
||||||
|
|
||||||
|
icsContent +=
|
||||||
|
'\r\n' +
|
||||||
|
[
|
||||||
|
'BEGIN:VEVENT',
|
||||||
|
`UID:${uid}`,
|
||||||
|
`DTSTAMP:${tsz}`,
|
||||||
|
`DTSTART:${dtStart}`,
|
||||||
|
`DTEND:${dtEnd}`,
|
||||||
|
`SUMMARY:${escapeICSText(item.text)}`,
|
||||||
|
description ? `DESCRIPTION:${escapeICSText(description)}` : '',
|
||||||
|
location ? `LOCATION:${escapeICSText(location)}` : '',
|
||||||
|
geo,
|
||||||
|
item.status ? `STATUS:${item.status.label.toUpperCase()}` : '',
|
||||||
|
'END:VEVENT',
|
||||||
|
]
|
||||||
|
.filter((line) => line)
|
||||||
|
.join('\r\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
icsContent += '\r\n' + 'END:VCALENDAR';
|
||||||
|
const blob = new Blob([icsContent], { type: 'text/calendar;charset=utf-8' });
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = URL.createObjectURL(blob);
|
||||||
|
link.download = `${tripName.replace(/\s+/g, '_')}.ics`;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
URL.revokeObjectURL(link.href);
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeICSText(text: string): string {
|
||||||
|
if (!text) return '';
|
||||||
|
return text.replace(/\\/g, '\\\\').replace(/;/g, '\\;').replace(/,/g, '\\,').replace(/\n/g, '\\n').replace(/\r/g, '');
|
||||||
|
}
|
||||||
98
src/src/app/components/trip/ics.ts
Normal file
98
src/src/app/components/trip/ics.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { FlattenedTripItem } from '../../types/trip';
|
||||||
|
import { UtilsService } from '../../services/utils.service';
|
||||||
|
|
||||||
|
export function generateTripICSFile(
|
||||||
|
tripItems: FlattenedTripItem[],
|
||||||
|
tripName: string = 'Trip Calendar',
|
||||||
|
utilsService: UtilsService,
|
||||||
|
): void {
|
||||||
|
const now = new Date();
|
||||||
|
const tsz = now.toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z';
|
||||||
|
|
||||||
|
let icsContent = [
|
||||||
|
'BEGIN:VCALENDAR',
|
||||||
|
'VERSION:2.0',
|
||||||
|
'PRODID:-//Trip//Trip Calendar//EN',
|
||||||
|
'CALSCALE:GREGORIAN',
|
||||||
|
'METHOD:PUBLISH',
|
||||||
|
`X-WR-CALNAME:${tripName}`,
|
||||||
|
'X-WR-TIMEZONE:Europe/Paris',
|
||||||
|
].join('\r\n');
|
||||||
|
|
||||||
|
if (tripItems.some((i) => !i.td_date))
|
||||||
|
utilsService.toast('warn', 'Caution', 'You have date-less days, they will not be included in your export');
|
||||||
|
|
||||||
|
tripItems.forEach((item, index) => {
|
||||||
|
if (!item.td_date) return;
|
||||||
|
|
||||||
|
const eventDate = item.td_date;
|
||||||
|
const eventTime = item.time ?? '00:00';
|
||||||
|
const [year, month, day] = eventDate.split('-');
|
||||||
|
const [hours, minutes] = eventTime.split(':');
|
||||||
|
const dtStart = `${year}${month}${day}T${hours.padStart(2, '0')}${minutes.padStart(2, '0')}00`;
|
||||||
|
|
||||||
|
const startDateTime = new Date(`${eventDate}T${eventTime}`);
|
||||||
|
|
||||||
|
const nextItem = tripItems.slice(index + 1).find((i) => i.td_date);
|
||||||
|
const nextItemSameDay = tripItems.slice(index + 1).find((i) => i.td_date === item.td_date && i.time);
|
||||||
|
|
||||||
|
const endDateTime = nextItemSameDay?.time
|
||||||
|
? new Date(`${nextItemSameDay.td_date}T${nextItemSameDay.time}`)
|
||||||
|
: new Date(startDateTime.getTime() + 60 * 60 * 1000);
|
||||||
|
const dtEnd = endDateTime.toISOString().replace(/[-:]/g, '').split('.')[0];
|
||||||
|
|
||||||
|
// Build description
|
||||||
|
const eventDescription: string[] = [];
|
||||||
|
|
||||||
|
if (item.comment) eventDescription.push(`Comment: ${item.comment}`);
|
||||||
|
|
||||||
|
if (item.place?.name) eventDescription.push(`Place: ${item.place.name}`);
|
||||||
|
|
||||||
|
const lat = item.lat ?? item.place?.lat;
|
||||||
|
const lng = item.lng ?? item.place?.lng;
|
||||||
|
if (lat && lng) {
|
||||||
|
eventDescription.push(`Coordinates: ${lat}, ${lng}`);
|
||||||
|
eventDescription.push(`GMaps: https://www.google.com/maps?q=${lat},${lng}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.price) eventDescription.push(`Price: ${item.price}€`);
|
||||||
|
|
||||||
|
const description = eventDescription.join('\\n').replace(/\n/g, '\\n');
|
||||||
|
const location = item.place?.name ?? (lat && lng ? `${lat}, ${lng}` : '');
|
||||||
|
const geo = lat && lng ? `GEO:${lat};${lng}` : '';
|
||||||
|
const uid = `Trip-${tripName.replace(/[^a-zA-Z0-9-_]/g, '_')}-item-${item.id}-${tsz}`;
|
||||||
|
|
||||||
|
icsContent +=
|
||||||
|
'\r\n' +
|
||||||
|
[
|
||||||
|
'BEGIN:VEVENT',
|
||||||
|
`UID:${uid}`,
|
||||||
|
`DTSTAMP:${tsz}`,
|
||||||
|
`DTSTART:${dtStart}`,
|
||||||
|
`DTEND:${dtEnd}`,
|
||||||
|
`SUMMARY:${escapeICSText(item.text)}`,
|
||||||
|
description ? `DESCRIPTION:${escapeICSText(description)}` : '',
|
||||||
|
location ? `LOCATION:${escapeICSText(location)}` : '',
|
||||||
|
geo,
|
||||||
|
item.status ? `STATUS:${item.status.label.toUpperCase()}` : '',
|
||||||
|
'END:VEVENT',
|
||||||
|
]
|
||||||
|
.filter((line) => line)
|
||||||
|
.join('\r\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
icsContent += '\r\n' + 'END:VCALENDAR';
|
||||||
|
const blob = new Blob([icsContent], { type: 'text/calendar;charset=utf-8' });
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = URL.createObjectURL(blob);
|
||||||
|
link.download = `${tripName.replace(/\s+/g, '_')}.ics`;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
URL.revokeObjectURL(link.href);
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeICSText(text: string): string {
|
||||||
|
if (!text) return '';
|
||||||
|
return text.replace(/\\/g, '\\\\').replace(/;/g, '\\;').replace(/,/g, '\\,').replace(/\n/g, '\\n').replace(/\r/g, '');
|
||||||
|
}
|
||||||
@ -70,7 +70,8 @@
|
|||||||
text />
|
text />
|
||||||
<p-button label="Highlight" pTooltip="Show itinerary on map" icon="pi pi-directions"
|
<p-button label="Highlight" pTooltip="Show itinerary on map" icon="pi pi-directions"
|
||||||
[severity]="tripMapAntLayerDayID == -1 ? 'help' : 'primary'" (click)="toggleTripDaysHighlight()" text />
|
[severity]="tripMapAntLayerDayID == -1 ? 'help' : 'primary'" (click)="toggleTripDaysHighlight()" text />
|
||||||
<p-button pTooltip="Pretty Print" icon="pi pi-print" (click)="togglePrint()" text />
|
<p-button icon="pi pi-ellipsis-v" label="Export" (click)="menuTripExport.toggle($event)" text />
|
||||||
|
<p-menu #menuTripExport [model]="menuTripExportItems" appendTo="body" [popup]="true" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex md:hidden">
|
<div class="flex md:hidden">
|
||||||
@ -92,7 +93,7 @@
|
|||||||
|
|
||||||
@defer {
|
@defer {
|
||||||
@if (flattenedTripItems.length) {
|
@if (flattenedTripItems.length) {
|
||||||
<p-table [value]="flattenedTripItems" class="max-w-[85vw] md:max-w-full print-striped-rows"
|
<p-table [value]="flattenedTripItems" class="max-w-[85vw] md:max-w-full print-striped-rows" [customSort]="true"
|
||||||
[class.table-colored-resizer]="tableExpandableMode" [rowGroupMode]="tableExpandableMode ? 'subheader': 'rowspan'"
|
[class.table-colored-resizer]="tableExpandableMode" [rowGroupMode]="tableExpandableMode ? 'subheader': 'rowspan'"
|
||||||
groupRowsBy="td_label" [resizableColumns]="tableExpandableMode">
|
groupRowsBy="td_label" [resizableColumns]="tableExpandableMode">
|
||||||
<ng-template #header>
|
<ng-template #header>
|
||||||
@ -190,7 +191,8 @@
|
|||||||
<td [attr.rowspan]="rowspan" class="font-normal! max-w-20 truncate cursor-pointer"
|
<td [attr.rowspan]="rowspan" class="font-normal! max-w-20 truncate cursor-pointer"
|
||||||
[class.text-blue-500]="tripMapAntLayerDayID == tripitem.day_id"
|
[class.text-blue-500]="tripMapAntLayerDayID == tripitem.day_id"
|
||||||
(click)="toggleTripDayHighlight(tripitem.day_id); $event.stopPropagation()">
|
(click)="toggleTripDayHighlight(tripitem.day_id); $event.stopPropagation()">
|
||||||
<div class="truncate">{{tripitem.td_label }}</div>
|
<div class="text-xs text-gray-500">{{ tripitem.td_date | date: 'd MMM, y' }}</div>
|
||||||
|
<div class="truncate">{{ tripitem.td_label }}</div>
|
||||||
</td>
|
</td>
|
||||||
}
|
}
|
||||||
@if (tripTableSelectedColumns.includes('time')) {<td class="font-mono text-sm">{{ tripitem.time }}</td>}
|
@if (tripTableSelectedColumns.includes('time')) {<td class="font-mono text-sm">{{ tripitem.time }}</td>}
|
||||||
@ -250,14 +252,23 @@
|
|||||||
<div class="px-4 mx-auto max-w-screen-xl mt-8 col-span-full print:hidden">
|
<div class="px-4 mx-auto max-w-screen-xl mt-8 col-span-full print:hidden">
|
||||||
<div class="py-8 px-4 flex flex-col items-center gap-1">
|
<div class="py-8 px-4 flex flex-col items-center gap-1">
|
||||||
<h2 class="mb-0 text-4xl text-center tracking-tight font-extrabold text-gray-900 dark:text-gray-200">
|
<h2 class="mb-0 text-4xl text-center tracking-tight font-extrabold text-gray-900 dark:text-gray-200">
|
||||||
No Trip.
|
Empty Trip.
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
|
@if (trip?.days?.length) {
|
||||||
|
<p class="mt-4 font-light text-gray-500 sm:text-xl">
|
||||||
|
Add <i>Item</i> to your <i>Trip</i> to start organizing !
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p-button class="mt-4" label="Add Item" icon="pi pi-plus" [disabled]="trip?.archived" (click)="addItem()"
|
||||||
|
text />
|
||||||
|
} @else {
|
||||||
<p class="mt-4 font-light text-gray-500 sm:text-xl">
|
<p class="mt-4 font-light text-gray-500 sm:text-xl">
|
||||||
Add <i>Day</i> to your <i>Trip</i> to start organizing !
|
Add <i>Day</i> to your <i>Trip</i> to start organizing !
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p-button class="mt-4" label="Add" icon="pi pi-plus" [disabled]="trip?.archived" (click)="addDay()" text />
|
<p-button class="mt-4" label="Add Day" icon="pi pi-plus" [disabled]="trip?.archived" (click)="addDay()" text />
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden print:block text-center text-sm text-gray-500 mt-4">
|
<div class="hidden print:block text-center text-sm text-gray-500 mt-4">
|
||||||
@ -490,6 +501,9 @@
|
|||||||
{{ d.label }}
|
{{ d.label }}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2 flex-none">
|
<div class="flex items-center gap-2 flex-none">
|
||||||
|
<span
|
||||||
|
class="bg-gray-100 text-gray-800 text-sm px-2.5 py-0.5 rounded-md min-w-fit flex items-center group-hover:hidden dark:bg-gray-100/85"><i
|
||||||
|
class="pi pi-calendar"></i> {{ (d.dt | date: 'd MMM, y' ) || '-' }}</span>
|
||||||
<span
|
<span
|
||||||
class="bg-gray-100 text-gray-800 text-sm px-2.5 py-0.5 rounded-md min-w-fit group-hover:hidden dark:bg-gray-100/85">{{
|
class="bg-gray-100 text-gray-800 text-sm px-2.5 py-0.5 rounded-md min-w-fit group-hover:hidden dark:bg-gray-100/85">{{
|
||||||
getDayStats(d).price || '-' }} @if (getDayStats(d).price) { {{ trip?.currency }} }</span>
|
getDayStats(d).price || '-' }} @if (getDayStats(d).price) { {{ trip?.currency }} }</span>
|
||||||
@ -911,6 +925,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<span class="text-sm">{{ place.name }}</span>
|
<span class="text-sm">{{ place.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
} @empty {
|
||||||
|
<div class="mt-4 border-l-3 border-gray-900 pl-6 py-2">
|
||||||
|
<p class="text-gray-800 text-lg leading-loose whitespace-pre-line font-light">Nothing there.</p>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -51,6 +51,8 @@ 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';
|
import { FileSizePipe } from '../../shared/filesize.pipe';
|
||||||
|
import { generateTripICSFile } from './ics';
|
||||||
|
import { generateTripCSVFile } from './csv';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-trip',
|
selector: 'app-trip',
|
||||||
@ -202,6 +204,30 @@ export class TripComponent implements AfterViewInit {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
readonly menuTripExportItems: MenuItem[] = [
|
||||||
|
{
|
||||||
|
label: 'Actions',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Calendar (.ics)',
|
||||||
|
icon: 'pi pi-calendar',
|
||||||
|
command: () => generateTripICSFile(this.flattenedTripItems, this.trip?.name, this.utilsService),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'CSV',
|
||||||
|
icon: 'pi pi-file',
|
||||||
|
command: () => generateTripCSVFile(this.flattenedTripItems, this.trip?.name),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Pretty Print',
|
||||||
|
icon: 'pi pi-print',
|
||||||
|
command: () => {
|
||||||
|
this.togglePrint();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
readonly tripTableColumns: string[] = [
|
readonly tripTableColumns: string[] = [
|
||||||
'day',
|
'day',
|
||||||
'time',
|
'time',
|
||||||
@ -345,52 +371,62 @@ export class TripComponent implements AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
flattenTripDayItems(searchValue?: string) {
|
flattenTripDayItems(searchValue?: string) {
|
||||||
|
const searchLower = (searchValue || '').toLowerCase();
|
||||||
let prevLat: number, prevLng: number;
|
let prevLat: number, prevLng: number;
|
||||||
this.flattenedTripItems = this.trip!.days.flatMap((day) =>
|
this.flattenedTripItems = this.trip!.days.flatMap((day) => day.items.map((item) => ({ item, day })))
|
||||||
[...day.items]
|
.filter(
|
||||||
.filter((item) =>
|
({ item }) =>
|
||||||
searchValue
|
!searchLower ||
|
||||||
? item.text.toLowerCase().includes(searchValue) ||
|
item.text.toLowerCase().includes(searchLower) ||
|
||||||
item.place?.name.toLowerCase().includes(searchValue) ||
|
item.place?.name.toLowerCase().includes(searchLower) ||
|
||||||
item.comment?.toLowerCase().includes(searchValue)
|
item.comment?.toLowerCase().includes(searchLower),
|
||||||
: true,
|
)
|
||||||
)
|
.sort((a, b) => {
|
||||||
.sort((a, b) => (a.time < b.time ? -1 : a.time > b.time ? 1 : 0))
|
const dateA = a.day.dt;
|
||||||
.map((item) => {
|
const dateB = b.day.dt;
|
||||||
const lat = item.lat ?? (item.place ? item.place.lat : undefined);
|
if (dateA && dateB) return dateA.localeCompare(dateB) || (a.item.time || '').localeCompare(b.item.time || '');
|
||||||
const lng = item.lng ?? (item.place ? item.place.lng : undefined);
|
if (!dateA && !dateB) {
|
||||||
|
return (
|
||||||
|
(a.day.label || '').localeCompare(b.day.label || '') || (a.item.time || '').localeCompare(b.item.time || '')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return dateA ? -1 : 1;
|
||||||
|
})
|
||||||
|
.map(({ item, day }) => {
|
||||||
|
const lat = item.lat ?? item.place?.lat;
|
||||||
|
const lng = item.lng ?? item.place?.lng;
|
||||||
|
|
||||||
let distance: number | undefined;
|
let distance: number | undefined;
|
||||||
if (lat && lng) {
|
if (lat && lng) {
|
||||||
if (prevLat && prevLng) {
|
if (prevLat && prevLng) {
|
||||||
const d = calculateDistanceBetween(prevLat, prevLng, lat, lng);
|
const d = calculateDistanceBetween(prevLat, prevLng, lat, lng);
|
||||||
distance = +(Math.round(d * 1000) / 1000).toFixed(2);
|
distance = +(Math.round(d * 1000) / 1000).toFixed(2);
|
||||||
}
|
|
||||||
prevLat = lat;
|
|
||||||
prevLng = lng;
|
|
||||||
}
|
}
|
||||||
|
prevLat = lat;
|
||||||
|
prevLng = lng;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
td_id: day.id,
|
td_id: day.id,
|
||||||
td_label: day.label,
|
td_label: day.label,
|
||||||
id: item.id,
|
td_date: day.dt,
|
||||||
time: item.time,
|
id: item.id,
|
||||||
text: item.text,
|
time: item.time,
|
||||||
status: this.statusToTripStatus(item.status as string),
|
text: item.text,
|
||||||
comment: item.comment,
|
status: this.statusToTripStatus(item.status as string),
|
||||||
price: item.price || undefined,
|
comment: item.comment,
|
||||||
day_id: item.day_id,
|
price: item.price || undefined,
|
||||||
place: item.place,
|
day_id: item.day_id,
|
||||||
image: item.image,
|
place: item.place,
|
||||||
image_id: item.image_id,
|
image: item.image,
|
||||||
gpx: item.gpx,
|
image_id: item.image_id,
|
||||||
lat,
|
gpx: item.gpx,
|
||||||
lng,
|
lat,
|
||||||
distance,
|
lng,
|
||||||
paid_by: item.paid_by,
|
distance,
|
||||||
};
|
paid_by: item.paid_by,
|
||||||
}),
|
};
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
computePlacesUsedInTable() {
|
computePlacesUsedInTable() {
|
||||||
@ -959,7 +995,7 @@ export class TripComponent implements AfterViewInit {
|
|||||||
closable: true,
|
closable: true,
|
||||||
dismissableMask: true,
|
dismissableMask: true,
|
||||||
width: '50vw',
|
width: '50vw',
|
||||||
data: { day: day, days: this.trip.days },
|
data: day,
|
||||||
breakpoints: {
|
breakpoints: {
|
||||||
'640px': '80vw',
|
'640px': '80vw',
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user