✨ Trip: checklist dialog
This commit is contained in:
parent
b4936c5b53
commit
63c9b751eb
39
backend/trip/alembic/versions/60a9bb641d8a_trip_checklist.py
Normal file
39
backend/trip/alembic/versions/60a9bb641d8a_trip_checklist.py
Normal file
@ -0,0 +1,39 @@
|
||||
"""Trip Checklist
|
||||
|
||||
Revision ID: 60a9bb641d8a
|
||||
Revises: 1181ac441ce5
|
||||
Create Date: 2025-08-17 21:12:41.336514
|
||||
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
import sqlmodel.sql.sqltypes
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "60a9bb641d8a"
|
||||
down_revision = "1181ac441ce5"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table(
|
||||
"tripchecklistitem",
|
||||
sa.Column("text", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column("checked", sa.Boolean(), nullable=True),
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("user", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column("trip_id", sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["trip_id"], ["trip.id"], name=op.f("fk_tripchecklistitem_trip_id_trip"), ondelete="CASCADE"
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["user"], ["user.username"], name=op.f("fk_tripchecklistitem_user_user"), ondelete="CASCADE"
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_tripchecklistitem")),
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_table("tripchecklistitem")
|
||||
@ -260,6 +260,7 @@ class Trip(TripBase, table=True):
|
||||
days: list["TripDay"] = Relationship(back_populates="trip", cascade_delete=True)
|
||||
shares: list["TripShare"] = Relationship(back_populates="trip", cascade_delete=True)
|
||||
packing_items: list["TripPackingListItem"] = Relationship(back_populates="trip", cascade_delete=True)
|
||||
checklist_items: list["TripChecklistItem"] = Relationship(back_populates="trip", cascade_delete=True)
|
||||
|
||||
|
||||
class TripCreate(TripBase):
|
||||
@ -297,7 +298,6 @@ class TripRead(TripBase):
|
||||
image_id: int | None
|
||||
days: list["TripDayRead"]
|
||||
places: list["PlaceRead"]
|
||||
shared: bool
|
||||
|
||||
@classmethod
|
||||
def serialize(cls, obj: Trip) -> "TripRead":
|
||||
@ -309,7 +309,6 @@ class TripRead(TripBase):
|
||||
image_id=obj.image_id,
|
||||
days=[TripDayRead.serialize(day) for day in obj.days],
|
||||
places=[PlaceRead.serialize(place) for place in obj.places],
|
||||
shared=bool(obj.shares),
|
||||
)
|
||||
|
||||
|
||||
@ -446,3 +445,34 @@ class TripPackingListItemRead(TripPackingListItemBase):
|
||||
category=obj.category,
|
||||
packed=obj.packed,
|
||||
)
|
||||
|
||||
|
||||
class TripChecklistItemBase(SQLModel):
|
||||
text: str | None = None
|
||||
checked: bool | None = None
|
||||
|
||||
|
||||
class TripChecklistItem(TripChecklistItemBase, table=True):
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
|
||||
trip_id: int = Field(foreign_key="trip.id", ondelete="CASCADE")
|
||||
trip: Trip | None = Relationship(back_populates="checklist_items")
|
||||
|
||||
|
||||
class TripChecklistItemCreate(TripChecklistItemBase):
|
||||
checked: bool = False
|
||||
|
||||
|
||||
class TripChecklistItemUpdate(TripChecklistItemBase): ...
|
||||
|
||||
|
||||
class TripChecklistItemRead(TripChecklistItemBase):
|
||||
id: int
|
||||
|
||||
@classmethod
|
||||
def serialize(cls, obj: "TripChecklistItem") -> "TripChecklistItemRead":
|
||||
return cls(
|
||||
id=obj.id,
|
||||
text=obj.text,
|
||||
checked=obj.checked,
|
||||
)
|
||||
|
||||
@ -5,14 +5,15 @@ from sqlmodel import select
|
||||
|
||||
from ..config import settings
|
||||
from ..deps import SessionDep, get_current_username
|
||||
from ..models.models import (Image, Place, Trip, TripCreate, TripDay,
|
||||
from ..models.models import (Image, Place, Trip, TripChecklistItem,
|
||||
TripChecklistItemCreate, TripChecklistItemRead,
|
||||
TripChecklistItemUpdate, TripCreate, TripDay,
|
||||
TripDayBase, TripDayRead, TripItem,
|
||||
TripItemCreate, TripItemRead, TripItemUpdate,
|
||||
TripPackingListItem, TripPackingListItemCreate,
|
||||
TripPackingListItemRead,
|
||||
TripPackingListItemUpdate, TripPlaceLink,
|
||||
TripRead, TripReadBase, TripShare, TripShareURL,
|
||||
TripUpdate)
|
||||
TripPackingListItemRead, TripPackingListItemUpdate,
|
||||
TripRead, TripReadBase, TripShare,
|
||||
TripShareURL, TripUpdate)
|
||||
from ..security import verify_exists_and_owns
|
||||
from ..utils.utils import (b64img_decode, generate_urlsafe, remove_image,
|
||||
save_image_to_file)
|
||||
@ -495,3 +496,91 @@ def delete_packing_item(
|
||||
session.delete(item)
|
||||
session.commit()
|
||||
return {}
|
||||
|
||||
|
||||
@router.get("/{trip_id}/checklist", response_model=list[TripChecklistItemRead])
|
||||
def read_checklist(
|
||||
session: SessionDep,
|
||||
trip_id: int,
|
||||
current_user: Annotated[str, Depends(get_current_username)],
|
||||
) -> list[TripChecklistItemRead]:
|
||||
_verify_trip_member(session, trip_id, current_user)
|
||||
items = session.exec(select(TripChecklistItem).where(TripChecklistItem.trip_id == trip_id))
|
||||
return [TripChecklistItemRead.serialize(i) for i in items]
|
||||
|
||||
|
||||
@router.get("/shared/{token}/checklist", response_model=list[TripChecklistItemRead])
|
||||
def read_shared_trip_checklist(
|
||||
session: SessionDep,
|
||||
token: str,
|
||||
) -> list[TripChecklistItemRead]:
|
||||
items = session.exec(
|
||||
select(TripChecklistItem).where(
|
||||
TripChecklistItem.trip_id == _trip_from_token_or_404(session, token).trip_id
|
||||
)
|
||||
)
|
||||
return [TripChecklistItemRead.serialize(i) for i in items]
|
||||
|
||||
|
||||
@router.post("/{trip_id}/checklist", response_model=TripChecklistItemRead)
|
||||
def create_checklist_item(
|
||||
session: SessionDep,
|
||||
trip_id: int,
|
||||
data: TripChecklistItemCreate,
|
||||
current_user: Annotated[str, Depends(get_current_username)],
|
||||
) -> TripChecklistItemRead:
|
||||
_verify_trip_member(session, trip_id, current_user)
|
||||
item = TripChecklistItem(**data.model_dump(), trip_id=trip_id)
|
||||
session.add(item)
|
||||
session.commit()
|
||||
session.refresh(item)
|
||||
return TripChecklistItemRead.serialize(item)
|
||||
|
||||
|
||||
@router.put("/{trip_id}/checklist/{id}", response_model=TripChecklistItemRead)
|
||||
def update_checklist_item(
|
||||
session: SessionDep,
|
||||
item: TripChecklistItemUpdate,
|
||||
trip_id: int,
|
||||
id: int,
|
||||
current_user: Annotated[str, Depends(get_current_username)],
|
||||
) -> TripChecklistItemRead:
|
||||
_verify_trip_member(session, trip_id, current_user)
|
||||
db_item = session.exec(
|
||||
select(TripChecklistItem).where(TripChecklistItem.id == id, TripChecklistItem.trip_id == trip_id)
|
||||
).one_or_none()
|
||||
|
||||
if not db_item:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
|
||||
item_data = item.model_dump(exclude_unset=True)
|
||||
for key, value in item_data.items():
|
||||
setattr(db_item, key, value)
|
||||
|
||||
session.add(db_item)
|
||||
session.commit()
|
||||
session.refresh(db_item)
|
||||
return TripChecklistItemRead.serialize(db_item)
|
||||
|
||||
|
||||
@router.delete("/{trip_id}/checklist/{id}")
|
||||
def delete_checklist_item(
|
||||
session: SessionDep,
|
||||
trip_id: int,
|
||||
id: int,
|
||||
current_user: Annotated[str, Depends(get_current_username)],
|
||||
):
|
||||
_verify_trip_member(session, trip_id, current_user)
|
||||
item = session.exec(
|
||||
select(TripChecklistItem).where(
|
||||
TripChecklistItem.id == id,
|
||||
TripChecklistItem.trip_id == trip_id,
|
||||
)
|
||||
).one_or_none()
|
||||
|
||||
if not item:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
|
||||
session.delete(item)
|
||||
session.commit()
|
||||
return {}
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
<p-button pTooltip="Delete Trip" text (click)="deleteTrip()" icon="pi pi-trash" severity="danger" />
|
||||
<p-button pTooltip="Edit Trip" text (click)="editTrip()" icon="pi pi-pencil" />
|
||||
<div class="border-l border-solid border-gray-700 h-4"></div>
|
||||
<p-button pTooltip="Checklist" text (click)="openChecklist()" icon="pi pi-check-square" severity="help" />
|
||||
<p-button pTooltip="Packing list" tooltipPosition="left" text (click)="openPackingList()" icon="pi pi-briefcase"
|
||||
severity="help" />
|
||||
</div>
|
||||
@ -121,7 +122,7 @@
|
||||
<span class="font-bold ml-2">{{ tripitem.td_label }}</span>
|
||||
<p-button class="ml-2" text icon="pi pi-directions"
|
||||
[severity]="tripMapAntLayerDayID == tripitem.day_id ? 'help' : 'primary'"
|
||||
(click)="toggleTripDayHighlightPathDay(tripitem.day_id)" />
|
||||
(click)="toggleTripDayHighlight(tripitem.day_id)" />
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
@ -174,7 +175,7 @@
|
||||
@if (tripTableSelectedColumns.includes('day') && rowgroup) {
|
||||
<td [attr.rowspan]="rowspan" class="font-normal! max-w-20 truncate cursor-pointer"
|
||||
[class.text-blue-500]="tripMapAntLayerDayID == tripitem.day_id"
|
||||
(click)="toggleTripDayHighlightPathDay(tripitem.day_id); $event.stopPropagation()">
|
||||
(click)="toggleTripDayHighlight(tripitem.day_id); $event.stopPropagation()">
|
||||
<div class="truncate">{{tripitem.td_label }}</div>
|
||||
</td>
|
||||
}
|
||||
@ -476,44 +477,6 @@
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="p-4 shadow rounded-md w-full min-h-20">
|
||||
<div class="group relative p-2 mb-2 flex flex-col items-start">
|
||||
<h1 class="font-semibold tracking-tight text-xl">Watchlist</h1>
|
||||
<span class="text-xs text-gray-500 line-clamp-1">{{ trip?.name }} pending/constraints</span>
|
||||
|
||||
<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">
|
||||
<p-button [icon]="collapsedTripStatuses ? 'pi pi-chevron-down' : 'pi pi-chevron-up'" text
|
||||
(click)="collapsedTripStatuses = !collapsedTripStatuses" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!collapsedTripStatuses) {
|
||||
<div [class.max-h-40!]="!isExpanded" class="max-h-[340px] overflow-y-auto">
|
||||
@defer {
|
||||
@for (item of getWatchlistData; track item.id) {
|
||||
<div class="flex items-center gap-2 h-10 px-4 py-2 w-full max-w-full">
|
||||
<div class="flex flex-none">
|
||||
<span [style.background]="item.status.color+'1A'" [style.color]="item.status.color"
|
||||
class="text-xs font-medium me-2 px-2.5 py-0.5 rounded">{{
|
||||
item.status.label }}</span>
|
||||
</div>
|
||||
<div class="line-clamp-1">{{ item.text }}</div>
|
||||
</div>
|
||||
} @empty {
|
||||
<p class="p-4 font-light text-gray-500">
|
||||
Nothing there
|
||||
</p>
|
||||
}
|
||||
} @placeholder (minimum 0.4s) {
|
||||
<div class="h-16">
|
||||
<p-skeleton height="100%" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
@ -590,3 +553,48 @@
|
||||
</div>
|
||||
</section>
|
||||
</p-dialog>
|
||||
|
||||
<p-dialog header="Checklist" [draggable]="false" [dismissableMask]="true" [modal]="true"
|
||||
[(visible)]="checklistDialogVisible" styleClass="w-[95%] md:w-[50%] lg:w-[30%]">
|
||||
<section class="p-4 max-w-full max-h-[80%] md:max-h-[600px]">
|
||||
<div class="flex justify-center">
|
||||
<p-button (click)="addChecklistItem()" icon="pi pi-plus" label="Add item" text />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-2 mt-4 pb-4">
|
||||
@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">
|
||||
<label [for]="item.id" [class.line-through]="item.checked"
|
||||
class="flex items-center gap-2 w-full cursor-pointer">
|
||||
<p-checkbox (onChange)="onCheckChecklistItem($event, item.id)" [binary]="true" [inputId]="item.id.toString()"
|
||||
[(ngModel)]="item.checked" />
|
||||
<div class="pr-6 md:pr-0 truncate select-none flex-1">
|
||||
<span>{{ item.text }}</span>
|
||||
</div>
|
||||
</label>
|
||||
<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">
|
||||
<p-button size="small" text icon="pi pi-trash" (click)="deleteChecklistItem(item)" severity="danger" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-2 mt-4 pb-4">
|
||||
@for (item of getWatchlistData; track item.id) {
|
||||
<div class="flex items-center gap-3 rounded-md p-2 hover:bg-gray-100 dark:hover:bg-white/5">
|
||||
<label [for]="item.id" class="flex items-center gap-2 w-full">
|
||||
<div class="relative">
|
||||
@if (item.status) {<div class="z-50 block absolute top-0 left-3 size-2.5 rounded-full"
|
||||
[style.background]="item.status.color"></div>}
|
||||
<p-checkbox disabled />
|
||||
</div>
|
||||
<div class="pr-6 md:pr-0 truncate select-none flex-1">
|
||||
<span>{{ item.text }}</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
</p-dialog>
|
||||
@ -15,6 +15,7 @@ import {
|
||||
TripItem,
|
||||
TripStatus,
|
||||
PackingItem,
|
||||
ChecklistItem,
|
||||
} from "../../types/trip";
|
||||
import { Place } from "../../types/poi";
|
||||
import {
|
||||
@ -55,6 +56,7 @@ import { MultiSelectModule } from "primeng/multiselect";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { CheckboxChangeEvent, CheckboxModule } from "primeng/checkbox";
|
||||
import { TripCreatePackingModalComponent } from "../../modals/trip-create-packing-modal/trip-create-packing-modal.component";
|
||||
import { TripCreateChecklistModalComponent } from "../../modals/trip-create-checklist-modal/trip-create-checklist-modal.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-trip",
|
||||
@ -95,13 +97,15 @@ export class TripComponent implements AfterViewInit {
|
||||
totalPrice = 0;
|
||||
collapsedTripDays = false;
|
||||
collapsedTripPlaces = false;
|
||||
collapsedTripStatuses = false;
|
||||
shareDialogVisible = false;
|
||||
packingDialogVisible = false;
|
||||
isExpanded = false;
|
||||
isFilteringMode = false;
|
||||
packingList: PackingItem[] = [];
|
||||
dispPackingList: Record<string, PackingItem[]> = {};
|
||||
checklistDialogVisible = false;
|
||||
checklistItems: ChecklistItem[] = [];
|
||||
dispchecklist: ChecklistItem[] = [];
|
||||
|
||||
map?: L.Map;
|
||||
markerClusterGroup?: L.MarkerClusterGroup;
|
||||
@ -122,6 +126,14 @@ export class TripComponent implements AfterViewInit {
|
||||
this.openPackingList();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Checklist",
|
||||
icon: "pi pi-check-square",
|
||||
iconClass: "text-purple-500!",
|
||||
command: () => {
|
||||
this.openChecklist();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Edit",
|
||||
icon: "pi pi-pencil",
|
||||
@ -666,7 +678,7 @@ export class TripComponent implements AfterViewInit {
|
||||
this.tripMapAntLayerDayID = -1; //Hardcoded value for global trace
|
||||
}
|
||||
|
||||
toggleTripDayHighlightPathDay(day_id: number) {
|
||||
toggleTripDayHighlight(day_id: number) {
|
||||
// Click on the currently displayed day: remove
|
||||
if (this.tripMapAntLayerDayID == day_id) {
|
||||
this.resetDayHighlight();
|
||||
@ -1447,4 +1459,97 @@ export class TripComponent implements AfterViewInit {
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
openChecklist() {
|
||||
if (!this.trip) return;
|
||||
|
||||
if (!this.checklistItems.length)
|
||||
this.apiService
|
||||
.getChecklist(this.trip.id)
|
||||
.pipe(take(1))
|
||||
.subscribe({
|
||||
next: (items) => {
|
||||
this.checklistItems = [...items];
|
||||
},
|
||||
});
|
||||
this.checklistDialogVisible = true;
|
||||
}
|
||||
|
||||
addChecklistItem() {
|
||||
if (!this.trip) return;
|
||||
|
||||
const modal: DynamicDialogRef = this.dialogService.open(
|
||||
TripCreateChecklistModalComponent,
|
||||
{
|
||||
header: "Create item",
|
||||
modal: true,
|
||||
appendTo: "body",
|
||||
closable: true,
|
||||
dismissableMask: true,
|
||||
width: "40vw",
|
||||
breakpoints: {
|
||||
"1260px": "70vw",
|
||||
"600px": "90vw",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
modal.onClose.pipe(take(1)).subscribe({
|
||||
next: (item: ChecklistItem | null) => {
|
||||
if (!item) return;
|
||||
|
||||
this.apiService
|
||||
.postChecklistItem(this.trip!.id, item)
|
||||
.pipe(take(1))
|
||||
.subscribe({
|
||||
next: (item) => {
|
||||
this.checklistItems = [...this.checklistItems, item];
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onCheckChecklistItem(e: CheckboxChangeEvent, id: number) {
|
||||
if (!this.trip) return;
|
||||
this.apiService
|
||||
.putChecklistItem(this.trip.id, id, { checked: e.checked })
|
||||
.pipe(take(1))
|
||||
.subscribe({
|
||||
next: (item) => {
|
||||
const i = this.checklistItems.find((p) => p.id == item.id);
|
||||
if (i) i.checked = item.checked;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
deleteChecklistItem(item: ChecklistItem) {
|
||||
const modal = this.dialogService.open(YesNoModalComponent, {
|
||||
header: "Confirm deletion",
|
||||
modal: true,
|
||||
closable: true,
|
||||
dismissableMask: true,
|
||||
breakpoints: {
|
||||
"640px": "90vw",
|
||||
},
|
||||
data: `Delete ${item.text.substring(0, 50)} ?`,
|
||||
});
|
||||
|
||||
modal.onClose.pipe(take(1)).subscribe({
|
||||
next: (bool) => {
|
||||
if (!bool) return;
|
||||
this.apiService
|
||||
.deleteChecklistItem(this.trip!.id, item.id)
|
||||
.pipe(take(1))
|
||||
.subscribe({
|
||||
next: () => {
|
||||
const index = this.checklistItems.findIndex(
|
||||
(p) => p.id == item.id,
|
||||
);
|
||||
if (index > -1) this.checklistItems.splice(index, 1);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
<div pFocusTrap class="grid items-center gap-4" [formGroup]="checklistForm">
|
||||
<p-floatlabel variant="in">
|
||||
<input class="col-span-2" id="text" formControlName="text" (keypress.enter)="closeDialog()" pInputText fluid />
|
||||
<label for="text">Text</label>
|
||||
</p-floatlabel>
|
||||
|
||||
<div class="mt-4 text-right">
|
||||
<p-button (click)="closeDialog()" [disabled]="!checklistForm.dirty || !checklistForm.valid">{{
|
||||
checklistForm.get("id")?.value
|
||||
!== -1 ? "Update" : "Create" }}</p-button>
|
||||
</div>
|
||||
@ -0,0 +1,52 @@
|
||||
import { Component } from "@angular/core";
|
||||
import {
|
||||
FormBuilder,
|
||||
FormGroup,
|
||||
ReactiveFormsModule,
|
||||
Validators,
|
||||
} from "@angular/forms";
|
||||
import { ButtonModule } from "primeng/button";
|
||||
import { DynamicDialogConfig, DynamicDialogRef } from "primeng/dynamicdialog";
|
||||
import { FloatLabelModule } from "primeng/floatlabel";
|
||||
import { InputTextModule } from "primeng/inputtext";
|
||||
import { FocusTrapModule } from "primeng/focustrap";
|
||||
|
||||
@Component({
|
||||
selector: "app-trip-create-checklist-modal",
|
||||
imports: [
|
||||
FloatLabelModule,
|
||||
InputTextModule,
|
||||
ButtonModule,
|
||||
ReactiveFormsModule,
|
||||
FocusTrapModule,
|
||||
],
|
||||
standalone: true,
|
||||
templateUrl: "./trip-create-checklist-modal.component.html",
|
||||
styleUrl: "./trip-create-checklist-modal.component.scss",
|
||||
})
|
||||
export class TripCreateChecklistModalComponent {
|
||||
checklistForm: FormGroup;
|
||||
constructor(
|
||||
private ref: DynamicDialogRef,
|
||||
private fb: FormBuilder,
|
||||
private config: DynamicDialogConfig,
|
||||
) {
|
||||
this.checklistForm = this.fb.group({
|
||||
id: -1,
|
||||
text: ["", { validators: Validators.required }],
|
||||
});
|
||||
|
||||
const patchValue = this.config.data?.packing;
|
||||
if (patchValue) {
|
||||
this.checklistForm.patchValue(patchValue);
|
||||
}
|
||||
}
|
||||
|
||||
closeDialog() {
|
||||
if (!this.checklistForm.valid) return;
|
||||
|
||||
// Normalize data for API POST
|
||||
let ret = this.checklistForm.value;
|
||||
this.ref.close(ret);
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,11 @@
|
||||
import { inject, Injectable } from "@angular/core";
|
||||
import { HttpClient, HttpHeaders } from "@angular/common/http";
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { Category, Place } from "../types/poi";
|
||||
import { BehaviorSubject, map, Observable, shareReplay, tap } from "rxjs";
|
||||
import { Info } from "../types/info";
|
||||
import { ImportResponse, Settings } from "../types/settings";
|
||||
import {
|
||||
ChecklistItem,
|
||||
PackingItem,
|
||||
SharedTripURL,
|
||||
Trip,
|
||||
@ -272,6 +273,45 @@ export class ApiService {
|
||||
);
|
||||
}
|
||||
|
||||
getChecklist(trip_id: number): Observable<ChecklistItem[]> {
|
||||
return this.httpClient.get<ChecklistItem[]>(
|
||||
`${this.apiBaseUrl}/trips/${trip_id}/checklist`,
|
||||
);
|
||||
}
|
||||
|
||||
getSharedTripChecklist(token: string): Observable<ChecklistItem[]> {
|
||||
return this.httpClient.get<ChecklistItem[]>(
|
||||
`${this.apiBaseUrl}/trips/shared/${token}/checklist`,
|
||||
);
|
||||
}
|
||||
|
||||
postChecklistItem(
|
||||
trip_id: number,
|
||||
item: ChecklistItem,
|
||||
): Observable<ChecklistItem> {
|
||||
return this.httpClient.post<ChecklistItem>(
|
||||
`${this.apiBaseUrl}/trips/${trip_id}/checklist`,
|
||||
item,
|
||||
);
|
||||
}
|
||||
|
||||
putChecklistItem(
|
||||
trip_id: number,
|
||||
id: number,
|
||||
item: Partial<ChecklistItem>,
|
||||
): Observable<ChecklistItem> {
|
||||
return this.httpClient.put<ChecklistItem>(
|
||||
`${this.apiBaseUrl}/trips/${trip_id}/checklist/${id}`,
|
||||
item,
|
||||
);
|
||||
}
|
||||
|
||||
deleteChecklistItem(trip_id: number, id: number): Observable<null> {
|
||||
return this.httpClient.delete<null>(
|
||||
`${this.apiBaseUrl}/trips/${trip_id}/checklist/${id}`,
|
||||
);
|
||||
}
|
||||
|
||||
checkVersion(): Observable<string> {
|
||||
return this.httpClient.get<string>(
|
||||
`${this.apiBaseUrl}/settings/checkversion`,
|
||||
|
||||
@ -16,11 +16,11 @@ export interface Trip {
|
||||
archived?: boolean;
|
||||
user: string;
|
||||
days: TripDay[];
|
||||
shared?: boolean;
|
||||
|
||||
// POST / PUT
|
||||
places: Place[];
|
||||
place_ids: number[];
|
||||
shared?: boolean;
|
||||
}
|
||||
|
||||
export interface TripDay {
|
||||
@ -73,3 +73,9 @@ export interface PackingItem {
|
||||
qt?: number;
|
||||
packed?: boolean;
|
||||
}
|
||||
|
||||
export interface ChecklistItem {
|
||||
id: number;
|
||||
text: string;
|
||||
checked?: boolean;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user