diff --git a/bun.lock b/bun.lock index 99890bc..6aa3816 100644 --- a/bun.lock +++ b/bun.lock @@ -6,6 +6,7 @@ "name": "kappa-hub", "dependencies": { "@lucide/vue": "^1.16.0", + "@tabler/icons-vue": "^3.44.0", "@tailwindcss/vite": "^4.3.0", "@tanstack/vue-table": "^8.21.3", "@vueuse/core": "^14.3.0", @@ -20,6 +21,7 @@ "tailwindcss": "^4.3.0", "tw-animate-css": "^1.4.0", "vue": "^3.4.21", + "vue-i18n": "^11.4.4", "vue-router": "^4.3.0", }, "devDependencies": { @@ -175,6 +177,14 @@ "@internationalized/number": ["@internationalized/number@3.6.6", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-iFgmQaXHE0vytNfpLZWOC2mEJCBRzcUxt53Xf/yCXG93lRvqas237i3r7X4RKMwO3txiyZD4mQjKAByFv6UGSQ=="], + "@intlify/core-base": ["@intlify/core-base@11.4.4", "", { "dependencies": { "@intlify/devtools-types": "11.4.4", "@intlify/message-compiler": "11.4.4", "@intlify/shared": "11.4.4" } }, "sha512-w/vItlylrAmhebkIbVl5YY8XMCtj8Mb2g70ttxktMYuf5AuRahgEHL2iLgLIsZBIbTSgs4hkUo7ucCL0uTJvOg=="], + + "@intlify/devtools-types": ["@intlify/devtools-types@11.4.4", "", { "dependencies": { "@intlify/core-base": "11.4.4", "@intlify/shared": "11.4.4" } }, "sha512-PcBLmGmDQsTSVV911P8upzpcLJO1CNVYi/IH6bGnLR2nA+0L963+kXN1ZrisTEnbtw2ewN6HMMSldqzjronA0Q=="], + + "@intlify/message-compiler": ["@intlify/message-compiler@11.4.4", "", { "dependencies": { "@intlify/shared": "11.4.4", "source-map-js": "^1.0.2" } }, "sha512-vn0OAV9pYkJlPPmgnsSm5eAG3mL0+9C/oaded2JY9jmxBbhmUXT3TcAUY8WRgLY9Hte7lkUJKpXrVlYjMXBD2w=="], + + "@intlify/shared": ["@intlify/shared@11.4.4", "", {}, "sha512-QRUCHqda1U6aR14FR0vvXD4+4gj6+fm0AhAozvSuRCw0fCvrmCugWpgiR4xH2NI6s8am6N9p5OhirplsX8ZS3g=="], + "@isaacs/cliui": ["@isaacs/cliui@9.0.0", "", {}, "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg=="], "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], @@ -255,6 +265,10 @@ "@swc/helpers": ["@swc/helpers@0.5.21", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg=="], + "@tabler/icons": ["@tabler/icons@3.44.0", "", {}, "sha512-Wn0AOZG9sg0L+bjfMqq4eNhC6pQjIrk94LvvWYNYkY8KH8wC3YILRzQlrnVJc4FUeMxH/AK97QsYCX35H3LndA=="], + + "@tabler/icons-vue": ["@tabler/icons-vue@3.44.0", "", { "dependencies": { "@tabler/icons": "3.44.0" }, "peerDependencies": { "vue": ">=3.0.1" } }, "sha512-mABxdhq3SWo2ZI77w/t0reiOGNim/SEDSlfMT5PeiWA3cZwnZoQUYRiq/X6SgeTaA7LzCTX0IuvQWVf4RWOvsg=="], + "@tailwindcss/node": ["@tailwindcss/node@4.3.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.21.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.0" } }, "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g=="], "@tailwindcss/oxide": ["@tailwindcss/oxide@4.3.0", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.0", "@tailwindcss/oxide-darwin-arm64": "4.3.0", "@tailwindcss/oxide-darwin-x64": "4.3.0", "@tailwindcss/oxide-freebsd-x64": "4.3.0", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", "@tailwindcss/oxide-linux-x64-musl": "4.3.0", "@tailwindcss/oxide-wasm32-wasi": "4.3.0", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" } }, "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg=="], @@ -1041,6 +1055,8 @@ "vue-eslint-parser": ["vue-eslint-parser@10.4.0", "", { "dependencies": { "debug": "^4.4.0", "eslint-scope": "^8.2.0 || ^9.0.0", "eslint-visitor-keys": "^4.2.0 || ^5.0.0", "espree": "^10.3.0 || ^11.0.0", "esquery": "^1.6.0", "semver": "^7.6.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0" } }, "sha512-Vxi9pJdbN3ZnVGLODVtZ7y4Y2kzAAE2Cm0CZ3ZDRvydVYxZ6VrnBhLikBsRS+dpwj4Jv4UCv21PTEwF5rQ9WXg=="], + "vue-i18n": ["vue-i18n@11.4.4", "", { "dependencies": { "@intlify/core-base": "11.4.4", "@intlify/devtools-types": "11.4.4", "@intlify/shared": "11.4.4", "@vue/devtools-api": "^6.5.0" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-gIbXVSFQV4jcSJxfwdZ5zSZmZ+12CnX0K3vBkRSd6Zn+HSzCp+QwUgPwpD/uN0oKNKI9RzlUXPKVedEuMgNG0A=="], + "vue-metamorph": ["vue-metamorph@3.3.4", "", { "dependencies": { "@babel/parser": "8.0.0-alpha.12", "ast-types-x": "1.18.0", "chalk": "^5.3.0", "cli-progress": "^3.12.0", "commander": "^14.0.0", "deep-diff": "^1.0.2", "fs-extra": "^11.2.0", "glob": "^11.0.0", "lodash-es": "^4.17.21", "magic-string": "^0.30.10", "micromatch": "^4.0.8", "node-html-parser": "^7.0.1", "postcss": "^8.4.38", "postcss-less": "^6.0.0", "postcss-sass": "^0.5.0", "postcss-scss": "^4.0.9", "postcss-styl": "^0.12.3", "recast-x": "1.0.5", "table": "^6.8.2", "vue-eslint-parser": "^10.1.0" }, "bin": { "vue-metamorph": "scripts/scaffold.js" } }, "sha512-WZ1xzHrmYh9UiZ7OC9eG1ASzgSybEB10jhop+k5KzMY9I1JmRKdreqUYzbV3hOnOMvLhyDn7y6f62mLE2jHFSg=="], "vue-router": ["vue-router@4.6.4", "", { "dependencies": { "@vue/devtools-api": "^6.6.4" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg=="], diff --git a/package.json b/package.json index a3ca2cb..7c428a3 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@lucide/vue": "^1.16.0", + "@tabler/icons-vue": "^3.44.0", "@tailwindcss/vite": "^4.3.0", "@tanstack/vue-table": "^8.21.3", "@vueuse/core": "^14.3.0", @@ -25,6 +26,7 @@ "tailwindcss": "^4.3.0", "tw-animate-css": "^1.4.0", "vue": "^3.4.21", + "vue-i18n": "^11.4.4", "vue-router": "^4.3.0" }, "devDependencies": { diff --git a/src/App.vue b/src/App.vue index 64aee0b..99f1c0f 100644 --- a/src/App.vue +++ b/src/App.vue @@ -2,27 +2,12 @@ import { ref, onMounted } from 'vue' import { useAuthStore } from '@/stores/auth' import { useProjectsStore } from '@/stores/projects' -import { SidebarProvider, SidebarInset, SidebarTrigger } from '@/components/ui/sidebar' -import { Separator } from '@/components/ui/separator' -import { Button } from '@/components/ui/button' -import AppSidebar from '@/components/AppSidebar.vue' import LoginView from '@/views/LoginView.vue' -import DashboardView from '@/views/DashboardView.vue' -import CalendarView from '@/views/CalendarView.vue' -import SchedulerView from '@/views/SchedulerView.vue' -import { isDark, toggleTheme } from '@/composables/useTheme' +import NewDashboardView from '@/views/NewDashboardView.vue' const auth = useAuthStore() const projectsStore = useProjectsStore() -const activeTab = ref('dashboard') - -const tabTitles: Record = { - dashboard: 'Diagnóstico', - calendar: 'Calendario', - scheduler: 'Recetas', -} - onMounted(() => { if (auth.isAuthenticated) { projectsStore.fetchProjects() @@ -32,49 +17,5 @@ onMounted(() => { + + \ No newline at end of file diff --git a/src/components/NavProjects.vue b/src/components/NavProjects.vue index 4693e62..1ffbdc7 100644 --- a/src/components/NavProjects.vue +++ b/src/components/NavProjects.vue @@ -1,5 +1,4 @@ + + \ No newline at end of file diff --git a/src/components/dashboard/ChartAreaInteractive.vue b/src/components/dashboard/ChartAreaInteractive.vue new file mode 100644 index 0000000..fb49094 --- /dev/null +++ b/src/components/dashboard/ChartAreaInteractive.vue @@ -0,0 +1,275 @@ + + + diff --git a/src/components/dashboard/DataTable.vue b/src/components/dashboard/DataTable.vue new file mode 100644 index 0000000..ce3234e --- /dev/null +++ b/src/components/dashboard/DataTable.vue @@ -0,0 +1,477 @@ + + + + + diff --git a/src/components/dashboard/DragHandle.vue b/src/components/dashboard/DragHandle.vue new file mode 100644 index 0000000..42d83a0 --- /dev/null +++ b/src/components/dashboard/DragHandle.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/components/dashboard/DraggableRow.vue b/src/components/dashboard/DraggableRow.vue new file mode 100644 index 0000000..cfcc409 --- /dev/null +++ b/src/components/dashboard/DraggableRow.vue @@ -0,0 +1,31 @@ + + + diff --git a/src/components/dashboard/NavDocuments.vue b/src/components/dashboard/NavDocuments.vue new file mode 100644 index 0000000..6ed9613 --- /dev/null +++ b/src/components/dashboard/NavDocuments.vue @@ -0,0 +1,45 @@ + + + \ No newline at end of file diff --git a/src/components/dashboard/NavMain.vue b/src/components/dashboard/NavMain.vue new file mode 100644 index 0000000..f3f44b8 --- /dev/null +++ b/src/components/dashboard/NavMain.vue @@ -0,0 +1,60 @@ + + + \ No newline at end of file diff --git a/src/components/dashboard/NavSecondary.vue b/src/components/dashboard/NavSecondary.vue new file mode 100644 index 0000000..0d821ee --- /dev/null +++ b/src/components/dashboard/NavSecondary.vue @@ -0,0 +1,41 @@ + + + \ No newline at end of file diff --git a/src/components/dashboard/NavUser.vue b/src/components/dashboard/NavUser.vue new file mode 100644 index 0000000..bfb6828 --- /dev/null +++ b/src/components/dashboard/NavUser.vue @@ -0,0 +1,114 @@ + + + diff --git a/src/components/dashboard/SectionCards.vue b/src/components/dashboard/SectionCards.vue new file mode 100644 index 0000000..d19d895 --- /dev/null +++ b/src/components/dashboard/SectionCards.vue @@ -0,0 +1,106 @@ + + + diff --git a/src/components/dashboard/SiteHeader.vue b/src/components/dashboard/SiteHeader.vue new file mode 100644 index 0000000..eb3ed29 --- /dev/null +++ b/src/components/dashboard/SiteHeader.vue @@ -0,0 +1,41 @@ + + + \ No newline at end of file diff --git a/src/i18n/index.ts b/src/i18n/index.ts new file mode 100644 index 0000000..1703ddc --- /dev/null +++ b/src/i18n/index.ts @@ -0,0 +1,20 @@ +import { createI18n } from 'vue-i18n' +import en from './locales/en.json' +import es from './locales/es.json' + +const messages = { en, es } + +function getBrowserLocale(): string { + const nav = navigator as Navigator & { userLanguage?: string } + const locale = nav.language || nav.userLanguage || 'en' + return locale.split('-')[0] +} + +export const i18n = createI18n({ + legacy: false, + locale: getBrowserLocale(), + fallbackLocale: 'en', + messages, +}) + +export default i18n \ No newline at end of file diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json new file mode 100644 index 0000000..f3b4b1d --- /dev/null +++ b/src/i18n/locales/en.json @@ -0,0 +1,18 @@ +{ + "nav": { + "quickCreate": "Create project", + "dashboard": "Dashboard", + "projects": "Projects", + "lifecycle": "Lifecycle", + "analytics": "Analytics", + "team": "Team", + "documents": "Documents", + "dataLibrary": "Data Library", + "reports": "Reports", + "wordAssistant": "Word Assistant", + "templates": "Templates" + }, + "siteHeader": { + "title": "Dashboard" + } +} \ No newline at end of file diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json new file mode 100644 index 0000000..ec707e8 --- /dev/null +++ b/src/i18n/locales/es.json @@ -0,0 +1,18 @@ +{ + "nav": { + "quickCreate": "Crear proyecto", + "dashboard": "Dashboard", + "projects": "Projects", + "lifecycle": "Lifecycle", + "analytics": "Analytics", + "team": "Team", + "documents": "Documents", + "dataLibrary": "Data Library", + "reports": "Reports", + "wordAssistant": "Word Assistant", + "templates": "Templates" + }, + "siteHeader": { + "title": "Dashboard" + } +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 283ec2a..9dcf690 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,10 @@ import { createApp } from 'vue' import { createPinia } from 'pinia' import App from './App.vue' +import { i18n } from './i18n' import './style.css' const app = createApp(App) app.use(createPinia()) +app.use(i18n) app.mount('#app') diff --git a/src/services/kappa-api.ts b/src/services/kappa-api.ts index 9dc3cbd..ab6dc85 100644 --- a/src/services/kappa-api.ts +++ b/src/services/kappa-api.ts @@ -92,6 +92,31 @@ class KappaAPI { return this.request('GET', '/users/all/') } + async getUserStories(initiativeId?: number): Promise { + const path = initiativeId ? `/userstorys/?initiative=${initiativeId}` : '/userstorys/' + return this.request('GET', path) + } + + async getLogbooks(initiativeId?: number): Promise { + const path = initiativeId ? `/logbooks/?initiative=${initiativeId}` : '/logbooks/' + return this.request('GET', path) + } + + async getPlannings(initiativeId?: number): Promise { + const path = initiativeId ? `/plannings/?initiative=${initiativeId}` : '/plannings/' + return this.request('GET', path) + } + + async getBusinessRules(initiativeId?: number): Promise { + const path = initiativeId ? `/business-rules/?initiative=${initiativeId}` : '/business-rules/' + return this.request('GET', path) + } + + async getRequirements(initiativeId?: number): Promise { + const path = initiativeId ? `/functionalrequirements/?initiative=${initiativeId}` : '/functionalrequirements/' + return this.request('GET', path) + } + // ─── Bitácoras (Logbooks) ──────────────────────────── async createLogbookMaster(data: KappaLogbookMaster): Promise { diff --git a/src/stores/projects.ts b/src/stores/projects.ts index 29f8664..45309dc 100644 --- a/src/stores/projects.ts +++ b/src/stores/projects.ts @@ -19,7 +19,9 @@ export const useProjectsStore = defineStore('projects', () => { loading.value = true error.value = null try { - projects.value = await kappa.getInitiatives() + const data = await kappa.getInitiatives() + console.log('[KAPPA] initiatives:', data) + projects.value = Array.isArray(data) ? data : (data.results ?? []) } catch (e: any) { error.value = e.message } finally { diff --git a/src/stores/workitems.ts b/src/stores/workitems.ts index fe06c2a..10fdc07 100644 --- a/src/stores/workitems.ts +++ b/src/stores/workitems.ts @@ -1,17 +1,31 @@ import { defineStore } from 'pinia' -import { ref } from 'vue' +import { ref, computed } from 'vue' import { kappa } from '@/services/kappa-api' -import type { KappaUserStory } from '@/types/kappa' +import type { KappaUserStory, KappaLogbookEntry, KappaPlanningEntry } from '@/types/kappa' export const useWorkItemsStore = defineStore('workitems', () => { const creating = ref(false) + const loading = ref(false) const error = ref(null) + const userStories = ref([]) + const logbooks = ref([]) + const plannings = ref([]) + + const totalHUs = computed(() => userStories.value.length) + const inProgressHUs = computed(() => + userStories.value.filter(us => + us.status && ['in_progress', 'doing', 'wip', 'active'].includes(us.status.toLowerCase()) + ).length + ) + const totalSessions = computed(() => logbooks.value.length) + async function createUserStory(story: KappaUserStory): Promise { creating.value = true error.value = null try { const result = await kappa.createUserStory(story) + userStories.value.push(result) return result } catch (e: any) { error.value = e.message @@ -21,5 +35,36 @@ export const useWorkItemsStore = defineStore('workitems', () => { } } - return { creating, error, createUserStory } -}) + async function fetchWorkItems(initiativeId?: number) { + loading.value = true + error.value = null + try { + const [stories, logs, plans] = await Promise.all([ + kappa.getUserStories(initiativeId), + kappa.getLogbooks(initiativeId), + kappa.getPlannings(initiativeId), + ]) + userStories.value = Array.isArray(stories) ? stories : (stories.results ?? []) + logbooks.value = Array.isArray(logs) ? logs : (logs.results ?? []) + plannings.value = Array.isArray(plans) ? plans : (plans.results ?? []) + } catch (e: any) { + error.value = e.message + } finally { + loading.value = false + } + } + + return { + creating, + loading, + error, + userStories, + logbooks, + plannings, + totalHUs, + inProgressHUs, + totalSessions, + createUserStory, + fetchWorkItems, + } +}) \ No newline at end of file diff --git a/src/types/kappa.ts b/src/types/kappa.ts index 53ed063..e9a5c1a 100644 --- a/src/types/kappa.ts +++ b/src/types/kappa.ts @@ -26,7 +26,8 @@ export interface KappaUser { export interface KappaInitiative { id: number - name: string + name?: string + initiative_name?: string key?: string description?: string start_date?: string diff --git a/src/views/DashboardView.vue b/src/views/DashboardView.vue index 91bc5b8..c21b895 100644 --- a/src/views/DashboardView.vue +++ b/src/views/DashboardView.vue @@ -1,7 +1,8 @@