Skip to content

Commit

Permalink
frontend: add category specific fields
Browse files Browse the repository at this point in the history
  • Loading branch information
Synar committed Sep 14, 2024
1 parent 016f29c commit ae90e08
Show file tree
Hide file tree
Showing 14 changed files with 157 additions and 60 deletions.
24 changes: 16 additions & 8 deletions backend/src/models/comment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub struct PublicNewComment {
pub author: String,
pub text: String,
pub data: Value,
pub entity_category_id: Uuid,
}

#[derive(FromRow, Deserialize, Serialize, ToSchema, Debug)]
Expand Down Expand Up @@ -45,7 +46,9 @@ impl PublicComment {
conn: &mut PgConnection,
) -> Result<PublicComment, AppError> {
let family = Family::get_from_entity(comment.entity_id, conn).await?;
family.comment_form.validate_data(&comment.data)?;
family
.comment_form
.validate_data(&comment.data, comment.entity_category_id)?;

sqlx::query_as!(
PublicComment,
Expand Down Expand Up @@ -105,7 +108,7 @@ pub struct AdminComment {
pub moderated: bool,
pub version: i32,
pub entity_display_name: String,
pub category_id: Uuid,
pub entity_category_id: Uuid,
}

#[derive(FromRow, Deserialize, Serialize, ToSchema, Debug)]
Expand All @@ -128,6 +131,7 @@ pub struct AdminNewOrUpdateComment {
pub data: Value,
pub moderated: bool,
pub version: i32,
pub entity_category_id: Uuid,
}

impl AdminComment {
Expand All @@ -136,7 +140,9 @@ impl AdminComment {
conn: &mut PgConnection,
) -> Result<AdminComment, AppError> {
let family = Family::get_from_entity(new_comment.entity_id, conn).await?;
family.comment_form.validate_data(&new_comment.data)?;
family
.comment_form
.validate_data(&new_comment.data, new_comment.entity_category_id)?;

sqlx::query_as!(
AdminComment,
Expand All @@ -147,7 +153,7 @@ impl AdminComment {
RETURNING *
)
SELECT i.id, i.entity_id, i.author, i.text, i.data, i.created_at, i.updated_at, i.moderated, i.version,
display_name as entity_display_name, category_id
display_name as entity_display_name, category_id as entity_category_id
FROM inserted i
JOIN entities e
ON e.id = entity_id
Expand All @@ -169,7 +175,9 @@ impl AdminComment {
conn: &mut PgConnection,
) -> Result<AdminComment, AppError> {
let family = Family::get_from_entity(update.entity_id, conn).await?;
family.comment_form.validate_data(&update.data)?;
family
.comment_form
.validate_data(&update.data, update.entity_category_id)?;

sqlx::query_as!(
AdminComment,
Expand All @@ -187,7 +195,7 @@ impl AdminComment {
RETURNING *
)
SELECT i.id, i.entity_id, i.author, i.text, i.data, i.created_at, i.updated_at, i.moderated, i.version,
e.display_name as entity_display_name, e.category_id
e.display_name as entity_display_name, e.category_id as entity_category_id
FROM inserted i
JOIN entities e
ON e.id = entity_id
Expand All @@ -210,7 +218,7 @@ impl AdminComment {
AdminComment,
r#"
SELECT c.id, c.entity_id, c.author, c.text, c.data, c.created_at, c.updated_at, c.moderated, c.version,
e.display_name as entity_display_name, e.category_id
e.display_name as entity_display_name, e.category_id as entity_category_id
FROM comments c
JOIN entities e ON e.id = c.entity_id
WHERE c.id = $1
Expand Down Expand Up @@ -245,7 +253,7 @@ impl AdminComment {
AdminComment,
r#"
SELECT c.id, c.entity_id, c.author, c.text, c.data, c.created_at, c.updated_at, c.moderated, c.version,
e.display_name as entity_display_name, e.category_id
e.display_name as entity_display_name, e.category_id as entity_category_id
FROM comments c
JOIN entities e ON e.id = c.entity_id
WHERE entity_id = $1
Expand Down
12 changes: 9 additions & 3 deletions backend/src/models/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ impl PublicEntity {
conn: &mut PgConnection,
) -> Result<PublicEntity, AppError> {
let family = Family::get_from_category(entity.category_id, conn).await?;
family.entity_form.validate_data(&entity.data)?;
family
.entity_form
.validate_data(&entity.data, entity.category_id)?;

let locations = to_value(entity.locations).unwrap();

Expand Down Expand Up @@ -246,7 +248,9 @@ impl AdminEntity {

// Validate the new data against the form from the corresponding family
let family = Family::get_from_category(new_entity.category_id, &mut tx).await?;
family.entity_form.validate_data(&new_entity.data)?;
family
.entity_form
.validate_data(&new_entity.data, new_entity.category_id)?;

// Serialize locations to JSON
let locations = to_value(new_entity.locations).unwrap();
Expand Down Expand Up @@ -327,7 +331,9 @@ impl AdminEntity {

// Validate the new data against the form from the corresponding family
let family = Family::get_from_category(update.category_id, &mut tx).await?;
family.entity_form.validate_data(&update.data)?;
family
.entity_form
.validate_data(&update.data, update.category_id)?;

// Serialize locations to JSON
let locations = to_value(update.locations).unwrap();
Expand Down
48 changes: 30 additions & 18 deletions backend/src/models/family.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub struct Field {
/// The type of the field
pub field_type: FieldType,

/// Used to store detail about the field that relevent
/// Used to store detail about the field that relevant
/// only for the frontend. For instance, if the field is an enum
/// use it to store possible values. If it is a SingleLineText, specify
/// if it's an email, a phone number, etc...
Expand All @@ -55,7 +55,7 @@ pub struct Field {
pub indexed: bool,

/// Sets if the field is indexed, the field must be indexed for this setting to be used.
/// Privatly indexed means only administrators can constraint on this field.
/// Privately indexed means only administrators can constraint on this field.
/// It only works for EnumSingleOption and EnumMultiOption
pub privately_indexed: bool,

Expand All @@ -73,6 +73,9 @@ pub struct Field {

/// The weight of the field in the display (when displayed, ordered by weight)
pub display_weight: u8,

/// The categories this field is restricted to, if any
pub categories: Option<Vec<Uuid>>,
}

#[derive(Deserialize, Serialize, ToSchema, Debug)]
Expand Down Expand Up @@ -118,9 +121,9 @@ impl Form {
Ok(())
}

pub fn validate_data(&self, data: &Value) -> Result<(), AppError> {
pub fn validate_data(&self, data: &Value, entity_category: Uuid) -> Result<(), AppError> {
for field in &self.fields {
field.validate_data(data.get(&field.key))?;
field.validate_data(data.get(&field.key), entity_category)?;
}
Ok(())
}
Expand All @@ -141,9 +144,18 @@ impl Field {
Ok(())
}

fn validate_data(&self, value: Option<&Value>) -> Result<(), AppError> {
let value = match value {
None if self.mandatory => {
fn validate_data(
&self,
field_value: Option<&Value>,
entity_category: Uuid,
) -> Result<(), AppError> {
let field_required = self.mandatory
&& self.categories.as_ref().map_or(true, |categories| {
categories.iter().any(|&c| c == entity_category)
});

let field_value = match field_value {
None if field_required => {
return Err(AppError::Validation(format!(
"Mandatory field {} is missing",
self.key
Expand All @@ -153,8 +165,8 @@ impl Field {
Some(value) => value,
};

if value.is_null() {
if self.mandatory {
if field_value.is_null() {
if field_required {
return Err(AppError::Validation(format!(
"Mandatory field {} is missing",
self.key
Expand All @@ -166,11 +178,11 @@ impl Field {

match &self.field_type {
FieldType::SingleLineText | FieldType::MultiLineText | FieldType::RichText => {
let str_value = value.as_str().ok_or_else(|| {
let str_value = field_value.as_str().ok_or_else(|| {
AppError::Validation(format!("Field {} is not a string", self.key))
})?;

if self.mandatory && str_value.is_empty() {
if field_required && str_value.is_empty() {
return Err(AppError::Validation(format!(
"Mandatory field {} is empty",
self.key
Expand All @@ -179,11 +191,11 @@ impl Field {
}

FieldType::Number | FieldType::DiscreteScore => {
let num_value = value.as_f64().ok_or_else(|| {
let num_value = field_value.as_f64().ok_or_else(|| {
AppError::Validation(format!("Field {} is not a number", self.key))
})?;

if self.mandatory && num_value.is_nan() {
if field_required && num_value.is_nan() {
return Err(AppError::Validation(format!(
"Mandatory field {} is not a valid number",
self.key
Expand All @@ -192,17 +204,17 @@ impl Field {
}

FieldType::Boolean => {
value.as_bool().ok_or_else(|| {
field_value.as_bool().ok_or_else(|| {
AppError::Validation(format!("Field {} is not a boolean", self.key))
})?;
}

FieldType::Date => {
let str_value = value.as_str().ok_or_else(|| {
let str_value = field_value.as_str().ok_or_else(|| {
AppError::Validation(format!("Field {} is not a string", self.key))
})?;

if self.mandatory && str_value.is_empty() {
if field_required && str_value.is_empty() {
return Err(AppError::Validation(format!(
"Mandatory field {} is empty",
self.key
Expand All @@ -213,11 +225,11 @@ impl Field {
}

FieldType::EnumMultiOption | FieldType::EventList => {
let arr_value = value.as_array().ok_or_else(|| {
let arr_value = field_value.as_array().ok_or_else(|| {
AppError::Validation(format!("Field {} is not an array", self.key))
})?;

if self.mandatory && arr_value.is_empty() {
if field_required && arr_value.is_empty() {
return Err(AppError::Validation(format!(
"Mandatory field {} is empty",
self.key
Expand Down
5 changes: 4 additions & 1 deletion frontend/components/CommentsDisplayer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
/>

<ViewerCommonFormFields
:fields="props.commentFormFields"
:fields="props.commentFormFields
.filter(field => field.categories == null || field.categories.includes(props.entityCategoryId))"
:data="comment.data"
/>
</AccordionContent>
Expand All @@ -50,10 +51,12 @@ const props = defineProps<{
commentFormFields: FormField[]
comments: PublicComment[]
public: true
entityCategoryId: string
} | {
commentFormFields: FormField[]
comments: AdminComment[]
public: false
entityCategoryId: string
}>()
const emit = defineEmits<{
Expand Down
49 changes: 37 additions & 12 deletions frontend/components/admin/families/EditForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,17 @@
value="!"
/>
</span>
<span class="flex items-center gap-2">
<label :for="'help_' + index">Texte d'aide :</label>
<Textarea
:id="'help_' + index"
v-model="field.help"
auto-resize
rows="1"
:variant="hasFieldAttributeBeenEdited(index, 'help') ? 'filled': 'outlined'"
class="flex grow"
/>
</span>
</div>

<div class="flex flex-col gap-4">
Expand Down Expand Up @@ -189,23 +200,36 @@
v-model="field.privately_indexed"
:label="'Filtrage admin uniquement'"
:variant="hasFieldAttributeBeenEdited(index, 'privately_indexed')"
:disabled="display_indexes[field.key]=='notDisplayed'"
:disabled="display_indexes[field.key]=='notDisplayed' || !field.indexed"
@update:model-value="onIndexableChange(field)"
/>
</span>
<span class="flex items-center gap-6">
<AdminInputSwitchField
:id="'is_categories_restricted_' + index"
:model-value="!(field.categories == null)"
:label="'Restriction par catégories'"
:variant="hasFieldAttributeBeenEdited(index, 'categories')"
@update:model-value="(is_categories_restricted: boolean) => {
if (is_categories_restricted) { field.categories = [] }
else { field.categories = null }
}"
/>
<MultiSelect
v-if="!(field.categories == null)"
:id="'field_type_' + index"
v-model="field.categories"
:max-selected-labels="1"
selected-items-label="{0} catégories selectionnées"
class="w-full"
:options="props.categories"
option-label="title"
option-value="id"
/>
</span>
</div>

</span>

<span class="flex items-center gap-2 mr-4">
<label :for="'help_' + index">Texte d'aide :</label>
<InputText
:id="'help_' + index"
v-model="field.help"
class="flex grow"
:variant="hasFieldAttributeBeenEdited(index, 'help') ? 'filled': 'outlined'"
/>
</span>
</Fieldset>
<div class="flex justify-center">
<Button
Expand Down Expand Up @@ -521,11 +545,12 @@
<script setup lang="ts">
import type { ConfirmationOptions } from 'primevue/confirmationoptions'
import type { FormField, StringFieldTypeMetadata, OptionsFieldTypeMetadata, EventsFieldTypeMetadata, FieldTypeMetadataEnum } from '~/lib'
import type { FormField, StringFieldTypeMetadata, OptionsFieldTypeMetadata, EventsFieldTypeMetadata, FieldTypeMetadataEnum, Category } from '~/lib'
const props = defineProps<{
kind: 'entity' | 'comment'
kindName: string
categories: Category[]
originalFormFields: FormField[]
onSaveCallback: (editedFormFields: FormField[]) => Promise<{ error: Error | undefined }>
}>()
Expand Down
4 changes: 3 additions & 1 deletion frontend/components/viewer/CommentAddForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@
>
<template v-if="page < page_count + 1">
<FormDynamicField
v-for="field in commentFieldsSortedByPage(page)"
v-for="field in commentFieldsSortedByPage(page)
.filter(field => field.categories == null || field.categories.includes(props.entity.category_id))"
:key="field.key"
v-model:field-content="(editedComment!.data as EntityOrCommentData)[field.key]"
:form-field="(field as FormField)"
Expand Down Expand Up @@ -146,6 +147,7 @@ function reset_refs(new_entity_id: string) {
data: {},
entity_id: new_entity_id,
text: '',
entity_category_id: props.entity.category_id,
}
curr_page.value = 0
page_count.value = Math.max(0, ...props.family.comment_form.fields.map(field => field.form_page))
Expand Down
Loading

0 comments on commit ae90e08

Please sign in to comment.