Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
4702616
Allow external_id param in API to create new terms
entantoencuanto Oct 16, 2025
2867e6f
Add new fields to mataro investments visualization config
entantoencuanto Oct 16, 2025
89f8cda
Allow configuring a list of allowed values in filters and configure f…
entantoencuanto Oct 28, 2025
4e07e1a
Small refactor to allow hidding the dropdown of a filter
entantoencuanto Oct 28, 2025
cf7018c
Remove filter
entantoencuanto Oct 28, 2025
99a1625
Remove useless filter
entantoencuanto Oct 28, 2025
22d175f
Remove detail fields
entantoencuanto Oct 28, 2025
75ced8e
Support up to 9 phases with proper gradient
entantoencuanto Oct 28, 2025
74c8800
Allow configuring vocabularies in plugins to return their terms in me…
entantoencuanto Nov 4, 2025
f502f9a
Add representation of tables including columns with vocabularies
entantoencuanto Nov 4, 2025
84752e7
Improve types detection
entantoencuanto Nov 4, 2025
e63c7a1
Change the width of columns depending on type
entantoencuanto Nov 4, 2025
bf4d1ce
Change mataro configuration
entantoencuanto Nov 4, 2025
0931926
Reorder items
entantoencuanto Nov 4, 2025
f27628a
Define 8 phases
entantoencuanto Nov 4, 2025
e73ffb7
Add new fields to mataro.conf
entantoencuanto Nov 13, 2025
2120290
Increase width of column of labels
entantoencuanto Nov 13, 2025
22fbf08
Refactor vocabulary configuration methods of custom fields
entantoencuanto Sep 9, 2026
32e77c0
Change serialization of custom fields vocabulary_terms
entantoencuanto Sep 9, 2026
1146a28
Add tests
entantoencuanto Sep 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 49 additions & 9 deletions app/assets/stylesheets/investments.scss
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@
}

&--cell-heading {
flex: 0 0 20%;
flex: 0 0 25%;
font-size: $f6;
font-weight: 700;
line-height: 19px;
Expand All @@ -254,6 +254,11 @@
line-height: 29px;
}

&--table-container {
margin-top: 1rem;
margin-bottom: 1rem;
}

&--inner-table-row {
display: flex;
align-items: baseline;
Expand All @@ -273,6 +278,41 @@
}
}

&--inner-table-header {
background-color: rgba($color_headers, 0.04);
padding: 0.5rem 0;
border-bottom: 2px solid $separator_dark;
margin-bottom: 0.5rem;

&:not(:first-child) {
margin-top: 0;
}
}

&--inner-table-cell {
flex: 1;
word-break: break-word;

&:not(:first-child) {
padding-left: 1rem;
}

&--date {
flex: 0 0 100px;
min-width: 100px;
}

&--vocabulary {
flex: 0 0 110px;
min-width: 110px;
}

&--text {
flex: 2;
min-width: 300px;
}
}

