Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 9 additions & 7 deletions app/controllers/candidate/lot_selections_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ class LotSelectionsController < Candidate::ApplicationController

def show
@presenter = MarketApplicationPresenter.new(@market_application)
@editing = !@presenter.lots_saved? || params[:edit].present?
end

def update
return complete_application if params[:final_submit].present?

policy = LotSelectionPolicy.new(@market_application, lot_ids_param)

unless policy.valid?
Expand All @@ -20,19 +23,17 @@ def update
end

@market_application.lot_ids = lot_ids_param

if params[:final_submit].present?
complete_application
else
redirect_to step_candidate_market_application_path(@market_application.identifier, :api_data_recovery_status)
end
redirect_to lot_selection_candidate_market_application_path(@market_application.identifier)
end

private

def find_market_application
@market_application = MarketApplication
.includes(:lots, public_market: :lots)
.includes(
{ lots: %i[market_type platform_market_type] },
public_market: { lots: %i[market_type platform_market_type] }
)
.find_by!(identifier: params[:identifier])
rescue ActiveRecord::RecordNotFound
render plain: "La candidature recherchée n'a pas été trouvée", status: :not_found
Expand All @@ -50,6 +51,7 @@ def lot_ids_param

def render_lot_selection_error(errors)
@errors = errors
@editing = true
@presenter = MarketApplicationPresenter.new(@market_application)
render :show, status: :unprocessable_content
end
Expand Down
52 changes: 27 additions & 25 deletions app/javascript/controllers/lot_selection_controller.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
static targets = ["checkbox", "checkboxGroup", "submitButton", "selectAllButton", "noLotsText", "progressCard", "selectedLotsCount", "limitError"]
static targets = ["checkbox", "checkboxGroup", "submitButton", "selectAllButton",
"noLotsText", "selectedLotsTags", "limitError"]
static values = { limit: Number, storageKey: String }

connect() {
Expand Down Expand Up @@ -48,6 +49,7 @@ export default class extends Controller {
const checked = this.checkboxTargets.filter(cb => cb.checked)
const hasChecked = checked.length > 0
const limitReached = checked.length >= this._limit()

this.submitButtonTargets.forEach(btn => { btn.disabled = !hasChecked })

this.checkboxTargets.forEach((cb, i) => {
Expand All @@ -69,39 +71,41 @@ export default class extends Controller {
: this.selectAllButtonTarget.dataset.selectText || "Tout sélectionner"
}

if (this.hasNoLotsTextTarget) {
this.noLotsTextTarget.hidden = hasChecked
}
this._renderSelectedLotsTags(checked)
this._persistSelection(checked)
}

if (this.hasProgressCardTarget) {
this.progressCardTarget.hidden = !hasChecked
}
_renderSelectedLotsTags(checked) {
if (!this.hasNoLotsTextTarget && !this.hasSelectedLotsTagsTarget) return

if (this.hasSelectedLotsCountTarget && hasChecked) {
const template = checked.length === 1
? this.selectedLotsCountTarget.dataset.one
: this.selectedLotsCountTarget.dataset.other
this.selectedLotsCountTarget.textContent = template.replace('%{count}', checked.length)
if (this.hasNoLotsTextTarget) {
this.noLotsTextTarget.hidden = checked.length > 0
}

this._persistSelection(checked)
if (this.hasSelectedLotsTagsTarget) {
this.selectedLotsTagsTarget.replaceChildren(
...checked.map(cb => {
const span = document.createElement("span")
span.className = "fr-tag"
span.style.background = "white"
const name = cb.dataset.lotName || ""
const type = cb.dataset.lotType
span.textContent = type ? `${name} - ${type}` : name
return span
})
)
}
}

_restoreSelectionFromStorage() {
if (!this.hasStorageKeyValue) {
return
}
if (!this.hasStorageKeyValue) return

const raw = localStorage.getItem(this.storageKeyValue)
if (!raw) {
return
}
if (!raw) return

try {
const selectedLotIds = JSON.parse(raw)
if (!Array.isArray(selectedLotIds)) {
return
}
if (!Array.isArray(selectedLotIds)) return

const selectedSet = new Set(selectedLotIds.map(String))
this.checkboxTargets.forEach((checkbox) => {
Expand All @@ -113,9 +117,7 @@ export default class extends Controller {
}

_persistSelection(checkedCheckboxes) {
if (!this.hasStorageKeyValue) {
return
}
if (!this.hasStorageKeyValue) return

const selectedLotIds = checkedCheckboxes.map(checkbox => String(checkbox.value))
localStorage.setItem(this.storageKeyValue, JSON.stringify(selectedLotIds))
Expand Down
13 changes: 11 additions & 2 deletions app/presenters/market_application_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def submitted_at
# === LOTS METHODS ===

def selected_lots
@selected_lots ||= @market_application.lots.ordered.to_a
@selected_lots ||= @market_application.lots.sort_by(&:position)
end

def public_market_lots
Expand Down Expand Up @@ -139,8 +139,17 @@ def lots_saved?
selected_lots.any?
end

def form_started?
responses_by_attribute_id.any?
end

def lot_type_label(lot)
code = lot.effective_market_type&.code || public_market.market_type_codes.first
code.present? ? I18n.t("market_types.#{code}", default: code.humanize) : nil
end

def cta_translation_key
lots_saved? ? 'candidate.lot_selection.modify' : 'candidate.lot_selection.prepare'
form_started? ? 'candidate.lot_selection.modify' : 'candidate.lot_selection.start'
end

# === PROGRESS METHODS ===
Expand Down
13 changes: 13 additions & 0 deletions app/views/candidate/lot_selections/_market_info_card.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<div class="fr-mb-2w fr-background-alt--grey" style="border: 1px solid var(--border-default-grey); padding: 20px 16px;">
<h2 class="fr-text--bold fr-mb-0" style="font-size: 1rem;">
<%= t('candidate.lot_selection.market_info_title') %>
</h2>
<hr class="fr-my-1w" style="border: none; border-top: 1px solid var(--border-default-grey);">
<%= render 'shared/sidebar_market_info',
buyer_label: t('candidate.lot_selection.buyer_label'),
market_name: public_market.name,
typology_label: t('candidate.lot_selection.market_type_label'),
market_types_label: presenter.market_types_label.presence || t('candidate.shared.no_market_type'),
deadline_label: t('candidate.lot_selection.deadline_label'),
deadline: l(public_market.deadline, format: '%d/%m/%Y %H:%M') %>
</div>
Loading