mirror of
https://github.com/SecurityBrewery/catalyst.git
synced 2026-01-13 17:51:22 +01:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b67d5e6cd |
19
ui/bun.lock
19
ui/bun.lock
@@ -15,7 +15,7 @@
|
|||||||
"@uppy/progress-bar": "^4.2.1",
|
"@uppy/progress-bar": "^4.2.1",
|
||||||
"@uppy/tus": "^4.2.2",
|
"@uppy/tus": "^4.2.2",
|
||||||
"@uppy/vue": "^2.2.0",
|
"@uppy/vue": "^2.2.0",
|
||||||
"@vueuse/core": "^13.4.0",
|
"@vueuse/core": "^14.1.0",
|
||||||
"caniuse-lite": "^1.0.30001723",
|
"caniuse-lite": "^1.0.30001723",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
"lucide-vue-next": "^0.475.0",
|
"lucide-vue-next": "^0.475.0",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
"radix-vue": "^1.9.17",
|
"radix-vue": "^1.9.17",
|
||||||
|
"reka-ui": "^2.7.0",
|
||||||
"tailwind-merge": "^2.6.0",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"v-calendar": "^3.1.2",
|
"v-calendar": "^3.1.2",
|
||||||
@@ -589,11 +590,11 @@
|
|||||||
|
|
||||||
"@vue/tsconfig": ["@vue/tsconfig@0.7.0", "", { "peerDependencies": { "typescript": "5.x", "vue": "^3.4.0" }, "optionalPeers": ["typescript", "vue"] }, "sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg=="],
|
"@vue/tsconfig": ["@vue/tsconfig@0.7.0", "", { "peerDependencies": { "typescript": "5.x", "vue": "^3.4.0" }, "optionalPeers": ["typescript", "vue"] }, "sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg=="],
|
||||||
|
|
||||||
"@vueuse/core": ["@vueuse/core@13.4.0", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "13.4.0", "@vueuse/shared": "13.4.0" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-OnK7zW3bTq/QclEk17+vDFN3tuAm8ONb9zQUIHrYQkkFesu3WeGUx/3YzpEp+ly53IfDAT9rsYXgGW6piNZC5w=="],
|
"@vueuse/core": ["@vueuse/core@14.1.0", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "14.1.0", "@vueuse/shared": "14.1.0" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-rgBinKs07hAYyPF834mDTigH7BtPqvZ3Pryuzt1SD/lg5wEcWqvwzXXYGEDb2/cP0Sj5zSvHl3WkmMELr5kfWw=="],
|
||||||
|
|
||||||
"@vueuse/metadata": ["@vueuse/metadata@13.4.0", "", {}, "sha512-CPDQ/IgOeWbqItg1c/pS+Ulum63MNbpJ4eecjFJqgD/JUCJ822zLfpw6M9HzSvL6wbzMieOtIAW/H8deQASKHg=="],
|
"@vueuse/metadata": ["@vueuse/metadata@14.1.0", "", {}, "sha512-7hK4g015rWn2PhKcZ99NyT+ZD9sbwm7SGvp7k+k+rKGWnLjS/oQozoIZzWfCewSUeBmnJkIb+CNr7Zc/EyRnnA=="],
|
||||||
|
|
||||||
"@vueuse/shared": ["@vueuse/shared@13.4.0", "", { "peerDependencies": { "vue": "^3.5.0" } }, "sha512-+AxuKbw8R1gYy5T21V5yhadeNM7rJqb4cPaRI9DdGnnNl3uqXh+unvQ3uCaA2DjYLbNr1+l7ht/B4qEsRegX6A=="],
|
"@vueuse/shared": ["@vueuse/shared@14.1.0", "", { "peerDependencies": { "vue": "^3.5.0" } }, "sha512-EcKxtYvn6gx1F8z9J5/rsg3+lTQnvOruQd8fUecW99DCK04BkWD7z5KQ/wTAx+DazyoEE9dJt/zV8OIEQbM6kw=="],
|
||||||
|
|
||||||
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||||
|
|
||||||
@@ -1231,6 +1232,8 @@
|
|||||||
|
|
||||||
"object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="],
|
"object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="],
|
||||||
|
|
||||||
|
"ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
|
||||||
|
|
||||||
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
||||||
|
|
||||||
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
|
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
|
||||||
@@ -1355,6 +1358,8 @@
|
|||||||
|
|
||||||
"reflect-metadata": ["reflect-metadata@0.2.2", "", {}, "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="],
|
"reflect-metadata": ["reflect-metadata@0.2.2", "", {}, "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="],
|
||||||
|
|
||||||
|
"reka-ui": ["reka-ui@2.7.0", "", { "dependencies": { "@floating-ui/dom": "^1.6.13", "@floating-ui/vue": "^1.1.6", "@internationalized/date": "^3.5.0", "@internationalized/number": "^3.5.0", "@tanstack/vue-virtual": "^3.12.0", "@vueuse/core": "^12.5.0", "@vueuse/shared": "^12.5.0", "aria-hidden": "^1.2.4", "defu": "^6.1.4", "ohash": "^2.0.11" }, "peerDependencies": { "vue": ">= 3.2.0" } }, "sha512-m+XmxQN2xtFzBP3OAdIafKq7C8OETo2fqfxcIIxYmNN2Ch3r5oAf6yEYCIJg5tL/yJU2mHqF70dCCekUkrAnXA=="],
|
||||||
|
|
||||||
"remove-accents": ["remove-accents@0.5.0", "", {}, "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A=="],
|
"remove-accents": ["remove-accents@0.5.0", "", {}, "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A=="],
|
||||||
|
|
||||||
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
||||||
@@ -1695,6 +1700,10 @@
|
|||||||
|
|
||||||
"readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
"readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||||
|
|
||||||
|
"reka-ui/@vueuse/core": ["@vueuse/core@12.8.2", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "12.8.2", "@vueuse/shared": "12.8.2", "vue": "^3.5.13" } }, "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ=="],
|
||||||
|
|
||||||
|
"reka-ui/@vueuse/shared": ["@vueuse/shared@12.8.2", "", { "dependencies": { "vue": "^3.5.13" } }, "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w=="],
|
||||||
|
|
||||||
"rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
|
"rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
|
||||||
|
|
||||||
"sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
|
"sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
|
||||||
@@ -1761,6 +1770,8 @@
|
|||||||
|
|
||||||
"radix-vue/@vueuse/core/@vueuse/metadata": ["@vueuse/metadata@10.11.1", "", {}, "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw=="],
|
"radix-vue/@vueuse/core/@vueuse/metadata": ["@vueuse/metadata@10.11.1", "", {}, "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw=="],
|
||||||
|
|
||||||
|
"reka-ui/@vueuse/core/@vueuse/metadata": ["@vueuse/metadata@12.8.2", "", {}, "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A=="],
|
||||||
|
|
||||||
"sucrase/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
"sucrase/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||||
|
|
||||||
"sucrase/glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
"sucrase/glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
"baseColor": "slate",
|
"baseColor": "slate",
|
||||||
"cssVariables": true
|
"cssVariables": true
|
||||||
},
|
},
|
||||||
"framework": "vite",
|
|
||||||
"aliases": {
|
"aliases": {
|
||||||
"components": "@/components",
|
"components": "@/components",
|
||||||
"utils": "@/lib/utils"
|
"utils": "@/lib/utils"
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
"@uppy/progress-bar": "^4.2.1",
|
"@uppy/progress-bar": "^4.2.1",
|
||||||
"@uppy/tus": "^4.2.2",
|
"@uppy/tus": "^4.2.2",
|
||||||
"@uppy/vue": "^2.2.0",
|
"@uppy/vue": "^2.2.0",
|
||||||
"@vueuse/core": "^13.4.0",
|
"@vueuse/core": "^14.1.0",
|
||||||
"caniuse-lite": "^1.0.30001723",
|
"caniuse-lite": "^1.0.30001723",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@@ -40,6 +40,7 @@
|
|||||||
"lucide-vue-next": "^0.475.0",
|
"lucide-vue-next": "^0.475.0",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
"radix-vue": "^1.9.17",
|
"radix-vue": "^1.9.17",
|
||||||
|
"reka-ui": "^2.7.0",
|
||||||
"tailwind-merge": "^2.6.0",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"v-calendar": "^3.1.2",
|
"v-calendar": "^3.1.2",
|
||||||
|
|||||||
@@ -8,17 +8,21 @@ import StatusIcon from '@/components/ticket/StatusIcon.vue'
|
|||||||
import TicketActionBar from '@/components/ticket/TicketActionBar.vue'
|
import TicketActionBar from '@/components/ticket/TicketActionBar.vue'
|
||||||
import TicketCloseBar from '@/components/ticket/TicketCloseBar.vue'
|
import TicketCloseBar from '@/components/ticket/TicketCloseBar.vue'
|
||||||
import TicketHeader from '@/components/ticket/TicketHeader.vue'
|
import TicketHeader from '@/components/ticket/TicketHeader.vue'
|
||||||
import TicketTab from '@/components/ticket/TicketTab.vue'
|
|
||||||
import TicketComments from '@/components/ticket/comment/TicketComments.vue'
|
import TicketComments from '@/components/ticket/comment/TicketComments.vue'
|
||||||
import TicketFiles from '@/components/ticket/file/TicketFiles.vue'
|
import TicketFiles from '@/components/ticket/file/TicketFiles.vue'
|
||||||
import TicketLinks from '@/components/ticket/link/TicketLinks.vue'
|
import TicketLinks from '@/components/ticket/link/TicketLinks.vue'
|
||||||
import TicketTasks from '@/components/ticket/task/TicketTasks.vue'
|
import TicketTasks from '@/components/ticket/task/TicketTasks.vue'
|
||||||
import TicketTimeline from '@/components/ticket/timeline/TicketTimeline.vue'
|
import TicketTimeline from '@/components/ticket/timeline/TicketTimeline.vue'
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger
|
||||||
|
} from '@/components/ui/accordion'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Card } from '@/components/ui/card'
|
import { Card } from '@/components/ui/card'
|
||||||
import { Separator } from '@/components/ui/separator'
|
import { Separator } from '@/components/ui/separator'
|
||||||
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
|
||||||
import { useToast } from '@/components/ui/toast/use-toast'
|
import { useToast } from '@/components/ui/toast/use-toast'
|
||||||
|
|
||||||
import { Edit } from 'lucide-vue-next'
|
import { Edit } from 'lucide-vue-next'
|
||||||
@@ -166,51 +170,64 @@ const updateDescription = (value: string | undefined) => (message.value = value
|
|||||||
class="min-h-14"
|
class="min-h-14"
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
<Separator />
|
<Accordion
|
||||||
<Tabs default-value="timeline" class="flex flex-1 flex-col">
|
type="multiple"
|
||||||
<TabsList>
|
:default-value="['tasks', 'comments', 'timeline']"
|
||||||
<TabsTrigger value="timeline">
|
class="w-full divide-y rounded-md border"
|
||||||
Timeline
|
>
|
||||||
<Badge
|
<AccordionItem value="tasks" class="border-0">
|
||||||
v-if="timeline && timeline.length > 0"
|
<AccordionTrigger class="px-3 py-2 hover:no-underline">
|
||||||
variant="outline"
|
<div class="flex items-center gap-2">
|
||||||
class="ml-2 hidden sm:inline-flex"
|
<span class="text-sm font-medium">Tasks</span>
|
||||||
>
|
<Badge
|
||||||
{{ timeline.length }}
|
v-if="tasks && tasks.length > 0"
|
||||||
</Badge>
|
variant="outline"
|
||||||
</TabsTrigger>
|
class="hidden sm:inline-flex"
|
||||||
<TabsTrigger value="tasks">
|
>
|
||||||
Tasks
|
{{ tasks.length }}
|
||||||
<Badge
|
<StatusIcon :status="taskStatus" class="size-6" />
|
||||||
v-if="tasks && tasks.length > 0"
|
</Badge>
|
||||||
variant="outline"
|
</div>
|
||||||
class="ml-2 hidden sm:inline-flex"
|
</AccordionTrigger>
|
||||||
>
|
<AccordionContent class="px-3 pt-2">
|
||||||
{{ tasks.length }}
|
<TicketTasks :ticket="ticket" :tasks="tasks" />
|
||||||
<StatusIcon :status="taskStatus" class="size-6" />
|
</AccordionContent>
|
||||||
</Badge>
|
</AccordionItem>
|
||||||
</TabsTrigger>
|
<AccordionItem value="comments" class="border-0">
|
||||||
<TabsTrigger value="comments">
|
<AccordionTrigger class="px-3 py-2 hover:no-underline">
|
||||||
Comments
|
<div class="flex items-center gap-2">
|
||||||
<Badge
|
<span class="text-sm font-medium">Comments</span>
|
||||||
v-if="comments && comments.length > 0"
|
<Badge
|
||||||
variant="outline"
|
v-if="comments && comments.length > 0"
|
||||||
class="ml-2 hidden sm:inline-flex"
|
variant="outline"
|
||||||
>
|
class="hidden sm:inline-flex"
|
||||||
{{ comments.length }}
|
>
|
||||||
</Badge>
|
{{ comments.length }}
|
||||||
</TabsTrigger>
|
</Badge>
|
||||||
</TabsList>
|
</div>
|
||||||
<TicketTab value="timeline">
|
</AccordionTrigger>
|
||||||
<TicketTimeline :ticket="ticket" :timeline="timeline" />
|
<AccordionContent class="px-3 pt-2">
|
||||||
</TicketTab>
|
<TicketComments :ticket="ticket" :comments="comments" />
|
||||||
<TicketTab value="tasks">
|
</AccordionContent>
|
||||||
<TicketTasks :ticket="ticket" :tasks="tasks" />
|
</AccordionItem>
|
||||||
</TicketTab>
|
<AccordionItem value="timeline" class="border-0">
|
||||||
<TicketTab value="comments">
|
<AccordionTrigger class="px-3 py-2 hover:no-underline">
|
||||||
<TicketComments :ticket="ticket" :comments="comments" />
|
<div class="flex items-center gap-2">
|
||||||
</TicketTab>
|
<span class="text-sm font-medium">Timeline</span>
|
||||||
</Tabs>
|
<Badge
|
||||||
|
v-if="timeline && timeline.length > 0"
|
||||||
|
variant="outline"
|
||||||
|
class="hidden sm:inline-flex"
|
||||||
|
>
|
||||||
|
{{ timeline.length }}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent class="px-3 pt-2">
|
||||||
|
<TicketTimeline :ticket="ticket" :timeline="timeline" />
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
<Separator class="xl:hidden" />
|
<Separator class="xl:hidden" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-4 xl:w-96 xl:flex-initial">
|
<div class="flex flex-col gap-4 xl:w-96 xl:flex-initial">
|
||||||
|
|||||||
15
ui/src/components/ui/accordion/Accordion.vue
Normal file
15
ui/src/components/ui/accordion/Accordion.vue
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { AccordionRootEmits, AccordionRootProps } from 'reka-ui'
|
||||||
|
import { AccordionRoot, useForwardPropsEmits } from 'reka-ui'
|
||||||
|
|
||||||
|
const props = defineProps<AccordionRootProps>()
|
||||||
|
const emits = defineEmits<AccordionRootEmits>()
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(props, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AccordionRoot v-bind="forwarded">
|
||||||
|
<slot />
|
||||||
|
</AccordionRoot>
|
||||||
|
</template>
|
||||||
23
ui/src/components/ui/accordion/AccordionContent.vue
Normal file
23
ui/src/components/ui/accordion/AccordionContent.vue
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { reactiveOmit } from '@vueuse/core'
|
||||||
|
import type { AccordionContentProps } from 'reka-ui'
|
||||||
|
import { AccordionContent } from 'reka-ui'
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<AccordionContentProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, 'class')
|
||||||
|
</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>
|
||||||
20
ui/src/components/ui/accordion/AccordionItem.vue
Normal file
20
ui/src/components/ui/accordion/AccordionItem.vue
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { reactiveOmit } from '@vueuse/core'
|
||||||
|
import type { AccordionItemProps } from 'reka-ui'
|
||||||
|
import { AccordionItem, useForwardProps } from 'reka-ui'
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<AccordionItemProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, 'class')
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AccordionItem v-bind="forwardedProps" :class="cn('border-b', props.class)">
|
||||||
|
<slot />
|
||||||
|
</AccordionItem>
|
||||||
|
</template>
|
||||||
33
ui/src/components/ui/accordion/AccordionTrigger.vue
Normal file
33
ui/src/components/ui/accordion/AccordionTrigger.vue
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ChevronDown } from 'lucide-vue-next'
|
||||||
|
|
||||||
|
import { reactiveOmit } from '@vueuse/core'
|
||||||
|
import type { AccordionTriggerProps } from 'reka-ui'
|
||||||
|
import { AccordionHeader, AccordionTrigger } from 'reka-ui'
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<AccordionTriggerProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, 'class')
|
||||||
|
</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>
|
||||||
4
ui/src/components/ui/accordion/index.ts
Normal file
4
ui/src/components/ui/accordion/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
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'
|
||||||
@@ -4,7 +4,7 @@ const typography = require('@tailwindcss/typography')
|
|||||||
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
darkMode: ['media'],
|
darkMode: ['media', 'class'],
|
||||||
safelist: ['dark', 'size-5', 'size-12'],
|
safelist: ['dark', 'size-5', 'size-12'],
|
||||||
prefix: '',
|
prefix: '',
|
||||||
|
|
||||||
@@ -16,90 +16,106 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
|
|
||||||
theme: {
|
theme: {
|
||||||
container: {
|
container: {
|
||||||
center: true,
|
center: true,
|
||||||
padding: '2rem',
|
padding: '2rem',
|
||||||
screens: {
|
screens: {
|
||||||
'2xl': '1400px'
|
'2xl': '1400px'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
border: 'hsl(var(--border))',
|
border: 'hsl(var(--border))',
|
||||||
input: 'hsl(var(--input))',
|
input: 'hsl(var(--input))',
|
||||||
ring: 'hsl(var(--ring))',
|
ring: 'hsl(var(--ring))',
|
||||||
background: 'hsl(var(--background))',
|
background: 'hsl(var(--background))',
|
||||||
foreground: 'hsl(var(--foreground))',
|
foreground: 'hsl(var(--foreground))',
|
||||||
primary: {
|
primary: {
|
||||||
DEFAULT: 'hsl(var(--primary))',
|
DEFAULT: 'hsl(var(--primary))',
|
||||||
foreground: 'hsl(var(--primary-foreground))'
|
foreground: 'hsl(var(--primary-foreground))'
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
DEFAULT: 'hsl(var(--secondary))',
|
DEFAULT: 'hsl(var(--secondary))',
|
||||||
foreground: 'hsl(var(--secondary-foreground))'
|
foreground: 'hsl(var(--secondary-foreground))'
|
||||||
},
|
},
|
||||||
destructive: {
|
destructive: {
|
||||||
DEFAULT: 'hsl(var(--destructive))',
|
DEFAULT: 'hsl(var(--destructive))',
|
||||||
foreground: 'hsl(var(--destructive-foreground))'
|
foreground: 'hsl(var(--destructive-foreground))'
|
||||||
},
|
},
|
||||||
muted: {
|
muted: {
|
||||||
DEFAULT: 'hsl(var(--muted))',
|
DEFAULT: 'hsl(var(--muted))',
|
||||||
foreground: 'hsl(var(--muted-foreground))'
|
foreground: 'hsl(var(--muted-foreground))'
|
||||||
},
|
},
|
||||||
accent: {
|
accent: {
|
||||||
DEFAULT: 'hsl(var(--accent))',
|
DEFAULT: 'hsl(var(--accent))',
|
||||||
foreground: 'hsl(var(--accent-foreground))'
|
foreground: 'hsl(var(--accent-foreground))'
|
||||||
},
|
},
|
||||||
popover: {
|
popover: {
|
||||||
DEFAULT: 'hsl(var(--popover))',
|
DEFAULT: 'hsl(var(--popover))',
|
||||||
foreground: 'hsl(var(--popover-foreground))'
|
foreground: 'hsl(var(--popover-foreground))'
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
DEFAULT: 'hsl(var(--card))',
|
DEFAULT: 'hsl(var(--card))',
|
||||||
foreground: 'hsl(var(--card-foreground))'
|
foreground: 'hsl(var(--card-foreground))'
|
||||||
},
|
},
|
||||||
sidebar: {
|
sidebar: {
|
||||||
DEFAULT: 'hsl(var(--sidebar-background))',
|
DEFAULT: 'hsl(var(--sidebar-background))',
|
||||||
foreground: 'hsl(var(--sidebar-foreground))',
|
foreground: 'hsl(var(--sidebar-foreground))',
|
||||||
primary: 'hsl(var(--sidebar-primary))',
|
primary: 'hsl(var(--sidebar-primary))',
|
||||||
'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
|
'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
|
||||||
accent: 'hsl(var(--sidebar-accent))',
|
accent: 'hsl(var(--sidebar-accent))',
|
||||||
'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
|
'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
|
||||||
border: 'hsl(var(--sidebar-border))',
|
border: 'hsl(var(--sidebar-border))',
|
||||||
ring: 'hsl(var(--sidebar-ring))'
|
ring: 'hsl(var(--sidebar-ring))'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
xl: 'calc(var(--radius) + 4px)',
|
xl: 'calc(var(--radius) + 4px)',
|
||||||
lg: 'var(--radius)',
|
lg: 'var(--radius)',
|
||||||
md: 'calc(var(--radius) - 2px)',
|
md: 'calc(var(--radius) - 2px)',
|
||||||
sm: 'calc(var(--radius) - 4px)'
|
sm: 'calc(var(--radius) - 4px)'
|
||||||
},
|
},
|
||||||
keyframes: {
|
keyframes: {
|
||||||
'accordion-down': {
|
'accordion-down': {
|
||||||
from: { height: 0 },
|
from: {
|
||||||
to: { height: 'var(--radix-accordion-content-height)' }
|
height: '0'
|
||||||
},
|
},
|
||||||
'accordion-up': {
|
to: {
|
||||||
from: { height: 'var(--radix-accordion-content-height)' },
|
height: 'var(--reka-accordion-content-height)'
|
||||||
to: { height: 0 }
|
}
|
||||||
},
|
},
|
||||||
'collapsible-down': {
|
'accordion-up': {
|
||||||
from: { height: 0 },
|
from: {
|
||||||
to: { height: 'var(--radix-collapsible-content-height)' }
|
height: 'var(--reka-accordion-content-height)'
|
||||||
},
|
},
|
||||||
'collapsible-up': {
|
to: {
|
||||||
from: { height: 'var(--radix-collapsible-content-height)' },
|
height: '0'
|
||||||
to: { height: 0 }
|
}
|
||||||
}
|
},
|
||||||
},
|
'collapsible-down': {
|
||||||
animation: {
|
from: {
|
||||||
'accordion-down': 'accordion-down 0.2s ease-out',
|
height: 0
|
||||||
'accordion-up': 'accordion-up 0.2s ease-out',
|
},
|
||||||
'collapsible-down': 'collapsible-down 0.2s ease-in-out',
|
to: {
|
||||||
'collapsible-up': 'collapsible-up 0.2s ease-in-out'
|
height: 'var(--radix-collapsible-content-height)'
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
'collapsible-up': {
|
||||||
|
from: {
|
||||||
|
height: 'var(--radix-collapsible-content-height)'
|
||||||
|
},
|
||||||
|
to: {
|
||||||
|
height: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
'accordion-down': 'accordion-down 0.2s ease-out',
|
||||||
|
'accordion-up': 'accordion-up 0.2s ease-out',
|
||||||
|
'collapsible-down': 'collapsible-down 0.2s ease-in-out',
|
||||||
|
'collapsible-up': 'collapsible-up 0.2s ease-in-out'
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
plugins: [animate, typography]
|
plugins: [animate, typography]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,10 +15,9 @@ test.describe('update a comment', () => {
|
|||||||
{
|
{
|
||||||
field: 'message',
|
field: 'message',
|
||||||
update: async (page) => {
|
update: async (page) => {
|
||||||
await page.getByRole('tab', { name: 'Comments' }).click()
|
|
||||||
await page.getByRole('button', { name: 'More' }).click()
|
await page.getByRole('button', { name: 'More' }).click()
|
||||||
await page.getByRole('menuitem', { name: 'Edit' }).click()
|
await page.getByRole('menuitem', { name: 'Edit' }).click()
|
||||||
await page.locator('textarea').nth(1).fill('Updated Comment')
|
await page.locator('.CodeMirror textarea').first().fill('Updated Comment')
|
||||||
await page.getByRole('button', { name: 'Save' }).click()
|
await page.getByRole('button', { name: 'Save' }).click()
|
||||||
},
|
},
|
||||||
assert: async (page) => {
|
assert: async (page) => {
|
||||||
@@ -46,7 +45,6 @@ test('can delete a comment', async ({ page }) => {
|
|||||||
await createTicket(page, ticketName)
|
await createTicket(page, ticketName)
|
||||||
const message = `comment-${randomUUID()}`
|
const message = `comment-${randomUUID()}`
|
||||||
await createComment(page, message)
|
await createComment(page, message)
|
||||||
await page.getByRole('tab', { name: 'Comments' }).click()
|
|
||||||
await page.getByRole('button', { name: 'More' }).click()
|
await page.getByRole('button', { name: 'More' }).click()
|
||||||
await page.getByRole('menuitem', { name: 'Delete' }).click()
|
await page.getByRole('menuitem', { name: 'Delete' }).click()
|
||||||
await page.getByRole('dialog').getByRole('button', { name: 'Delete' }).click()
|
await page.getByRole('dialog').getByRole('button', { name: 'Delete' }).click()
|
||||||
|
|||||||
@@ -17,9 +17,8 @@ test.describe('update a task', () => {
|
|||||||
update: async (page, taskName: string) => {
|
update: async (page, taskName: string) => {
|
||||||
await page.getByText("Toggle Sidebar").click()
|
await page.getByText("Toggle Sidebar").click()
|
||||||
|
|
||||||
await page.getByRole('tab', { name: 'Tasks' }).click()
|
|
||||||
await page.getByText(taskName).click()
|
await page.getByText(taskName).click()
|
||||||
await page.getByRole('tabpanel', { name: 'Tasks' }).getByRole('textbox').fill('Updated Task')
|
await page.locator('input[autofocus]').fill('Updated Task')
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
},
|
},
|
||||||
assert: async (page) => {
|
assert: async (page) => {
|
||||||
@@ -29,7 +28,6 @@ test.describe('update a task', () => {
|
|||||||
{
|
{
|
||||||
field: 'status',
|
field: 'status',
|
||||||
update: async (page) => {
|
update: async (page) => {
|
||||||
await page.getByRole('tab', { name: 'Tasks' }).click()
|
|
||||||
const cb = page.getByRole('checkbox').first()
|
const cb = page.getByRole('checkbox').first()
|
||||||
await cb.click()
|
await cb.click()
|
||||||
},
|
},
|
||||||
@@ -59,7 +57,6 @@ test('can delete a task', async ({ page }) => {
|
|||||||
await createTicket(page, ticketName)
|
await createTicket(page, ticketName)
|
||||||
const taskName = `task-${randomUUID()}`
|
const taskName = `task-${randomUUID()}`
|
||||||
await createTask(page, taskName, false)
|
await createTask(page, taskName, false)
|
||||||
await page.getByRole('tab', { name: 'Tasks' }).click()
|
|
||||||
await page.locator('button', { hasText: 'Delete Task' }).click()
|
await page.locator('button', { hasText: 'Delete Task' }).click()
|
||||||
await page.getByRole('dialog').getByRole('button', { name: 'Delete' }).click()
|
await page.getByRole('dialog').getByRole('button', { name: 'Delete' }).click()
|
||||||
await expect(page.getByText(taskName)).toHaveCount(0)
|
await expect(page.getByText(taskName)).toHaveCount(0)
|
||||||
|
|||||||
@@ -17,10 +17,9 @@ test.describe('update a timeline item', () => {
|
|||||||
await createTicket(page, ticketName)
|
await createTicket(page, ticketName)
|
||||||
const msg = `timeline-${randomUUID()}`
|
const msg = `timeline-${randomUUID()}`
|
||||||
await createTimeline(page, msg)
|
await createTimeline(page, msg)
|
||||||
await page.getByRole('tab', { name: 'Timeline' }).click()
|
|
||||||
await page.getByRole('button', { name: 'More' }).click()
|
await page.getByRole('button', { name: 'More' }).click()
|
||||||
await page.getByRole('menuitem', { name: 'Edit' }).click()
|
await page.getByRole('menuitem', { name: 'Edit' }).click()
|
||||||
await page.locator('textarea').nth(1).fill('Updated Timeline')
|
await page.locator('.CodeMirror textarea').first().fill('Updated Timeline')
|
||||||
await page.getByRole('button', { name: 'Save' }).click()
|
await page.getByRole('button', { name: 'Save' }).click()
|
||||||
await expect(page.getByText('Updated Timeline')).toBeVisible()
|
await expect(page.getByText('Updated Timeline')).toBeVisible()
|
||||||
})
|
})
|
||||||
@@ -32,7 +31,6 @@ test('can delete a timeline item', async ({ page }) => {
|
|||||||
await createTicket(page, ticketName)
|
await createTicket(page, ticketName)
|
||||||
const msg = `timeline-${randomUUID()}`
|
const msg = `timeline-${randomUUID()}`
|
||||||
await createTimeline(page, msg)
|
await createTimeline(page, msg)
|
||||||
await page.getByRole('tab', { name: 'Timeline' }).click()
|
|
||||||
await page.getByRole('button', { name: 'More' }).click()
|
await page.getByRole('button', { name: 'More' }).click()
|
||||||
await page.getByRole('menuitem', { name: 'Delete' }).click()
|
await page.getByRole('menuitem', { name: 'Delete' }).click()
|
||||||
await page.getByRole('dialog').getByRole('button', { name: 'Delete' }).click()
|
await page.getByRole('dialog').getByRole('button', { name: 'Delete' }).click()
|
||||||
|
|||||||
@@ -39,23 +39,26 @@ export const createTicket = async (page, name: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const createTimeline = async (page, message: string) => {
|
export const createTimeline = async (page, message: string) => {
|
||||||
await page.getByRole('tab', { name: 'Timeline' }).click()
|
|
||||||
await page.getByRole('button', { name: 'Add Timeline Item' }).click()
|
await page.getByRole('button', { name: 'Add Timeline Item' }).click()
|
||||||
await page.getByRole('tabpanel', { name: 'Timeline' }).getByRole('textbox').fill(message)
|
const editor = page.locator('.EasyMDEContainer .CodeMirror').last()
|
||||||
|
await expect(editor).toBeVisible()
|
||||||
|
await editor.click()
|
||||||
|
await page.keyboard.type(message)
|
||||||
await page.getByRole('button', { name: 'Save' }).click()
|
await page.getByRole('button', { name: 'Save' }).click()
|
||||||
await expect(page.getByText(message)).toBeVisible()
|
await expect(page.getByText(message)).toBeVisible()
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createComment = async (page, message: string) => {
|
export const createComment = async (page, message: string) => {
|
||||||
await page.getByRole('tab', { name: 'Comments' }).click()
|
|
||||||
await page.getByRole('button', { name: 'Add Comment' }).click()
|
await page.getByRole('button', { name: 'Add Comment' }).click()
|
||||||
await page.getByRole('tabpanel', { name: 'Comments' }).getByRole('textbox').fill(message)
|
const editor = page.locator('.EasyMDEContainer .CodeMirror').last()
|
||||||
|
await expect(editor).toBeVisible()
|
||||||
|
await editor.click()
|
||||||
|
await page.keyboard.type(message)
|
||||||
await page.getByRole('button', { name: 'Save' }).click()
|
await page.getByRole('button', { name: 'Save' }).click()
|
||||||
await expect(page.getByText(message)).toBeVisible()
|
await expect(page.getByText(message)).toBeVisible()
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createTask = async (page, name: string, done: boolean) => {
|
export const createTask = async (page, name: string, done: boolean) => {
|
||||||
await page.getByRole('tab', { name: 'Tasks' }).click()
|
|
||||||
await page.getByRole('button', { name: 'Add Task' }).click()
|
await page.getByRole('button', { name: 'Add Task' }).click()
|
||||||
await page.getByPlaceholder('Add a task...').fill(name)
|
await page.getByPlaceholder('Add a task...').fill(name)
|
||||||
await page.getByRole('button', { name: 'Save' }).click()
|
await page.getByRole('button', { name: 'Save' }).click()
|
||||||
|
|||||||
Reference in New Issue
Block a user