Files
crewli/apps/app/src/components/common/ImageUploadField.vue
bert.hausmans 47bd533179 style(app): apply eslint --fix to Tier 1 (Vue templates)
WS-3 session 1b-i Tier 1.

Scope: src/components/**, src/pages/**, src/layouts/**, src/views/**
restricted to *.vue files. Mechanical formatting only — predominantly
vue/html-indent (506 fixes in CrowdListDetailPanel.vue alone),
padding-line-between-statements, antfu/if-newline.

Excludes (per session prompt):
- apps/app/vite.config.ts (Tier 3)
- apps/app/themeConfig.ts (Tier 3)
- apps/app/vitest.config.ts (Tier 3)
- All TypeScript-only files in src/composables, src/lib, src/stores,
  src/plugins, src/types (Tier 2 — separate commit)

Includes session 1a layouts (PortalLayout.vue, PublicLayout.vue) where
2 'lines-around-comment' errors were flagged in the previous 1b-i
pre-flight inspection.

Tests + typecheck verified green post-fix:
- apps/app vitest: 49 passed (unchanged)
- apps/app vue-tsc: clean (unchanged)
- apps/portal vitest: 113 passed (unchanged — not touched)
- backend pest: 1486 passed (unchanged — not touched)

Lint baseline progression:
- Pre-Tier-1: 1451 problems
- Post-Tier-1: 422 problems

Visual smoke status:
- NOT YET SMOKED — Bert to verify before merge. This Claude Code
  session has no UI access; cannot run pnpm dev and click through
  affected routes. The high-traffic candidates are
  CrowdListDetailPanel (506 fixes), AssignPersonDialog (44),
  ShiftDetailPanel (36), and the events / form-failures pages.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-29 11:04:46 +02:00

136 lines
2.7 KiB
Vue

<script setup lang="ts">
import { apiClient } from '@/lib/axios'
interface Props {
modelValue: string | null
label?: string
purpose: 'logo' | 'banner' | 'icon' | 'avatar'
hint?: string
previewHeight?: number
}
const props = withDefaults(defineProps<Props>(), {
label: undefined,
hint: undefined,
previewHeight: 120,
})
const emit = defineEmits<{
'update:modelValue': [url: string | null]
}>()
const fileInput = ref<HTMLInputElement | null>(null)
const uploading = ref(false)
const error = ref('')
function triggerFileInput() {
fileInput.value?.click()
}
async function onFileSelected(event: Event) {
const input = event.target as HTMLInputElement
const file = input.files?.[0]
if (!file)
return
uploading.value = true
error.value = ''
try {
const formData = new FormData()
formData.append('file', file)
formData.append('purpose', props.purpose)
const { data } = await apiClient.post<{ data: { url: string } }>('/upload/image', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
})
emit('update:modelValue', data.data.url)
}
catch (e: unknown) {
const axiosError = e as { response?: { data?: { message?: string } } }
error.value = axiosError.response?.data?.message ?? 'Upload mislukt'
}
finally {
uploading.value = false
if (input)
input.value = ''
}
}
function remove() {
emit('update:modelValue', null)
error.value = ''
}
</script>
<template>
<div>
<label
v-if="label"
class="text-body-2 text-medium-emphasis d-block mb-1"
>
{{ label }}
</label>
<div
v-if="modelValue"
class="mb-2"
>
<VImg
:src="modelValue"
:max-height="previewHeight"
max-width="300"
class="rounded border"
cover
/>
</div>
<div class="d-flex align-center gap-2">
<VBtn
:variant="modelValue ? 'tonal' : 'elevated'"
color="primary"
size="small"
prepend-icon="tabler-upload"
:loading="uploading"
@click="triggerFileInput"
>
{{ modelValue ? 'Vervangen' : 'Uploaden' }}
</VBtn>
<VBtn
v-if="modelValue"
variant="text"
color="error"
size="small"
prepend-icon="tabler-trash"
@click="remove"
>
Verwijderen
</VBtn>
</div>
<input
ref="fileInput"
type="file"
accept="image/*"
style="display: none;"
@change="onFileSelected"
>
<div
v-if="error"
class="text-caption text-error mt-1"
>
{{ error }}
</div>
<div
v-else-if="hint"
class="text-caption text-medium-emphasis mt-1"
>
{{ hint }}
</div>
</div>
</template>