Refactor Self-Serve task handling and enhance UI components:
- Update `SelfServeTaskList.vue` to handle image and non-image task attachments separately and improve task card styling. - Add utility methods for filtering task services, attachments, and images. - Modify `MyWashStart.vue` and `SelfServeTasksStep.vue` headers for better readability, styling, and alignment. - Improve layout responsiveness with scoped styles for task cards and progress headers.
This commit is contained in:
@@ -29,6 +29,32 @@ const hasTaskDescription = (task: any) => {
|
||||
const trimmedDescription = description.trim();
|
||||
return trimmedDescription.length > 0 && trimmedDescription !== "-";
|
||||
};
|
||||
|
||||
const getImageAttachments = (task: any) => {
|
||||
if (!Array.isArray(task?.attachments)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return task.attachments.filter((attachment: any) => isImageAttachment(attachment));
|
||||
};
|
||||
|
||||
const getPrimaryImageAttachment = (task: any) => getImageAttachments(task)[0] || null;
|
||||
|
||||
const getNonImageAttachments = (task: any) => {
|
||||
if (!Array.isArray(task?.attachments)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return task.attachments.filter((attachment: any) => !isImageAttachment(attachment));
|
||||
};
|
||||
|
||||
const getVisibleServices = (task: any) => {
|
||||
if (!Array.isArray(task?.services)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return task.services.filter((service: any) => String(service || "").trim().toUpperCase() !== "MACHINE");
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -36,11 +62,62 @@ const hasTaskDescription = (task: any) => {
|
||||
<div
|
||||
v-for="task in tasks"
|
||||
:key="task.id || task.task_id || task.task"
|
||||
class="notification is-warning is-light mb-4"
|
||||
:class="{ 'is-success': completedTasks[task.id] }"
|
||||
class="notification is-warning is-light mb-4 self-serve-task-card"
|
||||
:class="{
|
||||
'is-success': completedTasks[task.id],
|
||||
'self-serve-task-card--image': !!getPrimaryImageAttachment(task),
|
||||
}"
|
||||
:data-testid="`self-serve-task-${task.id}`"
|
||||
>
|
||||
<div class="columns is-mobile is-vcentered">
|
||||
<template v-if="getPrimaryImageAttachment(task)">
|
||||
<div class="self-serve-task-image-wrapper">
|
||||
<img
|
||||
:src="getPrimaryImageAttachment(task)?.download_link"
|
||||
:alt="getPrimaryImageAttachment(task)?.content?.other || task.task"
|
||||
class="self-serve-task-image is-clickable"
|
||||
:data-testid="`self-serve-task-${task.id}-attachment-${getPrimaryImageAttachment(task)?.id}`"
|
||||
@click="emit('download-attachment', task.id, getPrimaryImageAttachment(task)?.id)"
|
||||
/>
|
||||
<div class="self-serve-task-image-overlay">
|
||||
<div class="self-serve-task-image-overlay-content">
|
||||
<div v-if="props.showCheckboxes" class="self-serve-task-image-overlay-checkbox">
|
||||
<b-field>
|
||||
<b-checkbox
|
||||
size="is-large"
|
||||
:model-value="completedTasks[task.id] === true"
|
||||
type="is-success"
|
||||
:data-testid="`self-serve-task-${task.id}-toggle`"
|
||||
@update:model-value="emit('toggle-task', task.id, $event)"
|
||||
@input="emit('toggle-task', task.id, $event)"
|
||||
/>
|
||||
</b-field>
|
||||
</div>
|
||||
<div class="self-serve-task-image-overlay-text">
|
||||
<p class="self-serve-task-title"><strong>{{ task.task }}</strong></p>
|
||||
<p v-if="hasTaskDescription(task)" class="self-serve-task-description">{{ task.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="getVisibleServices(task).length > 0" class="tags p-3 pb-0">
|
||||
<span v-for="service in getVisibleServices(task)" :key="service" class="tag is-success">{{ service }}</span>
|
||||
</div>
|
||||
<div v-if="getNonImageAttachments(task).length > 0" class="p-3 pt-2">
|
||||
<div v-for="attachment in getNonImageAttachments(task)" :key="attachment.id" class="mb-2">
|
||||
<div class="is-flex is-align-items-center">
|
||||
<b-icon pack="fas" icon="paperclip" size="is-small" class="mr-1" />
|
||||
<a
|
||||
class="is-size-7"
|
||||
:data-testid="`self-serve-task-${task.id}-attachment-${attachment.id}`"
|
||||
@click="emit('download-attachment', task.id, attachment.id)"
|
||||
>
|
||||
{{ attachment.content?.other || $t("self_wash.attached_file") }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="columns is-mobile is-vcentered">
|
||||
<div v-if="props.showCheckboxes" class="column is-narrow">
|
||||
<b-field>
|
||||
<b-checkbox
|
||||
@@ -56,8 +133,8 @@ const hasTaskDescription = (task: any) => {
|
||||
<div class="column">
|
||||
<p><strong>{{ task.task }}</strong></p>
|
||||
<p v-if="hasTaskDescription(task)" class="is-size-7">{{ task.description }}</p>
|
||||
<div v-if="task.services?.length" class="tags mt-2">
|
||||
<span v-for="service in task.services" :key="service" class="tag is-success">{{ service }}</span>
|
||||
<div v-if="getVisibleServices(task).length > 0" class="tags mt-2">
|
||||
<span v-for="service in getVisibleServices(task)" :key="service" class="tag is-success">{{ service }}</span>
|
||||
</div>
|
||||
<div v-if="task.attachments && task.attachments.length > 0" class="mt-2">
|
||||
<div v-for="attachment in task.attachments" :key="attachment.id" class="mb-2">
|
||||
@@ -88,3 +165,55 @@ const hasTaskDescription = (task: any) => {
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.self-serve-task-card--image {
|
||||
padding: 0.6rem;
|
||||
}
|
||||
|
||||
.self-serve-task-image-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.self-serve-task-image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.self-serve-task-image-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
background: linear-gradient(180deg, rgba(0, 0, 0, 0.08) 35%, rgba(0, 0, 0, 0.55) 100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.self-serve-task-image-overlay-content {
|
||||
margin: 0.75rem;
|
||||
width: calc(100% - 1.5rem);
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
border-radius: 8px;
|
||||
padding: 0.65rem 0.75rem;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.6rem;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.self-serve-task-image-overlay-checkbox :deep(.field) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.self-serve-task-title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.self-serve-task-description {
|
||||
margin: 0.25rem 0 0;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -33,12 +33,12 @@ const emitDownloadAttachment = (taskId: number, attachmentId: number) => {
|
||||
<b-icon pack="fas" icon="spinner" custom-class="fa-pulse" size="is-large" />
|
||||
<p>{{ $t("self_wash.loading_data") }}</p>
|
||||
</div>
|
||||
<div v-else class="mt-6">
|
||||
<div style="position: sticky; top: 0; background-color: white; z-index: 10; padding-top: 1rem; padding-bottom: 1rem; border-bottom: 1px solid #ddd; margin-bottom: 1rem;">
|
||||
<h1 class="title has-text-centered">{{ $t("self_wash.wash_in_progress") }}</h1>
|
||||
<h2 class="subtitle has-text-centered">
|
||||
<div v-else class="self-serve-tasks-content">
|
||||
<div class="self-serve-progress-header">
|
||||
<h1 class="title has-text-centered self-serve-progress-title">{{ $t("self_wash.wash_in_progress") }}</h1>
|
||||
<p class="self-serve-progress-timer">
|
||||
<strong data-testid="self-serve-elapsed">{{ formattedElapsed }}</strong>
|
||||
</h2>
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="dynamicImageUrl" class="mb-4">
|
||||
<img
|
||||
@@ -62,3 +62,32 @@ const emitDownloadAttachment = (taskId: number, attachmentId: number) => {
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.self-serve-tasks-content {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.self-serve-progress-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding: 0.75rem 0 0.9rem;
|
||||
margin-bottom: 1.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
.self-serve-progress-title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.self-serve-progress-timer {
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -783,11 +783,11 @@ watch(() => forceNearestDepartmentEvaluationId.value, () => {
|
||||
:clickable="clickableSteps[steps.WASH_IN_PROGRESS]()"
|
||||
:visible="currentStep === steps.WASH_IN_PROGRESS"
|
||||
>
|
||||
<div style="position: sticky; top: 0; background-color: white; z-index: 10; padding-top: 1rem; padding-bottom: 1rem; border-bottom: 1px solid #ddd; margin-bottom: 1rem;">
|
||||
<h1 class="title has-text-centered">{{ $t("self_wash.wash_in_progress") }}</h1>
|
||||
<h2 class="subtitle has-text-centered">
|
||||
<div class="wash-live-progress-header">
|
||||
<h1 class="title has-text-centered wash-live-progress-title">{{ $t("self_wash.wash_in_progress") }}</h1>
|
||||
<p class="wash-live-progress-timer">
|
||||
<strong data-testid="self-serve-live-elapsed">{{ formattedElapsed }}</strong>
|
||||
</h2>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<SelfServeGuidedInstructions
|
||||
@@ -890,3 +890,28 @@ watch(() => forceNearestDepartmentEvaluationId.value, () => {
|
||||
</div>
|
||||
</UserDashboardPageWrapper>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.wash-live-progress-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding: 0.75rem 0 0.9rem;
|
||||
margin-bottom: 1.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
.wash-live-progress-title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wash-live-progress-timer {
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user