import { computed, ref } from 'vue'
import { useToast } from 'gnexus-ui-kit/vue'
import * as api from '@/api/index.js'
import { useConfirm } from '@/composables/useConfirm.js'
import { locale, t } from '@/i18n/index.js'
import { useAuthStore } from '@/stores/auth.js'
import { TEMPLATES } from '../constants.js'
import { errorText } from '../errors.js'
/**
* The detail column: the selected entry, its reading view with history, and
* the source editor that creates, saves and deletes documents.
*
* `loadError` is the list's banner ref — a failed detail fetch shows there,
* above both panes, exactly as it did when this state lived in one setup.
*/
export function useCatalogEntry({ getKind, reload, loadError }) {
const authStore = useAuthStore()
const confirm = useConfirm()
const toast = useToast()
const entry = ref(null)
const selectedId = ref(null)
const detailLoading = ref(false)
const editing = ref(false)
const draft = ref('')
const visibility = ref('personal')
const saving = ref(false)
const saveError = ref('')
const versions = ref([])
const showHistory = ref(false)
// A selected entry or a new one takes the detail column; on a phone that also
// replaces the list, so the reader gets the whole width.
const detailOpen = computed(
() => editing.value || entry.value !== null || detailLoading.value
)
const canEdit = computed(() => (editing.value && !entry.value ? true : Boolean(entry.value?.can_edit)))
const visibilityOptions = computed(() => [
{ value: 'personal', label: t('settings.skillsPersonal') },
{ value: 'shared', label: t('settings.skillsShared') },
])
async function select(id) {
if (saving.value) return
selectedId.value = id
editing.value = false
showHistory.value = false
saveError.value = ''
entry.value = null
versions.value = []
// `detailLoading` keeps the pane open on a phone while the entry travels.
detailLoading.value = true
try {
const data = await api.getSkill(id)
// The reader may have gone back or picked another row meanwhile.
if (selectedId.value !== id) return
entry.value = data
draft.value = data.content
visibility.value = data.visibility
} catch (err) {
if (selectedId.value === id) loadError.value = errorText(err)
} finally {
if (selectedId.value === id) detailLoading.value = false
}
}
function startCreate() {
entry.value = null
selectedId.value = null
detailLoading.value = false
editing.value = true
draft.value = TEMPLATES[getKind()]
visibility.value = 'personal'
versions.value = []
showHistory.value = false
saveError.value = ''
}
function beginEdit() {
if (!entry.value) return
draft.value = entry.value.content
visibility.value = entry.value.visibility
saveError.value = ''
editing.value = true
}
function cancel() {
if (entry.value) {
editorBackToRead()
} else {
reset()
}
}
function editorBackToRead() {
draft.value = entry.value?.content || ''
visibility.value = entry.value?.visibility || 'personal'
saveError.value = ''
editing.value = false
}
function reset() {
entry.value = null
selectedId.value = null
detailLoading.value = false
editing.value = false
draft.value = ''
saveError.value = ''
versions.value = []
showHistory.value = false
}
async function save() {
saving.value = true
saveError.value = ''
try {
if (entry.value) {
const payload = { content: draft.value }
if (authStore.isAdmin && visibility.value !== entry.value.visibility) {
payload.visibility = visibility.value
}
const updated = await api.updateSkill(entry.value.id, payload)
entry.value = updated
draft.value = updated.content
visibility.value = updated.visibility
toast.success({ title: t('settings.skillsSaved') })
} else {
const created = await api.createSkill({
content: draft.value,
visibility: authStore.isAdmin ? visibility.value : 'personal',
})
entry.value = created
selectedId.value = created.id
draft.value = created.content
visibility.value = created.visibility
toast.success({ title: t('settings.skillsCreated') })
}
editing.value = false
await reload()
} catch (err) {
// The server names what is missing in a protocol; that message is the
// whole point of validating on save, so show it verbatim.
saveError.value = errorText(err)
} finally {
saving.value = false
}
}
async function remove() {
if (!entry.value) return
const ok = await confirm(t('settings.skillsDeleteConfirm', { name: entry.value.name }))
if (!ok) return
try {
await api.deleteSkill(entry.value.id)
toast.success({ title: t('settings.skillsDeleted') })
reset()
await reload()
} catch (err) {
toast.error({ title: t('common.error'), text: errorText(err) })
}
}
async function toggleHistory() {
showHistory.value = !showHistory.value
if (!showHistory.value || versions.value.length || !entry.value) return
try {
const data = await api.getSkillVersions(entry.value.id)
versions.value = data.items || []
} catch (err) {
saveError.value = errorText(err)
}
}
function formatWhen(iso) {
if (!iso) return ''
const d = new Date(iso)
return d.toLocaleString(locale.value, {
year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit',
})
}
return {
authStore,
entry,
selectedId,
detailLoading,
editing,
draft,
visibility,
saving,
saveError,
versions,
showHistory,
detailOpen,
canEdit,
visibilityOptions,
select,
startCreate,
beginEdit,
cancel,
reset,
save,
remove,
toggleHistory,
formatWhen,
}
}