Skip to content

Commit

Permalink
Fix date time and complex datation validations (catima#546)
Browse files Browse the repository at this point in the history
* fix date time and complex datation validations

* move errors from base to attrib and display them on their correct place

* move errors into react components

* fix translations

* update validation to reflect react component

* fix translation

* fix back and front validation to validate both dates

* fix back and front validations

---------

Co-authored-by: Manuel Wegria <[email protected]>
  • Loading branch information
abstracts33d and Manuel Wegria authored May 16, 2024
1 parent f3d54eb commit ae20cfc
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ const ComplexDatationInput = (props) => {
choiceSets: choiceSetsProps,
selectedFormat: selectedFormatProps,
fieldUuid,
componentPolicies
componentPolicies,
errors
} = props

const [choiceSets, setChoiceSets] = useState(choiceSetsProps)
Expand Down Expand Up @@ -110,9 +111,12 @@ const ComplexDatationInput = (props) => {
setToState(to);
}, [granularity])

let dateValid = isCurrentFormatValid();
let errorStl = dateValid ? {} : {border: "2px solid #f00"};
let errorMsg = dateValid ? "" : "Invalid value";
let fromDateValid = isCurrentFormatValid('from');
let toDateValid = isCurrentFormatValid('to');
let errorStl = {
"from": fromDateValid ? {} : {border: "2px solid #f00"},
"to": toDateValid ? {} : {border: "2px solid #f00"}
};
let fmt = getFieldOptions().format;

function _handleChangeBC(input) {
Expand Down Expand Up @@ -240,17 +244,18 @@ const ComplexDatationInput = (props) => {
});
}

