Added linting and enforced code styling.

This commit is contained in:
Cameron Redmore 2025-04-25 08:14:48 +01:00
parent 8655eae39c
commit 86967b26cd
37 changed files with 3356 additions and 1875 deletions

View file

@ -1,12 +1,17 @@
<template>
<q-page padding>
<q-inner-loading :showing="loading">
<q-spinner-gears size="50px" color="primary" />
<q-spinner-gears
size="50px"
color="primary"
/>
</q-inner-loading>
<div v-if="!loading && formTitle">
<div class="row justify-between items-center q-mb-md">
<div class="text-h4">Responses for: {{ formTitle }}</div>
<div class="text-h4">
Responses for: {{ formTitle }}
</div>
</div>
<!-- Add Search Input -->
@ -19,7 +24,7 @@
placeholder="Search responses..."
class="q-mb-md"
>
<template v-slot:append>
<template #append>
<q-icon name="search" />
</template>
</q-input>
@ -29,22 +34,25 @@
:rows="formattedResponses"
:columns="columns"
row-key="id"
flat bordered
flat
bordered
separator="cell"
wrap-cells
:filter="filterText"
>
<template v-slot:body-cell-submittedAt="props">
<template #body-cell-submittedAt="props">
<q-td :props="props">
{{ new Date(props.value).toLocaleString() }}
</q-td>
</template>
<!-- Slot for Actions column -->
<template v-slot:body-cell-actions="props">
<template #body-cell-actions="props">
<q-td :props="props">
<q-btn
flat dense round
flat
dense
round
icon="download"
color="primary"
@click="downloadResponsePdf(props.row.id)"
@ -54,24 +62,36 @@
</q-btn>
</q-td>
</template>
</q-table>
<q-banner v-else class="">
<template v-slot:avatar>
<q-icon name="info" color="info" />
<q-banner
v-else
class=""
>
<template #avatar>
<q-icon
name="info"
color="info"
/>
</template>
No responses have been submitted for this form yet.
</q-banner>
</div>
<q-banner v-else-if="!loading && !formTitle" class="bg-negative text-white">
<template v-slot:avatar>
<q-banner
v-else-if="!loading && !formTitle"
class="bg-negative text-white"
>
<template #avatar>
<q-icon name="error" />
</template>
Form not found or could not load responses.
<template v-slot:action>
<q-btn flat color="white" label="Back to Forms" :to="{ name: 'formList' }" />
<template #action>
<q-btn
flat
color="white"
label="Back to Forms"
:to="{ name: 'formList' }"
/>
</template>
</q-banner>
</q-page>
@ -81,9 +101,8 @@
import { ref, onMounted, computed } from 'vue';
import axios from 'axios';
import { useQuasar } from 'quasar';
import { useRoute } from 'vue-router';
const props = defineProps({
const componentProps = defineProps({
id: {
type: [String, Number],
required: true
@ -93,34 +112,37 @@ const props = defineProps({
const $q = useQuasar();
const formTitle = ref('');
const responses = ref([]);
const columns = ref([]); // Columns will be generated dynamically
const columns = ref([]);
const loading = ref(true);
const filterText = ref(''); // Add ref for filter text
const filterText = ref('');
// Fetch both form details (for title and field labels/order) and responses
async function fetchData() {
async function fetchData()
{
loading.value = true;
formTitle.value = '';
responses.value = [];
columns.value = [];
try {
try
{
// Fetch form details first to get the structure
const formDetailsResponse = await axios.get(`/api/forms/${props.id}`);
const formDetailsResponse = await axios.get(`/api/forms/${componentProps.id}`);
const form = formDetailsResponse.data;
formTitle.value = form.title;
// Generate columns based on form fields in correct order
const generatedColumns = [{ name: 'submittedAt', label: 'Submitted At', field: 'submittedAt', align: 'left', sortable: true }];
form.categories.forEach(cat => {
cat.fields.forEach(field => {
form.categories.forEach(cat =>
{
cat.fields.forEach(field =>
{
generatedColumns.push({
name: `field_${field.id}`, // Unique name for column
name: `field_${field.id}`,
label: field.label,
field: row => row.values[field.id]?.value ?? '', // Access nested value safely
field: row => row.values[field.id]?.value ?? '',
align: 'left',
sortable: true,
// Add formatting based on field.type if needed
});
});
});
@ -135,25 +157,31 @@ async function fetchData() {
});
// Fetch responses
const responsesResponse = await axios.get(`/api/forms/${props.id}/responses`);
responses.value = responsesResponse.data; // API already groups them
const responsesResponse = await axios.get(`/api/forms/${componentProps.id}/responses`);
responses.value = responsesResponse.data;
} catch (error) {
console.error(`Error fetching data for form ${props.id}:`, error);
}
catch (error)
{
console.error(`Error fetching data for form ${componentProps.id}:`, error);
$q.notify({
color: 'negative',
position: 'top',
message: 'Failed to load form responses.',
icon: 'report_problem'
});
} finally {
}
finally
{
loading.value = false;
}
}
// Computed property to match the structure expected by QTable rows
const formattedResponses = computed(() => {
return responses.value.map(response => {
const formattedResponses = computed(() =>
{
return responses.value.map(response =>
{
const row = {
id: response.id,
submittedAt: response.submittedAt,
@ -165,8 +193,10 @@ const formattedResponses = computed(() => {
});
// Function to download a single response as PDF
async function downloadResponsePdf(responseId) {
try {
async function downloadResponsePdf(responseId)
{
try
{
const response = await axios.get(`/api/responses/${responseId}/export/pdf`, {
responseType: 'blob', // Important for handling file downloads
});
@ -179,9 +209,11 @@ async function downloadResponsePdf(responseId) {
// Try to get filename from content-disposition header
const contentDisposition = response.headers['content-disposition'];
let filename = `response-${responseId}.pdf`; // Default filename
if (contentDisposition) {
if (contentDisposition)
{
const filenameMatch = contentDisposition.match(/filename="?(.+)"?/i);
if (filenameMatch && filenameMatch.length > 1) {
if (filenameMatch && filenameMatch.length > 1)
{
filename = filenameMatch[1];
}
}
@ -201,7 +233,9 @@ async function downloadResponsePdf(responseId) {
icon: 'check_circle'
});
} catch (error) {
}
catch (error)
{
console.error(`Error downloading PDF for response ${responseId}:`, error);
$q.notify({
color: 'negative',