dedup sesiones + calendario desde BD + scroll por fecha
- analyzeAsSession: detecta duplicados por UTC timestamp antes de guardar - TranscriptionsView: sessionDatesList reemplaza sessionOffsets - session dates se leen directo de tabla sessions (no desde markdown) - scrollToSession busca por fecha en markdown con regex escapada - calendario usa BD como fuente de verdad
This commit is contained in:
@@ -14,7 +14,7 @@ import {
|
||||
import { analyzeSession, type SessionExtraction } from '@/services/session-analyzer'
|
||||
import { generateMasterDoc } from '@/services/project-doc'
|
||||
import { parseFile } from '@/services/parse-transcription'
|
||||
import { saveSession, saveSessionSummary, saveProjectState, getSessionCount } from '@/services/transcriptions-db'
|
||||
import { saveSession, saveSessionSummary, saveProjectState, getSessionCount, getSessionsByProject } from '@/services/transcriptions-db'
|
||||
import { parseTeamsUTC, toColombiaTime } from '@/services/timezone'
|
||||
import {
|
||||
Card,
|
||||
@@ -77,7 +77,7 @@ const docGenerated = ref(false)
|
||||
const docMarkdown = ref('')
|
||||
const docSessionCount = ref(0)
|
||||
const docUpdatedAt = ref('')
|
||||
const sessionOffsets = ref<{ date: string; title: string; offset: number }[]>([])
|
||||
const sessionDatesList = ref<{ date: string; title: string; fileName: string }[]>([])
|
||||
const viewerRef = ref<HTMLTextAreaElement | null>(null)
|
||||
|
||||
// ─── Calendar state ──────────────────────────────────────
|
||||
@@ -96,45 +96,43 @@ const selectedProject = computed(() =>
|
||||
watch(selectedProjectId, async (id) => {
|
||||
if (!id) return
|
||||
clearAll()
|
||||
const count = await getSessionCount(id)
|
||||
await loadSessionDates(id)
|
||||
const count = sessionDatesList.value.length
|
||||
docSessionCount.value = count
|
||||
if (count > 0) {
|
||||
const md = await generateMasterDoc(id, selectedProject.value?.name || '')
|
||||
docMarkdown.value = md
|
||||
parseSessionOffsets(md)
|
||||
}
|
||||
}, { immediate: false })
|
||||
|
||||
function parseSessionOffsets(md: string) {
|
||||
const offsets: { date: string; title: string; offset: number }[] = []
|
||||
const re = /## 📍 Sesión \d+:\s*(.+?)\n\*\*Archivo fuente:/g
|
||||
let match
|
||||
while ((match = re.exec(md)) !== null) {
|
||||
const title = match[1].trim()
|
||||
const dateMatch = title.match(/(\d{4}-\d{2}-\d{2})/)
|
||||
offsets.push({
|
||||
date: dateMatch?.[1] || '',
|
||||
title,
|
||||
offset: match.index,
|
||||
})
|
||||
}
|
||||
sessionOffsets.value = offsets
|
||||
async function loadSessionDates(projectId: number) {
|
||||
const sessions = await getSessionsByProject(projectId)
|
||||
sessionDatesList.value = sessions.map(s => ({
|
||||
date: s.date,
|
||||
title: s.title,
|
||||
fileName: s.fileName,
|
||||
}))
|
||||
}
|
||||
|
||||
function scrollToSession(offset: number) {
|
||||
if (viewerRef.value) {
|
||||
const textarea = viewerRef.value
|
||||
const md = docMarkdown.value
|
||||
// Calculate line number from offset
|
||||
const lineNum = md.slice(0, offset).split('\n').length
|
||||
const lineHeight = 20 // approximate
|
||||
textarea.scrollTop = (lineNum - 5) * lineHeight
|
||||
textarea.focus()
|
||||
}
|
||||
function scrollToSession(date: string) {
|
||||
if (!viewerRef.value || !docMarkdown.value) return
|
||||
const md = docMarkdown.value
|
||||
// Buscar la sesión por fecha en el markdown: ## 📍 Sesión: YYYY-MM-DD
|
||||
const re = new RegExp(`## 📍 Sesión: ${escapeRegex(date)}`)
|
||||
const match = re.exec(md)
|
||||
if (!match) return
|
||||
const lineNum = md.slice(0, match.index).split('\n').length
|
||||
const lineHeight = 20
|
||||
viewerRef.value.scrollTop = (lineNum - 5) * lineHeight
|
||||
viewerRef.value.focus()
|
||||
}
|
||||
|
||||
function escapeRegex(s: string) {
|
||||
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
}
|
||||
|
||||
function sessionDates(): string[] {
|
||||
return sessionOffsets.value.map(s => s.date).filter(Boolean)
|
||||
return sessionDatesList.value.map(s => s.date).filter(Boolean)
|
||||
}
|
||||
|
||||
// ─── Calendar helpers ────────────────────────────────────
|
||||
@@ -149,14 +147,13 @@ function firstDayOfMonth(m: number, y: number) {
|
||||
}
|
||||
|
||||
function isSessionDate(d: number) {
|
||||
const ds = sessionDates()
|
||||
const dateStr = `${calYear.value}-${String(calMonth.value + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}`
|
||||
return ds.includes(dateStr)
|
||||
return sessionDatesList.value.some(s => s.date === dateStr)
|
||||
}
|
||||
|
||||
function getSessionForDate(d: number) {
|
||||
const dateStr = `${calYear.value}-${String(calMonth.value + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}`
|
||||
return sessionOffsets.value.find(s => s.date === dateStr)
|
||||
return sessionDatesList.value.find(s => s.date === dateStr) || null
|
||||
}
|
||||
|
||||
function prevMonth() {
|
||||
@@ -283,7 +280,20 @@ async function analyzeAsSession() {
|
||||
sessionTeamsInfo.value = teamsDate ? { utc: teamsDate.utc, timeStr: teamsDate.timeStr } : null
|
||||
const sessionDate = teamsDate?.dateStr || result.sessionDate || now.slice(0, 10)
|
||||
|
||||
// 1b. Guardar transcripción en BD
|
||||
// 1b. Deduplicación: verificar si ya existe una sesión con el mismo UTC timestamp
|
||||
const existing = await getSessionsByProject(selectedProjectId.value)
|
||||
const isDuplicate = existing.some(s => {
|
||||
// Comparar por UTC timestamp en filename o por fecha + título similar
|
||||
const existingUTC = parseTeamsUTC(s.fileName)
|
||||
return teamsDate !== null && existingUTC !== null && teamsDate.utc === existingUTC.utc
|
||||
})
|
||||
if (isDuplicate) {
|
||||
sessionError.value = `Esta sesión ya fue registrada (UTC ${teamsDate?.utc}). Se descarta.`
|
||||
sessionLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// 1c. Guardar transcripción en BD
|
||||
const sessionId = await saveSession({
|
||||
projectId: selectedProjectId.value,
|
||||
date: sessionDate,
|
||||
@@ -313,9 +323,9 @@ async function analyzeAsSession() {
|
||||
// 4. Regenerar documento MD
|
||||
const md = await generateMasterDoc(selectedProjectId.value, selectedProject.value?.name || '')
|
||||
docMarkdown.value = md
|
||||
docSessionCount.value = await getSessionCount(selectedProjectId.value)
|
||||
await loadSessionDates(selectedProjectId.value)
|
||||
docSessionCount.value = sessionDatesList.value.length
|
||||
docUpdatedAt.value = now.slice(0, 16).replace('T', ' ')
|
||||
parseSessionOffsets(md)
|
||||
docGenerated.value = true
|
||||
|
||||
// 5. Mostrar resultado en UI
|
||||
@@ -391,9 +401,9 @@ async function generateDoc() {
|
||||
try {
|
||||
const md = await generateMasterDoc(selectedProjectId.value, selectedProject.value?.name || '')
|
||||
docMarkdown.value = md
|
||||
docSessionCount.value = await getSessionCount(selectedProjectId.value)
|
||||
await loadSessionDates(selectedProjectId.value)
|
||||
docSessionCount.value = sessionDatesList.value.length
|
||||
docUpdatedAt.value = new Date().toISOString().slice(0, 16).replace('T', ' ')
|
||||
parseSessionOffsets(md)
|
||||
docGenerated.value = true
|
||||
} catch (e: any) {
|
||||
sessionError.value = e.message
|
||||
@@ -667,7 +677,7 @@ function clearAll() {
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent class="pt-0">
|
||||
<div v-if="sessionOffsets.length === 0" class="text-xs text-muted-foreground text-center py-2">
|
||||
<div v-if="sessionDatesList.length === 0" class="text-xs text-muted-foreground text-center py-2">
|
||||
{{ t('transcriptions.noSessions') }}
|
||||
</div>
|
||||
<template v-else>
|
||||
@@ -681,7 +691,7 @@ function clearAll() {
|
||||
:key="d"
|
||||
class="py-0.5 rounded hover:bg-muted transition-colors relative"
|
||||
:class="isSessionDate(d) ? 'bg-primary/10 text-primary font-bold' : 'text-muted-foreground'"
|
||||
@click="() => { const s = getSessionForDate(d); if (s) scrollToSession(s.offset) }"
|
||||
@click="() => { const s = getSessionForDate(d); if (s) scrollToSession(s.date) }"
|
||||
:title="getSessionForDate(d)?.title || ''"
|
||||
>
|
||||
{{ d }}
|
||||
|
||||
Reference in New Issue
Block a user