init
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
|
||||||
|
charset = utf-8
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
end_of_line = lf
|
||||||
|
max_line_length = 100
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Cypress
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Vitest
|
||||||
|
__screenshots__/
|
||||||
|
|
||||||
|
# Vite
|
||||||
|
*.timestamp-*-*.mjs
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||||
|
"plugins": ["eslint", "typescript", "unicorn", "oxc", "vue"],
|
||||||
|
"env": {
|
||||||
|
"browser": true
|
||||||
|
},
|
||||||
|
"categories": {
|
||||||
|
"correctness": "error"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/prettierrc",
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 100
|
||||||
|
}
|
||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"Vue.volar",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"EditorConfig.EditorConfig",
|
||||||
|
"oxc.oxc-vscode",
|
||||||
|
"esbenp.prettier-vscode"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# chat-app
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 in Vite.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||||
|
|
||||||
|
## Recommended Browser Setup
|
||||||
|
|
||||||
|
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
|
||||||
|
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
|
||||||
|
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
|
||||||
|
- Firefox:
|
||||||
|
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
|
||||||
|
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
|
||||||
|
|
||||||
|
## Type Support for `.vue` Imports in TS
|
||||||
|
|
||||||
|
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||||
|
|
||||||
|
## Customize configuration
|
||||||
|
|
||||||
|
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Hot-Reload for Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Type-Check, Compile and Minify for Production
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lint with [ESLint](https://eslint.org/)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { globalIgnores } from 'eslint/config'
|
||||||
|
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
|
||||||
|
import pluginVue from 'eslint-plugin-vue'
|
||||||
|
import pluginOxlint from 'eslint-plugin-oxlint'
|
||||||
|
import skipFormatting from 'eslint-config-prettier/flat'
|
||||||
|
|
||||||
|
// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
|
||||||
|
// import { configureVueProject } from '@vue/eslint-config-typescript'
|
||||||
|
// configureVueProject({ scriptLangs: ['ts', 'tsx'] })
|
||||||
|
// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup
|
||||||
|
|
||||||
|
export default defineConfigWithVueTs(
|
||||||
|
{
|
||||||
|
name: 'app/files-to-lint',
|
||||||
|
files: ['**/*.{vue,ts,mts,tsx}'],
|
||||||
|
},
|
||||||
|
|
||||||
|
globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
|
||||||
|
|
||||||
|
...pluginVue.configs['flat/essential'],
|
||||||
|
vueTsConfigs.recommended,
|
||||||
|
|
||||||
|
...pluginOxlint.buildFromOxlintConfigFile('.oxlintrc.json'),
|
||||||
|
|
||||||
|
skipFormatting,
|
||||||
|
)
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Vite App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Generated
+6879
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"name": "ulitka-app",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"build-only": "vite build",
|
||||||
|
"type-check": "vue-tsc --build",
|
||||||
|
"lint": "run-s \"lint:*\"",
|
||||||
|
"lint:oxlint": "oxlint . --fix",
|
||||||
|
"lint:eslint": "eslint . --fix --cache",
|
||||||
|
"format": "prettier --write --experimental-cli src/"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fontsource/roboto": "^5.3.0",
|
||||||
|
"@vueuse/core": "^14.4.0",
|
||||||
|
"better-auth": "^1.7.1",
|
||||||
|
"pinia": "^4.0.3",
|
||||||
|
"vue": "^3.5.41",
|
||||||
|
"vue-router": "^5.2.0",
|
||||||
|
"vuetify": "^4.1.11"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@mdi/font": "^7.4.47",
|
||||||
|
"@tsconfig/node24": "^24.0.5",
|
||||||
|
"@types/node": "^26.3.0",
|
||||||
|
"@vitejs/plugin-vue": "^6.0.8",
|
||||||
|
"@vue/eslint-config-typescript": "^14.9.0",
|
||||||
|
"@vue/tsconfig": "^0.9.1",
|
||||||
|
"eslint": "^10.9.1",
|
||||||
|
"eslint-config-prettier": "^10.1.8",
|
||||||
|
"eslint-plugin-oxlint": "~1.80.0",
|
||||||
|
"eslint-plugin-vue": "~10.10.0",
|
||||||
|
"jiti": "^2.7.0",
|
||||||
|
"npm-run-all2": "^9.0.3",
|
||||||
|
"oxlint": "~1.80.0",
|
||||||
|
"prettier": "3.9.6",
|
||||||
|
"sass-embedded": "^1.103.1",
|
||||||
|
"typescript": "~7.0.2",
|
||||||
|
"unplugin-fonts": "^2.0.0",
|
||||||
|
"vite": "^8.2.2",
|
||||||
|
"vite-plugin-vue-devtools": "^8.2.1",
|
||||||
|
"vue-eslint-parser": "^10.4.1",
|
||||||
|
"vue-tsc": "^3.3.11"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^22.18.0 || >=24.12.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
+40
@@ -0,0 +1,40 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import { useEventSource } from '@vueuse/core'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
// const { status, data, error, close } = useEventSource(
|
||||||
|
// 'https://event-source-url',
|
||||||
|
// ['notice', 'update'],
|
||||||
|
// { autoReconnect: {
|
||||||
|
// retries: 3,
|
||||||
|
// delay: 1000,
|
||||||
|
// onFailed() {
|
||||||
|
// alert('Failed to connect EventSource after 3 retries')
|
||||||
|
// },
|
||||||
|
// }, autoConnect: false },
|
||||||
|
// )
|
||||||
|
// console.log(data, status, error, close)
|
||||||
|
|
||||||
|
const layout = computed(() => {
|
||||||
|
const layout = route?.meta?.layout
|
||||||
|
return layout ? `${layout}Layout` : 'DefaultLayout'
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- <h1>You did it!</h1>-->
|
||||||
|
<!-- <router-view />-->
|
||||||
|
|
||||||
|
<component :is="layout">
|
||||||
|
<router-view v-slot="{ Component, route }">
|
||||||
|
<div :key="route.name">
|
||||||
|
<component :is="Component" />
|
||||||
|
</div>
|
||||||
|
</router-view>
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/*@import "tailwindcss";*/
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
font-family: "Montserrat", sans-serif;
|
||||||
|
font-optical-sizing: auto;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const menu = [
|
||||||
|
{ id: 1, title: 'home', to: '/' },
|
||||||
|
{ id: 2, title: 'issues', to: '/issues' },
|
||||||
|
{ id: 3, title: 'summary', to: '/summary' },
|
||||||
|
{ id: 4, title: 'reports', to: '/reports' },
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-list class="mt-1" width="200" density="compact">
|
||||||
|
<v-list-item v-for="item in menu" :key="item.id" :to="item.to">
|
||||||
|
<v-list-item-title class="d-flex align-center ga-4">
|
||||||
|
<v-icon icon="mdi-information-outline" />
|
||||||
|
<div>{{ item.title }}</div>
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const text = ref<string | null>(null)
|
||||||
|
|
||||||
|
async function onSearch() {
|
||||||
|
console.log('search')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<v-text-field
|
||||||
|
v-model="text"
|
||||||
|
clearable
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
placeholder="Search name..."
|
||||||
|
hide-details
|
||||||
|
@keydown.enter.prevent="onSearch"
|
||||||
|
@click:clear="onSearch"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { type Status, useStatusStore } from '@/stores/status.ts'
|
||||||
|
import { ref, watchEffect } from 'vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
value?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusStore = useStatusStore()
|
||||||
|
const model = defineModel<string>()
|
||||||
|
|
||||||
|
const { value } = defineProps<Props>()
|
||||||
|
|
||||||
|
const statuses = ref<Status[]>([])
|
||||||
|
|
||||||
|
watchEffect(async () => {
|
||||||
|
statuses.value = await statusStore.list()
|
||||||
|
})
|
||||||
|
|
||||||
|
const select = (value: unknown) => {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
model.value = value[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="menu">
|
||||||
|
<v-menu>
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn density="compact" v-bind="props" :text="model" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-list density="compact" select-strategy="single-leaf" @update:selected="select">
|
||||||
|
<v-list-item v-for="el in statuses" :key="el.id" :value="el.name">
|
||||||
|
<v-list-item-title>{{ el.name }}</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.menu {
|
||||||
|
width: 8rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-layout>
|
||||||
|
<v-main>
|
||||||
|
<v-container :fluid="true">
|
||||||
|
<slot></slot>
|
||||||
|
</v-container>
|
||||||
|
</v-main>
|
||||||
|
</v-layout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import AppMenu from '@/components/AppMenu.vue'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const title = 'Ulitka'
|
||||||
|
const version = '0.0.1'
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
console.log('logout')
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToLogin() {
|
||||||
|
router.push({ path: '/login' })
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToRegister() {
|
||||||
|
router.push({ path: '/register' })
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToProfile() {
|
||||||
|
router.push({ path: '/profile' })
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-layout>
|
||||||
|
<v-app-bar density="compact" elevation="0" border>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-menu>
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-app-bar-nav-icon variant="text" rounded="0" v-bind="props"></v-app-bar-nav-icon>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<app-menu />
|
||||||
|
</v-menu>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-app-bar-title>
|
||||||
|
<div class="d-flex ga-2 align-center">
|
||||||
|
<span>{{ title }}</span>
|
||||||
|
<!-- <span class="text-label-small">ver: {{ version }}</span>-->
|
||||||
|
<span>v{{ version }}</span>
|
||||||
|
</div>
|
||||||
|
</v-app-bar-title>
|
||||||
|
|
||||||
|
<v-btn @click="goToLogin">Login</v-btn>
|
||||||
|
<v-btn @click="goToRegister">Register</v-btn>
|
||||||
|
|
||||||
|
<v-divider class="mx-1" vertical />
|
||||||
|
|
||||||
|
<v-menu>
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn rounded="0" icon v-bind="props">
|
||||||
|
<v-icon icon="mdi-account-outline" />
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-list class="mt-1" width="200">
|
||||||
|
<v-list-item @click="goToProfile">Profile</v-list-item>
|
||||||
|
<v-list-item @click="logout">Logout</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
</v-app-bar>
|
||||||
|
<v-main>
|
||||||
|
<v-container :fluid="true">
|
||||||
|
<slot></slot>
|
||||||
|
</v-container>
|
||||||
|
</v-main>
|
||||||
|
</v-layout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import type { App, Component } from 'vue'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register layouts in the app.go instance
|
||||||
|
*
|
||||||
|
* @param {App<Element>} app
|
||||||
|
*/
|
||||||
|
export function registerLayouts(app: App<Element>) {
|
||||||
|
const modules = import.meta.glob<{ default: Component }>('@/layouts/*.vue', {
|
||||||
|
eager: true,
|
||||||
|
})
|
||||||
|
Object.entries(modules).forEach(([key, component]) => {
|
||||||
|
const componentName = key.split('/').at(-1)
|
||||||
|
|
||||||
|
if (componentName) {
|
||||||
|
app.component(componentName.replace('.vue', ''), component.default)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
|
||||||
|
import './assets/main.css'
|
||||||
|
import 'unfonts.css'
|
||||||
|
import 'vuetify/styles'
|
||||||
|
import '@mdi/font/css/materialdesignicons.css'
|
||||||
|
|
||||||
|
import { createVuetify } from 'vuetify'
|
||||||
|
import * as components from 'vuetify/components'
|
||||||
|
import * as directives from 'vuetify/directives'
|
||||||
|
import { registerLayouts } from '@/layouts/register.ts'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
|
||||||
|
app.use(createPinia())
|
||||||
|
app.use(router)
|
||||||
|
|
||||||
|
const vuetify = createVuetify({
|
||||||
|
components,
|
||||||
|
directives,
|
||||||
|
theme: {
|
||||||
|
defaultTheme: 'dark', // 'system' | 'light' | 'dark'
|
||||||
|
},
|
||||||
|
defaults: {
|
||||||
|
global: {},
|
||||||
|
VBtn: {
|
||||||
|
variant: 'tonal',
|
||||||
|
},
|
||||||
|
VTextField: {
|
||||||
|
variant: 'outlined',
|
||||||
|
density: 'compact',
|
||||||
|
hideDetails: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
app.use(vuetify)
|
||||||
|
|
||||||
|
registerLayouts(app)
|
||||||
|
|
||||||
|
app.mount('#app')
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import type { Status } from '@/stores/status.ts'
|
||||||
|
|
||||||
|
export const user = []
|
||||||
|
|
||||||
|
export const account = []
|
||||||
|
|
||||||
|
export const company = []
|
||||||
|
|
||||||
|
export const statusListMock: Status[] = [
|
||||||
|
{ id: '1', name: 'new' },
|
||||||
|
{ id: '2', name: 'progress' },
|
||||||
|
{ id: '3', name: 'done' },
|
||||||
|
{ id: '4', name: 'closed' },
|
||||||
|
{ id: '5', name: 'canceled' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export const issueListMock = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
key: 'AT-111',
|
||||||
|
title: 'title1',
|
||||||
|
text: 'test 1',
|
||||||
|
status: 'new',
|
||||||
|
priority: 1,
|
||||||
|
assignee: '1',
|
||||||
|
reporter: '2',
|
||||||
|
account_id: '1',
|
||||||
|
created_at: '',
|
||||||
|
tags: ['aa', 'bb'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
key: 'AT-112',
|
||||||
|
title: 'title2',
|
||||||
|
text: 'test 2',
|
||||||
|
status: 'working',
|
||||||
|
priority: 1,
|
||||||
|
assignee: '1',
|
||||||
|
reporter: '2',
|
||||||
|
account_id: '1',
|
||||||
|
created_at: '',
|
||||||
|
tags: ['aa', 'bb'],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export const type = [
|
||||||
|
{ id: '1', name: 'type one' },
|
||||||
|
{ id: '2', name: 'type two' },
|
||||||
|
{ id: '3', name: 'type three' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export const message = []
|
||||||
|
|
||||||
|
export const chat = []
|
||||||
|
|
||||||
|
export const history = []
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import LoginView from '@/views/LoginView.vue'
|
||||||
|
import ProfileView from '@/views/ProfileView.vue'
|
||||||
|
import { issuesRoutes } from '@/router/issues.ts'
|
||||||
|
import { reportsRoutes } from '@/router/reports.ts'
|
||||||
|
import { summaryRoutes } from '@/router/summary.ts'
|
||||||
|
import RegisterView from '@/views/RegisterView.vue'
|
||||||
|
import HomeView from '@/views/HomeView.vue'
|
||||||
|
|
||||||
|
declare module 'vue-router' {
|
||||||
|
interface RouteMeta {
|
||||||
|
layout?: 'Blank' | 'Default'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: HomeView,
|
||||||
|
meta: { layout: 'Default' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
component: LoginView,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/register',
|
||||||
|
component: RegisterView,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/profile',
|
||||||
|
component: ProfileView,
|
||||||
|
},
|
||||||
|
issuesRoutes,
|
||||||
|
summaryRoutes,
|
||||||
|
reportsRoutes,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import type { RouteRecordRaw } from 'vue-router'
|
||||||
|
import IssuesView from '@/views/IssuesView.vue'
|
||||||
|
|
||||||
|
export const issuesRoutes: RouteRecordRaw = {
|
||||||
|
path: '/issues',
|
||||||
|
component: IssuesView
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import type { RouteRecordRaw } from 'vue-router'
|
||||||
|
import ReportsView from '@/views/ReportsView.vue'
|
||||||
|
|
||||||
|
export const reportsRoutes: RouteRecordRaw = {
|
||||||
|
path: '/reports',
|
||||||
|
component: ReportsView,
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import type { RouteRecordRaw } from 'vue-router'
|
||||||
|
import SummaryView from '@/views/SummaryView.vue'
|
||||||
|
|
||||||
|
export const summaryRoutes: RouteRecordRaw = {
|
||||||
|
path: '/summary',
|
||||||
|
component: SummaryView,
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export const useCounterStore = defineStore('counter', () => {
|
||||||
|
const count = ref(0)
|
||||||
|
const doubleCount = computed(() => count.value * 2)
|
||||||
|
function increment() {
|
||||||
|
count.value++
|
||||||
|
}
|
||||||
|
|
||||||
|
return { count, doubleCount, increment }
|
||||||
|
})
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import { issueListMock } from '@/mocks.ts'
|
||||||
|
|
||||||
|
export interface Item {
|
||||||
|
id: string
|
||||||
|
key: string
|
||||||
|
title: string
|
||||||
|
text: string
|
||||||
|
status: string
|
||||||
|
priority: number
|
||||||
|
assignee: string
|
||||||
|
reporter: string
|
||||||
|
account_id: string
|
||||||
|
created_at: string
|
||||||
|
tags: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useIssueStore = defineStore('item', () => {
|
||||||
|
const load = ref<Item[]>(issueListMock)
|
||||||
|
|
||||||
|
async function list(params: { page: number; itemsPerPage: number; text: string | null }) {
|
||||||
|
const { page, itemsPerPage, text } = params
|
||||||
|
|
||||||
|
return new Promise<{ items: Item[]; total: number }>((resolve) => {
|
||||||
|
const start = (page - 1) * itemsPerPage
|
||||||
|
const end = start + itemsPerPage
|
||||||
|
const items = load.value.slice()
|
||||||
|
|
||||||
|
const filtered = text ? items.filter((el) => el.text.includes(text)) : items
|
||||||
|
|
||||||
|
const paginated = filtered.slice(start, end === -1 ? undefined : end)
|
||||||
|
|
||||||
|
resolve({ items: paginated, total: filtered.length })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return { list }
|
||||||
|
})
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import { statusListMock } from '@/mocks.ts'
|
||||||
|
|
||||||
|
export interface Status {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useStatusStore = defineStore('counter', () => {
|
||||||
|
const statuses = ref<Status[]>(statusListMock)
|
||||||
|
|
||||||
|
async function list(): Promise<Status[]> {
|
||||||
|
console.log('useStatusStore.list')
|
||||||
|
return Promise.resolve(statuses.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { list }
|
||||||
|
})
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>test</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, watchEffect } from 'vue'
|
||||||
|
import { type Item, useIssueStore } from '@/stores/issue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import IssueFilter from '@/components/IssueFilter.vue'
|
||||||
|
import StatusMenu from '@/components/StatusMenu.vue'
|
||||||
|
|
||||||
|
const issueStore = useIssueStore()
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const itemsPerPage = ref(10)
|
||||||
|
const page = ref(1)
|
||||||
|
const text = ref<string | null>(null)
|
||||||
|
const serverItems = ref<Item[]>([])
|
||||||
|
const loading = ref(true)
|
||||||
|
const totalItems = ref(0)
|
||||||
|
|
||||||
|
async function fetch() {
|
||||||
|
syncQuery()
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
const res = await issueStore.list({
|
||||||
|
page: page.value,
|
||||||
|
itemsPerPage: itemsPerPage.value,
|
||||||
|
text: text.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
serverItems.value = res.items
|
||||||
|
totalItems.value = res.total
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadItems(params: { page: number; itemsPerPage: number; sortBy?: unknown }) {
|
||||||
|
page.value = params.page
|
||||||
|
itemsPerPage.value = params.itemsPerPage
|
||||||
|
|
||||||
|
await fetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onSearch() {
|
||||||
|
page.value = 1
|
||||||
|
await fetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncQuery() {
|
||||||
|
const query: Record<string, string> = {
|
||||||
|
page: String(page.value),
|
||||||
|
}
|
||||||
|
if (text.value) query.text = text.value
|
||||||
|
|
||||||
|
router.replace({ query })
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.query,
|
||||||
|
(query) => {
|
||||||
|
page.value = query.page ? Number(query.page) : 1
|
||||||
|
text.value = query.text ? String(query.text) : null
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
watchEffect(async () => {
|
||||||
|
await loadItems({ page: 1, itemsPerPage: 10, sortBy: 1 })
|
||||||
|
})
|
||||||
|
|
||||||
|
const click = () => {
|
||||||
|
console.log('click')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="d-flex flex-column ga-2">
|
||||||
|
<issue-filter />
|
||||||
|
|
||||||
|
<v-list border rounded density="compact" :lines="false">
|
||||||
|
<v-list-item v-for="el in serverItems" :key="el.id" @click.stop="click">
|
||||||
|
<template v-slot:title>
|
||||||
|
<div>{{ el.key }}</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon icon="mdi-square" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:append>
|
||||||
|
<div class="d-flex ga-2">
|
||||||
|
<!-- <div>{{el.status}}</div>-->
|
||||||
|
|
||||||
|
<!-- <v-btn density="compact">btn</v-btn>-->
|
||||||
|
<status-menu v-model="el.status" />
|
||||||
|
<!-- <v-menu>-->
|
||||||
|
<!-- <template v-slot:activator="{ props }">-->
|
||||||
|
<!-- <v-btn variant="tonal" density="compact" v-bind="props"> Dropdown </v-btn>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
|
||||||
|
<!-- <v-list density="compact">-->
|
||||||
|
<!-- <v-list-item v-for="(item, index) in [{title: 'menu 1'}]" :key="index" :value="index">-->
|
||||||
|
<!-- <v-list-item-title>{{ item.title }}</v-list-item-title>-->
|
||||||
|
<!-- </v-list-item>-->
|
||||||
|
<!-- </v-list>-->
|
||||||
|
<!-- </v-menu>-->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item class="bg-black">
|
||||||
|
<v-btn density="compact" variant="text" prepend-icon="mdi-plus">create</v-btn>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="4" offset="4">
|
||||||
|
<div class="d-flex flex-column ga-2">
|
||||||
|
<div class="text-uppercase">login</div>
|
||||||
|
<v-text-field prepend-inner-icon="mdi-email" />
|
||||||
|
<v-text-field prepend-inner-icon="mdi-lock" />
|
||||||
|
<div>
|
||||||
|
<v-btn>login</v-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>profile</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="4" offset="4">
|
||||||
|
<div class="d-flex flex-column ga-2">
|
||||||
|
<div class="text-uppercase">register</div>
|
||||||
|
<v-text-field prepend-inner-icon="mdi-email" />
|
||||||
|
<v-text-field prepend-inner-icon="mdi-lock" />
|
||||||
|
<div>
|
||||||
|
<v-btn>register</v-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, watchEffect } from 'vue'
|
||||||
|
import { type Item, useIssueStore } from '@/stores/issue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const issueStore = useIssueStore()
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const itemsPerPage = ref(10)
|
||||||
|
const page = ref(1)
|
||||||
|
const text = ref<string | null>(null)
|
||||||
|
const serverItems = ref<Item[]>([])
|
||||||
|
const loading = ref(true)
|
||||||
|
const totalItems = ref(0)
|
||||||
|
|
||||||
|
async function fetch() {
|
||||||
|
syncQuery()
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
const res = await issueStore.list({
|
||||||
|
page: page.value,
|
||||||
|
itemsPerPage: itemsPerPage.value,
|
||||||
|
text: text.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
serverItems.value = res.items
|
||||||
|
totalItems.value = res.total
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadItems(params: { page: number; itemsPerPage: number; sortBy?: unknown }) {
|
||||||
|
page.value = params.page
|
||||||
|
itemsPerPage.value = params.itemsPerPage
|
||||||
|
|
||||||
|
await fetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onSearch() {
|
||||||
|
page.value = 1
|
||||||
|
await fetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncQuery() {
|
||||||
|
const query: Record<string, string> = {
|
||||||
|
page: String(page.value),
|
||||||
|
}
|
||||||
|
if (text.value) query.text = text.value
|
||||||
|
|
||||||
|
router.replace({ query })
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.query,
|
||||||
|
(query) => {
|
||||||
|
page.value = query.page ? Number(query.page) : 1
|
||||||
|
text.value = query.text ? String(query.text) : null
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="d-flex flex-column ga-2">
|
||||||
|
<v-text-field
|
||||||
|
v-model="text"
|
||||||
|
clearable
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
placeholder="Search name..."
|
||||||
|
hide-details
|
||||||
|
@keydown.enter.prevent="onSearch"
|
||||||
|
@click:clear="onSearch"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<v-data-table-server
|
||||||
|
density="compact"
|
||||||
|
hide-default-header
|
||||||
|
hover
|
||||||
|
:headers="[
|
||||||
|
{ key: 'key', title: 'key' },
|
||||||
|
{ key: 'text', title: 'text' },
|
||||||
|
{ key: 'status', title: 'status' },
|
||||||
|
{ key: 'priority', title: 'priority' },
|
||||||
|
{ key: 'assignee', title: 'assignee' },
|
||||||
|
]"
|
||||||
|
:items-per-page="itemsPerPage"
|
||||||
|
:items-per-page-options="[itemsPerPage]"
|
||||||
|
:page="page"
|
||||||
|
:items="serverItems"
|
||||||
|
:items-length="totalItems"
|
||||||
|
item-value="text"
|
||||||
|
@update:options="loadItems"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>summary</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
|
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||||
|
"exclude": ["src/**/__tests__/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
// Extra safety for array and object lookups, but may have false positives.
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
|
||||||
|
// Path mapping for cleaner imports.
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
|
||||||
|
// `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking.
|
||||||
|
// Specified here to keep it out of the root directory.
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.app.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// TSConfig for modules that run in Node.js environment via either transpilation or type-stripping.
|
||||||
|
{
|
||||||
|
"extends": "@tsconfig/node24/tsconfig.json",
|
||||||
|
"include": [
|
||||||
|
"vite.config.*",
|
||||||
|
"vitest.config.*",
|
||||||
|
"cypress.config.*",
|
||||||
|
"playwright.config.*",
|
||||||
|
"eslint.config.*"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
// Most tools use transpilation instead of Node.js's native type-stripping.
|
||||||
|
// Bundler mode provides a smoother developer experience.
|
||||||
|
"module": "preserve",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
|
||||||
|
// Include Node.js types and avoid accidentally including other `@types/*` packages.
|
||||||
|
"types": ["node"],
|
||||||
|
|
||||||
|
// Disable emitting output during `vue-tsc --build`, which is used for type-checking only.
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
// `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking.
|
||||||
|
// Specified here to keep it out of the root directory.
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||||
|
import ViteFonts from 'unplugin-fonts/vite'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
server: { port: 3333 },
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
vueDevTools(),
|
||||||
|
ViteFonts({
|
||||||
|
fontsource: {
|
||||||
|
families: [
|
||||||
|
{
|
||||||
|
name: 'Roboto',
|
||||||
|
weights: [100, 300, 400, 500, 700, 900],
|
||||||
|
styles: ['normal', 'italic'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user