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