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)
|
||||
{
|
||||
$this->generateFileLinks();
|
||||
$this->addTimestamp();
|
||||
$this->addExtraData();
|
||||
|
||||
return [
|
||||
'data' => $this->data,
|
||||
|
@ -25,10 +25,11 @@ class FormSubmissionResource extends JsonResource
|
|||
];
|
||||
}
|
||||
|
||||
private function addTimestamp()
|
||||
private function addExtraData()
|
||||
{
|
||||
$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.
|
||||
*/
|
||||
|
||||
use App\Exceptions\Coursework\InvalidJsonException;
|
||||
use Aws\Exception\InvalidJsonException;
|
||||
|
||||
/**
|
||||
* 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"
|
||||
:loading="isLoading"
|
||||
@resize="dataChanged()"
|
||||
@deleted="onDeleteRecord()"
|
||||
>
|
||||
</open-table>
|
||||
</scroll-shadow>
|
||||
|
@ -186,6 +187,11 @@ export default {
|
|||
return this.displayColumns[field.id] === true
|
||||
})
|
||||
this.$set(this.form, 'properties', final_properties)
|
||||
},
|
||||
onDeleteRecord() {
|
||||
this.fullyLoaded = false
|
||||
this.tableData = []
|
||||
this.getSubmissionsData()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
{{ col.name }}
|
||||
</p>
|
||||
</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
|
||||
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
|
||||
|
@ -39,7 +39,7 @@
|
|||
<slot name="actions"/>
|
||||
</td>
|
||||
</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"
|
||||
:key="col.id"
|
||||
:style="{width: col.width + 'px'}"
|
||||
|
@ -51,6 +51,11 @@
|
|||
:property="col" :value="row[col.id]"
|
||||
/>
|
||||
</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 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">
|
||||
|
@ -79,6 +84,7 @@ import OpenDate from './components/OpenDate.vue'
|
|||
import OpenFile from './components/OpenFile.vue'
|
||||
import OpenCheckbox from './components/OpenCheckbox.vue'
|
||||
import ResizableTh from './components/ResizableTh.vue'
|
||||
import RecordOperations from '../components/RecordOperations.vue'
|
||||
import clonedeep from 'clone-deep'
|
||||
|
||||
const cyrb53 = function (str, seed = 0) {
|
||||
|
@ -95,7 +101,7 @@ const cyrb53 = function (str, seed = 0) {
|
|||
}
|
||||
|
||||
export default {
|
||||
components: {ResizableTh},
|
||||
components: {ResizableTh, RecordOperations},
|
||||
props: {
|
||||
data: {
|
||||
type: Array,
|
||||
|
@ -129,7 +135,8 @@ export default {
|
|||
}
|
||||
},
|
||||
hasActions() {
|
||||
return false
|
||||
// In future if want to hide based on condition
|
||||
return true
|
||||
},
|
||||
fieldComponents() {
|
||||
return {
|
||||
|
|
|
@ -13,13 +13,13 @@
|
|||
Go back
|
||||
</a>
|
||||
|
||||
<div class="flex">
|
||||
<div class="flex flex-wrap">
|
||||
<h2 class="flex-grow text-gray-900 truncate">
|
||||
{{ form.title }}
|
||||
</h2>
|
||||
<div class="flex">
|
||||
<extra-menu :form="form" />
|
||||
|
||||
|
||||
<v-button target="_blank" :to="{name:'forms.show_public', params: {slug: form.slug}}"
|
||||
color="white" class="mr-2 text-blue-600 hidden sm:block"
|
||||
v-track.view_form_click="{form_id:form.id, form_slug:form.slug}">
|
||||
|
@ -44,15 +44,15 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="flex text-gray-500">
|
||||
<li class="pr-1">{{ form.views_count }} view{{ form.views_count > 0 ? 's' : '' }}</li>
|
||||
<li class="list-disc ml-6 pr-1">{{ form.submissions_count }}
|
||||
<p class="text-gray-500 text-sm">
|
||||
<span class="pr-1">{{ form.views_count }} view{{ form.views_count > 0 ? 's' : '' }}</span>
|
||||
<span class="pr-1">- {{ form.submissions_count }}
|
||||
submission{{ form.submissions_count > 0 ? 's' : '' }}
|
||||
</li>
|
||||
<li class="list-disc ml-6 pr-1 text-blue-500" v-if="form.visibility=='draft'">Draft (not public)</li>
|
||||
<li class="list-disc ml-6 pr-1 text-blue-500" v-if="form.visibility=='closed'">Closed</li>
|
||||
<li class="list-disc ml-6">Edited {{ form.last_edited_human }}</li>
|
||||
</ul>
|
||||
</span>
|
||||
<span class="text-blue-500" v-if="form.visibility=='draft'">- Draft (not public)</span>
|
||||
<span class="pr-1 text-blue-500" v-if="form.visibility=='closed'">- Closed</span>
|
||||
<span class="">- Edited {{ form.last_edited_human }}</span>
|
||||
</p>
|
||||
|
||||
<p v-if="form.closes_at" class="text-yellow-500">
|
||||
<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)
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
openCrisp() {
|
||||
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\FormSubmissionController;
|
||||
use App\Http\Controllers\Forms\FormController;
|
||||
use App\Http\Controllers\Forms\RecordController;
|
||||
use App\Http\Controllers\WorkspaceController;
|
||||
use App\Http\Controllers\TemplateController;
|
||||
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/export', [FormSubmissionController::class, 'export'])->name('submissions.export');
|
||||
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
|
||||
Route::put('/{id}/regenerate-link/{option}',
|
||||
[FormController::class, 'regenerateLink'])
|
||||
|
|
Loading…
Reference in New Issue