&--icon-text {
display: flex;
align-items: baseline;
Expand Down Expand Up @@ -307,16 +347,16 @@
> * {
padding: 1em 0;
border-bottom: 1px solid $separator_dark;
}

&:first-of-type {
padding-top: 0;
.gobierto-filter--hide-dropdown {
padding-top: 0;

.gobierto-vue-dropdown--trigger {
display: none;
.gobierto-vue-dropdown--trigger {
display: none;

& + * {
margin-top: 0;
}
& + * {
margin-top: 0;
}
}
}
Expand Down Expand Up @@ -606,7 +646,7 @@
}
}

$ranges-opacity: 10%, 25%, 40%, 60%, 75%, 90%, 100%;
$ranges-opacity: 13%, 25%, 38%, 50%, 63%, 75%, 88%, 100%;

&--item {
flex: 0 0 calc(100% / #{length($ranges-opacity)});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def load_vocabulary
end

def term_params
params.require(:term).permit(name_translations: {})
params.require(:term).permit(:external_id, name_translations: {})
end

def set_admin_by_session_or_token
Expand Down
6 changes: 3 additions & 3 deletions app/forms/gobierto_admin/gobierto_common/custom_field_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class CustomFieldForm < BaseForm
:multiple
)

delegate :persisted?, :has_vocabulary?, :allow_multiple?, to: :custom_field
delegate :persisted?, :has_vocabulary?, :requires_vocabulary?, :allow_multiple?, to: :custom_field

validates :name_translations, :site, :klass, :field_type, presence: true
validate :instance_type_is_enabled
Expand Down Expand Up @@ -100,7 +100,7 @@ def options
@options ||= {}.tap do |opts|
opts[:configuration] ||= {}
opts.merge!(options_translations.except("new_option")) if has_options? && options_translations
if has_vocabulary?
if requires_vocabulary?
opts[:vocabulary_id] = vocabulary_id.to_i if vocabulary_id
opts[:configuration][:vocabulary_type] = vocabulary_type if vocabulary_type.present?
end
Expand Down Expand Up @@ -137,7 +137,7 @@ def types_with_multiple_setting
end

def has_options?
@has_options ||= !has_vocabulary? && custom_field.has_options?
@has_options ||= !requires_vocabulary? && custom_field.has_options?
end

def valid_resource_name?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,22 @@
</div>

<!-- Table type -->
<div v-if="type === 'table'">

<div
v-if="type === 'table' && table && table.columns"
class="investments-project-main--table-container"
>
<!-- Table header with column titles -->
<div class="investments-project-main--inner-table-row investments-project-main--inner-table-header">
<div
v-for="column in table.columns"
:key="`header-${column.id}`"
:class="getCellClass(column)"
>
<strong>{{ translate(column.name_translations) }}</strong>
</div>
</div>
<!-- Table rows with data -->
<div
v-for="(v, i) in value"
:key="`${v}-${i}`"
Expand All @@ -14,19 +29,26 @@
<div
v-for="column in table.columns"
:key="`${column.id}-${v}`"
:class="getCellClass(column)"
>
<div v-if="column.filter === 'money'">
{{ v[column.id] | money }}
<div v-if="column.type === 'vocabulary'">
{{ getVocabularyValue(column.id, v[column.id]) }}
</div>
<div v-else-if="column.filter === 'date'">
<div v-else-if="column.type === 'date' || column.filter === 'date'">
{{ v[column.id] | date }}
</div>
<div v-else-if="column.type === 'money' || column.filter === 'money'">
{{ v[column.id] | money }}
</div>
<div v-else>
{{ v[column.id] }}
</div>
</div>
</div>
</div>
<div v-else-if="type === 'table' && (!table || !table.columns)">
<em>{{ value || "Table configuration missing" }}</em>
</div>

<!-- Icon type -->
<div v-else-if="type === 'icon'">
Expand Down Expand Up @@ -90,6 +112,51 @@ export default {
type: Object,
default: () => {}
}
},
data() {
return {
vocabularyLookup: {}
};
},
created() {
if (this.type === 'table' && this.table && this.table.vocabulary_terms) {
this.buildVocabularyLookup();
}
},
methods: {
buildVocabularyLookup() {
// Build a lookup dictionary from vocabulary_terms
// vocabulary_terms contains all terms for all vocabularies used in the table
if (Array.isArray(this.table.vocabulary_terms)) {
this.table.vocabulary_terms.forEach(term => {
// Map term id to translated name
this.vocabularyLookup[term.id] = this.translate(term.name_translations);
});
}
},
getVocabularyValue(columnId, value) {
// Look up the term by its ID
if (this.vocabularyLookup[value]) {
return this.vocabularyLookup[value];
}
// Fallback to original value if not found
return value || '-';
},
getCellClass(column) {
const baseClass = 'investments-project-main--inner-table-cell';
const type = column.type || column.filter || 'text';

// Map column types to CSS classes
const typeClasses = {
'date': `${baseClass} ${baseClass}--date`,
'vocabulary': `${baseClass} ${baseClass}--vocabulary`,
'money': `${baseClass} ${baseClass}--vocabulary`,
'text': `${baseClass} ${baseClass}--text`
};

// Return specific class or default to vocabulary width
return typeClasses[type] || `${baseClass} ${baseClass}--vocabulary`;
}
}
};
</script>
31 changes: 20 additions & 11 deletions app/javascript/gobierto_investments/webapp/conf/mataro.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,12 @@ export default {
maxZoom: 16
},
availableFilters: [
{
id: "date",
startKey: "data-inici",
endKey: "data-final",
},
{
id: "estat"
},
{
id: "any-estat"
id: "any-pla-inversions"//,
// allowedValues: ["2024", "2025"]
},
{
id: "tipus"
Expand All @@ -33,7 +29,10 @@ export default {
id: "tipus-projecte"
},
{
id: "import"
id: "zones"
},
{
id: "consells-territorials"
}
],
displayGalleryFieldTags: false,
Expand Down Expand Up @@ -76,6 +75,9 @@ export default {
{
type: "separator"
},
{
id: "data-inici-obra"
},
{
id: "data-inici-redaccio"
},
Expand All @@ -91,20 +93,27 @@ export default {
{
id: "data-final"
},
{
id: "data-final-obra"
},
{
type: "separator"
},
{
id: "import"
id: "import-adjudicacio-ambiva"
},
{
id: "import-adjudicacio"
id: "import-licitacio-ambiva"
},
{
id: "import-liquidacio"
id: "import-liquidacio-ambiva"
},
{
type: "separator"
},
{
id: "partida"
id: "estats",
type: "table"
}
]
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
:count="phase.count"
:budget="phase.budget | money"
:projects="phase.items"
:is-negative="i > 2"
:is-negative="i > 3"
/>
</div>
</template>
Expand Down
15 changes: 14 additions & 1 deletion app/javascript/gobierto_investments/webapp/mixins/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,24 @@ export const CommonsMixin = {
attr.field_type = "money"
}

// Extract table configuration from plugin_configuration if type is table
let table = element.table;
if (element.type === "table" && attr.options?.configuration?.plugin_configuration) {
table = attr.options.configuration.plugin_configuration;

// Add vocabulary_terms from metadata to table configuration
// The metadata provides vocabulary_terms for all vocabularies in vocabulary_ids
if (table && attr.vocabulary_terms) {
table.vocabulary_terms = attr.vocabulary_terms;
}
}

return {
...attr,
...element,
name: this.translate(attr.name_translations),
value: value
value: value,
table: table
};
},
setItem(element, metadata) {
Expand Down
6 changes: 4 additions & 2 deletions app/javascript/lib/shared/modules/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class Middleware {
const filters = [];

for (let index = 0; index < this.filters.length; index++) {
const { id: key, ...rest } = this.filters[index];
const { id: key, allowedValues, hideDropdown, ...rest } = this.filters[index];
const element = stats[key];
const { field_type: type, vocabulary_terms: options = [], name_translations: title = {} } = this.getAttributesByKey(key);

Expand All @@ -20,7 +20,9 @@ export class Middleware {
title: translate(title),
options: Array.isArray(options) ? options.map(opt => ({ ...opt, title: translate(opt.name_translations) })) : [],
type: type ? type : key,
key
key,
allowedValues,
hideDropdown
});
}

Expand Down
Loading