Feature: Delete submission (#137)
* Feature: Delete submission * Small polishing --------- Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
parent
645a3712d6
commit
1d5c6f4895
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Forms;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Forms\Form;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class RecordController extends Controller
|
||||||
|
{
|
||||||
|
public function delete(Request $request, $id, $recordId)
|
||||||
|
{
|
||||||
|
$form = Form::findOrFail((int) $id);
|
||||||
|
$this->authorize('delete', $form);
|
||||||
|
|
||||||
|
$record = $form->submissions()->where('id', $recordId)->firstOrFail();
|
||||||
|
$record->delete();
|
||||||
|
|
||||||
|
return $this->success([
|
||||||
|
'message' => 'Record successfully removed.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -16,7 +16,7 @@ class FormSubmissionResource extends JsonResource
|
||||||
public function toArray($request)
|
public function toArray($request)
|
||||||
{
|
{
|
||||||
$this->generateFileLinks();
|
$this->generateFileLinks();
|
||||||
$this->addTimestamp();
|
$this->addExtraData();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'data' => $this->data,
|
'data' => $this->data,
|
||||||
|
@ -25,10 +25,11 @@ class FormSubmissionResource extends JsonResource
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function addTimestamp()
|
private function addExtraData()
|
||||||
{
|
{
|
||||||
$this->data = array_merge($this->data, [
|
$this->data = array_merge($this->data, [
|
||||||
"created_at" => $this->created_at->toDateTimeString()
|
"created_at" => $this->created_at->toDateTimeString(),
|
||||||
|
'id' => $this->id
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ namespace App\Service\OpenAi\Utils;
|
||||||
* Licensed under MIT license.
|
* Licensed under MIT license.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use App\Exceptions\Coursework\InvalidJsonException;
|
use Aws\Exception\InvalidJsonException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to fix truncated JSON by padding contextual counterparts at the end.
|
* Attempts to fix truncated JSON by padding contextual counterparts at the end.
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex items-center justify-center space-x-1">
|
||||||
|
<button v-track.delete_record_click
|
||||||
|
class="border rounded py-1 px-2 text-gray-500 dark:text-gray-400 hover:text-red-700"
|
||||||
|
@click="onDeleteClick"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
||||||
|
class="w-4 h-4"
|
||||||
|
>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
import store from '~/store'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { },
|
||||||
|
props: {
|
||||||
|
form: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
structure: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
rowid: {
|
||||||
|
type: Number,
|
||||||
|
default: () => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onDeleteClick () {
|
||||||
|
this.alertConfirm('Do you really want to delete this record?', this.deleteRecord)
|
||||||
|
},
|
||||||
|
async deleteRecord () {
|
||||||
|
axios.delete('/api/open/forms/' + this.form.id + '/records/' + this.rowid + '/delete').then(async (response) => {
|
||||||
|
if (response.data.type === 'success') {
|
||||||
|
this.$emit('deleted')
|
||||||
|
this.alertSuccess(response.data.message)
|
||||||
|
} else {
|
||||||
|
this.alertError('Something went wrong!')
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
this.alertError(error.response.data.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -53,6 +53,7 @@
|
||||||
:data="tableData"
|
:data="tableData"
|
||||||
:loading="isLoading"
|
:loading="isLoading"
|
||||||
@resize="dataChanged()"
|
@resize="dataChanged()"
|
||||||
|
@deleted="onDeleteRecord()"
|
||||||
>
|
>
|
||||||
</open-table>
|
</open-table>
|
||||||
</scroll-shadow>
|
</scroll-shadow>
|
||||||
|
@ -186,6 +187,11 @@ export default {
|
||||||
return this.displayColumns[field.id] === true
|
return this.displayColumns[field.id] === true
|
||||||
})
|
})
|
||||||
this.$set(this.form, 'properties', final_properties)
|
this.$set(this.form, 'properties', final_properties)
|
||||||
|
},
|
||||||
|
onDeleteRecord() {
|
||||||
|
this.fullyLoaded = false
|
||||||
|
this.tableData = []
|
||||||
|
this.getSubmissionsData()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
{{ col.name }}
|
{{ col.name }}
|
||||||
</p>
|
</p>
|
||||||
</resizable-th>
|
</resizable-th>
|
||||||
<th v-if="hasActions" class="n-table-cell p-0 relative" style="width: 91px">
|
<th v-if="hasActions" class="n-table-cell p-0 relative" style="width: 100px">
|
||||||
<p
|
<p
|
||||||
class="bg-gray-50 dark:bg-notion-dark truncate sticky top-0 border-b border-gray-200 dark:border-gray-800 px-4 py-2 text-gray-500 font-semibold tracking-wider uppercase text-xs">
|
class="bg-gray-50 dark:bg-notion-dark truncate sticky top-0 border-b border-gray-200 dark:border-gray-800 px-4 py-2 text-gray-500 font-semibold tracking-wider uppercase text-xs">
|
||||||
Actions
|
Actions
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
<slot name="actions"/>
|
<slot name="actions"/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="row, index in data" :key="row.id" class="n-table-row" :class="{'first':index===0}">
|
<tr v-for="row, index in data" :key="index" class="n-table-row" :class="{'first':index===0}">
|
||||||
<td v-for="col, colIndex in form.properties"
|
<td v-for="col, colIndex in form.properties"
|
||||||
:key="col.id"
|
:key="col.id"
|
||||||
:style="{width: col.width + 'px'}"
|
:style="{width: col.width + 'px'}"
|
||||||
|
@ -51,6 +51,11 @@
|
||||||
:property="col" :value="row[col.id]"
|
:property="col" :value="row[col.id]"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
<td v-if="hasActions" class="n-table-cell border-gray-100 dark:border-gray-900 text-sm p-2 border-b"
|
||||||
|
style="width: 100px"
|
||||||
|
>
|
||||||
|
<record-operations :form="form" :structure="form.properties" :rowid="row.id" @deleted="$emit('deleted')" />
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="loading" class="n-table-row border-t bg-gray-50 dark:bg-gray-900">
|
<tr v-if="loading" class="n-table-row border-t bg-gray-50 dark:bg-gray-900">
|
||||||
<td :colspan="form.properties.length" class="p-8 w-full">
|
<td :colspan="form.properties.length" class="p-8 w-full">
|
||||||
|
@ -79,6 +84,7 @@ import OpenDate from './components/OpenDate.vue'
|
||||||
import OpenFile from './components/OpenFile.vue'
|
import OpenFile from './components/OpenFile.vue'
|
||||||
import OpenCheckbox from './components/OpenCheckbox.vue'
|
import OpenCheckbox from './components/OpenCheckbox.vue'
|
||||||
import ResizableTh from './components/ResizableTh.vue'
|
import ResizableTh from './components/ResizableTh.vue'
|
||||||
|
import RecordOperations from '../components/RecordOperations.vue'
|
||||||
import clonedeep from 'clone-deep'
|
import clonedeep from 'clone-deep'
|
||||||
|
|
||||||
const cyrb53 = function (str, seed = 0) {
|
const cyrb53 = function (str, seed = 0) {
|
||||||
|
@ -95,7 +101,7 @@ const cyrb53 = function (str, seed = 0) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {ResizableTh},
|
components: {ResizableTh, RecordOperations},
|
||||||
props: {
|
props: {
|
||||||
data: {
|
data: {
|
||||||
type: Array,
|
type: Array,
|
||||||
|
@ -129,7 +135,8 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
hasActions() {
|
hasActions() {
|
||||||
return false
|
// In future if want to hide based on condition
|
||||||
|
return true
|
||||||
},
|
},
|
||||||
fieldComponents() {
|
fieldComponents() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -13,13 +13,13 @@
|
||||||
Go back
|
Go back
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="flex">
|
<div class="flex flex-wrap">
|
||||||
<h2 class="flex-grow text-gray-900 truncate">
|
<h2 class="flex-grow text-gray-900 truncate">
|
||||||
{{ form.title }}
|
{{ form.title }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<extra-menu :form="form" />
|
<extra-menu :form="form" />
|
||||||
|
|
||||||
<v-button target="_blank" :to="{name:'forms.show_public', params: {slug: form.slug}}"
|
<v-button target="_blank" :to="{name:'forms.show_public', params: {slug: form.slug}}"
|
||||||
color="white" class="mr-2 text-blue-600 hidden sm:block"
|
color="white" class="mr-2 text-blue-600 hidden sm:block"
|
||||||
v-track.view_form_click="{form_id:form.id, form_slug:form.slug}">
|
v-track.view_form_click="{form_id:form.id, form_slug:form.slug}">
|
||||||
|
@ -44,15 +44,15 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="flex text-gray-500">
|
<p class="text-gray-500 text-sm">
|
||||||
<li class="pr-1">{{ form.views_count }} view{{ form.views_count > 0 ? 's' : '' }}</li>
|
<span class="pr-1">{{ form.views_count }} view{{ form.views_count > 0 ? 's' : '' }}</span>
|
||||||
<li class="list-disc ml-6 pr-1">{{ form.submissions_count }}
|
<span class="pr-1">- {{ form.submissions_count }}
|
||||||
submission{{ form.submissions_count > 0 ? 's' : '' }}
|
submission{{ form.submissions_count > 0 ? 's' : '' }}
|
||||||
</li>
|
</span>
|
||||||
<li class="list-disc ml-6 pr-1 text-blue-500" v-if="form.visibility=='draft'">Draft (not public)</li>
|
<span class="text-blue-500" v-if="form.visibility=='draft'">- Draft (not public)</span>
|
||||||
<li class="list-disc ml-6 pr-1 text-blue-500" v-if="form.visibility=='closed'">Closed</li>
|
<span class="pr-1 text-blue-500" v-if="form.visibility=='closed'">- Closed</span>
|
||||||
<li class="list-disc ml-6">Edited {{ form.last_edited_human }}</li>
|
<span class="">- Edited {{ form.last_edited_human }}</span>
|
||||||
</ul>
|
</p>
|
||||||
|
|
||||||
<p v-if="form.closes_at" class="text-yellow-500">
|
<p v-if="form.closes_at" class="text-yellow-500">
|
||||||
<span v-if="form.is_closed"> This form stopped accepting submissions on the {{
|
<span v-if="form.is_closed"> This form stopped accepting submissions on the {{
|
||||||
|
@ -208,7 +208,7 @@ export default {
|
||||||
this.workingForm = new Form(this.form)
|
this.workingForm = new Form(this.form)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
openCrisp() {
|
openCrisp() {
|
||||||
window.$crisp.push(['do', 'chat:show'])
|
window.$crisp.push(['do', 'chat:show'])
|
||||||
|
|
|
@ -14,6 +14,7 @@ use App\Http\Controllers\Forms\FormStatsController;
|
||||||
use App\Http\Controllers\Forms\PublicFormController;
|
use App\Http\Controllers\Forms\PublicFormController;
|
||||||
use App\Http\Controllers\Forms\FormSubmissionController;
|
use App\Http\Controllers\Forms\FormSubmissionController;
|
||||||
use App\Http\Controllers\Forms\FormController;
|
use App\Http\Controllers\Forms\FormController;
|
||||||
|
use App\Http\Controllers\Forms\RecordController;
|
||||||
use App\Http\Controllers\WorkspaceController;
|
use App\Http\Controllers\WorkspaceController;
|
||||||
use App\Http\Controllers\TemplateController;
|
use App\Http\Controllers\TemplateController;
|
||||||
use App\Http\Controllers\Forms\Integration\FormZapierWebhookController;
|
use App\Http\Controllers\Forms\Integration\FormZapierWebhookController;
|
||||||
|
@ -83,6 +84,9 @@ Route::group(['middleware' => 'auth:api'], function () {
|
||||||
Route::get('/{id}/submissions', [FormSubmissionController::class, 'submissions'])->name('submissions');
|
Route::get('/{id}/submissions', [FormSubmissionController::class, 'submissions'])->name('submissions');
|
||||||
Route::get('/{id}/submissions/export', [FormSubmissionController::class, 'export'])->name('submissions.export');
|
Route::get('/{id}/submissions/export', [FormSubmissionController::class, 'export'])->name('submissions.export');
|
||||||
Route::get('/{id}/submissions/file/{filename}', [FormSubmissionController::class, 'submissionFile'])->name('submissions.file');
|
Route::get('/{id}/submissions/file/{filename}', [FormSubmissionController::class, 'submissionFile'])->name('submissions.file');
|
||||||
|
|
||||||
|
Route::delete('/{id}/records/{recordid}/delete', [RecordController::class, 'delete'])->name('records.delete');
|
||||||
|
|
||||||
// Form Admin tool
|
// Form Admin tool
|
||||||
Route::put('/{id}/regenerate-link/{option}',
|
Route::put('/{id}/regenerate-link/{option}',
|
||||||
[FormController::class, 'regenerateLink'])
|
[FormController::class, 'regenerateLink'])
|
||||||
|
|
Loading…
Reference in New Issue