mirror of
https://github.com/acaloiaro/frm
synced 2026-07-21 18:29:12 +00:00
ui teaks and subject_id added
This commit is contained in:
parent
4120bf4d5e
commit
da68ab121a
8 changed files with 50 additions and 28 deletions
|
|
@ -12,6 +12,7 @@ CREATE TABLE IF NOT EXISTS form_submissions (
|
|||
id BIGINT PRIMARY KEY DEFAULT nextval('submission_ids'),
|
||||
form_id BIGINT REFERENCES forms(id) ON DELETE CASCADE,
|
||||
workspace_id TEXT NOT NULL,
|
||||
subject_id TEXT,
|
||||
fields jsonb default '{}' NOT NULL,
|
||||
status submission_status default 'partial' NOT NULL,
|
||||
created_at timestamptz not null default timezone('utc', now()),
|
||||
|
|
@ -24,3 +25,4 @@ CREATE INDEX IF NOT EXISTS form_idx ON form_submissions USING btree (form_id);
|
|||
|
||||
COMMENT ON table form_submissions IS 'Respondants submit forms/fields to the collector as form_submissions';
|
||||
COMMENT ON column form_submissions.fields IS 'all form submissions are serialized to JSON, see types.FormFieldValue for structure details';
|
||||
COMMENT ON column form_submissions.subject_id IS 'identifies the subject/respondent who filled out the form';
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ func TestCreateShortCode(t *testing.T) {
|
|||
PostgresDisableSSL: true,
|
||||
WorkspaceID: "1",
|
||||
WorkspaceIDUrlParam: "client_id",
|
||||
PostgresSchema: "frm_test",
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
|
|
|
|||
|
|
@ -115,6 +115,8 @@ type FormSubmission struct {
|
|||
ID int64 `json:"id"`
|
||||
FormID *int64 `json:"form_id"`
|
||||
WorkspaceID string `json:"workspace_id"`
|
||||
// identifies the subject/respondent who filled out the form
|
||||
SubjectID *string `json:"subject_id"`
|
||||
// all form submissions are serialized to JSON, see types.FormFieldValue for structure details
|
||||
Fields types.FormFieldValues `json:"fields"`
|
||||
Status SubmissionStatus `json:"status"`
|
||||
|
|
|
|||
|
|
@ -397,7 +397,7 @@ VALUES (coalesce(nullif($1, 0), nextval('submission_ids'))::bigint, $2, $3, $4,
|
|||
UPDATE
|
||||
SET updated_at = timezone('utc', now()),
|
||||
fields = $4,
|
||||
status = $5 RETURNING id, form_id, workspace_id, fields, status, created_at, updated_at
|
||||
status = $5 RETURNING id, form_id, workspace_id, subject_id, fields, status, created_at, updated_at
|
||||
`
|
||||
|
||||
type SaveSubmissionParams struct {
|
||||
|
|
@ -415,7 +415,7 @@ type SaveSubmissionParams struct {
|
|||
// UPDATE
|
||||
// SET updated_at = timezone('utc', now()),
|
||||
// fields = $4,
|
||||
// status = $5 RETURNING id, form_id, workspace_id, fields, status, created_at, updated_at
|
||||
// status = $5 RETURNING id, form_id, workspace_id, subject_id, fields, status, created_at, updated_at
|
||||
func (q *Queries) SaveSubmission(ctx context.Context, arg SaveSubmissionParams) (FormSubmission, error) {
|
||||
row := q.db.QueryRow(ctx, saveSubmission,
|
||||
arg.ID,
|
||||
|
|
@ -429,6 +429,7 @@ func (q *Queries) SaveSubmission(ctx context.Context, arg SaveSubmissionParams)
|
|||
&i.ID,
|
||||
&i.FormID,
|
||||
&i.WorkspaceID,
|
||||
&i.SubjectID,
|
||||
&i.Fields,
|
||||
&i.Status,
|
||||
&i.CreatedAt,
|
||||
|
|
|
|||
|
|
@ -2603,6 +2603,10 @@ input.tab:checked + .tab-content,
|
|||
place-self: center;
|
||||
}
|
||||
|
||||
.overflow-visible {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.overflow-y-auto {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
|
@ -2621,10 +2625,6 @@ input.tab:checked + .tab-content,
|
|||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.rounded-full {
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.rounded-lg {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
|
@ -2822,6 +2822,11 @@ input.tab:checked + .tab-content,
|
|||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.text-xl {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.75rem;
|
||||
}
|
||||
|
||||
.text-xs {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
|
|
@ -2882,6 +2887,11 @@ input.tab:checked + .tab-content,
|
|||
color: rgb(255 119 119 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-slate-700 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(51 65 85 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-white {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
|
|
@ -2954,6 +2964,10 @@ input {
|
|||
color: rgb(69 69 69 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.focus\:border-2:focus {
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.focus\:border-transparent:focus {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -317,7 +317,7 @@ templ builderColumnLeft(form frm.Form) {
|
|||
// FormPreview previews a frm.Form
|
||||
templ FormPreview(form frm.Form) {
|
||||
<section id="builder-main-middle-col" class="w-full h-screen overflow-y-auto overscroll-auto p-4 bg-gray-50 border-y-1">
|
||||
<div class="mockup-browser border-base-300 border">
|
||||
<div class="mockup-browser border-base-300 border overflow-visible">
|
||||
<div class="mockup-browser-toolbar">
|
||||
<div class="input border-base-300 border">https://your-form-domain.com/form</div>
|
||||
</div>
|
||||
|
|
@ -803,7 +803,7 @@ templ formFieldTypeLabel(fieldType types.FormFieldType) {
|
|||
}
|
||||
|
||||
templ fieldLabel(field types.FormField) {
|
||||
<label for={ field.ID.String() }>
|
||||
<label for={ field.ID.String() } class="text-slate-700 text-xl py-1">
|
||||
{ field.Label }
|
||||
if field.Required {
|
||||
@requiredFieldIndicator()
|
||||
|
|
@ -825,19 +825,20 @@ func (v ViewerMetadata) JSON() string {
|
|||
return string(b)
|
||||
}
|
||||
|
||||
// FormView renders forms
|
||||
// FormView is a view of Forms
|
||||
templ FormView(form frm.Form, isPreview bool) {
|
||||
<div id="form-viewer" class="flex flex-col w-1/2 mx-auto justify-top pt-12" data-hx-swap-oob="true">
|
||||
<div
|
||||
id="form-metadata"
|
||||
data-data={ ViewerMetadata{Form: form}.JSON() }
|
||||
></div>
|
||||
<h1 class="rounded relative mb-2 font-black text-5xl">
|
||||
<h1 class="rounded relative mb-2 font-black text-slate-700 text-5xl">
|
||||
{ form.Name }
|
||||
</h1>
|
||||
<form
|
||||
_="on field_change(field_id, value) formValueChanged(field_id, value)"
|
||||
>
|
||||
<input name="subject_id" type="hidden"/>
|
||||
for _, field := range sortFields(form.Fields) {
|
||||
<div
|
||||
id={ fmt.Sprintf("field-container-%s", field.ID.String()) }
|
||||
|
|
@ -861,7 +862,7 @@ templ FormView(form frm.Form, isPreview bool) {
|
|||
type="text"
|
||||
autocomplete="off"
|
||||
_={ fmt.Sprintf("on keyup debounced at 250ms trigger field_change(field_id: '%s', value: my.value)", field.ID.String()) }
|
||||
class="border-gray-300 dark:border-gray-600 rounded-full bg-sky-50"
|
||||
class="flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 shadow-sm focus:outline-none focus:ring-2 focus:border-2 focus:ring-opacity-100 px-4 py-2 text-base resize-y block rounded-xl bg-sky-50"
|
||||
/>
|
||||
case types.FormFieldTypeTextMultiple:
|
||||
@fieldLabel(field)
|
||||
|
|
@ -871,7 +872,6 @@ templ FormView(form frm.Form, isPreview bool) {
|
|||
class="flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 shadow-sm focus:outline-none focus:ring-2 focus:border-transparent focus:ring-opacity-100 px-4 py-2 text-base resize-y block rounded-xl bg-sky-50"
|
||||
placeholder={ field.Placeholder }
|
||||
autocomplete="off"
|
||||
style="--tw-ring-color: #3B82F6;"
|
||||
_={ fmt.Sprintf("on keyup debounced at 250ms trigger field_change(field_id: '%s', value: my.value)", field.ID.String()) }
|
||||
rows="3"
|
||||
></textarea>
|
||||
|
|
@ -880,6 +880,7 @@ templ FormView(form frm.Form, isPreview bool) {
|
|||
ID: field.ID.String(),
|
||||
Name: field.ID.String(),
|
||||
Label: field.Label,
|
||||
LabelClass: "text-slate-700 text-xl py-1",
|
||||
Required: field.Required,
|
||||
Placeholder: field.Placeholder,
|
||||
Multiple: field.Type == types.FormFieldTypeMultiSelect,
|
||||
|
|
|
|||
|
|
@ -2505,7 +2505,7 @@ func (v ViewerMetadata) JSON() string {
|
|||
return string(b)
|
||||
}
|
||||
|
||||
// FormView renders forms
|
||||
// FormView is a view of Forms
|
||||
func FormView(form frm.Form, isPreview bool) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
|
|
@ -2565,7 +2565,7 @@ func FormView(form frm.Form, isPreview bool) templ.Component {
|
|||
var templ_7745c5c3_Var112 string
|
||||
templ_7745c5c3_Var112, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("field-container-%s", field.ID.String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/common.templ`, Line: 843, Col: 62}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/common.templ`, Line: 844, Col: 62}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var112))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
|
|
@ -2593,7 +2593,7 @@ func FormView(form frm.Form, isPreview bool) templ.Component {
|
|||
var templ_7745c5c3_Var113 string
|
||||
templ_7745c5c3_Var113, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("errors-%s", field.ID.String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/common.templ`, Line: 851, Col: 54}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/common.templ`, Line: 852, Col: 54}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var113))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
|
|
@ -2616,7 +2616,7 @@ func FormView(form frm.Form, isPreview bool) templ.Component {
|
|||
var templ_7745c5c3_Var114 string
|
||||
templ_7745c5c3_Var114, templ_7745c5c3_Err = templ.JoinStringErrs(field.ID.String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/common.templ`, Line: 858, Col: 30}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/common.templ`, Line: 859, Col: 30}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var114))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
|
|
@ -2629,7 +2629,7 @@ func FormView(form frm.Form, isPreview bool) templ.Component {
|
|||
var templ_7745c5c3_Var115 string
|
||||
templ_7745c5c3_Var115, templ_7745c5c3_Err = templ.JoinStringErrs(field.ID.String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/common.templ`, Line: 859, Col: 32}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/common.templ`, Line: 860, Col: 32}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var115))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
|
|
@ -2642,7 +2642,7 @@ func FormView(form frm.Form, isPreview bool) templ.Component {
|
|||
var templ_7745c5c3_Var116 string
|
||||
templ_7745c5c3_Var116, templ_7745c5c3_Err = templ.JoinStringErrs(field.Placeholder)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/common.templ`, Line: 860, Col: 39}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/common.templ`, Line: 861, Col: 39}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var116))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
|
|
@ -2655,7 +2655,7 @@ func FormView(form frm.Form, isPreview bool) templ.Component {
|
|||
var templ_7745c5c3_Var117 string
|
||||
templ_7745c5c3_Var117, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("on keyup debounced at 250ms trigger field_change(field_id: '%s', value: my.value)", field.ID.String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/common.templ`, Line: 863, Col: 127}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/common.templ`, Line: 864, Col: 127}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var117))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
|
|
@ -2677,7 +2677,7 @@ func FormView(form frm.Form, isPreview bool) templ.Component {
|
|||
var templ_7745c5c3_Var118 string
|
||||
templ_7745c5c3_Var118, templ_7745c5c3_Err = templ.JoinStringErrs(field.ID.String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/common.templ`, Line: 869, Col: 30}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/common.templ`, Line: 870, Col: 30}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var118))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
|
|
@ -2690,7 +2690,7 @@ func FormView(form frm.Form, isPreview bool) templ.Component {
|
|||
var templ_7745c5c3_Var119 string
|
||||
templ_7745c5c3_Var119, templ_7745c5c3_Err = templ.JoinStringErrs(field.ID.String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/common.templ`, Line: 870, Col: 32}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/common.templ`, Line: 871, Col: 32}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var119))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
|
|
@ -2703,7 +2703,7 @@ func FormView(form frm.Form, isPreview bool) templ.Component {
|
|||
var templ_7745c5c3_Var120 string
|
||||
templ_7745c5c3_Var120, templ_7745c5c3_Err = templ.JoinStringErrs(field.Placeholder)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/common.templ`, Line: 872, Col: 39}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/common.templ`, Line: 873, Col: 39}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var120))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
|
|
@ -2731,6 +2731,7 @@ func FormView(form frm.Form, isPreview bool) templ.Component {
|
|||
ID: field.ID.String(),
|
||||
Name: field.ID.String(),
|
||||
Label: field.Label,
|
||||
LabelClass: "text-slate-700 text-xl py-1",
|
||||
Required: field.Required,
|
||||
Placeholder: field.Placeholder,
|
||||
Multiple: field.Type == types.FormFieldTypeMultiSelect,
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@
|
|||
\"></form></div>
|
||||
<section id=\"builder-main-left-col\" class=\"flex flex-col gap-3 w-1/4 min-w-max h-full p-4 text-gray-800 rounded-md\">
|
||||
</section>
|
||||
<section id=\"builder-main-middle-col\" class=\"w-full h-screen overflow-y-auto overscroll-auto p-4 bg-gray-50 border-y-1\"><div class=\"mockup-browser border-base-300 border\"><div class=\"mockup-browser-toolbar\"><div class=\"input border-base-300 border\">https://your-form-domain.com/form</div></div><div class=\"flex h-full border-base-300 bg-sky-100 border-t px-4 py-8\">
|
||||
<section id=\"builder-main-middle-col\" class=\"w-full h-screen overflow-y-auto overscroll-auto p-4 bg-gray-50 border-y-1\"><div class=\"mockup-browser border-base-300 border overflow-visible\"><div class=\"mockup-browser-toolbar\"><div class=\"input border-base-300 border\">https://your-form-domain.com/form</div></div><div class=\"flex h-full border-base-300 bg-sky-100 border-t px-4 py-8\">
|
||||
</div></div></section>
|
||||
<div class=\"p-1 rounded-md flex items-center justify-center bg-blue-100 text-blue-900 ml-2\">
|
||||
</div>
|
||||
|
|
@ -154,12 +154,12 @@ Multi select
|
|||
<label class=\"w-full cursor-pointer truncate\">I unno</label>
|
||||
</label></div>
|
||||
<label for=\"
|
||||
\">
|
||||
\" class=\"text-slate-700 text-xl py-1\">
|
||||
|
||||
</label>
|
||||
<div id=\"form-viewer\" class=\"flex flex-col w-1/2 mx-auto justify-top pt-12\" data-hx-swap-oob=\"true\"><div id=\"form-metadata\" data-data=\"
|
||||
\"></div><h1 class=\"rounded relative mb-2 font-black text-5xl\">
|
||||
</h1><form _=\"on field_change(field_id, value) formValueChanged(field_id, value)\">
|
||||
\"></div><h1 class=\"rounded relative mb-2 font-black text-slate-700 text-5xl\">
|
||||
</h1><form _=\"on field_change(field_id, value) formValueChanged(field_id, value)\"><input name=\"subject_id\" type=\"hidden\">
|
||||
<div id=\"
|
||||
\"
|
||||
class=\"flex flex-col py-3 hidden\"
|
||||
|
|
@ -170,11 +170,11 @@ Multi select
|
|||
\" name=\"
|
||||
\" placeholder=\"
|
||||
\" type=\"text\" autocomplete=\"off\" _=\"
|
||||
\" class=\"border-gray-300 dark:border-gray-600 rounded-full bg-sky-50\">
|
||||
\" class=\"flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 shadow-sm focus:outline-none focus:ring-2 focus:border-2 focus:ring-opacity-100 px-4 py-2 text-base resize-y block rounded-xl bg-sky-50\">
|
||||
<textarea id=\"
|
||||
\" name=\"
|
||||
\" class=\"flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 shadow-sm focus:outline-none focus:ring-2 focus:border-transparent focus:ring-opacity-100 px-4 py-2 text-base resize-y block rounded-xl bg-sky-50\" placeholder=\"
|
||||
\" autocomplete=\"off\" style=\"--tw-ring-color: #3B82F6;\" _=\"
|
||||
\" autocomplete=\"off\" _=\"
|
||||
\" rows=\"3\"></textarea>
|
||||
</div>
|
||||
<div class=\"py-3\"></div>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Loading…
Reference in a new issue