Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 69 additions & 18 deletions ClientApps/trip-editor/src/components/VisitProgressSurface.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ type VisitPlaceRow = {
type VisitRegionGroup = {
regionId: Guid;
regionName: string;
visitedCount: number;
totalCount: number;
percentVisited: number;
rows: VisitPlaceRow[];
};

Expand All @@ -42,14 +45,21 @@ const regionGroups = computed<VisitRegionGroup[]>(() => {

for (const regionId of props.state.regionOrder) {
const placeIds = props.state.placeOrderByRegionId[regionId] ?? [];
const rows = placeIds
const allRows = placeIds
.map(placeId => visitPlaceRow(regionId, placeId, orderedHistory))
.filter(row => row && filterMatches(row.summary)) as VisitPlaceRow[];
.filter(row => row) as VisitPlaceRow[];
const rows = allRows.filter(row => filterMatches(row.summary));

if (rows.length > 0) {
const visitedCount = allRows.filter(row => row.summary.isVisited).length;
const totalCount = allRows.length;

groups.push({
regionId,
regionName: regionName(regionId),
visitedCount,
totalCount,
percentVisited: percentVisited(visitedCount, totalCount),
rows
});
}
Expand Down Expand Up @@ -120,6 +130,10 @@ function regionName(regionId: Guid): string {
return props.state.regionsById[regionId]?.name || 'Unknown region';
}

function percentVisited(visitedCount: number, totalCount: number): number {
return totalCount > 0 ? Math.round((visitedCount / totalCount) * 100) : 0;
}

function historyRegionName(row: EditorVisitHistoryRow, fallbackRegionId: Guid): string {
return props.state.regionsById[row.regionId]?.name || props.state.regionsById[fallbackRegionId]?.name || 'Unknown region';
}
Expand Down Expand Up @@ -198,45 +212,82 @@ async function manageVisit(event: MouseEvent, visitId: Guid): Promise<void> {

<div class="trip-editor-expanded__body trip-editor-visit-progress__body">
<section class="trip-editor-visit-progress__summary" aria-label="Trip visit progress summary">
<div class="trip-editor-panel__line">
<span>Visit progress</span>
<strong>{{ state.visitProgress.percentVisited }}%</strong>
<div class="trip-editor-visit-progress__summary-heading">
<div>
<span>Visit progress</span>
<strong>Your overall trip progress</strong>
</div>
<strong class="trip-editor-visit-progress__percent">{{ state.visitProgress.percentVisited }}%</strong>
</div>
<div class="trip-editor-progress" aria-hidden="true">
<div
class="trip-editor-progress trip-editor-visit-progress__bar"
role="progressbar"
aria-label="Overall visit progress"
:aria-valuenow="state.visitProgress.percentVisited"
aria-valuemin="0"
aria-valuemax="100"
>
<span :style="{ width: `${state.visitProgress.percentVisited}%` }"></span>
</div>
<p>{{ state.visitProgress.visitedPlaces }} / {{ state.visitProgress.totalPlaces }} places visited</p>
<p class="trip-editor-visit-progress__summary-count">
<span class="trip-editor-visit-status trip-editor-visit-status--visited" aria-hidden="true">&check;</span>
{{ state.visitProgress.visitedPlaces }} / {{ state.visitProgress.totalPlaces }} places visited
</p>
<p v-if="totalPlaces > 0 && !hasVisits">No visit history yet.</p>
</section>

<fieldset class="trip-editor-visit-progress__filters" aria-label="Visit filters">
<legend>Filter places</legend>
<label>
<label class="trip-editor-visit-filter-option">
<input v-model="filter" type="radio" name="trip-editor-visit-filter" value="all" />
<span>All</span>
</label>
<label>
<label class="trip-editor-visit-filter-option">
<input v-model="filter" type="radio" name="trip-editor-visit-filter" value="visited" />
<span>Visited</span>
<span><span class="trip-editor-visit-status trip-editor-visit-status--visited" aria-hidden="true">&check;</span>Visited</span>
</label>
<label>
<label class="trip-editor-visit-filter-option">
<input v-model="filter" type="radio" name="trip-editor-visit-filter" value="not-visited" />
<span>Not visited</span>
<span><span class="trip-editor-visit-status trip-editor-visit-status--pending" aria-hidden="true"></span>Not visited</span>
</label>
</fieldset>

<p v-if="emptyState" class="trip-editor-empty-state">{{ emptyState }}</p>

<div v-else class="trip-editor-visit-region-list">
<section v-for="group in regionGroups" :key="group.regionId" class="trip-editor-visit-region" :aria-label="group.regionName">
<h3>{{ group.regionName }}</h3>
<header class="trip-editor-visit-region__header">
<h3>{{ group.regionName }}</h3>
<span>{{ group.visitedCount }} / {{ group.totalCount }} visited</span>
</header>
<div
v-if="group.totalCount > 0"
class="trip-editor-progress trip-editor-visit-region__bar"
role="progressbar"
:aria-label="`${group.regionName} visit progress`"
:aria-valuenow="group.percentVisited"
aria-valuemin="0"
aria-valuemax="100"
>
<span :style="{ width: `${group.percentVisited}%` }"></span>
</div>
<article v-for="row in group.rows" :key="row.placeId" class="trip-editor-visit-place-row" :data-visit-place-id="row.placeId">
<header>
<div>
<h4>{{ row.placeName }}</h4>
<small>{{ row.summary.isVisited ? 'Visited' : 'Not visited' }}</small>
<div class="trip-editor-visit-place-row__title">
<span
class="trip-editor-visit-status"
:class="row.summary.isVisited ? 'trip-editor-visit-status--visited' : 'trip-editor-visit-status--pending'"
:aria-label="row.summary.isVisited ? 'Visited' : 'Not visited'"
role="img"
>
<span v-if="row.summary.isVisited" aria-hidden="true">&check;</span>
</span>
<div>
<h4>{{ row.placeName }}</h4>
<small>{{ row.summary.isVisited ? 'Visited' : 'Not visited' }}</small>
</div>
</div>
<strong>{{ row.summary.visitCount }} visit{{ row.summary.visitCount === 1 ? '' : 's' }}</strong>
<strong class="trip-editor-visit-count-pill">{{ row.summary.visitCount }} visit{{ row.summary.visitCount === 1 ? '' : 's' }}</strong>
</header>
<dl class="trip-editor-visit-place-row__summary">
<div>
Expand All @@ -252,7 +303,7 @@ async function manageVisit(event: MouseEvent, visitId: Guid): Promise<void> {
<div v-if="row.summary.isVisited" class="trip-editor-visit-history">
<p v-if="row.historyRows.length === 0" class="trip-editor-empty-state">No visit history rows available for this place.</p>
<div v-for="history in row.historyRows" :key="history.visitId" class="trip-editor-visit-history-row" :data-visit-id="history.visitId">
<div>
<div class="trip-editor-visit-history-row__title">
<strong>{{ row.placeName }}</strong>
<span>{{ historyRegionName(history, row.regionId) }}</span>
</div>
Expand Down
Loading
Loading