feat: mobile ui (#1096)

This commit is contained in:
Jonas Plum
2024-08-07 22:18:59 +02:00
committed by GitHub
parent 96b7a9604c
commit a2dd6c05e6
68 changed files with 668 additions and 1315 deletions

View File

@@ -0,0 +1,43 @@
package migrations
import (
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/daos"
"github.com/pocketbase/pocketbase/tools/types"
)
const dashboardCountsViewUpdateQuery = `SELECT id, count FROM (
SELECT 'users' as id, COUNT(users.id) as count FROM users
UNION
SELECT 'tickets' as id, COUNT(tickets.id) as count FROM tickets
UNION
SELECT 'tasks' as id, COUNT(tasks.id) as count FROM tasks
UNION
SELECT 'reactions' as id, COUNT(reactions.id) as count FROM reactions
) as counts;`
func dashboardCountsViewUpdateUp(db dbx.Builder) error {
dao := daos.New(db)
collection, err := dao.FindCollectionByNameOrId(dashboardCountsViewName)
if err != nil {
return err
}
collection.Options = types.JsonMap{"query": dashboardCountsViewUpdateQuery}
return dao.SaveCollection(collection)
}
func dashboardCountsViewUpdateDown(db dbx.Builder) error {
dao := daos.New(db)
collection, err := dao.FindCollectionByNameOrId(dashboardCountsViewName)
if err != nil {
return err
}
collection.Options = types.JsonMap{"query": dashboardCountsViewQuery}
return dao.SaveCollection(collection)
}

View File

@@ -12,4 +12,5 @@ func Register() {
migrations.Register(reactionsUp, reactionsDown, "1700000005_reactions.go")
migrations.Register(systemuserUp, systemuserDown, "1700000006_systemuser.go")
migrations.Register(searchViewUp, searchViewDown, "1700000007_search_view.go")
migrations.Register(dashboardCountsViewUpdateUp, dashboardCountsViewUpdateDown, "1700000008_dashboardview.go")
}

View File

@@ -7,7 +7,7 @@
<title>Catalyst</title>
</head>
<body>
<div id="app" class="h-screen w-screen"></div>
<div id="app" class="h-screen w-screen overflow-hidden"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@@ -7,17 +7,16 @@ defineProps<{
isPending: boolean
isError: boolean
error: Error | null
value: any
}>()
</script>
<template>
<div v-if="isPending" class="flex justify-center">
<LoaderCircle class="h-16 w-16 animate-spin text-primary" />
<div v-if="isPending" class="flex h-full w-full">
<LoaderCircle class="m-auto h-16 w-16 animate-spin text-primary" />
</div>
<Alert v-else-if="isError" variant="destructive" class="mb-4">
<AlertTitle>Error</AlertTitle>
<AlertDescription>{{ error }}</AlertDescription>
</Alert>
<slot v-else-if="value" />
<slot v-else />
</template>

View File

@@ -1,5 +1,6 @@
<script setup lang="ts">
import PanelListElement from '@/components/common/PanelListElement.vue'
import TanView from '@/components/TanView.vue'
import PanelListElement from '@/components/layout/PanelListElement.vue'
import { buttonVariants } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
@@ -32,6 +33,7 @@ const {
<template>
<div class="flex flex-col gap-2">
<Card>
<TanView :isError="isError" :isPending="isPending" :error="error">
<div v-if="tasks && tasks.length === 0" class="p-2 text-center text-sm text-gray-500">
No open tasks
</div>
@@ -42,7 +44,12 @@ const {
name: 'tickets',
params: { type: task.expand.ticket.type, id: task.expand.ticket.id }
}"
:class="cn(buttonVariants({ variant: 'outline', size: 'sm' }), 'ml-auto h-8')"
:class="
cn(
buttonVariants({ variant: 'outline', size: 'sm' }),
'h-8 w-full sm:ml-auto sm:w-auto'
)
"
>
<span class="flex flex-row items-center text-sm text-gray-500">
Go to {{ task.expand.ticket.name }}
@@ -50,6 +57,7 @@ const {
</span>
</RouterLink>
</PanelListElement>
</TanView>
</Card>
</div>
</template>

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import PanelListElement from '@/components/common/PanelListElement.vue'
import PanelListElement from '@/components/layout/PanelListElement.vue'
import { buttonVariants } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { Separator } from '@/components/ui/separator'
@@ -42,16 +42,21 @@ const age = (ticket: Ticket) =>
</div>
<PanelListElement v-else v-for="ticket in tickets" :key="ticket.id" class="gap-2 pr-1">
<span>{{ ticket.name }}</span>
<Separator orientation="vertical" class="h-4" />
<Separator orientation="vertical" class="hidden h-4 sm:block" />
<span class="text-sm text-muted-foreground">{{ ticket.expand.type.singular }}</span>
<Separator orientation="vertical" class="h-4" />
<Separator orientation="vertical" class="hidden h-4 sm:block" />
<span class="text-sm text-muted-foreground">Open since {{ age(ticket) }} days</span>
<RouterLink
:to="{
name: 'tickets',
params: { type: ticket.type, id: ticket.id }
}"
:class="cn(buttonVariants({ variant: 'outline', size: 'sm' }), 'ml-auto h-8')"
:class="
cn(
buttonVariants({ variant: 'outline', size: 'sm' }),
'h-8 w-full sm:ml-auto sm:w-auto'
)
"
>
<span class="flex flex-row items-center text-sm text-gray-500">
Go to {{ ticket.name }}

View File

@@ -43,7 +43,7 @@ const ticketsPerWeek = computed(() => {
</script>
<template>
<TanView :isError="isError" :isPending="isPending" :error="error" :value="tickets">
<TanView :isError="isError" :isPending="isPending" :error="error">
<LineChart class="h-40" :data="ticketsPerWeek" index="week" :categories="['count']" />
</TanView>
</template>

View File

@@ -30,7 +30,7 @@ const namedTypes = computed(() => {
</script>
<template>
<TanView :isError="isError" :isPending="isPending" :error="error" :value="namedTypes">
<TanView :isError="isError" :isPending="isPending" :error="error">
<div v-if="namedTypes" class="flex flex-1 items-center">
<DonutChart index="plural" type="donut" category="count" :data="namedTypes" />
</div>

View File

@@ -3,9 +3,6 @@ import ShortCut from '@/components/ShortCut.vue'
import { ref } from 'vue'
// import { Textarea } from '@/components/ui/textarea'
// import { Input } from '@/components/ui/input'
const model = defineModel({
type: String
})

View File

@@ -0,0 +1,5 @@
<template>
<div class="flex flex-1 items-start justify-start overflow-y-auto overflow-x-hidden">
<slot />
</div>
</template>

View File

@@ -0,0 +1,13 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
defineProps<{
small?: boolean
}>()
</script>
<template>
<div :class="cn('mx-auto flex w-full max-w-[72rem] gap-4 p-4', small && 'max-w-[47rem]')">
<slot />
</div>
</template>

View File

@@ -0,0 +1,25 @@
<script setup lang="ts">
import { Separator } from '@/components/ui/separator'
import { cn } from '@/lib/utils'
defineProps<{
title?: string
nowrap?: boolean
hideSeparator?: boolean
}>()
</script>
<template>
<div
:class="
cn('flex min-h-14 flex-wrap items-center gap-2 bg-background p-2', nowrap && 'flex-nowrap')
"
>
<h1 v-if="title" class="text-xl font-bold">
{{ title }}
</h1>
<slot />
</div>
<Separator v-if="!hideSeparator" />
</template>

View File

@@ -12,7 +12,7 @@ const props = defineProps<{
<div
:class="
cn(
'flex w-full items-center border-t px-2 py-1 first:rounded-t first:border-none last:rounded-b',
'flex w-full flex-col items-start border-t px-2 py-1 first:rounded-t first:border-none last:rounded-b sm:flex-row sm:items-center',
props.class
)
"

View File

@@ -8,16 +8,28 @@ import { Separator } from '@/components/ui/separator'
import { Menu } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
import { useCatalystStore } from '@/store/catalyst'
const catalystStore = useCatalystStore()
</script>
<template>
<div
:class="
cn(
'flex min-w-48 shrink-0 flex-col border-r bg-popover', // transition-all duration-300 ease-in-out',
catalystStore.sidebarCollapsed && 'min-w-[50px]'
)
"
>
<div class="flex h-[57px] items-center border-b bg-background">
<CatalystLogo
class="size-8"
:class="{ 'flex-1': catalystStore.sidebarCollapsed, 'mx-3': !catalystStore.sidebarCollapsed }"
:class="{
'flex-1': catalystStore.sidebarCollapsed,
'mx-3': !catalystStore.sidebarCollapsed
}"
/>
<h1 class="text-xl font-bold" v-if="!catalystStore.sidebarCollapsed">Catalyst</h1>
</div>
@@ -52,13 +64,22 @@ const catalystStore = useCatalystStore()
<Separator />
<UserDropDown :is-collapsed="catalystStore.sidebarCollapsed" />
<Separator />
<div :class="cn('flex h-14 items-center px-3', !catalystStore.sidebarCollapsed && 'px-2')">
<Button
variant="ghost"
@click="catalystStore.toggleSidebar()"
size="sm"
class="m-2 justify-start px-3.5"
size="default"
:class="
cn(
'p-0',
catalystStore.sidebarCollapsed && 'w-9',
!catalystStore.sidebarCollapsed && 'w-full justify-start px-3'
)
"
>
<Menu class="size-4" />
<span v-if="!catalystStore.sidebarCollapsed" class="ml-2">Toggle Sidebar</span>
</Button>
</div>
</div>
</template>

View File

@@ -3,30 +3,38 @@ import SideBar from '@/components/layout/SideBar.vue'
import { TooltipProvider } from '@/components/ui/tooltip'
import { cn } from '@/lib/utils'
import { useCatalystStore } from '@/store/catalyst'
const catalystStore = useCatalystStore()
defineProps<{
showDetails?: boolean
}>()
</script>
<template>
<TooltipProvider :delay-duration="0">
<div class="flex h-full flex-row items-stretch bg-muted/40">
<SideBar />
<div
:class="
cn(
'flex min-w-48 flex-col border-r bg-popover', // transition-all duration-300 ease-in-out',
catalystStore.sidebarCollapsed && 'min-w-[50px]'
'w-full flex-initial border-r sm:w-72',
!showDetails && 'flex',
showDetails && 'hidden sm:flex'
)
"
>
<SideBar />
</div>
<div class="w-72 flex-initial border-r">
<div class="flex h-full w-full flex-col">
<slot name="list" />
</div>
<div class="flex-1">
</div>
<div
:class="
cn('flex-1 overflow-hidden', !showDetails && 'hidden sm:flex', showDetails && 'flex')
"
>
<div class="flex h-full w-full flex-1 flex-col">
<slot name="single" />
</div>
</div>
</div>
</TooltipProvider>
</template>

View File

@@ -1,27 +1,15 @@
<script lang="ts" setup>
import SideBar from '@/components/layout/SideBar.vue'
import { TooltipProvider } from '@/components/ui/tooltip'
import { cn } from '@/lib/utils'
import { useCatalystStore } from '@/store/catalyst'
const catalystStore = useCatalystStore()
</script>
<template>
<TooltipProvider :delay-duration="0">
<div class="flex h-full flex-row items-stretch bg-muted/40">
<div
:class="
cn(
'flex min-w-48 flex-col border-r bg-popover', // transition-all duration-300 ease-in-out',
catalystStore.sidebarCollapsed && 'min-w-[50px]'
)
"
>
<SideBar />
</div>
<div class="flex h-full w-full flex-col">
<slot />
</div>
</div>
</TooltipProvider>
</template>

View File

@@ -1,11 +1,15 @@
<script setup lang="ts">
import TanView from '@/components/TanView.vue'
import DeleteDialog from '@/components/common/DeleteDialog.vue'
import ColumnBody from '@/components/layout/ColumnBody.vue'
import ColumnBodyContainer from '@/components/layout/ColumnBodyContainer.vue'
import ColumnHeader from '@/components/layout/ColumnHeader.vue'
import ReactionForm from '@/components/reaction/ReactionForm.vue'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Separator } from '@/components/ui/separator'
import { Button } from '@/components/ui/button'
import { toast } from '@/components/ui/toast'
import { ChevronLeft } from 'lucide-vue-next'
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
import { onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
@@ -72,9 +76,12 @@ onUnmounted(() => {
</script>
<template>
<TanView :isError="isError" :isPending="isPending" :error="error" :value="reaction">
<div class="flex h-full flex-1 flex-col overflow-hidden">
<div class="flex items-center bg-background px-4 py-2">
<TanView :isError="isError" :isPending="isPending" :error="error">
<ColumnHeader>
<Button @click="router.push({ name: 'reactions' })" variant="outline" class="sm:hidden">
<ChevronLeft class="mr-2 size-4" />
Back
</Button>
<div class="ml-auto">
<DeleteDialog
v-if="reaction"
@@ -86,14 +93,12 @@ onUnmounted(() => {
:queryKey="['reactions']"
/>
</div>
</div>
<Separator />
</ColumnHeader>
<ScrollArea v-if="reaction" class="flex-1">
<div class="flex max-w-[640px] flex-col gap-4 p-4">
<ColumnBody v-if="reaction">
<ColumnBodyContainer small>
<ReactionForm :reaction="reaction" @submit="updateReactionMutation.mutate" />
</div>
</ScrollArea>
</div>
</ColumnBodyContainer>
</ColumnBody>
</TanView>
</template>

View File

@@ -239,7 +239,7 @@ const curlExample = computed(() => {
</script>
<template>
<form @submit="onSubmit" class="flex flex-col items-start gap-4">
<form @submit="onSubmit" class="flex w-full flex-col items-start gap-4">
<FormField name="name" v-slot="{ componentField }" validate-on-input>
<FormItem class="w-full">
<FormLabel for="name" class="text-right">Name</FormLabel>

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
import TanView from '@/components/TanView.vue'
import ResourceListElement from '@/components/common/ResourceListElement.vue'
import ColumnHeader from '@/components/layout/ColumnHeader.vue'
import ResourceListElement from '@/components/layout/ResourceListElement.vue'
import { Button } from '@/components/ui/button'
import { Separator } from '@/components/ui/separator'
import { useQuery, useQueryClient } from '@tanstack/vue-query'
import { onMounted } from 'vue'
@@ -63,16 +63,13 @@ onMounted(() => {
</script>
<template>
<TanView :isError="isError" :isPending="isPending" :error="error" :value="reactions">
<div class="flex h-screen flex-col">
<div class="flex items-center bg-background px-4 py-2">
<h1 class="text-xl font-bold">Reactions</h1>
<TanView :isError="isError" :isPending="isPending" :error="error">
<ColumnHeader title="Reactions">
<div class="ml-auto">
<Button variant="ghost" @click="openNew"> New Reaction</Button>
<Button variant="ghost" @click="openNew">New Reaction</Button>
</div>
</div>
<Separator />
<div class="mt-2 flex flex-1 flex-col gap-2 p-4 pt-0">
</ColumnHeader>
<div class="mt-2 flex flex-1 flex-col gap-2 p-2 pt-0">
<TransitionGroup name="list" appear>
<ResourceListElement
v-for="reaction in reactions"
@@ -89,6 +86,5 @@ onMounted(() => {
</ResourceListElement>
</TransitionGroup>
</div>
</div>
</TanView>
</template>

View File

@@ -1,7 +1,11 @@
<script setup lang="ts">
import ColumnBody from '@/components/layout/ColumnBody.vue'
import ColumnBodyContainer from '@/components/layout/ColumnBodyContainer.vue'
import ColumnHeader from '@/components/layout/ColumnHeader.vue'
import ReactionForm from '@/components/reaction/ReactionForm.vue'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Separator } from '@/components/ui/separator'
import { Button } from '@/components/ui/button'
import { ChevronLeft } from 'lucide-vue-next'
import { useMutation, useQueryClient } from '@tanstack/vue-query'
import { useRouter } from 'vue-router'
@@ -24,14 +28,16 @@ const addReactionMutation = useMutation({
</script>
<template>
<div class="flex h-full flex-1 flex-col overflow-hidden">
<div class="flex min-h-14 items-center bg-background px-4 py-2"></div>
<Separator />
<ColumnHeader>
<Button @click="router.push({ name: 'reactions' })" variant="outline" class="sm:hidden">
<ChevronLeft class="mr-2 size-4" />
Back
</Button>
</ColumnHeader>
<ScrollArea class="flex-1">
<div class="flex max-w-[640px] flex-col gap-4 p-4">
<ColumnBody>
<ColumnBodyContainer small>
<ReactionForm @submit="addReactionMutation.mutate" />
</div>
</ScrollArea>
</div>
</ColumnBodyContainer>
</ColumnBody>
</template>

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import Icon from '@/components/Icon.vue'
import DeleteDialog from '@/components/common/DeleteDialog.vue'
import ColumnHeader from '@/components/layout/ColumnHeader.vue'
import TicketCloseDialog from '@/components/ticket/TicketCloseDialog.vue'
import TicketUserSelect from '@/components/ticket/TicketUserSelect.vue'
import { Button } from '@/components/ui/button'
@@ -12,7 +13,7 @@ import {
} from '@/components/ui/dropdown-menu'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { Check, CircleDot, Repeat } from 'lucide-vue-next'
import { Check, ChevronLeft, CircleDot, Repeat } from 'lucide-vue-next'
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
import { computed, ref } from 'vue'
@@ -49,7 +50,7 @@ const changeTypeMutation = useMutation({
}),
onSuccess: (data: Ticket) => {
queryClient.invalidateQueries({ queryKey: ['tickets'] })
router.push({ name: 'tickets', params: { type: data.type, id: props.ticket.id } })
// router.push({ name: 'tickets', params: { type: data.type, id: props.ticket.id } })
},
onError: handleError
})
@@ -74,8 +75,15 @@ const closeTicketDialogOpen = ref(false)
</script>
<template>
<div class="flex items-center justify-between bg-background p-2">
<div class="flex items-center gap-2">
<ColumnHeader>
<Button
@click="router.push({ name: 'tickets', params: { type: ticket.type } })"
variant="outline"
class="sm:hidden"
>
<ChevronLeft class="mr-2 size-4" />
Back
</Button>
<Tooltip>
<TooltipTrigger as-child>
<div>
@@ -141,7 +149,7 @@ const closeTicketDialogOpen = ref(false)
</TooltipTrigger>
<TooltipContent>Change User</TooltipContent>
</Tooltip>
</div>
<div class="-mx-1 flex-1" />
<DeleteDialog
v-if="ticket"
:collection="'tickets'"
@@ -151,5 +159,5 @@ const closeTicketDialogOpen = ref(false)
:to="{ name: 'tickets' }"
:queryKey="['tickets']"
/>
</div>
</ColumnHeader>
</template>

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import ColumnHeader from '@/components/layout/ColumnHeader.vue'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
@@ -38,7 +39,7 @@ const closeButtonDisabled = false // computed(() => !props.ticket.open || messag
</script>
<template>
<div class="flex items-center justify-between gap-2 bg-background p-2">
<ColumnHeader nowrap hideSeparator>
<Input v-if="ticket.open" v-model="resolution" placeholder="Closing reason" />
<div v-else class="flex-1">
<p class="ml-2 text-gray-500">Closed: {{ ticket.resolution }}</p>
@@ -56,5 +57,5 @@ const closeButtonDisabled = false // computed(() => !props.ticket.open || messag
: 'Reopen ' + props.ticket.expand.type.singular
}}
</Button>
</div>
</ColumnHeader>
</template>

View File

@@ -2,6 +2,8 @@
import TanView from '@/components/TanView.vue'
import JSONSchemaFormFields from '@/components/form/JSONSchemaFormFields.vue'
import DynamicMDEditor from '@/components/input/DynamicMDEditor.vue'
import ColumnBody from '@/components/layout/ColumnBody.vue'
import ColumnBodyContainer from '@/components/layout/ColumnBodyContainer.vue'
import StatusIcon from '@/components/ticket/StatusIcon.vue'
import TicketActionBar from '@/components/ticket/TicketActionBar.vue'
import TicketCloseBar from '@/components/ticket/TicketCloseBar.vue'
@@ -15,14 +17,13 @@ import TicketTimeline from '@/components/ticket/timeline/TicketTimeline.vue'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Separator } from '@/components/ui/separator'
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Edit } from 'lucide-vue-next'
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { computed, ref } from 'vue'
import { useRoute } from 'vue-router'
import { pb } from '@/lib/pocketbase'
@@ -100,15 +101,20 @@ const updateDescription = (value: string) => (message.value = value)
</script>
<template>
<TanView :isError="isError" :isPending="isPending" :error="error" :value="ticket">
<div v-if="ticket" class="flex h-full flex-col">
<TicketActionBar :ticket="ticket" />
<Separator />
<div class="flex w-full max-w-7xl flex-1 flex-col overflow-hidden xl:m-auto xl:flex-row">
<div class="flex flex-1 flex-col gap-4 px-4 pt-4">
<TanView :isError="isError" :isPending="isPending" :error="error">
<template v-if="ticket">
<TicketActionBar :ticket="ticket" class="shrink-0" />
<ColumnBody>
<ColumnBodyContainer class="flex-col gap-4 xl:flex-row">
<div class="flex flex-1 flex-col gap-4">
<TicketHeader :ticket="ticket" />
<Card class="relative p-4">
<Button v-if="!editMode" variant="outline" class="float-right h-8 gap-2" @click="edit">
<Button
v-if="!editMode"
variant="outline"
class="float-right h-8 gap-2"
@click="edit"
>
<Edit class="h-3.5 w-3.5" />
<span>Edit</span>
</Button>
@@ -123,7 +129,7 @@ const updateDescription = (value: string) => (message.value = value)
/>
</Card>
<Separator />
<Tabs default-value="timeline" class="flex flex-1 flex-col overflow-hidden">
<Tabs default-value="timeline" class="flex flex-1 flex-col">
<TabsList>
<TabsTrigger value="timeline">
Timeline
@@ -133,19 +139,23 @@ const updateDescription = (value: string) => (message.value = value)
ticket.expand.timeline_via_ticket.length > 0
"
variant="outline"
class="ml-2"
class="ml-2 hidden sm:inline-flex"
>
{{
ticket.expand.timeline_via_ticket ? ticket.expand.timeline_via_ticket.length : 0
ticket.expand.timeline_via_ticket
? ticket.expand.timeline_via_ticket.length
: 0
}}
</Badge>
</TabsTrigger>
<TabsTrigger value="tasks">
Tasks
<Badge
v-if="ticket.expand.tasks_via_ticket && ticket.expand.tasks_via_ticket.length > 0"
v-if="
ticket.expand.tasks_via_ticket && ticket.expand.tasks_via_ticket.length > 0
"
variant="outline"
class="ml-2"
class="ml-2 hidden sm:inline-flex"
>
{{ ticket.expand.tasks_via_ticket ? ticket.expand.tasks_via_ticket.length : 0 }}
<StatusIcon :status="taskStatus" class="size-6" />
@@ -159,10 +169,12 @@ const updateDescription = (value: string) => (message.value = value)
ticket.expand.comments_via_ticket.length > 0
"
variant="outline"
class="ml-2"
class="ml-2 hidden sm:inline-flex"
>
{{
ticket.expand.comments_via_ticket ? ticket.expand.comments_via_ticket.length : 0
ticket.expand.comments_via_ticket
? ticket.expand.comments_via_ticket.length
: 0
}}
</Badge>
</TabsTrigger>
@@ -179,8 +191,7 @@ const updateDescription = (value: string) => (message.value = value)
</Tabs>
<Separator class="xl:hidden" />
</div>
<ScrollArea>
<div class="flex flex-initial flex-col gap-4 p-4 xl:w-96">
<div class="flex flex-col gap-4 xl:w-96 xl:flex-initial">
<div>
<div class="flex h-10 flex-row items-center justify-between text-muted-foreground">
<span class="text-sm font-semibold"> Details </span>
@@ -196,10 +207,10 @@ const updateDescription = (value: string) => (message.value = value)
<Separator />
<TicketFiles :ticket="ticket" :files="ticket.expand.files_via_ticket" />
</div>
</ScrollArea>
</div>
</ColumnBodyContainer>
</ColumnBody>
<Separator />
<TicketCloseBar :ticket="ticket" />
</div>
<TicketCloseBar :ticket="ticket" class="shrink-0" />
</template>
</TanView>
</template>

View File

@@ -38,13 +38,13 @@ const updateName = (value: string) => {
<DynamicInput :modelValue="ticket.name" @update:modelValue="updateName" class="-mx-1" />
</span>
<div class="flex flex-row space-x-2 px-1 text-xs">
<div class="flex items-center gap-1 text-muted-foreground">
<div class="flex flex-col items-stretch gap-1 text-xs text-muted-foreground md:h-4 md:flex-row">
<div>
Created:
{{ format(new Date(ticket.created), 'PPpp') }}
</div>
<Separator orientation="vertical" />
<div class="flex items-center gap-1 text-muted-foreground">
<Separator orientation="vertical" class="hidden md:block" />
<div>
Updated:
{{ format(new Date(ticket.updated), 'PPpp') }}
</div>

View File

@@ -1,4 +1,5 @@
<script lang="ts" setup>
import ColumnHeader from '@/components/layout/ColumnHeader.vue'
import TicketListList from '@/components/ticket/TicketListList.vue'
import TicketNewDialog from '@/components/ticket/TicketNewDialog.vue'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
@@ -14,7 +15,6 @@ import {
PaginationNext,
PaginationPrev
} from '@/components/ui/pagination'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Separator } from '@/components/ui/separator'
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
@@ -106,7 +106,7 @@ watch(
if (!route.params.id && ticketItems.value && ticketItems.value.items.length > 0) {
router.push({
name: 'tickets',
params: { type: props.selectedType.id, id: ticketItems.value.items[0].id }
params: { type: props.selectedType.id }
})
}
}
@@ -121,18 +121,13 @@ watch([tab, props.selectedType, page, perPage], () => refetch())
</script>
<template>
<div class="flex h-screen flex-col">
<div class="flex items-center bg-background px-4 py-2">
<h1 class="text-xl font-bold">
{{ selectedType?.plural }}
</h1>
<ColumnHeader :title="selectedType?.plural">
<div class="ml-auto">
<TicketNewDialog :selectedType="selectedType" />
</div>
</div>
<Separator />
</ColumnHeader>
<Tabs v-model="tab" class="flex flex-1 flex-col overflow-hidden">
<div class="flex items-center justify-between px-4 pt-2">
<div class="flex items-center justify-between px-2 pt-2">
<TabsList>
<TabsTrigger value="all">All</TabsTrigger>
<TabsTrigger value="open">Open</TabsTrigger>
@@ -143,7 +138,7 @@ watch([tab, props.selectedType, page, perPage], () => refetch())
<span class="sr-only sm:not-sr-only">Filter</span>
</Button-->
</div>
<div class="px-4 py-2">
<div class="p-2">
<form>
<div class="relative flex flex-row items-center">
<Input v-model="searchValue" placeholder="Search" @keydown.enter.prevent class="pl-8" />
@@ -157,13 +152,13 @@ watch([tab, props.selectedType, page, perPage], () => refetch())
<div v-if="isPending" class="flex h-full w-full items-center justify-center">
<LoaderCircle class="h-16 w-16 animate-spin text-primary" />
</div>
<Alert v-else-if="isError" variant="destructive" class="mb-4 h-screen w-screen">
<Alert v-else-if="isError" variant="destructive" class="mb-2 h-screen w-screen">
<AlertTitle>Error</AlertTitle>
<AlertDescription>{{ error }}</AlertDescription>
</Alert>
<ScrollArea v-else-if="ticketItems" class="flex-1">
<div v-else-if="ticketItems" class="flex-1 overflow-y-auto overflow-x-hidden">
<TicketListList :tickets="ticketItems.items" />
</ScrollArea>
</div>
<Separator />
<div class="my-2 flex items-center justify-center">
<span class="text-xs text-muted-foreground">
@@ -171,7 +166,7 @@ watch([tab, props.selectedType, page, perPage], () => refetch())
{{ ticketItems ? ticketItems.totalItems : '?' }} tickets
</span>
</div>
<div class="mb-4 flex items-center justify-center">
<div class="mb-2 flex items-center justify-center">
<Pagination
v-slot="{ page }"
:total="ticketItems ? ticketItems.totalItems : 0"
@@ -191,10 +186,7 @@ watch([tab, props.selectedType, page, perPage], () => refetch())
:value="item.value"
as-child
>
<Button
class="h-10 w-10 p-0"
:variant="item.value === page ? 'default' : 'outline'"
>
<Button class="h-10 w-10 p-0" :variant="item.value === page ? 'default' : 'outline'">
{{ item.value }}
</Button>
</PaginationListItem>
@@ -206,5 +198,4 @@ watch([tab, props.selectedType, page, perPage], () => refetch())
</Pagination>
</div>
</Tabs>
</div>
</template>

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import ResourceListElement from '@/components/common/ResourceListElement.vue'
import ResourceListElement from '@/components/layout/ResourceListElement.vue'
import { useRoute } from 'vue-router'
@@ -13,7 +13,7 @@ defineProps<{
</script>
<template>
<div class="mt-2 flex w-full flex-1 flex-col gap-2 p-4 pt-0">
<div class="mt-2 flex w-full flex-1 flex-col gap-2 p-2 pt-0">
<ResourceListElement
v-for="item of tickets"
:key="item.id"

View File

@@ -1,5 +1,4 @@
<script setup lang="ts">
import { ScrollArea } from '@/components/ui/scroll-area'
import { Separator } from '@/components/ui/separator'
import { TabsContent } from '@/components/ui/tabs'
@@ -12,10 +11,8 @@ defineProps<{
<TabsContent :value="value" class="flex-1 overflow-hidden">
<div class="flex h-full flex-col overflow-hidden">
<Separator class="mt-2" />
<ScrollArea class="flex-1">
<slot />
<div class="h-4" />
</ScrollArea>
</div>
</TabsContent>
</template>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import DeleteDialog from '@/components/common/DeleteDialog.vue'
import PanelListElement from '@/components/common/PanelListElement.vue'
import PanelListElement from '@/components/layout/PanelListElement.vue'
import TicketPanel from '@/components/ticket/TicketPanel.vue'
import LinkAddDialog from '@/components/ticket/link/LinkAddDialog.vue'
import { Button } from '@/components/ui/button'
@@ -28,7 +28,12 @@ const dialogOpen = ref(false)
>
No links added yet.
</div>
<PanelListElement v-for="link in links" :key="link.id" :title="link.url" class="pr-1">
<PanelListElement
v-for="link in links"
:key="link.id"
:title="link.url"
class="flex-row items-center pr-1"
>
<a :href="link.url" target="_blank" class="flex flex-1 items-center overflow-hidden">
<span class="mr-2 text-blue-500 underline">
{{ link.name }}

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
import DeleteDialog from '@/components/common/DeleteDialog.vue'
import PanelListElement from '@/components/common/PanelListElement.vue'
import UserSelect from '@/components/common/UserSelect.vue'
import DynamicInput from '@/components/input/DynamicInput.vue'
import PanelListElement from '@/components/layout/PanelListElement.vue'
import TaskAddDialog from '@/components/ticket/task/TaskAddDialog.vue'
import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
@@ -67,12 +67,14 @@ const updateTaskName = (id: string, name: string) => updateTaskNameMutation.muta
</Card>
<Card v-else>
<PanelListElement v-for="task in tasks" :key="task.id" class="pr-1">
<div class="flex flex-row items-center">
<Checkbox :checked="!task.open" class="mr-2" @click="check(task)" />
<DynamicInput
:modelValue="task.name"
@update:modelValue="updateTaskName(task.id, $event)"
class="mr-2 flex-1"
/>
</div>
<div class="ml-auto flex items-center">
<UserSelect v-if="!task.expand.owner" @update:modelValue="update(task.id, $event)">
<Button variant="outline" role="combobox" class="h-8">

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import PanelListElement from '@/components/common/PanelListElement.vue'
import DynamicMDEditor from '@/components/input/DynamicMDEditor.vue'
import PanelListElement from '@/components/layout/PanelListElement.vue'
import { Button } from '@/components/ui/button'
import {
Dialog,

View File

@@ -1,19 +0,0 @@
<script setup lang="ts">
import {
AccordionRoot,
type AccordionRootEmits,
type AccordionRootProps,
useForwardPropsEmits
} from 'radix-vue'
const props = defineProps<AccordionRootProps>()
const emits = defineEmits<AccordionRootEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<AccordionRoot v-bind="forwarded">
<slot />
</AccordionRoot>
</template>

View File

@@ -1,25 +0,0 @@
<script setup lang="ts">
import { AccordionContent, type AccordionContentProps } from 'radix-vue'
import { type HTMLAttributes, computed } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<AccordionContentProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<AccordionContent
v-bind="delegatedProps"
class="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
>
<div :class="cn('pb-4 pt-0', props.class)">
<slot />
</div>
</AccordionContent>
</template>

View File

@@ -1,22 +0,0 @@
<script setup lang="ts">
import { AccordionItem, type AccordionItemProps, useForwardProps } from 'radix-vue'
import { type HTMLAttributes, computed } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<AccordionItemProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<AccordionItem v-bind="forwardedProps" :class="cn('border-b', props.class)">
<slot />
</AccordionItem>
</template>

View File

@@ -1,33 +0,0 @@
<script setup lang="ts">
import { AccordionHeader, AccordionTrigger, type AccordionTriggerProps } from 'radix-vue'
import { type HTMLAttributes, computed } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<AccordionTriggerProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<AccordionHeader class="flex">
<AccordionTrigger
v-bind="delegatedProps"
:class="
cn(
'flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180',
props.class
)
"
>
<slot />
<!-- slot name="icon">
<ChevronDown class="h-4 w-4 shrink-0 transition-transform duration-200" />
</slot-->
</AccordionTrigger>
</AccordionHeader>
</template>

View File

@@ -1,4 +0,0 @@
export { default as Accordion } from './Accordion.vue'
export { default as AccordionContent } from './AccordionContent.vue'
export { default as AccordionItem } from './AccordionItem.vue'
export { default as AccordionTrigger } from './AccordionTrigger.vue'

View File

@@ -1,25 +0,0 @@
<script setup lang="ts">
import { type AvatarVariants, avatarVariant } from '.'
import { AvatarRoot } from 'radix-vue'
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = withDefaults(
defineProps<{
class?: HTMLAttributes['class']
size?: AvatarVariants['size']
shape?: AvatarVariants['shape']
}>(),
{
size: 'sm',
shape: 'circle'
}
)
</script>
<template>
<AvatarRoot :class="cn(avatarVariant({ size, shape }), props.class)">
<slot />
</AvatarRoot>
</template>

View File

@@ -1,11 +0,0 @@
<script setup lang="ts">
import { AvatarFallback, type AvatarFallbackProps } from 'radix-vue'
const props = defineProps<AvatarFallbackProps>()
</script>
<template>
<AvatarFallback v-bind="props">
<slot />
</AvatarFallback>
</template>

View File

@@ -1,9 +0,0 @@
<script setup lang="ts">
import { AvatarImage, type AvatarImageProps } from 'radix-vue'
const props = defineProps<AvatarImageProps>()
</script>
<template>
<AvatarImage v-bind="props" class="h-full w-full object-cover" />
</template>

View File

@@ -1,24 +0,0 @@
import { type VariantProps, cva } from 'class-variance-authority'
export { default as Avatar } from './Avatar.vue'
export { default as AvatarImage } from './AvatarImage.vue'
export { default as AvatarFallback } from './AvatarFallback.vue'
export const avatarVariant = cva(
'inline-flex items-center justify-center font-normal text-foreground select-none shrink-0 bg-secondary overflow-hidden',
{
variants: {
size: {
sm: 'h-10 w-10 text-xs',
base: 'h-16 w-16 text-2xl',
lg: 'h-32 w-32 text-5xl'
},
shape: {
circle: 'rounded-full',
square: 'rounded-md'
}
}
}
)
export type AvatarVariants = VariantProps<typeof avatarVariant>

View File

@@ -1,69 +0,0 @@
<script lang="ts" setup>
import {
CalendarCell,
CalendarCellTrigger,
CalendarGrid,
CalendarGridBody,
CalendarGridHead,
CalendarGridRow,
CalendarHeadCell,
CalendarHeader,
CalendarHeading,
CalendarNextButton,
CalendarPrevButton
} from '.'
import {
CalendarRoot,
type CalendarRootEmits,
type CalendarRootProps,
useForwardPropsEmits
} from 'radix-vue'
import { type HTMLAttributes, computed } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<CalendarRootProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<CalendarRootEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<CalendarRoot v-slot="{ grid, weekDays }" :class="cn('p-3', props.class)" v-bind="forwarded">
<CalendarHeader>
<CalendarPrevButton />
<CalendarHeading />
<CalendarNextButton />
</CalendarHeader>
<div class="mt-4 flex flex-col gap-y-4 sm:flex-row sm:gap-x-4 sm:gap-y-0">
<CalendarGrid v-for="month in grid" :key="month.value.toString()">
<CalendarGridHead>
<CalendarGridRow>
<CalendarHeadCell v-for="day in weekDays" :key="day">
{{ day }}
</CalendarHeadCell>
</CalendarGridRow>
</CalendarGridHead>
<CalendarGridBody>
<CalendarGridRow
v-for="(weekDates, index) in month.rows"
:key="`weekDate-${index}`"
class="mt-2 w-full"
>
<CalendarCell v-for="weekDate in weekDates" :key="weekDate.toString()" :date="weekDate">
<CalendarCellTrigger :day="weekDate" :month="month.value" />
</CalendarCell>
</CalendarGridRow>
</CalendarGridBody>
</CalendarGrid>
</div>
</CalendarRoot>
</template>

View File

@@ -1,30 +0,0 @@
<script lang="ts" setup>
import { CalendarCell, type CalendarCellProps, useForwardProps } from 'radix-vue'
import { type HTMLAttributes, computed } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<CalendarCellProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<CalendarCell
:class="
cn(
'relative h-9 w-9 p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([data-selected])]:rounded-md [&:has([data-selected])]:bg-accent [&:has([data-selected][data-outside-month])]:bg-accent/50',
props.class
)
"
v-bind="forwardedProps"
>
<slot />
</CalendarCell>
</template>

View File

@@ -1,42 +0,0 @@
<script lang="ts" setup>
import { buttonVariants } from '@/components/ui/button'
import { CalendarCellTrigger, type CalendarCellTriggerProps, useForwardProps } from 'radix-vue'
import { type HTMLAttributes, computed } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<CalendarCellTriggerProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<CalendarCellTrigger
:class="
cn(
buttonVariants({ variant: 'ghost' }),
'h-9 w-9 p-0 font-normal',
'[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground',
// Selected
'data-[selected]:bg-primary data-[selected]:text-primary-foreground data-[selected]:opacity-100 data-[selected]:hover:bg-primary data-[selected]:hover:text-primary-foreground data-[selected]:focus:bg-primary data-[selected]:focus:text-primary-foreground',
// Disabled
'data-[disabled]:text-muted-foreground data-[disabled]:opacity-50',
// Unavailable
'data-[unavailable]:text-destructive-foreground data-[unavailable]:line-through',
// Outside months
'data-[outside-month]:pointer-events-none data-[outside-month]:text-muted-foreground data-[outside-month]:opacity-50 [&[data-outside-month][data-selected]]:bg-accent/50 [&[data-outside-month][data-selected]]:text-muted-foreground [&[data-outside-month][data-selected]]:opacity-30',
props.class
)
"
v-bind="forwardedProps"
>
<slot />
</CalendarCellTrigger>
</template>

View File

@@ -1,25 +0,0 @@
<script lang="ts" setup>
import { CalendarGrid, type CalendarGridProps, useForwardProps } from 'radix-vue'
import { type HTMLAttributes, computed } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<CalendarGridProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<CalendarGrid
:class="cn('w-full border-collapse space-y-1', props.class)"
v-bind="forwardedProps"
>
<slot />
</CalendarGrid>
</template>

View File

@@ -1,11 +0,0 @@
<script lang="ts" setup>
import { CalendarGridBody, type CalendarGridBodyProps } from 'radix-vue'
const props = defineProps<CalendarGridBodyProps>()
</script>
<template>
<CalendarGridBody v-bind="props">
<slot />
</CalendarGridBody>
</template>

View File

@@ -1,11 +0,0 @@
<script lang="ts" setup>
import { CalendarGridHead, type CalendarGridHeadProps } from 'radix-vue'
const props = defineProps<CalendarGridHeadProps>()
</script>
<template>
<CalendarGridHead v-bind="props">
<slot />
</CalendarGridHead>
</template>

View File

@@ -1,22 +0,0 @@
<script lang="ts" setup>
import { CalendarGridRow, type CalendarGridRowProps, useForwardProps } from 'radix-vue'
import { type HTMLAttributes, computed } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<CalendarGridRowProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<CalendarGridRow :class="cn('flex', props.class)" v-bind="forwardedProps">
<slot />
</CalendarGridRow>
</template>

View File

@@ -1,25 +0,0 @@
<script lang="ts" setup>
import { CalendarHeadCell, type CalendarHeadCellProps, useForwardProps } from 'radix-vue'
import { type HTMLAttributes, computed } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<CalendarHeadCellProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<CalendarHeadCell
:class="cn('w-9 rounded-md text-[0.8rem] font-normal text-muted-foreground', props.class)"
v-bind="forwardedProps"
>
<slot />
</CalendarHeadCell>
</template>

View File

@@ -1,25 +0,0 @@
<script lang="ts" setup>
import { CalendarHeader, type CalendarHeaderProps, useForwardProps } from 'radix-vue'
import { type HTMLAttributes, computed } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<CalendarHeaderProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<CalendarHeader
:class="cn('relative flex w-full items-center justify-between pt-1', props.class)"
v-bind="forwardedProps"
>
<slot />
</CalendarHeader>
</template>

View File

@@ -1,28 +0,0 @@
<script lang="ts" setup>
import { CalendarHeading, type CalendarHeadingProps, useForwardProps } from 'radix-vue'
import { type HTMLAttributes, computed } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<CalendarHeadingProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<CalendarHeading
v-slot="{ headingValue }"
:class="cn('text-sm font-medium', props.class)"
v-bind="forwardedProps"
>
<slot :heading-value>
{{ headingValue }}
</slot>
</CalendarHeading>
</template>

View File

@@ -1,37 +0,0 @@
<script lang="ts" setup>
import { buttonVariants } from '@/components/ui/button'
import { ChevronRight } from 'lucide-vue-next'
import { CalendarNext, type CalendarNextProps, useForwardProps } from 'radix-vue'
import { type HTMLAttributes, computed } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<CalendarNextProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<CalendarNext
:class="
cn(
buttonVariants({ variant: 'outline' }),
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
props.class
)
"
v-bind="forwardedProps"
>
<slot>
<ChevronRight class="h-4 w-4" />
</slot>
</CalendarNext>
</template>

View File

@@ -1,37 +0,0 @@
<script lang="ts" setup>
import { buttonVariants } from '@/components/ui/button'
import { ChevronLeft } from 'lucide-vue-next'
import { CalendarPrev, type CalendarPrevProps, useForwardProps } from 'radix-vue'
import { type HTMLAttributes, computed } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<CalendarPrevProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<CalendarPrev
:class="
cn(
buttonVariants({ variant: 'outline' }),
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
props.class
)
"
v-bind="forwardedProps"
>
<slot>
<ChevronLeft class="h-4 w-4" />
</slot>
</CalendarPrev>
</template>

View File

@@ -1,12 +0,0 @@
export { default as Calendar } from './Calendar.vue'
export { default as CalendarCell } from './CalendarCell.vue'
export { default as CalendarCellTrigger } from './CalendarCellTrigger.vue'
export { default as CalendarGrid } from './CalendarGrid.vue'
export { default as CalendarGridBody } from './CalendarGridBody.vue'
export { default as CalendarGridHead } from './CalendarGridHead.vue'
export { default as CalendarGridRow } from './CalendarGridRow.vue'
export { default as CalendarHeadCell } from './CalendarHeadCell.vue'
export { default as CalendarHeader } from './CalendarHeader.vue'
export { default as CalendarHeading } from './CalendarHeading.vue'
export { default as CalendarNextButton } from './CalendarNextButton.vue'
export { default as CalendarPrevButton } from './CalendarPrevButton.vue'

View File

@@ -1,15 +0,0 @@
<script setup lang="ts">
import { CollapsibleRoot, useForwardPropsEmits } from 'radix-vue'
import type { CollapsibleRootEmits, CollapsibleRootProps } from 'radix-vue'
const props = defineProps<CollapsibleRootProps>()
const emits = defineEmits<CollapsibleRootEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<CollapsibleRoot v-slot="{ open }" v-bind="forwarded">
<slot :open="open" />
</CollapsibleRoot>
</template>

View File

@@ -1,14 +0,0 @@
<script setup lang="ts">
import { CollapsibleContent, type CollapsibleContentProps } from 'radix-vue'
const props = defineProps<CollapsibleContentProps>()
</script>
<template>
<CollapsibleContent
v-bind="props"
class="overflow-hidden transition-all data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down"
>
<slot />
</CollapsibleContent>
</template>

View File

@@ -1,11 +0,0 @@
<script setup lang="ts">
import { CollapsibleTrigger, type CollapsibleTriggerProps } from 'radix-vue'
const props = defineProps<CollapsibleTriggerProps>()
</script>
<template>
<CollapsibleTrigger v-bind="props">
<slot />
</CollapsibleTrigger>
</template>

View File

@@ -1,3 +0,0 @@
export { default as Collapsible } from './Collapsible.vue'
export { default as CollapsibleTrigger } from './CollapsibleTrigger.vue'
export { default as CollapsibleContent } from './CollapsibleContent.vue'

View File

@@ -1,43 +0,0 @@
<script setup lang="ts">
import { GripVertical } from 'lucide-vue-next'
import {
SplitterResizeHandle,
type SplitterResizeHandleEmits,
type SplitterResizeHandleProps,
useForwardPropsEmits
} from 'radix-vue'
import { type HTMLAttributes, computed } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<
SplitterResizeHandleProps & { class?: HTMLAttributes['class']; withHandle?: boolean }
>()
const emits = defineEmits<SplitterResizeHandleEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<SplitterResizeHandle
v-bind="forwarded"
:class="
cn(
'relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 [&[data-orientation=vertical]>div]:rotate-90 [&[data-orientation=vertical]]:h-px [&[data-orientation=vertical]]:w-full [&[data-orientation=vertical]]:after:left-0 [&[data-orientation=vertical]]:after:h-1 [&[data-orientation=vertical]]:after:w-full [&[data-orientation=vertical]]:after:-translate-y-1/2 [&[data-orientation=vertical]]:after:translate-x-0',
props.class
)
"
>
<template v-if="props.withHandle">
<div class="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
<GripVertical class="h-2.5 w-2.5" />
</div>
</template>
</SplitterResizeHandle>
</template>

View File

@@ -1,30 +0,0 @@
<script setup lang="ts">
import {
SplitterGroup,
type SplitterGroupEmits,
type SplitterGroupProps,
useForwardPropsEmits
} from 'radix-vue'
import { type HTMLAttributes, computed } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<SplitterGroupProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<SplitterGroupEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<SplitterGroup
v-bind="forwarded"
:class="cn('flex h-full w-full data-[panel-group-direction=vertical]:flex-col', props.class)"
>
<slot />
</SplitterGroup>
</template>

View File

@@ -1,3 +0,0 @@
export { default as ResizablePanelGroup } from './ResizablePanelGroup.vue'
export { default as ResizableHandle } from './ResizableHandle.vue'
export { SplitterPanel as ResizablePanel } from 'radix-vue'

View File

@@ -1,30 +0,0 @@
<script setup lang="ts">
import ScrollBar from './ScrollBar.vue'
import {
ScrollAreaCorner,
ScrollAreaRoot,
type ScrollAreaRootProps,
ScrollAreaViewport
} from 'radix-vue'
import { type HTMLAttributes, computed } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<ScrollAreaRootProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<ScrollAreaRoot v-bind="delegatedProps" :class="cn('relative overflow-hidden', props.class)">
<ScrollAreaViewport class="h-full w-full rounded-[inherit]">
<slot />
</ScrollAreaViewport>
<ScrollBar />
<ScrollAreaCorner />
</ScrollAreaRoot>
</template>

View File

@@ -1,35 +0,0 @@
<script setup lang="ts">
import { ScrollAreaScrollbar, type ScrollAreaScrollbarProps, ScrollAreaThumb } from 'radix-vue'
import { type HTMLAttributes, computed } from 'vue'
import { cn } from '@/lib/utils'
const props = withDefaults(
defineProps<ScrollAreaScrollbarProps & { class?: HTMLAttributes['class'] }>(),
{
orientation: 'vertical'
}
)
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<ScrollAreaScrollbar
v-bind="delegatedProps"
:class="
cn(
'flex touch-none select-none transition-colors',
orientation === 'vertical' && 'h-full w-2.5 border-l border-l-transparent p-px',
orientation === 'horizontal' && 'h-2.5 flex-col border-t border-t-transparent p-px',
props.class
)
"
>
<ScrollAreaThumb class="relative flex-1 rounded-full bg-border" />
</ScrollAreaScrollbar>
</template>

View File

@@ -1,2 +0,0 @@
export { default as ScrollArea } from './ScrollArea.vue'
export { default as ScrollBar } from './ScrollBar.vue'

View File

@@ -1,44 +0,0 @@
<script setup lang="ts">
import {
SwitchRoot,
type SwitchRootEmits,
type SwitchRootProps,
SwitchThumb,
useForwardPropsEmits
} from 'radix-vue'
import { type HTMLAttributes, computed } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<SwitchRootProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<SwitchRootEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<SwitchRoot
v-bind="forwarded"
:class="
cn(
'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
props.class
)
"
>
<SwitchThumb
:class="
cn(
'pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0'
)
"
/>
</SwitchRoot>
</template>

View File

@@ -1 +0,0 @@
export { default as Switch } from './Switch.vue'

View File

@@ -3,10 +3,11 @@ import OpenTasks from '@/components/dashboard/OpenTasks.vue'
import OpenTickets from '@/components/dashboard/OpenTickets.vue'
import TicketOverTime from '@/components/dashboard/TicketOverTime.vue'
import TicketTypes from '@/components/dashboard/TicketTypes.vue'
import ColumnBody from '@/components/layout/ColumnBody.vue'
import ColumnBodyContainer from '@/components/layout/ColumnBodyContainer.vue'
import ColumnHeader from '@/components/layout/ColumnHeader.vue'
import TwoColumn from '@/components/layout/TwoColumn.vue'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Separator } from '@/components/ui/separator'
import { ExternalLink } from 'lucide-vue-next'
@@ -46,14 +47,10 @@ onMounted(() => {
<template>
<TwoColumn>
<div class="flex h-screen flex-1 flex-col">
<div class="flex h-14 min-h-14 items-center bg-background px-4 py-2">
<h1 class="text-xl font-bold">Dashboard</h1>
</div>
<Separator class="shrink-0" />
<ScrollArea>
<div
class="m-auto grid max-w-7xl grid-cols-1 grid-rows-[100px_100px_100px_100px] gap-4 p-4 md:grid-cols-2 md:grid-rows-[100px_100px] xl:grid-cols-4 xl:grid-rows-[100px]"
<ColumnHeader title="Dashboard" />
<ColumnBody>
<ColumnBodyContainer
class="grid grid-cols-1 grid-rows-[100px_100px_100px_100px] md:grid-cols-2 md:grid-rows-[100px_100px] xl:grid-cols-4 xl:grid-rows-[100px]"
>
<Card>
<CardHeader>
@@ -75,8 +72,8 @@ onMounted(() => {
</Card>
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
<CardTitle>{{ count('reactions') }}</CardTitle>
<CardDescription>Reactions</CardDescription>
</CardHeader>
</Card>
<Card>
@@ -85,7 +82,7 @@ onMounted(() => {
</CardHeader>
<CardContent class="flex flex-1 flex-col gap-1">
<a
href="https://catalyst-soar.com/docs/category/catalyst-handbook"
href="https://catalyst.security-brewery.com/docs/category/catalyst-handbook"
target="_blank"
class="flex items-center rounded border p-2 text-blue-500 hover:bg-accent"
>
@@ -134,8 +131,7 @@ onMounted(() => {
<OpenTasks />
</CardContent>
</Card>
</div>
</ScrollArea>
</div>
</ColumnBodyContainer>
</ColumnBody>
</TwoColumn>
</template>

View File

@@ -1,4 +1,5 @@
<script setup lang="ts" xmlns="http://www.w3.org/1999/html">
<script setup lang="ts">
import ColumnBody from '@/components/layout/ColumnBody.vue'
import ThreeColumn from '@/components/layout/ThreeColumn.vue'
import ReactionDisplay from '@/components/reaction/ReactionDisplay.vue'
import ReactionList from '@/components/reaction/ReactionList.vue'
@@ -22,14 +23,14 @@ onMounted(() => {
</script>
<template>
<ThreeColumn>
<ThreeColumn :show-details="!!id">
<template #list>
<ReactionList />
</template>
<template #single>
<div v-if="!id" class="flex h-full w-full items-center justify-center text-lg text-gray-500">
<ColumnBody v-if="!id" class="items-center justify-center text-lg text-gray-500">
No reaction selected
</div>
</ColumnBody>
<ReactionNew v-else-if="id === 'new'" key="new" />
<ReactionDisplay v-else :key="id" :id="id" />
</template>

View File

@@ -1,5 +1,6 @@
<script setup lang="ts">
import TanView from '@/components/TanView.vue'
import ColumnBody from '@/components/layout/ColumnBody.vue'
import ThreeColumn from '@/components/layout/ThreeColumn.vue'
import TicketDisplay from '@/components/ticket/TicketDisplay.vue'
import TicketList from '@/components/ticket/TicketList.vue'
@@ -41,20 +42,17 @@ onMounted(() => {
</script>
<template>
<ThreeColumn>
<ThreeColumn :show-details="!!id">
<template #list>
<TanView :isError="isError" :isPending="isPending" :error="error" :value="selectedType">
<TanView :isError="isError" :isPending="isPending" :error="error">
<TicketList v-if="selectedType" :key="selectedType.id" :selectedType="selectedType" />
</TanView>
</template>
<template #single>
<TanView :isError="isError" :isPending="isPending" :error="error" :value="selectedType">
<div
v-if="!id"
class="flex h-full w-full items-center justify-center text-lg text-gray-500"
>
<TanView :isError="isError" :isPending="isPending" :error="error">
<ColumnBody v-if="!id" class="items-center justify-center text-lg text-gray-500">
No ticket selected
</div>
</ColumnBody>
<TicketDisplay v-else-if="selectedType" :key="id" :selectedType="selectedType" />
</TanView>
</template>