gigafibre-fsm/apps/client/src/pages/TicketsPage.vue
louispaulb 101faa21f1 feat: inline editing, search, notifications + full repo cleanup
- InlineField component + useInlineEdit composable for Odoo-style dblclick editing
- Client search by name, account ID, and legacy_customer_id (or_filters)
- SMS/Email notification panel on ContactCard via n8n webhooks
- Ticket reply thread via Communication docs
- All migration scripts (51 files) now tracked
- Client portal and field tech app added to monorepo
- README rewritten with full feature list, migration summary, architecture
- CHANGELOG updated with all recent work
- ROADMAP updated with current completion status
- Removed hardcoded tokens from docs (use $ERP_SERVICE_TOKEN)
- .gitignore updated (docker/, .claude/, exports/, .quasar/)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 07:34:41 -04:00

130 lines
4.0 KiB
Vue

<template>
<q-page padding>
<div class="flex items-center justify-between q-mb-md">
<div class="page-title q-mb-none">Support</div>
<q-btn color="primary" icon="add" label="Nouveau ticket" no-caps @click="showCreate = true" />
</div>
<q-table
:rows="tickets"
:columns="columns"
row-key="name"
:loading="loading"
flat bordered
class="bg-white"
no-data-label="Aucun ticket"
:pagination="{ rowsPerPage: 50 }"
>
<template #body-cell-creation="props">
<q-td :props="props">{{ formatShortDate(props.value) }}</q-td>
</template>
<template #body-cell-status="props">
<q-td :props="props">
<q-badge :color="statusColor(props.value)" :label="statusLabel(props.value)" />
</q-td>
</template>
<template #body-cell-priority="props">
<q-td :props="props">
<q-badge :color="priorityColor(props.value)" :label="props.value" outline />
</q-td>
</template>
</q-table>
<!-- New ticket dialog -->
<q-dialog v-model="showCreate" persistent>
<q-card style="min-width: 400px; max-width: 600px">
<q-card-section>
<div class="text-h6">Nouveau ticket de support</div>
</q-card-section>
<q-card-section>
<q-input v-model="newTicket.subject" label="Sujet" outlined class="q-mb-md"
:rules="[v => !!v || 'Le sujet est requis']" />
<q-input v-model="newTicket.description" label="Description" outlined type="textarea"
rows="4" :rules="[v => !!v || 'La description est requise']" />
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="Annuler" @click="showCreate = false" />
<q-btn color="primary" label="Envoyer" :loading="creating"
@click="submitTicket" :disable="!newTicket.subject || !newTicket.description" />
</q-card-actions>
</q-card>
</q-dialog>
</q-page>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useQuasar } from 'quasar'
import { useCustomerStore } from 'src/stores/customer'
import { fetchTickets, createTicket } from 'src/api/portal'
import { useFormatters } from 'src/composables/useFormatters'
const $q = useQuasar()
const store = useCustomerStore()
const { formatShortDate } = useFormatters()
const tickets = ref([])
const loading = ref(false)
const showCreate = ref(false)
const creating = ref(false)
const newTicket = ref({ subject: '', description: '' })
const columns = [
{ name: 'name', label: 'No.', field: 'name', align: 'left' },
{ name: 'subject', label: 'Sujet', field: 'subject', align: 'left' },
{ name: 'status', label: 'Statut', field: 'status', align: 'center' },
{ name: 'priority', label: 'Priorité', field: 'priority', align: 'center' },
{ name: 'creation', label: 'Créé le', field: 'creation', align: 'left' },
]
function statusColor (s) {
if (s === 'Open') return 'primary'
if (s === 'Closed' || s === 'Resolved') return 'positive'
if (s === 'Replied') return 'info'
return 'grey'
}
function statusLabel (s) {
const map = { Open: 'Ouvert', Closed: 'Fermé', Resolved: 'Résolu', Replied: 'Répondu' }
return map[s] || s
}
function priorityColor (p) {
if (p === 'High' || p === 'Urgent') return 'negative'
if (p === 'Medium') return 'warning'
return 'grey'
}
async function loadTickets () {
loading.value = true
try {
tickets.value = await fetchTickets(store.customerId)
} finally {
loading.value = false
}
}
async function submitTicket () {
creating.value = true
try {
await createTicket(store.customerId, newTicket.value.subject, newTicket.value.description)
showCreate.value = false
newTicket.value = { subject: '', description: '' }
$q.notify({ type: 'positive', message: 'Ticket créé avec succès' })
await loadTickets()
} catch (e) {
$q.notify({ type: 'negative', message: 'Erreur: ' + e.message })
} finally {
creating.value = false
}
}
onMounted(() => {
if (store.customerId) loadTickets()
})
</script>