[verified] fix debrief pain display ([object Object]) + add 'create persona from this one' button

- formatValue(): render arrays of objects (e.g. pains) as readable text
  instead of '[object Object], [object Object]'
- SessionDetail: new 'สร้างบุคคลต้นแบบจากต้นแบบนี้' button calls the
  existing group persona-variant endpoint so trainees/orders can regenerate
  a persona based on the revealed one after a session
This commit is contained in:
Macky
2026-08-18 22:01:27 +07:00
parent 19e9fd2328
commit 711e058b24

View File

@@ -45,6 +45,16 @@
</div>
</div>
</details>
<button
v-if="session.group_id && session.persona_id"
class="soft variant-btn"
@click="makeVariant"
:disabled="creating"
>
{{ creating ? i18n.t('creating') : i18n.t('createVariant') }}
</button>
<div v-if="variantMsg" class="muted" style="margin-top:8px">{{ variantMsg }}</div>
</div>
</template>
</div>
@@ -52,14 +62,17 @@
<script setup>
import { computed, onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import { api } from '../api'
import { i18n } from '../i18n'
const route = useRoute()
const router = useRouter()
const session = ref(null)
const loading = ref(true)
const error = ref('')
const creating = ref(false)
const variantMsg = ref('')
const statusClass = computed(() => session.value?.status === 'active' ? 'active' : session.value?.outcome || 'not_tried')
const statusLabel = computed(() => {
@@ -80,11 +93,43 @@ function fieldLabel(key) {
return labels[key] || key
}
function formatValue(value) {
if (Array.isArray(value)) return value.join(', ')
if (Array.isArray(value)) {
// Arrays may hold plain strings OR objects (e.g. pains [{title,description}]).
// Never let join() stringify an object into "[object Object]".
return value
.map((item) => {
if (item && typeof item === 'object') {
const readable = item.description ?? item.title ?? item.text
return readable && typeof readable === 'string' && readable.trim()
? readable.trim()
: JSON.stringify(item)
}
return String(item)
})
.join(', ')
}
if (value && typeof value === 'object') return JSON.stringify(value)
return String(value == null ? '—' : value)
}
async function makeVariant() {
if (!session.value?.group_id || !session.value?.persona_id) return
creating.value = true
variantMsg.value = ''
try {
const created = await api.createPersonaVariant(session.value.group_id, session.value.persona_id)
const target = created && typeof created === 'object' && created.group_id
? created.group_id
: session.value.group_id
// Trainee variants land in the private group; admin variants in the shared group.
await router.push(`/groups/${target}/personas`)
} catch (e) {
variantMsg.value = i18n.t('requestFailed')
} finally {
creating.value = false
}
}
onMounted(async () => {
try {
session.value = (await api.getSession(route.params.sid)).session
@@ -101,6 +146,7 @@ onMounted(async () => {
.bubble { max-width:72%; padding:10px 14px; white-space:pre-wrap; word-break:break-word; }
.msg-system { align-self:center; background:#fef3c7; color:#92400e; font-size:12px; max-width:88%; border-radius:999px; }
.debrief { margin-top:16px; }
.variant-btn { margin-top:14px; }
.reveal-grid { display:grid; grid-template-columns:1fr 1fr; gap:8px; margin-top:8px; }
.rev { display:flex; flex-direction:column; background:#f8fafc; border:1px solid var(--border); border-radius:8px; padding:8px 10px; }
.rk { font-size:12px; color:var(--muted); }