Skip to content

Commit d4b2710

Browse files
committed
1 parent 3fbb9d2 commit d4b2710

File tree

5 files changed

+73
-14
lines changed

5 files changed

+73
-14
lines changed

services/client/controllers/api/routes/form-entry.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ router.post('/:idOrName', json(), validate(schema.formIdOrNameSchema, {reqParts:
1414
}
1515
form = form.res;
1616

17-
const entrySchema = schema.formEntry?.[form.name]?.create;
17+
const entrySchema = schema.formEntry?.[form.name]?.[req.body?.original_form_entry_id ? 'update' : 'create'];
1818
if ( !entrySchema ) {
1919
throw new Error(`No form entry schema found for form: ${form.name}`);
2020
}
@@ -26,6 +26,7 @@ router.post('/:idOrName', json(), validate(schema.formIdOrNameSchema, {reqParts:
2626
logger.info('Form entry validated', req.context.logSignal, { formId: form.form_id});
2727
delete validated.data._formId;
2828

29+
// todo: if new versioning is added, check if same user as og
2930
const r = await models.formEntry.create(form.form_id, validated.data);
3031
if ( r.error ) {
3132
throw r.error;

services/client/controllers/api/utils/validation/schemas/form-entry.js

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,43 @@ function srPicklistItemsExist(field){
2626
}
2727
}
2828

29+
const srOriginalFormEntryExists = async (data, ctx) => {
30+
if ( !data.original_form_entry_id ) return;
31+
const existing = await models.formEntry.get(data.original_form_entry_id, data._formId);
32+
if (existing.error) {
33+
logger.error('Database error validating original form entry existence', { error: existing.error });
34+
ctx.addIssue({
35+
code: z.ZodIssueCode.custom,
36+
message: 'A database error occurred',
37+
fatal: true
38+
});
39+
return;
40+
}
41+
if ( !existing.res) {
42+
ctx.addIssue({
43+
code: z.ZodIssueCode.custom,
44+
message: 'Original form entry not found or does not belong to this form',
45+
path: ['original_form_entry_id']
46+
});
47+
}
48+
}
49+
2950
const instructionStatsBase = z.object({
3051
'_formId': z.string(),
3152
'participant-count': requiredNumber(),
32-
'instructor-session-type': requiredString().superRefine(srPicklistItemsExist('instructor-session-type'))
53+
'instructor-session-type': requiredString().superRefine(srPicklistItemsExist('instructor-session-type')),
54+
'department': requiredArray().superRefine(srPicklistItemsExist('department'))
3355
});
3456

35-
const instructionStatsCreate = instructionStatsBase.extend({
36-
'department': requiredArray().superRefine(srPicklistItemsExist('department')),
37-
});
57+
58+
const instructionStatsUpdate = instructionStatsBase.partial().extend({
59+
'original_form_entry_id': z.uuid()
60+
}).superRefine(srOriginalFormEntryExists);
3861

3962
export default {
4063

4164
'instruction-statistics': {
42-
'create': instructionStatsCreate
65+
'create': instructionStatsBase,
66+
'update': instructionStatsUpdate
4367
}
4468
};

services/client/dev/controllers/FormEntryController.js

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,11 @@ export default class FormEntryController {
201201
return html`<p>Form Not Found!</p>`;
202202
}
203203

204-
return template.render.call(this.host, this);
204+
return [
205+
this._renderFormVersionWarning(),
206+
this._renderFormEntrySummary(),
207+
template.render.call(this.host, this)
208+
];
205209
}
206210

207211
if ( this.hostIsField ) {
@@ -215,6 +219,25 @@ export default class FormEntryController {
215219
return html``;
216220
}
217221

222+
_renderFormEntrySummary(){
223+
if ( !this.formEntry ) return html``;
224+
return html`
225+
<div class="alert">
226+
<div><span class="bold primary">Submitted:</span><span> ${new Date(this.formEntry.created_at).toLocaleString()}</span></div>
227+
<div><span class="bold primary">Edited:</span><span> ${this.formEntry.form_entry_id !== this.formEntry.original_form_entry_id ? 'Yes' : 'No'}</span></div>
228+
</div>
229+
`;
230+
}
231+
232+
_renderFormVersionWarning(){
233+
if ( !this.formEntry || this.formEntry.is_latest_version ) return html``;
234+
return html`
235+
<div class="alert">
236+
There is a <a href="/form/${this.formNameOrId}/${this.formEntry.versions[this.formEntry.versions.length - 1]}">newer version</a> of this submission available.
237+
</div>
238+
`
239+
}
240+
218241
renderActionButtons(){
219242
if ( this.form?.is_archived ) return html``;
220243
return html`
@@ -231,20 +254,29 @@ export default class FormEntryController {
231254
}
232255

233256
async submit(){
234-
const r = await this.models.FormEntryModel.create(this.form.name, this.payload);
257+
const r = await this.models.FormEntryModel.create(this.form.name, {...this.payload, ...(this.formEntry ? { original_form_entry_id: this.formEntry?.original_form_entry_id } : {})});
235258
if ( r.state !== 'loaded' ) return;
236-
this.models.AppStateModel.showToast({text: 'Submission successful', type: 'success'});
237-
this.models.AppStateModel.refresh();
259+
if ( this.formEntry ){
260+
this.models.AppStateModel.showToast({text: 'Update successful', type: 'success'});
261+
this.models.AppStateModel.setLocation(`/form/${this.formNameOrId}/${r.payload.form_entry_id}`);
262+
} else {
263+
this.models.AppStateModel.showToast({text: 'Submission successful', type: 'success'});
264+
this.models.AppStateModel.refresh();
265+
}
238266
}
239267

240268
_onReset(){
241-
this.setPayload({});
269+
if ( this.formEntry?.fields ) {
270+
this.setPayload({...this.formEntry.fields});
271+
} else {
272+
this.setPayload({});
273+
}
242274
}
243275

244276
async _onAppStateUpdate(e) {
245277
await this.update(e);
246278
if ( this.formEntry?.fields ) {
247-
this.setPayload(this.formEntry.fields);
279+
this.setPayload({...this.formEntry.fields});
248280
} else {
249281
this.setPayload({});
250282
}

services/lib/cork/models/AppStateModel.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ class AppStateModelImpl extends AppStateModel {
9999
addErrorRequest(req) {
100100
if ( req.errorSettings?.suppressError ) return;
101101
if ( req?.payload?.error?.response?.status == 422 && !req?.errorSettings?.showValidationErrors ) return; // validation errors handled by form
102+
this.errorRequests = this.errorRequests.filter(r => r.payload.id !== req.payload.id);
102103
this.errorRequests.push(req);
103104
if ( this._errorVisible || this._showErrorTimer ) return;
104105

services/lib/models/form-entry.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ class FormEntry {
2121
try {
2222
await client.query('BEGIN');
2323

24-
const d = pgClient.prepareObjectForInsert({ form_id: formId});
24+
const d = pgClient.prepareObjectForInsert({ form_id: formId, original_form_entry_id: data.original_form_entry_id || null });
25+
delete data.original_form_entry_id;
2526
const sql = `INSERT INTO ${config.db.tables.formEntry} (${d.keysString}) VALUES (${d.placeholdersString}) RETURNING form_entry_id;`;
26-
let result = await client.query(sql, d.values);
27+
const result = await client.query(sql, d.values);
2728
const formEntryId = result.rows[0].form_entry_id;
2829

2930
for ( const [fieldName, fieldValue] of Object.entries(data) ) {

0 commit comments

Comments
 (0)