mirror of
https://github.com/lukaszraczylo/ghtracker.git
synced 2026-10-14 00:58:36 +00:00
Alerts that match a configured action get a button labelled from the config. A confirm dialog opens first when the action defines confirm text. After a press the UI posts the alert, reloads the state through the existing poll path and shows the reported status as a chip, with an anchor when the status carries a link. The alert dialog comes from reka-ui through the usual shadcn-vue wrappers.
84 lines
2.8 KiB
Vue
84 lines
2.8 KiB
Vue
<script setup lang="ts">
|
|
import { ref } from 'vue'
|
|
import { faSpinner } from '@fortawesome/free-solid-svg-icons'
|
|
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
|
import {
|
|
AlertDialog,
|
|
AlertDialogAction,
|
|
AlertDialogCancel,
|
|
AlertDialogContent,
|
|
AlertDialogDescription,
|
|
AlertDialogFooter,
|
|
AlertDialogHeader,
|
|
AlertDialogTitle,
|
|
} from '@/components/ui/alert-dialog'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import { Button } from '@/components/ui/button'
|
|
import { actionKey, useTrackerStore } from '@/stores/tracker'
|
|
import type { ActionDef, ActionStatus, Row } from '@/lib/types'
|
|
|
|
const props = defineProps<{ row: Row }>()
|
|
const store = useTrackerStore()
|
|
|
|
// `asking` outlives the dialog so the confirm click still knows which action to run.
|
|
const asking = ref<ActionDef | null>(null)
|
|
const open = ref(false)
|
|
|
|
const keyOf = (a: ActionDef) => actionKey(a.id, props.row.repo, props.row.url)
|
|
const busy = (a: ActionDef) => store.runningActions.includes(keyOf(a))
|
|
const statusOf = (a: ActionDef): ActionStatus | undefined =>
|
|
props.row.actionStatus?.[a.id] ?? store.localStatus[keyOf(a)]
|
|
|
|
function run(a: ActionDef): void {
|
|
void store.runAction(a.id, props.row.repo, props.row.url)
|
|
}
|
|
|
|
function press(a: ActionDef): void {
|
|
if (!a.confirm) return run(a)
|
|
asking.value = a
|
|
open.value = true
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="mt-2 flex flex-wrap items-center gap-2" data-test="row-actions">
|
|
<template v-for="a in row.actions" :key="a.id">
|
|
<Button
|
|
variant="outline"
|
|
size="xs"
|
|
:disabled="busy(a)"
|
|
:aria-busy="busy(a)"
|
|
:data-test="`action-${a.id}`"
|
|
@click="press(a)"
|
|
>
|
|
<FontAwesomeIcon v-if="busy(a)" :icon="faSpinner" spin aria-hidden="true" />
|
|
{{ a.label }}
|
|
</Button>
|
|
<Badge v-if="busy(a)" variant="secondary" data-test="action-status">Sending</Badge>
|
|
<Badge v-else-if="statusOf(a)" variant="secondary" data-test="action-status">
|
|
<a
|
|
v-if="statusOf(a)?.link"
|
|
:href="statusOf(a)?.link"
|
|
class="underline-offset-4 hover:underline"
|
|
>{{ statusOf(a)?.label || statusOf(a)?.state }}</a
|
|
>
|
|
<template v-else>{{ statusOf(a)?.label || statusOf(a)?.state }}</template>
|
|
</Badge>
|
|
</template>
|
|
<AlertDialog v-model:open="open">
|
|
<AlertDialogContent>
|
|
<AlertDialogHeader>
|
|
<AlertDialogTitle>{{ asking?.label }}</AlertDialogTitle>
|
|
<AlertDialogDescription>{{ asking?.confirm }}</AlertDialogDescription>
|
|
</AlertDialogHeader>
|
|
<AlertDialogFooter>
|
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
<AlertDialogAction data-test="action-confirm" @click="asking && run(asking)">
|
|
{{ asking?.label }}
|
|
</AlertDialogAction>
|
|
</AlertDialogFooter>
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
</div>
|
|
</template>
|