feat: add tracking input, save button, and PostNord link to admin dashboard
- api/admin.ts: updateTracking(orderId, trackingId) calls PATCH
/api/admin/orders/{id} with JSON { trackingId }
- AdminPage.vue expanded row: add "Spårnings-ID" section below
Brevtext with text input, save button, and PostNord link
- trackingInputValues reactive map tracks per-order input state
- toggleExpand initialises trackingInputValues[orderId] from
order.trackingId on first expand
- handleTrackingSave: PATCH API call with optimistic local update,
reverts on error, shows red inline error
- PostNord link (<a target="_blank">): https://www.postnord.se/
verktyg/spara/?id={trackingId}, only visible when trackingId
is non-null
- trackingError ref for inline error state
- CSS: tracking section styling, input focus ring, blue save button
This commit is contained in:
parent
ebab892e93
commit
dcc466439e
2 changed files with 156 additions and 3 deletions
|
|
@ -24,3 +24,13 @@ export function updateOrderStatus(
|
|||
body: JSON.stringify({ status }),
|
||||
})
|
||||
}
|
||||
|
||||
export function updateTracking(
|
||||
orderId: string,
|
||||
trackingId: string | null,
|
||||
): Promise<AdminOrder> {
|
||||
return request<AdminOrder>(`/admin/orders/${orderId}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ trackingId }),
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,19 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { fetchAllOrders, updateOrderStatus, type AdminOrder } from '@/api/admin'
|
||||
import { ref, onMounted, reactive } from 'vue'
|
||||
import {
|
||||
fetchAllOrders,
|
||||
updateOrderStatus,
|
||||
updateTracking,
|
||||
type AdminOrder,
|
||||
} from '@/api/admin'
|
||||
|
||||
const orders = ref<AdminOrder[]>([])
|
||||
const expandedOrderId = ref<string | null>(null)
|
||||
const loading = ref(true)
|
||||
const error = ref('')
|
||||
const statusError = ref('')
|
||||
const trackingError = ref('')
|
||||
const trackingInputValues = reactive<Record<string, string>>({})
|
||||
|
||||
const statusLabels: Record<string, string> = {
|
||||
pending_payment: 'Väntar på betalning',
|
||||
|
|
@ -44,7 +51,15 @@ function formatDate(iso: string): string {
|
|||
}
|
||||
|
||||
function toggleExpand(orderId: string) {
|
||||
expandedOrderId.value = expandedOrderId.value === orderId ? null : orderId
|
||||
if (expandedOrderId.value === orderId) {
|
||||
expandedOrderId.value = null
|
||||
} else {
|
||||
expandedOrderId.value = orderId
|
||||
const order = orders.value.find((o) => o.id === orderId)
|
||||
if (order && !(orderId in trackingInputValues)) {
|
||||
trackingInputValues[orderId] = order.trackingId ?? ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleStatusChange(orderId: string, newStatus: string) {
|
||||
|
|
@ -63,6 +78,23 @@ async function handleStatusChange(orderId: string, newStatus: string) {
|
|||
}
|
||||
}
|
||||
|
||||
async function handleTrackingSave(orderId: string) {
|
||||
const newTrackingId = trackingInputValues[orderId]?.trim() || null
|
||||
const order = orders.value.find((o) => o.id === orderId)
|
||||
if (!order) return
|
||||
|
||||
const previousTrackingId = order.trackingId
|
||||
order.trackingId = newTrackingId
|
||||
trackingError.value = ''
|
||||
|
||||
try {
|
||||
await updateTracking(orderId, newTrackingId)
|
||||
} catch {
|
||||
order.trackingId = previousTrackingId
|
||||
trackingError.value = 'Kunde inte spara spårnings-ID. Försök igen.'
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
orders.value = await fetchAllOrders()
|
||||
|
|
@ -153,6 +185,51 @@ onMounted(async () => {
|
|||
{{ order.letterText }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-dashboard__tracking">
|
||||
<div class="admin-dashboard__tracking-header">
|
||||
<span class="admin-dashboard__tracking-label"
|
||||
>Spårnings-ID</span
|
||||
>
|
||||
<a
|
||||
v-if="order.trackingId"
|
||||
class="admin-dashboard__tracking-link"
|
||||
:href="`https://www.postnord.se/verktyg/spara/?id=${order.trackingId}`"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
@click.stop
|
||||
>
|
||||
Spåra hos PostNord ↗
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p v-if="trackingError" class="admin-dashboard__status-error">
|
||||
{{ trackingError }}
|
||||
</p>
|
||||
|
||||
<div class="admin-dashboard__tracking-input-row">
|
||||
<input
|
||||
class="admin-dashboard__tracking-input"
|
||||
type="text"
|
||||
:value="
|
||||
trackingInputValues[order.id] ?? order.trackingId ?? ''
|
||||
"
|
||||
placeholder="PN..."
|
||||
@input="
|
||||
trackingInputValues[order.id] = (
|
||||
$event.target as HTMLInputElement
|
||||
).value
|
||||
"
|
||||
@click.stop
|
||||
/>
|
||||
<button
|
||||
class="admin-dashboard__tracking-save"
|
||||
@click.stop="handleTrackingSave(order.id)"
|
||||
>
|
||||
Spara spårning
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
|
@ -321,4 +398,70 @@ onMounted(async () => {
|
|||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.admin-dashboard__tracking {
|
||||
padding: 1rem 1.25rem;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.admin-dashboard__tracking-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.admin-dashboard__tracking-label {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: #a0aec0;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.admin-dashboard__tracking-link {
|
||||
font-size: 0.8125rem;
|
||||
color: #4299e1;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.admin-dashboard__tracking-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.admin-dashboard__tracking-input-row {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.admin-dashboard__tracking-input {
|
||||
flex: 1;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.8125rem;
|
||||
color: #4a5568;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.admin-dashboard__tracking-input:focus {
|
||||
border-color: #4299e1;
|
||||
box-shadow: 0 0 0 2px rgba(66, 153, 225, 0.2);
|
||||
}
|
||||
|
||||
.admin-dashboard__tracking-save {
|
||||
padding: 0.5rem 1rem;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
background: #4299e1;
|
||||
color: #fff;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.admin-dashboard__tracking-save:hover {
|
||||
background: #3182ce;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in a new issue