Alpha v0.1.0 — KAPPA Hub inicial
- Auth con KAPPA (login + token Bearer) - Cliente HTTP para 10 endpoints (proyectos, HUs, bitácoras, planeaciones) - Dashboard multi-proyecto con concepto médico Teloprax - Calendario colombiano con 19 feriados (Ley Emiliani + Pascua) - Scheduler tipo cron con Dexie (reglas recurrentes, toasts, log) - Diseño marca Teloprax: Inter, Space Grotesk, #1A1A2E, rojo #E63946 - Stack: Vue 3 + TypeScript + Pinia + Vite + Bun
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { kappa } from '@/services/kappa-api'
|
||||
import type { KappaLoginPayload } from '@/types/kappa'
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const token = ref<string | null>(localStorage.getItem('kappa_token'))
|
||||
const user = ref<{ name: string; email: string } | null>(null)
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
const isAuthenticated = computed(() => !!token.value)
|
||||
|
||||
async function login(payload: KappaLoginPayload) {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const data = await kappa.login(payload)
|
||||
token.value = kappa['token'] // sync with the service instance
|
||||
user.value = {
|
||||
name: `${data.user?.first_name || ''} ${data.user?.last_name || ''}`.trim(),
|
||||
email: payload.email,
|
||||
}
|
||||
return true
|
||||
} catch (e: any) {
|
||||
error.value = e.message
|
||||
return false
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function logout() {
|
||||
kappa.logout()
|
||||
token.value = null
|
||||
user.value = null
|
||||
}
|
||||
|
||||
return { token, user, loading, error, isAuthenticated, login, logout }
|
||||
})
|
||||
@@ -0,0 +1,36 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { kappa } from '@/services/kappa-api'
|
||||
import type { KappaInitiative } from '@/types/kappa'
|
||||
|
||||
export const useProjectsStore = defineStore('projects', () => {
|
||||
const projects = ref<KappaInitiative[]>([])
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
const selectedId = ref<number | null>(null)
|
||||
|
||||
const selected = computed(() =>
|
||||
projects.value.find(p => p.id === selectedId.value) ?? null
|
||||
)
|
||||
|
||||
const count = computed(() => projects.value.length)
|
||||
|
||||
async function fetchProjects() {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
projects.value = await kappa.getInitiatives()
|
||||
} catch (e: any) {
|
||||
error.value = e.message
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function select(id: number) {
|
||||
selectedId.value = id
|
||||
localStorage.setItem('kappa_last_project', String(id))
|
||||
}
|
||||
|
||||
return { projects, loading, error, selectedId, selected, count, fetchProjects, select }
|
||||
})
|
||||
@@ -0,0 +1,101 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import {
|
||||
db, startScheduler, stopScheduler, onScheduledTask,
|
||||
computeNextRun, DEFAULT_RULES,
|
||||
type ScheduleRule, type ScheduleAction, type ExecutionLog,
|
||||
} from '@/services/scheduler'
|
||||
|
||||
export const useSchedulerStore = defineStore('scheduler', () => {
|
||||
const rules = ref<ScheduleRule[]>([])
|
||||
const logs = ref<ExecutionLog[]>([])
|
||||
const notifications = ref<{ id: number; ruleId: number; message: string; time: string }[]>([])
|
||||
const running = ref(false)
|
||||
|
||||
const enabledRules = computed(() => rules.value.filter(r => r.enabled))
|
||||
const nextRuns = computed(() => {
|
||||
return rules.value.map(r => ({
|
||||
...r,
|
||||
nextRun: computeNextRun(r),
|
||||
}))
|
||||
})
|
||||
|
||||
async function loadRules() {
|
||||
rules.value = await db.rules.toArray()
|
||||
}
|
||||
|
||||
async function loadLogs(limit = 50) {
|
||||
logs.value = await db.logs.orderBy('executedAt').reverse().limit(limit).toArray()
|
||||
}
|
||||
|
||||
async function seedDefaults() {
|
||||
const existing = await db.rules.count()
|
||||
if (existing > 0) return
|
||||
const now = new Date().toISOString()
|
||||
for (const def of DEFAULT_RULES) {
|
||||
await db.rules.add({ ...def, createdAt: now, lastRun: null } as ScheduleRule)
|
||||
}
|
||||
await loadRules()
|
||||
}
|
||||
|
||||
async function addRule(rule: Omit<ScheduleRule, 'id' | 'createdAt' | 'lastRun'>) {
|
||||
await db.rules.add({
|
||||
...rule,
|
||||
createdAt: new Date().toISOString(),
|
||||
lastRun: null,
|
||||
} as ScheduleRule)
|
||||
await loadRules()
|
||||
}
|
||||
|
||||
async function updateRule(id: number, changes: Partial<ScheduleRule>) {
|
||||
await db.rules.update(id, changes)
|
||||
await loadRules()
|
||||
}
|
||||
|
||||
async function toggleRule(id: number) {
|
||||
const rule = rules.value.find(r => r.id === id)
|
||||
if (rule) {
|
||||
await db.rules.update(id, { enabled: !rule.enabled })
|
||||
await loadRules()
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteRule(id: number) {
|
||||
await db.rules.delete(id)
|
||||
await loadRules()
|
||||
}
|
||||
|
||||
function start() {
|
||||
startScheduler()
|
||||
running.value = true
|
||||
|
||||
onScheduledTask((ruleId, message) => {
|
||||
notifications.value.unshift({
|
||||
id: Date.now(),
|
||||
ruleId,
|
||||
message,
|
||||
time: new Date().toLocaleTimeString(),
|
||||
})
|
||||
loadLogs()
|
||||
// Mantener solo últimas 20 notificaciones
|
||||
if (notifications.value.length > 20) {
|
||||
notifications.value = notifications.value.slice(0, 20)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function stop() {
|
||||
stopScheduler()
|
||||
running.value = false
|
||||
}
|
||||
|
||||
function dismissNotification(id: number) {
|
||||
notifications.value = notifications.value.filter(n => n.id !== id)
|
||||
}
|
||||
|
||||
return {
|
||||
rules, logs, notifications, running, enabledRules, nextRuns,
|
||||
loadRules, loadLogs, seedDefaults, addRule, updateRule,
|
||||
toggleRule, deleteRule, start, stop, dismissNotification,
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,25 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { kappa } from '@/services/kappa-api'
|
||||
import type { KappaUserStory } from '@/types/kappa'
|
||||
|
||||
export const useWorkItemsStore = defineStore('workitems', () => {
|
||||
const creating = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
async function createUserStory(story: KappaUserStory): Promise<KappaUserStory | null> {
|
||||
creating.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const result = await kappa.createUserStory(story)
|
||||
return result
|
||||
} catch (e: any) {
|
||||
error.value = e.message
|
||||
return null
|
||||
} finally {
|
||||
creating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return { creating, error, createUserStory }
|
||||
})
|
||||
Reference in New Issue
Block a user