diff --git a/app/Http/Controllers/Forms/FormController.php b/app/Http/Controllers/Forms/FormController.php index f541de5..6c99b7b 100644 --- a/app/Http/Controllers/Forms/FormController.php +++ b/app/Http/Controllers/Forms/FormController.php @@ -34,7 +34,7 @@ class FormController extends Controller $this->authorize('viewAny', Form::class); $workspaceIsPro = $workspace->is_pro; - $forms = $workspace->forms()->with(['creator','views','submissions'])->get()->map(function (Form $form) use ($workspace, $workspaceIsPro){ + $forms = $workspace->forms()->with(['creator','views','submissions'])->paginate(10)->through(function (Form $form) use ($workspace, $workspaceIsPro){ // Add attributes for faster loading $form->extra = (object) [ 'loadedWorkspace' => $workspace, diff --git a/app/Http/Controllers/Forms/FormSubmissionController.php b/app/Http/Controllers/Forms/FormSubmissionController.php index 5c96bc9..ba7a8f8 100644 --- a/app/Http/Controllers/Forms/FormSubmissionController.php +++ b/app/Http/Controllers/Forms/FormSubmissionController.php @@ -35,7 +35,8 @@ class FormSubmissionController extends Controller foreach ($form->submissions->toArray() as $row) { $formatter = (new FormSubmissionFormatter($form, $row['data'])) ->outputStringsOnly() - ->setEmptyForNoValue(); + ->setEmptyForNoValue() + ->showRemovedFields(); $tmp = $formatter->getCleanKeyValue(); $tmp['Create Date'] = date("Y-m-d H:i", strtotime($row['created_at'])); $allRows[] = $tmp; diff --git a/app/Http/Controllers/Forms/PublicFormController.php b/app/Http/Controllers/Forms/PublicFormController.php index 1cbc092..5b4fab1 100644 --- a/app/Http/Controllers/Forms/PublicFormController.php +++ b/app/Http/Controllers/Forms/PublicFormController.php @@ -21,7 +21,7 @@ class PublicFormController extends Controller public function show(Request $request, string $slug) { - $form = Form::whereSlug($slug)->firstOrFail(); + $form = Form::whereSlug($slug)->whereVisibility('public')->firstOrFail(); if ($form->workspace == null) { // Workspace deleted return $this->error([ diff --git a/app/Http/Requests/AnswerFormRequest.php b/app/Http/Requests/AnswerFormRequest.php index 379d28a..7cd1f3b 100644 --- a/app/Http/Requests/AnswerFormRequest.php +++ b/app/Http/Requests/AnswerFormRequest.php @@ -40,7 +40,7 @@ class AnswerFormRequest extends FormRequest */ public function authorize() { - return !$this->form->is_closed && !$this->form->max_number_of_submissions_reached; + return !$this->form->is_closed && !$this->form->max_number_of_submissions_reached && $this->form->visibility === 'public'; } /** diff --git a/app/Http/Requests/UserFormRequest.php b/app/Http/Requests/UserFormRequest.php index ecfb073..e5fd267 100644 --- a/app/Http/Requests/UserFormRequest.php +++ b/app/Http/Requests/UserFormRequest.php @@ -29,6 +29,7 @@ abstract class UserFormRequest extends \Illuminate\Foundation\Http\FormRequest 'title' => 'required|string|max:60', 'description' => 'nullable|string|max:2000', 'tags' => 'nullable|array', + 'visibility' => ['required',Rule::in(Form::VISIBILITY)], // Notifications 'notifies' => 'boolean', @@ -83,6 +84,7 @@ abstract class UserFormRequest extends \Illuminate\Foundation\Http\FormRequest 'properties.*.timezone' => 'sometimes|nullable', 'properties.*.width' => ['sometimes', Rule::in(['full','1/2','1/3','2/3','1/3','3/4','1/4'])], 'properties.*.allowed_file_types' => 'sometimes|nullable', + 'properties.*.use_toggle_switch' => 'boolean|nullable', // Logic 'properties.*.logic' => ['array', 'nullable', new FormPropertyLogicRule()], diff --git a/app/Http/Resources/FormResource.php b/app/Http/Resources/FormResource.php index 71dee8c..bad73e6 100644 --- a/app/Http/Resources/FormResource.php +++ b/app/Http/Resources/FormResource.php @@ -42,8 +42,10 @@ class FormResource extends JsonResource 'can_be_indexed' => $this->can_be_indexed, 'password' => $this->password, 'tags' => $this->tags, + 'visibility' => $this->visibility, 'notification_emails' => $this->notification_emails, 'slack_webhook_url' => $this->slack_webhook_url, + 'removed_properties' => $this->removed_properties ] : []; $baseData = $this->getFilteredFormData(parent::toArray($request), $this->userIsFormOwner()); diff --git a/app/Models/Forms/Form.php b/app/Models/Forms/Form.php index b12cdbe..c2f2405 100644 --- a/app/Models/Forms/Form.php +++ b/app/Models/Forms/Form.php @@ -20,6 +20,7 @@ class Form extends Model const DARK_MODE_VALUES = ['auto', 'light', 'dark']; const THEMES = ['default', 'simple', 'notion']; const WIDTHS = ['centered', 'full']; + const VISIBILITY = ['public', 'draft']; use HasFactory, HasSlug, SoftDeletes; @@ -45,6 +46,7 @@ class Form extends Model 'title', 'description', 'tags', + 'visibility', // Customization 'theme', diff --git a/app/Service/Forms/FormSubmissionFormatter.php b/app/Service/Forms/FormSubmissionFormatter.php index 8ac7c5a..e4416d4 100644 --- a/app/Service/Forms/FormSubmissionFormatter.php +++ b/app/Service/Forms/FormSubmissionFormatter.php @@ -27,6 +27,8 @@ class FormSubmissionFormatter private $setEmptyForNoValue = false; + private $showRemovedFields = false; + public function __construct(private Form $form, private array $formData) { } @@ -55,6 +57,12 @@ class FormSubmissionFormatter return $this; } + public function showRemovedFields() + { + $this->showRemovedFields = true; + return $this; + } + /** * Return a nice "FieldName": "Field Response" array * - If createLink enabled, returns html link for emails and links @@ -63,10 +71,15 @@ class FormSubmissionFormatter public function getCleanKeyValue() { $data = $this->formData; - $fields = $this->form->properties; + $fields = ($this->showRemovedFields) ? array_merge($this->form->properties, $this->form->removed_properties) : $this->form->properties; $returnArray = []; foreach ($fields as &$field) { + $isRemoved = in_array($field['id'], array_column($this->form->removed_properties, 'id')) ?? false; + if($isRemoved){ + $field['name'] = $field['name']." (deleted)"; + } + // If not present skip if (!isset($data[$field['id']])) { if ($this->setEmptyForNoValue) { diff --git a/database/factories/FormFactory.php b/database/factories/FormFactory.php index 40d4850..b5e5809 100644 --- a/database/factories/FormFactory.php +++ b/database/factories/FormFactory.php @@ -58,6 +58,7 @@ class FormFactory extends Factory return [ 'title' => $this->faker->text(30), 'description' => $this->faker->randomHtml(1), + 'visibility' => 'public', 'notifies' => false, 'send_submission_confirmation' => false, 'webhook_url' => null, diff --git a/database/migrations/2022_10_07_122038_add_visibility_to_forms.php b/database/migrations/2022_10_07_122038_add_visibility_to_forms.php new file mode 100644 index 0000000..acea5b7 --- /dev/null +++ b/database/migrations/2022_10_07_122038_add_visibility_to_forms.php @@ -0,0 +1,32 @@ +string('visibility')->default('public'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('forms', function (Blueprint $table) { + $table->dropColumn('visibility'); + }); + } +}; diff --git a/resources/js/components/forms/CheckboxInput.vue b/resources/js/components/forms/CheckboxInput.vue index d4ea11c..d6ba46b 100644 --- a/resources/js/components/forms/CheckboxInput.vue +++ b/resources/js/components/forms/CheckboxInput.vue @@ -1,7 +1,7 @@ diff --git a/resources/js/components/forms/ToggleSwitchInput.vue b/resources/js/components/forms/ToggleSwitchInput.vue new file mode 100644 index 0000000..21739bd --- /dev/null +++ b/resources/js/components/forms/ToggleSwitchInput.vue @@ -0,0 +1,31 @@ + + + + \ No newline at end of file diff --git a/resources/js/components/forms/components/VSwitch.vue b/resources/js/components/forms/components/VSwitch.vue index 24da137..2be7471 100644 --- a/resources/js/components/forms/components/VSwitch.vue +++ b/resources/js/components/forms/components/VSwitch.vue @@ -1,42 +1,15 @@ - - + \ No newline at end of file diff --git a/resources/js/components/forms/index.js b/resources/js/components/forms/index.js index bbb854e..70fad78 100644 --- a/resources/js/components/forms/index.js +++ b/resources/js/components/forms/index.js @@ -16,6 +16,7 @@ import ImageInput from './ImageInput' import DateInput from './DateInput'; import RatingInput from './RatingInput'; import FlatSelectInput from './FlatSelectInput'; +import ToggleSwitchInput from './ToggleSwitchInput'; // Components that are registered globaly. [ @@ -34,7 +35,8 @@ import FlatSelectInput from './FlatSelectInput'; RichTextAreaInput, DateInput, RatingInput, - FlatSelectInput + FlatSelectInput, + ToggleSwitchInput ].forEach(Component => { Vue.component(Component.name, Component) }) diff --git a/resources/js/components/open/forms/OpenForm.vue b/resources/js/components/open/forms/OpenForm.vue index 427acb0..7cdba83 100644 --- a/resources/js/components/open/forms/OpenForm.vue +++ b/resources/js/components/open/forms/OpenForm.vue @@ -317,6 +317,9 @@ export default { if (['select', 'multi_select'].includes(field.type) && field.without_dropdown) { return 'FlatSelectInput' } + if (field.type === 'checkbox' && field.use_toggle_switch) { + return 'ToggleSwitchInput' + } return this.fieldComponents[field.type] }, getFieldClasses (field) { diff --git a/resources/js/components/open/forms/components/FormSubmissions.vue b/resources/js/components/open/forms/components/FormSubmissions.vue index 5bb97ef..1d6bff0 100644 --- a/resources/js/components/open/forms/components/FormSubmissions.vue +++ b/resources/js/components/open/forms/components/FormSubmissions.vue @@ -2,11 +2,41 @@

- Form Submissions - - Export as CSV - + Form Submissions + - Export as CSV + Display columns

+ + + +
+
+

+ Display columns +

+
+
+ + +
+
+ Close +
+
+
+
!property.hasOwnProperty('hidden') || !property.hidden) - tmp.push({ - "name": "Create Date", - "id": "create_date", - "type": "date" - }); - return tmp - }, exportUrl() { if (!this.form) { return '' @@ -104,6 +127,20 @@ export default { }) this.$set(this.form, 'properties', columns) this.formInitDone = true + + this.properties = clonedeep(this.form.properties) + this.removed_properties = clonedeep(this.form.removed_properties) + + // Get display columns from local storage + const tmpColumns = window.localStorage.getItem('display-columns-formid-'+this.form.id) + if(tmpColumns !== null && tmpColumns){ + this.displayColumns = JSON.parse(tmpColumns) + this.onChangeDisplayColumns() + }else{ + this.form.properties.forEach((field) => { + this.displayColumns[field.id] = true + }) + } }, getSubmissionsData() { if (!this.form || this.fullyLoaded) { @@ -131,6 +168,13 @@ export default { this.$refs.shadows.toggleShadow() this.$refs.shadows.calcDimensions() }, + onChangeDisplayColumns(){ + window.localStorage.setItem('display-columns-formid-'+this.form.id, JSON.stringify(this.displayColumns)) + const final_properties = this.properties.concat(this.removed_properties).filter((field) => { + return this.displayColumns[field.id] === true + }) + this.$set(this.form, 'properties', final_properties) + } }, } diff --git a/resources/js/components/open/forms/components/form-components/FormEditorPreview.vue b/resources/js/components/open/forms/components/form-components/FormEditorPreview.vue index 51fa584..00839ae 100644 --- a/resources/js/components/open/forms/components/form-components/FormEditorPreview.vue +++ b/resources/js/components/open/forms/components/form-components/FormEditorPreview.vue @@ -46,7 +46,7 @@ > Logo Picture
diff --git a/resources/js/components/open/forms/components/form-components/FormInformation.vue b/resources/js/components/open/forms/components/form-components/FormInformation.vue index 62631d4..f94bd12 100644 --- a/resources/js/components/open/forms/components/form-components/FormInformation.vue +++ b/resources/js/components/open/forms/components/form-components/FormInformation.vue @@ -27,6 +27,11 @@ placeholder="Select Tag(s)" :multiple="true" :allowCreation="true" :options="allTagsOptions" /> +