优化
This commit is contained in:
80
monisuo-admin/src/pages/tasks/components/columns.ts
Normal file
80
monisuo-admin/src/pages/tasks/components/columns.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import type { ColumnDef } from '@tanstack/vue-table'
|
||||
|
||||
import { h } from 'vue'
|
||||
|
||||
import { DataTableColumnHeader, SelectColumn } from '@/components/data-table'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
|
||||
import type { Task } from '../data/schema'
|
||||
|
||||
import { labels, priorities, statuses } from '../data/data'
|
||||
import DataTableRowActions from './data-table-row-actions.vue'
|
||||
|
||||
export const columns: ColumnDef<Task>[] = [
|
||||
SelectColumn as ColumnDef<Task>,
|
||||
{
|
||||
accessorKey: 'id',
|
||||
header: ({ column }) => h(DataTableColumnHeader<Task>, { column, title: 'Task' }),
|
||||
cell: ({ row }) => h('div', { class: 'w-20' }, row.getValue('id')),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
},
|
||||
{
|
||||
accessorKey: 'title',
|
||||
header: ({ column }) => h(DataTableColumnHeader<Task>, { column, title: 'Title' }),
|
||||
|
||||
cell: ({ row }) => {
|
||||
const label = labels.find(label => label.value === row.original.label)
|
||||
|
||||
return h('div', { class: 'flex space-x-2' }, [
|
||||
label ? h(Badge, { variant: 'outline' }, () => label.label) : null,
|
||||
h('span', { class: 'max-w-[500px] truncate font-medium' }, row.getValue('title')),
|
||||
])
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'status',
|
||||
header: ({ column }) => h(DataTableColumnHeader<Task>, { column, title: 'Status' }),
|
||||
|
||||
cell: ({ row }) => {
|
||||
const status = statuses.find(
|
||||
status => status.value === row.getValue('status'),
|
||||
)
|
||||
|
||||
if (!status)
|
||||
return null
|
||||
|
||||
return h('div', { class: 'flex w-[100px] items-center' }, [
|
||||
status.icon && h(status.icon, { class: 'mr-2 h-4 w-4 text-muted-foreground' }),
|
||||
h('span', status.label),
|
||||
])
|
||||
},
|
||||
filterFn: (row, id, value) => {
|
||||
return value.includes(row.getValue(id))
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'priority',
|
||||
header: ({ column }) => h(DataTableColumnHeader<Task>, { column, title: 'Priority' }),
|
||||
cell: ({ row }) => {
|
||||
const priority = priorities.find(
|
||||
priority => priority.value === row.getValue('priority'),
|
||||
)
|
||||
|
||||
if (!priority)
|
||||
return null
|
||||
|
||||
return h('div', { class: 'flex items-center' }, [
|
||||
priority.icon && h(priority.icon, { class: 'mr-2 h-4 w-4 text-muted-foreground' }),
|
||||
h('span', {}, priority.label),
|
||||
])
|
||||
},
|
||||
filterFn: (row, id, value) => {
|
||||
return value.includes(row.getValue(id))
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
cell: ({ row }) => h(DataTableRowActions, { row }),
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,97 @@
|
||||
<script setup lang="ts">
|
||||
import type { Row } from '@tanstack/vue-table'
|
||||
import type { Component } from 'vue'
|
||||
|
||||
import { Ellipsis, FilePenLine, Trash2 } from 'lucide-vue-next'
|
||||
|
||||
import { Modal, ModalContent } from '@/components/prop-ui/modal'
|
||||
|
||||
import type { Task } from '../data/schema'
|
||||
|
||||
import { labels } from '../data/data'
|
||||
import { taskSchema } from '../data/schema'
|
||||
|
||||
const props = defineProps<DataTableRowActionsProps>()
|
||||
|
||||
interface DataTableRowActionsProps {
|
||||
row: Row<Task>
|
||||
}
|
||||
const task = computed(() => taskSchema.parse(props.row.original))
|
||||
|
||||
const taskLabel = ref(task.value.label)
|
||||
|
||||
const showComponent = shallowRef<Component | null>(null)
|
||||
const isOpen = ref(false)
|
||||
|
||||
type TCommand = 'edit' | 'create' | 'delete'
|
||||
|
||||
const componentLoader: Record<TCommand, () => Promise<{ default: Component }>> = {
|
||||
edit: () => import('./task-resource-dialog.vue'),
|
||||
create: () => import('./task-resource-dialog.vue'),
|
||||
delete: () => import('./task-delete.vue'),
|
||||
}
|
||||
|
||||
async function handleSelect(command: TCommand) {
|
||||
try {
|
||||
const { default: component } = await componentLoader[command]()
|
||||
showComponent.value = component
|
||||
isOpen.value = true
|
||||
}
|
||||
catch (e) {
|
||||
console.error(`Failed to load component for "${command}"`, e)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal v-model:open="isOpen">
|
||||
<UiDropdownMenu>
|
||||
<UiDropdownMenuTrigger as-child>
|
||||
<UiButton
|
||||
variant="ghost"
|
||||
class="flex h-8 w-8 p-0 data-[state=open]:bg-muted"
|
||||
>
|
||||
<Ellipsis class="size-4" />
|
||||
<span class="sr-only">Open menu</span>
|
||||
</UiButton>
|
||||
</UiDropdownMenuTrigger>
|
||||
<UiDropdownMenuContent align="end" class="w-[160px]">
|
||||
<UiDropdownMenuItem @select.stop="handleSelect('edit')">
|
||||
<span>Edit</span>
|
||||
<UiDropdownMenuShortcut> <FilePenLine class="size-4" /> </UiDropdownMenuShortcut>
|
||||
</UiDropdownMenuItem>
|
||||
|
||||
<UiDropdownMenuItem disabled>
|
||||
Make a copy
|
||||
</UiDropdownMenuItem>
|
||||
<UiDropdownMenuItem disabled>
|
||||
Favorite
|
||||
</UiDropdownMenuItem>
|
||||
|
||||
<UiDropdownMenuSeparator />
|
||||
|
||||
<UiDropdownMenuSub>
|
||||
<UiDropdownMenuSubTrigger>Labels</UiDropdownMenuSubTrigger>
|
||||
<UiDropdownMenuSubContent>
|
||||
<UiDropdownMenuRadioGroup v-model="taskLabel">
|
||||
<UiDropdownMenuRadioItem v-for="label in labels" :key="label.value" :value="label.value">
|
||||
{{ label.label }}
|
||||
</UiDropdownMenuRadioItem>
|
||||
</UiDropdownMenuRadioGroup>
|
||||
</UiDropdownMenuSubContent>
|
||||
</UiDropdownMenuSub>
|
||||
|
||||
<UiDropdownMenuSeparator />
|
||||
|
||||
<UiDropdownMenuItem @select.stop="handleSelect('delete')">
|
||||
<span>Delete</span>
|
||||
<UiDropdownMenuShortcut> <Trash2 class="size-4" /> </UiDropdownMenuShortcut>
|
||||
</UiDropdownMenuItem>
|
||||
</UiDropdownMenuContent>
|
||||
</UiDropdownMenu>
|
||||
|
||||
<ModalContent>
|
||||
<component :is="showComponent" :task="task" @close="isOpen = false" />
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -0,0 +1,60 @@
|
||||
<script setup lang="ts">
|
||||
import type { Table } from '@tanstack/vue-table'
|
||||
|
||||
import { X } from 'lucide-vue-next'
|
||||
|
||||
import { DataTableFacetedFilter, DataTableViewOptions } from '@/components/data-table'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
|
||||
import type { Task } from '../data/schema'
|
||||
|
||||
import { priorities, statuses } from '../data/data'
|
||||
|
||||
interface DataTableToolbarProps {
|
||||
table: Table<Task>
|
||||
}
|
||||
|
||||
const props = defineProps<DataTableToolbarProps>()
|
||||
|
||||
const isFiltered = computed(() => props.table.getState().columnFilters.length > 0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-col items-start flex-1 space-y-2 md:items-center md:space-x-2 md:space-y-0 md:flex-row">
|
||||
<Input
|
||||
placeholder="Filter tasks..."
|
||||
:model-value="(table.getColumn('title')?.getFilterValue() as string) ?? ''"
|
||||
class="h-8 w-[150px] lg:w-[250px]"
|
||||
@input="table.getColumn('title')?.setFilterValue($event.target.value)"
|
||||
/>
|
||||
|
||||
<div class="space-x-2">
|
||||
<DataTableFacetedFilter
|
||||
v-if="table.getColumn('status')"
|
||||
:column="table.getColumn('status')"
|
||||
title="Status"
|
||||
:options="statuses"
|
||||
/>
|
||||
<DataTableFacetedFilter
|
||||
v-if="table.getColumn('priority')"
|
||||
:column="table.getColumn('priority')"
|
||||
title="Priority"
|
||||
:options="priorities"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
v-if="isFiltered"
|
||||
variant="ghost"
|
||||
class="h-8 px-2 lg:px-3"
|
||||
@click="table.resetColumnFilters()"
|
||||
>
|
||||
Reset
|
||||
<X class="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<DataTableViewOptions :table="table" />
|
||||
</div>
|
||||
</template>
|
||||
51
monisuo-admin/src/pages/tasks/components/data-table.vue
Normal file
51
monisuo-admin/src/pages/tasks/components/data-table.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<script setup lang="ts">
|
||||
import { Trash2Icon } from 'lucide-vue-next'
|
||||
|
||||
import type { DataTableProps } from '@/components/data-table'
|
||||
|
||||
import { DataTable, DataTableBulkActions, useGenerateVueTable } from '@/components/data-table'
|
||||
|
||||
import type { Task } from '../data/schema'
|
||||
|
||||
import DataTableToolbar from './data-table-toolbar.vue'
|
||||
import TaskDeleteBatch from './task-delete-batch.vue'
|
||||
|
||||
const props = defineProps<DataTableProps<Task>>()
|
||||
const table = useGenerateVueTable<Task>(props)
|
||||
|
||||
const taskDeleteBatchOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DataTableBulkActions entity-name="task" :table="table">
|
||||
<UiTooltip>
|
||||
<UiTooltipTrigger as-child>
|
||||
<UiButton
|
||||
variant="destructive"
|
||||
size="icon"
|
||||
class="size-8"
|
||||
aria-label="Delete selected tasks"
|
||||
title="Delete selected tasks"
|
||||
@click="taskDeleteBatchOpen = true"
|
||||
>
|
||||
<Trash2Icon />
|
||||
<span class="sr-only">Delete selected tasks</span>
|
||||
</UiButton>
|
||||
</UiTooltipTrigger>
|
||||
<UiTooltipContent>
|
||||
<p>Delete selected tasks</p>
|
||||
</UiTooltipContent>
|
||||
</UiTooltip>
|
||||
|
||||
<TaskDeleteBatch
|
||||
v-model:open="taskDeleteBatchOpen"
|
||||
:table
|
||||
/>
|
||||
</DataTableBulkActions>
|
||||
|
||||
<DataTable :columns :table :data :loading>
|
||||
<template #toolbar>
|
||||
<DataTableToolbar :table="table" class="w-full overflow-x-auto" />
|
||||
</template>
|
||||
</DataTable>
|
||||
</template>
|
||||
24
monisuo-admin/src/pages/tasks/components/task-create.vue
Normal file
24
monisuo-admin/src/pages/tasks/components/task-create.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<script lang="ts" setup>
|
||||
import { Plus } from 'lucide-vue-next'
|
||||
|
||||
import { Modal, ModalContent, ModalTrigger } from '@/components/prop-ui/modal'
|
||||
|
||||
import TaskResourceDialog from './task-resource-dialog.vue'
|
||||
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal v-model:open="isOpen">
|
||||
<ModalTrigger as-child>
|
||||
<UiButton>
|
||||
Create
|
||||
<Plus />
|
||||
</UiButton>
|
||||
</ModalTrigger>
|
||||
|
||||
<ModalContent>
|
||||
<TaskResourceDialog :task="null" @close="isOpen = false" />
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -0,0 +1,76 @@
|
||||
<script lang="ts" setup generic="T = Task">
|
||||
import type { Table as VueTable } from '@tanstack/vue-table'
|
||||
|
||||
import { toast } from 'vue-sonner'
|
||||
|
||||
import ConfirmDialog from '@/components/confirm-dialog.vue'
|
||||
|
||||
import type { Task } from '../data/schema'
|
||||
|
||||
const { table } = defineProps<{
|
||||
table: VueTable<T>
|
||||
}>()
|
||||
|
||||
const openModel = defineModel<boolean>('open', {
|
||||
default: false,
|
||||
})
|
||||
|
||||
const CONFIRM_WORD = 'DELETE'
|
||||
|
||||
const confirmValue = ref('')
|
||||
|
||||
const selectedRows = computed(() => table.getSelectedRowModel().rows)
|
||||
const selectedCount = computed(() => selectedRows.value.length || 0)
|
||||
function handleConfirm() {
|
||||
if (confirmValue.value !== CONFIRM_WORD) {
|
||||
toast.error(`Please type "${CONFIRM_WORD}" to confirm deletion.`)
|
||||
return
|
||||
}
|
||||
|
||||
openModel.value = false
|
||||
|
||||
toast.promise(new Promise(resolve => setTimeout(resolve, 2000)), {
|
||||
loading: 'Deleting tasks...',
|
||||
success: () => {
|
||||
table.resetRowSelection()
|
||||
return `Successfully deleted ${selectedRows.value.length} tasks.`
|
||||
},
|
||||
error: 'Failed to delete tasks.',
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ConfirmDialog
|
||||
v-model:open="openModel"
|
||||
confirm-button-text="Delete"
|
||||
destructive
|
||||
:disabled="confirmValue.trim() !== CONFIRM_WORD"
|
||||
@confirm="handleConfirm"
|
||||
>
|
||||
<template #title>
|
||||
Delete {{ selectedCount }} tasks?
|
||||
</template>
|
||||
<template #description>
|
||||
Are you sure you want to delete the selected tasks? <br>
|
||||
This action cannot be undone.
|
||||
</template>
|
||||
|
||||
<template #default>
|
||||
<UiLabel class="my-4 flex flex-col items-start gap-1.5">
|
||||
<span>Confirm by typing {{ CONFIRM_WORD }}:</span>
|
||||
<UiInput
|
||||
v-model="confirmValue"
|
||||
:placeholder="`Type "${CONFIRM_WORD}" to confirm.`"
|
||||
/>
|
||||
</UiLabel>
|
||||
|
||||
<UiAlert variant="destructive">
|
||||
<UiAlertTitle>Warning!</UiAlertTitle>
|
||||
<UiAlertDescription>
|
||||
Please be careful, this operation can not be rolled back.
|
||||
</UiAlertDescription>
|
||||
</UiAlert>
|
||||
</template>
|
||||
</ConfirmDialog>
|
||||
</template>
|
||||
44
monisuo-admin/src/pages/tasks/components/task-delete.vue
Normal file
44
monisuo-admin/src/pages/tasks/components/task-delete.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<script lang="ts" setup>
|
||||
import { toast } from 'vue-sonner'
|
||||
|
||||
import { ModalClose, ModalDescription, ModalFooter, ModalHeader, ModalTitle } from '@/components/prop-ui/modal'
|
||||
|
||||
import type { Task } from '../data/schema'
|
||||
|
||||
const props = defineProps<{
|
||||
task: Task
|
||||
}>()
|
||||
|
||||
function handleRemove() {
|
||||
toast(`The following task has been deleted:`, {
|
||||
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(props.task, null, 2))),
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<ModalHeader>
|
||||
<ModalTitle>
|
||||
Delete this task: {{ task.id }} ?
|
||||
</ModalTitle>
|
||||
<ModalDescription>
|
||||
You are about to delete a task with the ID {{ task.id }}. This action cannot be undone.
|
||||
</ModalDescription>
|
||||
</ModalHeader>
|
||||
|
||||
<ModalFooter>
|
||||
<ModalClose as-child>
|
||||
<UiButton variant="outline">
|
||||
Cancel
|
||||
</UiButton>
|
||||
</ModalClose>
|
||||
|
||||
<ModalClose as-child>
|
||||
<UiButton variant="destructive" @click="handleRemove">
|
||||
Delete
|
||||
</UiButton>
|
||||
</ModalClose>
|
||||
</ModalFooter>
|
||||
</div>
|
||||
</template>
|
||||
133
monisuo-admin/src/pages/tasks/components/task-form.vue
Normal file
133
monisuo-admin/src/pages/tasks/components/task-form.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<script lang="ts" setup>
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toast } from 'vue-sonner'
|
||||
|
||||
import { FormField } from '@/components/ui/form'
|
||||
|
||||
import type { Task } from '../data/schema'
|
||||
import type { TaskValidator } from '../validators/task.validator'
|
||||
|
||||
import { labels, priorities, statuses } from '../data/data'
|
||||
import { taskValidator } from '../validators/task.validator'
|
||||
|
||||
const props = defineProps<{
|
||||
task: Task | null
|
||||
}>()
|
||||
const emits = defineEmits(['close'])
|
||||
|
||||
const formSchema = toTypedSchema(taskValidator)
|
||||
|
||||
const initialValues = reactive<TaskValidator>({
|
||||
title: props.task ? props.task.title : '',
|
||||
status: props.task ? props.task.status : 'backlog',
|
||||
label: props.task ? props.task.label : 'feature',
|
||||
priority: props.task ? props.task.priority : 'medium',
|
||||
})
|
||||
|
||||
const { isFieldDirty, handleSubmit } = useForm({
|
||||
validationSchema: formSchema,
|
||||
initialValues,
|
||||
})
|
||||
const onSubmit = handleSubmit((values) => {
|
||||
toast('You submitted the following values:', {
|
||||
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||
})
|
||||
emits('close')
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<form class="w-2/3 space-y-6" @submit="onSubmit">
|
||||
<FormField v-slot="{ componentField }" name="title" :validate-on-blur="!isFieldDirty">
|
||||
<UiFormItem>
|
||||
<UiFormLabel>Title</UiFormLabel>
|
||||
<UiFormControl>
|
||||
<UiInput type="text" placeholder="shadcn" v-bind="componentField" />
|
||||
</UiFormControl>
|
||||
<UiFormDescription />
|
||||
<UiFormMessage />
|
||||
</UiFormItem>
|
||||
</FormField>
|
||||
<FormField v-slot="{ componentField }" name="status" :validate-on-blur="!isFieldDirty">
|
||||
<UiFormItem>
|
||||
<UiFormLabel>status</UiFormLabel>
|
||||
<UiFormControl>
|
||||
<UiSelect v-bind="componentField">
|
||||
<UiSelectTrigger class="w-[180px]">
|
||||
<UiSelectValue placeholder="Select a status" />
|
||||
</UiSelectTrigger>
|
||||
<UiSelectContent>
|
||||
<UiSelectGroup>
|
||||
<UiSelectItem v-for="status in statuses" :key="status.value" :value="status.value">
|
||||
<div class="flex items-center gap-2">
|
||||
<component :is="status.icon" class="size-4 shrink-0" />
|
||||
{{ status.label }}
|
||||
</div>
|
||||
</UiSelectItem>
|
||||
</UiSelectGroup>
|
||||
</UiSelectContent>
|
||||
</UiSelect>
|
||||
</UiFormControl>
|
||||
<UiFormDescription />
|
||||
<UiFormMessage />
|
||||
</UiFormItem>
|
||||
</FormField>
|
||||
<FormField v-slot="{ componentField }" name="label" :validate-on-blur="!isFieldDirty">
|
||||
<UiFormItem>
|
||||
<UiFormLabel>label</UiFormLabel>
|
||||
<UiFormControl>
|
||||
<UiRadioGroup
|
||||
class="flex flex-col space-y-1"
|
||||
v-bind="componentField"
|
||||
>
|
||||
<UiFormItem
|
||||
v-for="label in labels" :key="label.value"
|
||||
class="flex items-center space-y-0 gap-x-3"
|
||||
>
|
||||
<UiFormControl>
|
||||
<UiRadioGroupItem :value="label.value" />
|
||||
</UiFormControl>
|
||||
<UiFormLabel class="font-normal">
|
||||
{{ label.label }}
|
||||
</UiFormLabel>
|
||||
</UiFormItem>
|
||||
</UiRadioGroup>
|
||||
</UiFormControl>
|
||||
<UiFormDescription />
|
||||
<UiFormMessage />
|
||||
</UiFormItem>
|
||||
</FormField>
|
||||
<FormField v-slot="{ componentField }" name="priority" :validate-on-blur="!isFieldDirty">
|
||||
<UiFormItem>
|
||||
<UiFormLabel>priority</UiFormLabel>
|
||||
<UiFormControl>
|
||||
<UiRadioGroup
|
||||
class="flex flex-col space-y-1"
|
||||
v-bind="componentField"
|
||||
>
|
||||
<UiFormItem
|
||||
v-for="priority in priorities" :key="priority.value"
|
||||
class="flex items-center space-y-0 gap-x-3"
|
||||
>
|
||||
<UiFormControl>
|
||||
<UiRadioGroupItem :value="priority.value" />
|
||||
</UiFormControl>
|
||||
<UiFormLabel class="font-normal">
|
||||
{{ priority.label }}
|
||||
</UiFormLabel>
|
||||
</UiFormItem>
|
||||
</UiRadioGroup>
|
||||
</UiFormControl>
|
||||
<UiFormDescription />
|
||||
<UiFormMessage />
|
||||
</UiFormItem>
|
||||
</FormField>
|
||||
|
||||
<UiButton type="submit">
|
||||
Submit
|
||||
</UiButton>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
68
monisuo-admin/src/pages/tasks/components/task-import.vue
Normal file
68
monisuo-admin/src/pages/tasks/components/task-import.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<script lang="ts" setup>
|
||||
import { Download } from 'lucide-vue-next'
|
||||
import { toast } from 'vue-sonner'
|
||||
|
||||
import { Modal, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalTitle, ModalTrigger } from '@/components/prop-ui/modal'
|
||||
|
||||
const isOpen = ref(false)
|
||||
const file = ref()
|
||||
const error = ref()
|
||||
|
||||
watch(file, () => {
|
||||
error.value = null
|
||||
})
|
||||
watch(isOpen, () => {
|
||||
file.value = null
|
||||
})
|
||||
|
||||
function onSubmit() {
|
||||
error.value = null
|
||||
|
||||
if (!file.value) {
|
||||
error.value = 'File is required'
|
||||
return
|
||||
}
|
||||
|
||||
toast('You submitted the following values:', {
|
||||
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(file.value, null, 2))),
|
||||
})
|
||||
isOpen.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal v-model:open="isOpen">
|
||||
<ModalTrigger as-child>
|
||||
<UiButton variant="outline">
|
||||
Import
|
||||
<Download />
|
||||
</UiButton>
|
||||
</ModalTrigger>
|
||||
|
||||
<ModalContent>
|
||||
<ModalHeader>
|
||||
<ModalTitle>
|
||||
Import Tasks
|
||||
</ModalTitle>
|
||||
<ModalDescription>
|
||||
Import tasks quickly from a CSV file.
|
||||
</ModalDescription>
|
||||
</ModalHeader>
|
||||
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<UiLabel>File</UiLabel>
|
||||
<UiInput id="file" v-model="file" type="file" />
|
||||
<span v-if="error" class="text-destructive">{{ error }}</span>
|
||||
</div>
|
||||
|
||||
<ModalFooter>
|
||||
<UiButton variant="secondary" @click="isOpen = false">
|
||||
Cancel
|
||||
</UiButton>
|
||||
<UiButton @click="onSubmit">
|
||||
Import
|
||||
</UiButton>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -0,0 +1,30 @@
|
||||
<script lang="ts" setup>
|
||||
import { ModalDescription, ModalHeader, ModalTitle } from '@/components/prop-ui/modal'
|
||||
|
||||
import type { Task } from '../data/schema'
|
||||
|
||||
import TaskForm from './task-form.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
task: Task | null
|
||||
}>()
|
||||
defineEmits(['close'])
|
||||
|
||||
const task = computed(() => props.task)
|
||||
const title = computed(() => task.value?.id ? `Edit Task` : 'New Task')
|
||||
const description = computed(() => task.value?.id ? `Edit task ${task.value.id}` : 'Create new task')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<ModalHeader>
|
||||
<ModalTitle>
|
||||
{{ title }}
|
||||
</ModalTitle>
|
||||
<ModalDescription>
|
||||
{{ description }}
|
||||
</ModalDescription>
|
||||
</ModalHeader>
|
||||
<TaskForm class="mt-2" :task="task" @close="$emit('close')" />
|
||||
</div>
|
||||
</template>
|
||||
72
monisuo-admin/src/pages/tasks/data/data.ts
Normal file
72
monisuo-admin/src/pages/tasks/data/data.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import {
|
||||
ArrowDown,
|
||||
ArrowRight,
|
||||
ArrowUp,
|
||||
Circle,
|
||||
CircleCheck,
|
||||
CircleHelp,
|
||||
CirclePlus,
|
||||
TimerOff,
|
||||
} from 'lucide-vue-next'
|
||||
import { h } from 'vue'
|
||||
|
||||
export const labels = [
|
||||
{
|
||||
value: 'bug',
|
||||
label: 'Bug',
|
||||
},
|
||||
{
|
||||
value: 'feature',
|
||||
label: 'Feature',
|
||||
},
|
||||
{
|
||||
value: 'documentation',
|
||||
label: 'Documentation',
|
||||
},
|
||||
]
|
||||
|
||||
export const statuses = [
|
||||
{
|
||||
value: 'backlog',
|
||||
label: 'Backlog',
|
||||
icon: h(CircleHelp),
|
||||
},
|
||||
{
|
||||
value: 'todo',
|
||||
label: 'Todo',
|
||||
icon: h(Circle),
|
||||
},
|
||||
{
|
||||
value: 'in progress',
|
||||
label: 'In Progress',
|
||||
icon: h(TimerOff),
|
||||
},
|
||||
{
|
||||
value: 'done',
|
||||
label: 'Done',
|
||||
icon: h(CircleCheck),
|
||||
},
|
||||
{
|
||||
value: 'canceled',
|
||||
label: 'Canceled',
|
||||
icon: h(CirclePlus),
|
||||
},
|
||||
]
|
||||
|
||||
export const priorities = [
|
||||
{
|
||||
value: 'low',
|
||||
label: 'Low',
|
||||
icon: h(ArrowDown),
|
||||
},
|
||||
{
|
||||
value: 'medium',
|
||||
label: 'Medium',
|
||||
icon: h(ArrowRight),
|
||||
},
|
||||
{
|
||||
value: 'high',
|
||||
label: 'High',
|
||||
icon: h(ArrowUp),
|
||||
},
|
||||
]
|
||||
13
monisuo-admin/src/pages/tasks/data/schema.ts
Normal file
13
monisuo-admin/src/pages/tasks/data/schema.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
// We're keeping a simple non-relational schema here.
|
||||
// IRL, you will have a schema for your data models.
|
||||
export const taskSchema = z.object({
|
||||
id: z.string(),
|
||||
title: z.string(),
|
||||
status: z.string(),
|
||||
label: z.string(),
|
||||
priority: z.string(),
|
||||
})
|
||||
|
||||
export type Task = z.infer<typeof taskSchema>
|
||||
702
monisuo-admin/src/pages/tasks/data/tasks.json
Normal file
702
monisuo-admin/src/pages/tasks/data/tasks.json
Normal file
@@ -0,0 +1,702 @@
|
||||
[
|
||||
{
|
||||
"id": "TASK-8782",
|
||||
"title": "You can't compress the program without quantifying the open-source SSD pixel!",
|
||||
"status": "in progress",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7878",
|
||||
"title": "Try to calculate the EXE feed, maybe it will index the multi-byte pixel!",
|
||||
"status": "backlog",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7839",
|
||||
"title": "We need to bypass the neural TCP card!",
|
||||
"status": "todo",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5562",
|
||||
"title": "The SAS interface is down, bypass the open-source pixel so we can back up the PNG bandwidth!",
|
||||
"status": "backlog",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-8686",
|
||||
"title": "I'll parse the wireless SSL protocol, that should driver the API panel!",
|
||||
"status": "canceled",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1280",
|
||||
"title": "Use the digital TLS panel, then you can transmit the haptic system!",
|
||||
"status": "done",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7262",
|
||||
"title": "The UTF8 application is down, parse the neural bandwidth so we can back up the PNG firewall!",
|
||||
"status": "done",
|
||||
"label": "feature",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1138",
|
||||
"title": "Generating the driver won't do anything, we need to quantify the 1080p SMTP bandwidth!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7184",
|
||||
"title": "We need to program the back-end THX pixel!",
|
||||
"status": "todo",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5160",
|
||||
"title": "Calculating the bus won't do anything, we need to navigate the back-end JSON protocol!",
|
||||
"status": "in progress",
|
||||
"label": "documentation",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5618",
|
||||
"title": "Generating the driver won't do anything, we need to index the online SSL application!",
|
||||
"status": "done",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6699",
|
||||
"title": "I'll transmit the wireless JBOD capacitor, that should hard drive the SSD feed!",
|
||||
"status": "backlog",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-2858",
|
||||
"title": "We need to override the online UDP bus!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9864",
|
||||
"title": "I'll reboot the 1080p FTP panel, that should matrix the HEX hard drive!",
|
||||
"status": "done",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-8404",
|
||||
"title": "We need to generate the virtual HEX alarm!",
|
||||
"status": "in progress",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5365",
|
||||
"title": "Backing up the pixel won't do anything, we need to transmit the primary IB array!",
|
||||
"status": "in progress",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1780",
|
||||
"title": "The CSS feed is down, index the bluetooth transmitter so we can compress the CLI protocol!",
|
||||
"status": "todo",
|
||||
"label": "documentation",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6938",
|
||||
"title": "Use the redundant SCSI application, then you can hack the optical alarm!",
|
||||
"status": "todo",
|
||||
"label": "documentation",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9885",
|
||||
"title": "We need to compress the auxiliary VGA driver!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3216",
|
||||
"title": "Transmitting the transmitter won't do anything, we need to compress the virtual HDD sensor!",
|
||||
"status": "backlog",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9285",
|
||||
"title": "The IP monitor is down, copy the haptic alarm so we can generate the HTTP transmitter!",
|
||||
"status": "todo",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1024",
|
||||
"title": "Overriding the microchip won't do anything, we need to transmit the digital OCR transmitter!",
|
||||
"status": "in progress",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7068",
|
||||
"title": "You can't generate the capacitor without indexing the wireless HEX pixel!",
|
||||
"status": "canceled",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6502",
|
||||
"title": "Navigating the microchip won't do anything, we need to bypass the back-end SQL bus!",
|
||||
"status": "todo",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5326",
|
||||
"title": "We need to hack the redundant UTF8 transmitter!",
|
||||
"status": "todo",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6274",
|
||||
"title": "Use the virtual PCI circuit, then you can parse the bluetooth alarm!",
|
||||
"status": "canceled",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1571",
|
||||
"title": "I'll input the neural DRAM circuit, that should protocol the SMTP interface!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9518",
|
||||
"title": "Compressing the interface won't do anything, we need to compress the online SDD matrix!",
|
||||
"status": "canceled",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5581",
|
||||
"title": "I'll synthesize the digital COM pixel, that should transmitter the UTF8 protocol!",
|
||||
"status": "backlog",
|
||||
"label": "documentation",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-2197",
|
||||
"title": "Parsing the feed won't do anything, we need to copy the bluetooth DRAM bus!",
|
||||
"status": "todo",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-8484",
|
||||
"title": "We need to parse the solid state UDP firewall!",
|
||||
"status": "in progress",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9892",
|
||||
"title": "If we back up the application, we can get to the UDP application through the multi-byte THX capacitor!",
|
||||
"status": "done",
|
||||
"label": "documentation",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9616",
|
||||
"title": "We need to synthesize the cross-platform ASCII pixel!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9744",
|
||||
"title": "Use the back-end IP card, then you can input the solid state hard drive!",
|
||||
"status": "done",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1376",
|
||||
"title": "Generating the alarm won't do anything, we need to generate the mobile IP capacitor!",
|
||||
"status": "backlog",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7382",
|
||||
"title": "If we back up the firewall, we can get to the RAM alarm through the primary UTF8 pixel!",
|
||||
"status": "todo",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-2290",
|
||||
"title": "I'll compress the virtual JSON panel, that should application the UTF8 bus!",
|
||||
"status": "canceled",
|
||||
"label": "documentation",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1533",
|
||||
"title": "You can't input the firewall without overriding the wireless TCP firewall!",
|
||||
"status": "done",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4920",
|
||||
"title": "Bypassing the hard drive won't do anything, we need to input the bluetooth JSON program!",
|
||||
"status": "in progress",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5168",
|
||||
"title": "If we synthesize the bus, we can get to the IP panel through the virtual TLS array!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7103",
|
||||
"title": "We need to parse the multi-byte EXE bandwidth!",
|
||||
"status": "canceled",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4314",
|
||||
"title": "If we compress the program, we can get to the XML alarm through the multi-byte COM matrix!",
|
||||
"status": "in progress",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3415",
|
||||
"title": "Use the cross-platform XML application, then you can quantify the solid state feed!",
|
||||
"status": "todo",
|
||||
"label": "feature",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-8339",
|
||||
"title": "Try to calculate the DNS interface, maybe it will input the bluetooth capacitor!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6995",
|
||||
"title": "Try to hack the XSS bandwidth, maybe it will override the bluetooth matrix!",
|
||||
"status": "todo",
|
||||
"label": "feature",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-8053",
|
||||
"title": "If we connect the program, we can get to the UTF8 matrix through the digital UDP protocol!",
|
||||
"status": "todo",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4336",
|
||||
"title": "If we synthesize the microchip, we can get to the SAS sensor through the optical UDP program!",
|
||||
"status": "todo",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-8790",
|
||||
"title": "I'll back up the optical COM alarm, that should alarm the RSS capacitor!",
|
||||
"status": "done",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-8980",
|
||||
"title": "Try to navigate the SQL transmitter, maybe it will back up the virtual firewall!",
|
||||
"status": "canceled",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7342",
|
||||
"title": "Use the neural CLI card, then you can parse the online port!",
|
||||
"status": "backlog",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5608",
|
||||
"title": "I'll hack the haptic SSL program, that should bus the UDP transmitter!",
|
||||
"status": "canceled",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1606",
|
||||
"title": "I'll generate the bluetooth PNG firewall, that should pixel the SSL driver!",
|
||||
"status": "done",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7872",
|
||||
"title": "Transmitting the circuit won't do anything, we need to reboot the 1080p RSS monitor!",
|
||||
"status": "canceled",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4167",
|
||||
"title": "Use the cross-platform SMS circuit, then you can synthesize the optical feed!",
|
||||
"status": "canceled",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9581",
|
||||
"title": "You can't index the port without hacking the cross-platform XSS monitor!",
|
||||
"status": "backlog",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-8806",
|
||||
"title": "We need to bypass the back-end SSL panel!",
|
||||
"status": "done",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6542",
|
||||
"title": "Try to quantify the RSS firewall, maybe it will quantify the open-source system!",
|
||||
"status": "done",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6806",
|
||||
"title": "The VGA protocol is down, reboot the back-end matrix so we can parse the CSS panel!",
|
||||
"status": "canceled",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9549",
|
||||
"title": "You can't bypass the bus without connecting the neural JBOD bus!",
|
||||
"status": "todo",
|
||||
"label": "feature",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1075",
|
||||
"title": "Backing up the driver won't do anything, we need to parse the redundant RAM pixel!",
|
||||
"status": "done",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1427",
|
||||
"title": "Use the auxiliary PCI circuit, then you can calculate the cross-platform interface!",
|
||||
"status": "done",
|
||||
"label": "documentation",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1907",
|
||||
"title": "Hacking the circuit won't do anything, we need to back up the online DRAM system!",
|
||||
"status": "todo",
|
||||
"label": "documentation",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4309",
|
||||
"title": "If we generate the system, we can get to the TCP sensor through the optical GB pixel!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3973",
|
||||
"title": "I'll parse the back-end ADP array, that should bandwidth the RSS bandwidth!",
|
||||
"status": "todo",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7962",
|
||||
"title": "Use the wireless RAM program, then you can hack the cross-platform feed!",
|
||||
"status": "canceled",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3360",
|
||||
"title": "You can't quantify the program without synthesizing the neural OCR interface!",
|
||||
"status": "done",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9887",
|
||||
"title": "Use the auxiliary ASCII sensor, then you can connect the solid state port!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3649",
|
||||
"title": "I'll input the virtual USB system, that should circuit the DNS monitor!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3586",
|
||||
"title": "If we quantify the circuit, we can get to the CLI feed through the mobile SMS hard drive!",
|
||||
"status": "in progress",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5150",
|
||||
"title": "I'll hack the wireless XSS port, that should transmitter the IP interface!",
|
||||
"status": "canceled",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3652",
|
||||
"title": "The SQL interface is down, override the optical bus so we can program the ASCII interface!",
|
||||
"status": "backlog",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6884",
|
||||
"title": "Use the digital PCI circuit, then you can synthesize the multi-byte microchip!",
|
||||
"status": "canceled",
|
||||
"label": "feature",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1591",
|
||||
"title": "We need to connect the mobile XSS driver!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3802",
|
||||
"title": "Try to override the ASCII protocol, maybe it will parse the virtual matrix!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7253",
|
||||
"title": "Programming the capacitor won't do anything, we need to bypass the neural IB hard drive!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9739",
|
||||
"title": "We need to hack the multi-byte HDD bus!",
|
||||
"status": "done",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4424",
|
||||
"title": "Try to hack the HEX alarm, maybe it will connect the optical pixel!",
|
||||
"status": "in progress",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3922",
|
||||
"title": "You can't back up the capacitor without generating the wireless PCI program!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4921",
|
||||
"title": "I'll index the open-source IP feed, that should system the GB application!",
|
||||
"status": "canceled",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5814",
|
||||
"title": "We need to calculate the 1080p AGP feed!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-2645",
|
||||
"title": "Synthesizing the system won't do anything, we need to navigate the multi-byte HDD firewall!",
|
||||
"status": "todo",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4535",
|
||||
"title": "Try to copy the JSON circuit, maybe it will connect the wireless feed!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4463",
|
||||
"title": "We need to copy the solid state AGP monitor!",
|
||||
"status": "done",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9745",
|
||||
"title": "If we connect the protocol, we can get to the GB system through the bluetooth PCI microchip!",
|
||||
"status": "canceled",
|
||||
"label": "feature",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-2080",
|
||||
"title": "If we input the bus, we can get to the RAM matrix through the auxiliary RAM card!",
|
||||
"status": "todo",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3838",
|
||||
"title": "I'll bypass the online TCP application, that should panel the AGP system!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1340",
|
||||
"title": "We need to navigate the virtual PNG circuit!",
|
||||
"status": "todo",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6665",
|
||||
"title": "If we parse the monitor, we can get to the SSD hard drive through the cross-platform AGP alarm!",
|
||||
"status": "canceled",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7585",
|
||||
"title": "If we calculate the hard drive, we can get to the SSL program through the multi-byte CSS microchip!",
|
||||
"status": "backlog",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6319",
|
||||
"title": "We need to copy the multi-byte SCSI program!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4369",
|
||||
"title": "Try to input the SCSI bus, maybe it will generate the 1080p pixel!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9035",
|
||||
"title": "We need to override the solid state PNG array!",
|
||||
"status": "canceled",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3970",
|
||||
"title": "You can't index the transmitter without quantifying the haptic ASCII card!",
|
||||
"status": "todo",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4473",
|
||||
"title": "You can't bypass the protocol without overriding the neural RSS program!",
|
||||
"status": "todo",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4136",
|
||||
"title": "You can't hack the hard drive without hacking the primary JSON program!",
|
||||
"status": "canceled",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3939",
|
||||
"title": "Use the back-end SQL firewall, then you can connect the neural hard drive!",
|
||||
"status": "done",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-2007",
|
||||
"title": "I'll input the back-end USB protocol, that should bandwidth the PCI system!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7516",
|
||||
"title": "Use the primary SQL program, then you can generate the auxiliary transmitter!",
|
||||
"status": "done",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6906",
|
||||
"title": "Try to back up the DRAM system, maybe it will reboot the online transmitter!",
|
||||
"status": "done",
|
||||
"label": "feature",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5207",
|
||||
"title": "The SMS interface is down, copy the bluetooth bus so we can quantify the VGA card!",
|
||||
"status": "in progress",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
}
|
||||
]
|
||||
25
monisuo-admin/src/pages/tasks/index.vue
Normal file
25
monisuo-admin/src/pages/tasks/index.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import { BasicPage } from '@/components/global-layout'
|
||||
|
||||
import { columns } from './components/columns'
|
||||
import DataTable from './components/data-table.vue'
|
||||
import TaskCreate from './components/task-create.vue'
|
||||
import TaskImport from './components/task-import.vue'
|
||||
import tasks from './data/tasks.json'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BasicPage
|
||||
title="Tasks"
|
||||
description="Tasks description"
|
||||
sticky
|
||||
>
|
||||
<template #actions>
|
||||
<TaskImport />
|
||||
<TaskCreate />
|
||||
</template>
|
||||
<div class="overflow-x-auto">
|
||||
<DataTable :data="tasks" :columns="columns" />
|
||||
</div>
|
||||
</BasicPage>
|
||||
</template>
|
||||
10
monisuo-admin/src/pages/tasks/validators/task.validator.ts
Normal file
10
monisuo-admin/src/pages/tasks/validators/task.validator.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import z from 'zod'
|
||||
|
||||
export const taskValidator = z.object({
|
||||
title: z.string().min(2).max(50),
|
||||
status: z.string(),
|
||||
label: z.string(),
|
||||
priority: z.string(),
|
||||
})
|
||||
|
||||
export type TaskValidator = z.infer<typeof taskValidator>
|
||||
Reference in New Issue
Block a user