function getCurrentFormat() {
function getCurrentFormat(input) {
let d = getData();
let f = getFieldOptions().format;
return f.split('').map(function (k) {
return d['from'][k] ? k : d['to'][k] ? k : '';
return d[input][k] ? k : '';
}).join('')
}

function isCurrentFormatValid() {
let current = getCurrentFormat();
function isCurrentFormatValid(input) {
let current = getCurrentFormat(input);
if (current === '' && !isRequired) return true; // allow empty value if field is not required
if (current === '' && isRequired && getCurrentFormat(input === "from" ? "to" : "from") !== '') return true;
let allowed = getAllowedFormats();
return allowed.indexOf(current) > -1;
}
Expand All @@ -274,13 +279,13 @@ const ComplexDatationInput = (props) => {
</div>
) : null}
{fmt.includes('D') ? (
<input style={errorStl} type="number" min="0" max="31" className="input-2 form-control"
<input style={errorStl[input]} type="number" min="0" max="31" className="input-2 form-control"
value={input === 'from' ? fromState.D : toState.D}
onChange={_handleChangeDay(input)}/>
) : null
}
{fmt.includes('M') ? (
<select style={errorStl} className="form-control" value={input === 'from' ? fromState.M : toState.M}
<select style={errorStl[input]} className="form-control" value={input === 'from' ? fromState.M : toState.M}
onChange={_handleChangeMonth(input)}>
<option value=""></option>
<option value="1">
Expand Down Expand Up @@ -322,26 +327,26 @@ const ComplexDatationInput = (props) => {
</select>) : null
}
{fmt.includes('Y') ? (
<input style={errorStl} className="input-4 margin-right form-control"
<input style={errorStl[input]} className="input-4 margin-right form-control"
type="number" min="0"
value={input === 'from' ? fromState.Y : toState.Y}
onChange={_handleChangeYear(input)}/>
) : null
}
{fmt.includes('h') ? (
<input style={errorStl} min="0" max="23" type="number" className="input-2 form-control"
<input style={errorStl[input]} min="0" max="23" type="number" className="input-2 form-control"
value={input === 'from' ? fromState.h : toState.h}
onChange={_handleChangeHours(input)}/>
) : null
}
{fmt.includes('m') ? (
<input style={errorStl} min="0" max="59" type="number" className="input-2 form-control"
<input style={errorStl[input]} min="0" max="59" type="number" className="input-2 form-control"
value={input === 'from' ? fromState.m : toState.m}
onChange={_handleChangeMinutes(input)}/>
) : null
}
{fmt.includes('s') ? (
<input style={errorStl} min="0" max="59" type="number" className="input-2 form-control"
<input style={errorStl[input]} min="0" max="59" type="number" className="input-2 form-control"
value={input === 'from' ? fromState.s : toState.s}
onChange={_handleChangeSeconds(input)}/>
) : null
Expand All @@ -353,21 +358,21 @@ const ComplexDatationInput = (props) => {
)
}

if (!fromState || !toState) return ""
if (!fromState || !toState) return ""
return (
<div>
<div>{renderAllowedFormatsSelector()}</div>
{selectedFormat === 'date_time' && (
<div>
<div>{renderDateTimeInput('from')}</div>
<div>{renderDateTimeInput('to')}</div>
<span className="error helptext">{errorMsg}</span>
</div>
<div>
<div>{renderDateTimeInput('from', input)}</div>
<div>{renderDateTimeInput('to', input)}</div>
<div className="base-errors">{errors?.filter(e => e.field === input.split("#item_")[1].split("_json")[0])?.map(e => e.message)?.join(',')}</div>
</div>
)}
{selectedFormat === 'datation_choice' && (
<RenderChoiceSetList
choiceSets={choiceSets}
locales={locales}
{selectedFormat === 'datation_choice' && (
<RenderChoiceSetList
choiceSets={choiceSets}
locales={locales}
getData={getData}
setData={setData}
setChoiceData={setChoiceData}
Expand Down Expand Up @@ -777,7 +782,6 @@ const ModalForm = (props) => {
<div className="base-errors">
{errorMsg}
</div>

</div>
</div>
<div className="modal-footer">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ const DateTimeInput = (props) => {
const {
input,
allowBC,
preventNegativeInput
preventNegativeInput,
errors
} = props

const [state, setState] = useState(false)
Expand Down Expand Up @@ -39,7 +40,6 @@ const DateTimeInput = (props) => {

let dateValid = isCurrentFormatValid();
let errorStl = dateValid ? {} : {border: "2px solid #f00"};
let errorMsg = dateValid ? "" : "Invalid value";
let fmt = getFieldOptions().format;

function _handleChangeDay(e) {
Expand Down Expand Up @@ -218,7 +218,8 @@ const DateTimeInput = (props) => {
</select>) : null
}
{fmt.includes('Y') ? (
<input style={errorStl} type="number" min={preventNegativeInput ? "0" : "" } className="input-4 margin-right form-control" value={state.Y}
<input style={errorStl} type="number" min={preventNegativeInput ? "0" : ""}
className="input-4 margin-right form-control" value={state.Y}
onChange={_handleChangeYear}/>
) : null
}
Expand All @@ -238,7 +239,7 @@ const DateTimeInput = (props) => {
) : null
}
</div>
<span className="error helptext">{errorMsg}</span>
<div className="base-errors">{errors?.filter(e => e.field === input.split("#item_")[1].split("_json")[0])?.map(e => e.message)?.join(',')}</div>
</div>
);
};
Expand Down
25 changes: 21 additions & 4 deletions app/models/field/complex_datation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,13 @@ def edit_props(item)
componentPolicies:
{
modal: "super-editor"
},
errors: item[:item].errors.map do |error|
{
message: error.message,
field: error.attribute
}
end
}
end

Expand Down Expand Up @@ -276,18 +282,29 @@ class ComplexDatationValidator < ActiveModel::Validator
def validate(record)
attrib = Array.wrap(options[:attributes]).first
value = record.public_send(attrib)
field = Field.find_by(uuid: attrib)

return if value.blank?

return if value['selected_format'] != "date_time"
to_value_empty = value['to'].keys.reject{|k| k=="BC"}.all? { |key| value['to'][key].blank? || value['to'][key].nil? }
from_value_empty = value['from'].keys.reject{|k| k=="BC"}.all? { |key| value['from'][key].blank? || value['from'][key].nil? }
return if to_value_empty && from_value_empty && !field.required

from_date_is_positive = value['from'].compact.except("BC").all? { |_, v| v.to_i >= 0 }
if to_value_empty && from_value_empty && field.required
record.errors.add(attrib, I18n.t('activerecord.errors.models.item.attributes.base.cant_be_blank'))
return
end

from_date_is_positive = value['from'].compact.except("BC").all? { |_, v| v.to_i >= 0 }
to_date_is_positive = value['to'].compact.except("BC").all? { |_, v| v.to_i >= 0 }

return if to_date_is_positive && from_date_is_positive
allowed_formats = Field::ComplexDatation::FORMATS.select{|f| field.format.include?(f) || field.format == f}

current_from_format = field.format.chars.map {|char| value['from'][char].blank? || value['from'][char].nil? ? nil : char}.compact.join
current_to_format = field.format.chars.map {|char| value['to'][char].blank? || value['to'][char].nil? ? nil : char}.compact.join

record.errors.add(:base, :negative_dates)
record.errors.add(attrib, I18n.t('activerecord.errors.models.item.attributes.base.wrong_format', field_format: allowed_formats)) unless (allowed_formats.include?(current_from_format) || from_value_empty) && (allowed_formats.include?(current_to_format) || to_value_empty)
record.errors.add(attrib, :negative_dates) if !to_date_is_positive || !from_date_is_positive
end
end
end
38 changes: 38 additions & 0 deletions app/models/field/date_time.rb
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,17 @@ def sql_type
"JSON"
end

def edit_props(item)
{
errors: item[:item].errors.map do |error|
{
message: error.message,
field: error.attribute
}
end
}
end

private

def transform_value(v)
Expand All @@ -182,4 +193,31 @@ def coerce_to_array(values)
array << values[key]
end.compact
end

def build_validators
[DateTimeValidator]
end

class DateTimeValidator < ActiveModel::Validator
def validate(record)
attrib = Array.wrap(options[:attributes]).first
value = record.public_send(attrib)
field = Field.find_by(uuid: attrib)

return if value.blank?
return if value.is_a?(Hash) && value.has_key?("raw_value")
return if value.keys.all? { |key| value[key].blank? || value[key].nil? } && !field.required

if value.keys.all? { |key| value[key].blank? || value[key].nil? } && field.required
record.errors.add(attrib, I18n.t('activerecord.errors.models.item.attributes.base.cant_be_blank'))
return
end

allowed_formats = Field::DateTime::FORMATS.select{|f| field.format.include?(f) || field.format == f}
current_format = field.format.chars.map {|char| value[char].blank? || value[char].nil? ? nil : char}.compact.join

record.errors.add(attrib, I18n.t('activerecord.errors.models.item.attributes.base.wrong_format', field_format: allowed_formats)) unless allowed_formats.include?(current_format)
end
end
end

2 changes: 2 additions & 0 deletions config/locales/app/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ en:
item:
attributes:
base:
cant_be_blank: "Must be present"
negative_dates: "Negative dates must be entered with the option before Jesus Christ (if activated)"
wrong_format: "Do not respect the formats %{field_format}"
field/choice_set:
attributes:
choice_set_id:
Expand Down
2 changes: 2 additions & 0 deletions config/locales/app/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ fr:
item:
attributes:
base:
cant_be_blank: "Doit être rempli(e)"
negative_dates: "Les dates négatives doivent être saisies avec l'option avant Jesus Christ (si activée)"
wrong_format: "Ne respecte pas les formats %{field_format}"
field/choice_set:
attributes:
choice_set_id:
Expand Down

0 comments on commit ae20cfc

Please sign in to comment.