diff --git a/frontend/components/admin/AdminBookStackTab.vue b/frontend/components/admin/AdminBookStackTab.vue index 9584353..e846930 100644 --- a/frontend/components/admin/AdminBookStackTab.vue +++ b/frontend/components/admin/AdminBookStackTab.vue @@ -10,15 +10,13 @@ input-class="w-full" /> -
- -
+
{ if (!statusTarget.value) return [] - const current = statusTarget.value.status - const allStatuses: { label: string; value: ClientTicketStatus }[] = [ - { label: t('clientTicket.status.new'), value: 'new' }, - { label: t('clientTicket.status.in_progress'), value: 'in_progress' }, - { label: t('clientTicket.status.done'), value: 'done' }, - { label: t('clientTicket.status.rejected'), value: 'rejected' }, - ] - return allStatuses.filter(s => { - if (s.value === current) return false - if ((current === 'done' || current === 'rejected') && s.value === 'new') return false - return true - }) + return getAvailableStatusTransitions(statusTarget.value.status, t) }) async function loadTickets() { diff --git a/frontend/components/project/ProjectGroupTab.vue b/frontend/components/project/ProjectGroupTab.vue index 240ce73..d8d457a 100644 --- a/frontend/components/project/ProjectGroupTab.vue +++ b/frontend/components/project/ProjectGroupTab.vue @@ -117,7 +117,7 @@ async function loadItems() { const [g, t, at] = await Promise.all([ groupService.getByProject(props.projectId), taskService.getByProject(props.projectId), - taskService.getByProjectArchived(props.projectId), + taskService.getByProject(props.projectId, true), ]) allGroups.value = g activeTasks.value = t diff --git a/frontend/components/task/TaskDocumentList.vue b/frontend/components/task/TaskDocumentList.vue index dd4098c..25b0e0e 100644 --- a/frontend/components/task/TaskDocumentList.vue +++ b/frontend/components/task/TaskDocumentList.vue @@ -28,7 +28,7 @@

{{ doc.originalName }}

-

{{ formatSize(doc.size) }}

+

{{ formatFileSize(doc.size) }}

@@ -47,6 +47,7 @@ diff --git a/frontend/components/task/TaskDocumentPreview.vue b/frontend/components/task/TaskDocumentPreview.vue index b9b8543..243c009 100644 --- a/frontend/components/task/TaskDocumentPreview.vue +++ b/frontend/components/task/TaskDocumentPreview.vue @@ -56,7 +56,7 @@

{{ document.originalName }}

-

{{ formatSize(document.size) }}

+

{{ formatFileSize(document.size) }}

import type { TaskDocument } from '~/services/dto/task-document' import { useTaskDocumentService } from '~/services/task-documents' +import { formatFileSize } from '~/utils/format' const props = defineProps<{ document: TaskDocument | null @@ -98,12 +99,6 @@ const downloadUrl = computed(() => props.document ? getDownloadUrl(props.documen const isImage = computed(() => props.document?.mimeType.startsWith('image/') ?? false) const isPdf = computed(() => props.document?.mimeType === 'application/pdf') -function formatSize(bytes: number): string { - if (bytes < 1024) return `${bytes} o` - if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} Ko` - return `${(bytes / (1024 * 1024)).toFixed(1)} Mo` -} - // Focus overlay for keyboard events watch(() => props.document, (doc) => { if (doc) { diff --git a/frontend/components/task/TaskModal.vue b/frontend/components/task/TaskModal.vue index 0ebdf95..fe6f082 100644 --- a/frontend/components/task/TaskModal.vue +++ b/frontend/components/task/TaskModal.vue @@ -154,7 +154,7 @@ /> props.modelValue, async (open) => { } catch { clientTickets.value = [] } + if (props.task?.project?.giteaOwner && props.task?.project?.giteaRepo && !giteaUrl.value) { + try { + const settings = await getGiteaSettings() + giteaUrl.value = settings.url ?? '' + } catch { + // Gitea not available + } + } } }) @@ -405,17 +413,6 @@ watch(() => props.task, (task) => { } }) -watch(() => props.modelValue, async (open) => { - if (open && props.task?.project?.giteaOwner && props.task?.project?.giteaRepo && !giteaUrl.value) { - try { - const settings = await getGiteaSettings() - giteaUrl.value = settings.url ?? '' - } catch { - // Gitea not available - } - } -}) - const { create, update, remove } = useTaskService() const { remove: removeDocument, getByTask: getDocumentsByTask } = useTaskDocumentService() const clientTicketService = useClientTicketService() @@ -440,7 +437,6 @@ function ticketStatusClass(status: string): string { } const localDocuments = ref([]) -const documents = computed(() => localDocuments.value) const previewDoc = ref(null) // Sync documents from task prop when modal opens or task changes @@ -455,7 +451,7 @@ async function refreshDocuments() { const previewIndex = computed(() => { if (!previewDoc.value) return -1 - return documents.value.findIndex(d => d.id === previewDoc.value!.id) + return localDocuments.value.findIndex(d => d.id === previewDoc.value!.id) }) function openPreview(doc: TaskDocument) { @@ -464,13 +460,13 @@ function openPreview(doc: TaskDocument) { function prevPreview() { if (previewIndex.value > 0) { - previewDoc.value = documents.value[previewIndex.value - 1] + previewDoc.value = localDocuments.value[previewIndex.value - 1] } } function nextPreview() { - if (previewIndex.value < documents.value.length - 1) { - previewDoc.value = documents.value[previewIndex.value + 1] + if (previewIndex.value < localDocuments.value.length - 1) { + previewDoc.value = localDocuments.value[previewIndex.value + 1] } } diff --git a/frontend/composables/useApi.ts b/frontend/composables/useApi.ts index b81a602..760df5b 100644 --- a/frontend/composables/useApi.ts +++ b/frontend/composables/useApi.ts @@ -29,13 +29,14 @@ export type ApiFetchOptions = toastSuccessKey?: string } -export const useApi = (): ApiClient => { +let isHandlingUnauthorized = false + +export function useApi(): ApiClient { const config = useRuntimeConfig() const baseURL = config.public.apiBase || '/api' const toast = useToast() const auth = useAuthStore() const nuxtApp = useNuxtApp() - let isHandlingUnauthorized = false const i18n = nuxtApp.$i18n as | { t: (key: string) => string @@ -45,7 +46,7 @@ export const useApi = (): ApiClient => { const t = (key: string) => (i18n?.t ? String(i18n.t(key)) : key) const te = (key: string) => (i18n?.te ? i18n.te(key) : false) - const extractErrorMessage = (error: unknown, responseData?: unknown): string => { + function extractErrorMessage(error: unknown, responseData?: unknown): string { const data = responseData ?? (error as FetchError)?.data if (typeof data === 'string') { @@ -169,11 +170,11 @@ export const useApi = (): ApiClient => { } }) - const request = ( + function request( method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', url: string, options: ApiFetchOptions<'json'> = {} - ) => { + ) { const needsJsonBody = method === 'POST' || method === 'PUT' const needsMergePatch = method === 'PATCH' diff --git a/frontend/composables/useAppVersion.ts b/frontend/composables/useAppVersion.ts index 803278d..921f546 100644 --- a/frontend/composables/useAppVersion.ts +++ b/frontend/composables/useAppVersion.ts @@ -1,8 +1,8 @@ -export const useAppVersion = () => { +export function useAppVersion() { const api = useApi() const version = useState('app-version', () => null) - const load = async () => { + async function load(): Promise { if (version.value) { return version.value } diff --git a/frontend/composables/useClientTicketHelpers.ts b/frontend/composables/useClientTicketHelpers.ts index 2742eef..feab80f 100644 --- a/frontend/composables/useClientTicketHelpers.ts +++ b/frontend/composables/useClientTicketHelpers.ts @@ -1,3 +1,5 @@ +import type { ClientTicketStatus } from '~/services/dto/client-ticket' + export function useClientTicketHelpers() { function typeBadgeClass(type: string): string { switch (type) { @@ -25,5 +27,22 @@ export function useClientTicketHelpers() { }) } - return { typeBadgeClass, statusBadgeClass, formatDate } + function getAvailableStatusTransitions( + current: ClientTicketStatus, + t: (key: string) => string, + ): { label: string; value: ClientTicketStatus }[] { + const allStatuses: { label: string; value: ClientTicketStatus }[] = [ + { label: t('clientTicket.status.new'), value: 'new' }, + { label: t('clientTicket.status.in_progress'), value: 'in_progress' }, + { label: t('clientTicket.status.done'), value: 'done' }, + { label: t('clientTicket.status.rejected'), value: 'rejected' }, + ] + return allStatuses.filter(s => { + if (s.value === current) return false + if ((current === 'done' || current === 'rejected') && s.value === 'new') return false + return true + }) + } + + return { typeBadgeClass, statusBadgeClass, formatDate, getAvailableStatusTransitions } } diff --git a/frontend/layouts/auth.vue b/frontend/layouts/auth.vue index 009acb7..117c591 100644 --- a/frontend/layouts/auth.vue +++ b/frontend/layouts/auth.vue @@ -5,7 +5,3 @@
- - diff --git a/frontend/layouts/default.vue b/frontend/layouts/default.vue index 1616d11..bbf64ae 100644 --- a/frontend/layouts/default.vue +++ b/frontend/layouts/default.vue @@ -242,11 +242,6 @@ function onCompleteSaved() { timerStore.clearPendingEntry() }) } - -const handleLogout = async () => { - await auth.logout() - await navigateTo('/login') -}