Feature: Delete submission (#137)

* Feature: Delete submission

* Small polishing

---------

Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
formsdev 2023-06-30 19:23:04 +05:30 committed by GitHub
parent 645a3712d6
commit 1d5c6f4895
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 126 additions and 19 deletions

View File

@ -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.'
]);
}
}

View File

@ -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
]); ]);
} }

View File

@ -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.

View File

@ -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>

View File

@ -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()
} }
}, },
} }

View File

@ -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 {

View File

@ -13,7 +13,7 @@
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>
@ -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 {{

View File

@ -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'])