aboutsummaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/application.js3
-rw-r--r--app/assets/javascripts/forms.coffee19
-rw-r--r--app/assets/javascripts/i18n/extended.coffee24
-rw-r--r--app/assets/javascripts/main_menu.coffee44
-rw-r--r--app/assets/javascripts/select2.coffee17
-rw-r--r--app/assets/stylesheets/OpenLayers/custom.sass20
-rw-r--r--app/assets/stylesheets/_layout.sass1
-rw-r--r--app/assets/stylesheets/application.sass2
-rw-r--r--app/assets/stylesheets/base/_config.sass2
-rw-r--r--app/assets/stylesheets/base/_utilities.sass2
-rw-r--r--app/assets/stylesheets/components/_breadcrumb.sass5
-rw-r--r--app/assets/stylesheets/components/_buttons.sass45
-rw-r--r--app/assets/stylesheets/components/_color_selector.sass21
-rw-r--r--app/assets/stylesheets/components/_dropdown.sass22
-rw-r--r--app/assets/stylesheets/components/_forms.sass54
-rw-r--r--app/assets/stylesheets/components/_lists.sass5
-rw-r--r--app/assets/stylesheets/components/_main_nav.sass45
-rw-r--r--app/assets/stylesheets/components/_panels.sass1
-rw-r--r--app/assets/stylesheets/components/_referential_overview.sass346
-rw-r--r--app/assets/stylesheets/components/_referentials.sass4
-rw-r--r--app/assets/stylesheets/components/_select2.sass8
-rw-r--r--app/assets/stylesheets/components/_tables.sass35
-rw-r--r--app/assets/stylesheets/components/_toolbar.sass49
-rw-r--r--app/assets/stylesheets/modules/_jp_collection.sass230
-rw-r--r--app/assets/stylesheets/modules/_timetables.sass2
-rw-r--r--app/assets/stylesheets/modules/_vj_collection.sass6
-rw-r--r--app/assets/stylesheets/modules/import_messages.sass5
-rw-r--r--app/assets/stylesheets/typography/_sboiv.sass4
-rw-r--r--app/controllers/api_keys_controller.rb7
-rw-r--r--app/controllers/application_controller.rb19
-rw-r--r--app/controllers/autocomplete_purchase_windows_controller.rb12
-rw-r--r--app/controllers/autocomplete_stop_areas_controller.rb11
-rw-r--r--app/controllers/calendars_controller.rb75
-rw-r--r--app/controllers/companies_controller.rb10
-rw-r--r--app/controllers/compliance_check_messages_controller.rb34
-rw-r--r--app/controllers/compliance_check_sets_controller.rb22
-rw-r--r--app/controllers/compliance_control_sets_controller.rb50
-rw-r--r--app/controllers/compliance_controls_controller.rb6
-rw-r--r--app/controllers/concerns/activatable.rb11
-rw-r--r--app/controllers/concerns/feature_checker.rb42
-rw-r--r--app/controllers/concerns/ransack_date_filter.rb28
-rw-r--r--app/controllers/development_toolbar_controller.rb11
-rw-r--r--app/controllers/group_of_lines_controller.rb5
-rw-r--r--app/controllers/import_messages_controller.rb2
-rw-r--r--app/controllers/import_resources_controller.rb8
-rw-r--r--app/controllers/imports_controller.rb3
-rw-r--r--app/controllers/journey_patterns_collections_controller.rb67
-rw-r--r--app/controllers/line_referentials_controller.rb1
-rw-r--r--app/controllers/lines_controller.rb10
-rw-r--r--app/controllers/merges_controller.rb40
-rw-r--r--app/controllers/networks_controller.rb7
-rw-r--r--app/controllers/purchase_windows_controller.rb74
-rw-r--r--app/controllers/referential_companies_controller.rb10
-rw-r--r--app/controllers/referential_lines_controller.rb3
-rw-r--r--app/controllers/referential_networks_controller.rb7
-rw-r--r--app/controllers/referential_vehicle_journeys_controller.rb30
-rw-r--r--app/controllers/referentials_controller.rb29
-rw-r--r--app/controllers/routes_controller.rb5
-rw-r--r--app/controllers/routing_constraint_zones_controller.rb3
-rw-r--r--app/controllers/snapshots_controller.rb14
-rw-r--r--app/controllers/statuses_controller.rb20
-rw-r--r--app/controllers/stop_area_referentials_controller.rb1
-rw-r--r--app/controllers/stop_areas_controller.rb56
-rw-r--r--app/controllers/time_tables_controller.rb5
-rw-r--r--app/controllers/vehicle_journeys_controller.rb129
-rw-r--r--app/controllers/workbench_outputs_controller.rb9
-rw-r--r--app/controllers/workbenches_controller.rb3
-rw-r--r--app/decorators/api_key_decorator.rb30
-rw-r--r--app/decorators/calendar_decorator.rb23
-rw-r--r--app/decorators/company_decorator.rb52
-rw-r--r--app/decorators/compliance_check_decorator.rb8
-rw-r--r--app/decorators/compliance_check_set_decorator.rb21
-rw-r--r--app/decorators/compliance_control_decorator.rb51
-rw-r--r--app/decorators/compliance_control_set_decorator.rb43
-rw-r--r--app/decorators/import_decorator.rb42
-rw-r--r--app/decorators/import_resource_decorator.rb10
-rw-r--r--app/decorators/import_resources_decorator.rb2
-rw-r--r--app/decorators/line_decorator.rb105
-rw-r--r--app/decorators/network_decorator.rb46
-rw-r--r--app/decorators/purchase_window_decorator.rb28
-rw-r--r--app/decorators/referential_decorator.rb94
-rw-r--r--app/decorators/referential_line_decorator.rb73
-rw-r--r--app/decorators/referential_network_decorator.rb47
-rw-r--r--app/decorators/route_decorator.rb89
-rw-r--r--app/decorators/routing_constraint_zone_decorator.rb45
-rw-r--r--app/decorators/stop_area_decorator.rb69
-rw-r--r--app/decorators/stop_point_decorator.rb34
-rw-r--r--app/decorators/time_table_decorator.rb66
-rw-r--r--app/errors/table_lock_timeout_error.rb1
-rw-r--r--app/helpers/application_helper.rb21
-rw-r--r--app/helpers/breadcrumb_helper.rb9
-rw-r--r--app/helpers/common_helpers.rb26
-rw-r--r--app/helpers/compliance_check_resources_helper.rb12
-rw-r--r--app/helpers/compliance_check_sets_helper.rb6
-rw-r--r--app/helpers/compliance_control_sets_helper.rb3
-rw-r--r--app/helpers/imports_helper.rb14
-rw-r--r--app/helpers/links_helper.rb15
-rw-r--r--app/helpers/newapplication_helper.rb3
-rw-r--r--app/helpers/pagination_helper.rb2
-rw-r--r--app/helpers/referentials_helper.rb5
-rw-r--r--app/helpers/routes_helper.rb8
-rw-r--r--app/helpers/search_helper.rb15
-rw-r--r--app/helpers/stop_areas_helper.rb6
-rw-r--r--app/helpers/table_builder_helper.rb200
-rw-r--r--app/helpers/table_builder_helper/custom_links.rb7
-rw-r--r--app/helpers/table_builder_helper/url.rb5
-rw-r--r--app/helpers/time_tables_helper.rb2
-rw-r--r--app/helpers/vehicle_journeys_helper.rb24
-rw-r--r--app/inputs/color_select_input.rb44
-rw-r--r--app/javascript/date_filters/index.js2
-rw-r--r--app/javascript/date_filters/purchase_window.js5
-rw-r--r--app/javascript/helpers/master_slave.coffee16
-rw-r--r--app/javascript/helpers/routes_map.coffee161
-rw-r--r--app/javascript/helpers/save_button.js47
-rw-r--r--app/javascript/helpers/stop_area_header_manager.js56
-rw-r--r--app/javascript/journey_patterns/actions/index.js23
-rw-r--r--app/javascript/journey_patterns/components/ConfirmModal.js4
-rw-r--r--app/javascript/journey_patterns/components/CreateModal.js3
-rw-r--r--app/javascript/journey_patterns/components/EditModal.js16
-rw-r--r--app/javascript/journey_patterns/components/JourneyPattern.js131
-rw-r--r--app/javascript/journey_patterns/components/JourneyPatterns.js42
-rw-r--r--app/javascript/journey_patterns/components/Navigate.js3
-rw-r--r--app/javascript/journey_patterns/components/SaveJourneyPattern.js41
-rw-r--r--app/javascript/journey_patterns/containers/JourneyPatternList.js5
-rw-r--r--app/javascript/journey_patterns/reducers/journeyPatterns.js16
-rw-r--r--app/javascript/packs/calendars/edit.js74
-rw-r--r--app/javascript/packs/journey_patterns/index.js1
-rw-r--r--app/javascript/packs/referential_lines/show.js10
-rw-r--r--app/javascript/packs/referential_overview/overview.js1
-rw-r--r--app/javascript/packs/routes/edit.js6
-rw-r--r--app/javascript/packs/routes/show.js104
-rw-r--r--app/javascript/packs/stop_areas/new.js3
-rw-r--r--app/javascript/packs/vehicle_journeys/index.js12
-rw-r--r--app/javascript/referential_overview/index.coffee113
-rw-r--r--app/javascript/routes/components/App.js7
-rw-r--r--app/javascript/routes/components/BSelect2.js30
-rw-r--r--app/javascript/routes/components/OlMap.js27
-rw-r--r--app/javascript/routes/components/StopPoint.js16
-rw-r--r--app/javascript/routes/components/StopPointList.js16
-rw-r--r--app/javascript/routes/form_helper.js17
-rw-r--r--app/javascript/routes/reducers/stopPoints.js10
-rw-r--r--app/javascript/time_tables/actions/index.js27
-rw-r--r--app/javascript/time_tables/components/ConfirmModal.js14
-rw-r--r--app/javascript/time_tables/components/ErrorModal.js10
-rw-r--r--app/javascript/time_tables/components/ExceptionsInDay.js3
-rw-r--r--app/javascript/time_tables/components/Metas.js28
-rw-r--r--app/javascript/time_tables/components/Navigate.js9
-rw-r--r--app/javascript/time_tables/components/PeriodForm.js18
-rw-r--r--app/javascript/time_tables/components/PeriodManager.js3
-rw-r--r--app/javascript/time_tables/components/PeriodsInDay.js3
-rw-r--r--app/javascript/time_tables/components/SaveTimetable.js3
-rw-r--r--app/javascript/time_tables/components/TagsSelect2.js7
-rw-r--r--app/javascript/time_tables/components/TimeTableDay.js3
-rw-r--r--app/javascript/time_tables/components/Timetable.js11
-rw-r--r--app/javascript/time_tables/containers/App.js3
-rw-r--r--app/javascript/vehicle_journeys/actions/index.js156
-rw-r--r--app/javascript/vehicle_journeys/components/App.js3
-rw-r--r--app/javascript/vehicle_journeys/components/ConfirmModal.js9
-rw-r--r--app/javascript/vehicle_journeys/components/Filters.js28
-rw-r--r--app/javascript/vehicle_journeys/components/Navigate.js8
-rw-r--r--app/javascript/vehicle_journeys/components/SaveVehicleJourneys.js42
-rw-r--r--app/javascript/vehicle_journeys/components/ToggleArrivals.js8
-rw-r--r--app/javascript/vehicle_journeys/components/Tools.js28
-rw-r--r--app/javascript/vehicle_journeys/components/VehicleJourney.js70
-rw-r--r--app/javascript/vehicle_journeys/components/VehicleJourneys.js97
-rw-r--r--app/javascript/vehicle_journeys/components/tools/CreateModal.js46
-rw-r--r--app/javascript/vehicle_journeys/components/tools/CustomFieldsInputs.js50
-rw-r--r--app/javascript/vehicle_journeys/components/tools/DeleteVehicleJourneys.js4
-rw-r--r--app/javascript/vehicle_journeys/components/tools/DuplicateVehicleJourney.js3
-rw-r--r--app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js118
-rw-r--r--app/javascript/vehicle_journeys/components/tools/NotesEditVehicleJourney.js3
-rw-r--r--app/javascript/vehicle_journeys/components/tools/PurchaseWindowsEditVehicleJourney.js153
-rw-r--r--app/javascript/vehicle_journeys/components/tools/ShiftVehicleJourney.js3
-rw-r--r--app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js20
-rw-r--r--app/javascript/vehicle_journeys/components/tools/VehicleJourneyInfoButton.js30
-rw-r--r--app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js7
-rw-r--r--app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js141
-rw-r--r--app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js19
-rw-r--r--app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js5
-rw-r--r--app/javascript/vehicle_journeys/containers/Filters.js3
-rw-r--r--app/javascript/vehicle_journeys/containers/SaveVehicleJourneys.js7
-rw-r--r--app/javascript/vehicle_journeys/containers/VehicleJourneysList.js8
-rw-r--r--app/javascript/vehicle_journeys/containers/tools/AddVehicleJourney.js5
-rw-r--r--app/javascript/vehicle_journeys/containers/tools/PurchaseWindowsEditVehicleJourney.js38
-rw-r--r--app/javascript/vehicle_journeys/containers/tools/VehicleJourneyInfoButton.js20
-rw-r--r--app/javascript/vehicle_journeys/reducers/custom_fields.js6
-rw-r--r--app/javascript/vehicle_journeys/reducers/index.js9
-rw-r--r--app/javascript/vehicle_journeys/reducers/missions.js6
-rw-r--r--app/javascript/vehicle_journeys/reducers/modal.js76
-rw-r--r--app/javascript/vehicle_journeys/reducers/returnVehicleJourneys.js11
-rw-r--r--app/javascript/vehicle_journeys/reducers/vehicleJourneys.js83
-rw-r--r--app/jobs/mailer_job.rb3
-rw-r--r--app/models/calendar.rb185
-rw-r--r--app/models/calendar/period.rb7
-rw-r--r--app/models/chouette/area_type.rb55
-rw-r--r--app/models/chouette/company.rb2
-rw-r--r--app/models/chouette/journey_pattern.rb33
-rw-r--r--app/models/chouette/line.rb14
-rw-r--r--app/models/chouette/network.rb2
-rw-r--r--app/models/chouette/purchase_window.rb45
-rw-r--r--app/models/chouette/route.rb2
-rw-r--r--app/models/chouette/routing_constraint_zone.rb10
-rw-r--r--app/models/chouette/stop_area.rb77
-rw-r--r--app/models/chouette/time_table.rb254
-rw-r--r--app/models/chouette/time_table_period.rb7
-rw-r--r--app/models/chouette/vehicle_journey.rb106
-rw-r--r--app/models/chouette/vehicle_journey_at_stop.rb39
-rw-r--r--app/models/compliance_check_block.rb6
-rw-r--r--app/models/compliance_check_message_export.rb46
-rw-r--r--app/models/compliance_check_set.rb24
-rw-r--r--app/models/compliance_control.rb8
-rw-r--r--app/models/compliance_control_block.rb6
-rw-r--r--app/models/compliance_control_set.rb2
-rw-r--r--app/models/concerns/application_days_support.rb107
-rw-r--r--app/models/concerns/checksum_support.rb56
-rw-r--r--app/models/concerns/date_support.rb83
-rw-r--r--app/models/concerns/min_max_values_validation.rb6
-rw-r--r--app/models/concerns/objectid_support.rb5
-rw-r--r--app/models/concerns/period_support.rb80
-rw-r--r--app/models/concerns/timetable_support.rb149
-rw-r--r--app/models/custom_field.rb9
-rw-r--r--app/models/generic_attribute_control/min_max.rb6
-rw-r--r--app/models/generic_attribute_control/pattern.rb2
-rw-r--r--app/models/generic_attribute_control/uniqueness.rb2
-rw-r--r--app/models/import.rb11
-rw-r--r--app/models/import_message_export.rb4
-rw-r--r--app/models/line_control/route.rb2
-rw-r--r--app/models/merge.rb435
-rw-r--r--app/models/organisation.rb66
-rw-r--r--app/models/public_version.rb4
-rw-r--r--app/models/referential.rb188
-rw-r--r--app/models/referential_cloning.rb27
-rw-r--r--app/models/referential_suite.rb6
-rw-r--r--app/models/route_control/opposite_route.rb2
-rw-r--r--app/models/route_control/opposite_route_terminus.rb2
-rw-r--r--app/models/user.rb2
-rw-r--r--app/models/vehicle_journey_control/delta.rb5
-rw-r--r--app/models/vehicle_journey_control/speed.rb6
-rw-r--r--app/models/vehicle_journey_control/waiting_time.rb5
-rw-r--r--app/models/workbench.rb9
-rw-r--r--app/models/workgroup.rb20
-rw-r--r--app/policies/access_link_policy.rb6
-rw-r--r--app/policies/access_point_policy.rb6
-rw-r--r--app/policies/application_policy.rb14
-rw-r--r--app/policies/calendar_policy.rb19
-rw-r--r--app/policies/connection_link_policy.rb6
-rw-r--r--app/policies/journey_pattern_policy.rb6
-rw-r--r--app/policies/line_policy.rb15
-rw-r--r--app/policies/line_referential_policy.rb14
-rw-r--r--app/policies/merge_policy.rb15
-rw-r--r--app/policies/network_policy.rb11
-rw-r--r--app/policies/purchase_window_policy.rb20
-rw-r--r--app/policies/referential_policy.rb12
-rw-r--r--app/policies/route_policy.rb6
-rw-r--r--app/policies/routing_constraint_zone_policy.rb6
-rw-r--r--app/policies/stop_area_policy.rb16
-rw-r--r--app/policies/stop_area_referential_policy.rb14
-rw-r--r--app/policies/time_table_combination_policy.rb2
-rw-r--r--app/policies/time_table_policy.rb10
-rw-r--r--app/policies/vehicle_journey_policy.rb6
-rw-r--r--app/services/parent_import_notifier.rb15
-rw-r--r--app/services/parent_notifier.rb19
-rw-r--r--app/services/referential_overview.rb256
-rw-r--r--app/services/zip_service.rb31
-rw-r--r--app/uploaders/import_uploader.rb6
-rw-r--r--app/views/api/v1/journey_patterns/show.rabl14
-rw-r--r--app/views/autocomplete_purchase_windows/index.rabl12
-rw-r--r--app/views/autocomplete_stop_areas/around.rabl2
-rw-r--r--app/views/autocomplete_stop_areas/index.rabl5
-rw-r--r--app/views/calendar_mailer/created.html.slim4
-rw-r--r--app/views/calendar_mailer/updated.html.slim2
-rw-r--r--app/views/calendars/_filters.html.slim10
-rw-r--r--app/views/calendars/_form.html.slim53
-rw-r--r--app/views/calendars/_form_advanced.html.slim8
-rw-r--r--app/views/calendars/_form_simple.html.slim56
-rw-r--r--app/views/calendars/edit.html.slim9
-rw-r--r--app/views/calendars/index.html.slim8
-rw-r--r--app/views/calendars/month.rabl9
-rw-r--r--app/views/calendars/new.html.slim6
-rw-r--r--app/views/calendars/show.html.slim35
-rw-r--r--app/views/calendars/show.rabl22
-rw-r--r--app/views/companies/index.html.slim4
-rw-r--r--app/views/companies/show.html.slim18
-rw-r--r--app/views/compliance_check_sets/_filters.html.slim8
-rw-r--r--app/views/compliance_check_sets/executed.html.slim2
-rw-r--r--app/views/compliance_check_sets/index.html.slim17
-rw-r--r--app/views/compliance_check_sets/show.html.slim52
-rw-r--r--app/views/compliance_checks/_filters.html.slim8
-rw-r--r--app/views/compliance_control_sets/_filters.html.slim10
-rw-r--r--app/views/compliance_control_sets/index.html.slim6
-rw-r--r--app/views/compliance_control_sets/show.html.slim12
-rw-r--r--app/views/compliance_controls/_filters.html.slim8
-rw-r--r--app/views/compliance_controls/new.html.slim4
-rw-r--r--app/views/compliance_controls/show.html.slim6
-rw-r--r--app/views/dashboards/_dashboard.html.slim41
-rw-r--r--app/views/devise/invitations/edit.html.slim33
-rw-r--r--app/views/devise/mailer/invitation_instructions.fr.html.slim4
-rw-r--r--app/views/devise/passwords/edit.html.slim28
-rw-r--r--app/views/devise/passwords/new.html.slim24
-rw-r--r--app/views/devise/sessions/new.html.slim2
-rw-r--r--app/views/errors/forbidden.html.slim2
-rw-r--r--app/views/errors/server_error.html.slim2
-rw-r--r--app/views/import_resources/index.html.slim3
-rw-r--r--app/views/imports/_filters.html.slim6
-rw-r--r--app/views/imports/_import_messages.html.slim8
-rw-r--r--app/views/imports/index.html.slim3
-rw-r--r--app/views/imports/show.html.slim100
-rw-r--r--app/views/journey_patterns_collections/show.html.slim3
-rw-r--r--app/views/layouts/application.html.slim5
-rw-r--r--app/views/layouts/mailer.html.erb22
-rw-r--r--app/views/layouts/mailer.html.slim7
-rw-r--r--app/views/layouts/navigation/_main_nav_left_content_stif.html.slim2
-rw-r--r--app/views/layouts/navigation/_main_nav_top.html.slim8
-rw-r--r--app/views/layouts/navigation/_page_header.html.slim29
-rw-r--r--app/views/layouts/snapshots/actions_links.html.slim21
-rw-r--r--app/views/layouts/snapshots/default.html.slim19
-rw-r--r--app/views/line_footnotes/show.html.slim4
-rw-r--r--app/views/line_referentials/show.html.slim5
-rw-r--r--app/views/lines/_filters.html.slim10
-rw-r--r--app/views/lines/_form.html.slim3
-rw-r--r--app/views/lines/index.html.slim6
-rw-r--r--app/views/lines/show.html.slim10
-rw-r--r--app/views/merges/_form.html.slim12
-rw-r--r--app/views/merges/new.html.slim7
-rw-r--r--app/views/merges/show.html.slim17
-rw-r--r--app/views/networks/_form.html.slim10
-rw-r--r--app/views/networks/index.html.slim3
-rw-r--r--app/views/networks/show.html.slim16
-rw-r--r--app/views/purchase_windows/_date_value_fields.html.slim13
-rw-r--r--app/views/purchase_windows/_filters.html.slim15
-rw-r--r--app/views/purchase_windows/_form.html.slim29
-rw-r--r--app/views/purchase_windows/_period_fields.html.slim15
-rw-r--r--app/views/purchase_windows/edit.html.slim7
-rw-r--r--app/views/purchase_windows/index.html.slim41
-rw-r--r--app/views/purchase_windows/new.html.slim6
-rw-r--r--app/views/purchase_windows/show.html.slim12
-rw-r--r--app/views/referential_companies/index.html.slim4
-rw-r--r--app/views/referential_lines/_filters.html.slim4
-rw-r--r--app/views/referential_lines/show.html.slim21
-rw-r--r--app/views/referential_networks/index.html.slim3
-rw-r--r--app/views/referential_networks/show.html.slim9
-rw-r--r--app/views/referential_stop_areas/_form.html.slim2
-rw-r--r--app/views/referential_stop_areas/show.html.slim9
-rw-r--r--app/views/referential_vehicle_journeys/_filters.html.slim62
-rw-r--r--app/views/referential_vehicle_journeys/index.html.slim59
-rw-r--r--app/views/referentials/_filters.html.slim12
-rw-r--r--app/views/referentials/_form.html.slim8
-rw-r--r--app/views/referentials/_overview.html.slim75
-rw-r--r--app/views/referentials/new.html.slim2
-rw-r--r--app/views/referentials/show.html.slim37
-rw-r--r--app/views/routes/_form.html.slim2
-rw-r--r--app/views/routes/show.html.slim22
-rw-r--r--app/views/routing_constraint_zones/_filters.html.slim4
-rw-r--r--app/views/routing_constraint_zones/index.html.slim3
-rw-r--r--app/views/routing_constraint_zones/show.html.slim12
-rw-r--r--app/views/shared/_development_toolbar.html.slim53
-rw-r--r--app/views/stif/dashboards/_dashboard.html.slim9
-rw-r--r--app/views/stop_area_referentials/show.html.slim7
-rw-r--r--app/views/stop_areas/_filters.html.slim10
-rw-r--r--app/views/stop_areas/_form.html.slim30
-rw-r--r--app/views/stop_areas/autocomplete.rabl24
-rw-r--r--app/views/stop_areas/index.html.slim8
-rw-r--r--app/views/stop_areas/show.html.slim28
-rw-r--r--app/views/stop_points/_stop_point.html.slim4
-rw-r--r--app/views/time_tables/_filter.html.slim6
-rw-r--r--app/views/time_tables/_form.html.slim2
-rw-r--r--app/views/time_tables/_show_time_table.html.slim28
-rw-r--r--app/views/time_tables/edit.html.slim2
-rw-r--r--app/views/time_tables/index.html.slim6
-rw-r--r--app/views/time_tables/show.html.slim20
-rw-r--r--app/views/vehicle_journeys/index.html.slim19
-rw-r--r--app/views/vehicle_journeys/show.rabl23
-rw-r--r--app/views/workbench_outputs/show.html.slim40
-rw-r--r--app/views/workbenches/_filters.html.slim18
-rw-r--r--app/views/workbenches/show.html.slim15
-rw-r--r--app/workers/clean_up_worker.rb1
-rw-r--r--app/workers/line_referential_sync_worker.rb2
-rw-r--r--app/workers/merge_worker.rb7
-rw-r--r--app/workers/referential_cloning_worker.rb27
-rw-r--r--app/workers/stop_area_referential_sync_worker.rb2
-rw-r--r--app/workers/workbench_import_worker.rb92
-rw-r--r--app/workers/workbench_import_worker/object_state_updater.rb36
382 files changed, 7727 insertions, 2717 deletions
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 4c5aff22f..6a79f7e8e 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -22,3 +22,6 @@
//= require_directory .
// require('whatwg-fetch')
// require('babel-polyfill')
+//= require "i18n"
+//= require "i18n/extended"
+//= require "i18n/translations"
diff --git a/app/assets/javascripts/forms.coffee b/app/assets/javascripts/forms.coffee
index 12d82fef1..b7ae3c6ca 100644
--- a/app/assets/javascripts/forms.coffee
+++ b/app/assets/javascripts/forms.coffee
@@ -32,14 +32,25 @@ isEdge = !isIE && !!window.StyleMedia
@colorSelector = ->
$('.form-group .dropdown.color_selector').each ->
- selectedStatus = $(this).children('.dropdown-toggle').children('.fa-circle')
-
+ selectedStatusColor = $(this).children('.dropdown-toggle').children('.fa-circle')
+ selectedStatusLabel = $(this).children('.dropdown-toggle')
+ self = this
$(this).on 'click', "input[type='radio']", (e) ->
selectedValue = e.currentTarget.value
+ selectedText = $(e.currentTarget).parent()[0].textContent
+ if e.currentTarget.getAttribute("data-for")
+ hidden = $("[name=\"#{e.currentTarget.getAttribute("data-for")}\"]")
+
if selectedValue == ''
- $(selectedStatus).css('color', 'transparent')
+ $(selectedStatusColor).css('color', 'transparent')
+ $(selectedStatusLabel).contents().filter( -> this.nodeType == 3 ).filter(':first').text = ""
+ hidden?.val ""
else
- $(selectedStatus).css('color', selectedValue)
+ $(selectedStatusColor).css('color', selectedValue)
+ $(selectedStatusLabel).contents().filter( -> this.nodeType == 3 ).first().replaceWith selectedText
+ hidden?.val selectedValue
+
+ $(self).find('.dropdown-toggle').click()
$ ->
togglableFilter()
diff --git a/app/assets/javascripts/i18n/extended.coffee b/app/assets/javascripts/i18n/extended.coffee
new file mode 100644
index 000000000..aeb67bd09
--- /dev/null
+++ b/app/assets/javascripts/i18n/extended.coffee
@@ -0,0 +1,24 @@
+#= require i18n
+
+decorateI18n = (_i18n)->
+ _i18n.tc = (key, opts={}) ->
+ out = _i18n.t(key, opts)
+ out += " " if _i18n.locale == "fr"
+ out + ":"
+
+ _i18n.model_name = (model, opts={}) ->
+ last_key = if opts.plural then "other" else "one"
+ _i18n.t("activerecord.models.#{model}.#{last_key}")
+
+ _i18n.attribute_name = (model, attribute, opts={}) ->
+ _i18n.t("activerecord.attributes.#{model}.#{attribute}")
+
+ _i18n.enumerize = (enumerize, key, opts={}) ->
+ I18n.t("enumerize.#{enumerize}.#{key}")
+
+ _i18n
+
+module?.exports = decorateI18n
+
+if I18n?
+ decorateI18n(I18n)
diff --git a/app/assets/javascripts/main_menu.coffee b/app/assets/javascripts/main_menu.coffee
index a12c47576..e943f448a 100644
--- a/app/assets/javascripts/main_menu.coffee
+++ b/app/assets/javascripts/main_menu.coffee
@@ -1,12 +1,11 @@
$ ->
- link = []
+ stickyActions = []
ptitleCont = ""
$(document).on 'page:before-change', ->
- link = []
+ stickyActions = []
ptitleCont = ""
-
$el = $('#main_nav')
# Opening/closing left-side menu
$el.find('.openMenu').on 'click', (e) ->
@@ -24,34 +23,45 @@ $ ->
limit = 51
if $(window).scrollTop() >= limit
- if ($('.page-action .small').length > 0)
- data = $('.page-action .small')[0].innerHTML
+ if stickyActions.length == 0
+ if ($('.page-action .small').length > 0)
+ stickyActions.push
+ content: [
+ $('.page-action .small'),
+ $('.page-action .small').first().next()
+ ]
+ originalParent: $('.page-action .small').parent()
+
+ for action in $(".sticky-action, .sticky-actions")
+ stickyActions.push
+ class: "small",
+ content: [$(action)]
+ originalParent: $(action).parent()
- if ($(".page-title").length > 0)
+ if $(".page-title").length > 0
ptitleCont = $(".page-title").html()
- stickyContent = '<div class="sticky-content">'
- stickyContent += '<div class="sticky-ptitle">' + ptitleCont + '</div>'
- stickyContent += '<div class="sticky-paction"><div class="small">' + data + '</div></div>'
- stickyContent += '</div>'
+ stickyContent = $('<div class="sticky-content"></div>')
+ stickyContent.append $("<div class='sticky-ptitle'>#{ptitleCont}</div>")
+ stickyContent.append $('<div class="sticky-paction"></div>')
$('#main_nav').addClass 'sticky'
if $('#menu_top').find('.sticky-content').length == 0
if ptitleCont.length > 0
$('#menu_top').children('.menu-content').after(stickyContent)
- if link.length == 0
- link = $('.page-action .small').next()
-
- $('.sticky-paction .small').after(link)
+ for item in stickyActions
+ for child in item.content
+ child.appendTo $('.sticky-paction')
else
$('#main_nav').removeClass 'sticky'
if $('#menu_top').find('.sticky-content').length > 0
- if !$('.page-action').find('.formSubmitr').length
- $('.page-action .small').after(link)
+ for item in stickyActions
+ for child in item.content
+ child.appendTo item.originalParent
$('.sticky-content').remove()
- sticker();
+ sticker()
# Sticky behavior
$(document).on 'scroll', sticker
diff --git a/app/assets/javascripts/select2.coffee b/app/assets/javascripts/select2.coffee
index 2e3884d7f..05b73dc6b 100644
--- a/app/assets/javascripts/select2.coffee
+++ b/app/assets/javascripts/select2.coffee
@@ -9,18 +9,18 @@ bind_select2 = (el, cfg = {}) ->
target.select2 $.extend({}, default_cfg, cfg)
bind_select2_ajax = (el, cfg = {}) ->
- target = $(el)
+ _this = $(el)
cfg =
ajax:
data: (params) ->
- q:
- "#{target.data('term')}": params.term
- url: target.data('url'),
+ if _this.data('term')
+ { q: "#{_this.data('term')}": params.term }
+ else
+ { q: params.term }
+ url: _this.data('url'),
dataType: 'json',
delay: 125,
processResults: (data, params) -> results: data
- minimumInputLength: 1
- placeholder: target.data('select2ed-placeholder')
templateResult: (item) ->
item.text
templateSelection: (item) ->
@@ -28,6 +28,9 @@ bind_select2_ajax = (el, cfg = {}) ->
escapeMarkup: (markup) ->
markup
+ if _this.data('initvalue')
+ cfg["initSelection"] = (item, callback) -> callback(_this.data('initvalue'))
+
bind_select2(el, cfg)
@select_2 = ->
@@ -40,7 +43,5 @@ bind_select2_ajax = (el, cfg = {}) ->
$('select.form-control.tags').each ->
bind_select2(this, {tags: true})
-
-
$ ->
select_2()
diff --git a/app/assets/stylesheets/OpenLayers/custom.sass b/app/assets/stylesheets/OpenLayers/custom.sass
index 7a5b4baf1..fa874d924 100644
--- a/app/assets/stylesheets/OpenLayers/custom.sass
+++ b/app/assets/stylesheets/OpenLayers/custom.sass
@@ -2,6 +2,26 @@
.list-group-item &
margin-top: 15px
+ .routes-labels
+ padding: 0
+ display: flex
+ justify-content: space-between
+ flex-wrap: wrap
+ margin: 5px -5px
+ li
+ list-style-type: none
+ flex: 1 0 25%
+ border: 1px solid $lightgrey
+ padding: 5px
+ margin: 5px
+ border-radius: 5px
+ cursor: pointer
+ color: $blue
+ white-space: nowrap
+
+ &:hover
+ background: $orange
+ color: white
.ol-scale-line
background-color: transparent
diff --git a/app/assets/stylesheets/_layout.sass b/app/assets/stylesheets/_layout.sass
index b6b91b2a5..340467e77 100644
--- a/app/assets/stylesheets/_layout.sass
+++ b/app/assets/stylesheets/_layout.sass
@@ -28,6 +28,7 @@ body
// width: 75%
border-bottom: 1px solid rgba($blue, 0.5)
margin: 30px auto 45px auto
+ clear: both
.content_header
font-size: 2.2rem
diff --git a/app/assets/stylesheets/application.sass b/app/assets/stylesheets/application.sass
index 50f8b64cb..3f8467efe 100644
--- a/app/assets/stylesheets/application.sass
+++ b/app/assets/stylesheets/application.sass
@@ -19,3 +19,5 @@
@import 'modules/vj_collection'
@import 'modules/timetables'
@import 'modules/import_messages'
+
+@import 'flag-icon'
diff --git a/app/assets/stylesheets/base/_config.sass b/app/assets/stylesheets/base/_config.sass
index 65444479f..0fff1dd9c 100644
--- a/app/assets/stylesheets/base/_config.sass
+++ b/app/assets/stylesheets/base/_config.sass
@@ -16,6 +16,8 @@ $blue: #007fbb
$darkgrey: #4b4b4b
$grey: #a4a4a4
+$lightgrey: lighten($grey, 25)
+$lightergrey: lighten($lightgrey,5)
$green: #70b12b
$red: #da2f36
diff --git a/app/assets/stylesheets/base/_utilities.sass b/app/assets/stylesheets/base/_utilities.sass
index 24f2038f0..fbda5630d 100644
--- a/app/assets/stylesheets/base/_utilities.sass
+++ b/app/assets/stylesheets/base/_utilities.sass
@@ -44,7 +44,7 @@
// Empty zones
=emptyzone($col1, $col2)
- background-image: linear-gradient(-45deg, $col1 25%, $col2 25%, $col2 50%, $col1 50%, $col1 75%, $col2 75%, transparent)
+ background-image: linear-gradient(-45deg, $col1 25%, $col2 25%, $col2 50%, $col1 50%, $col1 75%, $col2 75%, $col2)
background-size: 40px 40px
.cellwrap
diff --git a/app/assets/stylesheets/components/_breadcrumb.sass b/app/assets/stylesheets/components/_breadcrumb.sass
index 62f167eb4..1b30ca42b 100644
--- a/app/assets/stylesheets/components/_breadcrumb.sass
+++ b/app/assets/stylesheets/components/_breadcrumb.sass
@@ -1,3 +1,6 @@
.breadcrumbs
+ white-space: nowrap
+ text-overflow: ellipsis
+ overflow: hidden
a
- color: white \ No newline at end of file
+ color: white
diff --git a/app/assets/stylesheets/components/_buttons.sass b/app/assets/stylesheets/components/_buttons.sass
index a59699383..2881cee3e 100644
--- a/app/assets/stylesheets/components/_buttons.sass
+++ b/app/assets/stylesheets/components/_buttons.sass
@@ -143,28 +143,49 @@ table, .table
border-radius: 0
box-shadow: 0 0 3px rgba($darkgrey, 0.25)
+ & > ul:not(:first-child)
+ position: relative
+ margin-top: 11px
+ &:before
+ content: ''
+ display: block
+ position: absolute
+ left: 15px
+ right: 15px
+ top: -6px
+ height: 1px
+ background-color: $grey
+
+ ul.dropdown-menu, .dropdown-menu > ul
+ padding: 0
+ margin: 0
> li > a, > li > button
padding: 5px 15px
+ white-space: nowrap
+ padding: 5px 15px
+ font-weight: normal
+ line-height: $line-height
+ display: block
+ font-size: 14px
+ &:hover, &:focus
+ text-decoration: none
+ background-color: whitesmoke
+ outline: none
> li.delete-action
> a, > button
display: block
position: relative
- margin-top: 11px
-
- &:before
- content: ''
- display: block
- position: absolute
- left: 15px
- right: 15px
- top: -6px
- height: 1px
- background-color: $grey
-
.fa:first-child
margin-right: 0.5em
+ & + li.delete-action
+ > a, > button
+ margin-top: 0
+ &:before
+ display: none
+
+
&.table-2entries .t2e-item
> .th
position: relative
diff --git a/app/assets/stylesheets/components/_color_selector.sass b/app/assets/stylesheets/components/_color_selector.sass
new file mode 100644
index 000000000..07bfa0c80
--- /dev/null
+++ b/app/assets/stylesheets/components/_color_selector.sass
@@ -0,0 +1,21 @@
+select.color_selector
+ option[value='#9B9B9B']
+ background-color: #9B9B9B
+ option[value='#FFA070']
+ background-color: #FFA070
+ option[value='#C67300']
+ background-color: #C67300
+ option[value='#7F551B']
+ background-color: #7F551B
+ option[value='#41CCE3']
+ background-color: #41CCE3
+ option[value='#09B09C']
+ background-color: #09B09C
+ option[value='#3655D7']
+ background-color: #3655D7
+ option[value='#6321A0']
+ background-color: #6321A0
+ option[value='#E796C6']
+ background-color: #E796C6
+ option[value='#DD2DAA']
+ background-color: #DD2DAA \ No newline at end of file
diff --git a/app/assets/stylesheets/components/_dropdown.sass b/app/assets/stylesheets/components/_dropdown.sass
index 8a8d69063..a0d217b14 100644
--- a/app/assets/stylesheets/components/_dropdown.sass
+++ b/app/assets/stylesheets/components/_dropdown.sass
@@ -18,9 +18,19 @@
background-color: whitesmoke
outline: none
- > .disabled > a, > .disabled > button
- cursor: not-allowed
- &, &:hover, &:focus
- color: rgba($darkgrey, 0.5)
- background-color: transparent
- outline: none
+ &, & > ul
+ > .disabled > a, > .disabled > button
+ cursor: not-allowed
+ &, &:hover, &:focus
+ color: rgba($darkgrey, 0.5)
+ background-color: transparent
+ outline: none
+
+ > ul > li > a
+ display: block
+ padding: 3px 20px
+ clear: both
+ font-weight: normal
+ line-height: 1.42857
+ color: #333333
+ white-space: nowrap
diff --git a/app/assets/stylesheets/components/_forms.sass b/app/assets/stylesheets/components/_forms.sass
index 9a363ab97..caa8ac0e4 100644
--- a/app/assets/stylesheets/components/_forms.sass
+++ b/app/assets/stylesheets/components/_forms.sass
@@ -85,9 +85,15 @@ input
// BS horizontal form label positionning fix
.form-horizontal
+ input[type="radio"].form-control
+ height: auto
+ width: auto
.form-group
position: relative
-
+ .radio-inline
+ padding-top: 4px
+ &:first-child
+ padding-left: 0
> .control-label
&[class*='col-sm-']
float: none
@@ -229,6 +235,13 @@ $cbx-size-xs: 15px
&[type='checkbox']:checked + label:before
background-color: $blue
+ &[type='checkbox']:disabled + label
+ &:before
+ border-color: $grey
+ background-color: $lightgrey
+ cursor: not-allowed
+ &:after
+ display: none
// Table adjustments
table, .table
.td, td, .th, th
@@ -243,8 +256,15 @@ table, .table
&.table-2entries .t2e-item
> .th
position: relative
-
- > .checkbox
+ > .st_action
+ list-style-type: none
+ button
+ border-radius: 50%
+ background: $blue
+ color: white
+ border: none
+
+ > .checkbox, > .st_action
position: absolute
right: 0
top: 0
@@ -277,6 +297,7 @@ table, .table
height: $cbx-size
width: $cbx-size
margin: 0 auto
+ transition: transform 0.2s, background-color 0.2s
> input[type='checkbox']
&:not(:checked), &:checked
@@ -436,6 +457,19 @@ table, .table
margin: 0
min-height: 41px
padding: 5px 15px
+ &.active
+ &:after
+ position: absolute
+ top: 0
+ left: 0
+ right: 0
+ height: 4px
+ background: $blue
+ content: ""
+ &.per-page-select
+ padding-top: 10px
+ .selected
+ font-weight: bold
.control-label
font-weight: 700
@@ -454,6 +488,20 @@ table, .table
> .form-group.select2ed
width: 300px
+ &.name-filter
+ .checkbox_list
+ .form-group
+ padding: 10px
+ width: 100px
+ &.to
+ width: 20px
+ color: $grey
+ text-align: center
+ input
+ width: 100%
+ & + .form-group
+ padding-left: 0px
+
> .actions
position: absolute
right: 15px
diff --git a/app/assets/stylesheets/components/_lists.sass b/app/assets/stylesheets/components/_lists.sass
index d8f83d72b..3cce20021 100644
--- a/app/assets/stylesheets/components/_lists.sass
+++ b/app/assets/stylesheets/components/_lists.sass
@@ -54,3 +54,8 @@ $dlWidth: 40%
// Definition
.dl-def
width: 100% - $dlWidth
+
+ ul
+ list-style: none
+ padding-left: 0
+ margin-bottom: 0 \ No newline at end of file
diff --git a/app/assets/stylesheets/components/_main_nav.sass b/app/assets/stylesheets/components/_main_nav.sass
index fdbf5836a..2af070389 100644
--- a/app/assets/stylesheets/components/_main_nav.sass
+++ b/app/assets/stylesheets/components/_main_nav.sass
@@ -17,6 +17,9 @@ $menuW: 300px
line-height: $menuH
padding-left: 10px
opacity: 0.6
+ > a
+ color: rgba(#fff, 0.9)
+ text-decoration: none
#menu_left
position: absolute
@@ -240,6 +243,9 @@ $menuW: 300px
left: 0
top: 13px
+ & > .menu-item
+ max-width: 75%
+
.menu-item
padding: 0 10px
@@ -321,54 +327,45 @@ $menuW: 300px
height: $menuH * 2
transition: 0.1s
- #menu_top > .menu-content > .menu-item-group
- display: none
+ #menu_top > .menu-content
+ & > .menu-item
+ max-width: 90%
+ & > .menu-item-group
+ display: none
.sticky-content
height: $menuH
padding: 0 50px 0 75px
margin-top: -4px
+ display: flex
> *
display: inline-block
&.sticky-ptitle
- width: 60%
+ flex: 1 1
height: $menuH
+ position: relative
h1
- position: relative
+ position: absolute
+ left: 0
+ right: 10px
+ top: 0
line-height: 1.1
white-space: nowrap
max-height: 1.1em
margin: 0 -1.4em 0 0
- padding: 0 1.4em 0 0
+ padding: 0 1.4em 5px 0
overflow: hidden
-
- &:before
- content: '[...]'
- font-size: 0.65em
- position: absolute
- z-index: 5
- right: 0
- bottom: 4px
-
- &:after
- content: ''
- position: absolute
- z-index: 5
- right: 0
- width: 1.4em
- height: 1em
- margin-top: 0.2em
- background-color: $blue
+ text-overflow: ellipsis
.small.fa
color: #fff
margin-left: 0.5em
&.sticky-paction
- width: 40%
+ flex: 0 0 auto
text-align: right
vertical-align: top
diff --git a/app/assets/stylesheets/components/_panels.sass b/app/assets/stylesheets/components/_panels.sass
index e9f615081..ab25d8184 100644
--- a/app/assets/stylesheets/components/_panels.sass
+++ b/app/assets/stylesheets/components/_panels.sass
@@ -34,6 +34,7 @@
a
text-decoration: none
color: $blue
+ text-transform: capitalize
&:hover, &:focus
color: $darkblue
diff --git a/app/assets/stylesheets/components/_referential_overview.sass b/app/assets/stylesheets/components/_referential_overview.sass
new file mode 100644
index 000000000..0beb8ab67
--- /dev/null
+++ b/app/assets/stylesheets/components/_referential_overview.sass
@@ -0,0 +1,346 @@
+.referential-overview
+ $left-size: 100px
+ $line-height: 60px
+ margin-top: 50px
+ overflow: hidden
+ .time-travel, .filters
+ background-color: $lightergrey
+ padding: 10px
+ float: right
+ border-top-left-radius: 4px
+ border-top-right-radius: 4px
+ border: 1px solid $lightgrey
+ border-bottom: none
+ position: relative
+ &:after
+ position: absolute
+ content: ""
+ left: 0
+ top: 100%
+ right: 0
+ height: 10px
+ box-shadow: 0 0 10px rgba(0,0,0,0.5)
+ z-index: 1
+ .time-travel
+ padding-top: 4px
+ padding-bottom: 4px
+ a.btn:first-child
+ margin-right: 1px
+ a.btn:last-child
+ margin-right: 1px
+
+ max-width: 33%
+ .btn-group, .form-group
+ position: relative
+ z-index: 2
+ .form-group
+ margin-left: 10px
+ margin-bottom: 0
+ display: inline-block
+ input
+ padding: 6px 5px
+ border: 1px solid $lightgrey
+ outline: none
+ height: 34px
+ border-radius: 4px
+ padding-right: 25px
+ a
+ padding: 4px
+ margin-top: 2px
+ margin-left: -25px
+ .filters
+ float: left
+ max-width: 66%
+ padding: 0
+ form
+ background: transparent
+ display: flex
+ .ffg-row
+ border-color: $grey
+ .form-group
+ border-color: $grey
+ width: auto
+ flex: 1 1
+ padding: 4px 11px 5px
+ .input-group-btn
+ right: 10px
+ &.togglable
+ padding-top: 6px
+ padding-bottom: 5px
+ &:before
+ top: 0px
+
+ .overview-table
+ position: relative
+ z-index: 2
+ border: 1px solid $grey
+ clear: both
+ display: flex
+ +emptyzone($lightgrey, $lightergrey)
+ .head
+
+ height: $left-size
+ .line
+ height: $line-height
+ .left
+ flex: 0 0
+ background: $lightergrey
+ min-width: $left-size
+ overflow: hidden
+ border-right: 1px solid white
+ .head
+ position: relative
+ border-bottom: 1px solid $grey
+ border-right: 1px solid $lightgrey
+ .dates, .lines
+ position: absolute
+ font-size: 0.8em
+ z-index: 2
+ .dates
+ right: 20px
+ top: 20px
+ .lines
+ left: 20px
+ bottom: 20px
+ &:after
+ position: absolute
+ border-left: ($left-size - 2px)/2 solid transparent
+ border-bottom: ($left-size - 2px)/2 solid transparent
+ border-right: ($left-size - 2px)/2 solid white
+ border-top: ($left-size - 2px)/2 solid white
+ z-index: 1
+ top: 0
+ right: 0
+ width: 0
+ content: ""
+ .line
+ padding: 7px 10px
+ border-bottom: 1px solid $grey
+ font-size: 0.8em
+ &:last-child
+ border-bottom: none
+ .number
+ border-radius: 100px
+ display: inline-block
+ min-width: 20px
+ height: 20px
+ text-align: center
+ padding: 1px 4px
+ text-decoration: none
+ color: black
+ border: 1px solid $grey
+ max-width: 100%
+ white-space: nowrap
+ text-overflow: ellipsis
+ overflow: hidden
+ .name
+ display: inline-block
+ width: $left-size - 50px()
+ white-space: nowrap
+ line-height: 20px
+ margin-left: 5px
+ text-overflow: ellipsis
+ overflow: hidden
+ vertical-align: bottom
+ color: black
+ text-decoration: none
+
+ .company, .mode
+ font-size: 0.9em
+ white-space: nowrap
+ text-overflow: ellipsis
+ overflow: hidden
+ margin-top: -2px
+ .mode
+ text-transform: uppercase
+ color: $grey
+ font-weight: bold
+ .right
+ flex: 1 1
+ overflow: hidden
+ .inner .lines
+ transition: margin-left 0.5s
+ .head
+ white-space: nowrap
+ position: relative
+ z-index: 3
+ &:after, &:before
+ opacity: 0
+ // transition: opacity 0.5s
+ content: ""
+ position: absolute
+ left: -1000px
+ right: 100%
+ top: 0px
+ bottom: 0
+ background: $darkblue
+ z-index: 11
+ border-top: 1px solid white
+ .week
+ display: inline-block
+ position: relative
+ height: 100%
+ transition: margin 0.5s
+ background: white
+ &:last-child
+ box-shadow: 0 -10px 10px rgba(0,0,0,0.5)
+ .week-span
+ left: 15px
+ top: 15px
+ right: 30px
+ white-space: nowrap
+ overflow: hidden
+ text-overflow: ellipsis
+ position: absolute
+
+ .week-number
+ background-color: $lightgrey
+ color: $grey
+ position: absolute
+ top: 0
+ right: 0
+ padding: 2px 4px
+
+ &:after
+ position: absolute
+ right: 0
+ top: 0
+ bottom: 0
+ background: $grey
+ width: 1px
+ content: ""
+
+ &:last-child:after
+ display: none
+
+ .days
+ position: relative
+ top: 50%
+ height: 50%
+ border-top: 1px solid $grey
+ border-bottom: 1px solid $grey
+
+ .day
+ float: left
+ border-left: 1px solid $grey
+ box-sizing: border-box
+ padding-left: 5px
+ padding-top: 3px
+ position: relative
+ height: 100%
+ .name, .number
+ position: absolute
+ left: 10px
+ right: 10px
+ top: 50%
+ transform: translateY(-50%)
+ margin-top: 10px
+ .name
+ font-weight: bold
+ font-size: 0.8em
+ margin-top: -10px
+ &:first-child
+ border: none
+ &.weekend
+ background: $lightergrey
+ &.selected, &:hover
+ color: $blue
+ background-color: transparentize($blue, 0.7)
+ &:after
+ content: ""
+ left: -1px
+ right: -1px
+ top: 100%
+ height: 10000px
+ background-color: transparentize($blue, 0.7)
+ position: absolute
+ z-index: 4
+ &:hover
+ background-color: transparentize(white, 0.7)
+ &:after
+ background-color: transparentize(white, 0.7)
+
+ .line
+ border-bottom: 1px solid $grey
+ position: relative
+ overflow: hidden
+ box-shadow: 0 -10px 10px rgba(0,0,0,0.5)
+
+ &:last-child
+ border-bottom: none
+
+ .period
+ height: 100%
+ top: 0
+ background: #aedd8a
+ position: absolute
+ box-shadow: 0 0 10px rgba(0,0,0,0.5)
+ z-index: 2
+ .title
+ position: absolute
+ left: 12px
+ top: 50%
+ margin-top: -6px
+ transform: translateY(-50%)
+ background-color: transparentize(white, 0.25)
+ padding: 5px
+ font-size: 0.7em
+ border-radius: 5px
+ transition: margin-left 0.5s
+ max-width: calc(100% - 24px)
+ margin-right: 12px
+ &:after
+ content: ""
+ position: absolute
+ bottom: 1px
+ left: 0
+ right: 0
+ height: 10px
+ background: white
+ opacity: 0.25
+ z-index: 3
+ &.empty
+ z-index: 1
+ background: rgb(244, 67, 67)
+ background: repeating-linear-gradient(-45deg, #f5e1cf,#f5e1cf 12px,#e49393 12px,#e49393 25px)
+ &.accepted
+ background: #f19039
+ background: repeating-linear-gradient(-45deg, #f5e1cf,#f5e1cf 12px,#f19039 12px,#f19039 25px)
+ &:hover
+ z-index: 3
+ &:after
+ opacity: 0.5
+ .title
+ background-color: transparentize(white, 0.1)
+
+ &.sticky
+ .time-travel
+ position: fixed
+ bottom: 0
+ z-index: 15
+ right: 35px
+ box-shadow: 0 0 10px rgba(0,0,0,0.5)
+
+ .overview-table .right
+ .lines
+ margin-top: $left-size
+ .head
+ position: fixed
+ top: 80px
+ z-index: 10
+ background: white
+ height: 50px
+ right: 51px
+ left: 51px + $left-size
+ // overflow-x: hidden
+ &:after, &:before
+ opacity: 1
+ &:after
+ left: 100%
+ right: -1000px
+ .week-span, .week-number
+ display: none
+ .days
+ height: 100%
+ top: 0
+ border-top: 1px solid white
diff --git a/app/assets/stylesheets/components/_referentials.sass b/app/assets/stylesheets/components/_referentials.sass
new file mode 100644
index 000000000..0bbb18f2b
--- /dev/null
+++ b/app/assets/stylesheets/components/_referentials.sass
@@ -0,0 +1,4 @@
+#referential_form
+ .metadatas-errors
+ margin-top: -20px
+ margin-bottom: 20px
diff --git a/app/assets/stylesheets/components/_select2.sass b/app/assets/stylesheets/components/_select2.sass
index 332af16cd..f31387c9f 100644
--- a/app/assets/stylesheets/components/_select2.sass
+++ b/app/assets/stylesheets/components/_select2.sass
@@ -7,6 +7,11 @@
// .select2-results__message, .loading-results
// display: none
+.select2-results__option, .select2-selection__rendered
+ .label
+ font-size: 0.8em
+ margin-right: 0.5em
+
.select2-container, .select2-container--bootstrap
.select2-selection--single .select2-selection__rendered
height: 100%
@@ -74,6 +79,9 @@
.select2-search--inline .select2-search__field
height: 28px
+ .select2-selection__rendered
+ padding-right: 20px
+
.select2-container--bootstrap .select2-selection
border-color: rgba($grey, 0.3)
diff --git a/app/assets/stylesheets/components/_tables.sass b/app/assets/stylesheets/components/_tables.sass
index 178ec2f36..1e02ad586 100644
--- a/app/assets/stylesheets/components/_tables.sass
+++ b/app/assets/stylesheets/components/_tables.sass
@@ -9,7 +9,6 @@
font-weight: 700
border-bottom: 2px solid $darkgrey
vertical-align: middle
-
> a
position: relative
display: block
@@ -211,6 +210,18 @@
top: 50%
margin-top: -8px
+ .zdlp
+ background: url( image-path('map/zdlp.png') ) no-repeat left 50%
+ padding-left: 30px
+
+ .lda
+ background: url( image-path('map/lda.png') ) no-repeat left 50%
+ padding-left: 30px
+
+ .gdl
+ background: url( image-path('map/lda.png') ) no-repeat left 50%
+ padding-left: 30px
+
// select_toolbox
.select_toolbox
@@ -314,6 +325,26 @@
padding: 6px 8px
border-bottom: 2px solid rgba($grey, 0.5)
border-top: 1px solid rgba($grey, 0.5)
+ text-transform: capitalize
+
+ .info-button
+ position: absolute
+ width: 20px
+ height: 20px
+ top: 0
+ right: 0
+ margin: 6px 8px
+ button
+ border: none
+ background: $blue
+ border-radius: 20px
+ width: 100%
+ height: 100%
+ font-size: 12px
+ line-height: 14px
+ color: white
+ outline: none
+
.td
position: relative
padding: 6px 8px
@@ -360,7 +391,7 @@
white-space: nowrap
// border-right: 1px solid rgba($grey, 0.5)
max-width: 100%
- min-width: 280px
+ min-width: 330px
padding-right: 1px
.t2e-item
diff --git a/app/assets/stylesheets/components/_toolbar.sass b/app/assets/stylesheets/components/_toolbar.sass
new file mode 100644
index 000000000..86a32bd82
--- /dev/null
+++ b/app/assets/stylesheets/components/_toolbar.sass
@@ -0,0 +1,49 @@
+#development-toolbar
+ .inner
+ overflow: scroll
+ padding: 10px
+ max-height: 70vh
+ display: flex
+ flex-direction: row
+ .toggles
+ font-size: 0.7em
+ float: right
+ a:first-child
+ padding-right: 3px
+ margin-right: 3px
+ border-right: 1px solid $lightgrey
+
+ .col
+ flex: 1 1
+ padding-right: 10px
+ padding-left: 10px
+ border-right: 1px solid $lightgrey
+ &:last-child
+ padding-right: 0
+ border-right: none
+
+ ul
+ padding: 0
+ li
+ list-style-type: none
+ label
+ padding-left: 5px
+ font-weight: normal
+ & + a
+ float: right
+ h5
+ font-weight: bold
+ &.permissions
+ ul
+ overflow: hidden
+ li
+ float: left
+ width: 33%
+ label
+ font-size: 0.8em
+ max-width: calc(100% - 15px)
+ text-overflow: ellipsis
+ overflow: hidden
+ padding-top: 3px
+ input
+ vertical-align: top
diff --git a/app/assets/stylesheets/modules/_jp_collection.sass b/app/assets/stylesheets/modules/_jp_collection.sass
index f579cf87b..46ea3fb6e 100644
--- a/app/assets/stylesheets/modules/_jp_collection.sass
+++ b/app/assets/stylesheets/modules/_jp_collection.sass
@@ -99,32 +99,204 @@
top: 50%
margin-top: -8px
- // Errors
- .table-2entries .t2e-item-list
- .t2e-item
- position: relative
-
- .th .vj_tt
- display: inline-block
- vertical-align: top
-
- + .vj_tt
- margin-left: 5px
-
- &.has-error
- &:before
- content: ''
- position: absolute
- top: 0
- left: 0
- right: 0
- bottom: 0
- border: 2px solid $red
-
- > .th
- > div:first-child, > div:first-child + div
- color: $red
-
- // Reset default behaviour
- .form-control
- border-color: #ccc
+ .table-2entries
+ .t2e-item-list
+ .td
+ overflow: hidden
+
+ .t2e-item
+ position: relative
+
+ .th .vj_tt
+ display: inline-block
+ vertical-align: top
+
+ + .vj_tt
+ margin-left: 5px
+
+ &.with-costs
+ .td
+ padding: 15px 8px
+
+ .totals
+ color: $blue
+ padding-top: 4px
+ margin-left: -5px
+ margin-right: -5px
+ span
+ white-space: nowrap
+ padding: 0 5px
+ i
+ padding-right: 3px
+
+ $link-size: 10px
+ .link
+ position: absolute
+ left: 50px
+ width: 10px
+ top: -15px
+ bottom: -15px
+ background: $blue
+ z-index: 3
+ opacity: 0.5
+ &:after
+ content: ""
+ width: $link-size
+ height: $link-size
+ position: absolute
+ top: 50%
+ bottom: 50%
+ margin-top: -$link-size/2
+ border-top: $link-size/2 solid transparent
+ border-left: $link-size/2 solid transparent
+ border-right: $link-size/2 solid $blue
+ border-bottom: $link-size/2 solid $blue
+ transform: rotate(135deg)
+ left: 0%
+ opacity: 0
+ transition: left 0.2s, opacity 0.2s
+
+ .headlined .link
+ top: 0
+ bottom: -15px
+
+ &:after
+ top: 75%
+ margin-top: -$link-size/2 - 1px
+
+ .activated .link
+ &:after
+ left: -50%
+ opacity: 1
+
+ & > div
+ position: relative
+
+ .link
+ left: 35px
+
+ .has_radio
+ margin-right: 150px
+
+ .costs
+ background: $blue
+ opacity: 0.5
+ padding: 5px
+ color: white
+ position: absolute
+ cursor: not-allowed
+ left: 75px
+ top: -1px
+ transform: translateY(-50%)
+ font-size: 0.75em
+ transition: background 0.1s
+ border: 1px solid white
+
+ &:hover
+ opacity: 1
+ &:after
+ opacity: 1
+
+ &:after
+ opacity: 0.5
+ content: ""
+ height: 2px
+ position: absolute
+ left: -23px
+ background: $blue
+ right: 100%
+ top: 50%
+ transition: background 0.1s
+
+ p
+ display: block
+ border: none
+ margin-bottom: 0
+ i
+ margin-right: 3px
+ width: 12px
+ & + p
+ position: relative
+ z-index: 2
+ padding-right: 0
+ margin: 0
+ border-right: none
+
+ input
+ display: inline-block
+ width: 50px
+ border: none
+ margin-right: 5px
+ color: black
+
+ .edit-mode
+ .costs
+ cursor: pointer
+ p
+ margin-bottom: 5px
+ & + p
+ margin-bottom: 0
+
+ opacity: 1
+ &:after
+ opacity: 1
+
+ .link
+ opacity: 1
+
+ .with-headline + .costs
+ top: 25%
+
+ .deactivated .costs
+ display: none
+
+ &.has-error
+ &:before
+ content: ''
+ position: absolute
+ top: 0
+ left: 0
+ right: 0
+ bottom: 0
+ border: 2px solid $red
+
+ > .th
+ > div:first-child, > div:first-child + div
+ color: $red
+
+ // Reset default behaviour
+ .form-control
+ border-color: #ccc
+
+ .t2e-head
+ .td.with-costs
+ & > div
+ &:not(.headlined)
+ height: calc(100% + 6px)
+ & > span
+ &:after
+ top: -15px
+ bottom: -9px
+
+ div.headlined
+ &:before
+ margin-bottom: 0
+ & > span
+ height: calc(100% - (1.4em + 15px))
+ &:after
+ top: calc((1.4em + 30px) * -1)
+ bottom: 0
+
+ .td.with-costs, .with-costs .td
+ padding-top: 15px
+ padding-bottom: 15px
+
+ & > div
+ height: calc(100% + 15px)
+ &.headlined
+ &:before
+ padding-top: 15px
+ padding-bottom: 15px
+ height: calc(1.4em + 30px)
+ margin-top: -15px
+ margin-bottom: 15px
diff --git a/app/assets/stylesheets/modules/_timetables.sass b/app/assets/stylesheets/modules/_timetables.sass
index b06972ef9..8999456ec 100644
--- a/app/assets/stylesheets/modules/_timetables.sass
+++ b/app/assets/stylesheets/modules/_timetables.sass
@@ -30,7 +30,7 @@
.t2e-item
.th
- padding: 6px 0 0 0
+ padding: 4px 0 0 0
border-color: $darkgrey
border-top-width: 2px
diff --git a/app/assets/stylesheets/modules/_vj_collection.sass b/app/assets/stylesheets/modules/_vj_collection.sass
index 56769e52b..d99c67bd7 100644
--- a/app/assets/stylesheets/modules/_vj_collection.sass
+++ b/app/assets/stylesheets/modules/_vj_collection.sass
@@ -9,6 +9,9 @@
position: relative
padding-left: 25px
+ .fa
+ margin-left: 5px
+
> .headlined
&:before
margin-left: -25px
@@ -113,6 +116,9 @@
margin-left: 5px
&.has-error
+ .errors
+ color: $red
+ font-size: 0.8em
&:before
content: ''
position: absolute
diff --git a/app/assets/stylesheets/modules/import_messages.sass b/app/assets/stylesheets/modules/import_messages.sass
index e5666cbcd..cde903b00 100644
--- a/app/assets/stylesheets/modules/import_messages.sass
+++ b/app/assets/stylesheets/modules/import_messages.sass
@@ -2,4 +2,7 @@
.status_icon
padding-right: 20px
h1
- padding-bottom: 20px
+ padding-bottom: 20px
+
+ul.list-unstyled
+ padding-bottom: 20px
diff --git a/app/assets/stylesheets/typography/_sboiv.sass b/app/assets/stylesheets/typography/_sboiv.sass
index f694306c4..f0943f843 100644
--- a/app/assets/stylesheets/typography/_sboiv.sass
+++ b/app/assets/stylesheets/typography/_sboiv.sass
@@ -52,6 +52,8 @@
&.sb-5x
font-size: 5em
+ &.sb-strong
+ font-weight: bold
.sb-ZDLR:before
content: '\e904'
@@ -89,7 +91,7 @@
.sb-OAS:before
content: '\e90f'
-.sb-calendar:before
+.sb-calendar:before, .sb-purchase_window:before
content: '\e910'
.sb-journey_pattern:before
diff --git a/app/controllers/api_keys_controller.rb b/app/controllers/api_keys_controller.rb
index 9706c5961..a03a67481 100644
--- a/app/controllers/api_keys_controller.rb
+++ b/app/controllers/api_keys_controller.rb
@@ -31,11 +31,4 @@ class ApiKeysController < ChouetteController
def api_key_params
params.require(:api_key).permit(:name, :referential_id)
end
-
- def decorate_api_keys(api_keys)
- ModelDecorator.decorate(
- api_keys,
- with: ApiKeyDecorator,
- )
- end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 97f5548ae..8bd3da2f9 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,6 +1,7 @@
class ApplicationController < ActionController::Base
include PaperTrailSupport
include Pundit
+ include FeatureChecker
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
@@ -40,12 +41,30 @@ class ApplicationController < ActionController::Base
end
helper_method :current_offer_workbench
+ def current_workgroup
+ current_offer_workbench.workgroup
+ end
+ helper_method :current_workgroup
+
def current_functional_scope
functional_scope = current_organisation.sso_attributes.try(:[], "functional_scope") if current_organisation
JSON.parse(functional_scope) if functional_scope
end
helper_method :current_functional_scope
+ def collection_name
+ self.class.name.split("::").last.gsub('Controller', '').underscore
+ end
+
+ def decorated_collection
+ if instance_variable_defined?("@#{collection_name}")
+ instance_variable_get("@#{collection_name}")
+ else
+ nil
+ end
+ end
+ helper_method :decorated_collection
+
def begin_of_association_chain
current_organisation
end
diff --git a/app/controllers/autocomplete_purchase_windows_controller.rb b/app/controllers/autocomplete_purchase_windows_controller.rb
new file mode 100644
index 000000000..70dc5a346
--- /dev/null
+++ b/app/controllers/autocomplete_purchase_windows_controller.rb
@@ -0,0 +1,12 @@
+class AutocompletePurchaseWindowsController < ChouetteController
+ respond_to :json, :only => [:index]
+
+ requires_feature :purchase_windows
+
+ include ReferentialSupport
+
+ protected
+ def collection
+ @purchase_windows = referential.purchase_windows.search(params[:q]).result.paginate(page: params[:page])
+ end
+end
diff --git a/app/controllers/autocomplete_stop_areas_controller.rb b/app/controllers/autocomplete_stop_areas_controller.rb
index 233012028..79154a6e0 100644
--- a/app/controllers/autocomplete_stop_areas_controller.rb
+++ b/app/controllers/autocomplete_stop_areas_controller.rb
@@ -17,13 +17,20 @@ class AutocompleteStopAreasController < ChouetteController
scope = scope.possible_parents if relation_parent?
scope = scope.possible_parents if relation_children?
end
+ if search_scope.present?
+ scope = StopAreaPolicy::Scope.new(current_user, scope).search_scope(search_scope)
+ end
args = [].tap{|arg| 4.times{arg << "%#{params[:q]}%"}}
- @stop_areas = scope.where("name ILIKE ? OR city_name ILIKE ? OR registration_number ILIKE ? OR objectid ILIKE ?", *args).limit(50)
+ @stop_areas = scope.where("unaccent(stop_areas.name) ILIKE unaccent(?) OR unaccent(stop_areas.city_name) ILIKE unaccent(?) OR stop_areas.registration_number ILIKE ? OR stop_areas.objectid ILIKE ?", *args).limit(50)
@stop_areas
end
def target_type?
- params.has_key?( :target_type)
+ params.has_key?(:target_type)
+ end
+
+ def search_scope
+ params[:scope]
end
def relation_parent?
diff --git a/app/controllers/calendars_controller.rb b/app/controllers/calendars_controller.rb
index 2ed10a111..75d4cbd09 100644
--- a/app/controllers/calendars_controller.rb
+++ b/app/controllers/calendars_controller.rb
@@ -1,25 +1,69 @@
class CalendarsController < ChouetteController
include PolicyChecker
+ include TimeTablesHelper
+
defaults resource_class: Calendar
before_action :ransack_contains_date, only: [:index]
respond_to :html
+ respond_to :json, only: :show
respond_to :js, only: :index
+ belongs_to :workgroup
+
def index
index! do
- @calendars = ModelDecorator.decorate(@calendars, with: CalendarDecorator)
+ @calendars = decorate_calendars(@calendars)
end
end
def show
show! do
- @calendar = @calendar.decorate
+ @year = params[:year] ? params[:year].to_i : Date.today.cwyear
+ @calendar = @calendar.decorate(context: {
+ workgroup: workgroup
+ })
+ end
+ end
+
+ def month
+ @date = params['date'] ? Date.parse(params['date']) : Date.today
+ @calendar = resource
+ end
+
+ def create
+ create! do
+ if @calendar.valid? && has_feature?('application_days_on_calendars')
+ redirect_to([:edit, @calendar])
+ return
+ end
+ end
+ end
+
+ def update
+ if params[:calendar]
+ super
+ else
+ state = JSON.parse request.raw_post
+ resource.state_update state
+ respond_to do |format|
+ format.json { render json: state, status: state['errors'] ? :unprocessable_entity : :ok }
+ end
end
end
private
+
+ def decorate_calendars(calendars)
+ CalendarDecorator.decorate(
+ calendars,
+ context: {
+ workgroup: workgroup
+ }
+ )
+ end
+
def calendar_params
- permitted_params = [:id, :name, :short_name, periods_attributes: [:id, :begin, :end, :_destroy], date_values_attributes: [:id, :value, :_destroy]]
+ permitted_params = [:id, :name, :short_name, :shared, periods_attributes: [:id, :begin, :end, :_destroy], date_values_attributes: [:id, :value, :_destroy]]
permitted_params << :shared if policy(Calendar).share?
params.require(:calendar).permit(*permitted_params)
end
@@ -33,25 +77,30 @@ class CalendarsController < ChouetteController
end
protected
+
+ alias_method :workgroup, :parent
+ helper_method :workgroup
+
def resource
- @calendar = Calendar.where('organisation_id = ? OR shared = true', current_organisation.id).find_by_id(params[:id])
+ @calendar ||= workgroup.calendars.where('(organisation_id = ? OR shared = ?)', current_organisation.id, true).find_by_id(params[:id])
end
def build_resource
super.tap do |calendar|
+ calendar.workgroup = workgroup
calendar.organisation = current_organisation
end
end
def collection
- return @calendars if @calendars
- scope = Calendar.where('organisation_id = ? OR shared = ?', current_organisation.id, true)
- scope = shared_scope(scope)
- @q = scope.ransack(params[:q])
-
- calendars = @q.result
- calendars = calendars.order(sort_column + ' ' + sort_direction) if sort_column && sort_direction
- @calendars = calendars.paginate(page: params[:page])
+ @calendars ||= begin
+ scope = workgroup.calendars.where('(organisation_id = ? OR shared = ?)', current_organisation.id, true)
+ scope = shared_scope(scope)
+ @q = scope.ransack(params[:q])
+ calendars = @q.result
+ calendars = calendars.order(sort_column + ' ' + sort_direction) if sort_column && sort_direction
+ calendars = calendars.paginate(page: params[:page])
+ end
end
def ransack_contains_date
@@ -76,4 +125,4 @@ class CalendarsController < ChouetteController
scope
end
-end
+end \ No newline at end of file
diff --git a/app/controllers/companies_controller.rb b/app/controllers/companies_controller.rb
index 931d846c5..4afd12be1 100644
--- a/app/controllers/companies_controller.rb
+++ b/app/controllers/companies_controller.rb
@@ -47,6 +47,9 @@ class CompaniesController < ChouetteController
end
end
+ def resource
+ super.decorate(context: { referential: line_referential })
+ end
def resource_url(company = nil)
line_referential_company_path(line_referential, company || resource)
@@ -61,6 +64,10 @@ class CompaniesController < ChouetteController
alias_method :current_referential, :line_referential
helper_method :current_referential
+ def begin_of_association_chain
+ current_organisation
+ end
+
def company_params
params.require(:company).permit( :objectid, :object_version, :name, :short_name, :organizational_unit, :operating_department_name, :code, :phone, :fax, :email, :registration_number, :url, :time_zone )
end
@@ -75,9 +82,8 @@ class CompaniesController < ChouetteController
end
def decorate_companies(companies)
- ModelDecorator.decorate(
+ CompanyDecorator.decorate(
companies,
- with: CompanyDecorator,
context: {
referential: line_referential
}
diff --git a/app/controllers/compliance_check_messages_controller.rb b/app/controllers/compliance_check_messages_controller.rb
new file mode 100644
index 000000000..7c416584e
--- /dev/null
+++ b/app/controllers/compliance_check_messages_controller.rb
@@ -0,0 +1,34 @@
+class ComplianceCheckMessagesController < ChouetteController
+ defaults resource_class: ComplianceCheckMessage, collection_name: 'compliance_check_messages', instance_name: 'compliance_check_message'
+ respond_to :csv
+ belongs_to :compliance_check_set, :parent_class => ComplianceCheckSet
+
+
+ def index
+ index! do |format|
+ format.csv {
+ send_data ComplianceCheckMessageExport.new(compliance_check_messages: collection).to_csv(:col_sep => "\;", :quote_char=>'"', force_quotes: true, server_url: request.base_url) , :filename => "compliance_check_set_errors_#{line_code}_#{Time.now.to_i}.csv"
+ }
+ end
+ end
+
+ protected
+ def collection
+ parent.compliance_check_messages.where(compliance_check_resource_id: params[:compliance_check_resource_id])
+ end
+
+ def parent
+ @compliance_check_set ||= ComplianceCheckSet.find(params[:compliance_check_set_id])
+ end
+
+ def compliance_check_resource
+ ComplianceCheckResource.find(params[:compliance_check_resource_id])
+ end
+
+ private
+
+ def line_code
+ Chouette::Line.find_by_objectid("#{compliance_check_resource.reference}").get_objectid.local_id
+ end
+
+end
diff --git a/app/controllers/compliance_check_sets_controller.rb b/app/controllers/compliance_check_sets_controller.rb
index 600c69126..271598428 100644
--- a/app/controllers/compliance_check_sets_controller.rb
+++ b/app/controllers/compliance_check_sets_controller.rb
@@ -11,9 +11,8 @@ class ComplianceCheckSetsController < ChouetteController
scope = self.ransack_period_range(scope: @compliance_check_sets, error_message: t('compliance_check_sets.filters.error_period_filter'), query: :where_created_at_between)
@q_for_form = scope.ransack(params[:q])
format.html {
- @compliance_check_sets = ModelDecorator.decorate(
- @q_for_form.result.order(created_at: :desc),
- with: ComplianceCheckSetDecorator
+ @compliance_check_sets = ComplianceCheckSetDecorator.decorate(
+ @q_for_form.result.order(created_at: :desc)
)
}
end
@@ -21,9 +20,7 @@ class ComplianceCheckSetsController < ChouetteController
def show
show! do
- @compliance_check_set = @compliance_check_set.decorate(context: {
- compliance_check_set: @compliance_check_set
- })
+ @compliance_check_set = @compliance_check_set.decorate
end
end
@@ -43,18 +40,9 @@ class ComplianceCheckSetsController < ChouetteController
def executed_for_html
@q_checks_form = @compliance_check_set.compliance_checks.ransack(params[:q])
@compliance_check_set = @compliance_check_set.decorate
- compliance_checks =
- decorate_compliance_checks( @q_checks_form.result)
- .group_by(&:compliance_check_block)
+ compliance_checks = @q_checks_form.result
+ .group_by(&:compliance_check_block)
@direct_compliance_checks = compliance_checks.delete nil
@blocks_to_compliance_checks_map = compliance_checks
end
-
- # Decoration
- # ----------
- def decorate_compliance_checks(compliance_checks)
- ModelDecorator.decorate(
- compliance_checks,
- with: ComplianceCheckDecorator)
- end
end
diff --git a/app/controllers/compliance_control_sets_controller.rb b/app/controllers/compliance_control_sets_controller.rb
index 2d3d03ad0..8f9251155 100644
--- a/app/controllers/compliance_control_sets_controller.rb
+++ b/app/controllers/compliance_control_sets_controller.rb
@@ -7,10 +7,8 @@ class ComplianceControlSetsController < ChouetteController
def index
index! do |format|
- scope = self.ransack_period_range(scope: @compliance_control_sets, error_message: t('imports.filters.error_period_filter'), query: :where_updated_at_between)
- @q_for_form = scope.ransack(params[:q])
format.html {
- @compliance_control_sets = decorate_compliance_control_sets(@q_for_form.result.paginate(page: params[:page], per_page: 30))
+ @compliance_control_sets = decorate_compliance_control_sets(@compliance_control_sets)
}
end
end
@@ -37,18 +35,20 @@ class ComplianceControlSetsController < ChouetteController
private
+ def collection
+ scope = self.ransack_period_range(scope: ComplianceControlSet.all, error_message: t('imports.filters.error_period_filter'), query: :where_updated_at_between)
+ @q_for_form = scope.ransack(params[:q])
+ compliance_control_sets = @q_for_form.result
+ compliance_control_sets = joins_with_associated_objects(compliance_control_sets).order(sort_column + ' ' + sort_direction) if sort_column && sort_direction
+ @compliance_control_sets = compliance_control_sets.paginate(page: params[:page], per_page: 30)
+ end
+
def decorate_compliance_control_sets(compliance_control_sets)
- ModelDecorator.decorate(
- compliance_control_sets,
- with: ComplianceControlSetDecorator
- )
+ ComplianceControlSetDecorator.decorate(compliance_control_sets)
end
def decorate_compliance_controls(compliance_controls)
- ModelDecorator.decorate(
- compliance_controls,
- with: ComplianceControlDecorator,
- )
+ ComplianceControlDecorator.decorate(compliance_controls)
end
def compliance_control_set_params
@@ -64,4 +64,32 @@ class ComplianceControlSetsController < ChouetteController
@direct_compliance_controls = compliance_controls.delete nil
@blocks_to_compliance_controls_map = compliance_controls
end
+
+ def sort_column
+ case params[:sort]
+ when 'name' then 'lower(compliance_control_sets.name)'
+ when 'owner_jdc' then 'lower(organisations.name)'
+ when 'control_numbers' then 'COUNT(compliance_controls.id)'
+ else
+ ComplianceControlSet.column_names.include?(params[:sort]) ? params[:sort] : 'lower(compliance_control_sets.name)'
+ end
+ end
+
+ def joins_with_associated_objects(collection)
+
+ # dont know if this is the right way to do it but since we need to join table deoending of the params
+ # it was to avoid loading associated objects if we don't need them
+ case params[:sort]
+ when 'owner_jdc'
+ collection.joins("LEFT JOIN organisations ON compliance_control_sets.organisation_id = organisations.id")
+ when 'control_numbers'
+ collection.joins("LEFT JOIN compliance_controls ON compliance_controls.compliance_control_set_id = compliance_control_sets.id").group(:id)
+ else
+ collection
+ end
+ end
+
+ def sort_direction
+ %w[asc desc].include?(params[:direction]) ? params[:direction] : 'asc'
+ end
end
diff --git a/app/controllers/compliance_controls_controller.rb b/app/controllers/compliance_controls_controller.rb
index dfbecaa71..73dc18f59 100644
--- a/app/controllers/compliance_controls_controller.rb
+++ b/app/controllers/compliance_controls_controller.rb
@@ -8,6 +8,12 @@ class ComplianceControlsController < ChouetteController
@sti_subclasses = ComplianceControl.subclasses
end
+ def show
+ show! do
+ @compliance_control = @compliance_control.decorate
+ end
+ end
+
def new
if params[:sti_class].blank?
flash[:notice] = I18n.t("compliance_controls.errors.mandatory_control_type")
diff --git a/app/controllers/concerns/activatable.rb b/app/controllers/concerns/activatable.rb
new file mode 100644
index 000000000..1a34551a9
--- /dev/null
+++ b/app/controllers/concerns/activatable.rb
@@ -0,0 +1,11 @@
+module Activatable
+ extend ActiveSupport::Concern
+
+ %w(activate deactivate).each do |action|
+ define_method action do
+ authorize resource, "#{action}?"
+ resource.send "#{action}!"
+ redirect_to request.referer || [current_referential, resource]
+ end
+ end
+end
diff --git a/app/controllers/concerns/feature_checker.rb b/app/controllers/concerns/feature_checker.rb
new file mode 100644
index 000000000..9ca5ed0a7
--- /dev/null
+++ b/app/controllers/concerns/feature_checker.rb
@@ -0,0 +1,42 @@
+# Check availability of optional features
+#
+# In your controller, use :
+#
+# requires_feature :test
+# requires_feature :test, only: [:show]
+#
+# In your view, use :
+#
+# has_feature? :test
+#
+module FeatureChecker
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def requires_feature(feature, options = {})
+ before_action options do
+ check_feature! feature
+ end
+ end
+ end
+
+ included do
+ helper_method :has_feature?
+ end
+
+ protected
+
+ def has_feature?(*features)
+ features.all? do |feature|
+ current_organisation.has_feature? feature
+ end
+ end
+
+ def check_feature!(*features)
+ unless has_feature?(*features)
+ raise NotAuthorizedError, "Feature not autorized"
+ end
+ end
+
+ class NotAuthorizedError < StandardError; end
+end
diff --git a/app/controllers/concerns/ransack_date_filter.rb b/app/controllers/concerns/ransack_date_filter.rb
index 0fbde91d3..055c01130 100644
--- a/app/controllers/concerns/ransack_date_filter.rb
+++ b/app/controllers/concerns/ransack_date_filter.rb
@@ -3,7 +3,15 @@ module RansackDateFilter
included do
- def set_date_time_params(param_name, klass)
+ def begin_range_var prefix
+ "@#{[prefix, "begin_range"].compact.join('_')}"
+ end
+
+ def end_range_var prefix
+ "@#{[prefix, "end_range"].compact.join('_')}"
+ end
+
+ def set_date_time_params(param_name, klass, prefix: nil)
start_date = []
end_date = []
@@ -16,26 +24,28 @@ module RansackDateFilter
params[:q].delete([param_name])
if klass == DateTime
- @begin_range = klass.new(*start_date,0,0,0) rescue nil
- @end_range = klass.new(*end_date,23,59,59) rescue nil
+ instance_variable_set begin_range_var(prefix), klass.new(*start_date,0,0,0) rescue nil
+ instance_variable_set end_range_var(prefix), klass.new(*end_date,23,59,59) rescue nil
else
- @begin_range = klass.new(*start_date) rescue nil
- @end_range = klass.new(*end_date) rescue nil
+ instance_variable_set begin_range_var(prefix), klass.new(*start_date) rescue nil
+ instance_variable_set end_range_var(prefix), klass.new(*end_date) rescue nil
end
end
end
# Fake ransack filter
def ransack_period_range **options
- return options[:scope] unless !!@begin_range && !!@end_range
+ prefix = options[:prefix]
+ return options[:scope] unless !!instance_variable_get(begin_range_var(prefix)) && !!instance_variable_get(end_range_var(prefix))
- if @begin_range > @end_range
+ scope = options[:scope]
+ if instance_variable_get(begin_range_var(prefix)) > instance_variable_get(end_range_var(prefix))
flash.now[:error] = options[:error_message]
else
- scope = options[:scope].send options[:query], @begin_range..@end_range
+ scope = scope.send options[:query], instance_variable_get(begin_range_var(prefix))..instance_variable_get(end_range_var(prefix))
end
scope
end
end
-end \ No newline at end of file
+end
diff --git a/app/controllers/development_toolbar_controller.rb b/app/controllers/development_toolbar_controller.rb
new file mode 100644
index 000000000..20349f7b8
--- /dev/null
+++ b/app/controllers/development_toolbar_controller.rb
@@ -0,0 +1,11 @@
+class DevelopmentToolbarController < ApplicationController
+ def update_settings
+ return unless Rails.application.config.development_toolbar.present?
+ organisation = current_user.organisation
+ organisation.features = params[:features].keys.select{|k| params[:features][k] == "true"}
+ organisation.save
+ current_user.permissions = params[:permissions].keys.select{|k| params[:permissions][k] == "true"}
+ current_user.save
+ redirect_to request.referrer || "/"
+ end
+end
diff --git a/app/controllers/group_of_lines_controller.rb b/app/controllers/group_of_lines_controller.rb
index 5762108dc..46d9d077f 100644
--- a/app/controllers/group_of_lines_controller.rb
+++ b/app/controllers/group_of_lines_controller.rb
@@ -42,7 +42,6 @@ class GroupOfLinesController < ChouetteController
end
end
-
protected
def filtered_group_of_lines_maps
@@ -70,6 +69,10 @@ class GroupOfLinesController < ChouetteController
alias_method :line_referential, :parent
+ def begin_of_association_chain
+ current_organisation
+ end
+
private
def group_of_line_params
diff --git a/app/controllers/import_messages_controller.rb b/app/controllers/import_messages_controller.rb
index 6546b25f8..286bb0ce8 100644
--- a/app/controllers/import_messages_controller.rb
+++ b/app/controllers/import_messages_controller.rb
@@ -9,7 +9,7 @@ class ImportMessagesController < ChouetteController
def index
index! do |format|
format.csv {
- send_data ImportMessageExport.new(:import_messages => @import_messages).to_csv(:col_sep => ";") , :filename => "#{File.basename(@import_resource.name)}_#{Time.now.to_i}.csv"
+ send_data ImportMessageExport.new(:import_messages => @import_messages).to_csv(:col_sep => "\;", :quote_char=>'"', force_quotes: true) , :filename => "import_errors_#{@import_resource.name.gsub('.xml', '')}_#{Time.now.to_i}.csv"
}
end
end
diff --git a/app/controllers/import_resources_controller.rb b/app/controllers/import_resources_controller.rb
index c83721310..ea78394a1 100644
--- a/app/controllers/import_resources_controller.rb
+++ b/app/controllers/import_resources_controller.rb
@@ -27,12 +27,6 @@ class ImportResourcesController < ChouetteController
private
def decorate_import_resources(import_resources)
- ImportResourcesDecorator.decorate(
- import_resources,
- with: ImportResourceDecorator,
- context: {
- import: @import
- }
- )
+ ImportResourcesDecorator.decorate(import_resources)
end
end
diff --git a/app/controllers/imports_controller.rb b/app/controllers/imports_controller.rb
index 46d34efda..7a999d657 100644
--- a/app/controllers/imports_controller.rb
+++ b/app/controllers/imports_controller.rb
@@ -84,9 +84,8 @@ class ImportsController < ChouetteController
end
def decorate_imports(imports)
- ModelDecorator.decorate(
+ ImportDecorator.decorate(
imports,
- with: ImportDecorator,
context: {
workbench: @workbench
}
diff --git a/app/controllers/journey_patterns_collections_controller.rb b/app/controllers/journey_patterns_collections_controller.rb
index 736fb1441..da567779e 100644
--- a/app/controllers/journey_patterns_collections_controller.rb
+++ b/app/controllers/journey_patterns_collections_controller.rb
@@ -17,38 +17,53 @@ class JourneyPatternsCollectionsController < ChouetteController
alias_method :vehicle_journey, :resource
def show
- @q = route.journey_patterns.search(params[:q]).result(distinct: true).includes(:stop_points)
+ @q = route.journey_patterns
+ if params[:q].present?
+ ids = @q.search(params[:q]).result(distinct: true).pluck(:id)
+ @q = @q.where(id: ids)
+ end
+ @q = @q.includes(:stop_points)
@ppage = 10
@journey_patterns ||= @q.paginate(page: params[:page], per_page: @ppage).order(:name)
-
- @stop_points_list = []
- route.stop_points.each do |sp|
- @stop_points_list << {
- :id => sp.stop_area.id,
- :route_id => sp.try(:route_id),
- :object_id => sp.try(:objectid),
- :position => sp.try(:position),
- :for_boarding => sp.try(:for_boarding),
- :for_alighting => sp.try(:for_alighting),
- :name => sp.stop_area.try(:name),
- :zip_code => sp.stop_area.try(:zip_code),
- :city_name => sp.stop_area.try(:city_name),
- :comment => sp.stop_area.try(:comment),
- :area_type => sp.stop_area.try(:area_type),
- :registration_number => sp.stop_area.try(:registration_number),
- :nearest_topic_name => sp.stop_area.try(:nearest_topic_name),
- :fare_code => sp.stop_area.try(:fare_code),
- :longitude => sp.stop_area.try(:longitude),
- :latitude => sp.stop_area.try(:latitude),
- :long_lat_type => sp.stop_area.try(:long_lat_type),
- :country_code => sp.stop_area.try(:country_code),
- :street_name => sp.stop_area.try(:street_name)
- }
+ respond_to do |format|
+ format.json do
+ @journey_patterns = @journey_patterns.includes(stop_points: {stop_area: :stop_area_referential})
+ end
+ format.html do
+ @stop_points_list = []
+ route.stop_points.includes(:stop_area).each do |sp|
+ @stop_points_list << {
+ :id => sp.stop_area.id,
+ :route_id => sp.try(:route_id),
+ :object_id => sp.try(:objectid),
+ :stop_area_object_id => sp.stop_area.try(:objectid),
+ :position => sp.try(:position),
+ :for_boarding => sp.try(:for_boarding),
+ :for_alighting => sp.try(:for_alighting),
+ :name => sp.stop_area.try(:name),
+ :zip_code => sp.stop_area.try(:zip_code),
+ :city_name => sp.stop_area.try(:city_name),
+ :country_name => sp.stop_area.try(:country_name),
+ :time_zone_formatted_offset => sp.stop_area.try(:time_zone_formatted_offset),
+ :comment => sp.stop_area.try(:comment),
+ :area_type => sp.stop_area.try(:area_type),
+ :registration_number => sp.stop_area.try(:registration_number),
+ :nearest_topic_name => sp.stop_area.try(:nearest_topic_name),
+ :fare_code => sp.stop_area.try(:fare_code),
+ :longitude => sp.stop_area.try(:longitude),
+ :latitude => sp.stop_area.try(:latitude),
+ :long_lat_type => sp.stop_area.try(:long_lat_type),
+ :country_code => sp.stop_area.try(:country_code),
+ :street_name => sp.stop_area.try(:street_name)
+ }
+ end
+ @stop_points_list = @stop_points_list.sort_by {|a| a[:position] }
+ end
end
- @stop_points_list = @stop_points_list.sort_by {|a| a[:position] }
end
def user_permissions
+ @features = Hash[*current_organisation.features.map{|f| [f, true]}.flatten].to_json
policy = policy(:journey_pattern)
@perms =
%w{create destroy update}.inject({}) do | permissions, action |
diff --git a/app/controllers/line_referentials_controller.rb b/app/controllers/line_referentials_controller.rb
index 39c2cdb89..03dab3f8f 100644
--- a/app/controllers/line_referentials_controller.rb
+++ b/app/controllers/line_referentials_controller.rb
@@ -3,6 +3,7 @@ class LineReferentialsController < ChouetteController
defaults :resource_class => LineReferential
def sync
+ authorize resource, :synchronize?
@sync = resource.line_referential_syncs.build
if @sync.save
flash[:notice] = t('notice.line_referential_sync.created')
diff --git a/app/controllers/lines_controller.rb b/app/controllers/lines_controller.rb
index 571c73f4a..27a9bf9be 100644
--- a/app/controllers/lines_controller.rb
+++ b/app/controllers/lines_controller.rb
@@ -1,6 +1,8 @@
class LinesController < ChouetteController
include ApplicationHelper
+ include Activatable
include PolicyChecker
+
defaults :resource_class => Chouette::Line
respond_to :html
respond_to :xml
@@ -13,9 +15,8 @@ class LinesController < ChouetteController
def index
@hide_group_of_line = line_referential.group_of_lines.empty?
index! do |format|
- @lines = ModelDecorator.decorate(
+ @lines = LineDecorator.decorate(
@lines,
- with: LineDecorator,
context: {
line_referential: @line_referential,
current_organisation: current_organisation
@@ -67,7 +68,6 @@ class LinesController < ChouetteController
respond_to do |format|
format.json { render :json => filtered_lines_maps}
end
-
end
protected
@@ -112,6 +112,10 @@ class LinesController < ChouetteController
alias_method :current_referential, :line_referential
helper_method :current_referential
+ def begin_of_association_chain
+ current_organisation
+ end
+
def line_params
params.require(:line).permit(
:transport_mode,
diff --git a/app/controllers/merges_controller.rb b/app/controllers/merges_controller.rb
new file mode 100644
index 000000000..1ce64ed58
--- /dev/null
+++ b/app/controllers/merges_controller.rb
@@ -0,0 +1,40 @@
+class MergesController < ChouetteController
+ # include PolicyChecker
+
+ defaults resource_class: Merge
+ belongs_to :workbench
+
+ respond_to :html
+
+ before_action :set_mergeable_controllers, only: [:new]
+
+ private
+
+ def set_mergeable_controllers
+ @mergeable_referentials ||= parent.referentials.mergeable
+ Rails.logger.debug "Mergeables: #{@mergeable_referentials.inspect}"
+ end
+
+ def build_resource
+ super.tap do |merge|
+ merge.creator = current_user.name
+ end
+ end
+
+ # def build_resource
+ # @import ||= WorkbenchImport.new(*resource_params) do |import|
+ # import.workbench = parent
+ # import.creator = current_user.name
+ # end
+ # end
+
+ def merge_params
+ params.require(:merge).permit(
+ referentials: []
+ # :name,
+ # :file,
+ # :type,
+ # :referential_id
+ )
+ end
+end
diff --git a/app/controllers/networks_controller.rb b/app/controllers/networks_controller.rb
index 494d1e69f..1c69b1240 100644
--- a/app/controllers/networks_controller.rb
+++ b/app/controllers/networks_controller.rb
@@ -71,6 +71,10 @@ class NetworksController < ChouetteController
alias_method :current_referential, :line_referential
helper_method :current_referential
+ def begin_of_association_chain
+ current_organisation
+ end
+
def network_params
params.require(:network).permit(:objectid, :object_version, :version_date, :description, :name, :registration_number, :source_name, :source_type_name, :source_identifier, :comment )
end
@@ -85,9 +89,8 @@ class NetworksController < ChouetteController
end
def decorate_networks(networks)
- ModelDecorator.decorate(
+ NetworkDecorator.decorate(
networks,
- with: NetworkDecorator,
context: {
line_referential: line_referential
}
diff --git a/app/controllers/purchase_windows_controller.rb b/app/controllers/purchase_windows_controller.rb
new file mode 100644
index 000000000..293a7d8e4
--- /dev/null
+++ b/app/controllers/purchase_windows_controller.rb
@@ -0,0 +1,74 @@
+class PurchaseWindowsController < ChouetteController
+ include ReferentialSupport
+ include RansackDateFilter
+ include PolicyChecker
+ before_action :ransack_contains_date, only: [:index]
+ defaults :resource_class => Chouette::PurchaseWindow, collection_name: 'purchase_windows', instance_name: 'purchase_window'
+ belongs_to :referential
+
+ requires_feature :purchase_windows
+
+ def index
+ index! do
+ @purchase_windows = decorate_purchase_windows(@purchase_windows)
+ end
+ end
+
+ def show
+ show! do
+ @purchase_window = @purchase_window.decorate(context: {
+ referential: @referential
+ })
+ end
+ end
+
+ protected
+
+ def create_resource(purchase_window)
+ purchase_window.referential = @referential
+ super
+ end
+
+ private
+
+ def purchase_window_params
+ params.require(:purchase_window).permit(:id, :name, :color, :referential_id, periods_attributes: [:id, :begin, :end, :_destroy])
+ end
+
+ def decorate_purchase_windows(purchase_windows)
+ PurchaseWindowDecorator.decorate(
+ purchase_windows,
+ context: {
+ referential: @referential
+ }
+ )
+ end
+
+ def sort_column
+ Chouette::PurchaseWindow.column_names.include?(params[:sort]) ? params[:sort] : 'name'
+ end
+
+ def sort_direction
+ %w[asc desc].include?(params[:direction]) ? params[:direction] : 'asc'
+ end
+
+ def collection
+ return @purchase_windows if @purchase_windows
+ @q = Chouette::PurchaseWindow.ransack(params[:q])
+
+ purchase_windows = @q.result
+ purchase_windows = purchase_windows.order(sort_column + ' ' + sort_direction) if sort_column && sort_direction
+ @purchase_windows = purchase_windows.paginate(page: params[:page])
+ end
+
+ def ransack_contains_date
+ date =[]
+ if params[:q] && params[:q]['contains_date(1i)'].present?
+ ['contains_date(1i)', 'contains_date(2i)', 'contains_date(3i)'].each do |key|
+ date << params[:q][key].to_i
+ params[:q].delete(key)
+ end
+ params[:q]['contains_date'] = @date = Date.new(*date) rescue nil
+ end
+ end
+end
diff --git a/app/controllers/referential_companies_controller.rb b/app/controllers/referential_companies_controller.rb
index ca1ff67db..806a70c8f 100644
--- a/app/controllers/referential_companies_controller.rb
+++ b/app/controllers/referential_companies_controller.rb
@@ -35,7 +35,8 @@ class ReferentialCompaniesController < ChouetteController
def collection
scope = referential.line_referential.companies
if params[:line_id]
- scope = referential.line_referential.lines.find(params[:line_id]).companies
+ line_scope = referential.line_referential.lines.find(params[:line_id]).companies
+ scope = line_scope if line_scope.exists?
end
@q = scope.search(params[:q])
@@ -68,10 +69,13 @@ class ReferentialCompaniesController < ChouetteController
%w[asc desc].include?(params[:direction]) ? params[:direction] : 'asc'
end
+ def collection_name
+ "companies"
+ end
+
def decorate_companies(companies)
- ModelDecorator.decorate(
+ CompanyDecorator.decorate(
companies,
- with: CompanyDecorator,
context: {
referential: referential
}
diff --git a/app/controllers/referential_lines_controller.rb b/app/controllers/referential_lines_controller.rb
index 9e8f5c512..37051faeb 100644
--- a/app/controllers/referential_lines_controller.rb
+++ b/app/controllers/referential_lines_controller.rb
@@ -28,9 +28,8 @@ class ReferentialLinesController < ChouetteController
@routes = @routes.paginate(page: params[:page], per_page: 10)
- @routes = ModelDecorator.decorate(
+ @routes = RouteDecorator.decorate(
@routes,
- with: RouteDecorator,
context: {
referential: referential,
line: @line
diff --git a/app/controllers/referential_networks_controller.rb b/app/controllers/referential_networks_controller.rb
index b2d83f953..fe00a99df 100644
--- a/app/controllers/referential_networks_controller.rb
+++ b/app/controllers/referential_networks_controller.rb
@@ -56,6 +56,10 @@ class ReferentialNetworksController < ChouetteController
end
end
+ def collection_name
+ 'networks'
+ end
+
def resource_url(network = nil)
referential_network_path(referential, network || resource)
end
@@ -78,9 +82,8 @@ class ReferentialNetworksController < ChouetteController
end
def decorate_networks(networks)
- ModelDecorator.decorate(
+ ReferentialNetworkDecorator.decorate(
networks,
- with: ReferentialNetworkDecorator,
context: {
referential: referential
}
diff --git a/app/controllers/referential_vehicle_journeys_controller.rb b/app/controllers/referential_vehicle_journeys_controller.rb
new file mode 100644
index 000000000..2ce28a5cc
--- /dev/null
+++ b/app/controllers/referential_vehicle_journeys_controller.rb
@@ -0,0 +1,30 @@
+#
+# Browse all VehicleJourneys of the Referential
+#
+class ReferentialVehicleJourneysController < ChouetteController
+ include ReferentialSupport
+ include RansackDateFilter
+
+ before_action only: [:index] { set_date_time_params("purchase_window", Date, prefix: :purchase_window) }
+ before_action only: [:index] { set_date_time_params("time_table", Date, prefix: :time_table) }
+
+ defaults :resource_class => Chouette::VehicleJourney, collection_name: :vehicle_journeys
+
+ requires_feature :referential_vehicle_journeys
+
+ private
+
+ def collection
+ @q ||= end_of_association_chain
+ @q = @q.with_stop_area_ids(params[:q][:stop_area_ids]) if params[:q] && params[:q][:stop_area_ids]
+ @q = ransack_period_range(scope: @q, error_message: t('vehicle_journeys.errors.purchase_window'), query: :in_purchase_window, prefix: :purchase_window)
+ @q = ransack_period_range(scope: @q, error_message: t('vehicle_journeys.errors.time_table'), query: :with_matching_timetable, prefix: :time_table)
+ @q = @q.ransack(params[:q])
+ @vehicle_journeys ||= @q.result.order(:published_journey_name).includes(:vehicle_journey_at_stops).paginate page: params[:page], per_page: params[:per_page] || 10
+ @all_companies = Chouette::Company.where("id IN (#{@referential.vehicle_journeys.select(:company_id).to_sql})").distinct
+ @all_stop_areas = Chouette::StopArea.where("id IN (#{@referential.vehicle_journeys.joins(:stop_areas).select("stop_areas.id").to_sql})").distinct
+ stop_area_ids = params[:q].try(:[], :stop_area_ids).try(:select, &:present?)
+ @filters_stop_areas = Chouette::StopArea.find(stop_area_ids) if stop_area_ids.present? && stop_area_ids.size <= 2
+ end
+
+end
diff --git a/app/controllers/referentials_controller.rb b/app/controllers/referentials_controller.rb
index ee1236912..0ed3f75dd 100644
--- a/app/controllers/referentials_controller.rb
+++ b/app/controllers/referentials_controller.rb
@@ -8,16 +8,21 @@ class ReferentialsController < ChouetteController
def new
new! do
- build_referenial
+ build_referential
end
end
def create
- create! do |format|
- build_referenial
-
- if !!@referential.created_from_id
- format.html { redirect_to workbench_path(@referential.workbench) }
+ create! do |success, failure|
+ success.html do
+ if @referential.created_from_id.present?
+ flash[:notice] = t('notice.referentials.duplicate')
+ end
+ redirect_to workbench_path(@referential.workbench)
+ end
+ failure.html do
+ Rails.logger.info "Can't create Referential : #{@referential.errors.inspect}"
+ render :new
end
end
end
@@ -27,9 +32,8 @@ class ReferentialsController < ChouetteController
show! do |format|
@referential = @referential.decorate(context: { current_workbench_id: params[:current_workbench_id] } )
@reflines = lines_collection.paginate(page: params[:page], per_page: 10)
- @reflines = ModelDecorator.decorate(
+ @reflines = ReferentialLineDecorator.decorate(
@reflines,
- with: ReferentialLineDecorator,
context: {
referential: referential,
current_organisation: current_organisation
@@ -60,8 +64,8 @@ class ReferentialsController < ChouetteController
def validate
ComplianceControlSetCopyWorker.perform_async(params[:compliance_control_set], params[:id])
- flash[:notice] = I18n.t("referentials.operation_in_progress")
- redirect_to(referential_path)
+ flash[:notice] = t('notice.referentials.validate')
+ redirect_to workbench_compliance_check_sets_path(referential.workbench_id)
end
def destroy
@@ -75,6 +79,7 @@ class ReferentialsController < ChouetteController
referential.archive!
redirect_to workbench_path(referential.workbench_id), notice: t('notice.referential.archived')
end
+
def unarchive
if referential.unarchive!
flash[:notice] = t('notice.referential.unarchived')
@@ -92,7 +97,7 @@ class ReferentialsController < ChouetteController
helper_method :current_referential
def resource
- @referential ||= current_organisation.find_referential(params[:id])
+ @referential ||= current_organisation.find_referential(params[:id]).decorate
end
def collection
@@ -132,7 +137,7 @@ class ReferentialsController < ChouetteController
super
end
- def build_referenial
+ def build_referential
if params[:from]
source_referential = Referential.find(params[:from])
@referential = Referential.new_from(source_referential, current_functional_scope)
diff --git a/app/controllers/routes_controller.rb b/app/controllers/routes_controller.rb
index 79f49143a..af5a9a91b 100644
--- a/app/controllers/routes_controller.rb
+++ b/app/controllers/routes_controller.rb
@@ -47,10 +47,7 @@ class RoutesController < ChouetteController
line: @line
})
- @route_sp = ModelDecorator.decorate(
- @route_sp,
- with: StopPointDecorator
- )
+ @route_sp = StopPointDecorator.decorate(@route_sp)
end
end
diff --git a/app/controllers/routing_constraint_zones_controller.rb b/app/controllers/routing_constraint_zones_controller.rb
index a72b288b8..47df211d0 100644
--- a/app/controllers/routing_constraint_zones_controller.rb
+++ b/app/controllers/routing_constraint_zones_controller.rb
@@ -13,9 +13,8 @@ class RoutingConstraintZonesController < ChouetteController
def index
index! do |format|
- @routing_constraint_zones = ModelDecorator.decorate(
+ @routing_constraint_zones = RoutingConstraintZoneDecorator.decorate(
@routing_constraint_zones,
- with: RoutingConstraintZoneDecorator,
context: {
referential: referential,
line: parent
diff --git a/app/controllers/snapshots_controller.rb b/app/controllers/snapshots_controller.rb
new file mode 100644
index 000000000..e453b4965
--- /dev/null
+++ b/app/controllers/snapshots_controller.rb
@@ -0,0 +1,14 @@
+class SnapshotsController < ApplicationController
+ if Rails.env.development? || Rails.env.test?
+ layout :which_layout
+ def show
+ tpl = params[:snap]
+ tpl = tpl.gsub Rails.root.to_s, ''
+ render file: tpl
+ end
+
+ def which_layout
+ "snapshots/#{params[:layout] || "default"}"
+ end
+ end
+end
diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb
new file mode 100644
index 000000000..e38a92982
--- /dev/null
+++ b/app/controllers/statuses_controller.rb
@@ -0,0 +1,20 @@
+class StatusesController < ChouetteController
+ respond_to :json
+
+ def index
+
+ status = {
+ referentials_blocked: Referential.blocked.count,
+ imports_blocked: Import.blocked.count,
+ compliance_check_sets_blocked: ComplianceCheckSet.blocked.count
+ }
+ status[:status] = global_status status
+ render json: status.to_json
+ end
+
+ private
+
+ def global_status status
+ status.values.all?(&:zero?) ? 'ok' : 'ko'
+ end
+end
diff --git a/app/controllers/stop_area_referentials_controller.rb b/app/controllers/stop_area_referentials_controller.rb
index 85541230d..f2d375e49 100644
--- a/app/controllers/stop_area_referentials_controller.rb
+++ b/app/controllers/stop_area_referentials_controller.rb
@@ -2,6 +2,7 @@ class StopAreaReferentialsController < ChouetteController
defaults :resource_class => StopAreaReferential
def sync
+ authorize resource, :synchronize?
@sync = resource.stop_area_referential_syncs.build
if @sync.save
flash[:notice] = t('notice.stop_area_referential_sync.created')
diff --git a/app/controllers/stop_areas_controller.rb b/app/controllers/stop_areas_controller.rb
index 133518324..41a1a8c6d 100644
--- a/app/controllers/stop_areas_controller.rb
+++ b/app/controllers/stop_areas_controller.rb
@@ -1,5 +1,6 @@
class StopAreasController < ChouetteController
include ApplicationHelper
+ include Activatable
defaults :resource_class => Chouette::StopArea
@@ -13,10 +14,12 @@ class StopAreasController < ChouetteController
respond_to :html, :kml, :xml, :json
respond_to :js, :only => :index
- # def complete
- # @stop_areas = line.stop_areas
- # render :layout => false
- # end
+ def autocomplete
+ scope = stop_area_referential.stop_areas.where(deleted_at: nil)
+ args = [].tap{|arg| 4.times{arg << "%#{params[:q]}%"}}
+ @stop_areas = scope.where("unaccent(name) ILIKE unaccent(?) OR unaccent(city_name) ILIKE unaccent(?) OR registration_number ILIKE ? OR objectid ILIKE ?", *args).limit(50)
+ @stop_areas
+ end
def select_parent
@stop_area = stop_area
@@ -54,10 +57,7 @@ class StopAreasController < ChouetteController
redirect_to params.merge(:page => 1)
end
- @stop_areas = ModelDecorator.decorate(
- @stop_areas,
- with: StopAreaDecorator
- )
+ @stop_areas = StopAreaDecorator.decorate(@stop_areas)
}
end
end
@@ -95,9 +95,8 @@ class StopAreasController < ChouetteController
def edit
authorize stop_area
edit! do
- stop_area.position ||= stop_area.default_position
map.editable = true
- end
+ end
end
def destroy
@@ -107,7 +106,6 @@ class StopAreasController < ChouetteController
def update
authorize stop_area
- stop_area.position ||= stop_area.default_position
map.editable = true
update!
@@ -154,6 +152,10 @@ class StopAreasController < ChouetteController
end
end
+ def begin_of_association_chain
+ current_organisation
+ end
+
private
def sort_column
@@ -171,7 +173,37 @@ class StopAreasController < ChouetteController
helper_method :current_referential
def stop_area_params
- params.require(:stop_area).permit( :routing_stop_ids, :routing_line_ids, :children_ids, :stop_area_type, :parent_id, :objectid, :object_version, :name, :comment, :area_type, :registration_number, :nearest_topic_name, :fare_code, :longitude, :latitude, :long_lat_type, :country_code, :street_name, :zip_code, :city_name, :mobility_restricted_suitability, :stairs_availability, :lift_availability, :int_user_needs, :coordinates, :url, :time_zone )
+ params.require(:stop_area).permit(
+ :area_type,
+ :children_ids,
+ :city_name,
+ :comment,
+ :coordinates,
+ :country_code,
+ :fare_code,
+ :int_user_needs,
+ :latitude,
+ :lift_availability,
+ :long_lat_type,
+ :longitude,
+ :mobility_restricted_suitability,
+ :name,
+ :nearest_topic_name,
+ :object_version,
+ :objectid,
+ :parent_id,
+ :registration_number,
+ :routing_line_ids,
+ :routing_stop_ids,
+ :stairs_availability,
+ :street_name,
+ :time_zone,
+ :url,
+ :waiting_time,
+ :zip_code,
+ :kind,
+ localized_names: Chouette::StopArea::AVAILABLE_LOCALIZATIONS
+ )
end
end
diff --git a/app/controllers/time_tables_controller.rb b/app/controllers/time_tables_controller.rb
index a0fa168f0..0dcadad1e 100644
--- a/app/controllers/time_tables_controller.rb
+++ b/app/controllers/time_tables_controller.rb
@@ -36,7 +36,6 @@ class TimeTablesController < ChouetteController
def create
tt_params = time_table_params
if tt_params[:calendar_id] && tt_params[:calendar_id] != ""
- %i(monday tuesday wednesday thursday friday saturday sunday).map { |d| tt_params[d] = true }
calendar = Calendar.find(tt_params[:calendar_id])
tt_params[:calendar_id] = nil if tt_params.has_key?(:dates_attributes) || tt_params.has_key?(:periods_attributes)
end
@@ -45,6 +44,7 @@ class TimeTablesController < ChouetteController
@time_table = created_from ? created_from.duplicate : Chouette::TimeTable.new(tt_params)
if calendar
+ @time_table.int_day_types = calendar.int_day_types
calendar.dates.each_with_index do |date, i|
@time_table.dates << Chouette::TimeTableDate.new(date: date, position: i, in_out: true)
end
@@ -167,9 +167,8 @@ class TimeTablesController < ChouetteController
end
def decorate_time_tables(time_tables)
- ModelDecorator.decorate(
+ TimeTableDecorator.decorate(
time_tables,
- with: TimeTableDecorator,
context: {
referential: @referential
}
diff --git a/app/controllers/vehicle_journeys_controller.rb b/app/controllers/vehicle_journeys_controller.rb
index c941aeae4..e031e4952 100644
--- a/app/controllers/vehicle_journeys_controller.rb
+++ b/app/controllers/vehicle_journeys_controller.rb
@@ -40,43 +40,26 @@ class VehicleJourneysController < ChouetteController
end
def index
- @stop_points_list = []
- route.stop_points.each do |sp|
- @stop_points_list << {
- :id => sp.stop_area.id,
- :route_id => sp.try(:route_id),
- :object_id => sp.try(:objectid),
- :position => sp.try(:position),
- :for_boarding => sp.try(:for_boarding),
- :for_alighting => sp.try(:for_alighting),
- :name => sp.stop_area.try(:name),
- :zip_code => sp.stop_area.try(:zip_code),
- :city_name => sp.stop_area.try(:city_name),
- :comment => sp.stop_area.try(:comment),
- :area_type => sp.stop_area.try(:area_type),
- :registration_number => sp.stop_area.try(:registration_number),
- :nearest_topic_name => sp.stop_area.try(:nearest_topic_name),
- :fare_code => sp.stop_area.try(:fare_code),
- :longitude => sp.stop_area.try(:longitude),
- :latitude => sp.stop_area.try(:latitude),
- :long_lat_type => sp.stop_area.try(:long_lat_type),
- :country_code => sp.stop_area.try(:country_code),
- :street_name => sp.stop_area.try(:street_name)
- }
- end
-
- @transport_mode = route.line['transport_mode']
- @transport_submode = route.line['transport_submode']
-
- if params[:jp]
- @jp_origin = Chouette::JourneyPattern.find_by(objectid: params[:jp])
- @jp_origin_stop_points = @jp_origin.stop_points
- end
-
- index! do
+ index! do |format|
if collection.out_of_bounds?
redirect_to params.merge(:page => 1)
end
+ format.json do
+ @vehicle_journeys = @vehicle_journeys.includes({stop_points: :stop_area})
+ end
+ format.html do
+ load_missions
+ load_custom_fields
+ @stop_points_list = map_stop_points(route.stop_points)
+ @return_stop_points_list = map_stop_points(route.opposite_route&.stop_points) if has_feature?(:vehicle_journeys_return_route)
+ @transport_mode = route.line['transport_mode']
+ @transport_submode = route.line['transport_submode']
+
+ if params[:jp]
+ @jp_origin = Chouette::JourneyPattern.find_by(objectid: params[:jp])
+ @jp_origin_stop_points = @jp_origin.stop_points
+ end
+ end
end
end
@@ -92,13 +75,15 @@ class VehicleJourneysController < ChouetteController
scope = maybe_filter_by_departure_time(scope)
scope = maybe_filter_out_journeys_with_time_tables(scope)
- @q = scope.search filtered_ransack_params
+ @vehicle_journeys ||= begin
+ @q = scope.search filtered_ransack_params
- @ppage = 20
- @vehicle_journeys = @q.result.paginate(:page => params[:page], :per_page => @ppage)
- @footnotes = route.line.footnotes.to_json
- @matrix = resource_class.matrix(@vehicle_journeys)
- @vehicle_journeys
+ @ppage = 20
+ vehicle_journeys = @q.result.paginate(:page => params[:page], :per_page => @ppage)
+ @footnotes = route.line.footnotes.to_json
+ @matrix = resource_class.matrix(vehicle_journeys)
+ vehicle_journeys
+ end
end
def maybe_filter_by_departure_time(scope)
@@ -159,6 +144,7 @@ class VehicleJourneysController < ChouetteController
end
def user_permissions
+ @features = Hash[*current_organisation.features.map{|f| [f, true]}.flatten].to_json
policy = policy(:vehicle_journey)
@perms =
%w{create destroy update}.inject({}) do | permissions, action |
@@ -167,6 +153,69 @@ class VehicleJourneysController < ChouetteController
end
private
+ def load_custom_fields
+ @custom_fields = current_workgroup.custom_fields_definitions
+ end
+
+ def map_stop_points points
+ (points&.includes(:stop_area) || []).map do |sp|
+ {
+ :id => sp.stop_area.id,
+ :route_id => sp.try(:route_id),
+ :object_id => sp.try(:objectid),
+ :area_object_id => sp.stop_area.try(:objectid),
+ :position => sp.try(:position),
+ :for_boarding => sp.try(:for_boarding),
+ :for_alighting => sp.try(:for_alighting),
+ :name => sp.stop_area.try(:name),
+ :time_zone_offset => sp.stop_area.try(:time_zone_offset),
+ :time_zone_formatted_offset => sp.stop_area.try(:time_zone_formatted_offset),
+ :zip_code => sp.stop_area.try(:zip_code),
+ :city_name => sp.stop_area.try(:city_name),
+ :comment => sp.stop_area.try(:comment),
+ :area_type => sp.stop_area.try(:area_type),
+ :area_type_i18n => I18n.t(sp.stop_area.try(:area_type), scope: 'area_types.label'),
+ :area_kind => sp.stop_area.try(:kind),
+ :stop_area_id => sp.stop_area_id,
+ :registration_number => sp.stop_area.try(:registration_number),
+ :nearest_topic_name => sp.stop_area.try(:nearest_topic_name),
+ :fare_code => sp.stop_area.try(:fare_code),
+ :longitude => sp.stop_area.try(:longitude),
+ :latitude => sp.stop_area.try(:latitude),
+ :long_lat_type => sp.stop_area.try(:long_lat_type),
+ :country_code => sp.stop_area.try(:country_code),
+ :country_name => sp.stop_area.try(:country_name),
+ :street_name => sp.stop_area.try(:street_name)
+ }
+ end
+ end
+
+ def load_missions
+ @all_missions = route.journey_patterns.count > 10 ? [] : route.journey_patterns.map do |item|
+ {
+ id: item.id,
+ "data-item": {
+ id: item.id,
+ name: item.name,
+ published_name: item.published_name,
+ object_id: item.objectid,
+ short_id: item.get_objectid.short_id,
+ full_schedule: item.full_schedule?,
+ costs: item.costs,
+ stop_area_short_descriptions: item.stop_areas.map do |stop|
+ {
+ stop_area_short_description: {
+ id: stop.id,
+ name: stop.name,
+ object_id: item.objectid
+ }
+ }
+ end
+ }.to_json,
+ text: "<strong>#{item.published_name} - #{item.get_objectid.short_id}</strong><br/><small>#{item.registration_number}</small>"
+ }
+ end
+ end
def vehicle_journey_params
params.require(:vehicle_journey).permit(
{ footnote_ids: [] },
diff --git a/app/controllers/workbench_outputs_controller.rb b/app/controllers/workbench_outputs_controller.rb
new file mode 100644
index 000000000..67ed7569e
--- /dev/null
+++ b/app/controllers/workbench_outputs_controller.rb
@@ -0,0 +1,9 @@
+class WorkbenchOutputsController < ChouetteController
+ respond_to :html, only: [:show]
+ defaults resource_class: Workbench
+
+ def show
+ @workbench = current_organisation.workbenches.find params[:workbench_id]
+ @workbench_merges = @workbench.merges.order("created_at desc").paginate(page: params[:page], per_page: 10)
+ end
+end
diff --git a/app/controllers/workbenches_controller.rb b/app/controllers/workbenches_controller.rb
index b2dac9e67..2a71fe811 100644
--- a/app/controllers/workbenches_controller.rb
+++ b/app/controllers/workbenches_controller.rb
@@ -18,9 +18,8 @@ class WorkbenchesController < ChouetteController
@q_for_form = scope.ransack(params[:q])
@q_for_result = scope.ransack(ransack_params)
@wbench_refs = sort_result(@q_for_result.result).paginate(page: params[:page], per_page: 30)
- @wbench_refs = ModelDecorator.decorate(
+ @wbench_refs = ReferentialDecorator.decorate(
@wbench_refs,
- with: ReferentialDecorator,
context: {
current_workbench_id: params[:id]
}
diff --git a/app/decorators/api_key_decorator.rb b/app/decorators/api_key_decorator.rb
deleted file mode 100644
index def3a6a01..000000000
--- a/app/decorators/api_key_decorator.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-class ApiKeyDecorator < Draper::Decorator
- decorates Api::V1::ApiKey
- delegate_all
-
-
- def action_links
- links = []
-
- links << Link.new(
- content: h.t('api_keys.actions.show'),
- href: h.organisation_api_key_path(object),
- )
-
- links << Link.new(
- content: h.t('api_keys.actions.edit'),
- href: h.edit_organisation_api_key_path(object),
- )
-
- if h.policy(object).destroy?
- links << Link.new(
- content: h.destroy_link_content,
- href: h.organisation_api_key_path(object),
- method: :delete,
- data: { confirm: h.t('api_keys.actions.destroy_confirm') }
- )
- end
-
- links
- end
-end
diff --git a/app/decorators/calendar_decorator.rb b/app/decorators/calendar_decorator.rb
index 37e2cfe80..4c6088e8e 100644
--- a/app/decorators/calendar_decorator.rb
+++ b/app/decorators/calendar_decorator.rb
@@ -1,18 +1,13 @@
-class CalendarDecorator < Draper::Decorator
- delegate_all
+class CalendarDecorator < AF83::Decorator
+ decorates Calendar
+ set_scope { context[:workgroup] }
+ create_action_link
- def action_links
- links = []
-
- if h.policy(object).destroy?
- links << Link.new(
- content: h.destroy_link_content,
- href: h.calendar_path(object),
- method: :delete,
- data: { confirm: h.t('calendars.actions.destroy_confirm') }
- )
+ with_instance_decorator do |instance_decorator|
+ instance_decorator.show_action_link
+ instance_decorator.edit_action_link
+ instance_decorator.destroy_action_link do |l|
+ l.data {{ confirm: h.t('calendars.actions.destroy_confirm') }}
end
-
- links
end
end
diff --git a/app/decorators/company_decorator.rb b/app/decorators/company_decorator.rb
index 9416c73ae..5580e0d4a 100644
--- a/app/decorators/company_decorator.rb
+++ b/app/decorators/company_decorator.rb
@@ -1,52 +1,22 @@
-class CompanyDecorator < Draper::Decorator
+class CompanyDecorator < AF83::Decorator
decorates Chouette::Company
- delegate_all
+ set_scope { context[:referential] }
- def self.collection_decorator_class
- PaginatingDecorator
+ create_action_link do |l|
+ l.content { h.t('companies.actions.new') }
end
- def linecount
- object.lines.count
- end
-
- # Requires:
- # context: {
- # referential:
- # }
- def action_links
- links = []
+ with_instance_decorator do |instance_decorator|
+ instance_decorator.show_action_link
- if h.policy(Chouette::Company).create?
- links << Link.new(
- content: h.t('companies.actions.new'),
- href: h.new_line_referential_company_path(context[:referential])
- )
+ instance_decorator.edit_action_link do |l|
+ l.content {|l| l.action == "show" ? h.t('actions.edit') : h.t('companies.actions.edit') }
end
- if h.policy(object).update?
- links << Link.new(
- content: h.t('companies.actions.edit'),
- href: h.edit_line_referential_company_path(
- context[:referential],
- object
- )
- )
+ instance_decorator.destroy_action_link do |l|
+ l.content { h.destroy_link_content('companies.actions.destroy') }
+ l.data {{ confirm: h.t('companies.actions.destroy_confirm') }}
end
-
- if h.policy(object).destroy?
- links << Link.new(
- content: t('companies.actions.destroy'),
- href: h.line_referential_company_path(
- context[:referential],
- object
- ),
- method: :delete,
- data: { confirm: h.t('companies.actions.destroy_confirm') }
- )
- end
-
- links
end
end
diff --git a/app/decorators/compliance_check_decorator.rb b/app/decorators/compliance_check_decorator.rb
deleted file mode 100644
index 5431f5796..000000000
--- a/app/decorators/compliance_check_decorator.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-class ComplianceCheckDecorator < Draper::Decorator
- delegate_all
-
- def action_links
- []
- end
-
-end
diff --git a/app/decorators/compliance_check_set_decorator.rb b/app/decorators/compliance_check_set_decorator.rb
index 096596b19..334f39e88 100644
--- a/app/decorators/compliance_check_set_decorator.rb
+++ b/app/decorators/compliance_check_set_decorator.rb
@@ -1,24 +1,15 @@
-class ComplianceCheckSetDecorator < Draper::Decorator
- delegate_all
+class ComplianceCheckSetDecorator < AF83::Decorator
+ decorates ComplianceCheckSet
- def action_links
- links = []
-
- links << Link.new(
- content: h.destroy_link_content,
- href: h.workbench_compliance_check_sets_path(object.id),
- method: :delete,
- data: {confirm: h.t('imports.actions.destroy_confirm')}
- )
-
- links
+ with_instance_decorator do |instance_decorator|
+ instance_decorator.show_action_link
end
- def lines_status
+ define_instance_method :lines_status do
object.compliance_check_resources.where(status: :OK, resource_type: :line).count
end
- def lines_in_compliance_check_set
+ define_instance_method :lines_in_compliance_check_set do
object.compliance_check_resources.where(resource_type: :line).count
end
diff --git a/app/decorators/compliance_control_decorator.rb b/app/decorators/compliance_control_decorator.rb
index f56e80417..fd2dbd9ce 100644
--- a/app/decorators/compliance_control_decorator.rb
+++ b/app/decorators/compliance_control_decorator.rb
@@ -1,30 +1,35 @@
-class ComplianceControlDecorator < Draper::Decorator
- delegate_all
+class ComplianceControlDecorator < AF83::Decorator
+ decorates ComplianceControl
- def action_links
- policy = h.policy(object)
- links = []
+ set_scope { object.compliance_control_set }
- links << Link.new(
- content: h.t('compliance_control_sets.actions.show'),
- href: h.compliance_control_set_compliance_control_path(object.compliance_control_set.id, object.id)
- )
-
- if policy.edit?
- links << Link.new(
- content: h.t('compliance_controls.actions.edit'),
- href: h.edit_compliance_control_set_compliance_control_path(object.compliance_control_set.id, object.id)
- )
+ with_instance_decorator do |instance_decorator|
+ instance_decorator.show_action_link do |l|
+ l.content h.t('compliance_control_sets.actions.show')
+ l.href do
+ h.compliance_control_set_compliance_control_path(
+ object.compliance_control_set.id,
+ object.id
+ )
+ end
end
- if policy.destroy?
- links << Link.new(
- content: h.destroy_link_content,
- href: h.compliance_control_set_compliance_control_path(object.compliance_control_set.id, object.id),
- method: :delete,
- data: { confirm: h.t('compliance_controls.actions.destroy_confirm') }
- )
+ instance_decorator.edit_action_link
+
+ instance_decorator.destroy_action_link do |l|
+ l.data confirm: h.t('compliance_controls.actions.destroy_confirm')
end
- links
+ end
+
+ define_instance_class_method :predicate do
+ object_class.predicate
+ end
+
+ define_instance_class_method :prerequisite do
+ object_class.prerequisite
+ end
+
+ define_instance_class_method :dynamic_attributes do
+ object_class.dynamic_attributes
end
end
diff --git a/app/decorators/compliance_control_set_decorator.rb b/app/decorators/compliance_control_set_decorator.rb
index 73d65d54a..b16a06886 100644
--- a/app/decorators/compliance_control_set_decorator.rb
+++ b/app/decorators/compliance_control_set_decorator.rb
@@ -1,35 +1,26 @@
-class ComplianceControlSetDecorator < Draper::Decorator
- delegate_all
+class ComplianceControlSetDecorator < AF83::Decorator
+ decorates ComplianceControlSet
- def action_links
- policy = h.policy(object)
- links = []
+ create_action_link do |l|
+ l.content t('compliance_control_sets.actions.new')
+ end
- if policy.edit?
- links << Link.new(
- content: h.t('compliance_control_sets.actions.edit'),
- href: h.edit_compliance_control_set_path(object.id)
- )
- end
+ with_instance_decorator do |instance_decorator|
+ instance_decorator.show_action_link
- if policy.clone?
- links << Link.new(
- content: h.t('actions.clone'),
- href: h.clone_compliance_control_set_path(object.id)
- )
+ instance_decorator.edit_action_link do |l|
+ l.content t('compliance_control_sets.actions.edit')
end
- if policy.destroy?
- links << Link.new(
- content: h.destroy_link_content,
- href: h.compliance_control_set_path(object.id),
- method: :delete,
- data: { confirm: h.t('compliance_control_sets.actions.destroy_confirm') }
- )
+ instance_decorator.action_link policy: :clone, secondary: :show do |l|
+ l.content t('actions.clone')
+ l.href { h.clone_compliance_control_set_path(object.id) }
end
- links
+ instance_decorator.destroy_action_link do |l|
+ l.content h.destroy_link_content
+ l.href { h.compliance_control_set_path(object.id) }
+ l.data confirm: h.t('compliance_control_sets.actions.destroy_confirm')
+ end
end
-
end
-
diff --git a/app/decorators/import_decorator.rb b/app/decorators/import_decorator.rb
index e748f830d..1964365ae 100644
--- a/app/decorators/import_decorator.rb
+++ b/app/decorators/import_decorator.rb
@@ -1,9 +1,9 @@
-class ImportDecorator < Draper::Decorator
+class ImportDecorator < AF83::Decorator
decorates Import
- delegate_all
+ set_scope { context[:workbench] }
- def import_status_css_class
+ define_instance_method :import_status_css_class do
cls =''
cls = 'overheaded-success' if object.status == 'successful'
cls = 'overheaded-warning' if object.status == 'warning'
@@ -11,36 +11,16 @@ class ImportDecorator < Draper::Decorator
cls
end
- def action_links
- policy = h.policy(object)
- links = []
-
- links << Link.new(
- content: h.t('imports.actions.show'),
- href: h.workbench_import_path(
- context[:workbench],
- object
- )
- )
+ create_action_link do |l|
+ l.content t('imports.actions.new')
+ end
- links << Link.new(
- content: h.t('imports.actions.download'),
- href: object.file.url
- )
+ with_instance_decorator do |instance_decorator|
+ instance_decorator.show_action_link
- if policy.destroy?
- links << Link.new(
- content: h.destroy_link_content,
- href: h.workbench_import_path(
- context[:workbench],
- object
- ),
- method: :delete,
- data: { confirm: h.t('imports.actions.destroy_confirm') }
- )
+ instance_decorator.action_link secondary: :show do |l|
+ l.content t('imports.actions.download')
+ l.href { object.file.url }
end
-
- links
end
-
end
diff --git a/app/decorators/import_resource_decorator.rb b/app/decorators/import_resource_decorator.rb
deleted file mode 100644
index 9bfd1f757..000000000
--- a/app/decorators/import_resource_decorator.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-class ImportResourceDecorator < Draper::Decorator
- decorates ImportResource
-
- delegate_all
-
- def action_links
- links = []
- end
-
-end
diff --git a/app/decorators/import_resources_decorator.rb b/app/decorators/import_resources_decorator.rb
index 2b1a25ef9..88a8057cf 100644
--- a/app/decorators/import_resources_decorator.rb
+++ b/app/decorators/import_resources_decorator.rb
@@ -1,4 +1,4 @@
-class ImportResourcesDecorator < ModelDecorator
+class ImportResourcesDecorator < AF83::Decorator
delegate :where
def lines_imported
diff --git a/app/decorators/line_decorator.rb b/app/decorators/line_decorator.rb
index ede670cbd..0e7b6b9ae 100644
--- a/app/decorators/line_decorator.rb
+++ b/app/decorators/line_decorator.rb
@@ -1,55 +1,70 @@
-class LineDecorator < Draper::Decorator
+class LineDecorator < AF83::Decorator
decorates Chouette::Line
- delegate_all
-
- # Requires:
- # context: {
- # line_referential: ,
- # current_organisation:
- # }
- def action_links
- links = []
-
- links << Link.new(
- content: h.t('lines.actions.show_network'),
- href: [context[:line_referential], object.network]
- )
-
- links << Link.new(
- content: h.t('lines.actions.show_company'),
- href: [context[:line_referential], object.company]
- )
-
- if h.policy(Chouette::Line).create? &&
- context[:line_referential].organisations.include?(
- context[:current_organisation]
- )
- links << Link.new(
- content: h.t('lines.actions.edit'),
- href: h.edit_line_referential_line_path(context[:line_referential], object.id)
- )
+ set_scope { context[:line_referential] }
+
+ create_action_link do |l|
+ l.content t('lines.actions.new')
+ end
+
+ with_instance_decorator do |instance_decorator|
+ ### primary (and secondary) can be
+ ### - a single action
+ ### - an array of actions
+ ### - a boolean
+
+ instance_decorator.show_action_link do |l|
+ l.content t('lines.actions.show')
+ end
+
+ instance_decorator.action_link secondary: :show do |l|
+ l.content t('lines.actions.show_network')
+ l.href { [scope, object.network] }
+ l.disabled { object.network.nil? }
+ end
+
+ instance_decorator.action_link secondary: :show do |l|
+ l.content t('lines.actions.show_company')
+ l.href { [scope, object.company] }
+ l.disabled { object.company.nil? }
end
- if h.policy(Chouette::Line).create? &&
- context[:line_referential].organisations.include?(
- context[:current_organisation]
- )
- links << Link.new(
- content: h.t('lines.actions.new'),
- href: h.new_line_referential_line_path(context[:line_referential])
- )
+ can_edit_line = ->(){ h.policy(Chouette::Line).create? && context[:line_referential].organisations.include?(context[:current_organisation]) }
+
+ instance_decorator.with_condition can_edit_line do
+ edit_action_link do |l|
+ l.content {|l| l.primary? ? h.t('actions.edit') : h.t('lines.actions.edit') }
+ end
+
+ action_link on: :index, secondary: :index do |l|
+ l.content t('lines.actions.new')
+ l.href { h.new_line_referential_line_path(context[:line_referential]) }
+ end
end
- if h.policy(object).destroy?
- links << Link.new(
- content: h.destroy_link_content('lines.actions.destroy'),
- href: h.line_referential_line_path(context[:line_referential], object),
- method: :delete,
- data: {confirm: h.t('lines.actions.destroy_confirm')}
- )
+ ### the option :policy will automatically check for the corresponding method
+ ### on the object's policy
+
+ instance_decorator.action_link policy: :deactivate, secondary: :show, footer: :index do |l|
+ l.content { h.deactivate_link_content('lines.actions.deactivate') }
+ l.href { h.deactivate_line_referential_line_path(context[:line_referential], object) }
+ l.method :put
+ l.data confirm: h.t('lines.actions.deactivate_confirm')
+ l.add_class "delete-action"
end
- links
+ instance_decorator.action_link policy: :activate, secondary: :show, footer: :index do |l|
+ l.content { h.activate_link_content('lines.actions.activate') }
+ l.href { h.activate_line_referential_line_path(context[:line_referential], object) }
+ l.method :put
+ l.data confirm: h.t('lines.actions.activate_confirm')
+ l.add_class "delete-action"
+ end
+
+ instance_decorator.destroy_action_link do |l|
+ l.content { h.destroy_link_content('lines.actions.destroy') }
+ l.data confirm: h.t('lines.actions.destroy_confirm')
+ l.add_class "delete-action"
+ end
end
end
diff --git a/app/decorators/network_decorator.rb b/app/decorators/network_decorator.rb
index 1f62fe512..ea0f73dc2 100644
--- a/app/decorators/network_decorator.rb
+++ b/app/decorators/network_decorator.rb
@@ -1,44 +1,32 @@
-class NetworkDecorator < Draper::Decorator
+class NetworkDecorator < AF83::Decorator
decorates Chouette::Network
- delegate_all
-
- # Requires:
+ set_scope { context[:line_referential] }
+ # Action links require:
# context: {
# line_referential: ,
# }
- def action_links
- links = []
- if h.policy(Chouette::Network).create?
- links << Link.new(
- content: h.t('networks.actions.new'),
- href: h.new_line_referential_network_path(context[:line_referential])
- )
- end
+ create_action_link do |l|
+ l.content t('networks.actions.new')
+ end
+
+ with_instance_decorator do |instance_decorator|
+ instance_decorator.show_action_link
- if h.policy(object).update?
- links << Link.new(
- content: h.t('networks.actions.edit'),
- href: h.edit_line_referential_network_path(
+ instance_decorator.action_link secondary: true, policy: :edit do |l|
+ l.content t('networks.actions.edit')
+ l.href do
+ h.edit_line_referential_network_path(
context[:line_referential],
object
)
- )
+ end
end
- if h.policy(object).destroy?
- links << Link.new(
- content: h.destroy_link_content('networks.actions.destroy'),
- href: h.line_referential_network_path(
- context[:line_referential],
- object
- ),
- method: :delete,
- data: { confirm: t('networks.actions.destroy_confirm') }
- )
+ instance_decorator.destroy_action_link do |l|
+ l.content h.destroy_link_content('networks.actions.destroy')
+ l.data confirm: h.t('networks.actions.destroy_confirm')
end
-
- links
end
end
diff --git a/app/decorators/purchase_window_decorator.rb b/app/decorators/purchase_window_decorator.rb
new file mode 100644
index 000000000..9b58577b2
--- /dev/null
+++ b/app/decorators/purchase_window_decorator.rb
@@ -0,0 +1,28 @@
+class PurchaseWindowDecorator < AF83::Decorator
+ decorates Chouette::PurchaseWindow
+
+ set_scope { context[:referential] }
+
+ create_action_link do |l|
+ l.content t('purchase_windows.actions.new')
+ end
+
+ with_instance_decorator do |instance_decorator|
+ instance_decorator.show_action_link do |l|
+ l.content t('purchase_windows.actions.show')
+ end
+
+ instance_decorator.edit_action_link
+
+ instance_decorator.destroy_action_link do |l|
+ l.data confirm: h.t('purchase_windows.actions.destroy_confirm')
+ end
+ end
+
+ define_instance_method :bounding_dates do
+ unless object.date_ranges.empty?
+ object.date_ranges.map(&:min).min..object.date_ranges.map(&:max).max
+ end
+ end
+
+end
diff --git a/app/decorators/referential_decorator.rb b/app/decorators/referential_decorator.rb
index 4103790aa..3132cbf92 100644
--- a/app/decorators/referential_decorator.rb
+++ b/app/decorators/referential_decorator.rb
@@ -1,66 +1,60 @@
-class ReferentialDecorator < Draper::Decorator
- delegate_all
+class ReferentialDecorator < AF83::Decorator
+ decorates Referential
- def action_links
- policy = h.policy(object)
- links = [
- Link.new(
- content: h.t('time_tables.index.title'),
- href: h.referential_time_tables_path(object)
- )
- ]
+ with_instance_decorator do |instance_decorator|
+ instance_decorator.show_action_link
+ instance_decorator.edit_action_link
- if policy.clone?
- links << Link.new(
- content: h.t('actions.clone'),
- href: h.new_referential_path(from: object.id, current_workbench_id: context[:current_workbench_id])
- )
+ instance_decorator.action_link feature: :referential_vehicle_journeys, secondary: :show, on: :show do |l|
+ l.content t('referential_vehicle_journeys.index.title')
+ l.href { h.referential_vehicle_journeys_path(object) }
end
- if policy.validate?
- links << Link.new(
- content: h.t('actions.validate'),
- href: h.referential_select_compliance_control_set_path(object.id)
- )
+ instance_decorator.action_link feature: :purchase_windows, secondary: :show, on: :show do |l|
+ l.content t('purchase_windows.index.title')
+ l.href { h.referential_purchase_windows_path(object) }
end
- if policy.archive?
- links << Link.new(
- content: h.t('actions.archive'),
- href: h.archive_referential_path(object.id),
- method: :put
- )
+ instance_decorator.action_link secondary: :show do |l|
+ l.content t('time_tables.index.title')
+ l.href { h.referential_time_tables_path(object) }
end
- if policy.unarchive?
- links << Link.new(
- content: h.t('actions.unarchive'),
- href: h.unarchive_referential_path(object.id),
- method: :put
- )
+ instance_decorator.action_link policy: :clone, secondary: :show do |l|
+ l.content t('actions.clone')
+ l.href { h.new_referential_path(from: object.id, current_workbench_id: context[:current_workbench_id]) }
end
- if policy.edit?
- links << HTMLElement.new(
- :button,
- 'Purger',
- type: 'button',
- data: {
- toggle: 'modal',
- target: '#purgeModal'
- }
- )
+ instance_decorator.action_link policy: :validate, secondary: :show do |l|
+ l.content t('actions.validate')
+ l.href { h.referential_select_compliance_control_set_path(object.id) }
end
- if policy.destroy?
- links << Link.new(
- content: h.destroy_link_content,
- href: h.referential_path(object),
- method: :delete,
- data: { confirm: h.t('referentials.actions.destroy_confirm') }
- )
+ instance_decorator.action_link policy: :archive, secondary: :show do |l|
+ l.content t('actions.archive')
+ l.href { h.archive_referential_path(object.id) }
+ l.method :put
end
- links
+ instance_decorator.action_link policy: :unarchive, secondary: :show, on: :show do |l|
+ l.content t('actions.unarchive')
+ l.href { h.unarchive_referential_path(object.id) }
+ l.method :put
+ end
+
+ instance_decorator.action_link policy: :edit, secondary: :show, on: :show do |l|
+ l.content 'Purger'
+ l.href '#'
+ l.type 'button'
+ l.data {{
+ toggle: 'modal',
+ target: '#purgeModal'
+ }}
+ end
+
+ instance_decorator.destroy_action_link do |l|
+ l.href { h.referential_path(object) }
+ l.data {{ confirm: h.t('referentials.actions.destroy_confirm') }}
+ end
end
end
diff --git a/app/decorators/referential_line_decorator.rb b/app/decorators/referential_line_decorator.rb
index 55acf7ed9..3ac846d76 100644
--- a/app/decorators/referential_line_decorator.rb
+++ b/app/decorators/referential_line_decorator.rb
@@ -1,64 +1,43 @@
-class ReferentialLineDecorator < Draper::Decorator
+class ReferentialLineDecorator < AF83::Decorator
decorates Chouette::Line
- delegate_all
+ set_scope { context[:referential] }
- # Requires:
+ # Action links require:
# context: {
# referential: ,
# current_organisation:
# }
- def action_links
- links = []
- links << Link.new(
- content: Chouette::Line.human_attribute_name(:footnotes),
- href: h.referential_line_footnotes_path(context[:referential], object)
- )
+ with_instance_decorator do |instance_decorator|
+ instance_decorator.show_action_link
- links << Link.new(
- content: h.t('routing_constraint_zones.index.title'),
- href: h.referential_line_routing_constraint_zones_path(
- context[:referential],
- object
- )
- )
-
- if h.policy(Chouette::Line).create? &&
- context[:referential].organisation == context[:current_organisation]
- links << Link.new(
- content: h.t('actions.new'),
- href: h.new_referential_line_path(context[:referential])
- )
- end
-
- if h.policy(object).update?
- links << Link.new(
- content: h.t('actions.edit'),
- href: h.edit_referential_line_path(context[:referential], object)
- )
+ instance_decorator.action_link secondary: true do |l|
+ l.content Chouette::Line.human_attribute_name(:footnotes)
+ l.href { h.referential_line_footnotes_path(context[:referential], object) }
end
- if h.policy(object).destroy?
- links << Link.new(
- content: h.destroy_link_content('actions.destroy'),
- href: h.referential_line_path(context[:referential], object),
- method: :delete,
- data: { confirm: t('lines.actions.destroy_confirm') }
- )
- end
-
- if !object.hub_restricted? ||
- (object.hub_restricted? && object.routes.size < 2)
- if h.policy(Chouette::Route).create? &&
- context[:referential].organisation == context[:current_organisation]
- links << Link.new(
- content: h.t('routes.actions.new'),
- href: h.new_referential_line_route_path(context[:referential], object)
+ instance_decorator.action_link secondary: true do |l|
+ l.content h.t('routing_constraint_zones.index.title')
+ l.href do
+ h.referential_line_routing_constraint_zones_path(
+ scope,
+ object
)
end
end
- links
+ instance_decorator.action_link(
+ if: ->() {
+ (!object.hub_restricted? ||
+ (object.hub_restricted? && object.routes.size < 2)) &&
+ (h.policy(Chouette::Route).create? &&
+ context[:referential].organisation == context[:current_organisation])
+ },
+ secondary: true
+ ) do |l|
+ l.content h.t('routes.actions.new')
+ l.href { h.new_referential_line_route_path(scope, object) }
+ end
end
end
diff --git a/app/decorators/referential_network_decorator.rb b/app/decorators/referential_network_decorator.rb
index 1260a38cb..c508452c0 100644
--- a/app/decorators/referential_network_decorator.rb
+++ b/app/decorators/referential_network_decorator.rb
@@ -1,38 +1,27 @@
-class ReferentialNetworkDecorator < Draper::Decorator
+class ReferentialNetworkDecorator < AF83::Decorator
decorates Chouette::Network
- delegate_all
+ set_scope { context[:referential] }
-# Requires:
-# context: {
-# referential: ,
-# }
-def action_links
- links = []
+ # Action links require:
+ # context: {
+ # referential: ,
+ # }
- if h.policy(Chouette::Network).create?
- links << Link.new(
- content: h.t('networks.actions.new'),
- href: h.new_referential_network_path(context[:referential])
- )
+ create_action_link do |l|
+ l.content t('networks.actions.new')
end
- if h.policy(object).update?
- links << Link.new(
- content: h.t('networks.actions.edit'),
- href: h.edit_referential_network_path(context[:referential], object)
- )
- end
+ with_instance_decorator do |instance_decorator|
+ instance_decorator.show_action_link
- if h.policy(object).destroy?
- links << Link.new(
- content: h.destroy_link_content('networks.actions.destroy'),
- href: h.referential_network_path(context[:referential], object),
- method: :delete,
- data: { confirm: t('networks.actions.destroy_confirm') }
- )
- end
+ instance_decorator.edit_action_link do |l|
+ l.content t('networks.actions.edit')
+ end
- links
+ instance_decorator.destroy_action_link do |l|
+ l.content h.destroy_link_content('networks.actions.destroy')
+ l.data confirm: h.t('networks.actions.destroy_confirm')
+ end
end
-end \ No newline at end of file
+end
diff --git a/app/decorators/route_decorator.rb b/app/decorators/route_decorator.rb
index ec7f0d6aa..fa6367924 100644
--- a/app/decorators/route_decorator.rb
+++ b/app/decorators/route_decorator.rb
@@ -1,75 +1,78 @@
-class RouteDecorator < Draper::Decorator
+class RouteDecorator < AF83::Decorator
decorates Chouette::Route
- delegate_all
-
- # Requires:
+ # Action links require:
# context: {
# referential: ,
# line:
# }
- def action_links
- links = []
- if object.stop_points.any?
- links << Link.new(
- content: h.t('journey_patterns.actions.index'),
- href: [
+ set_scope { [context[:referential], context[:line]] }
+
+ with_instance_decorator do |instance_decorator|
+ instance_decorator.show_action_link
+
+ instance_decorator.edit_action_link
+
+ instance_decorator.action_link(
+ if: ->() { object.stop_points.any? },
+ secondary: :show
+ ) do |l|
+ l.content h.t('journey_patterns.actions.index')
+ l.href do
+ [
context[:referential],
context[:line],
object,
:journey_patterns_collection
]
- )
+ end
end
- if object.journey_patterns.present?
- links << Link.new(
- content: h.t('vehicle_journeys.actions.index'),
- href: [
+ instance_decorator.action_link(
+ if: ->() { object.journey_patterns.present? },
+ secondary: :show
+ ) do |l|
+ l.content h.t('vehicle_journeys.actions.index')
+ l.href do
+ [
context[:referential],
context[:line],
object,
:vehicle_journeys
]
- )
+ end
end
- links << Link.new(
- content: h.t('vehicle_journey_exports.new.title'),
- href: h.referential_line_route_vehicle_journey_exports_path(
- context[:referential],
- context[:line],
- object,
- format: :zip
- )
- )
-
- if h.policy(object).duplicate?
- links << Link.new(
- content: h.t('routes.duplicate.title'),
- href: h.duplicate_referential_line_route_path(
+ instance_decorator.action_link secondary: :show do |l|
+ l.content h.t('vehicle_journey_exports.new.title')
+ l.href do
+ h.referential_line_route_vehicle_journey_exports_path(
context[:referential],
context[:line],
- object
- ),
- method: :post
- )
+ object,
+ format: :zip
+ )
+ end
end
- if h.policy(object).destroy?
- links << Link.new(
- content: h.destroy_link_content,
- href: h.referential_line_route_path(
+ instance_decorator.action_link(
+ secondary: :show,
+ policy: :duplicate
+ ) do |l|
+ l.content h.t('routes.duplicate.title')
+ l.method :post
+ l.href do
+ h.duplicate_referential_line_route_path(
context[:referential],
context[:line],
object
- ),
- method: :delete,
- data: { confirm: h.t('routes.actions.destroy_confirm') }
- )
+ )
+ end
end
- links
+ instance_decorator.destroy_action_link do |l|
+ l.data confirm: h.t('routes.actions.destroy_confirm')
+ end
end
end
diff --git a/app/decorators/routing_constraint_zone_decorator.rb b/app/decorators/routing_constraint_zone_decorator.rb
index 0b438a554..de73068be 100644
--- a/app/decorators/routing_constraint_zone_decorator.rb
+++ b/app/decorators/routing_constraint_zone_decorator.rb
@@ -1,42 +1,27 @@
-class RoutingConstraintZoneDecorator < Draper::Decorator
+class RoutingConstraintZoneDecorator < AF83::Decorator
decorates Chouette::RoutingConstraintZone
- delegate_all
+ set_scope { [context[:referential], context[:line]] }
- # Requires:
+ # Action links require:
# context: {
# referential: ,
# line:
# }
- def action_links
- links = []
- if h.policy(object).update?
- links << Link.new(
- content: h.t('actions.edit'),
- href: h.edit_referential_line_routing_constraint_zone_path(
- context[:referential],
- context[:line],
- object
- )
- )
- end
+ create_action_link(
+ if: ->() {
+ h.policy(Chouette::RoutingConstraintZone).create? &&
+ context[:referential].organisation == h.current_organisation
+ }
+ )
- if h.policy(object).destroy?
- links << Link.new(
- content: h.destroy_link_content,
- href: h.referential_line_routing_constraint_zone_path(
- context[:referential],
- context[:line],
- object
- ),
- method: :delete,
- data: {
- confirm: h.t('routing_constraint_zones.actions.destroy_confirm')
- }
- )
- end
+ with_instance_decorator do |instance_decorator|
+ instance_decorator.show_action_link
+ instance_decorator.edit_action_link
- links
+ instance_decorator.destroy_action_link do |l|
+ l.data confirm: h.t('routing_constraint_zones.actions.destroy_confirm')
+ end
end
end
diff --git a/app/decorators/stop_area_decorator.rb b/app/decorators/stop_area_decorator.rb
index 4e777292d..525681971 100644
--- a/app/decorators/stop_area_decorator.rb
+++ b/app/decorators/stop_area_decorator.rb
@@ -1,43 +1,54 @@
-class StopAreaDecorator < Draper::Decorator
+class StopAreaDecorator < AF83::Decorator
decorates Chouette::StopArea
- delegate_all
+ create_action_link do |l|
+ l.content t('stop_areas.actions.new')
+ l.href { h.new_stop_area_referential_stop_area_path }
+ end
+
+ with_instance_decorator do |instance_decorator|
+ set_scope { object.stop_area_referential }
+ instance_decorator.show_action_link
- def action_links(stop_area = nil)
- links = []
- stop_area ||= object
+ instance_decorator.edit_action_link do |l|
+ l.content h.t('stop_areas.actions.edit')
+ end
- if h.policy(Chouette::StopArea).new?
- links << Link.new(
- content: h.t('stop_areas.actions.new'),
- href: h.new_stop_area_referential_stop_area_path(
- stop_area.stop_area_referential
+ instance_decorator.action_link policy: :deactivate, secondary: true do |l|
+ l.content h.deactivate_link_content('stop_areas.actions.deactivate')
+ l.href do
+ h.deactivate_stop_area_referential_stop_area_path(
+ object.stop_area_referential,
+ object
)
- )
+ end
+ l.method :put
+ l.data confirm: h.t('stop_areas.actions.deactivate_confirm')
+ l.add_class 'delete-action'
end
- if h.policy(stop_area).update?
- links << Link.new(
- content: h.t('stop_areas.actions.edit'),
- href: h.edit_stop_area_referential_stop_area_path(
- stop_area.stop_area_referential,
- stop_area
+ instance_decorator.action_link policy: :activate, secondary: true do |l|
+ l.content h.activate_link_content('stop_areas.actions.activate')
+ l.href do
+ h.activate_stop_area_referential_stop_area_path(
+ object.stop_area_referential,
+ object
)
- )
+ end
+ l.method :put
+ l.data confirm: h.t('stop_areas.actions.activate_confirm')
+ l.add_class 'delete-action'
end
- if h.policy(stop_area).destroy?
- links << Link.new(
- content: h.destroy_link_content('stop_areas.actions.destroy'),
- href: h.stop_area_referential_stop_area_path(
- stop_area.stop_area_referential,
- stop_area
- ),
- method: :delete,
- data: { confirm: t('stop_areas.actions.destroy_confirm') }
- )
+ instance_decorator.destroy_action_link do |l|
+ l.content h.destroy_link_content('stop_areas.actions.destroy')
+ l.data confirm: h.t('stop_areas.actions.destroy_confirm')
end
+ end
- links
+ define_instance_method :waiting_time_text do
+ return '-' if [nil, 0].include? waiting_time
+ h.t('stop_areas.waiting_time_format', value: waiting_time)
end
+
end
diff --git a/app/decorators/stop_point_decorator.rb b/app/decorators/stop_point_decorator.rb
index 196d6d490..e777e2b56 100644
--- a/app/decorators/stop_point_decorator.rb
+++ b/app/decorators/stop_point_decorator.rb
@@ -1,9 +1,35 @@
-class StopPointDecorator < StopAreaDecorator
+class StopPointDecorator < AF83::Decorator
decorates Chouette::StopPoint
- delegate_all
+ with_instance_decorator do |instance_decorator|
+ instance_decorator.show_action_link do |l|
+ l.href do
+ h.referential_stop_area_path(
+ object.referential,
+ object.stop_area
+ )
+ end
+ end
- def action_links
- super(object.stop_area)
+ instance_decorator.edit_action_link do |l|
+ l.content h.t('stop_points.actions.edit')
+ l.href do
+ h.edit_stop_area_referential_stop_area_path(
+ object.stop_area.stop_area_referential,
+ object.stop_area
+ )
+ end
+ end
+
+ instance_decorator.destroy_action_link do |l|
+ l.content h.destroy_link_content('stop_points.actions.destroy')
+ l.href do
+ h.referential_stop_area_path(
+ object.referential,
+ object.stop_area
+ )
+ end
+ l.data confirm: h.t('stop_points.actions.destroy_confirm')
+ end
end
end
diff --git a/app/decorators/time_table_decorator.rb b/app/decorators/time_table_decorator.rb
index c6eeac176..e4f9d7dbc 100644
--- a/app/decorators/time_table_decorator.rb
+++ b/app/decorators/time_table_decorator.rb
@@ -1,55 +1,53 @@
-class TimeTableDecorator < Draper::Decorator
+class TimeTableDecorator < AF83::Decorator
decorates Chouette::TimeTable
- delegate_all
+ create_action_link if: ->{ h.policy(Chouette::TimeTable).create? && context[:referential].organisation == h.current_organisation } do |l|
+ l.href { h.new_referential_time_table_path(context[:referential]) }
+ end
+
+ with_instance_decorator do |instance_decorator|
+ instance_decorator.show_action_link do |l|
+ l.href { [context[:referential], object] }
+ end
- # Requires:
- # context: {
- # referential: ,
- # }
- def action_links
- links = []
+ instance_decorator.edit_action_link do |l|
+ l.href { [:edit, context[:referential], object] }
+ end
- if object.calendar
- links << Link.new(
- content: h.t('actions.actualize'),
- href: h.actualize_referential_time_table_path(
+ instance_decorator.action_link policy: :actualize, if: ->{ object.calendar }, secondary: true do |l|
+ l.content t('actions.actualize')
+ l.href do
+ h.actualize_referential_time_table_path(
context[:referential],
object
- ),
- method: :post
- )
+ )
+ end
+ l.method :post
end
- if h.policy(object).edit?
- links << Link.new(
- content: h.t('actions.combine'),
- href: h.new_referential_time_table_time_table_combination_path(
+ instance_decorator.action_link policy: :edit, secondary: true do |l|
+ l.content t('actions.combine')
+ l.href do
+ h.new_referential_time_table_time_table_combination_path(
context[:referential],
object
)
- )
+ end
end
- if h.policy(object).duplicate?
- links << Link.new(
- content: h.t('actions.duplicate'),
- href: h.duplicate_referential_time_table_path(
+ instance_decorator.action_link policy: :duplicate, secondary: true do |l|
+ l.content t('actions.duplicate')
+ l.href do
+ h.duplicate_referential_time_table_path(
context[:referential],
object
)
- )
+ end
end
- if h.policy(object).destroy?
- links << Link.new(
- content: h.destroy_link_content,
- href: h.referential_time_table_path(context[:referential], object),
- method: :delete,
- data: { confirm: h.t('time_tables.actions.destroy_confirm') }
- )
+ instance_decorator.destroy_action_link do |l|
+ l.href { h.referential_time_table_path(context[:referential], object) }
+ l.data {{ confirm: h.t('time_tables.actions.destroy_confirm') }}
end
-
- links
end
end
diff --git a/app/errors/table_lock_timeout_error.rb b/app/errors/table_lock_timeout_error.rb
new file mode 100644
index 000000000..102f3a4a0
--- /dev/null
+++ b/app/errors/table_lock_timeout_error.rb
@@ -0,0 +1 @@
+class TableLockTimeoutError < ActiveRecord::StatementInvalid; end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 124604cd9..0058c210d 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -14,6 +14,11 @@ module ApplicationHelper
def page_header_title(object)
# Unwrap from decorator, we want to know the object model name
object = object.object if object.try(:object)
+
+ if Referential === object
+ return object.full_name
+ end
+
local = "#{object.model_name.name.underscore.pluralize}.#{params[:action]}.title"
if object.try(:name)
t(local, name: object.name || object.id)
@@ -23,12 +28,18 @@ module ApplicationHelper
end
def page_header_meta(object)
- info = t('last_update', time: l(object.updated_at, format: :short))
- if object.try(:versions)
- author = object.versions.try(:last).try(:whodunnit) || t('default_whodunnit')
- info = "#{info} <br/> #{t('whodunnit', author: author)}"
+ out = ""
+ display = true
+ display = policy(object).synchronize? if policy(object).respond_to?(:synchronize?) rescue false
+ if display
+ info = t('last_update', time: l(object.updated_at, format: :short))
+ if object.try(:versions)
+ author = object.versions.try(:last).try(:whodunnit) || t('default_whodunnit')
+ info = "#{info} <br/> #{t('whodunnit', author: author)}"
+ end
+ out += content_tag :div, info.html_safe, class: 'small last-update'
end
- content_tag :div, info.html_safe, class: 'small'
+ out.html_safe
end
def page_header_content_for(object)
diff --git a/app/helpers/breadcrumb_helper.rb b/app/helpers/breadcrumb_helper.rb
index 3da119247..2175e0be4 100644
--- a/app/helpers/breadcrumb_helper.rb
+++ b/app/helpers/breadcrumb_helper.rb
@@ -1,5 +1,12 @@
module BreadcrumbHelper
def breadcrumb_name(object, prop='name')
- "#{object.class.model_name.human} #{object.public_send(prop)}".truncate(50)
+ name =
+ if prop == 'name' && object.respond_to?(:full_name)
+ object.full_name
+ else
+ "#{object.class.model_name.human} #{object.public_send(prop)}"
+ end
+
+ name.truncate(40)
end
end
diff --git a/app/helpers/common_helpers.rb b/app/helpers/common_helpers.rb
deleted file mode 100644
index 29cabddac..000000000
--- a/app/helpers/common_helpers.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-
-module CommonHelpers
- # TODO: Needs refactoring, but does not seem to be under test
- # so let us refactor this **after** test coverage.
- def access_links_pairs(access_links)
- hpairs = Hash.new
- pairs = Array.new
- access_links.each do |link|
- key = pair_key(link)
- pair = nil
- if (hpairs.has_key? key)
- pair = hpairs[key]
- else
- pair = AccessLinkPair.new
- pairs << pair
- hpairs[key] = pair
- end
- if (link.link_orientation_type == "access_point_to_stop_area")
- pair.from_access_point = link
- else
- pair.to_access_point = link
- end
- end
- pairs
- end
-end
diff --git a/app/helpers/compliance_check_resources_helper.rb b/app/helpers/compliance_check_resources_helper.rb
new file mode 100644
index 000000000..95cabed88
--- /dev/null
+++ b/app/helpers/compliance_check_resources_helper.rb
@@ -0,0 +1,12 @@
+module ComplianceCheckResourcesHelper
+
+ # Comlpiance Check Resources statuses helper
+ def compliance_check_resource_status(status)
+ cls = ''
+ cls = 'success' if status == 'OK'
+ cls = 'warning' if status == 'WARNING'
+ cls = 'danger' if %w[ERROR IGNORED].include? status
+
+ content_tag :span, '', class: "fa fa-circle text-#{cls}"
+ end
+end
diff --git a/app/helpers/compliance_check_sets_helper.rb b/app/helpers/compliance_check_sets_helper.rb
index b255aee63..690bee80e 100644
--- a/app/helpers/compliance_check_sets_helper.rb
+++ b/app/helpers/compliance_check_sets_helper.rb
@@ -20,9 +20,9 @@ module ComplianceCheckSetsHelper
content_tag :span, '', class: "fa fa-clock-o"
else
cls =''
- cls = 'success' if status == 'OK'
- cls = 'warning' if status == 'WARNING'
- cls = 'danger' if %w[ERROR IGNORED].include? status
+ cls = 'success' if status == 'successful'
+ cls = 'warning' if status == 'warning'
+ cls = 'danger' if %w[failed aborted canceled].include? status
content_tag :span, '', class: "fa fa-circle text-#{cls}"
end
diff --git a/app/helpers/compliance_control_sets_helper.rb b/app/helpers/compliance_control_sets_helper.rb
index 57e6d9608..448d5c008 100644
--- a/app/helpers/compliance_control_sets_helper.rb
+++ b/app/helpers/compliance_control_sets_helper.rb
@@ -100,7 +100,8 @@ module ComplianceControlSetsHelper
],
sortable: true,
cls: 'table has-filter has-search',
- model: ComplianceControl
+ model: ComplianceControl,
+ action: :index
end
metas = content_tag :div, I18n.t('compliance_control_blocks.metas.control', count: compliance_controls.count), class: 'pull-right'
table + metas
diff --git a/app/helpers/imports_helper.rb b/app/helpers/imports_helper.rb
index 1c4549e50..140660153 100644
--- a/app/helpers/imports_helper.rb
+++ b/app/helpers/imports_helper.rb
@@ -15,6 +15,20 @@ module ImportsHelper
end
end
+ # Compliance check set messages
+ def bootstrap_class_for_message_criticity message_criticity
+ case message_criticity
+ when "error"
+ "alert alert-danger"
+ when "warning"
+ "alert alert-warning"
+ when "info"
+ "alert alert-info"
+ else
+ message_criticity.to_s
+ end
+ end
+
##############################
#      TO CLEAN!!!
##############################
diff --git a/app/helpers/links_helper.rb b/app/helpers/links_helper.rb
index 4fb7a797d..088415dc3 100644
--- a/app/helpers/links_helper.rb
+++ b/app/helpers/links_helper.rb
@@ -1,5 +1,18 @@
module LinksHelper
+ def custom_link_content(translation_key, klass, extra_class: nil)
+ klass = ["fa", "fa-#{klass}", "mr-xs", extra_class].compact.join(" ")
+ content_tag(:span, nil, class: klass) + t(translation_key)
+ end
+
def destroy_link_content(translation_key = 'actions.destroy')
- content_tag(:span, nil, class: 'fa fa-trash mr-xs') + t(translation_key)
+ custom_link_content translation_key, 'trash'
+ end
+
+ def deactivate_link_content(translation_key = 'actions.deactivate')
+ custom_link_content translation_key, 'power-off', extra_class: "text-danger"
+ end
+
+ def activate_link_content(translation_key = 'actions.activate')
+ custom_link_content translation_key, 'power-off', extra_class: "text-success"
end
end
diff --git a/app/helpers/newapplication_helper.rb b/app/helpers/newapplication_helper.rb
index df19113db..6600a03f7 100644
--- a/app/helpers/newapplication_helper.rb
+++ b/app/helpers/newapplication_helper.rb
@@ -1,3 +1,4 @@
+# coding: utf-8
module NewapplicationHelper
# Table Builder
@@ -147,7 +148,7 @@ module NewapplicationHelper
content_tag :li, link_to(t("actions.#{action}"), polymorph_url)
end
elsif action == :archive
- unless item.archived?
+ unless item.referential_read_only?
content_tag :li, link_to(t("actions.#{action}"), polymorph_url, method: :put)
end
elsif action == :unarchive
diff --git a/app/helpers/pagination_helper.rb b/app/helpers/pagination_helper.rb
index 02eec39dc..9b6042377 100644
--- a/app/helpers/pagination_helper.rb
+++ b/app/helpers/pagination_helper.rb
@@ -25,7 +25,7 @@ module PaginationHelper
if collection.total_pages > 1
links = content_tag :div, '', class: 'page_links' do
- will_paginate collection, container: false, page_links: false, previous_label: '', next_label: ''
+ will_paginate collection, container: false, page_links: false, previous_label: '', next_label: '', param_name: collection.try(:pagination_param_name)
end
content_tag :div, pinfos.concat(links).html_safe, class: "pagination #{cls}"
diff --git a/app/helpers/referentials_helper.rb b/app/helpers/referentials_helper.rb
index 01e5a5879..8251377aa 100644
--- a/app/helpers/referentials_helper.rb
+++ b/app/helpers/referentials_helper.rb
@@ -10,4 +10,9 @@ module ReferentialsHelper
t('true')
end
end
+
+ def referential_overview referential
+ service = ReferentialOverview.new referential, self
+ render partial: "referentials/overview", locals: {referential: referential, overview: service}
+ end
end
diff --git a/app/helpers/routes_helper.rb b/app/helpers/routes_helper.rb
index 4bffa99d4..61714a066 100644
--- a/app/helpers/routes_helper.rb
+++ b/app/helpers/routes_helper.rb
@@ -19,13 +19,15 @@ module RoutesHelper
css
end
- def route_json_for_edit(route)
- route.stop_points.includes(:stop_area).order(:position).map do |stop_point|
+ def route_json_for_edit(route, serialize: true)
+ data = route.stop_points.includes(:stop_area).order(:position).map do |stop_point|
stop_area_attributes = stop_point.stop_area.attributes.slice("name","city_name", "zip_code", "registration_number", "longitude", "latitude", "area_type", "comment")
stop_area_attributes["short_name"] = truncate(stop_area_attributes["name"], :length => 30) || ""
stop_point_attributes = stop_point.attributes.slice("for_boarding","for_alighting")
stop_area_attributes.merge(stop_point_attributes).merge(stoppoint_id: stop_point.id, stoparea_id: stop_point.stop_area.id).merge(user_objectid: stop_point.stop_area.user_objectid)
- end.to_json
+ end
+ data = data.to_json if serialize
+ data
end
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index f4976ea53..be70d974d 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -8,4 +8,19 @@ module SearchHelper
link_to name, params.deep_merge("q" => search,:page => 1), html_options
end
+ def filter_item_class q, key
+ active = false
+ if q.present? && q[key].present?
+ val = q[key]
+ if val.is_a?(Array)
+ active = val.any? &:present?
+ elsif val.is_a?(Hash)
+ active = val.values.any? &:present?
+ else
+ active = true
+ end
+ end
+ active ? 'active' : 'inactive'
+ end
+
end
diff --git a/app/helpers/stop_areas_helper.rb b/app/helpers/stop_areas_helper.rb
index 3e04fac7d..05ae042f5 100644
--- a/app/helpers/stop_areas_helper.rb
+++ b/app/helpers/stop_areas_helper.rb
@@ -11,6 +11,10 @@ module StopAreasHelper
( "<img src='#{stop_area_picture_url(stop_area)}'/>" + " <span style='height:25px; line-height:25px; margin-left: 5px; '>" + name + " <small style='height:25px; line-height:25px; margin-left: 10px; color: #555;'>" + localization + "</small></span>").html_safe
end
+ def label_for_country country, txt=nil
+ "#{txt} <span title='#{ISO3166::Country[country]&.translation(I18n.locale)}' class='flag-icon flag-icon-#{country}'></span>".html_safe
+ end
+
def genealogical_title
return t("stop_areas.genealogical.genealogical_routing") if @stop_area.stop_area_type == 'itl'
t("stop_areas.genealogical.genealogical")
@@ -33,12 +37,10 @@ module StopAreasHelper
@stop_area.stop_area_type == 'stop_place' || @stop_area.stop_area_type == 'commercial_stop_point'
end
-
def pair_key(access_link)
"#{access_link.access_point.id}-#{access_link.stop_area.id}"
end
-
def geo_data(sa, sar)
if sa.long_lat_type.nil?
content_tag :span, '-'
diff --git a/app/helpers/table_builder_helper.rb b/app/helpers/table_builder_helper.rb
index 37f01ce0d..2068dd23c 100644
--- a/app/helpers/table_builder_helper.rb
+++ b/app/helpers/table_builder_helper.rb
@@ -86,18 +86,36 @@ module TableBuilderHelper
overhead: [],
# Possibility to override the result of collection.model
- model: nil
+ model: nil,
+
+ #overrides the params[:action] value
+ action: nil
)
content_tag :table,
- thead(collection, columns, sortable, selectable, links.any?, overhead, model || collection.model) +
- tbody(collection, columns, selectable, links, overhead),
+ thead(collection, columns, sortable, selectable, links.any?, overhead, model || collection.model, action || params[:action]) +
+ tbody(collection, columns, selectable, links, overhead, model, action || params[:action]),
class: cls
end
+ def self.item_row_class_name collection, model=nil
+ model_name = model&.name
+
+ model_name ||=
+ if collection.respond_to?(:model)
+ collection.model.name
+ elsif collection.respond_to?(:first)
+ collection.first.class.name
+ else
+ "item"
+ end
+
+ model_name.split("::").last.parameterize
+ end
+
private
- def thead(collection, columns, sortable, selectable, has_links, overhead, model )
+ def thead(collection, columns, sortable, selectable, has_links, overhead, model, action)
content_tag :thead do
# Inserts overhead content if any specified
over_head = ''
@@ -176,7 +194,9 @@ module TableBuilderHelper
end
# Inserts a blank column for the gear menu
- if has_links || collection.last.try(:action_links).try(:any?)
+ last_item = collection.last
+ action_links = last_item && last_item.respond_to?(:action_links) && (last_item&.action_links&.is_a?(AF83::Decorator::ActionLinks) ? last_item.action_links(action) : last_item.action_links)
+ if has_links || action_links.try(:any?)
hcont << content_tag(:th, '')
end
@@ -187,86 +207,94 @@ module TableBuilderHelper
end
end
- def tbody(collection, columns, selectable, links, overhead)
- content_tag :tbody do
- collection.map do |item|
+ def tr item, columns, selectable, links, overhead, model_name, action
+ klass = "#{model_name}-#{item.id}"
+ content_tag :tr, class: klass do
+ bcont = []
+ if selectable
+ disabled = selectable.respond_to?(:call) && !selectable.call(item)
+ bcont << content_tag(
+ :td,
+ checkbox(id_name: item.try(:id), value: item.try(:id), disabled: disabled)
+ )
+ end
- content_tag :tr do
- bcont = []
+ columns.each do |column|
+ value = column.value(item)
- if selectable
- bcont << content_tag(
- :td,
- checkbox(id_name: item.try(:id), value: item.try(:id))
- )
- end
+ if column.linkable?
+ path = column.link_to(item)
+ link = link_to(value, path)
- columns.each do |column|
- value = column.value(item)
+ if overhead.empty?
+ bcont << content_tag(:td, link, title: 'Voir')
- if column.linkable?
- path = column.link_to(item)
- link = link_to(value, path)
+ else
+ i = columns.index(column)
- if overhead.empty?
- bcont << content_tag(:td, link, title: 'Voir')
+ if overhead[i].blank?
+ if (i > 0) && (overhead[i - 1][:width] > 1)
+ clsArrayAlt = overhead[i - 1][:cls].split
+
+ bcont << content_tag(:td, link, title: 'Voir', class: td_cls(clsArrayAlt))
else
- i = columns.index(column)
+ bcont << content_tag(:td, link, title: 'Voir')
+ end
- if overhead[i].blank?
- if (i > 0) && (overhead[i - 1][:width] > 1)
- clsArrayAlt = overhead[i - 1][:cls].split
+ else
+ clsArray = overhead[columns.index(column)][:cls].split
- bcont << content_tag(:td, link, title: 'Voir', class: td_cls(clsArrayAlt))
+ bcont << content_tag(:td, link, title: 'Voir', class: td_cls(clsArray))
+ end
+ end
- else
- bcont << content_tag(:td, link, title: 'Voir')
- end
+ else
+ if overhead.empty?
+ bcont << content_tag(:td, value)
- else
- clsArray = overhead[columns.index(column)][:cls].split
+ else
+ i = columns.index(column)
- bcont << content_tag(:td, link, title: 'Voir', class: td_cls(clsArray))
- end
- end
+ if overhead[i].blank?
+ if (i > 0) && (overhead[i - 1][:width] > 1)
+ clsArrayAlt = overhead[i - 1][:cls].split
- else
- if overhead.empty?
- bcont << content_tag(:td, value)
+ bcont << content_tag(:td, value, class: td_cls(clsArrayAlt))
else
- i = columns.index(column)
+ bcont << content_tag(:td, value)
+ end
- if overhead[i].blank?
- if (i > 0) && (overhead[i - 1][:width] > 1)
- clsArrayAlt = overhead[i - 1][:cls].split
+ else
+ clsArray = overhead[i][:cls].split
- bcont << content_tag(:td, value, class: td_cls(clsArrayAlt))
+ bcont << content_tag(:td, value, class: td_cls(clsArray))
+ end
+ end
+ end
+ end
- else
- bcont << content_tag(:td, value)
- end
+ action_links = item && item.respond_to?(:action_links) && (item.action_links.is_a?(AF83::Decorator::ActionLinks) ? item.action_links(action) : item.action_links)
- else
- clsArray = overhead[i][:cls].split
+ if links.any? || action_links.try(:any?)
+ bcont << content_tag(
+ :td,
+ build_links(item, links, action),
+ class: 'actions'
+ )
+ end
- bcont << content_tag(:td, value, class: td_cls(clsArray))
- end
- end
- end
- end
+ bcont.join.html_safe
+ end
+ end
- if links.any? || item.try(:action_links).try(:any?)
- bcont << content_tag(
- :td,
- build_links(item, links),
- class: 'actions'
- )
- end
+ def tbody(collection, columns, selectable, links, overhead, model = nil, action)
+ model_name = TableBuilderHelper.item_row_class_name collection, model
- bcont.join.html_safe
- end
+ content_tag :tbody do
+ collection.map do |item|
+ tr item, columns, selectable, links, overhead, model_name, action
end.join.html_safe
end
end
@@ -279,7 +307,7 @@ module TableBuilderHelper
end
end
- def build_links(item, links)
+ def build_links(item, links, action)
trigger = content_tag(
:div,
class: 'btn dropdown-toggle',
@@ -288,13 +316,26 @@ module TableBuilderHelper
content_tag :span, '', class: 'fa fa-cog'
end
- menu = content_tag :ul, class: 'dropdown-menu' do
- (
- CustomLinks.new(item, pundit_user, links, referential).links +
- item.action_links.select { |link| link.is_a?(Link) }
- ).map do |link|
- gear_menu_link(link)
- end.join.html_safe
+ action_links = item.action_links
+ if action_links.is_a?(AF83::Decorator::ActionLinks)
+ menu = content_tag :div, class: 'dropdown-menu' do
+ item.action_links(action).grouped_by(:primary, :secondary, :footer).map do |group, _links|
+ if _links.any?
+ content_tag :ul, class: group do
+ _links.map{|link| gear_menu_link(link)}.join.html_safe
+ end
+ end
+ end.join.html_safe
+ end
+ else
+ menu = content_tag :ul, class: 'dropdown-menu' do
+ (
+ CustomLinks.new(item, pundit_user, links, referential, workgroup).links +
+ action_links.select { |link| link.is_a?(Link) }
+ ).map do |link|
+ gear_menu_link(link)
+ end.join.html_safe
+ end
end
content_tag :div, trigger + menu, class: 'btn-group'
@@ -341,14 +382,19 @@ module TableBuilderHelper
end
end
- def checkbox(id_name:, value:)
+ def checkbox(id_name:, value:, disabled: false)
content_tag :div, '', class: 'checkbox' do
- check_box_tag(id_name, value).concat(
+ check_box_tag(id_name, value, nil, disabled: disabled).concat(
content_tag(:label, '', for: id_name)
)
end
end
+
def gear_menu_link(link)
+ klass = []
+ klass << link.extra_class if link.extra_class
+ klass << 'delete-action' if link.method == :delete
+ klass << 'disabled' if link.disabled
content_tag(
:li,
link_to(
@@ -358,7 +404,7 @@ module TableBuilderHelper
) do
link.content
end,
- class: ('delete-action' if link.method == :delete)
+ class: (klass.join(' ') if klass.present?)
)
end
@@ -367,4 +413,10 @@ module TableBuilderHelper
# cases, avoid a `NoMethodError`.
@__referential__ ||= try(:current_referential)
end
+
+ def workgroup
+ # Certain controllers don't define a `#current_referential`. In these
+ # cases, avoid a `NoMethodError`.
+ @__workgroup__ ||= try(:current_workgroup)
+ end
end
diff --git a/app/helpers/table_builder_helper/custom_links.rb b/app/helpers/table_builder_helper/custom_links.rb
index b1bb11f10..e09078be0 100644
--- a/app/helpers/table_builder_helper/custom_links.rb
+++ b/app/helpers/table_builder_helper/custom_links.rb
@@ -8,13 +8,14 @@ module TableBuilderHelper
unarchive: :put
}
- attr_reader :actions, :object, :user_context, :referential
+ attr_reader :actions, :object, :user_context, :referential, :workgroup
- def initialize(object, user_context, actions, referential = nil)
+ def initialize(object, user_context, actions, referential = nil, workgroup = nil)
@object = object
@user_context = user_context
@actions = actions
@referential = referential
+ @workgroup = workgroup
end
def links
@@ -34,7 +35,7 @@ module TableBuilderHelper
polymorph_url << action
end
- polymorph_url += URL.polymorphic_url_parts(object, referential)
+ polymorph_url += URL.polymorphic_url_parts(object, referential, workgroup)
end
def method_for_action(action)
diff --git a/app/helpers/table_builder_helper/url.rb b/app/helpers/table_builder_helper/url.rb
index a53ac5620..0e3dce0aa 100644
--- a/app/helpers/table_builder_helper/url.rb
+++ b/app/helpers/table_builder_helper/url.rb
@@ -1,6 +1,6 @@
module TableBuilderHelper
class URL
- def self.polymorphic_url_parts(item, referential)
+ def self.polymorphic_url_parts(item, referential, workgroup)
polymorph_url = []
unless item.is_a?(Calendar) || item.is_a?(Referential) || item.is_a?(ComplianceControlSet)
@@ -10,7 +10,7 @@ module TableBuilderHelper
polymorph_url << item.route.line if item.is_a?(Chouette::RoutingConstraintZone)
polymorph_url << item if item.respond_to? :line_referential
polymorph_url << item.stop_area if item.respond_to? :stop_area
- polymorph_url << item if item.respond_to?(:stop_points) || item.is_a?(Chouette::TimeTable)
+ polymorph_url << item if item.respond_to?(:stop_points) || item.is_a?(Chouette::TimeTable) || item.is_a?(Chouette::PurchaseWindow)
elsif item.respond_to? :referential
if item.respond_to? :workbench
polymorph_url << item.workbench
@@ -20,6 +20,7 @@ module TableBuilderHelper
end
end
else
+ polymorph_url << item.workgroup if item.respond_to? :workgroup
polymorph_url << item
end
diff --git a/app/helpers/time_tables_helper.rb b/app/helpers/time_tables_helper.rb
index b380a2b0a..1be1fa5a1 100644
--- a/app/helpers/time_tables_helper.rb
+++ b/app/helpers/time_tables_helper.rb
@@ -84,7 +84,7 @@ module TimeTablesHelper
end unless first.wday == first_weekday
first.upto(last) do |cur|
- cell_text, cell_attrs = block.call(cur)
+ cell_text, cell_attrs = yield cur
cell_text ||= cur.mday
cell_attrs ||= {}
cell_attrs[:headers] = th_id(cur, options[:table_id])
diff --git a/app/helpers/vehicle_journeys_helper.rb b/app/helpers/vehicle_journeys_helper.rb
index 6877abd11..1cc865c62 100644
--- a/app/helpers/vehicle_journeys_helper.rb
+++ b/app/helpers/vehicle_journeys_helper.rb
@@ -1,5 +1,5 @@
module VehicleJourneysHelper
-
+
def vehicle_name( vehicle)
if !vehicle.published_journey_name.blank?
vehicle.published_journey_name.first(8)
@@ -11,11 +11,11 @@ module VehicleJourneysHelper
vehicle.id
end
end
-
+
def missing_time_check( is_present)
return "missing" if (is_present && is_present.departure_time.nil?)
end
-
+
def vehicle_departure(vehicle, departure_time=nil)
unless departure_time
first_vjas = vehicle.vehicle_journey_at_stops.first
@@ -24,7 +24,7 @@ module VehicleJourneysHelper
end
l(departure_time, :format => :hour).gsub( / /, ' ')
end
-
+
def vehicle_title(vehicle, journey_frequency=nil)
return t("vehicle_journeys.vehicle_journey#{'_frequency' if vehicle.frequency?}.title_stopless", :name => vehicle_name( vehicle)) if vehicle.vehicle_journey_at_stops.empty?
first_vjas = vehicle.vehicle_journey_at_stops.first
@@ -40,7 +40,7 @@ module VehicleJourneysHelper
:time => vehicle_departure(vehicle, (journey_frequency ? journey_frequency.first_departure_time : nil )))
end
end
-
+
def route_journey_pattern_label_pairs route
route
.journey_patterns
@@ -50,7 +50,7 @@ module VehicleJourneysHelper
def edit_vehicle_title( vehicle)
return t('vehicle_journeys.edit.title_stopless', :name => vehicle_name( vehicle)) if vehicle.vehicle_journey_at_stops.empty?
first_vjas = vehicle.vehicle_journey_at_stops.first
- t('vehicle_journeys.edit.title',
+ t('vehicle_journeys.edit.title',
:name => vehicle_name( vehicle),
:stop => first_vjas.stop_point.stop_area.name,
:time => vehicle_departure(vehicle))
@@ -59,6 +59,14 @@ module VehicleJourneysHelper
def exist_vehicle_journeys?(route)
route.vehicle_journeys.count > 0
end
-
-end
+ def table_builder_column_for_stop_area stop_area
+ return nil unless stop_area
+ TableBuilderHelper::Column.new(
+ name: stop_area.name,
+ attribute: Proc.new {|v| v.vehicle_journey_at_stops.find{|vjas| vjas.stop_point.stop_area_id == stop_area.id}&.departure },
+ sortable: false
+ )
+ end
+
+end
diff --git a/app/inputs/color_select_input.rb b/app/inputs/color_select_input.rb
new file mode 100644
index 000000000..f92c80a22
--- /dev/null
+++ b/app/inputs/color_select_input.rb
@@ -0,0 +1,44 @@
+class ColorSelectInput < SimpleForm::Inputs::CollectionInput
+ enable :placeholder
+
+ def input(wrapper_options = {})
+ selected_color = object.send(attribute_name)
+ label = if selected_color
+ collection.find{|i| i.is_a?(Enumerable) && i.last == selected_color}.try(:first)
+ end
+
+ out = @builder.hidden_field attribute_name, value: selected_color
+ tag_name = ActionView::Helpers::Tags::Base.new( ActiveModel::Naming.param_key(object), attribute_name, :dummy ).send(:tag_name)
+ select = <<-eos
+ <div class="dropdown color_selector">
+ <button type='button' class="btn btn-default dropdown-toggle" data-toggle='dropdown' aria-haspopup='true' aria-expanded='true'
+ ><span
+ class='fa fa-circle mr-xs'
+ style='color: #{selected_color == nil ? 'transparent' : selected_color}'
+ >
+ </span>
+ #{label}
+ <span class='caret'></span>
+ </button>
+
+ <div class="form-group dropdown-menu" aria-labelledby='dpdwn_color'>
+ eos
+
+ collection.each do |color|
+ name = nil
+ name, color = color if color.is_a?(Enumerable)
+ select += <<-eos
+ <span class="radio" key=#{color} >
+ <label>
+ <input type='radio' class='color_selector' value='#{color}' data-for='#{tag_name}'/>
+ <span class='fa fa-circle mr-xs' style='color: #{color == nil ? 'transparent' : color}'></span>
+ #{name}
+ </label>
+ </span>
+ eos
+ end
+ select += "</div></div>"
+
+ out + select.html_safe
+ end
+end
diff --git a/app/javascript/date_filters/index.js b/app/javascript/date_filters/index.js
index ee892a7fe..432166008 100644
--- a/app/javascript/date_filters/index.js
+++ b/app/javascript/date_filters/index.js
@@ -3,6 +3,7 @@ import complianceControlSetDF from './compliance_control_set'
import complianceCheckSetDF from './compliance_check_set'
import timetableDF from './time_table'
import importDF from './import'
+import purchaseWindowDF from './purchase_window'
import workbenchDF from './workbench'
const DateFilters = {
@@ -11,6 +12,7 @@ const DateFilters = {
complianceControlSetDF,
importDF,
timetableDF,
+ purchaseWindowDF,
workbenchDF
}
diff --git a/app/javascript/date_filters/purchase_window.js b/app/javascript/date_filters/purchase_window.js
new file mode 100644
index 000000000..2c46b6d52
--- /dev/null
+++ b/app/javascript/date_filters/purchase_window.js
@@ -0,0 +1,5 @@
+import DateFilter from '../helpers/date_filters'
+
+const purchaseWindowDF = new DateFilter("purchase_window_filter_btn", "Tous les champs du filtre de date doivent être remplis", "q_contains_date_NUMi")
+
+export default purchaseWindowDF \ No newline at end of file
diff --git a/app/javascript/helpers/master_slave.coffee b/app/javascript/helpers/master_slave.coffee
new file mode 100644
index 000000000..81bebe36a
--- /dev/null
+++ b/app/javascript/helpers/master_slave.coffee
@@ -0,0 +1,16 @@
+class MasterSlave
+ constructor: (selector)->
+ $(selector).find('[data-master]').each (i, slave)->
+ $slave = $(slave)
+ master = $($slave.data().master)
+ $slave.find("input:disabled, select:disabled").attr "data-slave-force-disabled", "true"
+ toggle = ->
+ val = master.filter(":checked").val() if master.filter("[type=radio]").length > 0
+ val ||= master.val()
+ selected = val == $slave.data().value
+ $slave.toggle selected
+ $slave.find("input, select").filter(":not([data-slave-force-disabled])").attr "disabled", !selected
+ master.change toggle
+ toggle()
+
+export default MasterSlave
diff --git a/app/javascript/helpers/routes_map.coffee b/app/javascript/helpers/routes_map.coffee
new file mode 100644
index 000000000..6834406fc
--- /dev/null
+++ b/app/javascript/helpers/routes_map.coffee
@@ -0,0 +1,161 @@
+class RoutesMap
+ constructor: (@target)->
+ @initMap()
+ @area = []
+ @seenStopIds = []
+ @routes = {}
+
+ initMap: ->
+ @map = new ol.Map
+ target: @target,
+ layers: [ new ol.layer.Tile(source: new ol.source.OSM()) ]
+ controls: [ new ol.control.ScaleLine(), new ol.control.Zoom(), new ol.control.ZoomSlider() ],
+ interactions: ol.interaction.defaults(zoom: true)
+ view: new ol.View()
+
+ addRoutes: (routes)->
+ for route in routes
+ @addRoute route
+
+ addRoute: (route)->
+ geoColPts = []
+ geoColLns = []
+ @routes[route.id] = route if route.id
+ stops = route.stops || route
+ geoColEdges = [
+ new ol.Feature({
+ geometry: new ol.geom.Point(ol.proj.fromLonLat([parseFloat(stops[0].longitude), parseFloat(stops[0].latitude)]))
+ }),
+ new ol.Feature({
+ geometry: new ol.geom.Point(ol.proj.fromLonLat([parseFloat(stops[stops.length - 1].longitude), parseFloat(stops[stops.length - 1].latitude)]))
+ })
+ ]
+
+ prevStop = null
+ stops.forEach (stop, i) =>
+ if stop.longitude && stop.latitude
+ if prevStop
+ geoColLns.push new ol.Feature
+ geometry: new ol.geom.LineString([
+ ol.proj.fromLonLat([parseFloat(prevStop.longitude), parseFloat(prevStop.latitude)]),
+ ol.proj.fromLonLat([parseFloat(stop.longitude), parseFloat(stop.latitude)])
+ ])
+ prevStop = stop
+
+ geoColPts.push(new ol.Feature({
+ geometry: new ol.geom.Point(ol.proj.fromLonLat([parseFloat(stop.longitude), parseFloat(stop.latitude)]))
+ }))
+ unless @seenStopIds.indexOf(stop.stoparea_id) > 0
+ @area.push [parseFloat(stop.longitude), parseFloat(stop.latitude)]
+ @seenStopIds.push stop.stoparea_id
+
+ vectorPtsLayer = new ol.layer.Vector({
+ source: new ol.source.Vector({
+ features: geoColPts
+ }),
+ style: @defaultStyles(),
+ zIndex: 2
+ })
+ route.vectorPtsLayer = vectorPtsLayer if route.id
+ vectorEdgesLayer = new ol.layer.Vector({
+ source: new ol.source.Vector({
+ features: geoColEdges
+ }),
+ style: @edgeStyles(),
+ zIndex: 3
+ })
+ route.vectorEdgesLayer = vectorEdgesLayer if route.id
+ vectorLnsLayer = new ol.layer.Vector({
+ source: new ol.source.Vector({
+ features: geoColLns
+ }),
+ style: [@lineStyle()],
+ zIndex: 1
+ })
+ route.vectorLnsLayer = vectorLnsLayer if route.id
+ @map.addLayer vectorPtsLayer
+ @map.addLayer vectorEdgesLayer
+ @map.addLayer vectorLnsLayer
+
+ lineStyle: (highlighted=false)->
+ new ol.style.Style
+ stroke: new ol.style.Stroke
+ color: if highlighted then "#ed7f00" else '#007fbb'
+ width: 3
+
+ edgeStyles: (highlighted=false)->
+ new ol.style.Style
+ image: new ol.style.Circle
+ radius: 5
+ stroke: new ol.style.Stroke
+ color: if highlighted then "#ed7f00" else '#007fbb'
+ width: 2
+ fill: new ol.style.Fill
+ color: if highlighted then "#ed7f00" else '#007fbb'
+ width: 2
+
+ defaultStyles: (highlighted=false)->
+ new ol.style.Style
+ image: new ol.style.Circle
+ radius: 4
+ stroke: new ol.style.Stroke
+ color: if highlighted then "#ed7f00" else '#007fbb'
+ width: 2
+ fill: new ol.style.Fill
+ color: '#ffffff'
+ width: 2
+
+ addRoutesLabels: ->
+ labelsContainer = $("<ul class='routes-labels'></ul>")
+ labelsContainer.appendTo $("##{@target}")
+ @vectorPtsLayer = null
+ @vectorEdgesLayer = null
+ @vectorLnsLayer = null
+ Object.keys(@routes).forEach (id)=>
+ route = @routes[id]
+ label = $("<li>#{route.name}</ul>")
+ label.appendTo labelsContainer
+ label.mouseleave =>
+ route.vectorPtsLayer.setStyle @defaultStyles(false)
+ route.vectorEdgesLayer.setStyle @edgeStyles(false)
+ route.vectorLnsLayer.setStyle @lineStyle(false)
+ route.vectorPtsLayer.setZIndex 2
+ route.vectorEdgesLayer.setZIndex 3
+ route.vectorLnsLayer.setZIndex 1
+ @fitZoom()
+ label.mouseenter =>
+ route.vectorPtsLayer.setStyle @defaultStyles(true)
+ route.vectorEdgesLayer.setStyle @edgeStyles(true)
+ route.vectorLnsLayer.setStyle @lineStyle(true)
+ route.vectorPtsLayer.setZIndex 11
+ route.vectorEdgesLayer.setZIndex 12
+ route.vectorLnsLayer.setZIndex 10
+ @fitZoom(route)
+
+ fitZoom: (route)->
+ if route
+ area = []
+ route.stops.forEach (stop, i) =>
+ area.push [parseFloat(stop.longitude), parseFloat(stop.latitude)]
+ else
+ area = @area
+ boundaries = ol.extent.applyTransform(
+ ol.extent.boundingExtent(area), ol.proj.getTransform('EPSG:4326', 'EPSG:3857')
+ )
+ @map.getView().fit boundaries, @map.getSize()
+ tooCloseToBounds = false
+ mapBoundaries = @map.getView().calculateExtent @map.getSize()
+ mapWidth = mapBoundaries[2] - mapBoundaries[0]
+ mapHeight = mapBoundaries[3] - mapBoundaries[1]
+ marginSize = 0.1
+ heightMargin = marginSize * mapHeight
+ widthMargin = marginSize * mapWidth
+ tooCloseToBounds = tooCloseToBounds || (boundaries[0] - mapBoundaries[0]) < widthMargin
+ tooCloseToBounds = tooCloseToBounds || (mapBoundaries[2] - boundaries[2]) < widthMargin
+ tooCloseToBounds = tooCloseToBounds || (boundaries[1] - mapBoundaries[1]) < heightMargin
+ tooCloseToBounds = tooCloseToBounds || (mapBoundaries[3] - boundaries[3]) < heightMargin
+ if tooCloseToBounds
+ @map.getView().setZoom(@map.getView().getZoom() - 1)
+
+
+export default RoutesMap
diff --git a/app/javascript/helpers/save_button.js b/app/javascript/helpers/save_button.js
new file mode 100644
index 000000000..7e0bd5bbe
--- /dev/null
+++ b/app/javascript/helpers/save_button.js
@@ -0,0 +1,47 @@
+import React, { PropTypes, Component } from 'react'
+
+export default class SaveButton extends Component{
+ constructor(props){
+ super(props)
+ }
+
+ btnDisabled(){
+ return !this.props.status.fetchSuccess || this.props.status.isFetching
+ }
+
+ btnClass(){
+ let className = ['btn btn-default']
+ if(this.btnDisabled()){
+ className.push('disabled')
+ }
+ return className.join(' ')
+ }
+
+ render() {
+ if (!this.hasPolicy()) {
+ return false
+ }else{
+ return (
+ <div className='row mt-md'>
+ <div className='col-lg-12 text-right'>
+ <form className={this.formClassName() + ' formSubmitr ml-xs'} onSubmit={e => {e.preventDefault()}}>
+ <div className="btn-group sticky-actions">
+ <button
+ className={this.btnClass()}
+ type='button'
+ disabled={this.btnDisabled()}
+ onClick={e => {
+ e.preventDefault()
+ this.props.editMode ? this.submitForm() : this.props.onEnterEditMode()
+ }}
+ >
+ {this.props.editMode ? "Valider" : "Editer"}
+ </button>
+ </div>
+ </form>
+ </div>
+ </div>
+ )
+ }
+ }
+}
diff --git a/app/javascript/helpers/stop_area_header_manager.js b/app/javascript/helpers/stop_area_header_manager.js
new file mode 100644
index 000000000..5b18e2f63
--- /dev/null
+++ b/app/javascript/helpers/stop_area_header_manager.js
@@ -0,0 +1,56 @@
+import React, { Component } from 'react'
+
+export default class StopAreaHeaderManager {
+ constructor(ids_list, stopPointsList, features) {
+ this.ids_list = ids_list
+ this.stopPointsList = stopPointsList
+ this.features = features
+ }
+
+ hasFeature(key) {
+ return this.features[key]
+ }
+
+ stopPointHeader(object_id) {
+ let index = this.ids_list.indexOf(object_id)
+ let sp = this.stopPointsList[index]
+ let showHeadline = this.showHeader(object_id)
+ return (
+ <div
+ className={(showHeadline) ? 'headlined' : ''}
+ data-headline={showHeadline}
+ title={sp.city_name ? sp.city_name + ' (' + sp.zip_code +')' : ""}
+ >
+ <span>
+ <span>
+ {sp.name}
+ {sp.time_zone_formatted_offset && <span className="small">
+ &nbsp;({sp.time_zone_formatted_offset})
+ </span>}
+ {sp.area_kind == 'non_commercial' && <span className="fa fa-question-circle" title={sp.area_type_i18n}>
+ </span>}
+ </span>
+ </span>
+ </div>
+ )
+ }
+
+ showHeader(object_id) {
+ let showHeadline = false
+ let headline = ""
+ let attribute_to_check = this.hasFeature('long_distance_routes') ? "country_code" : "city_name"
+ let index = this.ids_list.indexOf(object_id)
+ let sp = this.stopPointsList[index]
+ let previousBreakpoint = this.stopPointsList[index - 1]
+ if(sp == undefined){
+ console.log("STOP_POINT NOT FOUND: " + object_id)
+ console.log("AVAILABLE IDS:" + this.ids_list)
+ return
+ }
+ if(index == 0 || (sp[attribute_to_check] != previousBreakpoint[attribute_to_check])){
+ showHeadline = true
+ headline = this.hasFeature('long_distance_routes') ? sp.country_name : sp.city_name
+ }
+ return showHeadline ? headline : ""
+ }
+}
diff --git a/app/javascript/journey_patterns/actions/index.js b/app/javascript/journey_patterns/actions/index.js
index 4ff3f77ea..a70a2e6f2 100644
--- a/app/javascript/journey_patterns/actions/index.js
+++ b/app/javascript/journey_patterns/actions/index.js
@@ -64,6 +64,11 @@ const actions = {
type : 'DELETE_JOURNEYPATTERN',
index,
}),
+ updateJourneyPatternCosts : (index, costs) => ({
+ type : 'UPDATE_JOURNEYPATTERN_COSTS',
+ index,
+ costs
+ }),
closeModal : () => ({
type : 'CLOSE_MODAL'
}),
@@ -194,15 +199,13 @@ const actions = {
}
})
}
- journeyPatterns.push({
- name: val.name,
- object_id: val.object_id,
- short_id: val.short_id,
- published_name: val.published_name,
- registration_number: val.registration_number,
- stop_points: val.route_short_description.stop_points,
- deletable: false
- })
+ journeyPatterns.push(
+ _.assign({}, val, {
+ stop_points: val.route_short_description.stop_points,
+ costs: val.costs || {},
+ deletable: false
+ })
+ )
}
}
window.currentItemsLength = journeyPatterns.length
@@ -217,4 +220,4 @@ const actions = {
}
}
-export default actions \ No newline at end of file
+export default actions
diff --git a/app/javascript/journey_patterns/components/ConfirmModal.js b/app/javascript/journey_patterns/components/ConfirmModal.js
index 2cc1bef44..ccd0a9384 100644
--- a/app/javascript/journey_patterns/components/ConfirmModal.js
+++ b/app/javascript/journey_patterns/components/ConfirmModal.js
@@ -1,4 +1,6 @@
-import React, { PropTypes } from 'react'
+import React from 'react'
+import PropTypes from 'prop-types'
+
export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCancel, journeyPatterns}) {
return (
diff --git a/app/javascript/journey_patterns/components/CreateModal.js b/app/javascript/journey_patterns/components/CreateModal.js
index d0eff6e57..a6c1b608a 100644
--- a/app/javascript/journey_patterns/components/CreateModal.js
+++ b/app/javascript/journey_patterns/components/CreateModal.js
@@ -1,4 +1,5 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import actions from '../actions'
export default class CreateModal extends Component {
diff --git a/app/javascript/journey_patterns/components/EditModal.js b/app/javascript/journey_patterns/components/EditModal.js
index e7ce24aa1..c960cb41c 100644
--- a/app/javascript/journey_patterns/components/EditModal.js
+++ b/app/javascript/journey_patterns/components/EditModal.js
@@ -1,4 +1,5 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import actions from '../actions'
export default class EditModal extends Component {
@@ -36,7 +37,6 @@ export default class EditModal extends Component {
{this.renderModalTitle()}
<span type="button" className="close modal-close" data-dismiss="modal">&times;</span>
</div>
-
{(this.props.modal.type == 'edit') && (
<form>
<div className='modal-body'>
@@ -85,6 +85,16 @@ export default class EditModal extends Component {
</div>
</div>
</div>
+ <div>
+ <label className='control-label'>Signature métier</label>
+ <input
+ type='text'
+ ref='checksum'
+ className='form-control'
+ disabled='disabled'
+ defaultValue={this.props.modal.modalProps.journeyPattern.checksum}
+ />
+ </div>
</div>
{
this.props.editMode &&
@@ -121,4 +131,4 @@ EditModal.propTypes = {
modal: PropTypes.object,
onModalClose: PropTypes.func.isRequired,
saveModal: PropTypes.func.isRequired
-} \ No newline at end of file
+}
diff --git a/app/javascript/journey_patterns/components/JourneyPattern.js b/app/javascript/journey_patterns/components/JourneyPattern.js
index d4c9816ec..35765b99a 100644
--- a/app/javascript/journey_patterns/components/JourneyPattern.js
+++ b/app/javascript/journey_patterns/components/JourneyPattern.js
@@ -1,10 +1,21 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import actions from '../actions'
export default class JourneyPattern extends Component{
constructor(props){
super(props)
- this.previousCity = undefined
+ this.previousSpId = undefined
+ this.updateCosts = this.updateCosts.bind(this)
+ }
+
+ updateCosts(e) {
+ let costs = {
+ [e.target.dataset.costsKey]: {
+ [e.target.name]: parseFloat(e.target.value)
+ }
+ }
+ this.props.onUpdateJourneyPatternCosts(costs)
}
vehicleJourneyURL(jpOid) {
@@ -16,16 +27,20 @@ export default class JourneyPattern extends Component{
)
}
- cityNameChecker(sp) {
- let bool = false
- if(sp.city_name != this.previousCity){
- bool = true
- this.previousCity = sp.city_name
- }
+ hasFeature(key) {
+ return this.props.status.features[key]
+ }
+
+ cityNameChecker(sp, i) {
+ return this.props.journeyPatterns.showHeader((sp.stop_area_object_id || sp.object_id) + "-" + i)
+ }
+
+ spNode(sp, headlined){
return (
<div
- className={(bool) ? 'headlined' : ''}
+ className={(headlined) ? 'headlined' : ''}
>
+ <div className={'link '}></div>
<span className='has_radio'>
<input
onChange = {(e) => this.props.onCheckboxChange(e)}
@@ -59,19 +74,60 @@ export default class JourneyPattern extends Component{
return !this.props.status.policy[`journey_patterns.${action}`]
}
- render() {
- this.previousCity = undefined
+ totals(){
+ let totalTime = 0
+ let totalDistance = 0
+ let from = null
+ this.props.value.stop_points.map((stopPoint, i) =>{
+ if(from && stopPoint.checked){
+ let [costsKey, costs, time, distance] = this.getTimeAndDistanceBetweenStops(from, stopPoint.id)
+ totalTime += time
+ totalDistance += distance
+ }
+ if(stopPoint.checked){
+ from = stopPoint.id
+ }
+ })
+ return [this.formatTime(totalTime), this.formatDistance(totalDistance)]
+ }
- return (
- <div className={'t2e-item' + (this.props.value.deletable ? ' disabled' : '') + (this.props.value.object_id ? '' : ' to_record') + (this.props.value.errors ? ' has-error': '')}>
- {/* Errors */}
- {/* this.props.value.errors ? this.getErrors(this.props.value.errors) : '' */}
+ getTimeAndDistanceBetweenStops(from, to){
+ let costsKey = from + "-" + to
+ let costs = this.props.value.costs[costsKey] || {distance: 0, time: 0}
+ let time = costs['time'] || 0
+ let distance = costs['distance'] || 0
+ return [costsKey, costs, time, distance]
+ }
+
+ formatDistance(distance){
+ return parseFloat(Math.round(distance * 100) / 100).toFixed(2) + " km"
+ }
+
+ formatTime(time){
+ if(time < 60){
+ return time + " min"
+ }
+ else{
+ let hours = parseInt(time/60)
+ return hours + " h " + (time - 60*hours)
+ }
+ }
+ render() {
+ this.previousSpId = undefined
+ let [totalTime, totalDistance] = this.totals()
+ return (
+ <div className={'t2e-item' + (this.props.value.deletable ? ' disabled' : '') + (this.props.value.object_id ? '' : ' to_record') + (this.props.value.errors ? ' has-error': '') + (this.hasFeature('costs_in_journey_patterns') ? ' with-costs' : '')}>
<div className='th'>
<div className='strong mb-xs'>{this.props.value.object_id ? this.props.value.short_id : '-'}</div>
<div>{this.props.value.registration_number}</div>
<div>{actions.getChecked(this.props.value.stop_points).length} arrêt(s)</div>
-
+ {this.hasFeature('costs_in_journey_patterns') &&
+ <div className="small row totals">
+ <span className="col-md-6"><i className="fa fa-arrows-h"></i>{totalDistance}</span>
+ <span className="col-md-6"><i className="fa fa-clock-o"></i>{totalTime}</span>
+ </div>
+ }
<div className={this.props.value.deletable ? 'btn-group disabled' : 'btn-group'}>
<div
className={this.props.value.deletable ? 'btn dropdown-toggle disabled' : 'btn dropdown-toggle'}
@@ -80,10 +136,9 @@ export default class JourneyPattern extends Component{
<span className='fa fa-cog'></span>
</div>
<ul className='dropdown-menu'>
- <li className={this.isDisabled('update') ? 'disabled' : ''}>
+ <li>
<button
type='button'
- disabled={this.isDisabled('update')}
onClick={this.props.onOpenEditModal}
data-toggle='modal'
data-target='#JourneyPatternModal'
@@ -112,9 +167,40 @@ export default class JourneyPattern extends Component{
</div>
{this.props.value.stop_points.map((stopPoint, i) =>{
+ let costs = null
+ let costsKey = null
+ let time = null
+ let distance = null
+ let time_in_words = null
+ if(this.previousSpId && stopPoint.checked){
+ [costsKey, costs, time, distance] = this.getTimeAndDistanceBetweenStops(this.previousSpId, stopPoint.id)
+ time_in_words = this.formatTime(time)
+ }
+ if(stopPoint.checked){
+ this.previousSpId = stopPoint.id
+ }
+ let headlined = this.cityNameChecker(stopPoint, i)
return (
- <div key={i} className='td'>
- {this.cityNameChecker(stopPoint)}
+ <div key={i} className={(stopPoint.checked ? 'activated' : 'deactivated') + (this.props.editMode ? ' edit-mode' : '')}>
+ <div className={'td' + (headlined ? ' with-headline' : '')}>
+ {this.spNode(stopPoint, headlined)}
+ </div>
+ {this.hasFeature('costs_in_journey_patterns') && costs && <div className='costs' id={'costs-' + this.props.value.id + '-' + costsKey }>
+ {this.props.editMode && <div>
+ <p>
+ <input type="number" value={costs['distance'] || 0} min='0' name="distance" step="0.01" onChange={this.updateCosts} data-costs-key={costsKey}/>
+ <span>km</span>
+ </p>
+ <p>
+ <input type="number" value={costs['time'] || 0} min='0' name="time" onChange={this.updateCosts} data-costs-key={costsKey}/>
+ <span>min</span>
+ </p>
+ </div>}
+ {!this.props.editMode && <div>
+ <p><i className="fa fa-arrows-h"></i>{this.formatDistance(costs['distance'] || 0)}</p>
+ <p><i className="fa fa-clock-o"></i>{time_in_words}</p>
+ </div>}
+ </div>}
</div>
)
})}
@@ -128,5 +214,6 @@ JourneyPattern.propTypes = {
index: PropTypes.number,
onCheckboxChange: PropTypes.func.isRequired,
onOpenEditModal: PropTypes.func.isRequired,
- onDeleteJourneyPattern: PropTypes.func.isRequired
-} \ No newline at end of file
+ onDeleteJourneyPattern: PropTypes.func.isRequired,
+ journeyPatterns: PropTypes.object.isRequired
+}
diff --git a/app/javascript/journey_patterns/components/JourneyPatterns.js b/app/javascript/journey_patterns/components/JourneyPatterns.js
index 4b2badabb..31727fefc 100644
--- a/app/javascript/journey_patterns/components/JourneyPatterns.js
+++ b/app/javascript/journey_patterns/components/JourneyPatterns.js
@@ -1,16 +1,23 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import _ from 'lodash'
import JourneyPattern from './JourneyPattern'
-
+import StopAreaHeaderManager from '../../helpers/stop_area_header_manager'
export default class JourneyPatterns extends Component {
constructor(props){
super(props)
- this.previousCity = undefined
+ this.headerManager = new StopAreaHeaderManager(
+ _.map(this.props.stopPointsList, (sp, i)=>{return sp.stop_area_object_id + "-" + i}),
+ this.props.stopPointsList,
+ this.props.status.features
+ )
}
+
componentDidMount() {
this.props.onLoadFirstPage()
}
+
componentDidUpdate(prevProps, prevState) {
if(this.props.status.isFetching == false){
$('.table-2entries').each(function() {
@@ -54,21 +61,12 @@ export default class JourneyPatterns extends Component {
}
}
- cityNameChecker(sp) {
- let bool = false
- if(sp.city_name != this.previousCity){
- bool = true
- this.previousCity = sp.city_name
- }
- return (
- <div
- className={(bool) ? 'headlined' : ''}
- data-headline={(bool) ? sp.city_name : ''}
- title={sp.city_name + ' (' + sp.zip_code +')'}
- >
- <span><span>{sp.name}</span></span>
- </div>
- )
+ showHeader(object_id) {
+ return this.headerManager.showHeader(object_id)
+ }
+
+ hasFeature(key) {
+ return this.props.status.features[key]
}
render() {
@@ -115,8 +113,8 @@ export default class JourneyPatterns extends Component {
</div>
{this.props.stopPointsList.map((sp, i) =>{
return (
- <div key={i} className='td'>
- {this.cityNameChecker(sp)}
+ <div key={i} className={'td' + (this.hasFeature('costs_in_journey_patterns') ? ' with-costs' : '')}>
+ {this.headerManager.stopPointHeader(sp.stop_area_object_id + "-" + i)}
</div>
)
})}
@@ -131,8 +129,10 @@ export default class JourneyPatterns extends Component {
onCheckboxChange= {(e) => this.props.onCheckboxChange(e, index)}
onOpenEditModal= {() => this.props.onOpenEditModal(index, journeyPattern)}
onDeleteJourneyPattern={() => this.props.onDeleteJourneyPattern(index)}
+ onUpdateJourneyPatternCosts={(costs) => this.props.onUpdateJourneyPatternCosts(index, costs)}
status= {this.props.status}
editMode= {this.props.editMode}
+ journeyPatterns= {this}
/>
)}
</div>
@@ -152,4 +152,4 @@ JourneyPatterns.propTypes = {
onCheckboxChange: PropTypes.func.isRequired,
onLoadFirstPage: PropTypes.func.isRequired,
onOpenEditModal: PropTypes.func.isRequired
-} \ No newline at end of file
+}
diff --git a/app/javascript/journey_patterns/components/Navigate.js b/app/javascript/journey_patterns/components/Navigate.js
index f2fdd668f..78f324a7d 100644
--- a/app/javascript/journey_patterns/components/Navigate.js
+++ b/app/javascript/journey_patterns/components/Navigate.js
@@ -1,4 +1,5 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import actions from '../actions'
export default function Navigate({ dispatch, journeyPatterns, pagination, status }) {
diff --git a/app/javascript/journey_patterns/components/SaveJourneyPattern.js b/app/javascript/journey_patterns/components/SaveJourneyPattern.js
index d071fa542..4bb6a73a0 100644
--- a/app/javascript/journey_patterns/components/SaveJourneyPattern.js
+++ b/app/javascript/journey_patterns/components/SaveJourneyPattern.js
@@ -1,34 +1,19 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import SaveButton from '../../helpers/save_button'
import actions from '../actions'
-export default class SaveJourneyPattern extends Component {
- constructor(props){
- super(props)
+export default class SaveJourneyPattern extends SaveButton {
+ hasPolicy(){
+ return this.props.status.policy['journey_patterns.update'] == true
}
- render() {
- if(this.props.status.policy['journey_patterns.update'] == false) {
- return false
- }else{
- return (
- <div className='row mt-md'>
- <div className='col-lg-12 text-right'>
- <form className='jp_collection formSubmitr ml-xs' onSubmit={e => {e.preventDefault()}}>
- <button
- className='btn btn-default'
- type='button'
- onClick={e => {
- e.preventDefault()
- this.props.editMode ? this.props.onSubmitJourneyPattern(this.props.dispatch, this.props.journeyPatterns) : this.props.onEnterEditMode()
- }}
- >
- {this.props.editMode ? "Valider" : "Editer"}
- </button>
- </form>
- </div>
- </div>
- )
- }
+ formClassName(){
+ return 'jp_collection'
+ }
+
+ submitForm(){
+ this.props.onSubmitJourneyPattern(this.props.dispatch, this.props.journeyPatterns)
}
}
@@ -36,4 +21,4 @@ SaveJourneyPattern.propTypes = {
journeyPatterns: PropTypes.array.isRequired,
status: PropTypes.object.isRequired,
page: PropTypes.number.isRequired
-} \ No newline at end of file
+}
diff --git a/app/javascript/journey_patterns/containers/JourneyPatternList.js b/app/javascript/journey_patterns/containers/JourneyPatternList.js
index d98734407..d338345f2 100644
--- a/app/javascript/journey_patterns/containers/JourneyPatternList.js
+++ b/app/javascript/journey_patterns/containers/JourneyPatternList.js
@@ -25,7 +25,10 @@ const mapDispatchToProps = (dispatch) => {
},
onDeleteJourneyPattern: (index) =>{
dispatch(actions.deleteJourneyPattern(index))
- }
+ },
+ onUpdateJourneyPatternCosts: (index, costs) =>{
+ dispatch(actions.updateJourneyPatternCosts(index, costs))
+ },
}
}
diff --git a/app/javascript/journey_patterns/reducers/journeyPatterns.js b/app/javascript/journey_patterns/reducers/journeyPatterns.js
index 0bbcba976..1ce069522 100644
--- a/app/javascript/journey_patterns/reducers/journeyPatterns.js
+++ b/app/javascript/journey_patterns/reducers/journeyPatterns.js
@@ -17,6 +17,7 @@ const journeyPattern = (state = {}, action) =>{
published_name: action.data.published_name.value,
registration_number: action.data.registration_number.value,
stop_points: stopPoints,
+ costs: {},
deletable: false
}
case 'UPDATE_CHECKBOX_VALUE':
@@ -67,6 +68,19 @@ export default function journeyPatterns (state = [], action) {
return j
}
})
+ case 'UPDATE_JOURNEYPATTERN_COSTS':
+ return state.map((j, i) =>{
+ if(i == action.index) {
+ const new_costs = Object.assign({}, j.costs)
+ Object.keys(action.costs).map((key) => {
+ let new_costs_for_key = Object.assign({}, j.costs[key] || {}, action.costs[key])
+ new_costs[key] = new_costs_for_key
+ })
+ return _.assign({}, j, {costs: new_costs})
+ } else {
+ return j
+ }
+ })
case 'ADD_JOURNEYPATTERN':
return [
journeyPattern(state, action),
@@ -87,4 +101,4 @@ export default function journeyPatterns (state = [], action) {
default:
return state
}
-} \ No newline at end of file
+}
diff --git a/app/javascript/packs/calendars/edit.js b/app/javascript/packs/calendars/edit.js
new file mode 100644
index 000000000..bd09657ec
--- /dev/null
+++ b/app/javascript/packs/calendars/edit.js
@@ -0,0 +1,74 @@
+import React from 'react'
+import { render } from 'react-dom'
+import { Provider } from 'react-redux'
+import { createStore } from 'redux'
+import timeTablesApp from '../../time_tables/reducers'
+import App from '../../time_tables/containers/App'
+import clone from '../../helpers/clone'
+
+const actionType = clone(window, "actionType", true)
+
+// logger, DO NOT REMOVE
+// var applyMiddleware = require('redux').applyMiddleware
+// var createLogger = require('redux-logger')
+// var thunkMiddleware = require('redux-thunk').default
+// var promise = require('redux-promise')
+
+let initialState = {
+ status: {
+ actionType: actionType,
+ policy: window.perms,
+ fetchSuccess: true,
+ isFetching: false
+ },
+ timetable: {
+ current_month: [],
+ current_periode_range: '',
+ periode_range: [],
+ time_table_periods: [],
+ time_table_dates: []
+ },
+ metas: {
+ comment: '',
+ day_types: [],
+ initial_tags: []
+ },
+ pagination: {
+ stateChanged: false,
+ currentPage: '',
+ periode_range: []
+ },
+ modal: {
+ type: '',
+ modalProps: {
+ active: false,
+ begin: {
+ day: '01',
+ month: '01',
+ year: String(new Date().getFullYear())
+ },
+ end: {
+ day: '01',
+ month: '01',
+ year: String(new Date().getFullYear())
+ },
+ index: false,
+ error: ''
+ },
+ confirmModal: {}
+ }
+}
+// const loggerMiddleware = createLogger()
+
+let store = createStore(
+ timeTablesApp,
+ initialState,
+ // applyMiddleware(thunkMiddleware, promise, loggerMiddleware)
+)
+
+render(
+ <Provider store={store}>
+ <App />
+ </Provider>,
+ document.getElementById('periods')
+)
diff --git a/app/javascript/packs/journey_patterns/index.js b/app/javascript/packs/journey_patterns/index.js
index fde28b45d..367a8830f 100644
--- a/app/javascript/packs/journey_patterns/index.js
+++ b/app/javascript/packs/journey_patterns/index.js
@@ -16,6 +16,7 @@ var initialState = {
editMode: false,
status: {
policy: window.perms,
+ features: window.features,
fetchSuccess: true,
isFetching: false
},
diff --git a/app/javascript/packs/referential_lines/show.js b/app/javascript/packs/referential_lines/show.js
new file mode 100644
index 000000000..542188018
--- /dev/null
+++ b/app/javascript/packs/referential_lines/show.js
@@ -0,0 +1,10 @@
+import clone from '../../helpers/clone'
+import RoutesMap from '../../helpers/routes_map'
+
+let routes = clone(window, "routes", true)
+routes = JSON.parse(decodeURIComponent(routes))
+
+var map = new RoutesMap('routes_map')
+map.addRoutes(routes)
+// map.addRoutesLabels()
+map.fitZoom()
diff --git a/app/javascript/packs/referential_overview/overview.js b/app/javascript/packs/referential_overview/overview.js
new file mode 100644
index 000000000..59c326e9a
--- /dev/null
+++ b/app/javascript/packs/referential_overview/overview.js
@@ -0,0 +1 @@
+import ReferentialOverview from '../../referential_overview'
diff --git a/app/javascript/packs/routes/edit.js b/app/javascript/packs/routes/edit.js
index d6ceed60f..b787bec97 100644
--- a/app/javascript/packs/routes/edit.js
+++ b/app/javascript/packs/routes/edit.js
@@ -1,4 +1,6 @@
-import React, { PropTypes } from 'react'
+import React from 'react'
+import PropTypes from 'prop-types'
+
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
@@ -12,7 +14,7 @@ datas = JSON.parse(decodeURIComponent(datas))
// logger, DO NOT REMOVE
var applyMiddleware = require('redux').applyMiddleware
-var createLogger = require('redux-logger')
+import {createLogger} from 'redux-logger';
var thunkMiddleware = require('redux-thunk').default
var promise = require('redux-promise')
diff --git a/app/javascript/packs/routes/show.js b/app/javascript/packs/routes/show.js
index 7f14a6f11..c20de0800 100644
--- a/app/javascript/packs/routes/show.js
+++ b/app/javascript/packs/routes/show.js
@@ -1,102 +1,8 @@
import clone from '../../helpers/clone'
+import RoutesMap from '../../helpers/routes_map'
+
let route = clone(window, "route", true)
route = JSON.parse(decodeURIComponent(route))
-
-const geoColPts = []
-const geoColLns = []
-const geoColEdges = [
- new ol.Feature({
- geometry: new ol.geom.Point(ol.proj.fromLonLat([parseFloat(route[0].longitude), parseFloat(route[0].latitude)]))
- }),
- new ol.Feature({
- geometry: new ol.geom.Point(ol.proj.fromLonLat([parseFloat(route[route.length - 1].longitude), parseFloat(route[route.length - 1].latitude)]))
- })
-]
-route.forEach(function (stop, i) {
- if (i < route.length - 1) {
- geoColLns.push(new ol.Feature({
- geometry: new ol.geom.LineString([
- ol.proj.fromLonLat([parseFloat(route[i].longitude), parseFloat(route[i].latitude)]),
- ol.proj.fromLonLat([parseFloat(route[i + 1].longitude), parseFloat(route[i + 1].latitude)])
- ])
- }))
- }
- geoColPts.push(new ol.Feature({
- geometry: new ol.geom.Point(ol.proj.fromLonLat([parseFloat(stop.longitude), parseFloat(stop.latitude)]))
- })
- )
-})
-var edgeStyles = new ol.style.Style({
- image: new ol.style.Circle(({
- radius: 5,
- stroke: new ol.style.Stroke({
- color: '#007fbb',
- width: 2
- }),
- fill: new ol.style.Fill({
- color: '#007fbb',
- width: 2
- })
- }))
-})
-var defaultStyles = new ol.style.Style({
- image: new ol.style.Circle(({
- radius: 4,
- stroke: new ol.style.Stroke({
- color: '#007fbb',
- width: 2
- }),
- fill: new ol.style.Fill({
- color: '#ffffff',
- width: 2
- })
- }))
-})
-var lineStyle = new ol.style.Style({
- stroke: new ol.style.Stroke({
- color: '#007fbb',
- width: 3
- })
-})
-
-var vectorPtsLayer = new ol.layer.Vector({
- source: new ol.source.Vector({
- features: geoColPts
- }),
- style: defaultStyles,
- zIndex: 2
-})
-var vectorEdgesLayer = new ol.layer.Vector({
- source: new ol.source.Vector({
- features: geoColEdges
- }),
- style: edgeStyles,
- zIndex: 3
-})
-var vectorLnsLayer = new ol.layer.Vector({
- source: new ol.source.Vector({
- features: geoColLns
- }),
- style: [lineStyle],
- zIndex: 1
-})
-
-var map = new ol.Map({
- target: 'route_map',
- layers: [
- new ol.layer.Tile({
- source: new ol.source.OSM()
- }),
- vectorPtsLayer,
- vectorEdgesLayer,
- vectorLnsLayer
- ],
- controls: [new ol.control.ScaleLine(), new ol.control.Zoom(), new ol.control.ZoomSlider()],
- interactions: ol.interaction.defaults({
- zoom: true
- }),
- view: new ol.View({
- center: ol.proj.fromLonLat([parseFloat(route[0].longitude), parseFloat(route[0].latitude)]),
- zoom: 13
- })
-});
+var map = new RoutesMap('route_map')
+map.addRoute(route)
+map.fitZoom()
diff --git a/app/javascript/packs/stop_areas/new.js b/app/javascript/packs/stop_areas/new.js
new file mode 100644
index 000000000..ffe702cdb
--- /dev/null
+++ b/app/javascript/packs/stop_areas/new.js
@@ -0,0 +1,3 @@
+import MasterSlave from "../../helpers/master_slave"
+
+new MasterSlave("form")
diff --git a/app/javascript/packs/vehicle_journeys/index.js b/app/javascript/packs/vehicle_journeys/index.js
index 38431af1d..e6867cb17 100644
--- a/app/javascript/packs/vehicle_journeys/index.js
+++ b/app/javascript/packs/vehicle_journeys/index.js
@@ -23,6 +23,7 @@ var initialState = {
filters: {
selectedJourneyPatterns : selectedJP,
policy: window.perms,
+ features: window.features,
toggleArrivals: false,
queryString: '',
query: {
@@ -54,11 +55,12 @@ var initialState = {
},
status: {
- fetchSuccess: true,
+ fetchSuccess: false,
isFetching: false
},
vehicleJourneys: [],
stopPointsList: window.stopPoints,
+ returnStopPointsList: window.returnStopPoints,
pagination: {
page : 1,
totalCount: 0,
@@ -69,7 +71,9 @@ var initialState = {
type: '',
modalProps: {},
confirmModal: {}
- }
+ },
+ missions: window.all_missions,
+ custom_fields: window.custom_fields
}
if (window.jpOrigin){
@@ -96,7 +100,7 @@ let store = createStore(
render(
<Provider store={store}>
- <App />
+ <App returnRouteUrl={window.returnRouteUrl} />
</Provider>,
document.getElementById('vehicle_journeys_wip')
-) \ No newline at end of file
+)
diff --git a/app/javascript/referential_overview/index.coffee b/app/javascript/referential_overview/index.coffee
new file mode 100644
index 000000000..0e6541421
--- /dev/null
+++ b/app/javascript/referential_overview/index.coffee
@@ -0,0 +1,113 @@
+class TimeTravel
+ constructor: (@overview)->
+ @container = @overview.container.find('.time-travel')
+ @todayBt = @container.find(".today")
+ @prevBt = @container.find(".prev-page")
+ @nextBt = @container.find(".next-page")
+ @searchDateBt = @container.find("a.search-date")
+ @searchDateInput = @container.find("input.date-search")
+ @initButtons()
+
+ initButtons: ->
+ @prevBt.click (e)=>
+ @overview.prevPage()
+ e.preventDefault()
+ false
+
+ @nextBt.click (e)=>
+ @overview.nextPage()
+ e.preventDefault()
+ false
+
+ @todayBt.click (e)=>
+ today = new Date()
+ month = today.getMonth() + 1
+ month = "0#{month}" if month < 10
+ day = today.getDate()
+ day = "0#{month}" if day < 10
+ @overview.showDay "#{today.getFullYear()}-#{month}-#{day}"
+ e.preventDefault()
+ false
+
+ @searchDateBt.click (e)=>
+ @overview.showDay @searchDateInput.val() if @searchDateInput.val().length > 0
+ e.preventDefault()
+ false
+
+ scrolledTo: (progress)->
+ @prevBt.removeClass 'disabled'
+ @nextBt.removeClass 'disabled'
+ @prevBt.addClass 'disabled' if progress == 0
+ @nextBt.addClass 'disabled' if progress == 1
+
+class window.ReferentialOverview
+ constructor: (selector)->
+ @container = $(selector)
+ @timeTravel = new TimeTravel(this)
+ @currentOffset = 0
+ $(document).scroll (e)=>
+ @documentScroll(e)
+ @documentScroll pageY: $(document).scrollTop()
+
+ showDay: (date)->
+ day = @container.find(".day.#{date}")
+ @container.find(".day.selected").removeClass('selected')
+ day.addClass "selected"
+ offset = day.offset().left
+ parentOffset = @currentOffset + @container.find(".right").offset().left
+ @scrollTo parentOffset - offset
+
+ currentOffset: ->
+ @container.find(".right .inner").offset().left
+
+ top: ->
+ @_top ||= @container.find('.days').offset().top - 80
+ bottom: ->
+ @_bottom ||= @top() + @container.height() - 50
+
+ prevPage: ->
+ @scrollTo @currentOffset + @container.find(".right").width()
+
+ nextPage: ->
+ @scrollTo @currentOffset - @container.find(".right").width()
+
+ minOffset: ->
+ @_minOffset ||= @container.find(".right").width() - @container.find(".right .line").width()
+ @_minOffset
+
+ scrollTo: (offset)->
+ @currentOffset = offset
+ @currentOffset = Math.max(@currentOffset, @minOffset())
+ @currentOffset = Math.min(@currentOffset, 0)
+ @container.find(".right .inner .lines").css "margin-left": "#{@currentOffset}px"
+ @container.find(".head .week:first-child").css "margin-left", "#{@currentOffset}px"
+ @timeTravel.scrolledTo 1 - (@minOffset() - @currentOffset) / @minOffset()
+ setTimeout =>
+ @movePeriodTitles()
+ , 600
+
+ movePeriodTitles: ->
+ @_right_offset ||= @container.find('.right').offset().left
+ @container.find(".shifted").removeClass("shifted").css "margin-left", 0
+ @container.find(".right .line").each (i, l) =>
+ $(l).find(".period").each (i, _p) =>
+ p = $(_p)
+ offset = parseInt(p.css("left")) + @currentOffset
+ if offset < 0 && - offset < p.width()
+ offset = Math.min(-offset, p.width() - 100)
+ p.find(".title").addClass("shifted").css "margin-left", offset + "px"
+ return
+
+ documentScroll: (e)->
+ if @sticky
+ if e.pageY < @top() || e.pageY > @bottom()
+ @container.removeClass "sticky"
+ @sticky = false
+ else
+ if e.pageY > @top() && e.pageY < @bottom()
+ @sticky = true
+ @container.addClass "sticky"
+
+
+
+export default ReferentialOverview
diff --git a/app/javascript/routes/components/App.js b/app/javascript/routes/components/App.js
index 0f5786407..26e69bf53 100644
--- a/app/javascript/routes/components/App.js
+++ b/app/javascript/routes/components/App.js
@@ -1,4 +1,5 @@
-import React, { Component, PropTypes } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import AddStopPoint from '../containers/AddStopPoint'
import VisibleStopPoints from'../containers/VisibleStopPoints'
import clone from '../../helpers/clone'
@@ -16,8 +17,8 @@ export default class App extends Component {
<VisibleStopPoints />
<AddStopPoint />
</div>
- )
- }
+ )
+ }
}
App.childContextTypes = {
diff --git a/app/javascript/routes/components/BSelect2.js b/app/javascript/routes/components/BSelect2.js
index 340d9df95..035bce155 100644
--- a/app/javascript/routes/components/BSelect2.js
+++ b/app/javascript/routes/components/BSelect2.js
@@ -1,6 +1,7 @@
import _ from'lodash'
-import React, { Component, PropTypes } from 'react'
-import Select2 from 'react-select2'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import Select2 from 'react-select2-wrapper'
// get JSON full path
@@ -84,7 +85,7 @@ class BSelect2 extends Component{
onSelect={ this.props.onSelect }
ref='newSelect'
options={{
- placeholder: this.context.I18n.routes.edit.select2.placeholder,
+ placeholder: this.context.I18n.t("routes.edit.select2.placeholder"),
allowClear: true,
language: 'fr', /* Doesn't seem to work... :( */
theme: 'bootstrap',
@@ -96,17 +97,26 @@ class BSelect2 extends Component{
data: function(params) {
return {
q: params.term,
- target_type: 'zdep'
+ scope: 'route_editor'
};
},
processResults: function(data, params) {
return {
- results: data.map(
- item => _.assign(
- {},
- item,
- { text: item.name + ", " + item.zip_code + " " + item.short_city_name + " <small><em>(" + item.user_objectid + ")</em></small>" }
- )
+ results: data.map(
+ function(item) {
+ var text = item.name;
+ if (item.zip_code || item.short_city_name) {
+ text += ","
+ }
+ if (item.zip_code) {
+ text += ` ${item.zip_code}`
+ }
+ if (item.short_city_name) {
+ text += ` ${item.short_city_name}`
+ }
+ text += ` <small><em>(${item.area_type.toUpperCase()}, ${item.user_objectid})</em></small>`;
+ return _.assign({}, item, { text: text });
+ }
)
};
},
diff --git a/app/javascript/routes/components/OlMap.js b/app/javascript/routes/components/OlMap.js
index 2c01dfa7f..4beb02872 100644
--- a/app/javascript/routes/components/OlMap.js
+++ b/app/javascript/routes/components/OlMap.js
@@ -1,5 +1,6 @@
import _ from 'lodash'
-import React, { Component, PropTypes } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
export default class OlMap extends Component{
constructor(props, context){
@@ -114,40 +115,40 @@ export default class OlMap extends Component{
<strong>{this.props.value.olMap.json.name}</strong>
</p>
<p>
- <strong>{this.context.I18n.routes.edit.stop_point_type} : </strong>
+ <strong>{this.context.I18n.t('routes.edit.map.stop_point_type')} : </strong>
{this.props.value.olMap.json.area_type}
</p>
<p>
- <strong>{this.context.I18n.routes.edit.short_name} : </strong>
+ <strong>{this.context.I18n.t('routes.edit.map.short_name')} : </strong>
{this.props.value.olMap.json.short_name}
</p>
<p>
- <strong>{this.context.I18n.id_reflex} : </strong>
+ <strong>{this.context.I18n.t('id_reflex')} : </strong>
{this.props.value.olMap.json.user_objectid}
</p>
- <p><strong>{this.context.I18n.routes.edit.map.coordinates} : </strong></p>
+ <p><strong>{this.context.I18n.t('routes.edit.map.coordinates')} : </strong></p>
<p style={{paddingLeft: 10, marginTop: 0}}>
- <em>{this.context.I18n.routes.edit.map.proj}.: </em>WSG84<br/>
- <em>{this.context.I18n.routes.edit.map.lat}.: </em>{this.props.value.olMap.json.latitude} <br/>
- <em>{this.context.I18n.routes.edit.map.lon}.: </em>{this.props.value.olMap.json.longitude}
+ <em>{this.context.I18n.t('routes.edit.map.proj')}.: </em>WSG84<br/>
+ <em>{this.context.I18n.t('routes.edit.map.lat')}.: </em>{this.props.value.olMap.json.latitude} <br/>
+ <em>{this.context.I18n.t('routes.edit.map.lon')}.: </em>{this.props.value.olMap.json.longitude}
</p>
<p>
- <strong>{this.context.I18n.routes.edit.map.postal_code} : </strong>
+ <strong>{this.context.I18n.t('routes.edit.map.postal_code')} : </strong>
{this.props.value.olMap.json.zip_code}
</p>
<p>
- <strong>{this.context.I18n.routes.edit.map.city} : </strong>
+ <strong>{this.context.I18n.t('routes.edit.map.city')} : </strong>
{this.props.value.olMap.json.city_name}
</p>
<p>
- <strong>{this.context.I18n.routes.edit.map.comment} : </strong>
+ <strong>{this.context.I18n.t('routes.edit.map.comment')} : </strong>
{this.props.value.olMap.json.comment}
</p>
{(this.props.value.stoparea_id != this.props.value.olMap.json.stoparea_id) &&(
<div className='btn btn-outline-primary btn-sm'
onClick= {() => {this.props.onUpdateViaOlMap(this.props.index, this.props.value.olMap.json)}}
- >{this.context.I18n.actions.select}</div>
+ >{this.context.I18n.t('actions.select')}</div>
)}
</div>
<div className='map_content'>
@@ -161,7 +162,7 @@ export default class OlMap extends Component{
}
}
-OlMap.PropTypes = {
+OlMap.propTypes = {
}
OlMap.contextTypes = {
diff --git a/app/javascript/routes/components/StopPoint.js b/app/javascript/routes/components/StopPoint.js
index 606121f99..af51a6bb4 100644
--- a/app/javascript/routes/components/StopPoint.js
+++ b/app/javascript/routes/components/StopPoint.js
@@ -1,4 +1,6 @@
-import React, { PropTypes } from 'react'
+import React from 'react'
+import PropTypes from 'prop-types'
+
import BSelect2 from './BSelect2'
import OlMap from './OlMap'
@@ -16,15 +18,15 @@ export default function StopPoint(props, {I18n}) {
<div>
<select className='form-control' value={props.value.for_boarding} id="for_boarding" onChange={props.onSelectChange}>
- <option value="normal">{I18n.routes.edit.stop_point.boarding.normal}</option>
- <option value="forbidden">{I18n.routes.edit.stop_point.boarding.forbidden}</option>
+ <option value="normal">{I18n.t('routes.edit.stop_point.boarding.normal')}</option>
+ <option value="forbidden">{I18n.t('routes.edit.stop_point.boarding.forbidden')}</option>
</select>
</div>
<div>
<select className='form-control' value={props.value.for_alighting} id="for_alighting" onChange={props.onSelectChange}>
- <option value="normal">{I18n.routes.edit.stop_point.alighting.normal}</option>
- <option value="forbidden">{I18n.routes.edit.stop_point.alighting.forbidden}</option>
+ <option value="normal">{I18n.t('routes.edit.stop_point.alighting.normal')}</option>
+ <option value="forbidden">{I18n.t('routes.edit.stop_point.alighting.forbidden')}</option>
</select>
</div>
@@ -75,7 +77,7 @@ export default function StopPoint(props, {I18n}) {
)
}
-StopPoint.PropTypes = {
+StopPoint.propTypes = {
onToggleMap: PropTypes.func.isRequired,
onToggleEdit: PropTypes.func.isRequired,
onDeleteClick: PropTypes.func.isRequired,
@@ -91,4 +93,4 @@ StopPoint.PropTypes = {
StopPoint.contextTypes = {
I18n: PropTypes.object
-} \ No newline at end of file
+}
diff --git a/app/javascript/routes/components/StopPointList.js b/app/javascript/routes/components/StopPointList.js
index 68af16f57..b227abdea 100644
--- a/app/javascript/routes/components/StopPointList.js
+++ b/app/javascript/routes/components/StopPointList.js
@@ -1,4 +1,6 @@
-import React, { PropTypes } from 'react'
+import React from 'react'
+import PropTypes from 'prop-types'
+
import StopPoint from './StopPoint'
export default function StopPointList({ stopPoints, onDeleteClick, onMoveUpClick, onMoveDownClick, onChange, onSelectChange, onToggleMap, onToggleEdit, onSelectMarker, onUnselectMarker, onUpdateViaOlMap }, {I18n}) {
@@ -8,22 +10,22 @@ export default function StopPointList({ stopPoints, onDeleteClick, onMoveUpClick
<div className="wrapper">
<div style={{width: 100}}>
<div className="form-group">
- <label className="control-label">{I18n.reflex_id}</label>
+ <label className="control-label">{I18n.t('simple_form.labels.stop_point.reflex_id')}</label>
</div>
</div>
<div>
<div className="form-group">
- <label className="control-label">{I18n.simple_form.labels.stop_point.name}</label>
+ <label className="control-label">{I18n.t('simple_form.labels.stop_point.name')}</label>
</div>
</div>
<div>
<div className="form-group">
- <label className="control-label">{I18n.simple_form.labels.stop_point.for_boarding}</label>
+ <label className="control-label">{I18n.t('simple_form.labels.stop_point.for_boarding')}</label>
</div>
</div>
<div>
<div className="form-group">
- <label className="control-label">{I18n.simple_form.labels.stop_point.for_alighting}</label>
+ <label className="control-label">{I18n.t('simple_form.labels.stop_point.for_alighting')}</label>
</div>
</div>
<div className='actions-5'></div>
@@ -54,7 +56,7 @@ export default function StopPointList({ stopPoints, onDeleteClick, onMoveUpClick
)
}
-StopPointList.PropTypes = {
+StopPointList.propTypes = {
stopPoints: PropTypes.array.isRequired,
onDeleteClick: PropTypes.func.isRequired,
onMoveUpClick: PropTypes.func.isRequired,
@@ -66,4 +68,4 @@ StopPointList.PropTypes = {
StopPointList.contextTypes = {
I18n: PropTypes.object
-} \ No newline at end of file
+}
diff --git a/app/javascript/routes/form_helper.js b/app/javascript/routes/form_helper.js
index 8a3277234..865722fb6 100644
--- a/app/javascript/routes/form_helper.js
+++ b/app/javascript/routes/form_helper.js
@@ -7,14 +7,14 @@ const formHelper = {
input.setAttribute('name', formatedName)
input.setAttribute('value', value)
form.appendChild(input)
- },
+ },
addError: (ids) => {
ids.forEach((id) => {
if (!$(id).parents('.form-group').hasClass('has-error')) {
$(id).parents('.form-group').addClass('has-error')
$(id).parent().append(`<span class='help-block small'>${'doit être rempli(e)'}</span>`)
}
- })
+ })
},
cleanInputs: (ids) => {
ids.forEach((id) =>{
@@ -28,21 +28,22 @@ const formHelper = {
ids.forEach(id => {
$(id).val() == "" ? blankInputs.push(id) : filledInputs.push(id)
})
-
+
if (filledInputs.length > 0) formHelper.cleanInputs(filledInputs)
- if (blankInputs.length > 0) formHelper.addError(blankInputs)
+ if (blankInputs.length > 0) formHelper.addError(blankInputs)
},
handleStopPoints: (event, state) => {
if (state.stopPoints.length >= 2) {
state.stopPoints.map((stopPoint, i) => {
formHelper.addInput('id', stopPoint.stoppoint_id ? stopPoint.stoppoint_id : '', i)
formHelper.addInput('stop_area_id', stopPoint.stoparea_id, i)
- formHelper.addInput('position', i, i)
+ formHelper.addInput('position', stopPoint.index, i)
formHelper.addInput('for_boarding', stopPoint.for_boarding, i)
formHelper.addInput('for_alighting', stopPoint.for_alighting, i)
})
- if ($('.alert.alert-danger').length > 0) $('.alert.alert-danger').remove()
- } else {
+ if ($('.alert.alert-danger').length > 0) $('.alert.alert-danger').remove()
+ }
+ else {
event.preventDefault()
let msg = "L'itinéraire doit comporter au moins deux arrêts"
if ($('.alert.alert-danger').length == 0) {
@@ -52,4 +53,4 @@ const formHelper = {
}
}
-export default formHelper \ No newline at end of file
+export default formHelper
diff --git a/app/javascript/routes/reducers/stopPoints.js b/app/javascript/routes/reducers/stopPoints.js
index eeec06327..0b42b504f 100644
--- a/app/javascript/routes/reducers/stopPoints.js
+++ b/app/javascript/routes/reducers/stopPoints.js
@@ -38,15 +38,15 @@ const stopPoints = (state = [], action) => {
case 'MOVE_STOP_UP':
return [
...state.slice(0, action.index - 1),
- _.assign({}, state[action.index], { stoppoint_id: state[action.index - 1].stoppoint_id }),
- _.assign({}, state[action.index - 1], { stoppoint_id: state[action.index].stoppoint_id }),
+ _.assign({}, state[action.index], { index: action.index - 1 }),
+ _.assign({}, state[action.index - 1], { index: action.index }),
...state.slice(action.index + 1)
]
case 'MOVE_STOP_DOWN':
return [
...state.slice(0, action.index),
- _.assign({}, state[action.index + 1], { stoppoint_id: state[action.index].stoppoint_id }),
- _.assign({}, state[action.index], { stoppoint_id: state[action.index + 1].stoppoint_id }),
+ _.assign({}, state[action.index + 1], { index: action.index }),
+ _.assign({}, state[action.index], { index: action.index + 1 }),
...state.slice(action.index + 2)
]
case 'DELETE_STOP':
@@ -141,4 +141,4 @@ const stopPoints = (state = [], action) => {
}
}
-export default stopPoints \ No newline at end of file
+export default stopPoints
diff --git a/app/javascript/time_tables/actions/index.js b/app/javascript/time_tables/actions/index.js
index 13cb96b64..98b9eab4b 100644
--- a/app/javascript/time_tables/actions/index.js
+++ b/app/javascript/time_tables/actions/index.js
@@ -1,5 +1,5 @@
-import range from 'lodash/range'
import assign from 'lodash/assign'
+import range from 'lodash/range'
import reject from 'lodash/reject'
import some from 'lodash/some'
import every from 'lodash/every'
@@ -8,7 +8,7 @@ const I18n = clone(window, "I18n")
const actions = {
weekDays: (index) => {
- return range(1, 8).map(n => I18n.time_tables.edit.metas.days[n])
+ return range(1, 8).map(n => I18n.t('time_tables.edit.metas.days')[n])
},
strToArrayDayTypes: (str) =>{
return actions.weekDays().map(day => str.indexOf(day) !== -1)
@@ -155,9 +155,9 @@ const actions = {
type : 'CLOSE_MODAL'
}),
monthName(strDate) {
- let monthList = range(1,13).map(n => I18n.calendars.months[n])
+ let monthList = range(1,13).map(n => I18n.t('calendars.months.'+ n ))
let date = new Date(strDate)
- return monthList[date.getMonth()]
+ return monthList[date.getUTCMonth()]
},
getHumanDate(strDate, mLimit) {
let origin = strDate.split('-')
@@ -173,7 +173,7 @@ const actions = {
},
getLocaleDate(strDate) {
let date = new Date(strDate)
- return date.toLocaleDateString()
+ return date.toLocaleDateString(undefined, { timeZone: 'UTC' })
},
updateSynthesis: ({current_month, time_table_dates: dates, time_table_periods: periods}) => {
let newPeriods = reject(periods, 'deleted')
@@ -194,7 +194,7 @@ const actions = {
for (let period of periods) {
let begin = new Date(period.period_start)
- let end = new Date(period.period_end)
+ let end = new Date(period.period_end)
if (date >= begin && date <= end) return true
}
@@ -225,7 +225,7 @@ const actions = {
let period = periods[i]
if (index !== i && !period.deleted) {
if (new Date(period.period_start) <= end && new Date(period.period_end) >= start) {
- error = I18n.time_tables.edit.error_submit.periods_overlaps
+ error = I18n.t('time_tables.edit.error_submit.periods_overlaps')
break
}
}
@@ -239,14 +239,15 @@ const actions = {
for (let day of in_days) {
if (start <= new Date(day.date) && end >= new Date(day.date)) {
- error = I18n.time_tables.edit.error_submit.dates_overlaps
+ error = I18n.t('time_tables.edit.error_submit.dates_overlaps')
break
}
}
return error
},
fetchTimeTables: (dispatch, nextPage) => {
- let urlJSON = window.location.pathname.split('/', 5).join('/')
+ let urlJSON = window.timetablesUrl || window.location.pathname.split('/', 5).join('/')
+
if(nextPage) {
urlJSON += "/month.json?date=" + nextPage
}else{
@@ -277,7 +278,7 @@ const actions = {
let strDayTypes = actions.arrayToStrDayTypes(metas.day_types)
metas.day_types = strDayTypes
let sentState = assign({}, timetable, metas)
- let urlJSON = window.location.pathname.split('/', 5).join('/')
+ let urlJSON = window.timetablesUrl || window.location.pathname.split('/', 5).join('/')
let hasError = false
fetch(urlJSON + '.json', {
credentials: 'same-origin',
@@ -315,9 +316,9 @@ const actions = {
errorModalMessage: (errorKey) => {
switch (errorKey) {
case "withoutPeriodsWithDaysTypes":
- return I18n.time_tables.edit.error_modal.withoutPeriodsWithDaysTypes
+ return I18n.t('time_tables.edit.error_modal.withoutPeriodsWithDaysTypes')
case "withPeriodsWithoutDayTypes":
- return I18n.time_tables.edit.error_modal.withPeriodsWithoutDayTypes
+ return I18n.t('time_tables.edit.error_modal.withPeriodsWithoutDayTypes')
default:
return errorKey
@@ -325,4 +326,4 @@ const actions = {
}
}
-export default actions \ No newline at end of file
+export default actions
diff --git a/app/javascript/time_tables/components/ConfirmModal.js b/app/javascript/time_tables/components/ConfirmModal.js
index d89170ee7..4e8583bc0 100644
--- a/app/javascript/time_tables/components/ConfirmModal.js
+++ b/app/javascript/time_tables/components/ConfirmModal.js
@@ -1,4 +1,6 @@
-import React, { PropTypes } from 'react'
+import React from 'react'
+import PropTypes from 'prop-types'
+
export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCancel, timetable, metas}, {I18n}) {
return (
@@ -7,11 +9,11 @@ export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCan
<div className='modal-dialog'>
<div className='modal-content'>
<div className='modal-header'>
- <h4 className='modal-title'>{I18n.time_tables.edit.confirm_modal.title}</h4>
+ <h4 className='modal-title'>{I18n.t('time_tables.edit.confirm_modal.title')}</h4>
</div>
<div className='modal-body'>
<div className='mt-md mb-md'>
- <p>{I18n.time_tables.edit.confirm_modal.message}</p>
+ <p>{I18n.t('time_tables.edit.confirm_modal.message')}</p>
</div>
</div>
<div className='modal-footer'>
@@ -21,7 +23,7 @@ export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCan
type='button'
onClick={() => { onModalCancel(modal.confirmModal.callback) }}
>
- {I18n.cancel}
+ {I18n.t('cancel')}
</button>
<button
className='btn btn-primary'
@@ -29,7 +31,7 @@ export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCan
type='button'
onClick={() => { onModalAccept(modal.confirmModal.callback, timetable, metas) }}
>
- {I18n.actions.submit}
+ {I18n.t('actions.submit')}
</button>
</div>
</div>
@@ -47,4 +49,4 @@ ConfirmModal.propTypes = {
ConfirmModal.contextTypes = {
I18n: PropTypes.object
-} \ No newline at end of file
+}
diff --git a/app/javascript/time_tables/components/ErrorModal.js b/app/javascript/time_tables/components/ErrorModal.js
index e810f49ab..8af12f1d1 100644
--- a/app/javascript/time_tables/components/ErrorModal.js
+++ b/app/javascript/time_tables/components/ErrorModal.js
@@ -1,4 +1,6 @@
-import React, { PropTypes } from 'react'
+import React from 'react'
+import PropTypes from 'prop-types'
+
import actions from '../actions'
export default function ErrorModal({dispatch, modal, onModalClose}, {I18n}) {
@@ -8,7 +10,7 @@ export default function ErrorModal({dispatch, modal, onModalClose}, {I18n}) {
<div className='modal-dialog'>
<div className='modal-content'>
<div className='modal-header'>
- <h4 className='modal-title'>{I18n.time_tables.edit.error_modal.title}</h4>
+ <h4 className='modal-title'>{I18n.t('time_tables.edit.error_modal.title')}</h4>
</div>
<div className='modal-body'>
<div className='mt-md mb-md'>
@@ -22,7 +24,7 @@ export default function ErrorModal({dispatch, modal, onModalClose}, {I18n}) {
type='button'
onClick={() => { onModalClose() }}
>
- {I18n.back}
+ {I18n.t('back')}
</button>
</div>
</div>
@@ -39,4 +41,4 @@ ErrorModal.propTypes = {
ErrorModal.contextTypes = {
I18n: PropTypes.object
-} \ No newline at end of file
+}
diff --git a/app/javascript/time_tables/components/ExceptionsInDay.js b/app/javascript/time_tables/components/ExceptionsInDay.js
index 3335ee89d..f5ed625be 100644
--- a/app/javascript/time_tables/components/ExceptionsInDay.js
+++ b/app/javascript/time_tables/components/ExceptionsInDay.js
@@ -1,4 +1,5 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import actions from '../actions'
export default class ExceptionsInDay extends Component {
diff --git a/app/javascript/time_tables/components/Metas.js b/app/javascript/time_tables/components/Metas.js
index 7098d2b82..08a6e26fe 100644
--- a/app/javascript/time_tables/components/Metas.js
+++ b/app/javascript/time_tables/components/Metas.js
@@ -1,4 +1,6 @@
-import React, { PropTypes } from 'react'
+import React from 'react'
+import PropTypes from 'prop-types'
+
import actions from '../actions'
import TagsSelect2 from './TagsSelect2'
@@ -11,7 +13,7 @@ export default function Metas({metas, onUpdateDayTypes, onUpdateComment, onUpdat
{/* comment (name) */}
<div className="form-group">
<label htmlFor="" className="control-label col-sm-4 required">
- {I18n.time_tables.edit.metas.name} <abbr title="">*</abbr>
+ {I18n.t('time_tables.edit.metas.name')} <abbr title="">*</abbr>
</label>
<div className="col-sm-8">
<input
@@ -25,8 +27,8 @@ export default function Metas({metas, onUpdateDayTypes, onUpdateComment, onUpdat
</div>
{/* color */}
- <div className="form-group">
- <label htmlFor="" className="control-label col-sm-4">{I18n.activerecord.attributes.time_table.color}</label>
+ {metas.color !== undefined && <div className="form-group">
+ <label htmlFor="" className="control-label col-sm-4">{I18n.attribute_name('time_table', 'color')}</label>
<div className="col-sm-8">
<div className="dropdown color_selector">
<button
@@ -67,11 +69,11 @@ export default function Metas({metas, onUpdateDayTypes, onUpdateComment, onUpdat
</div>
</div>
</div>
- </div>
+ </div>}
{/* tags */}
- <div className="form-group">
- <label htmlFor="" className="control-label col-sm-4">{I18n.activerecord.attributes.time_table.tag_list}</label>
+ {metas.tags !== undefined && <div className="form-group">
+ <label htmlFor="" className="control-label col-sm-4">{I18n.attribute_name('time_table', 'tag_list')}</label>
<div className="col-sm-8">
<TagsSelect2
initialTags={metas.initial_tags}
@@ -80,20 +82,20 @@ export default function Metas({metas, onUpdateDayTypes, onUpdateComment, onUpdat
onUnselect2Tags={(e) => onUnselect2Tags(e)}
/>
</div>
- </div>
+ </div>}
{/* calendar */}
- <div className="form-group">
- <label htmlFor="" className="control-label col-sm-4">{I18n.activerecord.attributes.time_table.calendar}</label>
+ {metas.calendar !== null && <div className="form-group">
+ <label htmlFor="" className="control-label col-sm-4">{I18n.attribute_name('time_table', 'calendar')}</label>
<div className="col-sm-8">
- <span>{metas.calendar ? metas.calendar.name : I18n.time_tables.edit.metas.no_calendar}</span>
+ <span>{metas.calendar ? metas.calendar.name : I18n.t('time_tables.edit.metas.no_calendar')}</span>
</div>
- </div>
+ </div>}
{/* day_types */}
<div className="form-group">
<label htmlFor="" className="control-label col-sm-4">
- {I18n.time_tables.edit.metas.day_types}
+ {I18n.t('time_tables.edit.metas.day_types')}
</label>
<div className="col-sm-8">
<div className="form-group labelled-checkbox-group">
diff --git a/app/javascript/time_tables/components/Navigate.js b/app/javascript/time_tables/components/Navigate.js
index 7307d819b..1467fffe9 100644
--- a/app/javascript/time_tables/components/Navigate.js
+++ b/app/javascript/time_tables/components/Navigate.js
@@ -1,4 +1,5 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import map from 'lodash/map'
import actions from '../actions'
@@ -23,7 +24,7 @@ export default function Navigate({ dispatch, metas, timetable, pagination, statu
aria-haspopup='true'
aria-expanded='true'
>
- {pagination.currentPage ? (actions.monthName(pagination.currentPage) + ' ' + new Date(pagination.currentPage).getFullYear()) : ''}
+ {pagination.currentPage ? (actions.monthName(pagination.currentPage) + ' ' + new Date(pagination.currentPage).getUTCFullYear()) : ''}
<span className='caret'></span>
</div>
<ul
@@ -40,7 +41,7 @@ export default function Navigate({ dispatch, metas, timetable, pagination, statu
dispatch(actions.checkConfirmModal(e, actions.changePage(dispatch, e.currentTarget.value), pagination.stateChanged, dispatch, metas, timetable))
}}
>
- {actions.monthName(month) + ' ' + new Date(month).getFullYear()}
+ {actions.monthName(month) + ' ' + new Date(month).getUTCFullYear()}
</button>
</li>
))}
@@ -85,4 +86,4 @@ Navigate.propTypes = {
status: PropTypes.object.isRequired,
pagination: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired
-} \ No newline at end of file
+}
diff --git a/app/javascript/time_tables/components/PeriodForm.js b/app/javascript/time_tables/components/PeriodForm.js
index d9f1d3437..d17a246f7 100644
--- a/app/javascript/time_tables/components/PeriodForm.js
+++ b/app/javascript/time_tables/components/PeriodForm.js
@@ -1,4 +1,6 @@
-import React, { PropTypes } from 'react'
+import React from 'react'
+import PropTypes from 'prop-types'
+
import filter from 'lodash/filter'
let monthsArray = ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre']
@@ -44,7 +46,7 @@ export default function PeriodForm({modal, timetable, metas, onOpenAddPeriodForm
<div>
<div className="form-group">
<label htmlFor="" className="control-label required">
- {I18n.time_tables.edit.period_form.begin}
+ {I18n.t('time_tables.edit.period_form.begin')}
<abbr title="requis">*</abbr>
</label>
</div>
@@ -52,7 +54,7 @@ export default function PeriodForm({modal, timetable, metas, onOpenAddPeriodForm
<div>
<div className="form-group">
<label htmlFor="" className="control-label required">
- {I18n.time_tables.edit.period_form.end}
+ {I18n.t('time_tables.edit.period_form.end')}
<abbr title="requis">*</abbr>
</label>
</div>
@@ -103,14 +105,14 @@ export default function PeriodForm({modal, timetable, metas, onOpenAddPeriodForm
className='btn btn-link'
onClick={onClosePeriodForm}
>
- {I18n.cancel}
+ {I18n.t('cancel')}
</button>
<button
type='button'
className='btn btn-outline-primary mr-sm'
onClick={() => onValidatePeriodForm(modal.modalProps, timetable.time_table_periods, metas, filter(timetable.time_table_dates, ['in_out', true]))}
>
- {I18n.actions.submit}
+ {I18n.t('actions.submit')}
</button>
</div>
</div>
@@ -122,7 +124,7 @@ export default function PeriodForm({modal, timetable, metas, onOpenAddPeriodForm
className='btn btn-outline-primary'
onClick={onOpenAddPeriodForm}
>
- {I18n.time_tables.actions.add_period}
+ {I18n.t('time_tables.actions.add_period')}
</button>
</div>
}
@@ -130,7 +132,7 @@ export default function PeriodForm({modal, timetable, metas, onOpenAddPeriodForm
</div>
</div>
</div>
- )
+ )
}
PeriodForm.propTypes = {
@@ -145,4 +147,4 @@ PeriodForm.propTypes = {
PeriodForm.contextTypes = {
I18n: PropTypes.object
-} \ No newline at end of file
+}
diff --git a/app/javascript/time_tables/components/PeriodManager.js b/app/javascript/time_tables/components/PeriodManager.js
index 9922ce2c4..6b817fe73 100644
--- a/app/javascript/time_tables/components/PeriodManager.js
+++ b/app/javascript/time_tables/components/PeriodManager.js
@@ -1,4 +1,5 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import actions from '../actions'
export default class PeriodManager extends Component {
diff --git a/app/javascript/time_tables/components/PeriodsInDay.js b/app/javascript/time_tables/components/PeriodsInDay.js
index 888537579..1aed5c969 100644
--- a/app/javascript/time_tables/components/PeriodsInDay.js
+++ b/app/javascript/time_tables/components/PeriodsInDay.js
@@ -1,4 +1,5 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import PeriodManager from './PeriodManager'
export default class PeriodsInDay extends Component {
diff --git a/app/javascript/time_tables/components/SaveTimetable.js b/app/javascript/time_tables/components/SaveTimetable.js
index d5a57bd1c..704590abd 100644
--- a/app/javascript/time_tables/components/SaveTimetable.js
+++ b/app/javascript/time_tables/components/SaveTimetable.js
@@ -1,4 +1,5 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import actions from '../actions'
export default class SaveTimetable extends Component{
diff --git a/app/javascript/time_tables/components/TagsSelect2.js b/app/javascript/time_tables/components/TagsSelect2.js
index 70a748a04..43cf59fdf 100644
--- a/app/javascript/time_tables/components/TagsSelect2.js
+++ b/app/javascript/time_tables/components/TagsSelect2.js
@@ -1,9 +1,10 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import mapKeys from 'lodash/mapKeys'
import map from 'lodash/map'
import filter from 'lodash/filter'
import assign from 'lodash/assign'
-import Select2 from 'react-select2'
+import Select2 from 'react-select2-wrapper'
// get JSON full path
let origin = window.location.origin
@@ -39,7 +40,7 @@ export default class TagsSelect2 extends Component {
allowClear: true,
theme: 'bootstrap',
width: '100%',
- placeholder: this.context.I18n.time_tables.edit.select2.tag.placeholder,
+ placeholder: this.context.I18n.t('time_tables.edit.select2.tag.placeholder'),
ajax: {
url: origin + path + '/tags.json',
dataType: 'json',
diff --git a/app/javascript/time_tables/components/TimeTableDay.js b/app/javascript/time_tables/components/TimeTableDay.js
index 165c7b848..498e7d0cd 100644
--- a/app/javascript/time_tables/components/TimeTableDay.js
+++ b/app/javascript/time_tables/components/TimeTableDay.js
@@ -1,4 +1,5 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
export default class TimeTableDay extends Component {
constructor(props) {
diff --git a/app/javascript/time_tables/components/Timetable.js b/app/javascript/time_tables/components/Timetable.js
index df6e6016b..991f31435 100644
--- a/app/javascript/time_tables/components/Timetable.js
+++ b/app/javascript/time_tables/components/Timetable.js
@@ -1,4 +1,5 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import actions from '../actions'
import TimeTableDay from './TimeTableDay'
import PeriodsInDay from './PeriodsInDay'
@@ -30,11 +31,11 @@ export default class Timetable extends Component {
<div className="table table-2entries mb-sm">
<div className="t2e-head w20">
<div className="th">
- <div className="strong">{this.context.I18n.time_tables.synthesis}</div>
+ <div className="strong">{this.context.I18n.t('time_tables.edit.synthesis')}</div>
</div>
- <div className="td"><span>{this.context.I18n.time_tables.edit.day_types}</span></div>
- <div className="td"><span>{this.context.I18n.time_tables.edit.periods}</span></div>
- <div className="td"><span>{this.context.I18n.time_tables.edit.exceptions}</span></div>
+ <div className="td"><span>{this.context.I18n.t('time_tables.edit.day_types')}</span></div>
+ <div className="td"><span>{this.context.I18n.t('time_tables.edit.periods')}</span></div>
+ <div className="td"><span>{this.context.I18n.t('time_tables.edit.exceptions')}</span></div>
</div>
<div className="t2e-item-list w80">
<div>
diff --git a/app/javascript/time_tables/containers/App.js b/app/javascript/time_tables/containers/App.js
index 235dccb50..5963f8f1d 100644
--- a/app/javascript/time_tables/containers/App.js
+++ b/app/javascript/time_tables/containers/App.js
@@ -1,4 +1,5 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import { connect } from'react-redux'
import actions from '../actions'
import Metas from './Metas'
diff --git a/app/javascript/vehicle_journeys/actions/index.js b/app/javascript/vehicle_journeys/actions/index.js
index ce4b9209d..4ca8bd73b 100644
--- a/app/javascript/vehicle_journeys/actions/index.js
+++ b/app/javascript/vehicle_journeys/actions/index.js
@@ -13,8 +13,8 @@ const actions = {
exitEditMode: () => ({
type: "EXIT_EDIT_MODE"
}),
- receiveVehicleJourneys : (json) => ({
- type: "RECEIVE_VEHICLE_JOURNEYS",
+ receiveVehicleJourneys : (json, returnJourneys) => ({
+ type: (returnJourneys ? "RECEIVE_RETURN_VEHICLE_JOURNEYS" : "RECEIVE_VEHICLE_JOURNEYS"),
json
}),
receiveErrors : (json) => ({
@@ -57,15 +57,22 @@ const actions = {
selectedItem: {
id: selectedJP.id,
objectid: selectedJP.object_id,
+ short_id: selectedJP.short_id,
name: selectedJP.name,
published_name: selectedJP.published_name,
- stop_areas: selectedJP.stop_area_short_descriptions
+ stop_areas: selectedJP.stop_area_short_descriptions,
+ costs: selectedJP.costs,
+ full_schedule: selectedJP.full_schedule
}
}),
openEditModal : (vehicleJourney) => ({
type : 'EDIT_VEHICLEJOURNEY_MODAL',
vehicleJourney
}),
+ openInfoModal : (vehicleJourney) => ({
+ type : 'INFO_VEHICLEJOURNEY_MODAL',
+ vehicleJourney
+ }),
openNotesEditModal : (vehicleJourney) => ({
type : 'EDIT_NOTES_VEHICLEJOURNEY_MODAL',
vehicleJourney
@@ -84,7 +91,8 @@ const actions = {
selectedItem:{
id: selectedTT.id,
comment: selectedTT.comment,
- objectid: selectedTT.objectid
+ objectid: selectedTT.objectid,
+ color: selectedTT.color
}
}),
addSelectedTimetable: () => ({
@@ -99,6 +107,31 @@ const actions = {
vehicleJourneys,
timetables
}),
+ openPurchaseWindowsEditModal : (vehicleJourneys) => ({
+ type : 'EDIT_PURCHASE_WINDOWS_VEHICLEJOURNEY_MODAL',
+ vehicleJourneys
+ }),
+ selectPurchaseWindowsModal: (selectedWindow) =>({
+ type: 'SELECT_PURCHASE_WINDOW_MODAL',
+ selectedItem:{
+ id: selectedWindow.id,
+ name: selectedWindow.name,
+ color: selectedWindow.color,
+ objectid: selectedWindow.objectid
+ }
+ }),
+ addSelectedPurchaseWindow: () => ({
+ type: 'ADD_SELECTED_PURCHASE_WINDOW'
+ }),
+ deletePurchaseWindowsModal : (purchaseWindow) => ({
+ type : 'DELETE_PURCHASE_WINDOW_MODAL',
+ purchaseWindow
+ }),
+ editVehicleJourneyPurchaseWindows : (vehicleJourneys, purchase_windows) => ({
+ type: 'EDIT_VEHICLEJOURNEYS_PURCHASE_WINDOWS',
+ vehicleJourneys,
+ purchase_windows
+ }),
openShiftModal : () => ({
type : 'SHIFT_VEHICLEJOURNEY_MODAL'
}),
@@ -162,26 +195,18 @@ const actions = {
resetValidation: (target) => {
$(target).parent().removeClass('has-error').children('.help-block').remove()
},
- validateFields : (...fields) => {
- const test = []
-
- Object.keys(fields).map(function(key) {
- test.push(fields[key].validity.valid)
+ validateFields : (fields) => {
+ let valid = true
+ Object.keys(fields).forEach((key) => {
+ let field = fields[key]
+ if(field.validity && !field.validity.valid){
+ valid = false
+ $(field).parent().addClass('has-error').children('.help-block').remove()
+ $(field).parent().append("<span class='small help-block'>" + field.validationMessage + "</span>")
+ }
})
- if(test.indexOf(false) >= 0) {
- // Form is invalid
- test.map(function(item, i) {
- if(item == false) {
- const k = Object.keys(fields)[i]
- $(fields[k]).parent().addClass('has-error').children('.help-block').remove()
- $(fields[k]).parent().append("<span class='small help-block'>" + fields[k].validationMessage + "</span>")
- }
- })
- return false
- } else {
- // Form is valid
- return true
- }
+
+ return valid
},
toggleArrivals : () => ({
type: 'TOGGLE_ARRIVALS',
@@ -269,10 +294,17 @@ const actions = {
type: 'RECEIVE_TOTAL_COUNT',
total
}),
- fetchVehicleJourneys : (dispatch, currentPage, nextPage, queryString) => {
+ fetchVehicleJourneys : (dispatch, currentPage, nextPage, queryString, url) => {
+ let returnJourneys = false
if(currentPage == undefined){
currentPage = 1
}
+ if(url == undefined){
+ url = window.location.pathname
+ }
+ else{
+ returnJourneys = true
+ }
let vehicleJourneys = []
let page
switch (nextPage) {
@@ -294,7 +326,7 @@ const actions = {
str = '.json?page=' + page.toString()
sep = '&'
}
- let urlJSON = window.location.pathname + str
+ let urlJSON = url + str
if (queryString){
urlJSON = urlJSON + sep + queryString
}
@@ -313,6 +345,7 @@ const actions = {
let val
for (val of json.vehicle_journeys){
var timeTables = []
+ var purchaseWindows = []
let tt
for (tt of val.time_tables){
timeTables.push({
@@ -322,33 +355,56 @@ const actions = {
color: tt.color
})
}
+ if(val.purchase_windows){
+ for (tt of val.purchase_windows){
+ purchaseWindows.push({
+ objectid: tt.objectid,
+ name: tt.name,
+ id: tt.id,
+ color: tt.color
+ })
+ }
+ }
let vjasWithDelta = val.vehicle_journey_at_stops.map((vjas, i) => {
actions.fillEmptyFields(vjas)
return actions.getDelta(vjas)
})
- vehicleJourneys.push({
- journey_pattern: val.journey_pattern,
- published_journey_name: val.published_journey_name,
- objectid: val.objectid,
- short_id: val.short_id,
- footnotes: val.footnotes,
- time_tables: timeTables,
- vehicle_journey_at_stops: vjasWithDelta,
- deletable: false,
- selected: false,
- published_journey_name: val.published_journey_name || 'non renseigné',
- published_journey_identifier: val.published_journey_identifier || 'non renseigné',
- company: val.company || 'non renseigné',
- transport_mode: val.route.line.transport_mode || 'undefined',
- transport_submode: val.route.line.transport_submode || 'undefined'
- })
+
+ vehicleJourneys.push(
+ _.assign({}, val, {
+ time_tables: timeTables,
+ purchase_windows: purchaseWindows,
+ vehicle_journey_at_stops: vjasWithDelta,
+ deletable: false,
+ selected: false,
+ published_journey_name: val.published_journey_name || 'non renseigné',
+ published_journey_identifier: val.published_journey_identifier || 'non renseigné',
+ company: val.company || {name: 'non renseigné'},
+ transport_mode: val.route.line.transport_mode || 'undefined',
+ transport_submode: val.route.line.transport_submode || 'undefined'
+ })
+ )
}
window.currentItemsLength = vehicleJourneys.length
- dispatch(actions.receiveVehicleJourneys(vehicleJourneys))
- dispatch(actions.receiveTotalCount(json.total))
+ dispatch(actions.receiveVehicleJourneys(vehicleJourneys, returnJourneys))
+ if(!returnJourneys){
+ dispatch(actions.receiveTotalCount(json.total))
+ }
}
})
},
+
+ validate : (dispatch, vehicleJourneys, next) => {
+ dispatch(actions.didValidateVehicleJourneys(vehicleJourneys))
+ actions.submitVehicleJourneys(dispatch, vehicleJourneys, next)
+ return true
+ },
+
+ didValidateVehicleJourneys : (vehicleJourneys) => ({
+ type: 'DID_VALIDATE_VEHICLE_JOURNEYS',
+ vehicleJourneys
+ }),
+
submitVehicleJourneys : (dispatch, state, next) => {
dispatch(actions.fetchingApi())
let urlJSON = window.location.pathname + "_collection.json"
@@ -439,6 +495,20 @@ const actions = {
vjas.delta = delta
return vjas
},
+ adjustSchedule: (action, schedule) => {
+ // we enforce that the departure time remains after the arrival time
+ actions.getDelta(schedule)
+ if(schedule.delta < 0){
+ if(action.isDeparture){
+ schedule.arrival_time = schedule.departure_time
+ }
+ else{
+ schedule.departure_time = schedule.arrival_time
+ }
+ actions.getDelta(schedule)
+ }
+ return schedule
+ },
getShiftedSchedule: ({departure_time, arrival_time}, additional_time) => {
// We create dummy dates objects to manipulate time more easily
let departureDT = new Date (Date.UTC(2017, 2, 1, parseInt(departure_time.hour), parseInt(departure_time.minute)))
diff --git a/app/javascript/vehicle_journeys/components/App.js b/app/javascript/vehicle_journeys/components/App.js
index 8e5f7aa9d..5ac284438 100644
--- a/app/javascript/vehicle_journeys/components/App.js
+++ b/app/javascript/vehicle_journeys/components/App.js
@@ -22,6 +22,7 @@ export default function App() {
<Filters />
<VehicleJourneysList />
+ {window.returnRouteUrl && <VehicleJourneysList routeUrl={window.returnRouteUrl}/>}
<div className='row'>
<div className='col-lg-12 text-right'>
@@ -35,4 +36,4 @@ export default function App() {
<ConfirmModal />
</div>
)
-} \ No newline at end of file
+}
diff --git a/app/javascript/vehicle_journeys/components/ConfirmModal.js b/app/javascript/vehicle_journeys/components/ConfirmModal.js
index df3c96c48..75e8a3932 100644
--- a/app/javascript/vehicle_journeys/components/ConfirmModal.js
+++ b/app/javascript/vehicle_journeys/components/ConfirmModal.js
@@ -1,4 +1,5 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCancel, vehicleJourneys}) {
return (
@@ -6,7 +7,7 @@ export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCan
<div className='modal-dialog'>
<div className='modal-content'>
<div className='modal-body'>
- <p> Voulez-vous valider vos modifications avant de changer de page? </p>
+ <p> {I18n.t('vehicle_journeys.vehicle_journeys_matrix.modal_confirm')} </p>
</div>
<div className='modal-footer'>
<button
@@ -30,11 +31,11 @@ export default function ConfirmModal({dispatch, modal, onModalAccept, onModalCan
</div>
</div>
)
-}
+}
ConfirmModal.propTypes = {
vehicleJourneys: PropTypes.array.isRequired,
modal: PropTypes.object.isRequired,
onModalAccept: PropTypes.func.isRequired,
onModalCancel: PropTypes.func.isRequired
-} \ No newline at end of file
+}
diff --git a/app/javascript/vehicle_journeys/components/Filters.js b/app/javascript/vehicle_journeys/components/Filters.js
index db6707520..f8697c930 100644
--- a/app/javascript/vehicle_journeys/components/Filters.js
+++ b/app/javascript/vehicle_journeys/components/Filters.js
@@ -1,9 +1,11 @@
-import React, { PropTypes } from 'react'
+import React from 'react'
+import PropTypes from 'prop-types'
+
import MissionSelect2 from'./tools/select2s/MissionSelect2'
import VJSelect2 from'./tools/select2s/VJSelect2'
import TimetableSelect2 from'./tools/select2s/TimetableSelect2'
-export default function Filters({filters, pagination, onFilter, onResetFilters, onUpdateStartTimeFilter, onUpdateEndTimeFilter, onToggleWithoutSchedule, onToggleWithoutTimeTable, onSelect2Timetable, onSelect2JourneyPattern, onSelect2VehicleJourney}) {
+export default function Filters({filters, pagination, missions, onFilter, onResetFilters, onUpdateStartTimeFilter, onUpdateEndTimeFilter, onToggleWithoutSchedule, onToggleWithoutTimeTable, onSelect2Timetable, onSelect2JourneyPattern, onSelect2VehicleJourney}) {
return (
<div className='row'>
<div className='col-lg-12'>
@@ -24,6 +26,7 @@ export default function Filters({filters, pagination, onFilter, onResetFilters,
onSelect2JourneyPattern={onSelect2JourneyPattern}
filters={filters}
isFilter={true}
+ values={missions}
/>
</div>
@@ -33,6 +36,7 @@ export default function Filters({filters, pagination, onFilter, onResetFilters,
onSelect2Timetable={onSelect2Timetable}
hasRoute={true}
chunkURL={("/autocomplete_time_tables.json?route_id=" + String(window.route_id))}
+ searchKey={"comment_or_objectid_cont_any"}
filters={filters}
isFilter={true}
/>
@@ -42,10 +46,10 @@ export default function Filters({filters, pagination, onFilter, onResetFilters,
<div className='ffg-row'>
{/* Plage horaire */}
<div className='form-group togglable'>
- <label className='control-label'>Plage horaire au départ de la course</label>
+ <label className='control-label'>{I18n.t("vehicle_journeys.form.departure_range.label")}</label>
<div className='filter_menu'>
<div className='form-group time filter_menu-item'>
- <label className='control-label time'>Début</label>
+ <label className='control-label time'>{I18n.t("vehicle_journeys.form.departure_range.start")}</label>
<div className='form-inline'>
<div className='input-group time'>
<input
@@ -69,7 +73,7 @@ export default function Filters({filters, pagination, onFilter, onResetFilters,
</div>
</div>
<div className='form-group time filter_menu-item'>
- <label className='control-label time'>Fin</label>
+ <label className='control-label time'>{I18n.t("vehicle_journeys.form.departure_range.end")}</label>
<div className='form-inline'>
<div className='input-group time'>
<input
@@ -97,7 +101,7 @@ export default function Filters({filters, pagination, onFilter, onResetFilters,
{/* Switch avec/sans horaires */}
<div className='form-group has_switch'>
- <label className='control-label pull-left'>Afficher les courses sans horaires</label>
+ <label className='control-label pull-left'>{I18n.t("vehicle_journeys.form.show_journeys_without_schedule")}</label>
<div className='form-group pull-left' style={{padding: 0}}>
<div className='checkbox'>
<label>
@@ -106,8 +110,8 @@ export default function Filters({filters, pagination, onFilter, onResetFilters,
onChange={onToggleWithoutSchedule}
checked={filters.query.withoutSchedule}
></input>
- <span className='switch-label' data-checkedvalue='Non' data-uncheckedvalue='Oui'>
- {filters.query.withoutSchedule ? 'Oui' : 'Non'}
+ <span className='switch-label' data-checkedvalue={I18n.t("no")} data-uncheckedvalue={I18n.t("yes")}>
+ {filters.query.withoutSchedule ? I18n.t("yes") : I18n.t("no")}
</span>
</label>
</div>
@@ -118,7 +122,7 @@ export default function Filters({filters, pagination, onFilter, onResetFilters,
<div className="ffg-row">
{/* Switch avec/sans calendrier */}
<div className='form-group has_switch'>
- <label className='control-label pull-left'>Afficher les courses avec calendrier</label>
+ <label className='control-label pull-left'>{I18n.t("vehicle_journeys.form.show_journeys_with_calendar")}</label>
<div className='form-group pull-left' style={{padding: 0}}>
<div className='checkbox'>
<label>
@@ -127,8 +131,8 @@ export default function Filters({filters, pagination, onFilter, onResetFilters,
onChange={onToggleWithoutTimeTable}
checked={filters.query.withoutTimeTable}
></input>
- <span className='switch-label' data-checkedvalue='Non' data-uncheckedvalue='Oui'>
- {filters.query.withoutTimeTable ? 'Oui' : 'Non'}
+ <span className='switch-label' data-checkedvalue={I18n.t("no")} data-uncheckedvalue={I18n.t("yes")}>
+ {filters.query.withoutTimeTable ? I18n.t("yes") : I18n.t("no")}
</span>
</label>
</div>
@@ -165,4 +169,4 @@ Filters.propTypes = {
onSelect2Timetable: PropTypes.func.isRequired,
onSelect2JourneyPattern: PropTypes.func.isRequired,
onSelect2VehicleJourney: PropTypes.func.isRequired
-} \ No newline at end of file
+}
diff --git a/app/javascript/vehicle_journeys/components/Navigate.js b/app/javascript/vehicle_journeys/components/Navigate.js
index 7493b705b..24843babc 100644
--- a/app/javascript/vehicle_journeys/components/Navigate.js
+++ b/app/javascript/vehicle_journeys/components/Navigate.js
@@ -1,4 +1,5 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import actions from'../actions'
export default function Navigate({ dispatch, vehicleJourneys, pagination, status, filters}) {
@@ -16,8 +17,7 @@ export default function Navigate({ dispatch, vehicleJourneys, pagination, status
if(status.fetchSuccess == true) {
return (
<div className="pagination">
- Liste des horaires {minVJ} à {maxVJ} sur {pagination.totalCount}
-
+ {I18n.t("vehicle_journeys.vehicle_journeys_matrix.pagination", {minVJ, maxVJ, total:pagination.totalCount})}
<form className='page_links' onSubmit={e => {e.preventDefault()}}>
<button
onClick={e => {
@@ -52,4 +52,4 @@ Navigate.propTypes = {
status: PropTypes.object.isRequired,
pagination: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired
-} \ No newline at end of file
+}
diff --git a/app/javascript/vehicle_journeys/components/SaveVehicleJourneys.js b/app/javascript/vehicle_journeys/components/SaveVehicleJourneys.js
index e8c27f92e..fb921df9c 100644
--- a/app/javascript/vehicle_journeys/components/SaveVehicleJourneys.js
+++ b/app/javascript/vehicle_journeys/components/SaveVehicleJourneys.js
@@ -1,34 +1,19 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import SaveButton from '../../helpers/save_button'
import actions from '../actions'
-export default class SaveVehicleJourneys extends Component{
- constructor(props){
- super(props)
+export default class SaveVehicleJourneys extends SaveButton{
+ hasPolicy(){
+ return this.props.filters.policy['vehicle_journeys.update'] == true
}
- render() {
- if (this.props.filters.policy['vehicle_journeys.update'] == false) {
- return false
- }else{
- return (
- <div className='row mt-md'>
- <div className='col-lg-12 text-right'>
- <form className='vehicle_journeys formSubmitr ml-xs' onSubmit={e => {e.preventDefault()}}>
- <button
- className='btn btn-default'
- type='button'
- onClick={e => {
- e.preventDefault()
- this.props.editMode ? this.props.onSubmitVehicleJourneys(this.props.dispatch, this.props.vehicleJourneys) : this.props.onEnterEditMode()
- }}
- >
- {this.props.editMode ? "Valider" : "Editer"}
- </button>
- </form>
- </div>
- </div>
- )
- }
+ formClassName(){
+ return 'vehicle_journeys'
+ }
+
+ submitForm(){
+ this.props.validate(this.props.vehicleJourneys, this.props.dispatch)
}
}
@@ -38,5 +23,6 @@ SaveVehicleJourneys.propTypes = {
status: PropTypes.object.isRequired,
filters: PropTypes.object.isRequired,
onEnterEditMode: PropTypes.func.isRequired,
+ onExitEditMode: PropTypes.func.isRequired,
onSubmitVehicleJourneys: PropTypes.func.isRequired
-} \ No newline at end of file
+}
diff --git a/app/javascript/vehicle_journeys/components/ToggleArrivals.js b/app/javascript/vehicle_journeys/components/ToggleArrivals.js
index e26ceec3a..9a2b0097f 100644
--- a/app/javascript/vehicle_journeys/components/ToggleArrivals.js
+++ b/app/javascript/vehicle_journeys/components/ToggleArrivals.js
@@ -1,9 +1,11 @@
-import React, { PropTypes } from 'react'
+import React from 'react'
+import PropTypes from 'prop-types'
+
export default function ToggleArrivals({filters, onToggleArrivals}) {
return (
<div className='has_switch form-group inline'>
- <label htmlFor='toggleArrivals' className='control-label'>Afficher et éditer les horaires d'arrivée</label>
+ <label htmlFor='toggleArrivals' className='control-label'>{I18n.t('vehicle_journeys.form.show_arrival_time')}</label>
<div className='form-group'>
<div className='checkbox'>
<label>
@@ -24,4 +26,4 @@ export default function ToggleArrivals({filters, onToggleArrivals}) {
ToggleArrivals.propTypes = {
filters : PropTypes.object.isRequired,
onToggleArrivals: PropTypes.func.isRequired
-} \ No newline at end of file
+}
diff --git a/app/javascript/vehicle_journeys/components/Tools.js b/app/javascript/vehicle_journeys/components/Tools.js
index 7621dfc10..22ea44283 100644
--- a/app/javascript/vehicle_journeys/components/Tools.js
+++ b/app/javascript/vehicle_journeys/components/Tools.js
@@ -1,4 +1,5 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import actions from '../actions'
import AddVehicleJourney from '../containers/tools/AddVehicleJourney'
import DeleteVehicleJourneys from '../containers/tools/DeleteVehicleJourneys'
@@ -7,6 +8,7 @@ import DuplicateVehicleJourney from '../containers/tools/DuplicateVehicleJourney
import EditVehicleJourney from '../containers/tools/EditVehicleJourney'
import NotesEditVehicleJourney from '../containers/tools/NotesEditVehicleJourney'
import TimetablesEditVehicleJourney from '../containers/tools/TimetablesEditVehicleJourney'
+import PurchaseWindowsEditVehicleJourney from '../containers/tools/PurchaseWindowsEditVehicleJourney'
export default class Tools extends Component {
@@ -17,7 +19,12 @@ export default class Tools extends Component {
hasPolicy(key) {
// Check if the user has the policy to disable or not the action
- return this.props.filters.policy[`vehicle_journeys.${key}`]
+ return this.props.filters.policy[`vehicle_journeys.${key}`]
+ }
+
+ hasFeature(key) {
+ // Check if the organisation has the given feature
+ return this.props.filters.features[key]
}
render() {
@@ -25,17 +32,20 @@ export default class Tools extends Component {
return (
<div className='select_toolbox'>
<ul>
- <AddVehicleJourney disabled={this.hasPolicy("create") && !editMode} />
- <DuplicateVehicleJourney disabled={this.hasPolicy("create") && this.hasPolicy("update") && !editMode}/>
- <ShiftVehicleJourney disabled={this.hasPolicy("update") && !editMode}/>
+ <AddVehicleJourney disabled={!this.hasPolicy("create") || !editMode} />
+ <DuplicateVehicleJourney disabled={!this.hasPolicy("create") || !this.hasPolicy("update") || !editMode}/>
+ <ShiftVehicleJourney disabled={!this.hasPolicy("update") || !editMode}/>
<EditVehicleJourney disabled={!this.hasPolicy("update")}/>
<TimetablesEditVehicleJourney disabled={!this.hasPolicy("update")}/>
+ { this.hasFeature('purchase_windows') &&
+ <PurchaseWindowsEditVehicleJourney disabled={!this.hasPolicy("update")}/>
+ }
<NotesEditVehicleJourney disabled={!this.hasPolicy("update")}/>
- <DeleteVehicleJourneys disabled={this.hasPolicy("destroy") && !editMode}/>
+ <DeleteVehicleJourneys disabled={!this.hasPolicy("destroy") || !editMode}/>
</ul>
- <span className='info-msg'>{actions.getSelected(vehicleJourneys).length} course(s) sélectionnée(s)</span>
- <button className='btn btn-xs btn-link pull-right' onClick={onCancelSelection}>Annuler la sélection</button>
+ <span className='info-msg'>{I18n.t('vehicle_journeys.vehicle_journeys_matrix.selected_journeys', {count: actions.getSelected(vehicleJourneys).length})}</span>
+ <button className='btn btn-xs btn-link pull-right' onClick={onCancelSelection}>{I18n.t('vehicle_journeys.vehicle_journeys_matrix.cancel_selection')}</button>
</div>
)
}
@@ -45,4 +55,4 @@ Tools.propTypes = {
vehicleJourneys : PropTypes.array.isRequired,
onCancelSelection: PropTypes.func.isRequired,
filters: PropTypes.object.isRequired
-} \ No newline at end of file
+}
diff --git a/app/javascript/vehicle_journeys/components/VehicleJourney.js b/app/javascript/vehicle_journeys/components/VehicleJourney.js
index 929cbc5c4..4a9432231 100644
--- a/app/javascript/vehicle_journeys/components/VehicleJourney.js
+++ b/app/javascript/vehicle_journeys/components/VehicleJourney.js
@@ -1,5 +1,8 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import actions from '../actions'
+import EditVehicleJourney from '../containers/tools/EditVehicleJourney'
+import VehicleJourneyInfoButton from '../containers/tools/VehicleJourneyInfoButton'
export default class VehicleJourney extends Component {
constructor(props) {
@@ -8,13 +11,11 @@ export default class VehicleJourney extends Component {
}
cityNameChecker(sp) {
- let bool = false
- if(sp.stop_area_cityname != this.previousCity){
- bool = true
- this.previousCity = sp.stop_area_cityname
- }
+ return this.props.vehicleJourneys.showHeader(sp.stop_point_objectid)
+ }
- return bool
+ hasFeature(key) {
+ return this.props.filters.features[key]
}
timeTableURL(tt) {
@@ -22,7 +23,16 @@ export default class VehicleJourney extends Component {
let ttURL = refURL + '/time_tables/' + tt.id
return (
- <a href={ttURL} title='Voir le calendrier'><span className='fa fa-calendar' style={{color: (tt.color ? tt.color : '')}}></span></a>
+ <a href={ttURL} title={I18n.t('vehicle_journeys.vehicle_journeys_matrix.show_timetable')}><span className='fa fa-calendar' style={{ color: (tt.color ? tt.color : '#4B4B4B')}}></span></a>
+ )
+ }
+
+ purchaseWindowURL(tt) {
+ let refURL = window.location.pathname.split('/', 3).join('/')
+ let ttURL = refURL + '/purchase_windows/' + tt.id
+
+ return (
+ <a href={ttURL} title={I18n.t('vehicle_journeys.vehicle_journeys_matrix.show_purchase_window')}><span className='fa fa-calendar' style={{color: (tt.color ? tt.color : '')}}></span></a>
)
}
@@ -44,20 +54,35 @@ export default class VehicleJourney extends Component {
render() {
this.previousCity = undefined
- let {time_tables} = this.props.value
+ let {time_tables, purchase_windows} = this.props.value
return (
<div className={'t2e-item' + (this.props.value.deletable ? ' disabled' : '') + (this.props.value.errors ? ' has-error': '')}>
- <div className='th'>
+ <div
+ className='th'
+ onClick={(e) =>
+ !this.props.disabled && ($(e.target).parents("a").length == 0) && this.props.onSelectVehicleJourney(this.props.index)
+ }
+ >
<div className='strong mb-xs'>{this.props.value.short_id || '-'}</div>
+ <div>{this.props.value.published_journey_name && this.props.value.published_journey_name != I18n.t('undefined') ? this.props.value.published_journey_name : '-'}</div>
<div>{this.props.value.journey_pattern.short_id || '-'}</div>
+ <div>{this.props.value.company ? this.props.value.company.name : '-'}</div>
<div>
{time_tables.slice(0,3).map((tt, i)=>
<span key={i} className='vj_tt'>{this.timeTableURL(tt)}</span>
)}
{time_tables.length > 3 && <span className='vj_tt'> + {time_tables.length - 3}</span>}
</div>
- <div className={(this.props.value.deletable ? 'disabled ' : '') + 'checkbox'}>
+ { this.hasFeature('purchase_windows') &&
+ <div>
+ {purchase_windows.slice(0,3).map((tt, i)=>
+ <span key={i} className='vj_tt'>{this.purchaseWindowURL(tt)}</span>
+ )}
+ {purchase_windows.length > 3 && <span className='vj_tt'> + {purchase_windows.length - 3}</span>}
+ </div>
+ }
+ {!this.props.disabled && <div className={(this.props.value.deletable ? 'disabled ' : '') + 'checkbox'}>
<input
id={this.props.index}
name={this.props.index}
@@ -68,20 +93,21 @@ export default class VehicleJourney extends Component {
checked={this.props.value.selected}
></input>
<label htmlFor={this.props.index}></label>
- </div>
+ </div>}
+ {this.props.disabled && <VehicleJourneyInfoButton vehicleJourney={this.props.value} />}
</div>
{this.props.value.vehicle_journey_at_stops.map((vj, i) =>
<div key={i} className='td text-center'>
<div className={'cellwrap' + (this.cityNameChecker(vj) ? ' headlined' : '')}>
{this.props.filters.toggleArrivals &&
- <div data-headline='Arrivée à'>
+ <div data-headline={I18n.t("vehicle_journeys.form.arrival_at")}>
<span className={((this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false) ? 'disabled ' : '') + 'input-group time'}>
<input
type='number'
min='00'
max='23'
className='form-control'
- disabled={this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false}
+ disabled={!this.props.editMode || this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false}
readOnly={!this.props.editMode && !vj.dummy}
onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', false, false)}}
value={vj.arrival_time['hour']}
@@ -92,7 +118,7 @@ export default class VehicleJourney extends Component {
min='00'
max='59'
className='form-control'
- disabled={this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false}
+ disabled={!this.props.editMode || this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false}
readOnly={!this.props.editMode && !vj.dummy}
onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'minute', false, false)}}
value={vj.arrival_time['minute']}
@@ -105,14 +131,14 @@ export default class VehicleJourney extends Component {
<span className='sb sb-chrono sb-lg text-warning' data-textinside={vj.delta}></span>
}
</div>
- <div data-headline='Départ à'>
+ <div data-headline={I18n.t("vehicle_journeys.form.departure_at")}>
<span className={((this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false) ? 'disabled ' : '') + 'input-group time'}>
<input
type='number'
min='00'
max='23'
className='form-control'
- disabled={this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false}
+ disabled={!this.props.editMode || this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false}
readOnly={!this.props.editMode && !vj.dummy}
onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', true, this.props.filters.toggleArrivals)}}
value={vj.departure_time['hour']}
@@ -123,13 +149,16 @@ export default class VehicleJourney extends Component {
min='00'
max='59'
className='form-control'
- disabled={this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false}
+ disabled={!this.props.editMode || this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false}
readOnly={!this.props.editMode && !vj.dummy}
onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, "minute", true, this.props.filters.toggleArrivals)}}
value={vj.departure_time['minute']}
/>
</span>
</div>
+ {vj.errors && <div className="errors">
+ {vj.errors}
+ </div>}
</div>
</div>
)}
@@ -143,5 +172,6 @@ VehicleJourney.propTypes = {
filters: PropTypes.object.isRequired,
index: PropTypes.number.isRequired,
onUpdateTime: PropTypes.func.isRequired,
- onSelectVehicleJourney: PropTypes.func.isRequired
-} \ No newline at end of file
+ onSelectVehicleJourney: PropTypes.func.isRequired,
+ vehicleJourneys: PropTypes.object.isRequired,
+}
diff --git a/app/javascript/vehicle_journeys/components/VehicleJourneys.js b/app/javascript/vehicle_journeys/components/VehicleJourneys.js
index 6bce9766b..01e07ee0c 100644
--- a/app/javascript/vehicle_journeys/components/VehicleJourneys.js
+++ b/app/javascript/vehicle_journeys/components/VehicleJourneys.js
@@ -1,15 +1,51 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import _ from 'lodash'
import VehicleJourney from './VehicleJourney'
-
+import StopAreaHeaderManager from '../../helpers/stop_area_header_manager'
export default class VehicleJourneys extends Component {
constructor(props){
super(props)
- this.previousCity = undefined
+ this.headerManager = new StopAreaHeaderManager(
+ _.map(this.stopPoints(), (sp)=>{return sp.object_id}),
+ this.stopPoints(),
+ this.props.filters.features
+ )
+ }
+
+ isReturn() {
+ return this.props.routeUrl != undefined
+ }
+
+ vehicleJourneysList() {
+ if(this.isReturn()){
+ return this.props.returnVehicleJourneys
+ }
+ else{
+ return this.props.vehicleJourneys
+ }
+ }
+
+ stopPoints() {
+ if(this.isReturn()){
+ return this.props.returnStopPointsList
+ }
+ else{
+ return this.props.stopPointsList
+ }
}
+
componentDidMount() {
- this.props.onLoadFirstPage(this.props.filters)
+ this.props.onLoadFirstPage(this.props.filters, this.props.routeUrl)
+ }
+
+ hasFeature(key) {
+ return this.props.filters.features[key]
+ }
+
+ showHeader(object_id) {
+ return this.headerManager.showHeader(object_id)
}
componentDidUpdate(prevProps, prevState) {
@@ -55,25 +91,8 @@ export default class VehicleJourneys extends Component {
}
}
- cityNameChecker(sp) {
- let bool = false
- if(sp.city_name != this.previousCity){
- bool = true
- this.previousCity = sp.city_name
- }
- return (
- <div
- className={(bool) ? 'headlined' : ''}
- data-headline={(bool) ? sp.city_name : ''}
- title={sp.city_name + ' (' + sp.zip_code +')'}
- >
- <span><span>{sp.name}</span></span>
- </div>
- )
- }
-
render() {
- this.previousCity = undefined
+ this.previousBreakpoint = undefined
if(this.props.status.isFetching == true) {
return (
@@ -87,15 +106,15 @@ export default class VehicleJourneys extends Component {
<div className='col-lg-12'>
{(this.props.status.fetchSuccess == false) && (
<div className='alert alert-danger mt-sm'>
- <strong>Erreur : </strong>
- la récupération des missions a rencontré un problème. Rechargez la page pour tenter de corriger le problème.
+ <strong>{I18n.tc("error")}</strong>
+ {I18n.t("vehicle_journeys.vehicle_journeys_matrix.fetching_error")}
</div>
)}
- { _.some(this.props.vehicleJourneys, 'errors') && (
+ { this.vehicleJourneysList().errors && this.vehicleJourneysList().errors.length && _.some(this.vehicleJourneysList(), 'errors') && (
<div className="alert alert-danger mt-sm">
- <strong>Erreur : </strong>
- {this.props.vehicleJourneys.map((vj, index) =>
+ <strong>{I18n.tc("error")}</strong>
+ {this.vehicleJourneysList().map((vj, index) =>
vj.errors && vj.errors.map((err, i) => {
return (
<ul key={i}>
@@ -107,17 +126,20 @@ export default class VehicleJourneys extends Component {
</div>
)}
- <div className={'table table-2entries mt-sm mb-sm' + ((this.props.vehicleJourneys.length > 0) ? '' : ' no_result')}>
+ <div className={'table table-2entries mt-sm mb-sm' + ((this.vehicleJourneysList().length > 0) ? '' : ' no_result')}>
<div className='t2e-head w20'>
<div className='th'>
- <div className='strong mb-xs'>ID course</div>
- <div>ID mission</div>
- <div>Calendriers</div>
+ <div className='strong mb-xs'>{I18n.attribute_name("vehicle_journey", "id")}</div>
+ <div>{I18n.attribute_name("vehicle_journey", "name")}</div>
+ <div>{I18n.attribute_name("vehicle_journey", "journey_pattern_id")}</div>
+ <div>{I18n.model_name("company")}</div>
+ <div>{I18n.model_name("time_table", "plural": true)}</div>
+ { this.hasFeature('purchase_windows') && <div>{I18n.model_name("purchase_window", "plural": true)}</div> }
</div>
- {this.props.stopPointsList.map((sp, i) =>{
+ {this.stopPoints().map((sp, i) =>{
return (
<div key={i} className='td'>
- {this.cityNameChecker(sp)}
+ {this.headerManager.stopPointHeader(sp.object_id)}
</div>
)
})}
@@ -125,15 +147,18 @@ export default class VehicleJourneys extends Component {
<div className='t2e-item-list w80'>
<div>
- {this.props.vehicleJourneys.map((vj, index) =>
+ {this.vehicleJourneysList().map((vj, index) =>
<VehicleJourney
value={vj}
key={index}
index={index}
- editMode={this.props.editMode}
+ editMode={this.isReturn() ? false : this.props.editMode}
filters={this.props.filters}
+ features={this.props.features}
onUpdateTime={this.props.onUpdateTime}
onSelectVehicleJourney={this.props.onSelectVehicleJourney}
+ vehicleJourneys={this}
+ disabled={this.isReturn()}
/>
)}
</div>
@@ -153,4 +178,4 @@ VehicleJourneys.propTypes = {
onLoadFirstPage: PropTypes.func.isRequired,
onUpdateTime: PropTypes.func.isRequired,
onSelectVehicleJourney: PropTypes.func.isRequired
-} \ No newline at end of file
+}
diff --git a/app/javascript/vehicle_journeys/components/tools/CreateModal.js b/app/javascript/vehicle_journeys/components/tools/CreateModal.js
index 33873219c..8536f66e6 100644
--- a/app/javascript/vehicle_journeys/components/tools/CreateModal.js
+++ b/app/javascript/vehicle_journeys/components/tools/CreateModal.js
@@ -1,16 +1,19 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import actions from '../../actions'
import MissionSelect2 from './select2s/MissionSelect2'
import CompanySelect2 from './select2s/CompanySelect2'
+import CustomFieldsInputs from './CustomFieldsInputs'
export default class CreateModal extends Component {
constructor(props) {
super(props)
+ this.custom_fields = _.assign({}, this.props.custom_fields)
}
handleSubmit() {
if (actions.validateFields(...this.refs, $('.vjCreateSelectJP')[0]) && this.props.modal.modalProps.selectedJPModal) {
- this.props.onAddVehicleJourney(this.refs, this.props.modal.modalProps.selectedJPModal, this.props.stopPointsList, this.props.modal.modalProps.selectedCompany)
+ this.props.onAddVehicleJourney(_.assign({}, this.refs, {custom_fields: this.custom_fields}), this.props.modal.modalProps.selectedJPModal, this.props.stopPointsList, this.props.modal.modalProps.vehicleJourney && this.props.modal.modalProps.vehicleJourney.company)
this.props.onModalClose()
$('#NewVehicleJourneyModal').modal('hide')
}
@@ -61,8 +64,9 @@ export default class CreateModal extends Component {
<div className='form-group'>
<label className='control-label'>Nom du transporteur</label>
<CompanySelect2
- company = {undefined}
+ company = {this.props.modal.modalProps.vehicleJourney && this.props.modal.modalProps.vehicleJourney.company || undefined}
onSelect2Company = {(e) => this.props.onSelect2Company(e)}
+ onUnselect2Company = {() => this.props.onUnselect2Company()}
/>
</div>
</div>
@@ -72,6 +76,7 @@ export default class CreateModal extends Component {
<MissionSelect2
selection={this.props.modal.modalProps}
onSelect2JourneyPattern={this.props.onSelect2JourneyPattern}
+ values={this.props.missions}
isFilter={false}
/>
</div>
@@ -87,6 +92,36 @@ export default class CreateModal extends Component {
/>
</div>
</div>
+ <CustomFieldsInputs
+ values={this.props.custom_fields}
+ onUpdate={(code, value) => this.custom_fields[code]["value"] = value}
+ disabled={false}
+ />
+ { this.props.modal.modalProps.selectedJPModal && this.props.modal.modalProps.selectedJPModal.full_schedule && <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>
+ <div className='form-group'>
+ <label className='control-label'>Heure de départ</label>
+ <div className='input-group time'>
+ <input
+ type='number'
+ min='00'
+ max='23'
+ ref='start_time.hour'
+ className='form-control'
+ onKeyDown={(e) => actions.resetValidation(e.currentTarget)}
+ />
+ <input
+ type='number'
+ min='00'
+ max='59'
+ ref='start_time.minute'
+ className='form-control'
+ onKeyDown={(e) => actions.resetValidation(e.currentTarget)}
+ />
+ </div>
+ </div>
+ </div>
+ }
+
</div>
</div>
<div className='modal-footer'>
@@ -129,5 +164,6 @@ CreateModal.propTypes = {
onModalClose: PropTypes.func.isRequired,
onAddVehicleJourney: PropTypes.func.isRequired,
onSelect2JourneyPattern: PropTypes.func.isRequired,
- disabled: PropTypes.bool.isRequired
-} \ No newline at end of file
+ disabled: PropTypes.bool.isRequired,
+ missions: PropTypes.array.isRequired
+}
diff --git a/app/javascript/vehicle_journeys/components/tools/CustomFieldsInputs.js b/app/javascript/vehicle_journeys/components/tools/CustomFieldsInputs.js
new file mode 100644
index 000000000..90d72a801
--- /dev/null
+++ b/app/javascript/vehicle_journeys/components/tools/CustomFieldsInputs.js
@@ -0,0 +1,50 @@
+import _ from 'lodash'
+import Select2 from 'react-select2-wrapper'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+
+export default class CustomFieldsInputs extends Component {
+ constructor(props) {
+ super(props)
+ }
+
+ listInput(cf){
+ return(
+ <Select2
+ data={_.map(cf.options.list_values, (v, k) => {
+ return {id: k, text: (v.length > 0 ? v : '\u00A0')}
+ })}
+ ref={'custom_fields.' + cf.code}
+ className='form-control'
+ defaultValue={cf.value}
+ disabled={this.props.disabled}
+ options={{
+ theme: 'bootstrap',
+ width: '100%'
+ }}
+ onSelect={(e) => this.props.onUpdate(cf.code, e.params.data.id) }
+ />
+ )
+ }
+
+ render() {
+ return (
+ <div>
+ {_.map(this.props.values, (cf, code) =>
+ <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12' key={code}>
+ <div className='form-group'>
+ <label className='control-label'>{cf.name}</label>
+ {this[cf.field_type + "Input"](cf)}
+ </div>
+ </div>
+ )}
+ </div>
+ )
+ }
+}
+
+CustomFieldsInputs.propTypes = {
+ onUpdate: PropTypes.func.isRequired,
+ values: PropTypes.object.isRequired,
+ disabled: PropTypes.bool.isRequired
+}
diff --git a/app/javascript/vehicle_journeys/components/tools/DeleteVehicleJourneys.js b/app/javascript/vehicle_journeys/components/tools/DeleteVehicleJourneys.js
index fc13ae964..4815003d3 100644
--- a/app/javascript/vehicle_journeys/components/tools/DeleteVehicleJourneys.js
+++ b/app/javascript/vehicle_journeys/components/tools/DeleteVehicleJourneys.js
@@ -1,4 +1,6 @@
-import React, { PropTypes } from 'react'
+import React from 'react'
+import PropTypes from 'prop-types'
+
import actions from '../../actions'
export default function DeleteVehicleJourneys({onDeleteVehicleJourneys, vehicleJourneys, disabled}) {
diff --git a/app/javascript/vehicle_journeys/components/tools/DuplicateVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/DuplicateVehicleJourney.js
index 8083defb9..102a87d85 100644
--- a/app/javascript/vehicle_journeys/components/tools/DuplicateVehicleJourney.js
+++ b/app/javascript/vehicle_journeys/components/tools/DuplicateVehicleJourney.js
@@ -1,4 +1,5 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import actions from '../../actions'
import _ from 'lodash'
diff --git a/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js
index f8d6add03..d3c01f154 100644
--- a/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js
+++ b/app/javascript/vehicle_journeys/components/tools/EditVehicleJourney.js
@@ -1,33 +1,47 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import actions from '../../actions'
import CompanySelect2 from './select2s/CompanySelect2'
+import CustomFieldsInputs from './CustomFieldsInputs'
export default class EditVehicleJourney extends Component {
constructor(props) {
super(props)
+ this.updateValue = this.updateValue.bind(this)
}
handleSubmit() {
if(actions.validateFields(this.refs) == true) {
- var company;
+ var company = undefined
if(this.props.modal.modalProps.selectedCompany) {
company = this.props.modal.modalProps.selectedCompany
- } else if (typeof this.props.modal.modalProps.vehicleJourney.company === Object) {
+ } else if (typeof this.props.modal.modalProps.vehicleJourney.company === "object") {
company = this.props.modal.modalProps.vehicleJourney.company
- } else {
- company = undefined
}
- this.props.onEditVehicleJourney(this.refs, company)
+ this.props.onEditVehicleJourney(_.assign({}, this.refs, {custom_fields: this.custom_fields}), company)
this.props.onModalClose()
$('#EditVehicleJourneyModal').modal('hide')
}
}
+ updateValue(attribute, e) {
+ this.props.modal.modalProps.vehicleJourney[attribute] = e.target.value
+ actions.resetValidation(e.currentTarget)
+ this.forceUpdate()
+ }
+
+ editMode() {
+ return !this.props.modal.modalProps.info && this.props.editMode
+ }
+
render() {
if(this.props.status.isFetching == true) {
return false
}
if(this.props.status.fetchSuccess == true) {
+ if(this.props.modal.modalProps.vehicleJourney){
+ this.custom_fields = _.assign({}, this.props.modal.modalProps.vehicleJourney.custom_fields)
+ }
return (
<li className='st_action'>
<button
@@ -45,60 +59,60 @@ export default class EditVehicleJourney extends Component {
<div className='modal-dialog'>
<div className='modal-content'>
<div className='modal-header'>
- <h4 className='modal-title'>Informations</h4>
+ <h4 className='modal-title'>{I18n.t('vehicle_journeys.form.infos')}</h4>
<span type="button" className="close modal-close" data-dismiss="modal">&times;</span>
</div>
{(this.props.modal.type == 'edit') && (
<form>
<div className='modal-body'>
- <div className='row'>
- <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>
- <div className='form-group'>
- <label className='control-label'>Nom de la course</label>
- <input
- type='text'
- ref='published_journey_name'
- className='form-control'
- disabled={!this.props.editMode}
- defaultValue={this.props.modal.modalProps.vehicleJourney.published_journey_name}
- onKeyDown={(e) => actions.resetValidation(e.currentTarget)}
- />
+ <div className='row'>
+ <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>
+ <div className='form-group'>
+ <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'journey_name')}</label>
+ <input
+ type='text'
+ ref='published_journey_name'
+ className='form-control'
+ disabled={!this.editMode()}
+ value={this.props.modal.modalProps.vehicleJourney.published_journey_name}
+ onChange={(e) => this.updateValue('published_journey_name', e)}
+ />
+ </div>
</div>
- </div>
- <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>
- <div className='form-group'>
- <label className='control-label'>Mission</label>
- <input
- type='text'
- className='form-control'
- value={this.props.modal.modalProps.vehicleJourney.journey_pattern.short_id + ' - ' + (this.props.modal.modalProps.vehicleJourney.journey_pattern.name)}
- disabled={true}
- />
+ <div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>
+ <div className='form-group'>
+ <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'journey_pattern')}</label>
+ <input
+ type='text'
+ className='form-control'
+ value={this.props.modal.modalProps.vehicleJourney.journey_pattern.short_id + ' - ' + (this.props.modal.modalProps.vehicleJourney.journey_pattern.name)}
+ disabled={true}
+ />
+ </div>
</div>
</div>
- </div>
<div className='row'>
<div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>
<div className='form-group'>
- <label className='control-label'>Numéro de train</label>
+ <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'company')}</label>
<input
type='text'
ref='published_journey_identifier'
className='form-control'
- disabled={!this.props.editMode}
- defaultValue={this.props.modal.modalProps.vehicleJourney.published_journey_identifier}
- onKeyDown={(e) => actions.resetValidation(e.currentTarget)}
+ disabled={!this.editMode()}
+ value={this.props.modal.modalProps.vehicleJourney.published_journey_identifier}
+ onChange={(e) => this.updateValue('published_journey_identifier', e)}
/>
</div>
</div>
<div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>
<div className='form-group'>
- <label className='control-label'>Transporteur</label>
+ <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'company')}</label>
<CompanySelect2
editModal={this.props.modal.type == "edit"}
- editMode={this.props.editMode}
+ editMode={this.editMode()}
company = {this.props.modal.modalProps.vehicleJourney.company}
onSelect2Company = {(e) => this.props.onSelect2Company(e)}
onUnselect2Company = {() => this.props.onUnselect2Company()}
@@ -110,30 +124,48 @@ export default class EditVehicleJourney extends Component {
<div className='row'>
<div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>
<div className='form-group'>
- <label className='control-label'>Mode de transport</label>
+ <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'transport_mode')}</label>
<input
type='text'
className='form-control'
- value={window.I18n.fr.enumerize.transport_mode[this.props.modal.modalProps.vehicleJourney.transport_mode]}
+ value={I18n.enumerize('transport_mode', this.props.modal.modalProps.vehicleJourney.transport_mode)}
disabled={true}
/>
</div>
</div>
<div className='col-lg-6 col-md-6 col-sm-6 col-xs-12'>
<div className='form-group'>
- <label className='control-label'>Sous mode de transport</label>
+ <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'transport_submode')}</label>
<input
type='text'
className='form-control'
- value={window.I18n.fr.enumerize.transport_submode[this.props.modal.modalProps.vehicleJourney.transport_submode]}
+ value={I18n.enumerize('transport_submode', this.props.modal.modalProps.vehicleJourney.transport_submode)}
disabled={true}
/>
</div>
</div>
</div>
+ <div className='form-group'>
+ <label className='control-label'>{I18n.attribute_name('vehicle_journey', 'checksum')}</label>
+ <input
+ type='text'
+ ref='checksum'
+ className='form-control'
+ disabled='disabled'
+ defaultValue={this.props.modal.modalProps.vehicleJourney.checksum}
+ />
+ </div>
+ <div className='row'>
+ <CustomFieldsInputs
+ values={this.props.modal.modalProps.vehicleJourney.custom_fields}
+ onUpdate={(code, value) => this.custom_fields[code]["value"] = value}
+ disabled={!this.editMode()}
+ />
+ </div>
</div>
+
{
- this.props.editMode &&
+ this.editMode() &&
<div className='modal-footer'>
<button
className='btn btn-link'
@@ -151,7 +183,7 @@ export default class EditVehicleJourney extends Component {
Valider
</button>
</div>
- }
+ }
</form>
)}
@@ -171,4 +203,4 @@ EditVehicleJourney.propTypes = {
onOpenEditModal: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired,
disabled: PropTypes.bool.isRequired
-} \ No newline at end of file
+}
diff --git a/app/javascript/vehicle_journeys/components/tools/NotesEditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/NotesEditVehicleJourney.js
index de97bc403..880542216 100644
--- a/app/javascript/vehicle_journeys/components/tools/NotesEditVehicleJourney.js
+++ b/app/javascript/vehicle_journeys/components/tools/NotesEditVehicleJourney.js
@@ -1,4 +1,5 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import actions from '../../actions'
import _ from 'lodash'
diff --git a/app/javascript/vehicle_journeys/components/tools/PurchaseWindowsEditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/PurchaseWindowsEditVehicleJourney.js
new file mode 100644
index 000000000..ce9a4cde9
--- /dev/null
+++ b/app/javascript/vehicle_journeys/components/tools/PurchaseWindowsEditVehicleJourney.js
@@ -0,0 +1,153 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import actions from '../../actions'
+import TimetableSelect2 from './select2s/TimetableSelect2'
+
+export default class PurchaseWindowsEditVehicleJourney extends Component {
+ constructor(props) {
+ super(props)
+ this.handleSubmit = this.handleSubmit.bind(this)
+ this.purchaseWindowURL = this.purchaseWindowURL.bind(this)
+ }
+
+ handleSubmit() {
+ this.props.onShoppingWindowsEditVehicleJourney(this.props.modal.modalProps.vehicleJourneys, this.props.modal.modalProps.purchase_windows)
+ this.props.onModalClose()
+ $('#PurchaseWindowsEditVehicleJourneyModal').modal('hide')
+ }
+
+ purchaseWindowURL(tt) {
+ let refURL = window.location.pathname.split('/', 3).join('/')
+ return refURL + '/purchase_windows/' + tt.id
+ }
+
+ render() {
+ if(this.props.status.isFetching == true) {
+ return false
+ }
+ if(this.props.status.fetchSuccess == true) {
+ return (
+ <li className='st_action'>
+ <button
+ type='button'
+ disabled={(actions.getSelected(this.props.vehicleJourneys).length < 1 || this.props.disabled)}
+ data-toggle='modal'
+ data-target='#PurchaseWindowsEditVehicleJourneyModal'
+ onClick={() => this.props.onOpenCalendarsEditModal(actions.getSelected(this.props.vehicleJourneys))}
+ title='Calendriers commerciaux'
+ >
+ <span className='sb sb-purchase_window sb-strong'></span>
+ </button>
+
+ <div className={ 'modal fade ' + ((this.props.modal.type == 'duplicate') ? 'in' : '') } id='PurchaseWindowsEditVehicleJourneyModal'>
+ <div className='modal-container'>
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <h4 className='modal-title'>Calendriers commerciaux associés</h4>
+ <span type="button" className="close modal-close" data-dismiss="modal">&times;</span>
+ </div>
+
+ {(this.props.modal.type == 'purchase_windows_edit') && (
+ <form>
+ <div className='modal-body'>
+ <div className='row'>
+ <div className='col-lg-12'>
+ <div className='subform'>
+ <div className='nested-head'>
+ <div className='wrapper'>
+ <div>
+ <div className='form-group'>
+ <label className='control-label'>{this.props.modal.modalProps.purchase_windows.length == 0 ? "Aucun calendrier commercial associé" : "Calendriers commerciaux associés"}</label>
+ </div>
+ </div>
+ <div></div>
+ </div>
+ </div>
+ {this.props.modal.modalProps.purchase_windows.map((tt, i) =>
+ <div className='nested-fields' key={i}>
+ <div className='wrapper'>
+ <div> <a href={this.purchaseWindowURL(tt)} target="_blank">
+ <span className="fa fa-circle mr-xs" style={{color: tt.color}}></span>
+ {tt.name}
+ </a> </div>
+ {
+ this.props.editMode &&
+ <div>
+ <a
+ href='#'
+ title='Supprimer'
+ className='fa fa-trash remove_fields'
+ style={{ height: 'auto', lineHeight: 'normal' }}
+ onClick={(e) => {
+ e.preventDefault()
+ this.props.onDeleteCalendarModal(tt)
+ }}
+ ></a>
+ </div>
+ }
+ </div>
+ </div>
+ )}
+ {
+ this.props.editMode &&
+ <div className='nested-fields'>
+ <div className='wrapper'>
+ <div>
+ <TimetableSelect2
+ onSelect2Timetable={this.props.onSelect2Timetable}
+ chunkURL={'/autocomplete_purchase_windows.json'}
+ searchKey={"name_or_objectid_cont_any"}
+ isFilter={false}
+ />
+ </div>
+ </div>
+ </div>
+ }
+ </div>
+ </div>
+ </div>
+ </div>
+ {
+ this.props.editMode &&
+ <div className='modal-footer'>
+ <button
+ className='btn btn-link'
+ data-dismiss='modal'
+ type='button'
+ onClick={this.props.onModalClose}
+ >
+ Annuler
+ </button>
+ <button
+ className='btn btn-primary'
+ type='button'
+ onClick={this.handleSubmit}
+ >
+ Valider
+ </button>
+ </div>
+ }
+ </form>
+ )}
+
+ </div>
+ </div>
+ </div>
+ </div>
+ </li>
+ )
+ } else {
+ return false
+ }
+ }
+}
+
+PurchaseWindowsEditVehicleJourney.propTypes = {
+ onOpenCalendarsEditModal: PropTypes.func.isRequired,
+ onModalClose: PropTypes.func.isRequired,
+ onShoppingWindowsEditVehicleJourney: PropTypes.func.isRequired,
+ onDeleteCalendarModal: PropTypes.func.isRequired,
+ onSelect2Timetable: PropTypes.func.isRequired,
+ disabled: PropTypes.bool.isRequired
+}
diff --git a/app/javascript/vehicle_journeys/components/tools/ShiftVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/ShiftVehicleJourney.js
index a54e40502..6574bfa2d 100644
--- a/app/javascript/vehicle_journeys/components/tools/ShiftVehicleJourney.js
+++ b/app/javascript/vehicle_journeys/components/tools/ShiftVehicleJourney.js
@@ -1,4 +1,5 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import actions from '../../actions'
export default class ShiftVehicleJourney extends Component {
diff --git a/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js
index 6629135dd..e2fcd27d5 100644
--- a/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js
+++ b/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js
@@ -1,4 +1,5 @@
-import React, { PropTypes, Component } from 'react'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
import actions from '../../actions'
import TimetableSelect2 from './select2s/TimetableSelect2'
@@ -33,6 +34,7 @@ export default class TimetablesEditVehicleJourney extends Component {
data-toggle='modal'
data-target='#CalendarsEditVehicleJourneyModal'
onClick={() => this.props.onOpenCalendarsEditModal(actions.getSelected(this.props.vehicleJourneys))}
+ title='Calendriers'
>
<span className='fa fa-calendar'></span>
</button>
@@ -65,9 +67,14 @@ export default class TimetablesEditVehicleJourney extends Component {
{this.props.modal.modalProps.timetables.map((tt, i) =>
<div className='nested-fields' key={i}>
<div className='wrapper'>
- <div> <a href={this.timeTableURL(tt)} target="_blank">{tt.comment}</a> </div>
+ <div>
+ <a href={this.timeTableURL(tt)} target="_blank">
+ <span className="fa fa-circle mr-xs" style={{color: tt.color || 'black'}}></span>
+ {tt.comment}
+ </a>
+ </div>
{
- this.props.editMode &&
+ this.props.editMode &&
<div>
<a
href='#'
@@ -85,13 +92,14 @@ export default class TimetablesEditVehicleJourney extends Component {
</div>
)}
{
- this.props.editMode &&
+ this.props.editMode &&
<div className='nested-fields'>
<div className='wrapper'>
<div>
<TimetableSelect2
onSelect2Timetable={this.props.onSelect2Timetable}
chunkURL={'/autocomplete_time_tables.json'}
+ searchKey={"comment_or_objectid_cont_any"}
isFilter={false}
/>
</div>
@@ -103,7 +111,7 @@ export default class TimetablesEditVehicleJourney extends Component {
</div>
</div>
{
- this.props.editMode &&
+ this.props.editMode &&
<div className='modal-footer'>
<button
className='btn btn-link'
@@ -144,4 +152,4 @@ TimetablesEditVehicleJourney.propTypes = {
onDeleteCalendarModal: PropTypes.func.isRequired,
onSelect2Timetable: PropTypes.func.isRequired,
disabled: PropTypes.bool.isRequired
-} \ No newline at end of file
+}
diff --git a/app/javascript/vehicle_journeys/components/tools/VehicleJourneyInfoButton.js b/app/javascript/vehicle_journeys/components/tools/VehicleJourneyInfoButton.js
new file mode 100644
index 000000000..538bbdbd6
--- /dev/null
+++ b/app/javascript/vehicle_journeys/components/tools/VehicleJourneyInfoButton.js
@@ -0,0 +1,30 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import actions from '../../actions'
+
+export default class VehicleJourneyInfoButton extends Component {
+ constructor(props) {
+ super(props)
+ }
+
+
+ render() {
+ return (
+ <div className='info-button'>
+ <button
+ type='button'
+ data-toggle='modal'
+ data-target='#EditVehicleJourneyModal'
+ onClick={() => this.props.onOpenEditModal(this.props.vehicleJourney)}
+ >
+ <span className='fa fa-info'></span>
+ </button>
+ </div>
+ )
+ }
+}
+
+VehicleJourneyInfoButton.propTypes = {
+ onOpenEditModal: PropTypes.func.isRequired,
+ vehicleJourney: PropTypes.object.isRequired,
+}
diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js
index 79ba8f094..5c7f75d99 100644
--- a/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js
+++ b/app/javascript/vehicle_journeys/components/tools/select2s/CompanySelect2.js
@@ -1,6 +1,7 @@
import _ from 'lodash'
-import React, { PropTypes, Component } from 'react'
-import Select2 from 'react-select2'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import Select2 from 'react-select2-wrapper'
import actions from '../../../actions'
// get JSON full path
@@ -25,7 +26,7 @@ export default class BSelect4 extends Component {
multiple={false}
ref='company_id'
options={{
- allowClear: this.props.editMode,
+ allowClear: true,
theme: 'bootstrap',
width: '100%',
placeholder: 'Filtrer par transporteur...',
diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js
index fa847886c..7ab85a1ea 100644
--- a/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js
+++ b/app/javascript/vehicle_journeys/components/tools/select2s/MissionSelect2.js
@@ -1,6 +1,7 @@
import _ from 'lodash'
-import React, { PropTypes, Component } from 'react'
-import Select2 from 'react-select2'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import Select2 from 'react-select2-wrapper'
import actions from '../../../actions'
// get JSON full path
@@ -11,50 +12,114 @@ let path = window.location.pathname.split('/', 7).join('/')
export default class BSelect4 extends Component {
constructor(props) {
super(props)
+ this.onSelect = this.onSelect.bind(this)
+ }
+
+ useAjax(){
+ return this.props.values == undefined || this.props.values.length == 0
+ }
+
+ value(){
+ let val = undefined
+ if(this.props.isFilter) {
+ val = this.props.filters.query.journeyPattern
+ }
+ else{
+ if(this.props.selection.selectedJPModal){
+ val = this.props.selection.selectedJPModal
+ }
+ }
+ if(this.useAjax()){
+ val = val.published_name
+ }
+ else{
+ if(val){
+ val = val.id
+ }
+ }
+ return val
+ }
+
+ data(){
+ if(!this.useAjax()){
+ let values = [{}]
+ values.push(...this.props.values)
+ return values
+ }
+ if(this.props.isFilter){
+ return [this.props.filters.query.journeyPattern.published_name]
+ }
+
+ return (this.props.selection.selectedJPModal) ? [this.props.selection.selectedJPModal.published_name] : undefined
+ }
+
+ onSelect(e){
+ if(this.useAjax()){
+ this.props.onSelect2JourneyPattern(e)
+ }
+ else{
+ let data = JSON.parse(e.currentTarget.selectedOptions[0].dataset.item)
+
+ this.props.onSelect2JourneyPattern({params:
+ {
+ data: _.assign({}, e.params.data, data)
+ }
+ })
+ }
+ }
+
+ options(){
+ let options = {
+ theme: 'bootstrap',
+ width: '100%',
+ escapeMarkup: function (markup) { return markup; },
+ templateResult: formatRepo,
+ placeholder: 'Filtrer par code, nom ou OID de mission...',
+ language: require('./fr'),
+ allowClear: false,
+ escapeMarkup: function (markup) { return markup; },
+ }
+ if(this.useAjax()){
+ options = _.assign({}, options, {
+ ajax: {
+ url: origin + path + '/journey_patterns_collection.json',
+ dataType: 'json',
+ delay: '500',
+ data: function(params) {
+ return {
+ q: { published_name_or_objectid_or_registration_number_cont: params.term},
+ };
+ },
+ processResults: function(data, params) {
+ return {
+ results: data.map(
+ item => _.assign(
+ {},
+ item,
+ { text: "<strong>" + item.published_name + " - " + item.short_id + "</strong><br/><small>" + item.registration_number + "</small>" }
+ )
+ )
+ };
+ },
+ cache: true
+ },
+ minimumInputLength: 1
+ })
+ }
+ return options
}
render() {
return (
<Select2
- data={(this.props.isFilter) ? [this.props.filters.query.journeyPattern.published_name] : ((this.props.selection.selectedJPModal) ? [this.props.selection.selectedJPModal.published_name] : undefined)}
- value={(this.props.isFilter) ? this.props.filters.query.journeyPattern.published_name : ((this.props.selection.selectedJPModal) ? this.props.selection.selectedJPModal.published_name : undefined) }
- onSelect={(e) => this.props.onSelect2JourneyPattern(e)}
+ data={this.data()}
+ value={this.value()}
+ onSelect={this.onSelect}
multiple={false}
ref='journey_pattern_id'
className={!this.props.isFilter ? "vjCreateSelectJP" : null}
required={!this.props.isFilter}
- options={{
- allowClear: false,
- theme: 'bootstrap',
- placeholder: 'Filtrer par code, nom ou OID de mission...',
- language: require('./fr'),
- width: '100%',
- ajax: {
- url: origin + path + '/journey_patterns_collection.json',
- dataType: 'json',
- delay: '500',
- data: function(params) {
- return {
- q: { published_name_or_objectid_or_registration_number_cont: params.term},
- };
- },
- processResults: function(data, params) {
- return {
- results: data.map(
- item => _.assign(
- {},
- item,
- { text: "<strong>" + item.published_name + " - " + item.short_id + "</strong><br/><small>" + item.registration_number + "</small>" }
- )
- )
- };
- },
- cache: true
- },
- minimumInputLength: 1,
- escapeMarkup: function (markup) { return markup; },
- templateResult: formatRepo
- }}
+ options={this.options()}
/>
)
}
@@ -62,4 +127,4 @@ export default class BSelect4 extends Component {
const formatRepo = (props) => {
if(props.text) return props.text
-} \ No newline at end of file
+}
diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js
index 19c183839..0339455ca 100644
--- a/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js
+++ b/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js
@@ -1,6 +1,7 @@
import _ from 'lodash'
-import React, { PropTypes, Component } from 'react'
-import Select2 from 'react-select2'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import Select2 from 'react-select2-wrapper'
import actions from '../../../actions'
// get JSON full path
@@ -31,12 +32,10 @@ export default class BSelect4 extends Component {
url: origin + path + this.props.chunkURL,
dataType: 'json',
delay: '500',
- data: function(params) {
- return {
- q: {
- comment_or_objectid_cont_any: params.term
- }
- };
+ data: (params) => {
+ let q = {}
+ q[this.props.searchKey] = params.term
+ return {q}
},
processResults: function(data, params) {
return {
@@ -44,7 +43,7 @@ export default class BSelect4 extends Component {
item => _.assign(
{},
item,
- {text: '<strong>' + "<span class='fa fa-circle' style='color:" + (item.color ? item.color : '#4B4B4B') + "'></span> " + item.comment + ' - ' + item.short_id + '</strong><br/><small>' + (item.day_types ? item.day_types.match(/[A-Z]?[a-z]+/g).join(', ') : "") + '</small>'}
+ {text: '<strong>' + "<span class='fa fa-circle' style='color:" + (item.color ? item.color : '#4B4B4B') + "'></span> " + (item.comment || item.name) + ' - ' + item.short_id + '</strong><br/><small>' + (item.day_types ? item.day_types.match(/[A-Z]?[a-z]+/g).join(', ') : "") + '</small>'}
)
)
};
@@ -62,4 +61,4 @@ export default class BSelect4 extends Component {
const formatRepo = (props) => {
if(props.text) return props.text
-} \ No newline at end of file
+}
diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js
index b063abeca..ccb4c9595 100644
--- a/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js
+++ b/app/javascript/vehicle_journeys/components/tools/select2s/VJSelect2.js
@@ -1,6 +1,7 @@
import _ from 'lodash'
-import React, { PropTypes, Component } from 'react'
-import Select2 from 'react-select2'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import Select2 from 'react-select2-wrapper'
import actions from '../../../actions'
// get JSON full path
diff --git a/app/javascript/vehicle_journeys/containers/Filters.js b/app/javascript/vehicle_journeys/containers/Filters.js
index bec3527f4..a41c599f7 100644
--- a/app/javascript/vehicle_journeys/containers/Filters.js
+++ b/app/javascript/vehicle_journeys/containers/Filters.js
@@ -5,7 +5,8 @@ import Filters from '../components/Filters'
const mapStateToProps = (state) => {
return {
filters: state.filters,
- pagination: state.pagination
+ pagination: state.pagination,
+ missions: state.missions,
}
}
diff --git a/app/javascript/vehicle_journeys/containers/SaveVehicleJourneys.js b/app/javascript/vehicle_journeys/containers/SaveVehicleJourneys.js
index 18f9e994e..3daf831f8 100644
--- a/app/javascript/vehicle_journeys/containers/SaveVehicleJourneys.js
+++ b/app/javascript/vehicle_journeys/containers/SaveVehicleJourneys.js
@@ -17,8 +17,15 @@ const mapDispatchToProps = (dispatch) => {
onEnterEditMode: () => {
dispatch(actions.enterEditMode())
},
+ onExitEditMode: () => {
+ dispatch(actions.cancelSelection())
+ dispatch(actions.exitEditMode())
+ },
onSubmitVehicleJourneys: (next, state) => {
actions.submitVehicleJourneys(dispatch, state, next)
+ },
+ validate: (state) =>{
+ actions.validate(dispatch, state)
}
}
}
diff --git a/app/javascript/vehicle_journeys/containers/VehicleJourneysList.js b/app/javascript/vehicle_journeys/containers/VehicleJourneysList.js
index 38ab9f6d3..76d1c3a78 100644
--- a/app/javascript/vehicle_journeys/containers/VehicleJourneysList.js
+++ b/app/javascript/vehicle_journeys/containers/VehicleJourneysList.js
@@ -6,17 +6,19 @@ const mapStateToProps = (state) => {
return {
editMode: state.editMode,
vehicleJourneys: state.vehicleJourneys,
+ returnVehicleJourneys: state.returnVehicleJourneys,
status: state.status,
filters: state.filters,
- stopPointsList: state.stopPointsList
+ stopPointsList: state.stopPointsList,
+ returnStopPointsList: state.returnStopPointsList
}
}
const mapDispatchToProps = (dispatch) => {
return {
- onLoadFirstPage: (filters) =>{
+ onLoadFirstPage: (filters, routeUrl) =>{
dispatch(actions.fetchingApi())
- actions.fetchVehicleJourneys(dispatch, undefined, undefined, filters.queryString)
+ actions.fetchVehicleJourneys(dispatch, undefined, undefined, filters.queryString, routeUrl)
},
onUpdateTime: (e, subIndex, index, timeUnit, isDeparture, isArrivalsToggled) => {
dispatch(actions.updateTime(e.target.value, subIndex, index, timeUnit, isDeparture, isArrivalsToggled))
diff --git a/app/javascript/vehicle_journeys/containers/tools/AddVehicleJourney.js b/app/javascript/vehicle_journeys/containers/tools/AddVehicleJourney.js
index 5da0bd3e9..d982f5a5f 100644
--- a/app/javascript/vehicle_journeys/containers/tools/AddVehicleJourney.js
+++ b/app/javascript/vehicle_journeys/containers/tools/AddVehicleJourney.js
@@ -9,6 +9,8 @@ const mapStateToProps = (state, ownProps) => {
vehicleJourneys: state.vehicleJourneys,
status: state.status,
stopPointsList: state.stopPointsList,
+ missions: state.missions,
+ custom_fields: state.custom_fields,
}
}
@@ -28,6 +30,9 @@ const mapDispatchToProps = (dispatch) => {
},
onSelect2Company: (e) => {
dispatch(actions.select2Company(e.params.data))
+ },
+ onUnselect2Company: () => {
+ dispatch(actions.unselect2Company())
}
}
}
diff --git a/app/javascript/vehicle_journeys/containers/tools/PurchaseWindowsEditVehicleJourney.js b/app/javascript/vehicle_journeys/containers/tools/PurchaseWindowsEditVehicleJourney.js
new file mode 100644
index 000000000..3fef44489
--- /dev/null
+++ b/app/javascript/vehicle_journeys/containers/tools/PurchaseWindowsEditVehicleJourney.js
@@ -0,0 +1,38 @@
+import actions from '../../actions'
+import { connect } from 'react-redux'
+import PurchaseWindowsEditVehicleJourneyComponent from '../../components/tools/PurchaseWindowsEditVehicleJourney'
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ editMode: state.editMode,
+ modal: state.modal,
+ vehicleJourneys: state.vehicleJourneys,
+ status: state.status,
+ disabled: ownProps.disabled
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onModalClose: () =>{
+ dispatch(actions.closeModal())
+ },
+ onOpenCalendarsEditModal: (vehicleJourneys) =>{
+ dispatch(actions.openPurchaseWindowsEditModal(vehicleJourneys))
+ },
+ onDeleteCalendarModal: (timetable) => {
+ dispatch(actions.deletePurchaseWindowsModal(timetable))
+ },
+ onShoppingWindowsEditVehicleJourney: (vehicleJourneys, timetables) =>{
+ dispatch(actions.editVehicleJourneyPurchaseWindows(vehicleJourneys, timetables))
+ },
+ onSelect2Timetable: (e) =>{
+ dispatch(actions.selectPurchaseWindowsModal(e.params.data))
+ dispatch(actions.addSelectedPurchaseWindow())
+ }
+ }
+}
+
+const PurchaseWindowsEditVehicleJourney = connect(mapStateToProps, mapDispatchToProps)(PurchaseWindowsEditVehicleJourneyComponent)
+
+export default PurchaseWindowsEditVehicleJourney
diff --git a/app/javascript/vehicle_journeys/containers/tools/VehicleJourneyInfoButton.js b/app/javascript/vehicle_journeys/containers/tools/VehicleJourneyInfoButton.js
new file mode 100644
index 000000000..19010c312
--- /dev/null
+++ b/app/javascript/vehicle_journeys/containers/tools/VehicleJourneyInfoButton.js
@@ -0,0 +1,20 @@
+import actions from '../../actions'
+import { connect } from 'react-redux'
+import VehicleJourneyInfoButtonComponent from '../../components/tools/VehicleJourneyInfoButton'
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onOpenEditModal: (vj) =>{
+ dispatch(actions.openInfoModal(vj))
+ },
+ }
+}
+
+const VehicleJourneyInfoButton = connect(mapStateToProps, mapDispatchToProps)(VehicleJourneyInfoButtonComponent)
+
+export default VehicleJourneyInfoButton
diff --git a/app/javascript/vehicle_journeys/reducers/custom_fields.js b/app/javascript/vehicle_journeys/reducers/custom_fields.js
new file mode 100644
index 000000000..482fd91cb
--- /dev/null
+++ b/app/javascript/vehicle_journeys/reducers/custom_fields.js
@@ -0,0 +1,6 @@
+export default function custom_fields(state = [], action) {
+ switch (action.type) {
+ default:
+ return state
+ }
+}
diff --git a/app/javascript/vehicle_journeys/reducers/index.js b/app/javascript/vehicle_journeys/reducers/index.js
index bb24aa185..95ac9c7e1 100644
--- a/app/javascript/vehicle_journeys/reducers/index.js
+++ b/app/javascript/vehicle_journeys/reducers/index.js
@@ -1,20 +1,27 @@
import { combineReducers } from 'redux'
import vehicleJourneys from './vehicleJourneys'
+import returnVehicleJourneys from './returnVehicleJourneys'
import pagination from './pagination'
import modal from './modal'
import status from './status'
import filters from './filters'
import editMode from './editMode'
import stopPointsList from './stopPointsList'
+import missions from './missions'
+import custom_fields from './custom_fields'
const vehicleJourneysApp = combineReducers({
vehicleJourneys,
+ returnVehicleJourneys,
pagination,
modal,
status,
filters,
editMode,
- stopPointsList
+ stopPointsList,
+ returnStopPointsList: stopPointsList,
+ missions,
+ custom_fields
})
export default vehicleJourneysApp
diff --git a/app/javascript/vehicle_journeys/reducers/missions.js b/app/javascript/vehicle_journeys/reducers/missions.js
new file mode 100644
index 000000000..7c1a355c7
--- /dev/null
+++ b/app/javascript/vehicle_journeys/reducers/missions.js
@@ -0,0 +1,6 @@
+export default function missions(state = [], action) {
+ switch (action.type) {
+ default:
+ return state
+ }
+}
diff --git a/app/javascript/vehicle_journeys/reducers/modal.js b/app/javascript/vehicle_journeys/reducers/modal.js
index 57f54a144..bcfc6ea0b 100644
--- a/app/javascript/vehicle_journeys/reducers/modal.js
+++ b/app/javascript/vehicle_journeys/reducers/modal.js
@@ -1,6 +1,6 @@
import _ from 'lodash'
-let vehicleJourneysModal, newModalProps
+let vehicleJourneysModal, newModalProps, vehicleJourney
export default function modal(state = {}, action) {
switch (action.type) {
@@ -37,10 +37,18 @@ export default function modal(state = {}, action) {
},
confirmModal: {}
}
+ case 'INFO_VEHICLEJOURNEY_MODAL':
+ return {
+ type: 'edit',
+ modalProps: {
+ vehicleJourney: action.vehicleJourney,
+ info: true
+ },
+ confirmModal: {}
+ }
case 'EDIT_CALENDARS_VEHICLEJOURNEY_MODAL':
vehicleJourneysModal = JSON.parse(JSON.stringify(action.vehicleJourneys))
let uniqTimetables = []
- let timetable = {}
vehicleJourneysModal.map((vj, i) => {
vj.time_tables.map((tt, j) =>{
if(!(_.find(uniqTimetables, tt))){
@@ -56,15 +64,38 @@ export default function modal(state = {}, action) {
},
confirmModal: {}
}
+ case 'EDIT_PURCHASE_WINDOWS_VEHICLEJOURNEY_MODAL':
+ var vehicleJourneys = JSON.parse(JSON.stringify(action.vehicleJourneys))
+ let uniqPurchaseWindows = []
+ vehicleJourneys.map((vj, i) => {
+ vj.purchase_windows.map((pw, j) =>{
+ if(!(_.find(uniqPurchaseWindows, pw))){
+ uniqPurchaseWindows.push(pw)
+ }
+ })
+ })
+ return {
+ type: 'purchase_windows_edit',
+ modalProps: {
+ vehicleJourneys: vehicleJourneys,
+ purchase_windows: uniqPurchaseWindows
+ },
+ confirmModal: {}
+ }
case 'SELECT_CP_EDIT_MODAL':
- newModalProps = _.assign({}, state.modalProps, {selectedCompany : action.selectedItem})
+ vehicleJourney = _.assign({}, state.modalProps.vehicleJourney, {company: action.selectedItem})
+ newModalProps = _.assign({}, state.modalProps, {vehicleJourney})
return _.assign({}, state, {modalProps: newModalProps})
case 'UNSELECT_CP_EDIT_MODAL':
- newModalProps = _.assign({}, state.modalProps, {selectedCompany : undefined})
+ vehicleJourney = _.assign({}, state.modalProps.vehicleJourney, {company: undefined})
+ newModalProps = _.assign({}, state.modalProps, {vehicleJourney})
return _.assign({}, state, {modalProps: newModalProps})
case 'SELECT_TT_CALENDAR_MODAL':
newModalProps = _.assign({}, state.modalProps, {selectedTimetable : action.selectedItem})
return _.assign({}, state, {modalProps: newModalProps})
+ case 'SELECT_PURCHASE_WINDOW_MODAL':
+ newModalProps = _.assign({}, state.modalProps, {selectedPurchaseWindow : action.selectedItem})
+ return _.assign({}, state, {modalProps: newModalProps})
case 'ADD_SELECTED_TIMETABLE':
if(state.modalProps.selectedTimetable){
newModalProps = JSON.parse(JSON.stringify(state.modalProps))
@@ -73,6 +104,14 @@ export default function modal(state = {}, action) {
}
return _.assign({}, state, {modalProps: newModalProps})
}
+ case 'ADD_SELECTED_PURCHASE_WINDOW':
+ if(state.modalProps.selectedPurchaseWindow){
+ newModalProps = JSON.parse(JSON.stringify(state.modalProps))
+ if (!_.find(newModalProps.purchase_windows, newModalProps.selectedPurchaseWindow)){
+ newModalProps.purchase_windows.push(newModalProps.selectedPurchaseWindow)
+ }
+ return _.assign({}, state, {modalProps: newModalProps})
+ }
case 'DELETE_CALENDAR_MODAL':
newModalProps = JSON.parse(JSON.stringify(state.modalProps))
let timetablesModal = state.modalProps.timetables.slice(0)
@@ -92,19 +131,32 @@ export default function modal(state = {}, action) {
newModalProps.vehicleJourneys = vehicleJourneysModal
newModalProps.timetables = timetablesModal
return _.assign({}, state, {modalProps: newModalProps})
+ case 'DELETE_PURCHASE_WINDOW_MODAL':
+ newModalProps = JSON.parse(JSON.stringify(state.modalProps))
+ let purchase_windows = state.modalProps.purchase_windows.slice(0)
+ purchase_windows.map((tt, i) =>{
+ if(tt == action.purchaseWindow){
+ purchase_windows.splice(i, 1)
+ }
+ })
+ vehicleJourneysModal = state.modalProps.vehicleJourneys.slice(0)
+ vehicleJourneysModal.map((vj) =>{
+ vj.purchase_windows.map((tt, i) =>{
+ if (_.isEqual(tt, action.purchaseWindow)){
+ vj.purchase_windows.splice(i, 1)
+ }
+ })
+ })
+ newModalProps.vehicleJourneys = vehicleJourneysModal
+ newModalProps.purchase_windows = purchase_windows
+ return _.assign({}, state, {modalProps: newModalProps})
case 'CREATE_VEHICLEJOURNEY_MODAL':
let selectedJP = {}
if (window.jpOrigin){
let stopAreas = _.map(window.jpOriginStopPoints, (sa, i) =>{
return _.assign({}, {stop_area_short_description : {id : sa.stop_area_id}})
})
- selectedJP = {
- id: window.jpOrigin.id,
- name: window.jpOrigin.name,
- published_name: window.jpOrigin.published_name,
- objectid: window.jpOrigin.objectid,
- stop_areas: stopAreas
- }
+ selectedJP = _.assign({}, window.jpOrigin, {stop_areas: stopAreas})
}
return {
type: 'create',
@@ -135,4 +187,4 @@ export default function modal(state = {}, action) {
default:
return state
}
-} \ No newline at end of file
+}
diff --git a/app/javascript/vehicle_journeys/reducers/returnVehicleJourneys.js b/app/javascript/vehicle_journeys/reducers/returnVehicleJourneys.js
new file mode 100644
index 000000000..db3c71d17
--- /dev/null
+++ b/app/javascript/vehicle_journeys/reducers/returnVehicleJourneys.js
@@ -0,0 +1,11 @@
+import _ from 'lodash'
+import actions from '../actions'
+
+export default function returnVehicleJourneys(state = [], action) {
+ switch (action.type) {
+ case 'RECEIVE_RETURN_VEHICLE_JOURNEYS':
+ return [...action.json]
+ default:
+ return state
+ }
+}
diff --git a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js
index 136e1b41a..1a15ec46d 100644
--- a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js
+++ b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js
@@ -9,29 +9,68 @@ const vehicleJourney= (state = {}, action, keep) => {
return _.assign({}, state, {selected: false})
case 'ADD_VEHICLEJOURNEY':
let pristineVjasList = []
+ let prevSp = action.stopPointsList[0]
+ let current_time = {
+ hour: 0,
+ minute: 0
+ }
+ if(action.data["start_time.hour"] && action.data["start_time.minute"] && action.selectedJourneyPattern.full_schedule){
+ current_time.hour = parseInt(action.data["start_time.hour"].value)
+ current_time.minute = parseInt(action.data["start_time.minute"].value) || 0
+ }
_.each(action.stopPointsList, (sp) =>{
+ if(action.selectedJourneyPattern.full_schedule && action.selectedJourneyPattern.costs && action.selectedJourneyPattern.costs[prevSp.stop_area_id + "-" + sp.stop_area_id]){
+ let delta = parseInt(action.selectedJourneyPattern.costs[prevSp.stop_area_id + "-" + sp.stop_area_id].time)
+ let delta_hour = parseInt(delta/60)
+ let delta_minute = delta - 60*delta_hour
+ current_time.hour += delta_hour
+ current_time.minute += delta_minute
+ let extra_hours = parseInt(current_time.minute/60)
+ current_time.hour += extra_hours
+ current_time.minute -= extra_hours*60
+ current_time.hour = current_time.hour % 24
+ prevSp = sp
+ }
+ let offsetHours = sp.time_zone_offset / 3600
+ let offsetminutes = sp.time_zone_offset/60 - 60*offsetHours
let newVjas = {
delta: 0,
departure_time:{
- hour: '00',
- minute: '00'
+ hour: (24 + current_time.hour + offsetHours) % 24,
+ minute: current_time.minute + offsetminutes
},
arrival_time:{
- hour: '00',
- minute: '00'
+ hour: (24 + current_time.hour + offsetHours) % 24,
+ minute: current_time.minute + offsetminutes
},
stop_point_objectid: sp.object_id,
stop_area_cityname: sp.city_name,
dummy: true
}
+ if(current_time.hour + offsetHours > 24){
+ newVjas.departure_day_offset = 1
+ newVjas.arrival_day_offset = 1
+ }
+ if(current_time.hour + offsetHours < 0){
+ newVjas.departure_day_offset = -1
+ newVjas.arrival_day_offset = -1
+ }
+
_.each(action.selectedJourneyPattern.stop_areas, (jp) =>{
if (jp.stop_area_short_description.id == sp.id){
newVjas.dummy = false
return
}
})
+
+ if(newVjas.dummy){
+ newVjas.departure_time = {hour: "00", minute: "00"}
+ newVjas.arrival_time = {hour: "00", minute: "00"}
+ }
pristineVjasList.push(newVjas)
+
})
+
return {
company: action.selectedCompany,
journey_pattern: action.selectedJourneyPattern,
@@ -41,11 +80,13 @@ const vehicleJourney= (state = {}, action, keep) => {
short_id: '',
footnotes: [],
time_tables: [],
+ purchase_windows: [],
vehicle_journey_at_stops: pristineVjasList,
selected: false,
deletable: false,
transport_mode: window.transportMode ? window.transportMode : 'undefined',
- transport_submode: window.transportSubmode ? window.transportSubmode : 'undefined'
+ transport_submode: window.transportSubmode ? window.transportSubmode : 'undefined',
+ custom_fields: action.data.custom_fields
}
case 'DUPLICATE_VEHICLEJOURNEY':
case 'SHIFT_VEHICLEJOURNEY':
@@ -79,18 +120,12 @@ const vehicleJourney= (state = {}, action, keep) => {
if (action.isDeparture){
newSchedule.departure_time[action.timeUnit] = actions.pad(action.val, action.timeUnit)
if(!action.isArrivalsToggled)
- newSchedule.arrival_time[action.timeUnit] = actions.pad(action.val, action.timeUnit)
- newSchedule = actions.getDelta(newSchedule)
- if(newSchedule.delta < 0){
- return vjas
- }
+ newSchedule.arrival_time[action.timeUnit] = newSchedule.departure_time[action.timeUnit]
+ newSchedule = actions.adjustSchedule(action, newSchedule)
return _.assign({}, state.vehicle_journey_at_stops[action.subIndex], {arrival_time: newSchedule.arrival_time, departure_time: newSchedule.departure_time, delta: newSchedule.delta})
}else{
newSchedule.arrival_time[action.timeUnit] = actions.pad(action.val, action.timeUnit)
- newSchedule = actions.getDelta(newSchedule)
- if(newSchedule.delta < 0){
- return vjas
- }
+ newSchedule = actions.adjustSchedule(action, newSchedule)
return _.assign({}, state.vehicle_journey_at_stops[action.subIndex], {arrival_time: newSchedule.arrival_time, departure_time: newSchedule.departure_time, delta: newSchedule.delta})
}
}else{
@@ -131,6 +166,7 @@ export default function vehicleJourneys(state = [], action) {
company: action.selectedCompany,
published_journey_name: action.data.published_journey_name.value,
published_journey_identifier: action.data.published_journey_identifier.value,
+ custom_fields: action.data.custom_fields,
})
}else{
return vj
@@ -161,6 +197,21 @@ export default function vehicleJourneys(state = [], action) {
return vj
}
})
+ case 'EDIT_VEHICLEJOURNEYS_PURCHASE_WINDOWS':
+ let newWindows = JSON.parse(JSON.stringify(action.purchase_windows))
+ return state.map((vj,i) =>{
+ if(vj.selected){
+ let updatedVJ = _.assign({}, vj)
+ action.vehicleJourneys.map((vjm, j) =>{
+ if(vj.objectid == vjm.objectid){
+ updatedVJ.purchase_windows = newWindows
+ }
+ })
+ return updatedVJ
+ }else{
+ return vj
+ }
+ })
case 'SHIFT_VEHICLEJOURNEY':
return state.map((vj, i) => {
if (vj.selected){
@@ -222,7 +273,9 @@ export default function vehicleJourneys(state = [], action) {
return vj
}
})
+ case 'DID_VALIDATE_VEHICLE_JOURNEYS':
+ return [...action.vehicleJourneys]
default:
return state
}
-} \ No newline at end of file
+}
diff --git a/app/jobs/mailer_job.rb b/app/jobs/mailer_job.rb
index 3918745b8..eb3250a27 100644
--- a/app/jobs/mailer_job.rb
+++ b/app/jobs/mailer_job.rb
@@ -1,6 +1,9 @@
class MailerJob < ActiveJob::Base
# No need to specify queue it's already used mailers queue
+ # This job will be retried, unlike Sidekiq jobs which are configured
+ # to not retry
+
def perform klass, action, params
klass.constantize.public_send(action, *params).deliver_later
end
diff --git a/app/models/calendar.rb b/app/models/calendar.rb
index b2e73929f..84b569ab4 100644
--- a/app/models/calendar.rb
+++ b/app/models/calendar.rb
@@ -3,176 +3,115 @@ require_relative 'calendar/date_value'
require_relative 'calendar/period'
class Calendar < ActiveRecord::Base
- has_paper_trail
+ include DateSupport
+ include PeriodSupport
+ include ApplicationDaysSupport
+ include TimetableSupport
+
+ has_paper_trail class_name: 'PublicVersion'
belongs_to :organisation
- has_many :time_tables
+ belongs_to :workgroup
- validates_presence_of :name, :short_name, :organisation
+ validates_presence_of :name, :short_name, :organisation, :workgroup
validates_uniqueness_of :short_name
- after_initialize :init_dates_and_date_ranges
+ has_many :time_tables
scope :contains_date, ->(date) { where('date ? = any (dates) OR date ? <@ any (date_ranges)', date, date) }
- def init_dates_and_date_ranges
- self.dates ||= []
- self.date_ranges ||= []
- end
+ after_initialize :set_defaults
def self.ransackable_scopes(auth_object = nil)
[:contains_date]
end
- def convert_to_time_table
- Chouette::TimeTable.new.tap do |tt|
- self.dates.each do |d|
- tt.dates << Chouette::TimeTableDate.new(date: d, in_out: true)
- end
- self.periods.each do |p|
- tt.periods << Chouette::TimeTablePeriod.new(period_start: p.begin, period_end: p.end)
- end
- tt.int_day_types = 508
- end
+ def self.state_permited_attributes item
+ {name: item["comment"]}
end
-
- ### Calendar::Period
- # Required by coocon
- def build_period
- Calendar::Period.new
+ def set_defaults
+ self.excluded_dates ||= []
+ self.int_day_types ||= EVERYDAY
end
- def periods
- @periods ||= init_periods
+ def human_attribute_name(*args)
+ self.class.human_attribute_name(*args)
end
- def init_periods
- (date_ranges || [])
- .each_with_index
- .map( &Calendar::Period.method(:from_range) )
+ def shortcuts_update(date=nil)
end
- private :init_periods
-
- validate :validate_periods
- def validate_periods
- periods_are_valid = periods.all?(&:valid?)
-
- periods.each do |period|
- if period.intersect?(periods)
- period.errors.add(:base, I18n.t('calendars.errors.overlapped_periods'))
- periods_are_valid = false
+ def convert_to_time_table
+ Chouette::TimeTable.new.tap do |tt|
+ self.dates.each do |d|
+ tt.dates << Chouette::TimeTableDate.new(date: d, in_out: true)
end
- end
-
- unless periods_are_valid
- errors.add(:periods, :invalid)
+ self.periods.each do |p|
+ tt.periods << Chouette::TimeTablePeriod.new(period_start: p.begin, period_end: p.end)
+ end
+ tt.int_day_types = self.int_day_types
end
end
- def flatten_date_array attributes, key
- date_int = %w(1 2 3).map {|e| attributes["#{key}(#{e}i)"].to_i }
- Date.new(*date_int)
+ def include_in_dates?(day)
+ self.dates.include? day
end
- def periods_attributes=(attributes = {})
- @periods = []
- attributes.each do |index, period_attribute|
- # Convert date_select to date
- ['begin', 'end'].map do |attr|
- period_attribute[attr] = flatten_date_array(period_attribute, attr)
- end
- period = Calendar::Period.new(period_attribute.merge(id: index))
- @periods << period unless period.marked_for_destruction?
- end
-
- date_ranges_will_change!
+ def excluded_date?(day)
+ self.excluded_dates.include? day
end
- before_validation :fill_date_ranges
-
- def fill_date_ranges
- if @periods
- self.date_ranges = @periods.map(&:range).compact.sort_by(&:begin)
+ def update_in_out date, in_out
+ if in_out
+ self.excluded_dates.delete date
+ self.dates << date unless include_in_dates?(date)
+ else
+ self.dates.delete date
+ self.excluded_dates << date unless excluded_date?(date)
end
+ date
end
- after_save :clear_periods
-
- def clear_periods
- @periods = nil
+ def included_days
+ dates
end
- private :clear_periods
-
- ### Calendar::DateValue
-
- # Required by coocon
- def build_date_value
- Calendar::DateValue.new
+ def excluded_days
+ excluded_dates
end
- def date_values
- @date_values ||= init_date_values
+ def saved_dates
+ Hash[*self.dates.each_with_index.to_a.map(&:reverse).flatten]
end
- def init_date_values
- if dates
- dates.each_with_index.map { |d, index| Calendar::DateValue.from_date(index, d) }
- else
- []
+ def all_dates
+ (dates + excluded_dates).sort.each_with_index.map do |d, i|
+ OpenStruct.new(id: i, date: d, in_out: include_in_dates?(d))
end
end
- private :init_date_values
- validate :validate_date_values
-
- def validate_date_values
- date_values_are_valid = date_values.all?(&:valid?)
-
- date_values.each do |date_value|
- if date_values.count { |d| d.value == date_value.value } > 1
- date_value.errors.add(:base, I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_dates'))
- date_values_are_valid = false
- end
- date_ranges.each do |date_range|
- if date_range.cover? date_value.value
- date_value.errors.add(:base, I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_date_ranges'))
- date_values_are_valid = false
- end
- end
- end
-
- unless date_values_are_valid
- errors.add(:date_values, :invalid)
- end
+ def find_date_by_id id
+ self.dates[id]
end
- def date_values_attributes=(attributes = {})
- @date_values = []
- attributes.each do |index, date_value_attribute|
- date_value_attribute['value'] = flatten_date_array(date_value_attribute, 'value')
- date_value = Calendar::DateValue.new(date_value_attribute.merge(id: index))
- @date_values << date_value unless date_value.marked_for_destruction?
- end
-
- dates_will_change!
+ def destroy_date date
+ self.dates -= [date]
end
- before_validation :fill_dates
-
- def fill_dates
- if @date_values
- self.dates = @date_values.map(&:value).compact.sort
- end
+ def create_date in_out:, date:
+ update_in_out date, in_out
end
- after_save :clear_date_values
-
- def clear_date_values
- @date_values = nil
+ def find_period_by_id id
+ self.periods.find{|p| p.id == id}
end
- private :clear_date_values
+ def build_period
+ self.periods << Calendar::Period.new(id: self.periods.count + 1)
+ self.periods.last
+ end
+ def destroy_period period
+ @periods = self.periods.select{|p| p.end != period.end || p.begin != period.begin}
+ end
end
diff --git a/app/models/calendar/period.rb b/app/models/calendar/period.rb
index 1c423dfcc..8b3e4109b 100644
--- a/app/models/calendar/period.rb
+++ b/app/models/calendar/period.rb
@@ -1,5 +1,5 @@
class Calendar < ActiveRecord::Base
-
+
class Period
include ActiveAttr::Model
@@ -10,6 +10,11 @@ class Calendar < ActiveRecord::Base
validates_presence_of :begin, :end
validate :check_end_greather_than_begin
+ alias_method :period_start, :begin
+ alias_method :period_end, :end
+ alias_method :period_start=, :begin=
+ alias_method :period_end=, :end=
+
def check_end_greather_than_begin
if self.begin && self.end && self.begin >= self.end
errors.add(:base, I18n.t('calendars.errors.short_period'))
diff --git a/app/models/chouette/area_type.rb b/app/models/chouette/area_type.rb
new file mode 100644
index 000000000..e17d2ee8d
--- /dev/null
+++ b/app/models/chouette/area_type.rb
@@ -0,0 +1,55 @@
+class Chouette::AreaType
+ include Comparable
+
+ COMMERCIAL = %i(zdep zder zdlp zdlr lda gdl).freeze
+ NON_COMMERCIAL = %i(deposit border service_area relief other).freeze
+ ALL = COMMERCIAL + NON_COMMERCIAL
+
+ @@commercial = COMMERCIAL
+ @@non_commercial = NON_COMMERCIAL
+ @@all = ALL
+ mattr_accessor :all, :commercial, :non_commercial
+
+ def self.commercial=(values)
+ @@commercial = COMMERCIAL & values
+ reset_caches!
+ end
+
+ def self.non_commercial=(values)
+ @@non_commercial = NON_COMMERCIAL & values
+ reset_caches!
+ end
+
+ @@instances = {}
+ def self.find(code)
+ return unless code
+
+ code = code.to_sym
+ @@instances[code] ||= new(code) if ALL.include? code
+ end
+
+ def self.reset_caches!
+ @@all = @@commercial + @@non_commercial
+ @@instances = {}
+ @@options = {}
+ end
+
+ def self.options(kind=:all)
+ @@options ||= {}
+ @@options[kind] ||= self.send(kind).map { |c| find(c) }.map { |t| [ t.label, t.code ] }
+ end
+
+ attr_reader :code
+ def initialize(code)
+ @code = code
+ end
+
+ def <=>(other)
+ all.index(code) <=> all.index(other.code)
+ end
+
+ def label
+ I18n.translate code, scope: 'area_types.label'
+ end
+
+end
diff --git a/app/models/chouette/company.rb b/app/models/chouette/company.rb
index 12b21e347..b3d40ab96 100644
--- a/app/models/chouette/company.rb
+++ b/app/models/chouette/company.rb
@@ -3,7 +3,7 @@ module Chouette
include CompanyRestrictions
include LineReferentialSupport
include ObjectidSupport
- has_paper_trail
+ has_paper_trail class_name: 'PublicVersion'
has_many :lines
diff --git a/app/models/chouette/journey_pattern.rb b/app/models/chouette/journey_pattern.rb
index a62da6353..aa9fdb810 100644
--- a/app/models/chouette/journey_pattern.rb
+++ b/app/models/chouette/journey_pattern.rb
@@ -27,7 +27,7 @@ module Chouette
def checksum_attributes
values = self.slice(*['name', 'published_name', 'registration_number']).values
- values << self.stop_points.map(&:stop_area).map(&:user_objectid)
+ values << self.stop_points.sort_by(&:position).map(&:stop_area).map(&:user_objectid)
values.flatten
end
@@ -40,7 +40,8 @@ module Chouette
# Update attributes and stop_points associations
jp.update_attributes(state_permited_attributes(item)) unless item['new_record']
jp.state_stop_points_update(item) if !jp.errors.any? && jp.persisted?
- item['errors'] = jp.errors if jp.errors.any?
+ item['errors'] = jp.errors if jp.errors.any?
+ item['checksum'] = jp.checksum
end
if state.any? {|item| item['errors']}
@@ -57,21 +58,23 @@ module Chouette
{
name: item['name'],
published_name: item['published_name'],
- registration_number: item['registration_number']
+ registration_number: item['registration_number'],
+ costs: item['costs']
}
end
def self.state_create_instance route, item
# Flag new record, so we can unset object_id if transaction rollback
jp = route.journey_patterns.create(state_permited_attributes(item))
-
# FIXME
# DefaultAttributesSupport will trigger some weird validation on after save
# wich will call to valid?, wich will populate errors
# In this case, we mark jp to be valid if persisted? return true
jp.errors.clear if jp.persisted?
+ jp.after_commit_objectid
item['object_id'] = jp.objectid
+ item['short_id'] = jp.get_objectid.short_id
item['new_record'] = true
jp
end
@@ -145,5 +148,27 @@ module Chouette
vjas.destroy
end
end
+
+ def costs
+ read_attribute(:costs) || {}
+ end
+
+ def costs_between start, finish
+ key = "#{start.stop_area_id}-#{finish.stop_area_id}"
+ costs[key]&.symbolize_keys || {}
+ end
+
+ def full_schedule?
+ full = true
+ stop_points.order(:position).inject(nil) do |start, finish|
+ next finish unless start.present?
+ costs = costs_between(start, finish)
+ full = false unless costs.present?
+ full = false unless costs[:distance] && costs[:distance] > 0
+ full = false unless costs[:time] && costs[:time] > 0
+ finish
+ end
+ full
+ end
end
end
diff --git a/app/models/chouette/line.rb b/app/models/chouette/line.rb
index 93d4f5e8b..ba2e2755d 100644
--- a/app/models/chouette/line.rb
+++ b/app/models/chouette/line.rb
@@ -1,6 +1,6 @@
module Chouette
class Line < Chouette::ActiveRecord
- has_paper_trail
+ has_paper_trail class_name: 'PublicVersion'
include LineRestrictions
include LineReferentialSupport
include ObjectidSupport
@@ -21,6 +21,7 @@ module Chouette
has_many :journey_patterns, :through => :routes
has_many :vehicle_journeys, :through => :journey_patterns
has_many :routing_constraint_zones, through: :routes
+ has_many :time_tables, -> { distinct }, :through => :vehicle_journeys
has_and_belongs_to_many :group_of_lines, :class_name => 'Chouette::GroupOfLine', :order => 'group_of_lines.name'
@@ -79,5 +80,16 @@ module Chouette
line_referential.companies.where(id: ([company_id] + Array(secondary_company_ids)).compact)
end
+ def deactivate!
+ update_attribute :deactivated, true
+ end
+
+ def activate!
+ update_attribute :deactivated, false
+ end
+
+ def activated?
+ !deactivated
+ end
end
end
diff --git a/app/models/chouette/network.rb b/app/models/chouette/network.rb
index 9b3f2fe29..6843c69ad 100644
--- a/app/models/chouette/network.rb
+++ b/app/models/chouette/network.rb
@@ -1,6 +1,6 @@
module Chouette
class Network < Chouette::ActiveRecord
- has_paper_trail
+ has_paper_trail class_name: 'PublicVersion'
include NetworkRestrictions
include LineReferentialSupport
include ObjectidSupport
diff --git a/app/models/chouette/purchase_window.rb b/app/models/chouette/purchase_window.rb
new file mode 100644
index 000000000..334493015
--- /dev/null
+++ b/app/models/chouette/purchase_window.rb
@@ -0,0 +1,45 @@
+require 'range_ext'
+require_relative '../calendar/period'
+
+module Chouette
+ class PurchaseWindow < Chouette::TridentActiveRecord
+ # include ChecksumSupport
+ include ObjectidSupport
+ include PeriodSupport
+ include ChecksumSupport
+ extend Enumerize
+
+ enumerize :color, in: %w(#9B9B9B #FFA070 #C67300 #7F551B #41CCE3 #09B09C #3655D7 #6321A0 #E796C6 #DD2DAA)
+
+ has_paper_trail
+ belongs_to :referential
+ has_and_belongs_to_many :vehicle_journeys, :class_name => 'Chouette::VehicleJourney'
+
+ validates_presence_of :name, :referential
+
+ scope :contains_date, ->(date) { where('date ? <@ any (date_ranges)', date) }
+ scope :overlap_dates, ->(date_range) { where('daterange(?, ?) && any (date_ranges)', date_range.first, date_range.last + 1.day) }
+
+ def self.ransackable_scopes(auth_object = nil)
+ [:contains_date]
+ end
+
+ def self.colors_i18n
+ Hash[*color.values.map{|c| [I18n.t("enumerize.purchase_window.color.#{c[1..-1]}"), c]}.flatten]
+ end
+
+ def local_id
+ "IBOO-#{self.referential.id}-#{self.id}"
+ end
+
+ def checksum_attributes
+ attrs = ['name', 'color', 'referential_id']
+ ranges_attrs = date_ranges.map{|r| [r.first, r.last]}.flatten.sort
+ self.slice(*attrs).values + ranges_attrs
+ end
+
+ # def checksum_attributes
+ # end
+
+ end
+end
diff --git a/app/models/chouette/route.rb b/app/models/chouette/route.rb
index 5c0ad24a1..5cc5d8b0d 100644
--- a/app/models/chouette/route.rb
+++ b/app/models/chouette/route.rb
@@ -133,7 +133,7 @@ module Chouette
def checksum_attributes
values = self.slice(*['name', 'published_name', 'wayback']).values
values.tap do |attrs|
- attrs << self.stop_points.map{|sp| "#{sp.stop_area.user_objectid}#{sp.for_boarding}#{sp.for_alighting}" }.join
+ attrs << self.stop_points.sort_by(&:position).map{|sp| [sp.stop_area.user_objectid, sp.for_boarding, sp.for_alighting]}
attrs << self.routing_constraint_zones.map(&:checksum)
end
end
diff --git a/app/models/chouette/routing_constraint_zone.rb b/app/models/chouette/routing_constraint_zone.rb
index fcf47f154..58703598e 100644
--- a/app/models/chouette/routing_constraint_zone.rb
+++ b/app/models/chouette/routing_constraint_zone.rb
@@ -7,7 +7,7 @@ module Chouette
belongs_to :route
has_array_of :stop_points, class_name: 'Chouette::StopPoint'
- validates_presence_of :name, :stop_points, :route
+ validates_presence_of :name, :stop_points, :route_id
# validates :stop_point_ids, length: { minimum: 2, too_short: I18n.t('activerecord.errors.models.routing_constraint_zone.attributes.stop_points.not_enough_stop_points') }
validate :stop_points_belong_to_route, :not_all_stop_points_selected
@@ -25,14 +25,20 @@ module Chouette
end
def checksum_attributes
- self.stop_points.map(&:stop_area).map(&:user_objectid)
+ [
+ self.stop_points.map(&:stop_area).map(&:user_objectid)
+ ]
end
def stop_points_belong_to_route
+ return unless route
+
errors.add(:stop_point_ids, I18n.t('activerecord.errors.models.routing_constraint_zone.attributes.stop_points.stop_points_not_from_route')) unless stop_points.all? { |sp| route.stop_points.include? sp }
end
def not_all_stop_points_selected
+ return unless route
+
errors.add(:stop_point_ids, I18n.t('activerecord.errors.models.routing_constraint_zone.attributes.stop_points.all_stop_points_selected')) if stop_points.length == route.stop_points.length
end
diff --git a/app/models/chouette/stop_area.rb b/app/models/chouette/stop_area.rb
index cc7170728..bb8747faa 100644
--- a/app/models/chouette/stop_area.rb
+++ b/app/models/chouette/stop_area.rb
@@ -2,14 +2,17 @@ require 'geokit'
require 'geo_ruby'
module Chouette
class StopArea < Chouette::ActiveRecord
- has_paper_trail
+ has_paper_trail class_name: 'PublicVersion'
include ProjectionFields
include StopAreaRestrictions
include StopAreaReferentialSupport
include ObjectidSupport
extend Enumerize
- enumerize :area_type, in: %i(zdep zder zdlp zdlr lda)
+ enumerize :area_type, in: Chouette::AreaType::ALL
+ enumerize :kind, in: %i(commercial non_commercial)
+
+ AVAILABLE_LOCALIZATIONS = %i(gb nl de fr it es)
with_options dependent: :destroy do |assoc|
assoc.has_many :stop_points
@@ -31,6 +34,7 @@ module Chouette
validates_format_of :registration_number, :with => %r{\A[\d\w_\-]+\Z}, :allow_blank => true
validates_presence_of :name
+ validates_presence_of :kind
validates_presence_of :latitude, :if => :longitude
validates_presence_of :longitude, :if => :latitude
validates_numericality_of :latitude, :less_than_or_equal_to => 90, :greater_than_or_equal_to => -90, :allow_nil => true
@@ -39,11 +43,36 @@ module Chouette
validates_format_of :coordinates, :with => %r{\A *-?(0?[0-9](\.[0-9]*)?|[0-8][0-9](\.[0-9]*)?|90(\.[0]*)?) *\, *-?(0?[0-9]?[0-9](\.[0-9]*)?|1[0-7][0-9](\.[0-9]*)?|180(\.[0]*)?) *\Z}, :allow_nil => true, :allow_blank => true
validates_format_of :url, :with => %r{\Ahttps?:\/\/([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?\Z}, :allow_nil => true, :allow_blank => true
+ validates_numericality_of :waiting_time, greater_than_or_equal_to: 0, only_integer: true, if: :waiting_time
+ validate :parent_area_type_must_be_greater
+ validate :area_type_of_right_kind
+
def self.nullable_attributes
[:registration_number, :street_name, :country_code, :fare_code,
:nearest_topic_name, :comment, :long_lat_type, :zip_code, :city_name, :url, :time_zone]
end
+ def localized_names
+ val = read_attribute(:localized_names) || {}
+ Hash[*AVAILABLE_LOCALIZATIONS.map{|k| [k, val[k.to_s]]}.flatten]
+ end
+
+ def parent_area_type_must_be_greater
+ return unless self.parent
+
+ parent_area_type = Chouette::AreaType.find(self.parent.area_type)
+ if Chouette::AreaType.find(self.area_type) >= parent_area_type
+ errors.add(:parent_id, I18n.t('stop_areas.errors.parent_area_type', area_type: parent_area_type.label))
+ end
+ end
+
+ def area_type_of_right_kind
+ return unless self.kind
+ unless Chouette::AreaType.send(self.kind).map(&:to_s).include?(self.area_type)
+ errors.add(:area_type, I18n.t('stop_areas.errors.incorrect_kind_area_type'))
+ end
+ end
+
after_update :clean_invalid_access_links
before_save :coordinates_to_lat_lng
@@ -72,6 +101,10 @@ module Chouette
end
end
+ def full_name
+ "#{name} #{zip_code} #{city_name} - #{user_objectid}"
+ end
+
def user_objectid
if objectid =~ /^.*:([0-9A-Za-z_-]+):STIF$/
$1
@@ -80,6 +113,8 @@ module Chouette
end
end
+ alias_method :local_id, :user_objectid
+
def children_in_depth
return [] if self.children.empty?
@@ -196,10 +231,12 @@ module Chouette
GeoRuby::SimpleFeatures::Envelope.from_coordinates coordinates
end
+ # DEPRECATED use StopArea#area_type
def stop_area_type
area_type ? area_type : " "
end
+ # DEPRECATED use StopArea#area_type
def stop_area_type=(stop_area_type)
self.area_type = (stop_area_type ? stop_area_type.camelcase : nil)
end
@@ -324,5 +361,41 @@ module Chouette
end
end
+ def activated?
+ deleted_at.nil?
+ end
+
+ def deactivated?
+ !activated?
+ end
+
+ def activate!
+ update_attribute :deleted_at, nil
+ end
+
+ def deactivate!
+ update_attribute :deleted_at, Time.now
+ end
+
+ def time_zone_offset
+ return 0 unless time_zone.present?
+ ActiveSupport::TimeZone[time_zone]&.utc_offset
+ end
+
+ def country_name
+ return unless country_code
+
+ country = ISO3166::Country[country_code]
+ country.translations[I18n.locale.to_s] || country.name
+ end
+
+ def time_zone_formatted_offset
+ return nil unless time_zone.present?
+ ActiveSupport::TimeZone[time_zone]&.formatted_offset
+ end
+
+ def commercial?
+ kind == "commercial"
+ end
end
end
diff --git a/app/models/chouette/time_table.rb b/app/models/chouette/time_table.rb
index 74c20f061..15b22b671 100644
--- a/app/models/chouette/time_table.rb
+++ b/app/models/chouette/time_table.rb
@@ -4,11 +4,13 @@ module Chouette
include ChecksumSupport
include TimeTableRestrictions
include ObjectidSupport
+ include ApplicationDaysSupport
+ include TimetableSupport
+
# FIXME http://jira.codehaus.org/browse/JRUBY-6358
self.primary_key = "id"
acts_as_taggable
- attr_accessor :monday,:tuesday,:wednesday,:thursday,:friday,:saturday,:sunday
attr_accessor :tag_search
def self.ransackable_attributes auth_object = nil
@@ -40,11 +42,18 @@ module Chouette
def checksum_attributes
[].tap do |attrs|
attrs << self.int_day_types
- attrs << self.dates.map(&:checksum).map(&:to_s).sort
- attrs << self.periods.map(&:checksum).map(&:to_s).sort
+ dates = self.dates
+ dates += TimeTableDate.where(time_table_id: self.id)
+ attrs << dates.map(&:checksum).map(&:to_s).sort
+ periods = self.periods
+ periods += TimeTablePeriod.where(time_table_id: self.id)
+ attrs << periods.map(&:checksum).map(&:to_s).sort
end
end
+ has_checksum_children TimeTableDate
+ has_checksum_children TimeTablePeriod
+
def self.object_id_key
"Timetable"
end
@@ -81,72 +90,36 @@ module Chouette
end
end
- def state_update state
- update_attributes(self.class.state_permited_attributes(state))
- self.tag_list = state['tags'].collect{|t| t['name']}.join(', ')
- self.calendar_id = nil unless state['calendar']
-
- days = state['day_types'].split(',')
- Date::DAYNAMES.map(&:underscore).each do |name|
- prefix = human_attribute_name(name).first(2)
- send("#{name}=", days.include?(prefix))
- end
-
- saved_dates = Hash[self.dates.collect{ |d| [d.id, d.date]}]
- cmonth = Date.parse(state['current_periode_range'])
-
- state['current_month'].each do |d|
- date = Date.parse(d['date'])
- checked = d['include_date'] || d['excluded_date']
- in_out = d['include_date'] ? true : false
-
- date_id = saved_dates.key(date)
- time_table_date = self.dates.find(date_id) if date_id
+ def find_date_by_id id
+ self.dates.find id
+ end
- next if !checked && !time_table_date
- # Destroy date if no longer checked
- next if !checked && time_table_date.destroy
+ def destroy_date date
+ date.destroy
+ end
- # Create new date
- unless time_table_date
- time_table_date = self.dates.create({in_out: in_out, date: date})
- end
- # Update in_out
- if in_out != time_table_date.in_out
- time_table_date.update_attributes({in_out: in_out})
- end
+ def update_in_out date, in_out
+ if in_out != date.in_out
+ date.update_attributes({in_out: in_out})
end
-
- self.state_update_periods state['time_table_periods']
- self.save
end
- def state_update_periods state_periods
- state_periods.each do |item|
- period = self.periods.find(item['id']) if item['id']
- next if period && item['deleted'] && period.destroy
- period ||= self.periods.build
-
- period.period_start = Date.parse(item['period_start'])
- period.period_end = Date.parse(item['period_end'])
+ def find_period_by_id id
+ self.periods.find id
+ end
- if period.changed?
- period.save
- item['id'] = period.id
- end
- end
+ def build_period
+ periods.build
+ end
- state_periods.delete_if {|item| item['deleted']}
+ def destroy_period period
+ period.destroy
end
def self.state_permited_attributes item
item.slice('comment', 'color').to_hash
end
- def presenter
- @presenter ||= ::TimeTablePresenter.new( self)
- end
-
def self.start_validity_period
[Chouette::TimeTable.minimum(:start_date)].compact.min
end
@@ -167,20 +140,6 @@ module Chouette
self.save
end
- def month_inspect(date)
- (date.beginning_of_month..date.end_of_month).map do |d|
- {
- day: I18n.l(d, format: '%A'),
- date: d.to_s,
- wday: d.wday,
- wnumber: d.strftime("%W").to_s,
- mday: d.mday,
- include_date: include_in_dates?(d),
- excluded_date: excluded_date?(d)
- }
- end
- end
-
def save_shortcuts
shortcuts_update
self.update_column(:start_date, start_date)
@@ -311,102 +270,9 @@ module Chouette
bounding_max = periods_max_date if periods_max_date &&
(bounding_max.nil? || (bounding_max < periods_max_date))
end
-
[bounding_min, bounding_max].compact
end
- def display_day_types
- %w(monday tuesday wednesday thursday friday saturday sunday).select{ |d| self.send(d) }.map{ |d| self.human_attribute_name(d).first(2)}.join(', ')
- end
-
- def day_by_mask(flag)
- int_day_types & flag == flag
- end
-
- def self.day_by_mask(int_day_types,flag)
- int_day_types & flag == flag
- end
-
-
- def valid_days
- # Build an array with day of calendar week (1-7, Monday is 1).
- [].tap do |valid_days|
- valid_days << 1 if monday
- valid_days << 2 if tuesday
- valid_days << 3 if wednesday
- valid_days << 4 if thursday
- valid_days << 5 if friday
- valid_days << 6 if saturday
- valid_days << 7 if sunday
- end
- end
-
- def self.valid_days(int_day_types)
- # Build an array with day of calendar week (1-7, Monday is 1).
- [].tap do |valid_days|
- valid_days << 1 if day_by_mask(int_day_types,4)
- valid_days << 2 if day_by_mask(int_day_types,8)
- valid_days << 3 if day_by_mask(int_day_types,16)
- valid_days << 4 if day_by_mask(int_day_types,32)
- valid_days << 5 if day_by_mask(int_day_types,64)
- valid_days << 6 if day_by_mask(int_day_types,128)
- valid_days << 7 if day_by_mask(int_day_types,256)
- end
- end
-
- def monday
- day_by_mask(4)
- end
- def tuesday
- day_by_mask(8)
- end
- def wednesday
- day_by_mask(16)
- end
- def thursday
- day_by_mask(32)
- end
- def friday
- day_by_mask(64)
- end
- def saturday
- day_by_mask(128)
- end
- def sunday
- day_by_mask(256)
- end
-
- def set_day(day,flag)
- if day == '1' || day == true
- self.int_day_types |= flag
- else
- self.int_day_types &= ~flag
- end
- shortcuts_update
- end
-
- def monday=(day)
- set_day(day,4)
- end
- def tuesday=(day)
- set_day(day,8)
- end
- def wednesday=(day)
- set_day(day,16)
- end
- def thursday=(day)
- set_day(day,32)
- end
- def friday=(day)
- set_day(day,64)
- end
- def saturday=(day)
- set_day(day,128)
- end
- def sunday=(day)
- set_day(day,256)
- end
-
def effective_days_of_period(period,valid_days=self.valid_days)
days = []
period.period_start.upto(period.period_end) do |date|
@@ -453,6 +319,17 @@ module Chouette
days.sort
end
+ def create_date in_out:, date:
+ self.dates.create in_out: in_out, date: date
+ end
+
+ def saved_dates
+ Hash[self.dates.collect{ |d| [d.id, d.date]}]
+ end
+
+ def all_dates
+ dates
+ end
# produce a copy of periods without anyone overlapping or including another
def optimize_overlapping_periods
@@ -569,5 +446,56 @@ module Chouette
tt.comment = I18n.t("activerecord.copy", :name => self.comment)
tt
end
+
+ def intersect_periods!(mask_periods)
+ dates.each do |date|
+ unless mask_periods.any? { |p| p.include? date.date }
+ dates.delete date
+ end
+ end
+
+ periods.each do |period|
+ mask_periods_with_common_part = mask_periods.select { |p| p.intersect? period.range }
+
+ if mask_periods_with_common_part.empty?
+ self.periods.delete period
+ else
+ mask_periods_with_common_part.each do |mask_period|
+ intersection = (mask_period & period.range)
+ period.period_start, period.period_end = intersection.begin, intersection.end
+ end
+ end
+ end
+ end
+
+ def remove_periods!(removed_periods)
+ dates.each do |date|
+ if removed_periods.any? { |p| p.include? date.date }
+ dates.delete date
+ end
+ end
+
+ periods.each do |period|
+ modified_ranges = removed_periods.inject([period.range]) do |period_ranges, removed_period|
+ period_ranges.map { |p| p.remove removed_period }.flatten
+ end
+
+ unless modified_ranges.empty?
+ modified_ranges.each_with_index do |modified_range, index|
+ new_period = index == 0 ? period : periods.build
+
+ new_period.period_start, new_period.period_end =
+ modified_range.min, modified_range.max
+ end
+ else
+ periods.delete period
+ end
+ end
+ end
+
+ def empty?
+ dates.empty? && periods.empty?
+ end
+
end
end
diff --git a/app/models/chouette/time_table_period.rb b/app/models/chouette/time_table_period.rb
index ab3e79d7e..d9b707675 100644
--- a/app/models/chouette/time_table_period.rb
+++ b/app/models/chouette/time_table_period.rb
@@ -42,5 +42,10 @@ module Chouette
def contains?(p)
(p.period_start >= self.period_start && p.period_end <= self.period_end)
end
+
+ def range
+ period_start..period_end
+ end
+
end
-end \ No newline at end of file
+end
diff --git a/app/models/chouette/vehicle_journey.rb b/app/models/chouette/vehicle_journey.rb
index 247c30668..4a6ba3f75 100644
--- a/app/models/chouette/vehicle_journey.rb
+++ b/app/models/chouette/vehicle_journey.rb
@@ -1,3 +1,4 @@
+# coding: utf-8
module Chouette
class VehicleJourney < Chouette::TridentActiveRecord
has_paper_trail
@@ -21,8 +22,10 @@ module Chouette
belongs_to :company
belongs_to :route
belongs_to :journey_pattern
+ has_many :stop_areas, through: :journey_pattern
has_and_belongs_to_many :footnotes, :class_name => 'Chouette::Footnote'
+ has_and_belongs_to_many :purchase_windows, :class_name => 'Chouette::PurchaseWindow'
validates_presence_of :route
validates_presence_of :journey_pattern
@@ -40,6 +43,39 @@ module Chouette
before_validation :set_default_values,
:calculate_vehicle_journey_at_stop_day_offset
+ scope :with_stop_area_ids, ->(ids){
+ _ids = ids.select(&:present?).map(&:to_i)
+ if _ids.present?
+ where("array(SELECT stop_points.stop_area_id::integer FROM stop_points INNER JOIN journey_patterns_stop_points ON journey_patterns_stop_points.stop_point_id = stop_points.id WHERE journey_patterns_stop_points.journey_pattern_id = vehicle_journeys.journey_pattern_id) @> array[?]", _ids)
+ else
+ all
+ end
+ }
+
+ scope :in_purchase_window, ->(range){
+ purchase_windows = Chouette::PurchaseWindow.overlap_dates(range)
+ sql = purchase_windows.joins(:vehicle_journeys).select('vehicle_journeys.id').uniq.to_sql
+ where("id IN (#{sql})")
+ }
+
+ # We need this for the ransack object in the filters
+ ransacker :purchase_window_date_gt
+ ransacker :stop_area_ids
+
+ # returns VehicleJourneys with at least 1 day in their time_tables
+ # included in the given range
+ def self.with_matching_timetable date_range
+ out = []
+ time_tables = Chouette::TimeTable.where(id: self.joins("INNER JOIN time_tables_vehicle_journeys ON vehicle_journeys.id = time_tables_vehicle_journeys.vehicle_journey_id").pluck('time_tables_vehicle_journeys.time_table_id')).overlapping(date_range)
+ time_tables = time_tables.select do |time_table|
+ range = date_range
+ range = date_range & (time_table.start_date-1.day..time_table.end_date+1.day) || [] if time_table.start_date.present? && time_table.end_date.present?
+ range.any?{|d| time_table.include_day?(d) }
+ end
+ out += time_tables.map{|t| t.vehicle_journey_ids}.flatten
+ where(id: out)
+ end
+
# TODO: Remove this validator
# We've eliminated this validation because it prevented vehicle journeys
# from being saved with at-stops having a day offset greater than 0,
@@ -68,10 +104,14 @@ module Chouette
attrs << self.published_journey_identifier
attrs << self.try(:company).try(:get_objectid).try(:local_id)
attrs << self.footnotes.map(&:checksum).sort
- attrs << self.vehicle_journey_at_stops.map(&:checksum).sort
+ vjas = self.vehicle_journey_at_stops
+ vjas += VehicleJourneyAtStop.where(vehicle_journey_id: self.id)
+ attrs << vjas.uniq.sort_by { |s| s.stop_point&.position }.map(&:checksum).sort
end
end
+ has_checksum_children VehicleJourneyAtStop
+
def set_default_values
if number.nil?
self.number = 0
@@ -117,10 +157,14 @@ module Chouette
def update_vjas_from_state state
state.each do |vjas|
next if vjas["dummy"]
+ stop_point = Chouette::StopPoint.find_by(objectid: vjas['stop_point_objectid'])
+ stop_area = stop_point&.stop_area
+ tz = stop_area&.time_zone
+ tz = tz && ActiveSupport::TimeZone[tz]
params = {}.tap do |el|
['arrival_time', 'departure_time'].each do |field|
time = "#{vjas[field]['hour']}:#{vjas[field]['minute']}"
- el[field.to_sym] = Time.parse("2000-01-01 #{time}:00 UTC")
+ el[field.to_sym] = Time.parse("2000-01-01 #{time}:00 #{tz&.formatted_offset || "UTC"}")
end
end
stop = create_or_find_vjas_from_state(vjas)
@@ -139,7 +183,7 @@ module Chouette
end
def update_has_and_belongs_to_many_from_state item
- ['time_tables', 'footnotes'].each do |assos|
+ ['time_tables', 'footnotes', 'purchase_windows'].each do |assos|
saved = self.send(assos).map(&:id)
(saved - item[assos].map{|t| t['id']}).each do |id|
@@ -168,7 +212,8 @@ module Chouette
vj.update_attributes(state_permited_attributes(item))
vj.update_has_and_belongs_to_many_from_state(item)
- item['errors'] = vj.errors.full_messages.uniq if vj.errors.any?
+ item['errors'] = vj.errors.full_messages.uniq if vj.errors.any?
+ item['checksum'] = vj.checksum
end
# Delete ids of new object from state if we had to rollback
@@ -192,16 +237,33 @@ module Chouette
def self.state_create_instance route, item
# Flag new record, so we can unset object_id if transaction rollback
vj = route.vehicle_journeys.create(state_permited_attributes(item))
- item['objectid'] = vj.objectid
+ vj.after_commit_objectid
+ item['objectid'] = vj.objectid
+ item['short_id'] = vj.get_objectid.short_id
item['new_record'] = true
vj
end
def self.state_permited_attributes item
- attrs = item.slice('published_journey_identifier', 'published_journey_name', 'journey_pattern_id', 'company_id').to_hash
- ['company', 'journey_pattern'].map do |association|
- attrs["#{association}_id"] = item[association]['id'] if item[association]
+ attrs = item.slice(
+ 'published_journey_identifier',
+ 'published_journey_name',
+ 'journey_pattern_id',
+ 'company_id'
+ ).to_hash
+
+ if item['journey_pattern']
+ attrs['journey_pattern_id'] = item['journey_pattern']['id']
end
+
+ attrs['company_id'] = item['company'] ? item['company']['id'] : nil
+
+ attrs["custom_field_values"] = Hash[
+ *(item["custom_fields"] || {})
+ .map { |k, v| [k, v["value"]] }
+ .flatten
+ ]
+
attrs
end
@@ -240,12 +302,25 @@ module Chouette
end
end
+ def self.custom_fields
+ CustomField.where(resource_type: self.name.split("::").last)
+ end
+
+
+ def custom_fields
+ Hash[*self.class.custom_fields.map do |v|
+ [v.code, v.slice(:code, :name, :field_type, :options).update(value: custom_field_value(v.code))]
+ end.flatten]
+ end
+
+ def custom_field_value key
+ (custom_field_values || {})[key.to_s]
+ end
+
def self.matrix(vehicle_journeys)
- {}.tap do |hash|
- vehicle_journeys.map{ |vj|
- vj.vehicle_journey_at_stops.map{ |vjas |hash[ "#{vj.id}-#{vjas.stop_point_id}"] = vjas }
- }
- end
+ Hash[*VehicleJourneyAtStop.where(vehicle_journey_id: vehicle_journeys.pluck(:id)).map do |vjas|
+ [ "#{vjas.vehicle_journey_id}-#{vjas.stop_point_id}", vjas]
+ end.flatten]
end
def self.with_stops
@@ -304,5 +379,10 @@ module Chouette
')
.where('"time_tables_vehicle_journeys"."vehicle_journey_id" IS NULL')
end
+
+ def self.lines
+ lines_query = joins(:route).select("routes.line_id").to_sql
+ Chouette::Line.where("id IN (#{lines_query})")
+ end
end
end
diff --git a/app/models/chouette/vehicle_journey_at_stop.rb b/app/models/chouette/vehicle_journey_at_stop.rb
index 6f0119e74..eda711ade 100644
--- a/app/models/chouette/vehicle_journey_at_stop.rb
+++ b/app/models/chouette/vehicle_journey_at_stop.rb
@@ -75,5 +75,42 @@ module Chouette
attrs << self.arrival_day_offset.to_s
end
end
+
+ def departure
+ format_time departure_time.utc
+ end
+
+ def arrival
+ format_time arrival_time.utc
+ end
+
+ def departure_local_time
+ local_time departure_time
+ end
+
+ def arrival_local_time
+ local_time arrival_time
+ end
+
+ def departure_local
+ format_time departure_local_time
+ end
+
+ def arrival_local
+ format_time arrival_local_time
+ end
+
+ private
+ def local_time time
+ return unless time
+ return time unless stop_point&.stop_area&.time_zone.present?
+ return time unless ActiveSupport::TimeZone[stop_point.stop_area.time_zone].present?
+ time + ActiveSupport::TimeZone[stop_point.stop_area.time_zone].utc_offset
+ end
+
+ def format_time time
+ time.strftime "%H:%M" if time
+ end
+
end
-end \ No newline at end of file
+end
diff --git a/app/models/compliance_check_block.rb b/app/models/compliance_check_block.rb
index 05240b428..059547e1b 100644
--- a/app/models/compliance_check_block.rb
+++ b/app/models/compliance_check_block.rb
@@ -6,8 +6,8 @@ class ComplianceCheckBlock < ActiveRecord::Base
has_many :compliance_checks
- hstore_accessor :condition_attributes,
- transport_mode: :string,
- transport_submode: :string
+ store_accessor :condition_attributes,
+ :transport_mode,
+ :transport_submode
end
diff --git a/app/models/compliance_check_message_export.rb b/app/models/compliance_check_message_export.rb
new file mode 100644
index 000000000..04e1a9caa
--- /dev/null
+++ b/app/models/compliance_check_message_export.rb
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+require "csv"
+require "zip"
+
+class ComplianceCheckMessageExport
+ include ActiveModel::Validations
+ include ActiveModel::Conversion
+ extend ActiveModel::Naming
+
+ attr_accessor :compliance_check_messages
+
+ def initialize(attributes = {})
+ attributes.each { |name, value| send("#{name}=", value) }
+ end
+
+ def persisted?
+ false
+ end
+
+ def label(name)
+ I18n.t "vehicle_journey_exports.label.#{name}"
+ end
+
+ def column_names
+ ["criticity", "message key", "resource objectid", "link", "message"]
+ end
+
+ def to_csv(options = {})
+ CSV.generate(options.slice(:col_sep, :quote_char, :force_quotes)) do |csv|
+ csv << column_names
+ compliance_check_messages.each do |compliance_check_message|
+ csv << [compliance_check_message.compliance_check.criticity, *compliance_check_message.message_attributes.values_at('test_id', 'source_objectid'), options[:server_url] + compliance_check_message.message_attributes['source_object_path'], I18n.t("compliance_check_messages.#{compliance_check_message.message_key}", compliance_check_message.message_attributes.deep_symbolize_keys)]
+ end
+ end
+ end
+
+ def to_zip(temp_file,options = {})
+ ::Zip::OutputStream.open(temp_file) { |zos| }
+ ::Zip::File.open(temp_file.path, ::Zip::File::CREATE) do |zipfile|
+ zipfile.get_output_stream(label("vj_filename")+route.id.to_s+".csv") { |f| f.puts to_csv(options) }
+ zipfile.get_output_stream(label("tt_filename")+".csv") { |f| f.puts time_tables_to_csv(options) }
+ zipfile.get_output_stream(label("ftn_filename")+".csv") { |f| f.puts footnotes_to_csv(options) }
+ end
+ end
+
+end
diff --git a/app/models/compliance_check_set.rb b/app/models/compliance_check_set.rb
index 020100f4a..289fc134f 100644
--- a/app/models/compliance_check_set.rb
+++ b/app/models/compliance_check_set.rb
@@ -1,6 +1,6 @@
class ComplianceCheckSet < ActiveRecord::Base
extend Enumerize
- has_paper_trail
+ has_paper_trail class_name: 'PublicVersion'
belongs_to :referential
belongs_to :compliance_control_set
@@ -19,6 +19,20 @@ class ComplianceCheckSet < ActiveRecord::Base
where('created_at BETWEEN :begin AND :end', begin: period_range.begin, end: period_range.end)
end
+ scope :blocked, -> { where('created_at < ? AND status = ?', 4.hours.ago, 'running') }
+
+ def self.finished_statuses
+ %w(successful failed warning aborted canceled)
+ end
+
+ def self.abort_old
+ where(
+ 'created_at < ? AND status NOT IN (?)',
+ 4.hours.ago,
+ finished_statuses
+ ).update_all(status: 'aborted')
+ end
+
def notify_parent
if parent
# parent.child_change
@@ -26,6 +40,14 @@ class ComplianceCheckSet < ActiveRecord::Base
end
end
+ def organisation
+ workbench.organisation
+ end
+
+ def human_attribute_name(*args)
+ self.class.human_attribute_name(*args)
+ end
+
def update_status
statuses = compliance_check_resources.map do |resource|
case resource.status
diff --git a/app/models/compliance_control.rb b/app/models/compliance_control.rb
index 65e22643d..298a63ab9 100644
--- a/app/models/compliance_control.rb
+++ b/app/models/compliance_control.rb
@@ -3,10 +3,8 @@ class ComplianceControl < ActiveRecord::Base
class << self
def criticities; %i(warning error) end
def default_code; "" end
- def prerequisite; I18n.t('compliance_controls.metas.no_prerequisite'); end
- def predicate; I18n.t("compliance_controls.#{self.name.underscore}.description") end
def dynamic_attributes
- hstore_metadata_for_control_attributes.keys
+ stored_attributes[:control_attributes] || []
end
def policy_class
@@ -39,7 +37,6 @@ class ComplianceControl < ActiveRecord::Base
belongs_to :compliance_control_block
enumerize :criticity, in: criticities, scope: true, default: :warning
- hstore_accessor :control_attributes, {}
validates :criticity, presence: true
validates :name, presence: true
@@ -66,6 +63,9 @@ def initialize(attributes = {})
self.origin_code ||= self.class.default_code
end
+def predicate; I18n.t("compliance_controls.#{self.class.name.underscore}.description") end
+def prerequisite; I18n.t('compliance_controls.metas.no_prerequisite'); end
+
end
# Ensure STI subclasses are loaded
diff --git a/app/models/compliance_control_block.rb b/app/models/compliance_control_block.rb
index e27f85ae0..d7d84fd06 100644
--- a/app/models/compliance_control_block.rb
+++ b/app/models/compliance_control_block.rb
@@ -5,9 +5,9 @@ class ComplianceControlBlock < ActiveRecord::Base
belongs_to :compliance_control_set
has_many :compliance_controls, dependent: :destroy
- hstore_accessor :condition_attributes,
- transport_mode: :string,
- transport_submode: :string
+ store_accessor :condition_attributes,
+ :transport_mode,
+ :transport_submode
validates :transport_mode, presence: true
validates :compliance_control_set, presence: true
diff --git a/app/models/compliance_control_set.rb b/app/models/compliance_control_set.rb
index 41076fefc..c0ea692f2 100644
--- a/app/models/compliance_control_set.rb
+++ b/app/models/compliance_control_set.rb
@@ -1,5 +1,5 @@
class ComplianceControlSet < ActiveRecord::Base
- has_paper_trail
+ has_paper_trail class_name: 'PublicVersion'
belongs_to :organisation
has_many :compliance_control_blocks, dependent: :destroy
has_many :compliance_controls, dependent: :destroy
diff --git a/app/models/concerns/application_days_support.rb b/app/models/concerns/application_days_support.rb
new file mode 100644
index 000000000..348436aa4
--- /dev/null
+++ b/app/models/concerns/application_days_support.rb
@@ -0,0 +1,107 @@
+module ApplicationDaysSupport
+ extend ActiveSupport::Concern
+
+ MONDAY = 4
+ TUESDAY = 8
+ WEDNESDAY = 16
+ THURSDAY = 32
+ FRIDAY = 64
+ SATURDAY = 128
+ SUNDAY = 256
+ EVERYDAY = MONDAY | TUESDAY | WEDNESDAY | THURSDAY | FRIDAY | SATURDAY | SUNDAY
+
+ def display_day_types
+ %w(monday tuesday wednesday thursday friday saturday sunday).select{ |d| self.send(d) }.map{ |d| self.human_attribute_name(d).first(2)}.join(', ')
+ end
+
+ def day_by_mask(flag)
+ int_day_types & flag == flag
+ end
+
+ def self.day_by_mask(int_day_types,flag)
+ int_day_types & flag == flag
+ end
+
+ def valid_days
+ # Build an array with day of calendar week (1-7, Monday is 1).
+ [].tap do |valid_days|
+ valid_days << 1 if monday
+ valid_days << 2 if tuesday
+ valid_days << 3 if wednesday
+ valid_days << 4 if thursday
+ valid_days << 5 if friday
+ valid_days << 6 if saturday
+ valid_days << 7 if sunday
+ end
+ end
+
+ def valid_day? wday
+ valid_days.include?(wday)
+ end
+
+ def self.valid_days(int_day_types)
+ # Build an array with day of calendar week (1-7, Monday is 1).
+ [].tap do |valid_days|
+ valid_days << 1 if day_by_mask(int_day_types,MONDAY)
+ valid_days << 2 if day_by_mask(int_day_types,TUESDAY)
+ valid_days << 3 if day_by_mask(int_day_types,WEDNESDAY)
+ valid_days << 4 if day_by_mask(int_day_types,THURSDAY)
+ valid_days << 5 if day_by_mask(int_day_types,FRIDAY)
+ valid_days << 6 if day_by_mask(int_day_types,SATURDAY)
+ valid_days << 7 if day_by_mask(int_day_types,SUNDAY)
+ end
+ end
+
+ def monday
+ day_by_mask(MONDAY)
+ end
+ def tuesday
+ day_by_mask(TUESDAY)
+ end
+ def wednesday
+ day_by_mask(WEDNESDAY)
+ end
+ def thursday
+ day_by_mask(THURSDAY)
+ end
+ def friday
+ day_by_mask(FRIDAY)
+ end
+ def saturday
+ day_by_mask(SATURDAY)
+ end
+ def sunday
+ day_by_mask(SUNDAY)
+ end
+
+ def set_day(day,flag)
+ if day == '1' || day == true
+ self.int_day_types |= flag
+ else
+ self.int_day_types &= ~flag
+ end
+ shortcuts_update
+ end
+
+ def monday=(day)
+ set_day(day,4)
+ end
+ def tuesday=(day)
+ set_day(day,8)
+ end
+ def wednesday=(day)
+ set_day(day,16)
+ end
+ def thursday=(day)
+ set_day(day,32)
+ end
+ def friday=(day)
+ set_day(day,64)
+ end
+ def saturday=(day)
+ set_day(day,128)
+ end
+ def sunday=(day)
+ set_day(day,256)
+ end
+end
diff --git a/app/models/concerns/checksum_support.rb b/app/models/concerns/checksum_support.rb
index c95e23bcf..92103798e 100644
--- a/app/models/concerns/checksum_support.rb
+++ b/app/models/concerns/checksum_support.rb
@@ -3,18 +3,50 @@ module ChecksumSupport
SEPARATOR = '|'
VALUE_FOR_NIL_ATTRIBUTE = '-'
- included do
+ included do |into|
before_save :set_current_checksum_source, :update_checksum
+ Referential.register_model_with_checksum self
+ into.extend ClassMethods
+ end
+
+ module ClassMethods
+ def has_checksum_children klass, opts={}
+ parent_class = self
+ relation = opts[:relation] || self.model_name.singular
+ klass.after_save do
+ parent = self.send(relation)
+ parent&.update_checksum_without_callbacks!
+ end
+ end
end
def checksum_attributes
self.attributes.values
end
+ def checksum_replace_nil_or_empty_values values
+ # Replace empty array by nil & nil by VALUE_FOR_NIL_ATTRIBUTE
+ values
+ .map { |x| x.present? && x || VALUE_FOR_NIL_ATTRIBUTE }
+ .map do |item|
+ item =
+ if item.kind_of?(Array)
+ checksum_replace_nil_or_empty_values(item)
+ else
+ item
+ end
+ end
+ end
+
def current_checksum_source
- source = self.checksum_attributes.map{ |x| x unless x.try(:empty?) }
- source = source.map{ |x| x || VALUE_FOR_NIL_ATTRIBUTE }
- source.map(&:to_s).join(SEPARATOR)
+ source = checksum_replace_nil_or_empty_values(self.checksum_attributes)
+ source.map{ |item|
+ if item.kind_of?(Array)
+ item.map{ |x| x.kind_of?(Array) ? "(#{x.join(',')})" : x }.join(',')
+ else
+ item
+ end
+ }.join(SEPARATOR)
end
def set_current_checksum_source
@@ -26,4 +58,20 @@ module ChecksumSupport
self.checksum = Digest::SHA256.new.hexdigest(self.checksum_source)
end
end
+
+ def update_checksum!
+ set_current_checksum_source
+ if checksum_source_changed?
+ update checksum: Digest::SHA256.new.hexdigest(checksum_source)
+ end
+ end
+
+ def update_checksum_without_callbacks!
+ set_current_checksum_source
+ _checksum = Digest::SHA256.new.hexdigest(checksum_source)
+ if _checksum != self.checksum
+ self.checksum = _checksum
+ self.class.where(id: self.id).update_all(checksum: _checksum) unless self.new_record?
+ end
+ end
end
diff --git a/app/models/concerns/date_support.rb b/app/models/concerns/date_support.rb
new file mode 100644
index 000000000..5c66cb1a9
--- /dev/null
+++ b/app/models/concerns/date_support.rb
@@ -0,0 +1,83 @@
+module DateSupport
+ extend ActiveSupport::Concern
+
+ included do
+ after_initialize :init_dates
+
+ def init_dates
+ self.dates ||= []
+ end
+
+ ### Calendar::DateValue
+ # Required by coocon
+ def build_date_value
+ Calendar::DateValue.new
+ end
+
+ def date_values
+ @date_values ||= init_date_values
+ end
+
+ def init_date_values
+ if dates
+ dates.each_with_index.map { |d, index| Calendar::DateValue.from_date(index, d) }
+ else
+ []
+ end
+ end
+ private :init_date_values
+
+ validate :validate_date_values
+
+ def validate_date_values
+ date_values_are_valid = date_values.all?(&:valid?)
+
+ date_values.each do |date_value|
+ if date_values.count { |d| d.value == date_value.value } > 1
+ date_value.errors.add(:base, I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_dates'))
+ date_values_are_valid = false
+ end
+ date_ranges.each do |date_range|
+ if date_range.cover?(date_value.value)
+ excluded_day = self.respond_to?(:valid_day?) && !self.valid_day?(date_value.value.wday)
+ unless excluded_day
+ date_value.errors.add(:base, I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_date_ranges'))
+ date_values_are_valid = false
+ end
+ end
+ end
+ end
+
+ unless date_values_are_valid
+ errors.add(:date_values, :invalid)
+ end
+ end
+
+ def date_values_attributes=(attributes = {})
+ @date_values = []
+ attributes.each do |index, date_value_attribute|
+ date_value_attribute['value'] = flatten_date_array(date_value_attribute, 'value')
+ date_value = Calendar::DateValue.new(date_value_attribute.merge(id: index))
+ @date_values << date_value unless date_value.marked_for_destruction?
+ end
+
+ dates_will_change!
+ end
+
+ before_validation :fill_dates
+
+ def fill_dates
+ if @date_values
+ self.dates = @date_values.map(&:value).compact.sort
+ end
+ end
+
+ after_save :clear_date_values
+
+ def clear_date_values
+ @date_values = nil
+ end
+
+ private :clear_date_values
+ end
+end
diff --git a/app/models/concerns/min_max_values_validation.rb b/app/models/concerns/min_max_values_validation.rb
index c177e55ca..eff779d81 100644
--- a/app/models/concerns/min_max_values_validation.rb
+++ b/app/models/concerns/min_max_values_validation.rb
@@ -2,12 +2,12 @@ module MinMaxValuesValidation
extend ActiveSupport::Concern
included do
+ validates_presence_of :minimum, :maximum
validate :min_max_values_validation
end
def min_max_values_validation
- return true unless minimum && maximum
- return true unless maximum < minimum
- errors.add(:min_max_values, I18n.t('compliance_controls.min_max_values', min: minimum, max: maximum))
+ return true if (minimum && maximum) && (minimum.to_i < maximum.to_i)
+ errors.add(:minimum, I18n.t('compliance_controls.min_max_values', min: minimum, max: maximum))
end
end
diff --git a/app/models/concerns/objectid_support.rb b/app/models/concerns/objectid_support.rb
index cec36678e..5d1f1a1c2 100644
--- a/app/models/concerns/objectid_support.rb
+++ b/app/models/concerns/objectid_support.rb
@@ -26,5 +26,10 @@ module ObjectidSupport
def objectid_class
get_objectid.try(:class)
end
+
+ def raw_objectid
+ read_attribute(:objectid)
+ end
+
end
end
diff --git a/app/models/concerns/period_support.rb b/app/models/concerns/period_support.rb
new file mode 100644
index 000000000..e17451fe4
--- /dev/null
+++ b/app/models/concerns/period_support.rb
@@ -0,0 +1,80 @@
+module PeriodSupport
+ extend ActiveSupport::Concern
+
+ included do
+ after_initialize :init_date_ranges
+
+ def init_date_ranges
+ self.date_ranges ||= []
+ end
+
+ ### Calendar::Period
+ # Required by coocon
+ def build_period
+ Calendar::Period.new
+ end
+
+ def periods
+ @periods ||= init_periods
+ end
+
+ def init_periods
+ (date_ranges || [])
+ .each_with_index
+ .map( &Calendar::Period.method(:from_range) )
+ end
+ private :init_periods
+
+ validate :validate_periods
+
+ def validate_periods
+ periods_are_valid = periods.all?(&:valid?)
+
+ periods.each do |period|
+ if period.intersect?(periods)
+ period.errors.add(:base, I18n.t('calendars.errors.overlapped_periods'))
+ periods_are_valid = false
+ end
+ end
+
+ unless periods_are_valid
+ errors.add(:periods, :invalid)
+ end
+ end
+
+ def flatten_date_array attributes, key
+ date_int = %w(1 2 3).map {|e| attributes["#{key}(#{e}i)"].to_i }
+ Date.new(*date_int)
+ end
+
+ def periods_attributes=(attributes = {})
+ @periods = []
+ attributes.each do |index, period_attribute|
+ # Convert date_select to date
+ ['begin', 'end'].map do |attr|
+ period_attribute[attr] = flatten_date_array(period_attribute, attr)
+ end
+ period = Calendar::Period.new(period_attribute.merge(id: index))
+ @periods << period unless period.marked_for_destruction?
+ end
+
+ date_ranges_will_change!
+ end
+
+ before_validation :fill_date_ranges
+
+ def fill_date_ranges
+ if @periods
+ self.date_ranges = @periods.map(&:range).compact.sort_by(&:begin)
+ end
+ end
+
+ after_save :clear_periods
+
+ def clear_periods
+ @periods = nil
+ end
+
+ private :clear_periods
+ end
+end
diff --git a/app/models/concerns/timetable_support.rb b/app/models/concerns/timetable_support.rb
new file mode 100644
index 000000000..5242abc33
--- /dev/null
+++ b/app/models/concerns/timetable_support.rb
@@ -0,0 +1,149 @@
+module TimetableSupport
+ extend ActiveSupport::Concern
+
+ def presenter
+ @presenter ||= ::TimeTablePresenter.new( self)
+ end
+
+ def periods_max_date
+ return nil if self.periods.empty?
+
+ min_start = self.periods.map(&:period_start).compact.min
+ max_end = self.periods.map(&:period_end).compact.max
+ result = nil
+
+ if max_end && min_start
+ max_end.downto( min_start) do |date|
+ if self.valid_days.include?(date.cwday) && !self.excluded_date?(date)
+ result = date
+ break
+ end
+ end
+ end
+ result
+ end
+
+ def periods_min_date
+ return nil if self.periods.empty?
+
+ min_start = self.periods.map(&:period_start).compact.min
+ max_end = self.periods.map(&:period_end).compact.max
+ result = nil
+
+ if max_end && min_start
+ min_start.upto(max_end) do |date|
+ if self.valid_days.include?(date.cwday) && !self.excluded_date?(date)
+ result = date
+ break
+ end
+ end
+ end
+ result
+ end
+
+ def bounding_dates
+ bounding_min = self.all_dates.select{|d| d.in_out}.map(&:date).compact.min
+ bounding_max = self.all_dates.select{|d| d.in_out}.map(&:date).compact.max
+
+ unless self.periods.empty?
+ bounding_min = periods_min_date if periods_min_date &&
+ (bounding_min.nil? || (periods_min_date < bounding_min))
+
+ bounding_max = periods_max_date if periods_max_date &&
+ (bounding_max.nil? || (bounding_max < periods_max_date))
+ end
+
+ [bounding_min, bounding_max].compact
+ end
+
+ def month_inspect(date)
+ (date.beginning_of_month..date.end_of_month).map do |d|
+ {
+ day: I18n.l(d, format: '%A'),
+ date: d.to_s,
+ wday: d.wday,
+ wnumber: d.strftime("%W").to_s,
+ mday: d.mday,
+ include_date: include_in_dates?(d),
+ excluded_date: excluded_date?(d)
+ }
+ end
+ end
+
+ def include_in_dates?(day)
+ self.dates.any?{ |d| d.date === day && d.in_out == true }
+ end
+
+ def excluded_date?(day)
+ self.dates.any?{ |d| d.date === day && d.in_out == false }
+ end
+
+ def include_in_overlap_dates?(day)
+ return false if self.excluded_date?(day)
+
+ self.all_dates.any?{ |d| d.date === day} \
+ && self.periods.any?{ |period| period.period_start <= day && day <= period.period_end && valid_days.include?(day.cwday) }
+ end
+
+ def include_in_periods?(day)
+ self.periods.any?{ |period| period.period_start <= day &&
+ day <= period.period_end &&
+ valid_days.include?(day.cwday) &&
+ ! excluded_date?(day) }
+ end
+
+ def state_update_periods state_periods
+ state_periods.each do |item|
+ period = self.find_period_by_id(item['id']) if item['id']
+ next if period && item['deleted'] && self.destroy_period(period)
+ period ||= self.build_period
+
+ period.period_start = Date.parse(item['period_start'])
+ period.period_end = Date.parse(item['period_end'])
+
+ period.save if period.is_a?(ActiveRecord::Base) && period.changed?
+
+ item['id'] = period.id
+ end
+
+ state_periods.delete_if {|item| item['deleted']}
+ end
+
+ def state_update state
+ update_attributes(self.class.state_permited_attributes(state))
+ self.tag_list = state['tags'].collect{|t| t['name']}.join(', ') if state['tags']
+ self.calendar_id = nil if self.respond_to?(:calendar_id) && !state['calendar']
+
+ days = state['day_types'].split(',')
+ Date::DAYNAMES.map(&:underscore).each do |name|
+ prefix = human_attribute_name(name).first(2)
+ send("#{name}=", days.include?(prefix))
+ end
+
+ cmonth = Date.parse(state['current_periode_range'])
+
+ state['current_month'].each do |d|
+ date = Date.parse(d['date'])
+ checked = d['include_date'] || d['excluded_date']
+ in_out = d['include_date'] ? true : false
+
+ date_id = saved_dates.key(date)
+ time_table_date = self.find_date_by_id(date_id) if date_id
+
+ next if !checked && !time_table_date
+ # Destroy date if no longer checked
+ next if !checked && destroy_date(time_table_date)
+
+ # Create new date
+ unless time_table_date
+ time_table_date = self.create_date in_out: in_out, date: date
+ end
+ # Update in_out
+ self.update_in_out time_table_date, in_out
+ end
+
+ self.state_update_periods state['time_table_periods']
+ self.save
+ end
+
+end
diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb
new file mode 100644
index 000000000..774c8b0f6
--- /dev/null
+++ b/app/models/custom_field.rb
@@ -0,0 +1,9 @@
+class CustomField < ActiveRecord::Base
+
+ extend Enumerize
+ belongs_to :workgroup
+ enumerize :field_type, in: %i{list}
+
+ validates :name, uniqueness: {scope: [:resource_type, :workgroup_id]}
+ validates :code, uniqueness: {scope: [:resource_type, :workgroup_id], case_sensitive: false}
+end
diff --git a/app/models/generic_attribute_control/min_max.rb b/app/models/generic_attribute_control/min_max.rb
index ab6f546a7..18873b683 100644
--- a/app/models/generic_attribute_control/min_max.rb
+++ b/app/models/generic_attribute_control/min_max.rb
@@ -1,9 +1,9 @@
module GenericAttributeControl
class MinMax < ComplianceControl
- hstore_accessor :control_attributes, minimum: :integer, maximum: :integer, target: :string
+ store_accessor :control_attributes, :minimum, :maximum, :target
- validates :minimum, numericality: true, allow_nil: true
- validates :maximum, numericality: true, allow_nil: true
+ validates_numericality_of :minimum, allow_nil: true, greater_than_or_equal_to: 0
+ validates_numericality_of :maximum, allow_nil: true, greater_than_or_equal_to: 0
validates :target, presence: true
include MinMaxValuesValidation
diff --git a/app/models/generic_attribute_control/pattern.rb b/app/models/generic_attribute_control/pattern.rb
index 3a4a55d5c..7fc008e28 100644
--- a/app/models/generic_attribute_control/pattern.rb
+++ b/app/models/generic_attribute_control/pattern.rb
@@ -1,6 +1,6 @@
module GenericAttributeControl
class Pattern < ComplianceControl
- hstore_accessor :control_attributes, pattern: :string, target: :string
+ store_accessor :control_attributes, :pattern, :target
validates :target, presence: true
validates :pattern, presence: true
diff --git a/app/models/generic_attribute_control/uniqueness.rb b/app/models/generic_attribute_control/uniqueness.rb
index f707c944b..82b5c0892 100644
--- a/app/models/generic_attribute_control/uniqueness.rb
+++ b/app/models/generic_attribute_control/uniqueness.rb
@@ -1,6 +1,6 @@
module GenericAttributeControl
class Uniqueness < ComplianceControl
- hstore_accessor :control_attributes, target: :string
+ store_accessor :control_attributes, :target
validates :target, presence: true
diff --git a/app/models/import.rb b/app/models/import.rb
index 19e835986..29aadcd56 100644
--- a/app/models/import.rb
+++ b/app/models/import.rb
@@ -13,13 +13,14 @@ class Import < ActiveRecord::Base
where('started_at BETWEEN :begin AND :end', begin: period_range.begin, end: period_range.end)
end
+ scope :blocked, -> { where('created_at < ? AND status = ?', 4.hours.ago, 'running') }
+
extend Enumerize
enumerize :status, in: %w(new pending successful warning failed running aborted canceled), scope: true, default: :new
validates :name, presence: true
validates :file, presence: true
validates_presence_of :workbench, :creator
- validates_format_of :file, with: %r{\.zip\z}i, message: I18n.t('activerecord.errors.models.import.attributes.file.wrong_file_extension')
before_create :initialize_fields
@@ -43,6 +44,14 @@ class Import < ActiveRecord::Base
%w(successful failed warning aborted canceled)
end
+ def self.abort_old
+ where(
+ 'created_at < ? AND status NOT IN (?)',
+ 4.hours.ago,
+ finished_statuses
+ ).update_all(status: 'aborted')
+ end
+
def notify_parent
parent.child_change
update(notified_parent_at: DateTime.now)
diff --git a/app/models/import_message_export.rb b/app/models/import_message_export.rb
index 88d0f27e2..05f8a2cc7 100644
--- a/app/models/import_message_export.rb
+++ b/app/models/import_message_export.rb
@@ -22,14 +22,14 @@ class ImportMessageExport
end
def column_names
- ["criticity", "message key", "message"]
+ ["criticity", "message key", "message", "file name", "line", "column"]
end
def to_csv(options = {})
CSV.generate(options) do |csv|
csv << column_names
import_messages.each do |import_message|
- csv << [import_message.criticity, import_message.message_key, I18n.t("import_messages.compliance_check_messages.#{import_message.message_key}", import_message.message_attributes.deep_symbolize_keys) ]
+ csv << [import_message.criticity, import_message.message_key, I18n.t("import_messages.#{import_message.message_key}", import_message.message_attributes.deep_symbolize_keys), *import_message.resource_attributes.values_at("filename", "line_number", "column_number") ]
end
end
end
diff --git a/app/models/line_control/route.rb b/app/models/line_control/route.rb
index b4b2bd9d8..b6c1f3630 100644
--- a/app/models/line_control/route.rb
+++ b/app/models/line_control/route.rb
@@ -3,6 +3,6 @@ module LineControl
def self.default_code; "3-Line-1" end
- def self.prerequisite; I18n.t("compliance_controls.#{self.name.underscore}.prerequisite") end
+ def prerequisite; I18n.t("compliance_controls.#{self.class.name.underscore}.prerequisite") end
end
end
diff --git a/app/models/merge.rb b/app/models/merge.rb
new file mode 100644
index 000000000..62bf581d6
--- /dev/null
+++ b/app/models/merge.rb
@@ -0,0 +1,435 @@
+class Merge < ActiveRecord::Base
+ extend Enumerize
+
+ belongs_to :workbench
+ validates :workbench, presence: true
+
+ enumerize :status, in: %w[new pending successful failed running], default: :new
+
+ has_array_of :referentials, class_name: 'Referential'
+
+ delegate :output, to: :workbench
+
+ after_commit :merge, :on => :create
+
+ def merge
+ MergeWorker.perform_async(id)
+ end
+
+ def name
+ referentials.first(3).map { |r| r.name.truncate(10) }.join(',')
+ end
+
+ def full_names
+ referentials.map(&:name).to_sentence
+ end
+
+ attr_reader :new
+
+ def merge!
+ update started_at: Time.now, status: :running
+
+ prepare_new
+
+ referentials.each do |referential|
+ merge_referential referential
+ end
+
+ save_current
+ rescue => e
+ Rails.logger.error "Merge failed: #{e} #{e.backtrace.join("\n")}"
+ update status: :failed
+ raise e if Rails.env.test?
+ ensure
+ attributes = { ended_at: Time.now }
+ attributes[:status] = :successful if status == :running
+ update attributes
+ end
+
+ def prepare_new
+ new =
+ if workbench.output.current
+ Rails.logger.debug "Clone current output"
+ Referential.new_from(workbench.output.current, fixme_functional_scope).tap do |clone|
+ clone.inline_clone = true
+ end
+ else
+ Rails.logger.debug "Create a new output"
+ # 'empty' one
+ attributes = {
+ workbench: workbench,
+ organisation: workbench.organisation, # TODO could be workbench.organisation by default
+ }
+ workbench.output.referentials.new attributes
+ end
+
+ new.referential_suite = output
+ new.workbench = workbench
+ new.organisation = workbench.organisation
+ new.slug = "output_#{workbench.id}_#{created_at.to_i}"
+ new.name = I18n.t("merges.referential_name", date: I18n.l(created_at))
+
+ unless new.valid?
+ Rails.logger.error "New referential isn't valid : #{new.errors.inspect}"
+ end
+
+ new.save!
+
+ output.update new: new
+ @new = new
+ end
+
+ def merge_referential(referential)
+ Rails.logger.debug "Merge #{referential.slug}"
+
+ metadata_merger = MetadatasMerger.new new, referential
+ metadata_merger.merge
+
+ new.metadatas.delete metadata_merger.empty_metadatas
+
+ new.save!
+
+ line_periods = LinePeriods.from_metadatas(referential.metadatas)
+
+ new.switch do
+ line_periods.each do |line_id, periods|
+ Rails.logger.debug "Clean data for #{line_id} #{periods.inspect}"
+
+ new.lines.find(line_id).time_tables.find_each do |time_table|
+ time_table.remove_periods! periods
+ unless time_table.empty?
+ time_table.save!
+ else
+ time_table.destroy
+ end
+ end
+ end
+ end
+
+ # let's merge data :)
+
+ # Routes
+
+ # Always the same pattern :
+ # - load models from original Referential
+ # - load associated datas (children, checksum for associated models)
+ # - switch to new Referential
+ # - enumerate loaded models
+ # - skip model if its checksum exists "in the same line"
+ # - prepare attributes for a fresh model
+ # - remove all primary keys
+ # - compute an ObjectId (TODO)
+ # - process children models as nested attributes
+ # - associated other models (by line/checksum)
+ # - save! and next one
+
+ referential_routes = referential.switch do
+ referential.routes.all.to_a
+ end
+
+ referential_routes_checksums = Hash[referential_routes.map { |r| [ r.id, r.checksum ] }]
+
+ referential_stop_points = referential.switch do
+ referential.stop_points.all.to_a
+ end
+
+ referential_stop_points_by_route = referential_stop_points.group_by(&:route_id)
+
+ new.switch do
+ referential_routes.each do |route|
+ existing_route = new.routes.find_by line_id: route.line_id, checksum: route.checksum
+ unless existing_route
+ objectid = Chouette::Route.where(objectid: route.objectid).exists? ? nil : route.objectid
+ attributes = route.attributes.merge(
+ id: nil,
+ objectid: objectid,
+ # line_id is the same
+ # all other primary must be changed
+ opposite_route_id: nil #FIXME
+ )
+ new_route = new.routes.build attributes
+
+ route_stop_points = referential_stop_points_by_route[route.id]
+
+ # Stop Points
+ route_stop_points.each do |stop_point|
+ objectid = Chouette::StopPoint.where(objectid: stop_point.objectid).exists? ? nil : stop_point.objectid
+ attributes = stop_point.attributes.merge(
+ id: nil,
+ route_id: nil,
+ objectid: objectid,
+ )
+
+ new_route.stop_points.build attributes
+ end
+
+ new_route.save!
+
+ if new_route.checksum != route.checksum
+ raise "Checksum has changed: #{route.inspect} #{new_route.inspect}"
+ end
+ end
+ end
+ end
+
+ # JourneyPatterns
+
+ referential_journey_patterns, referential_journey_patterns_stop_areas_objectids = referential.switch do
+ journey_patterns = referential.journey_patterns.includes(stop_points: :stop_area)
+
+ journey_patterns_stop_areas_objectids = Hash[
+ journey_patterns.map do |journey_pattern|
+ [ journey_pattern.id, journey_pattern.stop_points.map(&:stop_area).map(&:raw_objectid)]
+ end
+ ]
+
+ [journey_patterns, journey_patterns_stop_areas_objectids]
+ end
+
+ referential_journey_patterns_checksums = Hash[referential_journey_patterns.map { |j| [ j.id, j.checksum ] }]
+
+ new.switch do
+ referential_journey_patterns.each do |journey_pattern|
+ # find parent route by checksum
+ # TODO add line_id for security
+ associated_route_checksum = referential_routes_checksums[journey_pattern.route_id]
+ existing_associated_route = new.routes.find_by checksum: associated_route_checksum
+
+ existing_journey_pattern = new.journey_patterns.find_by route_id: existing_associated_route.id, checksum: journey_pattern.checksum
+
+ unless existing_journey_pattern
+ objectid = Chouette::JourneyPattern.where(objectid: journey_pattern.objectid).exists? ? nil : journey_pattern.objectid
+ attributes = journey_pattern.attributes.merge(
+ id: nil,
+ objectid: objectid,
+
+ # all other primary must be changed
+ route_id: existing_associated_route.id,
+
+ departure_stop_point_id: nil, # FIXME
+ arrival_stop_point_id: nil
+ )
+
+ stop_areas_objectids = referential_journey_patterns_stop_areas_objectids[journey_pattern.id]
+
+ stop_points = existing_associated_route.stop_points.joins(:stop_area).where("stop_areas.objectid": stop_areas_objectids).order(:position)
+ if stop_points.count != stop_areas_objectids.count
+ raise "Can't find StopPoints for #{stop_areas_objectids} : #{stop_points.inspect} #{existing_associated_route.stop_points.inspect}"
+ end
+
+ attributes.merge!(stop_points: stop_points)
+
+ new_journey_pattern = new.journey_patterns.create! attributes
+ if new_journey_pattern.checksum != journey_pattern.checksum
+ raise "Checksum has changed for #{journey_pattern.inspect}: #{journey_pattern.checksum_source} #{new_journey_pattern.checksum_source} "
+ end
+ end
+ end
+ end
+
+ # Vehicle Journeys
+
+ referential_vehicle_journeys = referential.switch do
+ referential.vehicle_journeys.includes(:vehicle_journey_at_stops).all.to_a
+ end
+
+ new.switch do
+ referential_vehicle_journeys.each do |vehicle_journey|
+ # find parent journey pattern by checksum
+ # TODO add line_id for security
+ associated_journey_pattern_checksum = referential_journey_patterns_checksums[vehicle_journey.journey_pattern_id]
+ existing_associated_journey_pattern = new.journey_patterns.find_by checksum: associated_journey_pattern_checksum
+
+ existing_vehicle_journey = new.vehicle_journeys.find_by journey_pattern_id: existing_associated_journey_pattern.id, checksum: vehicle_journey.checksum
+
+ unless existing_vehicle_journey
+ objectid = Chouette::VehicleJourney.where(objectid: vehicle_journey.objectid).exists? ? nil : vehicle_journey.objectid
+ attributes = vehicle_journey.attributes.merge(
+ id: nil,
+ objectid: objectid,
+
+ # all other primary must be changed
+ route_id: existing_associated_journey_pattern.route_id,
+ journey_pattern_id: existing_associated_journey_pattern.id,
+ )
+ new_vehicle_journey = new.vehicle_journeys.build attributes
+
+ # Create VehicleJourneyAtStops
+
+ vehicle_journey.vehicle_journey_at_stops.each_with_index do |vehicle_journey_at_stop, index|
+ at_stop_attributes = vehicle_journey_at_stop.attributes.merge(
+ id: nil,
+ stop_point_id: existing_associated_journey_pattern.stop_points[index].id
+ )
+ new_vehicle_journey.vehicle_journey_at_stops.build at_stop_attributes
+ end
+
+ new_vehicle_journey.save!
+
+ if new_vehicle_journey.checksum != vehicle_journey.checksum
+ raise "Checksum has changed: #{vehicle_journey.checksum_source} #{new_vehicle_journey.checksum_source}"
+ end
+ end
+
+ end
+ end
+
+ # Time Tables
+
+ referential_time_tables_by_id, referential_time_tables_with_lines = referential.switch do
+ time_tables_by_id = Hash[referential.time_tables.includes(:dates, :periods).all.to_a.map { |t| [t.id, t] }]
+
+ time_tables_with_associated_lines =
+ referential.time_tables.joins(vehicle_journeys: {route: :line}).pluck("lines.id", :id, "vehicle_journeys.checksum")
+
+ # Because TimeTables will be modified according metadata periods
+ # we're loading timetables per line (line is associated to a period list)
+ #
+ # line_id: [ { time_table.id, vehicle_journey.checksum } ]
+ time_tables_by_lines = time_tables_with_associated_lines.inject(Hash.new { |h,k| h[k] = [] }) do |hash, row|
+ hash[row.shift] << {id: row.first, vehicle_journey_checksum: row.second}
+ hash
+ end
+
+ [ time_tables_by_id, time_tables_by_lines ]
+ end
+
+ new.switch do
+ referential_time_tables_with_lines.each do |line_id, time_tables_properties|
+ # Because TimeTables will be modified according metadata periods
+ # we're loading timetables per line (line is associated to a period list)
+ line = workbench.line_referential.lines.find(line_id)
+
+ time_tables_properties.each do |properties|
+ time_table = referential_time_tables_by_id[properties[:id]]
+
+ # we can't test if TimeTable already exist by checksum
+ # because checksum is modified by intersect_periods!
+
+ attributes = time_table.attributes.merge(
+ id: nil,
+ comment: "Ligne #{line.name} - #{time_table.comment}",
+ calendar_id: nil
+ )
+ candidate_time_table = new.time_tables.build attributes
+
+ time_table.dates.each do |date|
+ date_attributes = date.attributes.merge(
+ id: nil,
+ time_table_id: nil
+ )
+ candidate_time_table.dates.build date_attributes
+ end
+ time_table.periods.each do |period|
+ period_attributes = period.attributes.merge(
+ id: nil,
+ time_table_id: nil
+ )
+ candidate_time_table.periods.build period_attributes
+ end
+
+ candidate_time_table.intersect_periods! line_periods.periods(line_id)
+
+ # FIXME
+ candidate_time_table.set_current_checksum_source
+ candidate_time_table.update_checksum
+
+ # after intersect_periods!, the checksum is the expected one
+ # we can search an existing TimeTable
+
+ existing_time_table = line.time_tables.find_by checksum: candidate_time_table.checksum
+
+ unless existing_time_table
+ objectid = Chouette::TimeTable.where(objectid: time_table.objectid).exists? ? nil : time_table.objectid
+ candidate_time_table.objectid = objectid
+
+ candidate_time_table.save!
+
+ # Checksum is changed by #intersect_periods
+ # if new_time_table.checksum != time_table.checksum
+ # raise "Checksum has changed: #{time_table.checksum_source} #{new_time_table.checksum_source}"
+ # end
+
+ existing_time_table = candidate_time_table
+ end
+
+ # associate VehicleJourney
+
+ associated_vehicle_journey = line.vehicle_journeys.find_by!(checksum: properties[:vehicle_journey_checksum])
+ associated_vehicle_journey.time_tables << existing_time_table
+ end
+ end
+ end
+ end
+
+ def save_current
+ output.update current: new, new: nil
+ output.current.update referential_suite: output
+
+ referentials.update_all merged_at: created_at, archived_at: created_at
+ end
+
+ def fixme_functional_scope
+ if attribute = workbench.organisation.sso_attributes.try(:[], "functional_scope")
+ JSON.parse(attribute)
+ end
+ end
+
+ def child_change
+
+ end
+
+ class MetadatasMerger
+
+ attr_reader :merge_metadatas, :referential
+ def initialize(merge_referential, referential)
+ @merge_metadatas = merge_referential.metadatas
+ @referential = referential
+ end
+
+ delegate :metadatas, to: :referential, prefix: :referential
+
+ def merge
+ referential_metadatas.each do |metadata|
+ merge_one metadata
+ end
+ end
+
+ def merged_line_metadatas(line_id)
+ merge_metadatas.select do |m|
+ m.line_ids.include? line_id
+ end
+ end
+
+ def merge_one(metadata)
+ metadata.line_ids.each do |line_id|
+ line_metadatas = merged_line_metadatas(line_id)
+
+ metadata.periodes.each do |period|
+ line_metadatas.each do |m|
+ m.periodes = m.periodes.map do |existing_period|
+ existing_period.remove period
+ end.flatten
+ end
+
+ attributes = {
+ line_ids: [line_id],
+ periodes: [period],
+ referential_source_id: referential.id,
+ created_at: metadata.created_at # TODO check required dates
+ }
+
+ # line_metadatas should not contain conflicted metadatas
+ merge_metadatas << ReferentialMetadata.new(attributes)
+ end
+ end
+ end
+
+ def empty_metadatas
+ merge_metadatas.select { |m| m.periodes.empty? }
+ end
+
+
+ end
+
+end
diff --git a/app/models/organisation.rb b/app/models/organisation.rb
index f6fba2d67..da7d1fcf3 100644
--- a/app/models/organisation.rb
+++ b/app/models/organisation.rb
@@ -1,3 +1,4 @@
+# coding: utf-8
class Organisation < ActiveRecord::Base
include DataFormatEnumerations
@@ -18,36 +19,39 @@ class Organisation < ActiveRecord::Base
validates_presence_of :name
validates_uniqueness_of :code
- def self.portail_api_request
- conf = Rails.application.config.try(:stif_portail_api)
- raise 'Rails.application.config.stif_portail_api configuration is not defined' unless conf
+ class << self
- HTTPService.get_json_resource(
- host: conf[:url],
- path: '/api/v1/organizations',
- token: conf[:key])
- end
+ def portail_api_request
+ conf = Rails.application.config.try(:stif_portail_api)
+ raise 'Rails.application.config.stif_portail_api configuration is not defined' unless conf
+
+ HTTPService.get_json_resource(
+ host: conf[:url],
+ path: '/api/v1/organizations',
+ token: conf[:key])
+ end
- def self.sync_update code, name, scope
- org = Organisation.find_or_initialize_by(code: code)
- if scope
- org.sso_attributes ||= {}
- if org.sso_attributes['functional_scope'] != scope
- org.sso_attributes['functional_scope'] = scope
- # FIXME see #1941
- org.sso_attributes_will_change!
+ def sync_update code, name, scope
+ org = Organisation.find_or_initialize_by(code: code)
+ if scope
+ org.sso_attributes ||= {}
+ if org.sso_attributes['functional_scope'] != scope
+ org.sso_attributes['functional_scope'] = scope
+ # FIXME see #1941
+ org.sso_attributes_will_change!
+ end
end
+ org.name = name
+ org.synced_at = Time.now
+ org.save
+ org
end
- org.name = name
- org.synced_at = Time.now
- org.save
- org
- end
- def self.portail_sync
- self.portail_api_request.each do |el|
- org = self.sync_update el['code'], el['name'], el['functional_scope']
- puts "✓ Organisation #{org.name} has been updated" unless Rails.env.test?
+ def portail_sync
+ portail_api_request.each do |el|
+ org = self.sync_update el['code'], el['name'], el['functional_scope']
+ puts "✓ Organisation #{org.name} has been updated" unless Rails.env.test?
+ end
end
end
@@ -64,4 +68,16 @@ class Organisation < ActiveRecord::Base
raise ActiveRecord::RecordNotFound
end
+ def functional_scope
+ JSON.parse( (sso_attributes || {}).fetch('functional_scope', '[]') )
+ end
+
+ def lines_set
+ STIF::CodifligneLineId.lines_set_from_functional_scope( functional_scope )
+ end
+
+ def has_feature?(feature)
+ features && features.include?(feature.to_s)
+ end
+
end
diff --git a/app/models/public_version.rb b/app/models/public_version.rb
new file mode 100644
index 000000000..4dbf6ce27
--- /dev/null
+++ b/app/models/public_version.rb
@@ -0,0 +1,4 @@
+class PublicVersion < PaperTrail::Version
+ # custom behaviour, e.g:
+ self.table_name = :'public.versions'
+end
diff --git a/app/models/referential.rb b/app/models/referential.rb
index 851a33653..509e0412f 100644
--- a/app/models/referential.rb
+++ b/app/models/referential.rb
@@ -13,17 +13,17 @@ class Referential < ActiveRecord::Base
validates_uniqueness_of :slug
- validates_format_of :slug, :with => %r{\A[a-z][0-9a-z_]+\Z}
- validates_format_of :prefix, :with => %r{\A[0-9a-zA-Z_]+\Z}
- validates_format_of :upper_corner, :with => %r{\A-?[0-9]+\.?[0-9]*\,-?[0-9]+\.?[0-9]*\Z}
- validates_format_of :lower_corner, :with => %r{\A-?[0-9]+\.?[0-9]*\,-?[0-9]+\.?[0-9]*\Z}
+ validates_format_of :slug, with: %r{\A[a-z][0-9a-z_]+\Z}
+ validates_format_of :prefix, with: %r{\A[0-9a-zA-Z_]+\Z}
+ validates_format_of :upper_corner, with: %r{\A-?[0-9]+\.?[0-9]*\,-?[0-9]+\.?[0-9]*\Z}
+ validates_format_of :lower_corner, with: %r{\A-?[0-9]+\.?[0-9]*\,-?[0-9]+\.?[0-9]*\Z}
validate :slug_excluded_values
attr_accessor :upper_corner
attr_accessor :lower_corner
has_one :user
- has_many :api_keys, :class_name => 'Api::V1::ApiKey', :dependent => :destroy
+ has_many :api_keys, class_name: 'Api::V1::ApiKey', dependent: :destroy
belongs_to :organisation
validates_presence_of :organisation
@@ -61,6 +61,60 @@ class Referential < ActiveRecord::Base
scope :include_metadatas_lines, ->(line_ids) { where('referential_metadata.line_ids && ARRAY[?]::bigint[]', line_ids) }
scope :order_by_validity_period, ->(dir) { joins(:metadatas).order("unnest(periodes) #{dir}") }
scope :order_by_lines, ->(dir) { joins(:metadatas).group("referentials.id").order("sum(array_length(referential_metadata.line_ids,1)) #{dir}") }
+ scope :not_in_referential_suite, -> { where referential_suite_id: nil }
+ scope :blocked, -> { where('ready = ? AND created_at < ?', false, 4.hours.ago) }
+
+ def save_with_table_lock_timeout(options = {})
+ save_without_table_lock_timeout(options)
+ rescue ActiveRecord::StatementInvalid => e
+ if e.message.include?('PG::LockNotAvailable')
+ raise TableLockTimeoutError.new(e)
+ else
+ raise
+ end
+ end
+
+ alias_method_chain :save, :table_lock_timeout
+
+ if Rails.env.development?
+ def self.force_register_models_with_checksum
+ paths = Rails.application.paths['app/models'].to_a
+ Rails.application.railties.each do |tie|
+ next unless tie.respond_to? :paths
+ paths += tie.paths['app/models'].to_a
+ end
+
+ paths.each do |path|
+ next unless File.directory?(path)
+ Dir.chdir path do
+ Dir['**/*.rb'].each do |src|
+ next if src =~ /^concerns/
+ # thanks for inconsistent naming ...
+ if src == "route_control/zdl_stop_area.rb"
+ RouteControl::ZDLStopArea
+ next
+ end
+ Rails.logger.info "Loading #{src}"
+ begin
+ src[0..-4].classify.safe_constantize
+ rescue => e
+ Rails.logger.info "Failed: #{e.message}"
+ nil
+ end
+ end
+ end
+ end
+ end
+ end
+
+ def self.register_model_with_checksum klass
+ @_models_with_checksum ||= []
+ @_models_with_checksum << klass
+ end
+
+ def self.models_with_checksum
+ @_models_with_checksum || []
+ end
def lines
if metadatas.blank?
@@ -79,7 +133,7 @@ class Referential < ActiveRecord::Base
errors.add(:slug,I18n.t("referentials.errors.public_excluded"))
end
if slug == self.class.connection_config[:username]
- errors.add(:slug,I18n.t("referentials.errors.user_excluded", :user => slug))
+ errors.add(:slug,I18n.t("referentials.errors.user_excluded", user: slug))
end
end
end
@@ -92,8 +146,12 @@ class Referential < ActiveRecord::Base
self.class.human_attribute_name(*args)
end
- def stop_areas
- Chouette::StopArea.all
+ def full_name
+ if in_referential_suite?
+ name
+ else
+ "#{self.class.model_name.human.capitalize} #{name}"
+ end
end
def access_points
@@ -128,6 +186,26 @@ class Referential < ActiveRecord::Base
Chouette::RoutingConstraintZone.all
end
+ def purchase_windows
+ Chouette::PurchaseWindow.all
+ end
+
+ def routes
+ Chouette::Route.all
+ end
+
+ def journey_patterns
+ Chouette::JourneyPattern.all
+ end
+
+ def stop_points
+ Chouette::StopPoint.all
+ end
+
+ def compliance_check_sets
+ ComplianceCheckSet.all
+ end
+
before_validation :define_default_attributes
def define_default_attributes
@@ -135,14 +213,26 @@ class Referential < ActiveRecord::Base
self.objectid_format ||= workbench.objectid_format if workbench
end
- def switch
+ def switch(&block)
raise "Referential not created" if new_record?
- Apartment::Tenant.switch!(slug)
- self
+
+ unless block_given?
+ Rails.logger.debug "Referential switch to #{slug}"
+ Apartment::Tenant.switch! slug
+ self
+ else
+ result = nil
+ Apartment::Tenant.switch slug do
+ Rails.logger.debug "Referential switch to #{slug}"
+ result = yield
+ end
+ Rails.logger.debug "Referential back"
+ result
+ end
end
def self.new_from(from, functional_scope)
- Referential.new(
+ Referential.new(
name: I18n.t("activerecord.copy", name: from.name),
slug: "#{from.slug}_clone",
prefix: from.prefix,
@@ -192,15 +282,29 @@ class Referential < ActiveRecord::Base
projection_type || ""
end
- before_validation :assign_line_and_stop_area_referential, :on => :create, if: :workbench
- before_validation :assign_slug, :on => :create
- before_validation :assign_prefix, :on => :create
+ before_validation :assign_line_and_stop_area_referential, on: :create, if: :workbench
+ before_validation :assign_slug, on: :create
+ before_validation :assign_prefix, on: :create
+
+ # Lock the `referentials` table to prevent duplicate referentials from being
+ # created simultaneously in separate transactions. This must be the last hook
+ # to minimise the duration of the lock.
+ before_save :lock_table, on: [:create, :update]
+
before_create :create_schema
after_create :clone_schema, if: :created_from
before_destroy :destroy_schema
before_destroy :destroy_jobs
+ def referential_read_only?
+ in_referential_suite? || archived?
+ end
+
+ def in_referential_suite?
+ referential_suite_id.present?
+ end
+
def in_workbench?
workbench_id.present?
end
@@ -264,7 +368,7 @@ class Referential < ActiveRecord::Base
query = "select distinct(public.referential_metadata.referential_id) FROM public.referential_metadata, unnest(line_ids) line, LATERAL unnest(periodes) period
WHERE public.referential_metadata.referential_id
- IN (SELECT public.referentials.id FROM public.referentials WHERE referentials.workbench_id = #{workbench_id} and referentials.archived_at is null #{not_myself})
+ IN (SELECT public.referentials.id FROM public.referentials WHERE referentials.workbench_id = #{workbench_id} and referentials.archived_at is null and referentials.referential_suite_id is null #{not_myself})
AND line in (#{line_ids.join(',')}) and (#{periods_query});"
self.class.connection.select_values(query).map(&:to_i)
@@ -274,22 +378,45 @@ class Referential < ActiveRecord::Base
overlapped_referential_ids.present?
end
- validate :detect_overlapped_referentials
+ validate :detect_overlapped_referentials, unless: :in_referential_suite?
def detect_overlapped_referentials
self.class.where(id: overlapped_referential_ids).each do |referential|
+ Rails.logger.info "Referential #{referential.id} #{referential.metadatas.inspect} overlaps #{metadatas.inspect}"
errors.add :metadatas, I18n.t("referentials.errors.overlapped_referential", :referential => referential.name)
end
end
+
+ attr_accessor :inline_clone
def clone_schema
- ReferentialCloning.create(source_referential: created_from, target_referential: self)
+ cloning = ReferentialCloning.new source_referential: created_from, target_referential: self
+
+ if inline_clone
+ cloning.clone!
+ else
+ cloning.save!
+ end
end
def create_schema
unless created_from
- Apartment::Tenant.create slug
- Rails.logger.error( "Schema migrations count for Referential #{slug} " + Referential.connection.select_value("select count(*) from #{slug}.schema_migrations;").to_s )
+ report = Benchmark.measure do
+ Apartment::Tenant.create slug
+ end
+
+ check_migration_count(report)
+ end
+ end
+
+ def check_migration_count(report)
+ Rails.logger.info("Schema create benchmark: '#{slug}'\t#{report}")
+ Rails.logger.info("Schema migrations count for Referential #{slug}: #{migration_count || '-'}")
+ end
+
+ def migration_count
+ if self.class.connection.table_exists?("#{slug}.schema_migrations")
+ self.class.connection.select_value("select count(*) from #{slug}.schema_migrations;")
end
end
@@ -373,4 +500,25 @@ class Referential < ActiveRecord::Base
not metadatas_overlap?
end
+ def merged?
+ merged_at.present?
+ end
+
+ def self.not_merged
+ where merged_at: nil
+ end
+
+ def self.mergeable
+ ready.not_merged.not_in_referential_suite
+ end
+
+ private
+
+ def lock_table
+ # No explicit unlock is needed as it will be released at the end of the
+ # transaction.
+ ActiveRecord::Base.connection.execute(
+ 'LOCK public.referentials IN ACCESS EXCLUSIVE MODE'
+ )
+ end
end
diff --git a/app/models/referential_cloning.rb b/app/models/referential_cloning.rb
index 5bf283814..d4b74bd52 100644
--- a/app/models/referential_cloning.rb
+++ b/app/models/referential_cloning.rb
@@ -2,14 +2,33 @@ class ReferentialCloning < ActiveRecord::Base
include AASM
belongs_to :source_referential, class_name: 'Referential'
belongs_to :target_referential, class_name: 'Referential'
- after_commit :perform_clone, :on => :create
+ after_commit :clone, on: :create
- private
- def perform_clone
+ def clone
ReferentialCloningWorker.perform_async(id)
- # ReferentialCloningWorker.new.perform(id)
end
+ def clone_with_status!
+ run!
+ clone!
+ successful!
+ rescue Exception => e
+ Rails.logger.error "Clone failed : #{e}"
+ Rails.logger.error e.backtrace.join('\n')
+ failed!
+ end
+
+ def clone!
+ report = Benchmark.measure do
+ AF83::SchemaCloner
+ .new(source_referential.slug, target_referential.slug)
+ .clone_schema
+ end
+ target_referential.check_migration_count(report)
+ end
+
+ private
+
aasm column: :status do
state :new, :initial => true
state :pending
diff --git a/app/models/referential_suite.rb b/app/models/referential_suite.rb
index 93c2c3f36..4f825628c 100644
--- a/app/models/referential_suite.rb
+++ b/app/models/referential_suite.rb
@@ -1,7 +1,7 @@
class ReferentialSuite < ActiveRecord::Base
belongs_to :new, class_name: 'Referential'
validate def validate_consistent_new
- return true if new_id.nil?
+ return true if new_id.nil? || new.nil?
return true if new.referential_suite_id == id
errors.add(:inconsistent_new,
I18n.t('referential_suites.errors.inconsistent_new', name: new.name))
@@ -9,11 +9,11 @@ class ReferentialSuite < ActiveRecord::Base
belongs_to :current, class_name: 'Referential'
validate def validate_consistent_current
- return true if current_id.nil?
+ return true if current_id.nil? || current.nil?
return true if current.referential_suite_id == id
errors.add(:inconsistent_current,
I18n.t('referential_suites.errors.inconsistent_current', name: current.name))
end
- has_many :referentials
+ has_many :referentials, -> { order "created_at desc" }
end
diff --git a/app/models/route_control/opposite_route.rb b/app/models/route_control/opposite_route.rb
index d5616ca6f..e0e9572ce 100644
--- a/app/models/route_control/opposite_route.rb
+++ b/app/models/route_control/opposite_route.rb
@@ -4,6 +4,6 @@ module RouteControl
def self.default_code; "3-Route-2" end
- def self.prerequisite; I18n.t("compliance_controls.#{self.name.underscore}.prerequisite") end
+ def prerequisite; I18n.t("compliance_controls.#{self.class.name.underscore}.prerequisite") end
end
end
diff --git a/app/models/route_control/opposite_route_terminus.rb b/app/models/route_control/opposite_route_terminus.rb
index 24c557734..e70d2c702 100644
--- a/app/models/route_control/opposite_route_terminus.rb
+++ b/app/models/route_control/opposite_route_terminus.rb
@@ -3,6 +3,6 @@ module RouteControl
def self.default_code; "3-Route-5" end
- def self.prerequisite; I18n.t("compliance_controls.#{self.name.underscore}.prerequisite") end
+ def prerequisite; I18n.t("compliance_controls.#{self.class.name.underscore}.prerequisite") end
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 37d35209a..1342f60ed 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -36,7 +36,7 @@ class User < ActiveRecord::Base
self.name = extra[:full_name]
self.email = extra[:email]
self.organisation = Organisation.sync_update extra[:organisation_code], extra[:organisation_name], extra[:functional_scope]
- self.permissions = Stif::PermissionTranslator.translate(extra[:permissions])
+ self.permissions = Stif::PermissionTranslator.translate(extra[:permissions], self.organisation)
end
def self.portail_api_request
diff --git a/app/models/vehicle_journey_control/delta.rb b/app/models/vehicle_journey_control/delta.rb
index 1f3a4d492..737b7d78c 100644
--- a/app/models/vehicle_journey_control/delta.rb
+++ b/app/models/vehicle_journey_control/delta.rb
@@ -1,9 +1,10 @@
module VehicleJourneyControl
class Delta < ComplianceControl
- hstore_accessor :control_attributes, maximum: :integer
+ store_accessor :control_attributes, :maximum
- validates :maximum, numericality: true, allow_nil: true
+ validates_numericality_of :maximum, allow_nil: true, greater_than_or_equal_to: 0
+ validates_presence_of :maximum
def self.default_code; "3-VehicleJourney-3" end
end
diff --git a/app/models/vehicle_journey_control/speed.rb b/app/models/vehicle_journey_control/speed.rb
index be9f838e4..e5e331b50 100644
--- a/app/models/vehicle_journey_control/speed.rb
+++ b/app/models/vehicle_journey_control/speed.rb
@@ -1,9 +1,9 @@
module VehicleJourneyControl
class Speed < ComplianceControl
- hstore_accessor :control_attributes, minimum: :integer, maximum: :integer
+ store_accessor :control_attributes, :minimum, :maximum
- validates :minimum, numericality: true, allow_nil: true
- validates :maximum, numericality: true, allow_nil: true
+ validates_numericality_of :minimum, allow_nil: true, greater_than_or_equal_to: 0
+ validates_numericality_of :maximum, allow_nil: true, greater_than_or_equal_to: 0
include MinMaxValuesValidation
def self.default_code; "3-VehicleJourney-2" end
diff --git a/app/models/vehicle_journey_control/waiting_time.rb b/app/models/vehicle_journey_control/waiting_time.rb
index 68fccb5c1..89a18a5d9 100644
--- a/app/models/vehicle_journey_control/waiting_time.rb
+++ b/app/models/vehicle_journey_control/waiting_time.rb
@@ -1,8 +1,9 @@
module VehicleJourneyControl
class WaitingTime < ComplianceControl
- hstore_accessor :control_attributes, maximum: :integer
+ store_accessor :control_attributes, :maximum
- validates :maximum, numericality: true, allow_nil: true
+ validates_numericality_of :maximum, allow_nil: true, greater_than_or_equal_to: 0
+ validates_presence_of :maximum
def self.default_code; "3-VehicleJourney-1" end
end
diff --git a/app/models/workbench.rb b/app/models/workbench.rb
index e36589210..b80fa64ac 100644
--- a/app/models/workbench.rb
+++ b/app/models/workbench.rb
@@ -4,6 +4,7 @@ class Workbench < ActiveRecord::Base
belongs_to :line_referential
belongs_to :stop_area_referential
belongs_to :output, class_name: 'ReferentialSuite'
+ belongs_to :workgroup
has_many :lines, -> (workbench) { Stif::MyWorkbenchScopes.new(workbench).line_scope(self) }, through: :line_referential
has_many :networks, through: :line_referential
@@ -14,6 +15,7 @@ class Workbench < ActiveRecord::Base
has_many :workbench_imports
has_many :compliance_check_sets
has_many :compliance_control_sets
+ has_many :merges
validates :name, presence: true
validates :organisation, presence: true
@@ -29,7 +31,12 @@ class Workbench < ActiveRecord::Base
if line_ids.empty?
Referential.none
else
- Referential.joins(:metadatas).where(['referential_metadata.line_ids && ARRAY[?]::bigint[]', line_ids]).ready
+ workgroup
+ .referentials
+ .joins(:metadatas)
+ .where(['referential_metadata.line_ids && ARRAY[?]::bigint[]', line_ids])
+ .ready
+ .not_in_referential_suite
end
end
diff --git a/app/models/workgroup.rb b/app/models/workgroup.rb
new file mode 100644
index 000000000..3af20ae23
--- /dev/null
+++ b/app/models/workgroup.rb
@@ -0,0 +1,20 @@
+class Workgroup < ActiveRecord::Base
+ belongs_to :line_referential
+ belongs_to :stop_area_referential
+
+ has_many :workbenches
+ has_many :calendars
+ has_many :organisations, through: :workbenches
+ has_many :referentials, through: :workbenches
+
+ validates_uniqueness_of :name
+
+ validates_presence_of :line_referential_id
+ validates_presence_of :stop_area_referential_id
+
+ has_many :custom_fields
+
+ def custom_fields_definitions
+ Hash[*custom_fields.map{|cf| [cf.code, cf]}.flatten]
+ end
+end
diff --git a/app/policies/access_link_policy.rb b/app/policies/access_link_policy.rb
index 1f1147f60..f2ea7027f 100644
--- a/app/policies/access_link_policy.rb
+++ b/app/policies/access_link_policy.rb
@@ -6,14 +6,14 @@ class AccessLinkPolicy < ApplicationPolicy
end
def create?
- !archived? && organisation_match? && user.has_permission?('access_links.create')
+ !referential_read_only? && organisation_match? && user.has_permission?('access_links.create')
end
def update?
- !archived? && organisation_match? && user.has_permission?('access_links.update')
+ !referential_read_only? && organisation_match? && user.has_permission?('access_links.update')
end
def destroy?
- !archived? && organisation_match? && user.has_permission?('access_links.destroy')
+ !referential_read_only? && organisation_match? && user.has_permission?('access_links.destroy')
end
end
diff --git a/app/policies/access_point_policy.rb b/app/policies/access_point_policy.rb
index 41436e77c..4fa887b9e 100644
--- a/app/policies/access_point_policy.rb
+++ b/app/policies/access_point_policy.rb
@@ -6,14 +6,14 @@ class AccessPointPolicy < ApplicationPolicy
end
def create?
- !archived? && organisation_match? && user.has_permission?('access_points.create')
+ !referential_read_only? && organisation_match? && user.has_permission?('access_points.create')
end
def update?
- !archived? && organisation_match? && user.has_permission?('access_points.update')
+ !referential_read_only? && organisation_match? && user.has_permission?('access_points.update')
end
def destroy?
- !archived? && organisation_match? && user.has_permission?('access_points.destroy')
+ !referential_read_only? && organisation_match? && user.has_permission?('access_points.destroy')
end
end
diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb
index dbe4542e7..c44937c9e 100644
--- a/app/policies/application_policy.rb
+++ b/app/policies/application_policy.rb
@@ -81,6 +81,11 @@ class ApplicationPolicy
@is_archived = is_archived
end
+ def referential_read_only?
+ return @is_referential_read_only if instance_variable_defined?(:@is_referential_read_only)
+ @is_referential_read_only = is_referential_read_only
+ end
+
def organisation_match?
user.organisation_id == organisation_id
end
@@ -124,4 +129,13 @@ class ApplicationPolicy
current_referential.try(:archived_at)
end
end
+
+ def is_referential_read_only
+ !!case referential
+ when Referential
+ referential.referential_read_only?
+ else
+ current_referential.try(:referential_read_only?)
+ end
+ end
end
diff --git a/app/policies/calendar_policy.rb b/app/policies/calendar_policy.rb
index 074c41d8d..c2da8c924 100644
--- a/app/policies/calendar_policy.rb
+++ b/app/policies/calendar_policy.rb
@@ -5,18 +5,15 @@ class CalendarPolicy < ApplicationPolicy
end
end
- def create?
- !archived? && user.has_permission?('calendars.create')
- end
- def destroy?
- !archived? & organisation_match? && user.has_permission?('calendars.destroy')
- end
- def update?
- !archived? && organisation_match? && user.has_permission?('calendars.update')
+ def create?
+ user.has_permission?('calendars.create')
end
+ def destroy?; instance_permission("destroy") end
+ def update?; instance_permission("update") end
+ def share?; instance_permission("share") end
- def share?
- user.organisation.name == 'STIF' # FIXME
+ private
+ def instance_permission permission
+ organisation_match? && user.has_permission?("calendars.#{permission}")
end
-
end
diff --git a/app/policies/connection_link_policy.rb b/app/policies/connection_link_policy.rb
index 240c2a804..9bab5e4db 100644
--- a/app/policies/connection_link_policy.rb
+++ b/app/policies/connection_link_policy.rb
@@ -6,14 +6,14 @@ class ConnectionLinkPolicy < ApplicationPolicy
end
def create?
- !archived? && organisation_match? && user.has_permission?('connection_links.create')
+ !referential_read_only? && organisation_match? && user.has_permission?('connection_links.create')
end
def destroy?
- !archived? && organisation_match? && user.has_permission?('connection_links.destroy')
+ !referential_read_only? && organisation_match? && user.has_permission?('connection_links.destroy')
end
def update?
- !archived? && organisation_match? && user.has_permission?('connection_links.update')
+ !referential_read_only? && organisation_match? && user.has_permission?('connection_links.update')
end
end
diff --git a/app/policies/journey_pattern_policy.rb b/app/policies/journey_pattern_policy.rb
index 12bcced17..beb18d151 100644
--- a/app/policies/journey_pattern_policy.rb
+++ b/app/policies/journey_pattern_policy.rb
@@ -7,14 +7,14 @@ class JourneyPatternPolicy < ApplicationPolicy
end
def create?
- !archived? && organisation_match? && user.has_permission?('journey_patterns.create')
+ !referential_read_only? && organisation_match? && user.has_permission?('journey_patterns.create')
end
def destroy?
- !archived? && organisation_match? && user.has_permission?('journey_patterns.destroy')
+ !referential_read_only? && organisation_match? && user.has_permission?('journey_patterns.destroy')
end
def update?
- !archived? && organisation_match? && user.has_permission?('journey_patterns.update')
+ !referential_read_only? && organisation_match? && user.has_permission?('journey_patterns.update')
end
end
diff --git a/app/policies/line_policy.rb b/app/policies/line_policy.rb
index 67ea0b611..f7b03b0b5 100644
--- a/app/policies/line_policy.rb
+++ b/app/policies/line_policy.rb
@@ -6,7 +6,6 @@ class LinePolicy < ApplicationPolicy
end
def create?
- Rails.logger.debug "LinePolicy.create?"
user.has_permission?('lines.create')
end
@@ -14,20 +13,28 @@ class LinePolicy < ApplicationPolicy
user.has_permission?('lines.destroy')
end
+ def deactivate?
+ !record.deactivated? && user.has_permission?('lines.change_status')
+ end
+
+ def activate?
+ record.deactivated? && user.has_permission?('lines.change_status')
+ end
+
def update?
user.has_permission?('lines.update')
end
def create_footnote?
- !archived? && organisation_match? && user.has_permission?('footnotes.create')
+ !referential_read_only? && organisation_match? && user.has_permission?('footnotes.create')
end
def edit_footnote?
- !archived? && organisation_match? && user.has_permission?('footnotes.update')
+ !referential_read_only? && organisation_match? && user.has_permission?('footnotes.update')
end
def destroy_footnote?
- !archived? && organisation_match? && user.has_permission?('footnotes.destroy')
+ !referential_read_only? && organisation_match? && user.has_permission?('footnotes.destroy')
end
def update_footnote? ; edit_footnote? end
diff --git a/app/policies/line_referential_policy.rb b/app/policies/line_referential_policy.rb
new file mode 100644
index 000000000..ee742a083
--- /dev/null
+++ b/app/policies/line_referential_policy.rb
@@ -0,0 +1,14 @@
+class LineReferentialPolicy < ApplicationPolicy
+ class Scope < Scope
+ def resolve
+ scope
+ end
+ end
+
+ def synchronize?; instance_permission("synchronize") end
+
+ private
+ def instance_permission permission
+ user.has_permission?("line_referentials.#{permission}")
+ end
+end
diff --git a/app/policies/merge_policy.rb b/app/policies/merge_policy.rb
new file mode 100644
index 000000000..82eb72e08
--- /dev/null
+++ b/app/policies/merge_policy.rb
@@ -0,0 +1,15 @@
+class MergePolicy < ApplicationPolicy
+ class Scope < Scope
+ def resolve
+ scope
+ end
+ end
+
+ def create?
+ user.has_permission?('merges.create')
+ end
+
+ def update?
+ user.has_permission?('merges.update')
+ end
+end
diff --git a/app/policies/network_policy.rb b/app/policies/network_policy.rb
index 9f86451a5..9b871d2b5 100644
--- a/app/policies/network_policy.rb
+++ b/app/policies/network_policy.rb
@@ -4,4 +4,15 @@ class NetworkPolicy < ApplicationPolicy
scope
end
end
+ def create?
+ user.has_permission?('networks.create')
+ end
+
+ def destroy?
+ user.has_permission?('networks.destroy')
+ end
+
+ def update?
+ user.has_permission?('networks.update')
+ end
end
diff --git a/app/policies/purchase_window_policy.rb b/app/policies/purchase_window_policy.rb
new file mode 100644
index 000000000..eb3b04bf7
--- /dev/null
+++ b/app/policies/purchase_window_policy.rb
@@ -0,0 +1,20 @@
+class PurchaseWindowPolicy < ApplicationPolicy
+ class Scope < Scope
+ def resolve
+ scope
+ end
+ end
+
+ def create?
+ !referential_read_only? && organisation_match? && user.has_permission?('purchase_windows.create')
+ end
+
+ def update?
+ !referential_read_only? && organisation_match? && user.has_permission?('purchase_windows.update')
+ end
+
+ def destroy?
+ !referential_read_only? && organisation_match? && user.has_permission?('purchase_windows.destroy')
+ end
+
+end
diff --git a/app/policies/referential_policy.rb b/app/policies/referential_policy.rb
index 253917509..f5c2d7c08 100644
--- a/app/policies/referential_policy.rb
+++ b/app/policies/referential_policy.rb
@@ -10,27 +10,27 @@ class ReferentialPolicy < ApplicationPolicy
end
def destroy?
- !archived? && organisation_match? && user.has_permission?('referentials.destroy')
+ !referential_read_only? && organisation_match? && user.has_permission?('referentials.destroy')
end
def update?
- !archived? && organisation_match? && user.has_permission?('referentials.update')
+ !referential_read_only? && organisation_match? && user.has_permission?('referentials.update')
end
def clone?
- !archived? && create?
+ !record.in_referential_suite? && create?
end
def validate?
- !archived? && create? && organisation_match?
+ !referential_read_only? && create? && organisation_match?
end
def archive?
- record.archived_at.nil? && organisation_match? && user.has_permission?('referentials.update')
+ !referential_read_only? && record.archived_at.nil? && organisation_match? && user.has_permission?('referentials.update')
end
def unarchive?
- !record.archived_at.nil? && organisation_match? && user.has_permission?('referentials.update')
+ record.archived? && !record.merged? && organisation_match? && user.has_permission?('referentials.update')
end
def common_lines?
diff --git a/app/policies/route_policy.rb b/app/policies/route_policy.rb
index 7e9fe251a..0337a5300 100644
--- a/app/policies/route_policy.rb
+++ b/app/policies/route_policy.rb
@@ -6,15 +6,15 @@ class RoutePolicy < ApplicationPolicy
end
def create?
- !archived? && organisation_match? && user.has_permission?('routes.create')
+ !referential_read_only? && organisation_match? && user.has_permission?('routes.create')
end
def destroy?
- !archived? && organisation_match? && user.has_permission?('routes.destroy')
+ !referential_read_only? && organisation_match? && user.has_permission?('routes.destroy')
end
def update?
- !archived? && organisation_match? && user.has_permission?('routes.update')
+ !referential_read_only? && organisation_match? && user.has_permission?('routes.update')
end
def duplicate?
diff --git a/app/policies/routing_constraint_zone_policy.rb b/app/policies/routing_constraint_zone_policy.rb
index 3cfcf46ff..fd8081bef 100644
--- a/app/policies/routing_constraint_zone_policy.rb
+++ b/app/policies/routing_constraint_zone_policy.rb
@@ -6,14 +6,14 @@ class RoutingConstraintZonePolicy < ApplicationPolicy
end
def create?
- !archived? && organisation_match? && user.has_permission?('routing_constraint_zones.create')
+ !referential_read_only? && organisation_match? && user.has_permission?('routing_constraint_zones.create')
end
def destroy?
- !archived? && organisation_match? && user.has_permission?('routing_constraint_zones.destroy')
+ !referential_read_only? && organisation_match? && user.has_permission?('routing_constraint_zones.destroy')
end
def update?
- !archived? && organisation_match? && user.has_permission?('routing_constraint_zones.update')
+ !referential_read_only? && organisation_match? && user.has_permission?('routing_constraint_zones.update')
end
end
diff --git a/app/policies/stop_area_policy.rb b/app/policies/stop_area_policy.rb
index faeebbc2a..fd73b7092 100644
--- a/app/policies/stop_area_policy.rb
+++ b/app/policies/stop_area_policy.rb
@@ -1,5 +1,13 @@
class StopAreaPolicy < ApplicationPolicy
class Scope < Scope
+ def search_scope scope_name
+ scope = resolve
+ if scope_name&.to_s == "route_editor"
+ scope = scope.where("kind = ? OR area_type = ?", :non_commercial, 'zdep') unless user.organisation.has_feature?("route_stop_areas_all_types")
+ end
+ scope
+ end
+
def resolve
scope
end
@@ -16,4 +24,12 @@ class StopAreaPolicy < ApplicationPolicy
def update?
user.has_permission?('stop_areas.update')
end
+
+ def deactivate?
+ !record.deactivated? && user.has_permission?('stop_areas.change_status')
+ end
+
+ def activate?
+ record.deactivated? && user.has_permission?('stop_areas.change_status')
+ end
end
diff --git a/app/policies/stop_area_referential_policy.rb b/app/policies/stop_area_referential_policy.rb
new file mode 100644
index 000000000..e370babf8
--- /dev/null
+++ b/app/policies/stop_area_referential_policy.rb
@@ -0,0 +1,14 @@
+class StopAreaReferentialPolicy < ApplicationPolicy
+ class Scope < Scope
+ def resolve
+ scope
+ end
+ end
+
+ def synchronize?; instance_permission("synchronize") end
+
+ private
+ def instance_permission permission
+ user.has_permission?("stop_area_referentials.#{permission}")
+ end
+end
diff --git a/app/policies/time_table_combination_policy.rb b/app/policies/time_table_combination_policy.rb
index daa6808e4..bba458c18 100644
--- a/app/policies/time_table_combination_policy.rb
+++ b/app/policies/time_table_combination_policy.rb
@@ -7,6 +7,6 @@ class TimeTableCombinationPolicy < ApplicationPolicy
end
def create?
- !archived? && organisation_match? && user.has_permission?('time_tables.update')
+ !referential_read_only? && organisation_match? && user.has_permission?('time_tables.update')
end
end
diff --git a/app/policies/time_table_policy.rb b/app/policies/time_table_policy.rb
index 92d3aef3e..390c170c7 100644
--- a/app/policies/time_table_policy.rb
+++ b/app/policies/time_table_policy.rb
@@ -7,23 +7,23 @@ class TimeTablePolicy < ApplicationPolicy
end
def create?
- !archived? && organisation_match? && user.has_permission?('time_tables.create')
+ !referential_read_only? && organisation_match? && user.has_permission?('time_tables.create')
end
def destroy?
- !archived? && organisation_match? && user.has_permission?('time_tables.destroy')
+ !referential_read_only? && organisation_match? && user.has_permission?('time_tables.destroy')
end
def update?
- !archived? && organisation_match? && user.has_permission?('time_tables.update')
+ !referential_read_only? && organisation_match? && user.has_permission?('time_tables.update')
end
def actualize?
- !archived? && organisation_match? && edit?
+ !referential_read_only? && organisation_match? && edit?
end
def duplicate?
- !archived? && organisation_match? && create?
+ !referential_read_only? && organisation_match? && create?
end
def month?
diff --git a/app/policies/vehicle_journey_policy.rb b/app/policies/vehicle_journey_policy.rb
index 24040455f..adbc5fd89 100644
--- a/app/policies/vehicle_journey_policy.rb
+++ b/app/policies/vehicle_journey_policy.rb
@@ -6,14 +6,14 @@ class VehicleJourneyPolicy < ApplicationPolicy
end
def create?
- !archived? && organisation_match? && user.has_permission?('vehicle_journeys.create')
+ !referential_read_only? && organisation_match? && user.has_permission?('vehicle_journeys.create')
end
def destroy?
- !archived? && organisation_match? && user.has_permission?('vehicle_journeys.destroy')
+ !referential_read_only? && organisation_match? && user.has_permission?('vehicle_journeys.destroy')
end
def update?
- !archived? && organisation_match? && user.has_permission?('vehicle_journeys.update')
+ !referential_read_only? && organisation_match? && user.has_permission?('vehicle_journeys.update')
end
end
diff --git a/app/services/parent_import_notifier.rb b/app/services/parent_import_notifier.rb
deleted file mode 100644
index 47e6755e4..000000000
--- a/app/services/parent_import_notifier.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-class ParentImportNotifier
- def self.notify_when_finished(imports = nil)
- imports ||= imports_pending_notification
- imports.each(&:notify_parent)
- end
-
- def self.imports_pending_notification
- Import
- .where(
- notified_parent_at: nil,
- status: Import.finished_statuses
- )
- .where.not(parent: nil)
- end
-end
diff --git a/app/services/parent_notifier.rb b/app/services/parent_notifier.rb
new file mode 100644
index 000000000..653c98aff
--- /dev/null
+++ b/app/services/parent_notifier.rb
@@ -0,0 +1,19 @@
+class ParentNotifier
+ def initialize(klass)
+ @klass = klass
+ end
+
+ def notify_when_finished(collection = nil)
+ collection ||= objects_pending_notification
+ collection.each(&:notify_parent)
+ end
+
+ def objects_pending_notification
+ @klass
+ .where(
+ notified_parent_at: nil,
+ status: @klass.finished_statuses
+ )
+ .where.not(parent: nil)
+ end
+end
diff --git a/app/services/referential_overview.rb b/app/services/referential_overview.rb
new file mode 100644
index 000000000..ccfe0617a
--- /dev/null
+++ b/app/services/referential_overview.rb
@@ -0,0 +1,256 @@
+class ReferentialOverview
+ attr_reader :h
+ attr_reader :referential
+
+ PER_PAGE = 10
+
+ def initialize referential, h=nil
+ @referential = referential
+ @page = h && h.params[pagination_param_name]&.to_i || 1
+ @h = h
+ end
+
+ def lines
+ filtered_lines.includes(:company).map{|l| Line.new(l, @referential, period.first, h)}
+ end
+
+ def period
+ @period ||= @referential.metadatas_period || []
+ end
+
+ def includes_today?
+ period.include? Time.now.to_date
+ end
+
+ def weeks
+ @weeks = {}
+ period.map do |d|
+ @weeks[Week.key(d)] ||= Week.new(d, period.last, h)
+ end
+ @weeks.values
+ end
+
+ def referential_lines
+ @referential.metadatas_lines
+ end
+
+ def filtered_lines
+ search.result.page(@page).per_page(PER_PAGE)
+ end
+
+ ### Pagination
+
+ delegate :empty?, :first, :total_pages, :size, :total_entries, :offset, :length, to: :filtered_lines
+ def current_page
+ @page
+ end
+
+ ### search
+ def search
+ lines = referential_lines
+ lines = lines.search h.params[search_param_name]
+ lines
+ end
+
+ def pagination_param_name
+ "referential_#{@referential.slug}_overview"
+ end
+
+ def search_param_name
+ "q_#{pagination_param_name}"
+ end
+
+ class Line
+ attr_reader :h
+ attr_reader :referential_line
+
+ delegate :name, :number, :company, :color, :transport_mode, to: :referential_line
+
+ def initialize line, referential, start, h
+ @referential_line = line
+ @referential = referential
+ @start = start
+ @h = h
+ end
+
+ def period
+ @period ||= @referential.metadatas_period || []
+ end
+
+ def referential_periods
+ @referential_periods ||= @referential.metadatas.include_lines([@referential_line.id]).map(&:periodes).flatten.sort{|p1, p2| p1.first <=> p2.first}
+ end
+
+ def periods
+ @periods ||= begin
+ periods = referential_periods.flatten.map{|p| Period.new p, @start, h}
+ periods = fill_periods periods
+ periods = merge_periods periods
+ periods
+ end
+ end
+
+ def fill_periods periods
+ [].tap do |out|
+ previous = OpenStruct.new(end: period.first - 1.day)
+ (periods + [OpenStruct.new(start: period.last + 1.day)]).each do |p|
+ if p.start > previous.end + 1.day
+ out << Period.new((previous.end+1.day..p.start-1.day), @start, h).tap{|p| p.empty = true}
+ end
+ out << p if p.respond_to?(:end)
+ previous = p
+ end
+ end
+ end
+
+ def merge_periods periods
+ [].tap do |out|
+ current = periods.first
+ periods[1..-1].each do |p|
+ if p.start <= current.end
+ current.end = p.end
+ else
+ out << current
+ current = p
+ end
+ end
+ out << current
+ end
+ end
+
+ def width
+ period.count * Day::WIDTH
+ end
+
+ def html_style
+ {
+ width: "#{width}px"
+ }.map{|k, v| "#{k}: #{v}"}.join("; ")
+ end
+
+ def html_class
+ out = []
+ out
+ end
+
+ class Period
+ attr_accessor :empty
+ attr_accessor :h
+
+ def initialize period, start, h
+ @period = period
+ @start = start
+ @empty = false
+ @h = h
+ end
+
+ def start
+ @period.first
+ end
+
+ def end
+ @period.last
+ end
+
+ def end= val
+ @period = (start..val)
+ end
+
+ def width
+ @period.count * Day::WIDTH
+ end
+
+ def left
+ (@period.first - @start).to_i * Day::WIDTH
+ end
+
+ def html_style
+ {
+ width: "#{width}px",
+ left: "#{left}px",
+ }.map{|k, v| "#{k}: #{v}"}.join("; ")
+ end
+
+ def empty?
+ @empty
+ end
+
+ def accepted?
+ @period.count < 7
+ end
+
+ def title
+ h.l(self.start, format: :short) + " - " + h.l(self.end, format: :short)
+ end
+
+ def html_class
+ out = []
+ out << "empty" if empty?
+ out << "accepted" if accepted?
+ out
+ end
+ end
+ end
+
+ class Week
+ attr_reader :h
+ attr_reader :start_date
+ attr_reader :end_date
+
+ def initialize start_date, boundary, h
+ @start_date = start_date.to_date
+ @end_date = [start_date.end_of_week, boundary].min.to_date
+ @h = h
+ end
+
+ def self.key date
+ date.beginning_of_week.to_s
+ end
+
+ def span
+ h.l(@start_date, format: "#{@start_date.day}-#{@end_date.day} %b")
+ end
+
+ def number
+ h.l(@start_date, format: "%W")
+ end
+
+ def period
+ (@start_date..@end_date)
+ end
+
+ def days
+ period.map {|d| Day.new d, h }
+ end
+ end
+
+ class Day
+ attr_reader :h
+
+ WIDTH=50
+
+ def initialize date, h
+ @date = date
+ @h = h
+ end
+
+ def html_style
+ {width: "#{WIDTH}px"}.map{|k, v| "#{k}: #{v}"}.join("; ")
+ end
+
+ def html_class
+ out = [h.l(@date, format: "%Y-%m-%d")]
+ out << "weekend" if [0, 6].include?(@date.wday)
+ out << "today" if @date == Time.now.to_date
+ out
+ end
+
+ def short_name
+ h.l(@date, format: "%a")
+ end
+
+ def number
+ @date.day
+ end
+ end
+end
diff --git a/app/services/zip_service.rb b/app/services/zip_service.rb
index 7a4bdad1b..7166e6448 100644
--- a/app/services/zip_service.rb
+++ b/app/services/zip_service.rb
@@ -1,12 +1,16 @@
class ZipService
- class Subdir < Struct.new(:name, :stream, :spurious)
+ class Subdir < Struct.new(:name, :stream, :spurious, :foreign_lines)
+ def ok?
+ foreign_lines.empty? && spurious.empty?
+ end
end
- attr_reader :current_key, :current_output, :current_spurious, :yielder
+ attr_reader :allowed_lines, :current_key, :foreign_lines, :current_output, :current_spurious, :yielder
- def initialize data
+ def initialize data, allowed_lines
@zip_data = StringIO.new(data)
+ @allowed_lines = allowed_lines
@current_key = nil
@current_output = nil
end
@@ -35,7 +39,8 @@ class ZipService
end
def add_to_current_output entry
- return if is_spurious! entry.name
+ return if is_spurious!(entry.name) || is_foreign_line!(entry.name)
+
current_output.put_next_entry entry.name
write_to_current_output entry.get_input_stream
end
@@ -52,20 +57,22 @@ class ZipService
current_key,
# Second part of the solution, yield the closed stream
current_output.close_buffer,
- current_spurious)
+ current_spurious.to_a,
+ foreign_lines)
end
end
def open_new_output entry_key
@current_key = entry_key
# First piece of the solution, use internal way to create a Zip::OutputStream
- @current_output = Zip::OutputStream.new(StringIO.new(''), true, nil)
- @current_spurious = []
+ @current_output = Zip::OutputStream.new(StringIO.new(''), true, nil)
+ @current_spurious = Set.new
+ @foreign_lines = []
end
def entry_key entry
# last dir name File.dirname.split("/").last
- entry.name.split('/', -1)[-2]
+ entry.name.split('/').first
end
def is_spurious! entry_name
@@ -75,4 +82,12 @@ class ZipService
current_spurious << segments.second
return true
end
+
+ def is_foreign_line! entry_name
+ STIF::NetexFile::Frame.get_short_id(entry_name).tap do | line_object_id |
+ return nil unless line_object_id
+ return nil if line_object_id.in? allowed_lines
+ foreign_lines << line_object_id
+ end
+ end
end
diff --git a/app/uploaders/import_uploader.rb b/app/uploaders/import_uploader.rb
index 2740393ca..60e17ca0f 100644
--- a/app/uploaders/import_uploader.rb
+++ b/app/uploaders/import_uploader.rb
@@ -36,9 +36,9 @@ class ImportUploader < CarrierWave::Uploader::Base
# Add a white list of extensions which are allowed to be uploaded.
# For images you might use something like this:
- # def extension_whitelist
- # %w(jpg jpeg gif png)
- # end
+ def extension_whitelist
+ %w(zip)
+ end
# Override the filename of the uploaded files:
# Avoid using model.id or version_name here, see uploader/store.rb for details.
diff --git a/app/views/api/v1/journey_patterns/show.rabl b/app/views/api/v1/journey_patterns/show.rabl
index 3ea1bb6ff..aac66b6f3 100644
--- a/app/views/api/v1/journey_patterns/show.rabl
+++ b/app/views/api/v1/journey_patterns/show.rabl
@@ -1,20 +1,30 @@
object @journey_pattern
extends "api/v1/trident_objects/show"
-[:id, :name, :published_name, :registration_number, :comment].each do |attr|
+[:id, :name, :published_name, :registration_number, :comment, :checksum].each do |attr|
attributes attr, :unless => lambda { |m| m.send( attr).nil?}
end
+node :full_schedule do |journey_pattern|
+ journey_pattern.full_schedule?
+end
+
+if has_feature? :costs_in_journey_patterns
+ attribute :costs
+end
+
node(:route_short_description) do |journey_pattern|
partial("api/v1/routes/short_description", :object => journey_pattern.route)
end
+
node(:vehicle_journey_object_ids) do |journey_pattern|
- journey_pattern.vehicle_journeys.map(&:objectid)
+ journey_pattern.vehicle_journeys.pluck(:objectid)
end unless root_object.vehicle_journeys.empty?
child :stop_points => :stop_area_short_descriptions do |stop_points|
node do |stop_point|
+ cache stop_point.stop_area_id
partial("api/v1/stop_areas/short_description", :object => stop_point.stop_area)
end
end
diff --git a/app/views/autocomplete_purchase_windows/index.rabl b/app/views/autocomplete_purchase_windows/index.rabl
new file mode 100644
index 000000000..1d0287602
--- /dev/null
+++ b/app/views/autocomplete_purchase_windows/index.rabl
@@ -0,0 +1,12 @@
+collection @purchase_windows, :object_root => false
+
+node do |window|
+ {
+ :id => window.id,
+ :name => window.name,
+ :objectid => window.objectid,
+ :color => window.color,
+ :short_id => window.get_objectid.short_id,
+ :text => "<strong><span class='fa fa-circle' style='color:" + (window.color ? window.color : '#4b4b4b') + "'></span> " + window.name + " - " + window.get_objectid.short_id + "</strong>"
+ }
+end
diff --git a/app/views/autocomplete_stop_areas/around.rabl b/app/views/autocomplete_stop_areas/around.rabl
index bc8f06054..d067dc4d0 100644
--- a/app/views/autocomplete_stop_areas/around.rabl
+++ b/app/views/autocomplete_stop_areas/around.rabl
@@ -12,7 +12,7 @@ child @stop_areas, root: :features, object_root: false do
name: s.name,
short_name: truncate(s.name, :length => 30) || "",
city_name: s.city_name,
- area_type: s.area_type,
+ area_type: Chouette::AreaType.find(s.area_type).label,
registration_number: s.registration_number,
stoparea_id: s.id,
text: "#{s.name}, #{s.zip_code} #{s.city_name}",
diff --git a/app/views/autocomplete_stop_areas/index.rabl b/app/views/autocomplete_stop_areas/index.rabl
index 5a9f76a47..c92b708f4 100644
--- a/app/views/autocomplete_stop_areas/index.rabl
+++ b/app/views/autocomplete_stop_areas/index.rabl
@@ -13,8 +13,9 @@ node do |stop_area|
:user_objectid => stop_area.user_objectid,
:longitude => stop_area.longitude,
:latitude => stop_area.latitude,
- :area_type => stop_area.area_type,
- :comment => stop_area.comment
+ :area_type => Chouette::AreaType.find(stop_area.area_type).label,
+ :comment => stop_area.comment,
+ :text => stop_area.full_name
}
end
diff --git a/app/views/calendar_mailer/created.html.slim b/app/views/calendar_mailer/created.html.slim
index 37b2a86ea..bee071150 100644
--- a/app/views/calendar_mailer/created.html.slim
+++ b/app/views/calendar_mailer/created.html.slim
@@ -1,4 +1,4 @@
-div = t('mailers.calendar_mailer.created.body', cal_name: @calendar.name, cal_index_url: calendars_url)
+div = t('mailers.calendar_mailer.created.body', cal_name: @calendar.name, cal_index_url: workgroup_calendars_url(@calendar.workgroup))
table style="border-collapse:collapse;font-family:'Open Sans', Arial, sans serif;width:550px;margin:0px auto;color:#333333;"
@@ -16,7 +16,7 @@ table style="border-collapse:collapse;font-family:'Open Sans', Arial, sans serif
= t('mailers.calendar_mailer.updated.subject')
p style="font-size:14px;margin:0px 0px 10px 0px;"
- = t('mailers.calendar_mailer.created.body', cal_name: @calendar.name, cal_index_url: calendars_url).html_safe
+ = t('mailers.calendar_mailer.created.body', cal_name: @calendar.name, cal_index_url: workgroup_calendars_url(@calendar.workgroup)).html_safe
tr
td style="text-align:center;padding:20px 0 0 0;border-top:1px solid #007fbb;"
diff --git a/app/views/calendar_mailer/updated.html.slim b/app/views/calendar_mailer/updated.html.slim
index bf128439a..0bdc2e7db 100644
--- a/app/views/calendar_mailer/updated.html.slim
+++ b/app/views/calendar_mailer/updated.html.slim
@@ -14,7 +14,7 @@ table style="border-collapse:collapse;font-family:'Open Sans', Arial, sans serif
= t('mailers.calendar_mailer.updated.subject')
p style="font-size:14px;margin:0px 0px 10px 0px;"
- = t('mailers.calendar_mailer.updated.body', cal_name: @calendar.name, cal_index_url: calendars_url).html_safe
+ = t('mailers.calendar_mailer.updated.body', cal_name: @calendar.name, cal_index_url: workgroup_calendars_url(@calendar.workgroup)).html_safe
tr
td style="text-align:center;padding:20px 0 0 0;border-top:1px solid #007fbb;"
diff --git a/app/views/calendars/_filters.html.slim b/app/views/calendars/_filters.html.slim
index b5283c1e8..8bfe1974e 100644
--- a/app/views/calendars/_filters.html.slim
+++ b/app/views/calendars/_filters.html.slim
@@ -1,22 +1,22 @@
-= search_form_for @q, url: calendars_path, builder: SimpleForm::FormBuilder, html: { method: :get, class: 'form form-filter' } do |f|
+= search_form_for @q, url: workgroup_calendars_path(@workgroup), builder: SimpleForm::FormBuilder, html: { method: :get, class: 'form form-filter' } do |f|
.ffg-row
- .input-group.search_bar
+ .input-group.search_bar class=filter_item_class(params[:q], :name_or_short_name_cont)
= f.search_field :name_or_short_name_cont, class: 'form-control', placeholder: 'Indiquez un nom/nom court de calendrier...'
span.input-group-btn
button.btn.btn-default#search_btn type='submit'
span.fa.fa-search
.ffg-row
- .form-group.togglable
+ .form-group.togglable class=filter_item_class(params[:q], :shared_true)
= f.label Calendar.human_attribute_name(:shared), required: false, class: 'control-label'
.form-group.checkbox_list
= f.input :shared_true, as: :boolean, label: ("<span>Oui</span>").html_safe, wrapper_html: { class: 'checkbox-wrapper' }
= f.input :shared_false, as: :boolean, label: ("<span>Non</span>").html_safe, wrapper_html: { class: 'checkbox-wrapper' }
- .form-group
+ .form-group class=filter_item_class(params[:q], :contains_date)
= f.label Calendar.human_attribute_name(:date), class: 'control-label'
= f.input :contains_date, as: :date, label: false, wrapper_html: { class: 'date smart_date' }, class: 'form-control', include_blank: true
.actions
- = link_to 'Effacer', calendars_path, class: 'btn btn-link'
+ = link_to 'Effacer', workgroup_calendars_path(@workgroup), class: 'btn btn-link'
= f.submit 'Filtrer', id: 'calendar_filter_btn', class: 'btn btn-default'
diff --git a/app/views/calendars/_form.html.slim b/app/views/calendars/_form.html.slim
deleted file mode 100644
index 3c152c61d..000000000
--- a/app/views/calendars/_form.html.slim
+++ /dev/null
@@ -1,53 +0,0 @@
-= simple_form_for @calendar, html: { class: 'form-horizontal', id: 'calendar_form' }, wrapper: :horizontal_form do |f|
- .row
- .col-lg-12
- = f.input :name
- = f.input :short_name
-
- - if policy(@calendar).share?
- .form-group.has_switch
- = f.label :shared, class: 'col-sm-4 col-xs-5 control-label'
- = f.input :shared, as: :boolean, checked_value: true, unchecked_value: false, label: content_tag(:span, t("#{@calendar.shared}"), class: 'switch-label', data: {checkedValue: t('true'), uncheckedValue: t('false')}), wrapper_html: { class: 'col-sm-8 col-xs-7'}
-
- .separator
-
- .row
- .col-lg-12
- .subform
- .nested-head
- .wrapper
- div
- .form-group
- label.control-label
- = Calendar.human_attribute_name(:date)
- div
-
- = f.simple_fields_for :date_values do |date_value|
- = render 'date_value_fields', f: date_value
-
- .links.nested-linker
- = link_to_add_association t('simple_form.labels.calendar.add_a_date'), f, :date_values, class: 'btn btn-outline-primary'
-
- .separator
-
- .row
- .col-lg-12
- .subform
- .nested-head
- .wrapper
- div
- .form-group
- label.control-label
- = t('simple_form.labels.calendar.ranges.begin')
- div
- .form-group
- label.control-label
- = t('simple_form.labels.calendar.ranges.end')
- div
-
- = f.simple_fields_for :periods do |period|
- = render 'period_fields', f: period
- .links.nested-linker
- = link_to_add_association t('simple_form.labels.calendar.add_a_date_range'), f, :periods, class: 'btn btn-outline-primary'
-
- = f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'calendar_form'
diff --git a/app/views/calendars/_form_advanced.html.slim b/app/views/calendars/_form_advanced.html.slim
new file mode 100644
index 000000000..e796e2e36
--- /dev/null
+++ b/app/views/calendars/_form_advanced.html.slim
@@ -0,0 +1,8 @@
+#periods
+
+= javascript_tag do
+ | window.actionType = "#{raw params[:action]}";
+ // | window.I18n = #{(I18n.backend.send(:translations)[I18n.locale].to_json).html_safe};
+ | window.timetablesUrl = "#{calendar_url(@calendar).html_safe}";
+
+= javascript_pack_tag 'calendars/edit.js'
diff --git a/app/views/calendars/_form_simple.html.slim b/app/views/calendars/_form_simple.html.slim
new file mode 100644
index 000000000..ba18c765b
--- /dev/null
+++ b/app/views/calendars/_form_simple.html.slim
@@ -0,0 +1,56 @@
+.row
+ .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1
+ = simple_form_for [@workgroup, @calendar], html: { class: 'form-horizontal', id: 'calendar_form' }, wrapper: :horizontal_form do |f|
+ .row
+ .col-lg-12
+ = f.input :name
+ = f.input :short_name
+
+ - if policy(@calendar).share?
+ .form-group.has_switch
+ = f.label :shared, class: 'col-sm-4 col-xs-5 control-label'
+ = f.input :shared, as: :boolean, checked_value: true, unchecked_value: false, label: content_tag(:span, t("#{@calendar.shared}"), class: 'switch-label', data: {checkedValue: t('true'), uncheckedValue: t('false')}), wrapper_html: { class: 'col-sm-8 col-xs-7'}
+
+ .separator
+
+ - unless has_feature?('application_days_on_calendars')
+ .row
+ .col-lg-12
+ .subform
+ .nested-head
+ .wrapper
+ div
+ .form-group
+ label.control-label
+ = Calendar.human_attribute_name(:date)
+ div
+
+ = f.simple_fields_for :date_values do |date_value|
+ = render 'date_value_fields', f: date_value
+
+ .links.nested-linker
+ = link_to_add_association t('simple_form.labels.calendar.add_a_date'), f, :date_values, class: 'btn btn-outline-primary'
+
+ .separator
+
+ .row
+ .col-lg-12
+ .subform
+ .nested-head
+ .wrapper
+ div
+ .form-group
+ label.control-label
+ = t('simple_form.labels.calendar.ranges.begin')
+ div
+ .form-group
+ label.control-label
+ = t('simple_form.labels.calendar.ranges.end')
+ div
+
+ = f.simple_fields_for :periods do |period|
+ = render 'period_fields', f: period
+ .links.nested-linker
+ = link_to_add_association t('simple_form.labels.calendar.add_a_date_range'), f, :periods, class: 'btn btn-outline-primary'
+
+ = f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'calendar_form'
diff --git a/app/views/calendars/edit.html.slim b/app/views/calendars/edit.html.slim
index e806fc94b..79ab1f5d0 100644
--- a/app/views/calendars/edit.html.slim
+++ b/app/views/calendars/edit.html.slim
@@ -1,7 +1,8 @@
-- breadcrumb :calendar, @calendar
+- breadcrumb :calendar, @workgroup, @calendar
- page_header_content_for @calendar
.page_content
.container-fluid
- .row
- .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1
- = render 'form'
+ - if has_feature?('application_days_on_calendars')
+ = render 'form_advanced'
+ - else
+ = render 'form_simple'
diff --git a/app/views/calendars/index.html.slim b/app/views/calendars/index.html.slim
index 77478a624..0b58c0c72 100644
--- a/app/views/calendars/index.html.slim
+++ b/app/views/calendars/index.html.slim
@@ -1,7 +1,4 @@
-- breadcrumb :calendars
-- content_for :page_header_actions do
- - if policy(Calendar).create?
- = link_to(t('actions.add'), new_calendar_path, class: 'btn btn-default')
+- breadcrumb :calendars, workgroup
.page_content
.container-fluid
@@ -19,7 +16,7 @@
key: :name, \
attribute: 'name', \
link_to: lambda do |calendar| \
- calendar_path(calendar) \
+ workgroup_calendar_path(workgroup, calendar) \
end \
), \
TableBuilderHelper::Column.new( \
@@ -35,7 +32,6 @@
attribute: Proc.new { |c| t("#{c.try(:shared)}") } \
) \
],
- links: [:show, :edit],
cls: 'table has-filter'
= new_pagination @calendars, 'pull-right'
diff --git a/app/views/calendars/month.rabl b/app/views/calendars/month.rabl
new file mode 100644
index 000000000..1584db44c
--- /dev/null
+++ b/app/views/calendars/month.rabl
@@ -0,0 +1,9 @@
+object @calendar
+
+node do |tt|
+ {
+ name: I18n.l(@date, format: '%B'),
+ days: tt.month_inspect(@date),
+ day_types: %w(monday tuesday wednesday thursday friday saturday sunday).select{ |d| tt.send(d) }.map{ |d| tt.human_attribute_name(d).first(2)}.join('')
+ }
+end
diff --git a/app/views/calendars/new.html.slim b/app/views/calendars/new.html.slim
index ce8b6a036..5657a0c55 100644
--- a/app/views/calendars/new.html.slim
+++ b/app/views/calendars/new.html.slim
@@ -1,6 +1,4 @@
-- breadcrumb :calendars
+- breadcrumb :calendars, @workgroup
.page_content
.container-fluid
- .row
- .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1
- = render 'form'
+ = render 'form_simple'
diff --git a/app/views/calendars/show.html.slim b/app/views/calendars/show.html.slim
index da4afa3e6..cec4f66a5 100644
--- a/app/views/calendars/show.html.slim
+++ b/app/views/calendars/show.html.slim
@@ -1,25 +1,24 @@
-- breadcrumb :calendar, @calendar
+- breadcrumb :calendar, @workgroup, @calendar
- page_header_content_for @calendar
-- content_for :page_header_content do
- .row.mb-sm
- .col-lg-12.text-right
- - @calendar.action_links.each do |link|
- = link_to link.href,
- method: link.method,
- data: link.data,
- class: 'btn btn-primary' do
- = link.content
-- if policy(@calendar).edit?
- - content_for :page_header_actions do
- = link_to(t('actions.edit'), edit_calendar_path(@calendar), class: 'btn btn-default')
.page_content
.container-fluid
.row
.col-lg-6.col-md-6.col-sm-12.col-xs-12
= definition_list t('metadatas'),
- { 'Nom court' => @calendar.try(:short_name),
- Calendar.human_attribute_name(:shared) => t("#{@calendar.shared}"),
- 'Organisation' => @calendar.organisation.name,
- Calendar.human_attribute_name(:dates) => @calendar.dates.collect{|d| l(d, format: :short)}.join(', ').html_safe,
- Calendar.human_attribute_name(:date_ranges) => @calendar.periods.map{|d| t('validity_range', debut: l(d.begin, format: :short), end: l(d.end, format: :short))}.join('<br>').html_safe }
+ { 'Nom court' => resource.try(:short_name),
+ Calendar.human_attribute_name(:shared) => t("#{resource.shared}"),
+ 'Organisation' => resource.organisation.name,
+ Calendar.human_attribute_name(:dates) => resource.dates.collect{|d| l(d, format: :short)}.join(', ').html_safe,
+ Calendar.human_attribute_name(:date_ranges) => resource.periods.map{|d| t('validity_range', debut: l(d.begin, format: :short), end: l(d.end, format: :short))}.join('<br>').html_safe }
+
+ - if has_feature?('application_days_on_calendars')
+ .row
+ .col-lg-12.mb-sm
+ .pagination.pull-right
+ = @year
+ .page_links
+ = link_to '', calendar_path(@calendar, year: (@year - 1)), class: 'previous_page'
+ = link_to '', calendar_path(@calendar, year: (@year + 1)), class: 'next_page'
+
+ = render 'time_tables/show_time_table', time_table: @calendar
diff --git a/app/views/calendars/show.rabl b/app/views/calendars/show.rabl
new file mode 100644
index 000000000..295d97528
--- /dev/null
+++ b/app/views/calendars/show.rabl
@@ -0,0 +1,22 @@
+object @calendar
+
+attributes :id
+node do |tt|
+ {
+ comment: tt.name,
+ time_table_bounding: tt.presenter.time_table_bounding,
+ day_types: %w(monday tuesday wednesday thursday friday saturday sunday).select{ |d| tt.send(d) }.map{ |d| tt.human_attribute_name(d).first(2)}.join(''),
+ current_month: tt.month_inspect(Date.today),
+ periode_range: month_periode_enum(3),
+ current_periode_range: Date.today.beginning_of_month,
+ short_id: tt.object_id,
+ }
+end
+
+child(:periods, object_root: false, root: :time_table_periods) do
+ attributes :id, :period_start, :period_end
+end
+
+child(:all_dates, object_root: false, root: :time_table_dates) do
+ attributes :id, :date, :in_out
+end
diff --git a/app/views/companies/index.html.slim b/app/views/companies/index.html.slim
index 5d746642f..9f1502e54 100644
--- a/app/views/companies/index.html.slim
+++ b/app/views/companies/index.html.slim
@@ -1,7 +1,4 @@
- breadcrumb :companies, @line_referential
-- content_for :page_header_actions do
- - if policy(Chouette::Company).create?
- = link_to(t('companies.actions.new'), new_line_referential_company_path(@line_referential), class: 'btn btn-primary')
.page_content
.container-fluid
@@ -34,7 +31,6 @@
end \
) \
],
- links: [:show, :edit],
cls: 'table has-search'
= new_pagination @companies, 'pull-right'
diff --git a/app/views/companies/show.html.slim b/app/views/companies/show.html.slim
index 0d6b4aae3..ca0a410b3 100644
--- a/app/views/companies/show.html.slim
+++ b/app/views/companies/show.html.slim
@@ -1,25 +1,13 @@
- breadcrumb :company, @company
-- content_for :page_header_content do
- .row
- .col-lg-12.text-right.mb-sm
- - if policy(Chouette::Company).create?
- = link_to t('companies.actions.new'), new_line_referential_company_path(@line_referential), class: 'btn btn-primary'
- - if policy(@company).update?
- = link_to t('companies.actions.edit'), edit_line_referential_company_path(@line_referential, @company), class: 'btn btn-primary'
- - if policy(@company).destroy?
- = link_to line_referential_company_path(@line_referential, @company), method: :delete, data: {confirm: t('companies.actions.destroy_confirm')}, class: 'btn btn-primary' do
- span.fa.fa-trash
- span = t('companies.actions.destroy')
- page_header_content_for @company
-
.page_content
.container-fluid
.row
.col-lg-6.col-md-6.col-sm-12.col-xs-12
= definition_list t('metadatas'),
{ 'ID Codif' => @company.try(:get_objectid).try(:short_id),
- Chouette::Company.human_attribute_name(:phone) => @company.phone,
- Chouette::Company.human_attribute_name(:email) => @company.email,
- Chouette::Company.human_attribute_name(:url) => @company.url }
+ Chouette::Company.human_attribute_name(:phone) => resource.phone,
+ Chouette::Company.human_attribute_name(:email) => resource.email,
+ Chouette::Company.human_attribute_name(:url) => resource.url }
diff --git a/app/views/compliance_check_sets/_filters.html.slim b/app/views/compliance_check_sets/_filters.html.slim
index bf929bc08..e413a6cdd 100644
--- a/app/views/compliance_check_sets/_filters.html.slim
+++ b/app/views/compliance_check_sets/_filters.html.slim
@@ -1,23 +1,23 @@
= search_form_for @q_for_form, url: workbench_compliance_check_sets_path(@workbench), builder: SimpleForm::FormBuilder, class: 'form form-filter' do |f|
.ffg-row
- .input-group.search_bar
+ .input-group.search_bar class=filter_item_class(params[:q], :referential_name_cont)
= f.search_field :referential_name_cont, class: 'form-control', placeholder: t('compliance_check_sets.filters.name')
span.input-group-btn
button.btn.btn-default type='submit'
span.fa.fa-search
.ffg-row
- .form-group.togglable
+ .form-group.togglable class=filter_item_class(params[:q], :parent_type_eq_any)
= f.label t('activerecord.attributes.compliance_check_set.assigned_to'), required: false, class: 'control-label'
= f.input :parent_type_eq_any, collection: ComplianceCheckSet.order('parent_type'), as: :check_boxes, label: false, label_method: lambda {|w| ("<span>#{w}</span>").html_safe}, required: false, wrapper_html: {class: 'checkbox_list'}
- .form-group.togglable
+ .form-group.togglable class=filter_item_class(params[:q], :created_at)
= f.label Import.human_attribute_name(:created_at), required: false, class: 'control-label'
.filter_menu
= f.simple_fields_for :created_at do |p|
= p.input :start_date, as: :date, label: false, wrapper_html: {class: 'date smart_date filter_menu-item'}, default: @begin_range, include_blank: @begin_range ? false : true
= p.input :end_date, as: :date, label: false, wrapper_html: {class: 'date smart_date filter_menu-item'}, default: @end_range, include_blank: @end_range ? false : true
.form-group.search
- .input-group.search_bar
+ .input-group.search_bar class=filter_item_class(params[:q], :compliance_control_set_name_cont)
= f.search_field :compliance_control_set_name_cont, class: 'form-control', placeholder: t('compliance_check_sets.filters.name_compliance_control_set')
span.input-group-btn
button.btn.btn-default type='submit'
diff --git a/app/views/compliance_check_sets/executed.html.slim b/app/views/compliance_check_sets/executed.html.slim
index da8a2a3de..b33b516b4 100644
--- a/app/views/compliance_check_sets/executed.html.slim
+++ b/app/views/compliance_check_sets/executed.html.slim
@@ -1,4 +1,4 @@
-- breadcrumb :compliance_check_set, @workbench, @compliance_check_set
+- breadcrumb :compliance_check_set_executed, @workbench, @compliance_check_set
- page_header_content_for @compliance_check_set
.page_content
diff --git a/app/views/compliance_check_sets/index.html.slim b/app/views/compliance_check_sets/index.html.slim
index f5d1bd777..31ad31e5b 100644
--- a/app/views/compliance_check_sets/index.html.slim
+++ b/app/views/compliance_check_sets/index.html.slim
@@ -12,7 +12,10 @@
[ \
TableBuilderHelper::Column.new( \
key: :ref, \
- attribute: 'compliance_check_set_id' \
+ attribute: 'id', \
+ link_to: lambda do |compliance_check_set| \
+ workbench_compliance_check_set_path(compliance_check_set.workbench_id, compliance_check_set.id) \
+ end \
), \
TableBuilderHelper::Column.new( \
key: :creation_date, \
@@ -20,9 +23,9 @@
), \
TableBuilderHelper::Column.new( \
key: :associated_object, \
- attribute: Proc.new{|n| n.referential.name}, \
+ attribute: Proc.new{|n| n.referential.present? ? n.referential.name : ''}, \
link_to: lambda do |compliance_check_set| \
- referential_path(compliance_check_set.referential_id) \
+ compliance_check_set.referential.present? ? referential_path(compliance_check_set.referential_id) : '#' \
end \
), \
TableBuilderHelper::Column.new( \
@@ -31,15 +34,15 @@
), \
TableBuilderHelper::Column.new(\
key: :compliance_control_set, \
- attribute: Proc.new{ |n| (n.compliance_control_set.name) if n.compliance_control_set} \
+ attribute: 'name', \
+ link_to: lambda do |compliance_check_set| \
+ executed_workbench_compliance_check_set_path(compliance_check_set.workbench_id, compliance_check_set.id) \
+ end \
), \
],
sortable: true,
- links: [:show],
cls: 'table has-filter has-search'
- unless @compliance_check_sets.any?
.row.mt-xs
.col-lg-12
= replacement_msg t('compliance_check_sets.search_no_results')
-
-
diff --git a/app/views/compliance_check_sets/show.html.slim b/app/views/compliance_check_sets/show.html.slim
index 5d8e3fa15..4df14ab06 100644
--- a/app/views/compliance_check_sets/show.html.slim
+++ b/app/views/compliance_check_sets/show.html.slim
@@ -1,41 +1,27 @@
- breadcrumb :compliance_check_sets, @workbench, @compliance_check_set
-/ PageHeader
-- content_for :page_header_content do
- .row
- .col-lg-12.text-right.mb-sm
- - @compliance_check_set.action_links.each do |link|
- = link_to link.href,
- method: link.method,
- data: link.data,
- class: 'btn btn-primary' do
- = link.content
-
- page_header_content_for @compliance_check_set
-/ = pageheader 'jeux-de-donnees',
-/ @compliance_check_set.name,
-/ '',
-/ t('last_update', time: l(@compliance_check_set.updated_at, format: :short)) do
-
- / Below is secundary actions & optional contents (filters, ...)
- / .row
- / .col-lg-12.text-right.mb-sm
- / - @compliance_check_set.action_links.each do |link|
- / = link_to link.href,
- / method: link.method,
- / data: link.data,
- / class: 'btn btn-primary' do
- / = link.content
/ PageContent
.page_content.import_messages
.container-fluid
.row
+ .col-lg-6.col-md-6.col-sm-12.col-xs-12
+ = definition_list( t('metadatas'),
+ { I18n.t("compliance_check_sets.show.metadatas.referential") => (@compliance_check_set.referential.nil? ? '' : link_to(@compliance_check_set.referential.name, referential_path(@compliance_check_set.referential)) ),
+ I18n.t("compliance_check_sets.show.metadatas.referential_type") => 'Jeu de données',
+ I18n.t("compliance_check_sets.show.metadatas.compliance_check_set_executed") => link_to(@compliance_check_set.name, executed_workbench_compliance_check_set_path(@compliance_check_set.workbench_id, @compliance_check_set)),
+ I18n.t("compliance_check_sets.show.metadatas.compliance_control_owner") => @compliance_check_set.organisation.name,
+ I18n.t("compliance_check_sets.show.metadatas.import") => '' })
+ .row
.col-lg-12
h1
span.status_icon = compliance_check_set_status(@compliance_check_set.status)
- span = t('compliance_check_sets.show.table_state', lines_status: @compliance_check_set.lines_status , lines_in_compliance_check_set: @compliance_check_set.lines_in_compliance_check_set )
+ span = t('.table_state', lines_status: @compliance_check_set.lines_status , lines_in_compliance_check_set: @compliance_check_set.lines_in_compliance_check_set )
+ .col-lg-12
+ h2 = t('.table_title')
.col-lg-12
- = t('compliance_check_sets.show.table_explanation')
+ = t('.table_explanation')
+
.row
.col-lg-12
= table_builder_2 @compliance_check_set.compliance_check_resources,
@@ -46,13 +32,21 @@
), \
TableBuilderHelper::Column.new( \
key: :status, \
- attribute: Proc.new { |n| compliance_check_set_status(n.status) } \
+ attribute: Proc.new { |n| compliance_check_resource_status(n.status) } \
), \
TableBuilderHelper::Column.new( \
key: :metrics, \
attribute: Proc.new { |n| I18n.t('compliance_check_sets.show.metrics', n.metrics.deep_symbolize_keys) } \
+ ), \
+ TableBuilderHelper::Column.new( \
+ name: 'Téléchargement' , \
+ attribute: Proc.new { |n| '<i class="fa fa-download" aria-hidden="true"></i>'.html_safe }, \
+ sortable: false, \
+ link_to: lambda do |compliance_check_resource| \
+ workbench_compliance_check_set_compliance_check_messages_path(@compliance_check_set.workbench, @compliance_check_set, format: 'csv', compliance_check_resource_id: compliance_check_resource.id ) \
+ end \
) \
],
sortable: false, \
links: [],
- cls: 'table'
+ cls: 'table has-search'
diff --git a/app/views/compliance_checks/_filters.html.slim b/app/views/compliance_checks/_filters.html.slim
index 0d747da27..40e45dd92 100644
--- a/app/views/compliance_checks/_filters.html.slim
+++ b/app/views/compliance_checks/_filters.html.slim
@@ -5,7 +5,7 @@
class: 'form form-filter' do |f|
.ffg-row
- .input-group.search_bar
+ .input-group.search_bar class=filter_item_class(params[:q], :name_cont)
= f.search_field :name_cont,
class: 'form-control',
placeholder: t('compliance_checks.filters.name')
@@ -14,7 +14,7 @@
span.fa.fa-search
.ffg-row
- .form-group.togglable#compliance_check_block-filter
+ .form-group.togglable#compliance_check_block-filter class=filter_item_class(params[:q], :compliance_check_block_id_eq_any)
= f.label t('activerecord.models.compliance_check_block.one'), required: false, class: 'control-label'
= f.input :compliance_check_block_id_eq_any,
collection: @compliance_check_set.compliance_check_blocks,
@@ -23,7 +23,7 @@
label_method: lambda {|w| ("<span>#{transport_mode_text(w)}</span>").html_safe},
required: false,
wrapper_html: {class: 'checkbox_list'}
- .form-group.togglable#subclass-filter
+ .form-group.togglable#subclass-filter class=filter_item_class(params[:q], :origin_code_cont_any)
= f.label t('compliance_checks.filters.subclass'), required: false, class: 'control-label'
= f.input :origin_code_cont_any,
collection: subclass_selection_list,
@@ -32,7 +32,7 @@
label_method: lambda {|w| ("<span>#{w.first}</span>").html_safe},
required: false,
wrapper_html: {class: 'checkbox_list'}
- .form-group.togglable#severity-filter
+ .form-group.togglable#severity-filter class=filter_item_class(params[:q], :criticity_eq_any)
= f.label t('compliance_checks.filters.criticity'), required: false, class: 'control-label'
= f.input :criticity_eq_any,
collection: ComplianceControl.criticities,
diff --git a/app/views/compliance_control_sets/_filters.html.slim b/app/views/compliance_control_sets/_filters.html.slim
index 6a5d3ac44..4348defac 100644
--- a/app/views/compliance_control_sets/_filters.html.slim
+++ b/app/views/compliance_control_sets/_filters.html.slim
@@ -1,16 +1,16 @@
= search_form_for @q_for_form, url: compliance_control_sets_path, builder: SimpleForm::FormBuilder, class: 'form form-filter' do |f|
.ffg-row
- .input-group.search_bar
+ .input-group.search_bar class=filter_item_class(params[:q], :name_cont)
= f.search_field :name_cont, class: 'form-control', placeholder: t('compliance_control_sets.filters.name')
span.input-group-btn
button.btn.btn-default type='submit'
span.fa.fa-search
.ffg-row
- .form-group.togglable
+ .form-group.togglable class=filter_item_class(params[:q], :organisation_name_eq_any)
= f.label t('activerecord.models.organisation.one'), required: false, class: 'control-label'
= f.input :organisation_name_eq_any, collection: organisations_filters_values, as: :check_boxes, label: false, label_method: lambda {|w| ("<span>#{w.name}</span>").html_safe}, required: false, wrapper_html: {class: 'checkbox_list'}
-
- .form-group.togglable
+
+ .form-group.togglable class=filter_item_class(params[:q], :updated_at)
= f.label Import.human_attribute_name(:updated_at), required: false, class: 'control-label'
.filter_menu
= f.simple_fields_for :updated_at do |p|
@@ -19,4 +19,4 @@
.actions
= link_to t('actions.erase'), @compliance_control_set, class: 'btn btn-link'
- = f.submit t('actions.filter'), class: 'btn btn-default', id: 'compliance_control_set_filter_btn' \ No newline at end of file
+ = f.submit t('actions.filter'), class: 'btn btn-default', id: 'compliance_control_set_filter_btn'
diff --git a/app/views/compliance_control_sets/index.html.slim b/app/views/compliance_control_sets/index.html.slim
index 2a5651280..144a4e5b9 100644
--- a/app/views/compliance_control_sets/index.html.slim
+++ b/app/views/compliance_control_sets/index.html.slim
@@ -1,7 +1,4 @@
- breadcrumb :compliance_control_sets
-- content_for :page_header_actions do
- - if policy(ComplianceControlSet).create?
- = link_to(t('compliance_control_sets.actions.new'), new_compliance_control_set_path, class: 'btn btn-default')
.page_content
.container-fluid
@@ -30,7 +27,7 @@
), \
TableBuilderHelper::Column.new( \
key: :control_numbers, \
- attribute: 'control_numbers' \
+ attribute: Proc.new {|n| n.compliance_controls.count }\
), \
TableBuilderHelper::Column.new( \
key: :updated_at, \
@@ -38,7 +35,6 @@
) \
],
sortable: true,
- links: [:show],
cls: 'table has-filter has-search'
= new_pagination @compliance_control_sets, 'pull-right'
diff --git a/app/views/compliance_control_sets/show.html.slim b/app/views/compliance_control_sets/show.html.slim
index d915bbdaf..59100681d 100644
--- a/app/views/compliance_control_sets/show.html.slim
+++ b/app/views/compliance_control_sets/show.html.slim
@@ -1,17 +1,5 @@
- breadcrumb :compliance_control_set, @compliance_control_set
- page_header_content_for @compliance_control_set
-- content_for :page_header_content do
- .row.mb-sm
- .col-lg-12.text-right
- - @compliance_control_set.action_links.each do |link|
- - if link.is_a?(HTMLElement)
- = link.to_html(class: 'btn btn-primary')
- - else
- = link_to link.href,
- method: link.method,
- data: link.data,
- class: 'btn btn-primary' do
- = link.content
.page_content
.container-fluid
diff --git a/app/views/compliance_controls/_filters.html.slim b/app/views/compliance_controls/_filters.html.slim
index d38da5d2d..f6b9970f2 100644
--- a/app/views/compliance_controls/_filters.html.slim
+++ b/app/views/compliance_controls/_filters.html.slim
@@ -5,7 +5,7 @@
class: 'form form-filter' do |f|
.ffg-row
- .input-group.search_bar
+ .input-group.search_bar class=filter_item_class(params[:q], :name_cont)
= f.search_field :name_cont,
class: 'form-control',
placeholder: t('compliance_controls.filters.name')
@@ -14,7 +14,7 @@
span.fa.fa-search
.ffg-row
- .form-group.togglable#compliance_control_block-filter
+ .form-group.togglable#compliance_control_block-filter class=filter_item_class(params[:q], :compliance_control_block_id_eq_any)
= f.label t('activerecord.models.compliance_control_block.one'), required: false, class: 'control-label'
= f.input :compliance_control_block_id_eq_any,
collection: @compliance_control_set.compliance_control_blocks,
@@ -23,7 +23,7 @@
label_method: lambda {|w| ("<span>#{transport_mode_text(w)}</span>").html_safe},
required: false,
wrapper_html: {class: 'checkbox_list'}
- .form-group.togglable#subclass-filter
+ .form-group.togglable#subclass-filter class=filter_item_class(params[:q], :origin_code_cont_any)
= f.label t('compliance_controls.filters.subclass'), required: false, class: 'control-label'
= f.input :origin_code_cont_any,
collection: subclass_selection_list,
@@ -32,7 +32,7 @@
label_method: lambda {|w| ("<span>#{w.first}</span>").html_safe},
required: false,
wrapper_html: {class: 'checkbox_list'}
- .form-group.togglable#severity-filter
+ .form-group.togglable#severity-filter class=filter_item_class(params[:q], :criticity_eq_any)
= f.label t('compliance_controls.filters.criticity'), required: false, class: 'control-label'
= f.input :criticity_eq_any,
collection: ComplianceControl.criticities,
diff --git a/app/views/compliance_controls/new.html.slim b/app/views/compliance_controls/new.html.slim
index f7f47fba3..c0abc522f 100644
--- a/app/views/compliance_controls/new.html.slim
+++ b/app/views/compliance_controls/new.html.slim
@@ -6,5 +6,5 @@
= render 'form'
= definition_list t('metadatas'),
- I18n.t('activerecord.attributes.compliance_control.predicate') => @compliance_control.class.predicate,
- I18n.t('activerecord.attributes.compliance_control.prerequisite') => @compliance_control.class.prerequisite
+ I18n.t('activerecord.attributes.compliance_control.predicate') => @compliance_control.predicate,
+ I18n.t('activerecord.attributes.compliance_control.prerequisite') => @compliance_control.prerequisite
diff --git a/app/views/compliance_controls/show.html.slim b/app/views/compliance_controls/show.html.slim
index 54b07abf1..ab25747a9 100644
--- a/app/views/compliance_controls/show.html.slim
+++ b/app/views/compliance_controls/show.html.slim
@@ -1,6 +1,4 @@
- breadcrumb :compliance_control, @compliance_control
-- content_for :page_header_actions do
- = link_to(t('actions.edit'), edit_compliance_control_set_compliance_control_path(params[:compliance_control_set_id], params[:id]), class: 'btn btn-default')
- page_header_content_for @compliance_control
@@ -15,8 +13,8 @@
ComplianceControl.human_attribute_name(:code) => @compliance_control.code,
ComplianceControl.human_attribute_name(:criticity) => @compliance_control.criticity,
ComplianceControl.human_attribute_name(:comment) => @compliance_control.comment,
- I18n.t('activerecord.attributes.compliance_control.predicate') => @compliance_control.class.predicate,
- I18n.t('activerecord.attributes.compliance_control.prerequisite') => @compliance_control.class.prerequisite,
+ I18n.t('activerecord.attributes.compliance_control.predicate') => @compliance_control.predicate,
+ I18n.t('activerecord.attributes.compliance_control.prerequisite') => @compliance_control.prerequisite,
}.merge( \
{}.tap do |hash| \
@compliance_control.class.dynamic_attributes.each do |attribute| \
diff --git a/app/views/dashboards/_dashboard.html.slim b/app/views/dashboards/_dashboard.html.slim
index f03301e23..05257a766 100644
--- a/app/views/dashboards/_dashboard.html.slim
+++ b/app/views/dashboards/_dashboard.html.slim
@@ -5,11 +5,11 @@
.panel-heading
h3.panel-title.with_actions
div
- = workbench.name
+ = link_to workbench.name, workbench_path(workbench)
span.badge.ml-xs = workbench.referentials.count if workbench.referentials.present?
div
- = link_to '', workbench_path(workbench), class: ' fa fa-chevron-right pull-right', title: t('.offers.see')
+ = link_to '', workbench_path(workbench), class: ' fa fa-chevron-right pull-right', title: t('workbenches.index.offers.see')
- if workbench.referentials.present?
.list-group
@@ -17,35 +17,38 @@
= link_to referential.name, referential_path(referential, workbench_id: referential.workbench_id, current_workbench_id: workbench.id), class: 'list-group-item'
- else
.panel-body
- em.small.text-muted = t('.offers.no_content')
+ em.small.text-muted = t('workbenches.index.offers.no_content')
.panel.panel-default
.panel-heading
h3.panel-title.with_actions
- = "Modèles de calendrier"
+ = link_to I18n.t("activerecord.models.calendar", count: @dashboard.current_organisation.calendars.size), workgroup_calendars_path(current_workgroup)
div
- = link_to '', calendars_path, class: ' fa fa-chevron-right pull-right'
+ = link_to '', workgroup_calendars_path(current_workgroup), class: ' fa fa-chevron-right pull-right'
- if @dashboard.current_organisation.calendars.present?
.list-group
- @dashboard.current_organisation.calendars.order("updated_at desc").limit(5).each do |calendar|
- = link_to calendar.name, calendar_path(calendar), class: 'list-group-item'
+ = link_to calendar.name, workgroup_calendars_path(current_workgroup, calendar), class: 'list-group-item'
- else
.panel-body
- em.small.text-muted Aucun modèle de calendrier défini
+ em.small.text-muted
+ = t('dasboard.calendars.none')
.col-lg-6.col-md-6.col-sm-6.col-xs-12
.panel.panel-default
- .panel-heading
- h3.panel-title
- = "Référentiels d'arrêts"
- .list-group
- - @dashboard.current_organisation.stop_area_referentials.each do |referential|
- = link_to referential.name, stop_area_referential_stop_areas_path(referential), class: 'list-group-item'
+ - @dashboard.current_organisation.stop_area_referentials.each do |referential|
+ .panel-heading
+ h3.panel-title
+ = referential.name
+ .list-group
+ = link_to Chouette::StopArea.model_name.human.pluralize.capitalize, stop_area_referential_stop_areas_path(referential), class: 'list-group-item'
.panel.panel-default
- .panel-heading
- h3.panel-title
- = "Référentiels de lignes"
- .list-group
- - @dashboard.current_organisation.line_referentials.all.each do |referential|
- = link_to referential.name, line_referential_lines_path(referential), class: 'list-group-item'
+ - @dashboard.current_organisation.line_referentials.all.each do |referential|
+ .panel-heading
+ h3.panel-title
+ = referential.name
+ .list-group
+ = link_to Chouette::Line.model_name.human.pluralize.capitalize, line_referential_lines_path(referential), class: 'list-group-item'
+ = link_to Chouette::Company.model_name.human.pluralize.capitalize, line_referential_companies_path(referential), class: 'list-group-item'
+ = link_to Chouette::Network.model_name.human.pluralize.capitalize, line_referential_networks_path(referential), class: 'list-group-item'
diff --git a/app/views/devise/invitations/edit.html.slim b/app/views/devise/invitations/edit.html.slim
index 6c9a6f436..7a22146c0 100644
--- a/app/views/devise/invitations/edit.html.slim
+++ b/app/views/devise/invitations/edit.html.slim
@@ -1,14 +1,19 @@
-.col-md-offset-2.col-md-8
- .panel.panel-default
- .panel-heading = t('devise.invitations.edit.header')
-
- .panel-body
- = simple_form_for resource, as: resource_name, :url => invitation_path(resource_name), :html => { :method => :put, class: "form-horizontal" } do |form|
- = form.hidden_field :invitation_token
-
- = form.input :name
- = form.input :password, as: :password
- = form.input :password_confirmation, as: :password
-
- .submit
- = form.button :submit, value: t('devise.invitations.edit.submit_button'), class: 'btn-info' \ No newline at end of file
+/ PageHeader
+
+- content_for :page_header_title, t('.title')
+
+/ PageContent
+.page_content
+ .container-fluid
+ .row
+ .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1
+ = simple_form_for resource, as: resource_name, :url => invitation_path(resource_name), :html => { :method => :put, class: "form-horizontal", id: 'invitation_form' } do |form|
+ .row
+ .col-lg-12
+ = form.hidden_field :invitation_token
+
+ = form.input :name
+ = form.input :password, as: :password
+ = form.input :password_confirmation, as: :password
+
+ = form.button :submit, value: t('devise.invitations.edit.submit_button'), class: 'btn-info btn-default formSubmitr', form: 'invitation_form'
diff --git a/app/views/devise/mailer/invitation_instructions.fr.html.slim b/app/views/devise/mailer/invitation_instructions.fr.html.slim
index 484e0d68d..e263e188a 100644
--- a/app/views/devise/mailer/invitation_instructions.fr.html.slim
+++ b/app/views/devise/mailer/invitation_instructions.fr.html.slim
@@ -1,7 +1,7 @@
-p = t("devise.mailer.invitation_instructions.hello", email: @resource.email)
+p = t("devise.mailer.invitation_instructions.hello", email: @resource.name)
p = t("devise.mailer.invitation_instructions.someone_invited_you", url: unauthenticated_root_url)
p = link_to t("devise.mailer.invitation_instructions.accept"), accept_invitation_url(@resource, :invitation_token => @token)
-p = t("devise.mailer.invitation_instructions.ignore").html_safe \ No newline at end of file
+p = t("devise.mailer.invitation_instructions.ignore").html_safe
diff --git a/app/views/devise/passwords/edit.html.slim b/app/views/devise/passwords/edit.html.slim
index 864a44499..0d18f657c 100644
--- a/app/views/devise/passwords/edit.html.slim
+++ b/app/views/devise/passwords/edit.html.slim
@@ -1,13 +1,17 @@
-.col-md-offset-2.col-md-8
- .panel.panel-default
- .panel-heading = t('.title')
- .panel-body
- = simple_form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :put, class: "form-horizontal" }) do |f|
+/ PageHeader
- = f.input :reset_password_token, as: :hidden
- = f.input :password, as: :password
- = f.input :password_confirmation, as: :password
-
- .form-actions
- = link_to t("cancel"), unauthenticated_root_path, class: 'btn btn-default'
- = f.button :submit, :value => t("devise.passwords.edit.commit"), class: 'btn-info' \ No newline at end of file
+- content_for :page_header_title, t('.title')
+
+/ PageContent
+.page_content
+ .container-fluid
+ .row
+ .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1
+ = simple_form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :put, class: "form-horizontal", id: 'password_form' }) do |f|
+ .row
+ .col-lg-12
+ = f.input :reset_password_token, as: :hidden
+ = f.input :password, as: :password
+ = f.input :password_confirmation, as: :password
+
+ = f.button :submit, :value => t("devise.passwords.edit.commit"), class: 'btn btn-default formSubmitr', form: 'password_form'
diff --git a/app/views/devise/passwords/new.html.slim b/app/views/devise/passwords/new.html.slim
index 8a739ecc1..303f78f0e 100644
--- a/app/views/devise/passwords/new.html.slim
+++ b/app/views/devise/passwords/new.html.slim
@@ -1,11 +1,15 @@
-.col-md-offset-2.col-md-8
- .panel.panel-default
- .panel-heading = t('.title')
-
- .panel-body
- = simple_form_for(resource, :as => resource_name, :url => password_path(resource_name), html: {class: 'form-horizontal' } ) do |form|
- = form.input :email, :as => :email, placeholder: 'user@domain.com'
+/ PageHeader
- .form-actions
- = link_to t("cancel"), unauthenticated_root_path, class: 'btn btn-default'
- = form.button :submit, :value => t("devise.passwords.new.commit"), class: 'btn-info' \ No newline at end of file
+- content_for :page_header_title, t('.title')
+
+/ PageContent
+.page_content
+ .container-fluid
+ .row
+ .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1
+ = simple_form_for(resource, :as => resource_name, :url => password_path(resource_name), html: {class: 'form-horizontal', id: 'password_form' } ) do |form|
+ .row
+ .col-lg-12
+ = form.input :email, :as => :email, placeholder: 'user@domain.com'
+
+ = form.button :submit, :value => t("devise.passwords.new.commit"), class: 'btn btn-default formSubmitr', form: 'password_form'
diff --git a/app/views/devise/sessions/new.html.slim b/app/views/devise/sessions/new.html.slim
index 0ed17e24a..a812726e5 100644
--- a/app/views/devise/sessions/new.html.slim
+++ b/app/views/devise/sessions/new.html.slim
@@ -1,3 +1,5 @@
+- content_for :page_header_title, t('.title')
+
.page_content#devise
.container-fluid
#sessions_new.row
diff --git a/app/views/errors/forbidden.html.slim b/app/views/errors/forbidden.html.slim
index 23ea67eff..8c35b46a8 100644
--- a/app/views/errors/forbidden.html.slim
+++ b/app/views/errors/forbidden.html.slim
@@ -9,7 +9,7 @@
p
strong = "Désolé, la page demandée la page n'est pas accessible avec votre profil utilisateur."
- p = "Vous pouvez néanmoins continuer à utiliser l'application IBOO."
+ p = "Vous pouvez néanmoins continuer à utiliser l'application."
- else
p
diff --git a/app/views/errors/server_error.html.slim b/app/views/errors/server_error.html.slim
index 189a48760..529ad73e8 100644
--- a/app/views/errors/server_error.html.slim
+++ b/app/views/errors/server_error.html.slim
@@ -9,7 +9,7 @@
p
strong = "Désolé, une erreur est survenue."
- p = "Vous pouvez néanmoins continuer à utiliser l'application IBOO."
+ p = "Vous pouvez néanmoins continuer à utiliser l'application."
- else
p
diff --git a/app/views/import_resources/index.html.slim b/app/views/import_resources/index.html.slim
index 728d9f4a8..6b4e60026 100644
--- a/app/views/import_resources/index.html.slim
+++ b/app/views/import_resources/index.html.slim
@@ -43,9 +43,8 @@
end \
), \
],
- links: [],
cls: 'table has-search'
- else
.col-lg-12
- @import.messages.each do |message|
- = I18n.t("import_messages.compliance_check_messages.#{message.message_key}")
+ = I18n.t("import_messages.#{message.message_key}")
diff --git a/app/views/imports/_filters.html.slim b/app/views/imports/_filters.html.slim
index b1f23e2c8..25c0d10d9 100644
--- a/app/views/imports/_filters.html.slim
+++ b/app/views/imports/_filters.html.slim
@@ -1,17 +1,17 @@
= search_form_for @q, url: workbench_imports_path(@workbench), html: { method: :get, class: 'form form-filter' } do |f|
.ffg-row
- .input-group.search_bar
+ .input-group.search_bar class=filter_item_class(params[:q], :name_or_creator_cont)
= f.search_field :name_or_creator_cont, class: 'form-control', placeholder: t('imports.filters.name_or_creator_cont')
span.input-group-btn
button.btn.btn-default#search_btn type='submit'
span.fa.fa-search
.ffg-row
- .form-group.togglable
+ .form-group.togglable class=filter_item_class(params[:q], :status_eq_any)
= f.label Import.human_attribute_name(:status), required: false, class: 'control-label'
= f.input :status_eq_any, collection: %w(pending successful warning failed), as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + import_status(l) + "</span>").html_safe}, required: false, wrapper_html: { class: "checkbox_list"}
- .form-group.togglable
+ .form-group.togglable class=filter_item_class(params[:q], :started_at)
= f.label Import.human_attribute_name(:started_at), required: false, class: 'control-label'
.filter_menu
= f.simple_fields_for :started_at do |p|
diff --git a/app/views/imports/_import_messages.html.slim b/app/views/imports/_import_messages.html.slim
new file mode 100644
index 000000000..af10b23e5
--- /dev/null
+++ b/app/views/imports/_import_messages.html.slim
@@ -0,0 +1,8 @@
+- if import_messages.any?
+ ul.list-unstyled.import_message-list
+ - import_messages.each do | import_message |
+ li
+ span(class="#{bootstrap_class_for_message_criticity import_message.criticity}")
+ = t( ['import_messages',
+ 'compliance_check_messages',
+ import_message.message_key].join('.'), import_message.message_attributes.symbolize_keys)
diff --git a/app/views/imports/index.html.slim b/app/views/imports/index.html.slim
index 79452bbfc..4fc077bd6 100644
--- a/app/views/imports/index.html.slim
+++ b/app/views/imports/index.html.slim
@@ -1,6 +1,4 @@
- breadcrumb :imports, @workbench
-- content_for :page_header_actions do
- = link_to(t('imports.actions.new'), new_workbench_import_path(workbench_id: @workbench), class: 'btn btn-primary')
.page_content
.container-fluid
@@ -34,7 +32,6 @@
attribute: 'creator' \
) \
],
- links: [],
cls: 'table has-search'
= new_pagination @imports, 'pull-right'
diff --git a/app/views/imports/show.html.slim b/app/views/imports/show.html.slim
index 5e22e03e0..7a9d02077 100644
--- a/app/views/imports/show.html.slim
+++ b/app/views/imports/show.html.slim
@@ -1,13 +1,4 @@
- breadcrumb :import, @workbench, @import
-- content_for :page_header_content do
- .row
- .col-lg-12.text-right.mb-sm
- - @import.action_links.each do |link|
- = link_to link.href,
- method: link.method,
- data: link.data,
- class: 'btn btn-primary' do
- = link.content
- page_header_content_for @import
@@ -19,46 +10,51 @@
.row
.col-lg-12
- = table_builder_2 @import.children,
- [ \
- TableBuilderHelper::Column.new( \
- name: 'Nom du jeu de données', \
- attribute: 'name', \
- sortable: false, \
- link_to: lambda do |import| \
- referential_path(import.referential) if import.referential.present? \
- end \
- ), \
- TableBuilderHelper::Column.new( \
- key: :status, \
- attribute: Proc.new { |n| import_status(n.status) }, \
- sortable: false, \
- link_to: lambda do |import| \
- workbench_import_import_resources_path(import.workbench_id, import) \
- end \
- ), \
- TableBuilderHelper::Column.new( \
- name: 'Contrôle STIF', \
- attribute: '', \
- sortable: false, \
- ), \
- TableBuilderHelper::Column.new( \
- name: 'Contrôle organisation', \
- attribute: '', \
- sortable: false, \
- ) \
- ],
- links: [],
- cls: 'table',
- overhead: [ \
- {}, \
- { \
- title: "#{@import.children_succeedeed} jeu de données validé sur #{@import.children.count} présent(s) dans l'archive", \
- width: 1, \
- cls: "#{@import.import_status_css_class} full-border" \
- }, { \
- title: 'Bilan des jeux de contrôles d\'import <span title="Lorem ipsum..." class="fa fa-lg fa-info-circle text-info"></span>', \
- width: 2, \
- cls: 'overheaded-default colspan="2"' \
- } \
- ]
+ .error_messages
+ = render 'import_messages', import_messages: @import.messages
+
+ - if @import.children.any?
+ .row
+ .col-lg-12
+ = table_builder_2 @import.children,
+ [ \
+ TableBuilderHelper::Column.new( \
+ name: 'Nom du jeu de données', \
+ attribute: 'name', \
+ sortable: false, \
+ link_to: lambda do |import| \
+ referential_path(import.referential) if import.referential.present? \
+ end \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :status, \
+ attribute: Proc.new { |n| import_status(n.status) }, \
+ sortable: false, \
+ link_to: lambda do |import| \
+ workbench_import_import_resources_path(import.workbench_id, import) \
+ end \
+ ), \
+ TableBuilderHelper::Column.new( \
+ name: 'Contrôle STIF', \
+ attribute: '', \
+ sortable: false, \
+ ), \
+ TableBuilderHelper::Column.new( \
+ name: 'Contrôle organisation', \
+ attribute: '', \
+ sortable: false, \
+ ) \
+ ],
+ cls: 'table',
+ overhead: [ \
+ {}, \
+ { \
+ title: "#{@import.children_succeedeed} jeu de données validé sur #{@import.children.count} présent(s) dans l'archive", \
+ width: 1, \
+ cls: "#{@import.import_status_css_class} full-border" \
+ }, { \
+ title: 'Bilan des jeux de contrôles d\'import <span title="Lorem ipsum..." class="fa fa-lg fa-info-circle text-info"></span>', \
+ width: 2, \
+ cls: 'overheaded-default colspan="2"' \
+ } \
+ ]
diff --git a/app/views/journey_patterns_collections/show.html.slim b/app/views/journey_patterns_collections/show.html.slim
index 834501da3..8a7b0c47c 100644
--- a/app/views/journey_patterns_collections/show.html.slim
+++ b/app/views/journey_patterns_collections/show.html.slim
@@ -17,6 +17,7 @@
| window.stopPoints = #{(@stop_points_list.to_json).html_safe};
| window.journeyPatternLength = #{@journey_patterns.total_entries()};
| window.journeyPatternsPerPage = #{@ppage};
- | window.perms = #{raw @perms}
+ | window.perms = #{raw @perms};
+ | window.features = #{raw @features};
= javascript_pack_tag 'journey_patterns/index.js'
diff --git a/app/views/layouts/application.html.slim b/app/views/layouts/application.html.slim
index 567e14ef0..3921c8701 100644
--- a/app/views/layouts/application.html.slim
+++ b/app/views/layouts/application.html.slim
@@ -13,6 +13,8 @@ html lang=I18n.locale
= javascript_pack_tag 'application'
= javascript_include_tag 'application'
+ = javascript_tag do
+ | I18n.locale = '#{I18n.locale}'
body
= render 'layouts/navigation/main_nav'
@@ -21,3 +23,6 @@ html lang=I18n.locale
= yield
#sidebar
= yield :sidebar
+
+ = render 'shared/development_toolbar'
+ = yield :javascript
diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb
new file mode 100644
index 000000000..977cbfe5b
--- /dev/null
+++ b/app/views/layouts/mailer.html.erb
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <style>
+ /* Email styles need to be inline */
+ body {
+ font-family: "Open Sans", sans-serif;
+ font-size: 14px;
+ line-height: 1.4;
+ color: #4b4b4b;
+ width: 800px;
+ }
+ </style>
+ </head>
+
+ <body>
+ <%= image_tag 'mail-header.png' %>
+
+ <%= yield %>
+ </body>
+</html>
diff --git a/app/views/layouts/mailer.html.slim b/app/views/layouts/mailer.html.slim
deleted file mode 100644
index 5db6917b3..000000000
--- a/app/views/layouts/mailer.html.slim
+++ /dev/null
@@ -1,7 +0,0 @@
-doctype html
-html
- head
- meta charset="utf-8"
- /!* Email styles need to be inline */
- body
- = yield
diff --git a/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim b/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim
index 3963d4cd4..1b7293d21 100644
--- a/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim
+++ b/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim
@@ -29,7 +29,7 @@
span Jeux de données
= link_to workbench_imports_path(current_offer_workbench), class: "list-group-item #{(params[:controller] == 'imports') ? 'active' : ''}" do
span Import
- = link_to calendars_path, class: 'list-group-item' do
+ = link_to workgroup_calendars_path(current_workgroup), class: 'list-group-item' do
span Modèles de calendrier
= link_to workbench_compliance_check_sets_path(current_offer_workbench), class: 'list-group-item' do
span Rapport de contrôle
diff --git a/app/views/layouts/navigation/_main_nav_top.html.slim b/app/views/layouts/navigation/_main_nav_top.html.slim
index d6c849d3f..f664d5416 100644
--- a/app/views/layouts/navigation/_main_nav_top.html.slim
+++ b/app/views/layouts/navigation/_main_nav_top.html.slim
@@ -1,5 +1,5 @@
.nav-menu#menu_top
- .brandname = t('brandname')
+ .brandname = link_to t('brandname'), root_path
.menu-content
.menu-item
@@ -11,12 +11,16 @@
span.fa.fa-lg.fa-tasks
= link_to '#', class: 'menu-item', data: { panel: 'toggle', target: '#profile_panel' }, title: 'Profil' do
- span = current_user.username
+ span = current_user.name
span.fa.fa-lg.fa-user
+ - if Rails.application.config.development_toolbar
+ = link_to '#', data: { toggle: 'modal', target: '#development-toolbar' }, class: "toolbar-button menu-item" do
+ .fa.fa-cog
= link_to destroy_user_session_path, method: :delete, class: 'menu-item', title: 'Se déconnecter' do
span.fa.fa-lg.fa-sign-out
+
= render 'layouts/navigation/nav_panel_operations'
= render 'layouts/navigation/nav_panel_profile' if user_signed_in?
diff --git a/app/views/layouts/navigation/_page_header.html.slim b/app/views/layouts/navigation/_page_header.html.slim
index 90fd7d855..e407e53da 100644
--- a/app/views/layouts/navigation/_page_header.html.slim
+++ b/app/views/layouts/navigation/_page_header.html.slim
@@ -1,23 +1,36 @@
-div.page_header
- div.container-fluid
- div.row
- div.col-lg-9.col-md-8.col-sm-7.col-xs-7
+- action_links = resource.action_links(params[:action]) rescue nil
+- action_links ||= decorated_collection.action_links(params[:action]) rescue nil
+
+.page_header
+ .container-fluid
+ .row
+ .col-lg-9.col-md-8.col-sm-7.col-xs-7
- if defined?(resource_class)
- div.page-icon
+ .page-icon
span.sb class="sb-#{resource_class.model_name.name.underscore}"
- div.page-title
+ .page-title
- if content_for? :page_header_title
h1 = yield :page_header_title
- else
- if defined?(resource_class)
h1 = t("#{resource_class.model_name.name.underscore.pluralize}.#{params[:action]}.title")
- div.col-lg-3.col-md-4.col-sm-5.col-xs-5.text-right
- div.page-action
+ .col-lg-3.col-md-4.col-sm-5.col-xs-5.text-right
+ .page-action
- if content_for? :page_header_meta
= yield :page_header_meta
- if content_for? :page_header_actions
= yield :page_header_actions
+ - if action_links&.primary&.any?
+ - action_links.primary.each do |link|
+ = link.to_html do |l|
+ - l.class "btn btn-default #{l.disabled ? "disabled" : ""}"
+ - if action_links&.secondary&.any?
+ .row.mb-sm
+ .col-lg-12.text-right
+ - action_links.secondary.each do |link|
+ = link.to_html do |l|
+ - l.class "btn btn-primary #{l.disabled ? "disabled" : ""}"
- if content_for? :page_header_content
= yield :page_header_content
diff --git a/app/views/layouts/snapshots/actions_links.html.slim b/app/views/layouts/snapshots/actions_links.html.slim
new file mode 100644
index 000000000..f1fa55e87
--- /dev/null
+++ b/app/views/layouts/snapshots/actions_links.html.slim
@@ -0,0 +1,21 @@
+doctype html
+html lang=I18n.locale
+ head
+ meta charset="utf-8"
+ meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
+
+ = csrf_meta_tag
+
+ title = t('brandname')
+
+ = stylesheet_link_tag 'base'
+ = stylesheet_link_tag 'application'
+
+ = javascript_pack_tag 'application'
+ = javascript_include_tag 'application'
+
+ body
+ = render 'layouts/navigation/main_nav'
+ = render 'layouts/flash_messages', flash: flash
+ div.page_header
+ = yield
diff --git a/app/views/layouts/snapshots/default.html.slim b/app/views/layouts/snapshots/default.html.slim
new file mode 100644
index 000000000..9e4565dcb
--- /dev/null
+++ b/app/views/layouts/snapshots/default.html.slim
@@ -0,0 +1,19 @@
+doctype html
+html lang=I18n.locale
+ head
+ meta charset="utf-8"
+ meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
+
+ = csrf_meta_tag
+
+ title = t('brandname')
+
+ = stylesheet_link_tag 'base'
+ = stylesheet_link_tag 'application'
+
+ = javascript_pack_tag 'application'
+ = javascript_include_tag 'application'
+
+ body
+ = yield
+
diff --git a/app/views/line_footnotes/show.html.slim b/app/views/line_footnotes/show.html.slim
index e4f2a1d42..29e1f5708 100644
--- a/app/views/line_footnotes/show.html.slim
+++ b/app/views/line_footnotes/show.html.slim
@@ -16,7 +16,11 @@
.panel-heading = footnote.code
.panel-body
p = footnote.label
+
.panel-footer.text-right
+ div
+ p.text-muted.small = Chouette::Footnote.human_attribute_name(:checksum)
+ p.text-muted.small = footnote.checksum
p.text-muted
em.small = "Dernière mise à jour le #{l(footnote.updated_at, format: :short)}"
diff --git a/app/views/line_referentials/show.html.slim b/app/views/line_referentials/show.html.slim
index b4b32bc52..763eb076e 100644
--- a/app/views/line_referentials/show.html.slim
+++ b/app/views/line_referentials/show.html.slim
@@ -1,7 +1,8 @@
- breadcrumb :line_referential, @line_referential
- page_header_content_for @line_referential
-- content_for :page_header_actions do
- = link_to(t('actions.sync'), sync_line_referential_path(@line_referential), method: :post, class: 'btn btn-default')
+- if policy(@line_referential).synchronize?
+ - content_for :page_header_actions do
+ = link_to(t('actions.sync'), sync_line_referential_path(@line_referential), method: :post, class: 'btn btn-default')
- content_for :page_header_content do
.row.mb-md
diff --git a/app/views/lines/_filters.html.slim b/app/views/lines/_filters.html.slim
index e3674656a..67ba297cf 100644
--- a/app/views/lines/_filters.html.slim
+++ b/app/views/lines/_filters.html.slim
@@ -1,25 +1,25 @@
= search_form_for @q, url: line_referential_lines_path(@line_referential), html: {method: :get}, class: 'form form-filter' do |f|
.ffg-row
- .input-group.search_bar
+ .input-group.search_bar class=filter_item_class(params[:q], :name_or_number_or_objectid_cont)
= f.search_field :name_or_number_or_objectid_cont, placeholder: t('lines.index.name_or_number_or_objectid'), class: 'form-control'
span.input-group-btn
button.btn.btn-default#search-btn type='submit'
span.fa.fa-search
.ffg-row
- .form-group.togglable
+ .form-group.togglable class=filter_item_class(params[:q], :network_id_eq_any)
= f.label Chouette::Line.human_attribute_name(:network_id), required: false, class: 'control-label'
= f.input :network_id_eq_any, collection: @line_referential.networks.order(name: :asc), as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + l.name + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'}
- .form-group.togglable
+ .form-group.togglable class=filter_item_class(params[:q], :company_id_eq_any)
= f.label Chouette::Line.human_attribute_name(:company_id), required: false, class: 'control-label'
= f.input :company_id_eq_any, collection: @line_referential.companies.order(name: :asc), as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + l.name + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'}
- .form-group.togglable
+ .form-group.togglable class=filter_item_class(params[:q], :transport_mode_eq_any)
= f.label Chouette::Line.human_attribute_name(:transport_mode), required: false, class: 'control-label'
= f.input :transport_mode_eq_any, collection: StifTransportModeEnumerations.sorted_transport_modes, as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + t("enumerize.transport_mode.#{l}") + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'}
- .form-group.togglable
+ .form-group.togglable class=filter_item_class(params[:q], :transport_submode_eq_any)
= f.label Chouette::Line.human_attribute_name(:transport_submode), required: false, class: 'control-label'
= f.input :transport_submode_eq_any, collection: StifTransportSubmodeEnumerations.sorted_transport_submodes, as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + t("enumerize.transport_submode.#{l}") + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'}
diff --git a/app/views/lines/_form.html.slim b/app/views/lines/_form.html.slim
index de0308289..909d6512e 100644
--- a/app/views/lines/_form.html.slim
+++ b/app/views/lines/_form.html.slim
@@ -3,7 +3,7 @@
.col-lg-12
= f.input :name
= f.input :network_id, as: :select, :collection => @line_referential.networks, include_blank: false
- = f.input :company_id, as: :select, :collection => @line_referential.companies, include_blank: false
+ = f.input :company_id, as: :select, :collection => @line_referential.companies, include_blank: true
= f.input :secondary_company_ids, :collection => @line_referential.companies, include_blank: false, input_html: { multiple: true, 'data-select2ed': true }, label: t('activerecord.attributes.line.secondary_company')
= f.input :published_name
= f.input :registration_number
@@ -20,4 +20,3 @@
.separator
= f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'lines_form'
-
diff --git a/app/views/lines/index.html.slim b/app/views/lines/index.html.slim
index 8b035b477..2d64e5f73 100644
--- a/app/views/lines/index.html.slim
+++ b/app/views/lines/index.html.slim
@@ -1,7 +1,4 @@
- breadcrumb :lines, @line_referential
-- content_for :page_header_actions do
- - if (policy(Chouette::Line).create? && @line_referential.organisations.include?(current_organisation))
- = link_to(t('lines.actions.new'), new_line_referential_line_path(@line_referential), class: 'btn btn-primary')
.page_content
.container-fluid
@@ -41,7 +38,7 @@
), \
TableBuilderHelper::Column.new( \
key: 'companies.name', \
- attribute: Proc.new { |n| n.try(:company).try(:name) } \
+ attribute: Proc.new { |n| n&.company&.name || "-" } \
), \
TableBuilderHelper::Column.new( \
key: :transport_mode, \
@@ -52,7 +49,6 @@
attribute: Proc.new { |n| n.transport_submode.present? ? t("enumerize.transport_submode.#{n.try(:transport_submode)}") : "-" } \
) \
],
- links: [:show],
cls: 'table has-filter has-search'
= new_pagination @lines, 'pull-right'
diff --git a/app/views/lines/show.html.slim b/app/views/lines/show.html.slim
index d62fe30d6..34b907bdd 100644
--- a/app/views/lines/show.html.slim
+++ b/app/views/lines/show.html.slim
@@ -1,14 +1,4 @@
- breadcrumb :line, @line
-- content_for :page_header_content do
- .row
- .col-lg-12.text-right.mb-sm
- - @line.action_links.each do |link|
- = link_to link.href,
- method: link.method,
- data: link.data,
- class: 'btn btn-primary' do
- = link.content
-
- page_header_content_for @line
.page_content
diff --git a/app/views/merges/_form.html.slim b/app/views/merges/_form.html.slim
new file mode 100644
index 000000000..b97a4b03e
--- /dev/null
+++ b/app/views/merges/_form.html.slim
@@ -0,0 +1,12 @@
+= simple_form_for merge, as: :merge, url: workbench_merges_path(workbench), html: {class: 'form-horizontal', id: 'wb_merge_form'}, wrapper: :horizontal_form do |form|
+
+ .row
+ .col-lg-12
+ = form.input :referentials, :collection => @mergeable_referentials, include_blank: false, input_html: { multiple: true, 'data-select2ed': true }
+
+
+ .row
+ .col-lg-12
+ = form.input :creator, input_html: { disabled: true }
+
+ = form.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'wb_merge_form'
diff --git a/app/views/merges/new.html.slim b/app/views/merges/new.html.slim
new file mode 100644
index 000000000..dab4bdf4e
--- /dev/null
+++ b/app/views/merges/new.html.slim
@@ -0,0 +1,7 @@
+- breadcrumb :merges, @workbench
+
+.page_content
+ .container-fluid
+ .row
+ .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1
+ = render 'form', merge: @merge, workbench: @workbench
diff --git a/app/views/merges/show.html.slim b/app/views/merges/show.html.slim
new file mode 100644
index 000000000..a94552c0d
--- /dev/null
+++ b/app/views/merges/show.html.slim
@@ -0,0 +1,17 @@
+- breadcrumb :merge, @merge
+- page_header_content_for @merge
+
+.page_content
+ .container-fluid
+ .row
+ .col-lg-6.col-md-6.col-sm-12.col-xs-12
+ = definition_list t('metadatas'),
+ { @merge.class.human_attribute_name(:referentials) => @merge.referentials.map(&:name).join(', '),
+ @merge.class.human_attribute_name(:operator) => @merge.creator,
+ @merge.class.human_attribute_name(:status) => @merge.status,
+ @merge.class.human_attribute_name(:created_at) => @merge.created_at ? l(@merge.created_at) : '-',
+ @merge.class.human_attribute_name(:started_at) => @merge.started_at ? l(@merge.started_at) : '-',
+ @merge.class.human_attribute_name(:ended_at) => @merge.ended_at ? l(@merge.ended_at) : '-' }
+
+ - if resource.output.current
+ = referential_overview resource.output.current
diff --git a/app/views/networks/_form.html.slim b/app/views/networks/_form.html.slim
index 362584f97..f91a112e8 100644
--- a/app/views/networks/_form.html.slim
+++ b/app/views/networks/_form.html.slim
@@ -4,11 +4,11 @@
= f.input :name, :input_html => { :title => t("formtastic.titles#{format_restriction_for_locales(@line_referential)}.network.name")}
= f.input :registration_number, :input_html => { :title => t("formtastic.titles#{format_restriction_for_locales(@line_referential)}.network.registration_number")}
= f.input :comment
- = f.input :version_date, :label_html => { :class => 'string optional col-sm-4 col-xs-5 control-label' }, :wrapper => :multi_select_inline
- = f.input :description
- = f.input :source_name
- = f.input :source_type_name, as: :select, :collection => Chouette::Network.source_type_name.options, :include_blank => true
- = f.input :source_identifier
+ / = f.input :version_date, :label_html => { :class => 'string optional col-sm-4 col-xs-5 control-label' }, :wrapper => :multi_select_inline
+ / = f.input :description
+ / = f.input :source_name
+ / = f.input :source_type_name, as: :select, :collection => Chouette::Network.source_type_name.options, :include_blank => true
+ / = f.input :source_identifier
.separator
= f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'network_form'
diff --git a/app/views/networks/index.html.slim b/app/views/networks/index.html.slim
index b13c73e9e..6aeb140cc 100644
--- a/app/views/networks/index.html.slim
+++ b/app/views/networks/index.html.slim
@@ -1,7 +1,4 @@
- breadcrumb :networks, @line_referential
-- content_for :page_header_actions do
- - if policy(Chouette::Network).create?
- = link_to(t('networks.actions.new'), new_line_referential_network_path(@line_referential), class: 'btn btn-primary')
.page_content
.container-fluid
diff --git a/app/views/networks/show.html.slim b/app/views/networks/show.html.slim
index f7d40a049..527fb8d5b 100644
--- a/app/views/networks/show.html.slim
+++ b/app/views/networks/show.html.slim
@@ -1,17 +1,13 @@
- breadcrumb :network, @network
- page_header_content_for @network
-- content_for :page_header_content do
- .row
- .col-lg-12.text-right.mb-sm
- - @network.action_links.each do |link|
- = link_to link.href,
- method: link.method,
- data: link.data,
- class: 'btn btn-primary' do
- = link.content
+
.page_content
.container-fluid
.row
.col-lg-6.col-md-6.col-sm-12.col-xs-12
= definition_list t('metadatas'),
- { 'ID Codif' => @network.try(:get_objectid).try(:short_id) }
+ { @network.human_attribute_name(:id) => @network.get_objectid.try(:short_id), \
+ @network.human_attribute_name(:name) => @network.name, \
+ @network.human_attribute_name(:registration_number) => @network.registration_number, \
+ @network.human_attribute_name(:comment) => (@network.comment.presence || '-'), \
+ }
diff --git a/app/views/purchase_windows/_date_value_fields.html.slim b/app/views/purchase_windows/_date_value_fields.html.slim
new file mode 100644
index 000000000..7bde06a94
--- /dev/null
+++ b/app/views/purchase_windows/_date_value_fields.html.slim
@@ -0,0 +1,13 @@
+.nested-fields
+ - if f.object.errors.has_key? :base
+ .row
+ .col-lg-12
+ .alert.alert-danger
+ - f.object.errors[:base].each do |message|
+ p.small = message
+
+ .wrapper
+ div
+ = f.input :value, as: :date, label: false, wrapper_html: { class: 'date smart_date' }
+ div
+ = link_to_remove_association '', f, class: 'fa fa-trash', data: { confirm: 'Etes-vous sûr(e) ?' }, title: t('actions.delete')
diff --git a/app/views/purchase_windows/_filters.html.slim b/app/views/purchase_windows/_filters.html.slim
new file mode 100644
index 000000000..eedbf31d5
--- /dev/null
+++ b/app/views/purchase_windows/_filters.html.slim
@@ -0,0 +1,15 @@
+= search_form_for @q, url: referential_purchase_windows_path, builder: SimpleForm::FormBuilder, html: { method: :get, class: 'form form-filter' } do |f|
+ .ffg-row
+ .input-group.search_bar class=filter_item_class(params[:q], :name_cont)
+ = f.search_field :name_cont, class: 'form-control', placeholder: t('purchase_windows.index.filter_placeholder')
+ span.input-group-btn
+ button.btn.btn-default#search_btn type='submit'
+ span.fa.fa-search
+
+ .form-group class=filter_item_class(params[:q], :contains_date)
+ = f.label Chouette::PurchaseWindow.human_attribute_name(:date), class: 'control-label'
+ = f.input :contains_date, as: :date, label: false, wrapper_html: { class: 'date smart_date' }, class: 'form-control', default: @date, include_blank: @date ? false : true
+
+ .actions
+ = link_to t('actions.erase'), referential_purchase_windows_path, class: 'btn btn-link'
+ = f.submit t('actions.filter'), id: 'purchase_window_filter_btn', class: 'btn btn-default'
diff --git a/app/views/purchase_windows/_form.html.slim b/app/views/purchase_windows/_form.html.slim
new file mode 100644
index 000000000..2101ae6db
--- /dev/null
+++ b/app/views/purchase_windows/_form.html.slim
@@ -0,0 +1,29 @@
+= simple_form_for [@referential, @purchase_window], html: { class: 'form-horizontal', id: 'purchase_window_form' }, wrapper: :horizontal_form do |f|
+ .row
+ .col-lg-12
+ = f.input :name
+ = f.input :color, as: :color_select, collection: Chouette::PurchaseWindow.colors_i18n
+
+ .separator
+
+ .row
+ .col-lg-12
+ .subform
+ .nested-head
+ .wrapper
+ div
+ .form-group
+ label.control-label
+ = t('simple_form.labels.purchase_window.ranges.begin')
+ div
+ .form-group
+ label.control-label
+ = t('simple_form.labels.purchase_window.ranges.end')
+ div
+
+ = f.simple_fields_for :periods do |period|
+ = render 'period_fields', f: period
+ .links.nested-linker
+ = link_to_add_association t('simple_form.labels.purchase_window.add_a_date_range'), f, :periods, class: 'btn btn-outline-primary'
+
+ = f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'purchase_window_form'
diff --git a/app/views/purchase_windows/_period_fields.html.slim b/app/views/purchase_windows/_period_fields.html.slim
new file mode 100644
index 000000000..95e204554
--- /dev/null
+++ b/app/views/purchase_windows/_period_fields.html.slim
@@ -0,0 +1,15 @@
+.nested-fields
+ - if f.object.errors.has_key? :base
+ .row
+ .col-lg-12
+ .alert.alert-danger
+ - f.object.errors[:base].each do |message|
+ p.small = message
+
+ .wrapper
+ div
+ = f.input :begin, as: :date, label: false, wrapper_html: { class: 'date smart_date' }
+ div
+ = f.input :end, as: :date, label: false, wrapper_html: { class: 'date smart_date' }
+ div
+ = link_to_remove_association '', f, class: 'fa fa-trash', data: { confirm: 'Etes-vous sûr(e) ?' }, title: t('actions.delete')
diff --git a/app/views/purchase_windows/edit.html.slim b/app/views/purchase_windows/edit.html.slim
new file mode 100644
index 000000000..6354db853
--- /dev/null
+++ b/app/views/purchase_windows/edit.html.slim
@@ -0,0 +1,7 @@
+- breadcrumb :purchase_window, @referential, @purchase_window
+- page_header_content_for @purchase_window
+.page_content
+ .container-fluid
+ .row
+ .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1
+ = render 'form'
diff --git a/app/views/purchase_windows/index.html.slim b/app/views/purchase_windows/index.html.slim
new file mode 100644
index 000000000..0dba86935
--- /dev/null
+++ b/app/views/purchase_windows/index.html.slim
@@ -0,0 +1,41 @@
+- breadcrumb :purchase_windows, @referential
+
+.page_content
+ .container-fluid
+ - if params[:q].present? or @purchase_windows.any?
+ .row
+ .col-lg-12
+ = render 'filters'
+
+ - if @purchase_windows.any?
+ .row
+ .col-lg-12
+ = table_builder_2 @purchase_windows,
+ [ \
+ TableBuilderHelper::Column.new( \
+ key: :name, \
+ attribute: 'name', \
+ link_to: lambda do |purchase_window| \
+ referential_purchase_window_path(purchase_window.referential, purchase_window) \
+ end \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :color, \
+ attribute: Proc.new { |tt| tt.color ? content_tag(:span, '', class: 'fa fa-circle', style: "color:#{tt.color}") : '-' }\
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :bounding_dates, \
+ attribute: Proc.new {|w| w.bounding_dates.nil? ? '-' : t('validity_range', debut: l(w.bounding_dates.begin, format: :short), end: l(w.bounding_dates.end, format: :short))}, \
+ sortable: false \
+ ) \
+ ],
+ cls: 'table has-filter'
+
+ = new_pagination @purchase_windows, 'pull-right'
+
+ - unless @purchase_windows.any?
+ .row.mt-xs
+ .col-lg-12
+ = replacement_msg t('purchase_windows.search_no_results')
+
+= javascript_pack_tag 'date_filters'
diff --git a/app/views/purchase_windows/new.html.slim b/app/views/purchase_windows/new.html.slim
new file mode 100644
index 000000000..402084167
--- /dev/null
+++ b/app/views/purchase_windows/new.html.slim
@@ -0,0 +1,6 @@
+- breadcrumb :purchase_windows, @referential
+.page_content
+ .container-fluid
+ .row
+ .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1
+ = render 'form'
diff --git a/app/views/purchase_windows/show.html.slim b/app/views/purchase_windows/show.html.slim
new file mode 100644
index 000000000..4e836f424
--- /dev/null
+++ b/app/views/purchase_windows/show.html.slim
@@ -0,0 +1,12 @@
+- breadcrumb :purchase_window, @referential, @purchase_window
+- page_header_content_for @purchase_window
+
+.page_content
+ .container-fluid
+ .row
+ .col-lg-6.col-md-6.col-sm-12.col-xs-12
+ = definition_list t('metadatas'),
+ { Chouette::PurchaseWindow.human_attribute_name(:name) => @purchase_window.try(:name),
+ 'Organisation' => @purchase_window.referential.organisation.name,
+ Chouette::PurchaseWindow.human_attribute_name(:date_ranges) => @purchase_window.periods.map{|d| t('validity_range', debut: l(d.begin, format: :short), end: l(d.end, format: :short))}.join('<br>').html_safe,
+ Chouette::PurchaseWindow.human_attribute_name(:checksum) => @purchase_window.checksum }
diff --git a/app/views/referential_companies/index.html.slim b/app/views/referential_companies/index.html.slim
index de0f7de69..3bff448c7 100644
--- a/app/views/referential_companies/index.html.slim
+++ b/app/views/referential_companies/index.html.slim
@@ -1,7 +1,4 @@
- breadcrumb :referential_companies, @referential
-- content_for :page_header_actions do
- - if policy(Chouette::Company).create?
- = link_to(t('companies.actions.new'), new_referential_company_path(@referential), class: 'btn btn-default')
.page_content
.container-fluid
@@ -46,7 +43,6 @@
attribute: 'url' \
) \
],
- links: [:show, :edit],
cls: 'table has-search'
= new_pagination @companies, 'pull-right'
diff --git a/app/views/referential_lines/_filters.html.slim b/app/views/referential_lines/_filters.html.slim
index 379d6234f..501f61c16 100644
--- a/app/views/referential_lines/_filters.html.slim
+++ b/app/views/referential_lines/_filters.html.slim
@@ -1,13 +1,13 @@
= search_form_for @q, url: referential_line_path(@referential, @line), class: 'form form-filter' do |f|
.ffg-row
- .input-group.search_bar
+ .input-group.search_bar class=filter_item_class(params[:q], :name_or_objectid_cont)
= f.search_field :name_or_objectid_cont, class: 'form-control', placeholder: "Indiquez un nom d'itinéraire ou un ID..."
span.input-group-btn
button.btn.btn-default#search-btn type='submit'
span.fa.fa-search
.ffg-row
- .form-group.togglable
+ .form-group.togglable class=filter_item_class(params[:q], :wayback_eq_any)
= f.label Chouette::Route.human_attribute_name(:wayback), required: false, class: 'control-label'
= f.input :wayback_eq_any, class: 'form-control', collection: Chouette::Route.wayback.values, as: :check_boxes, label: false, required: false, wrapper_html: { class: 'checkbox_list'}, label_method: lambda{|l| ("<span>" + t("enumerize.route.wayback.#{l}") + "</span>").html_safe}
diff --git a/app/views/referential_lines/show.html.slim b/app/views/referential_lines/show.html.slim
index cfba8cab3..5ea0e31bb 100644
--- a/app/views/referential_lines/show.html.slim
+++ b/app/views/referential_lines/show.html.slim
@@ -1,13 +1,4 @@
- breadcrumb :referential_line, @referential, @line
-- content_for :page_header_content do
- .row
- .col-lg-12.text-right.mb-sm
- - @line.action_links.each do |link|
- = link_to link.href,
- method: link.method,
- data: link.data,
- class: 'btn btn-primary' do
- = link.content
- page_header_content_for @line
.page_content
@@ -26,7 +17,8 @@
@line.human_attribute_name(:transport_submode) => (@line.transport_submode.present? ? t("enumerize.transport_submode.#{@line.transport_submode}") : '-'),
@line.human_attribute_name(:url) => (@line.url ? @line.url : '-'),
@line.human_attribute_name(:seasonal) => (@line.seasonal? ? t('true') : t('false')),}
-
+ .col-lg-6.col-md-6.col-sm-12.col-xs-12
+ #routes_map.map.mb-lg
.row
.col-lg-12
.h3 = t('lines.show.routes.title')
@@ -79,8 +71,8 @@
attribute: Proc.new{ |r| r.try(:journey_patterns).count } \
) \
],
- links: [:show, :edit],
- cls: 'table has-search'
+ cls: 'table has-search',
+ action: :index
= new_pagination @routes, 'pull-right'
@@ -88,3 +80,8 @@
.row.mt-xs
.col-lg-12
= replacement_msg t('routes.search_no_results')
+
+= javascript_tag do
+ | window.routes = "#{URI.escape(@routes.map{|r| {name: r.name, id: r.id, stops: route_json_for_edit(r, serialize: false)}}.to_json)}"
+
+= javascript_pack_tag 'referential_lines/show.js'
diff --git a/app/views/referential_networks/index.html.slim b/app/views/referential_networks/index.html.slim
index d556e7e5e..efa28dc05 100644
--- a/app/views/referential_networks/index.html.slim
+++ b/app/views/referential_networks/index.html.slim
@@ -1,7 +1,4 @@
- breadcrumb :referential_networks, @referential
-- if policy(Chouette::Network).create?
- - content_for :page_header_actions do
- = link_to(t('networks.actions.new'), new_referential_network_path(@referential), class: 'btn btn-default')
.page_content
.container-fluid
diff --git a/app/views/referential_networks/show.html.slim b/app/views/referential_networks/show.html.slim
index 7de304671..3d13d9211 100644
--- a/app/views/referential_networks/show.html.slim
+++ b/app/views/referential_networks/show.html.slim
@@ -1,14 +1,5 @@
- breadcrumb :referential_network, @referential, @network
- page_header_content_for @network
-- content_for :page_header_content do
- .row
- .col-lg-12.text-right.mb-sm
- - @network.action_links.each do |link|
- = link_to link.href,
- method: link.method,
- data: link.data,
- class: 'btn btn-primary' do
- = link.content
/ PageContent
.page_content
diff --git a/app/views/referential_stop_areas/_form.html.slim b/app/views/referential_stop_areas/_form.html.slim
index 50f5d4aaf..8181ec3f3 100644
--- a/app/views/referential_stop_areas/_form.html.slim
+++ b/app/views/referential_stop_areas/_form.html.slim
@@ -7,7 +7,7 @@
= form.inputs do
= form.input :id, as: :hidden
= form.input :name, :input_html => { :title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.name")}
- = form.input :stop_area_type, as: :select, :input_html => { :disabled => !@stop_area.new_record? }, :collection => Chouette::StopArea.area_type.options, :include_blank => false }
+ = form.input :stop_area_type, as: :select, :input_html => { :disabled => !@stop_area.new_record? }, :collection => Chouette::StopArea.area_type.options, :include_blank => false
.location_info
h3 = t("stop_areas.stop_area.localisation")
diff --git a/app/views/referential_stop_areas/show.html.slim b/app/views/referential_stop_areas/show.html.slim
index 0470b4654..cb04ab7a6 100644
--- a/app/views/referential_stop_areas/show.html.slim
+++ b/app/views/referential_stop_areas/show.html.slim
@@ -1,14 +1,5 @@
- breadcrumb :referential_stop_area, @referential, @stop_area
- page_header_content_for @stop_area
-- content_for :page_header_content do
- .row
- .col-lg-12.text-right.mb-sm
- - @stop_area.action_links.each do |link|
- = link_to link.href,
- method: link.method,
- data: link.data,
- class: 'btn btn-primary' do
- = link.content
.page_content
.container-fluid
diff --git a/app/views/referential_vehicle_journeys/_filters.html.slim b/app/views/referential_vehicle_journeys/_filters.html.slim
new file mode 100644
index 000000000..3104e3a71
--- /dev/null
+++ b/app/views/referential_vehicle_journeys/_filters.html.slim
@@ -0,0 +1,62 @@
+= search_form_for @q, url: referential_vehicle_journeys_path(@referential), html: {method: :get}, class: 'form form-filter' do |f|
+ .ffg-row
+ .input-group.search_bar
+ = f.search_field :published_journey_name_or_objectid_cont, placeholder: t('.published_journey_name_or_objectid'), class: 'form-control'
+ span.input-group-btn
+ button.btn.btn-default#search-btn type='submit'
+ span.fa.fa-search
+ .ffg-row
+ .form-group.per-page-select
+ = I18n.t("simple_form.per_page")
+ = %w(10 50 100).each_with_index.map{ |v, i| (params[:per_page] == v || params[:per_page].nil? && i == 0) ? "<span class='selected'>#{v}</span>" : link_to(v, referential_vehicle_journeys_path(@referential, q: params[:q], per_page: v)) }.join(' - ').html_safe
+ .form-group.togglable class=filter_item_class(params[:q], :company_id_eq_any)
+ = f.label Chouette::VehicleJourney.human_attribute_name(:company), required: false, class: 'control-label'
+ - if @all_companies.present?
+ = f.input :company_id_eq_any, collection: @all_companies.select(:id, :name).order(name: :asc), as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + l.name + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'}
+ - else
+ = f.input :company_id_eq_any, collection: [[I18n.t('companies.search_no_results_for_filter'), nil]], as: :check_boxes, label: false, disabled: true, required: false, wrapper_html: { class: 'checkbox_list disabled'}
+
+ .form-group.togglable class=filter_item_class(params[:q], :route_line_id_eq)
+ = f.label Chouette::Line.model_name.human,
+ required: false,
+ class: 'control-label'
+ .form-inline.filter_menu
+ = f.input :route_line_id_eq,
+ as: :select,
+ include_blank: t(".all"),
+ collection: @vehicle_journeys.lines,
+ selected: params[:q] && params[:q][:route_line_id_eq],
+ input_html: { \
+ 'data-select2ed': 'true',
+ 'data-select2ed-placeholder': t('referentials.filters.line') \
+ },
+ label: false,
+ label_method: :display_name,
+ wrapper_html: { class: 'filter_menu-item select2ed' }
+
+ .form-group.togglable.name-filter class=filter_item_class(params[:q], :published_journey_name_gteq)
+ = f.label Chouette::VehicleJourney.human_attribute_name(:published_journey_name), required: false, class: 'control-label'
+ .inputs.form-inline.checkbox_list
+ = f.input :published_journey_name_gteq, label: false, wrapper_html: { class: 'w45'}
+ .form-group.w10.to= I18n.t('vehicle_journeys.form.to')
+ = f.input :published_journey_name_lteq, label: false, wrapper_html: { class: 'w45'}
+ .form-group.togglable class=filter_item_class(params[:q], :stop_area_ids)
+ = f.label Chouette::StopArea.model_name.human.pluralize, required: false, class: 'control-label'
+ = f.input :stop_area_ids, collection: @all_stop_areas.select(:id, :name).order(name: :asc), checked: params[:q] && params[:q][:stop_area_ids], as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + l.name + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'}, multiple: true
+ .form-group.togglable class=filter_item_class(params[:q], :purchase_window)
+ = f.label Chouette::VehicleJourney.human_attribute_name(:purchase_window), class: 'control-label'
+ .filter_menu
+ = f.simple_fields_for :purchase_window do |p|
+ = p.input :start_date, as: :date, label: t('simple_form.from'), wrapper_html: { class: 'date smart_date filter_menu-item' }, default: @purchase_window_begin_range, include_blank: @purchase_window_begin_range ? false : true
+ = p.input :end_date, as: :date, label: t('simple_form.to'), wrapper_html: { class: 'date smart_date filter_menu-item' }, default: @purchase_window_end_range, include_blank: @purchase_window_end_range ? false : true
+ .form-group.togglable class=filter_item_class(params[:q], :time_table)
+ = f.label Chouette::TimeTable.model_name.human, class: 'control-label'
+ .filter_menu
+ = f.simple_fields_for :time_table do |p|
+ = p.input :start_date, as: :date, label: t('simple_form.from'), wrapper_html: { class: 'date smart_date filter_menu-item' }, default: @time_table_begin_range, include_blank: @time_table_begin_range ? false : true
+ = p.input :end_date, as: :date, label: t('simple_form.to'), wrapper_html: { class: 'date smart_date filter_menu-item' }, default: @time_table_end_range, include_blank: @time_table_end_range ? false : true
+
+
+ .actions
+ = link_to 'Effacer', referential_vehicle_journeys_path(@referential), class: 'btn btn-link'
+ = f.submit 'Filtrer', class: 'btn btn-default'
diff --git a/app/views/referential_vehicle_journeys/index.html.slim b/app/views/referential_vehicle_journeys/index.html.slim
new file mode 100644
index 000000000..69e29597c
--- /dev/null
+++ b/app/views/referential_vehicle_journeys/index.html.slim
@@ -0,0 +1,59 @@
+- breadcrumb :referential_vehicle_journeys, @referential
+- content_for :page_header_title, t('.title')
+
+.page_content
+ .container-fluid
+ - if params[:q].present? or @vehicle_journeys.present?
+ .row
+ .col-lg-12
+ = render 'filters'
+
+ - if @vehicle_journeys.present?
+ .row
+ .col-lg-12
+ .select_table
+ = table_builder_2 @vehicle_journeys,
+ [ \
+ TableBuilderHelper::Column.new( \
+ name: t('objectid'), \
+ attribute: Proc.new { |n| n.get_objectid.short_id }, \
+ sortable: false \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :published_journey_name, \
+ attribute: 'published_journey_name', \
+ link_to: lambda do |vehicle_journey| \
+ vehicle_journey.published_journey_name ? referential_line_route_vehicle_journeys_path(@referential, vehicle_journey.route.line, vehicle_journey.route) : '' \
+ end, \
+ sortable: false \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :line, \
+ attribute: Proc.new {|v| v.route.line.name}, \
+ sortable: false \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :route, \
+ attribute: Proc.new {|v| v.route.name}, \
+ sortable: false \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :departure_time, \
+ attribute: Proc.new {|v| v.vehicle_journey_at_stops.first&.departure }, \
+ sortable: false \
+ ), \
+ @filters_stop_areas&.map{|s| table_builder_column_for_stop_area(s)},
+ TableBuilderHelper::Column.new( \
+ key: :arrival_time, \
+ attribute: Proc.new {|v| v.vehicle_journey_at_stops.last&.arrival }, \
+ sortable: false \
+ ), \
+ ].flatten.compact,
+ cls: 'table has-filter has-search'
+
+ = new_pagination @vehicle_journeys, 'pull-right'
+
+ - unless @vehicle_journeys.any?
+ .row.mt-xs
+ .col-lg-12
+ = replacement_msg t('.search_no_results')
diff --git a/app/views/referentials/_filters.html.slim b/app/views/referentials/_filters.html.slim
index c5b6042f0..190e70ebe 100644
--- a/app/views/referentials/_filters.html.slim
+++ b/app/views/referentials/_filters.html.slim
@@ -1,22 +1,22 @@
= search_form_for @q, url: referential_path(@referential.id), class: 'form form-filter' do |f|
.ffg-row
- .input-group.search_bar
+ .input-group.search_bar class=filter_item_class(params[:q], :name_or_number_or_objectid_cont)
= f.search_field :name_or_number_or_objectid_cont, class: 'form-control', placeholder: t('.name_or_number_or_objectid')
span.input-group-btn
button.btn.btn-default type='submit'
span.fa.fa-search
.ffg-row
- .form-group.togglable
+ .form-group.togglable class=filter_item_class(params[:q], :transport_mode_eq_any)
= f.label Chouette::Line.human_attribute_name(:transport_mode), required: false, class: 'control-label'
= f.input :transport_mode_eq_any, collection: @referential.lines.pluck(:transport_mode).uniq.compact, as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + t("enumerize.transport_mode.#{l}") + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list' }
- .form-group.togglable
- = f.label Chouette::Line.human_attribute_name(:network), required: false, class: 'control-label'
+ .form-group.togglable class=filter_item_class(params[:q], :network_id_eq_any)
+ = f.label t('activerecord.attributes.referential.networks'), required: false, class: 'control-label'
= f.input :network_id_eq_any, collection: LineReferential.first.networks.order('name').pluck(:id), as: :check_boxes, label: false, label_method: lambda{|l| ("<span>#{LineReferential.first.networks.find(l).name}</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list' }
- .form-group.togglable
- = f.label Chouette::Line.human_attribute_name(:company), required: false, class: 'control-label'
+ .form-group.togglable class=filter_item_class(params[:q], :company_id_eq_any)
+ = f.label t('activerecord.attributes.referential.companies'), required: false, class: 'control-label'
= f.input :company_id_eq_any, collection: LineReferential.first.companies.order('name').pluck(:id), as: :check_boxes, label: false, label_method: lambda{|l| ("<span>#{LineReferential.first.companies.find(l).name}</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list' }
.actions
diff --git a/app/views/referentials/_form.html.slim b/app/views/referentials/_form.html.slim
index 6f7da84c7..9927f05bd 100644
--- a/app/views/referentials/_form.html.slim
+++ b/app/views/referentials/_form.html.slim
@@ -17,7 +17,7 @@
.row
.col-lg-12
- if @referential.errors.has_key? :metadatas
- .row
+ .row.metadatas-errors
.col-lg-12
.alert.alert-danger
- @referential.errors[:metadatas].each do |msg|
@@ -51,4 +51,8 @@
.hidden = form.input :workbench_id, as: :hidden
- = form.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'referential_form'
+ = form.button :submit,
+ t('actions.submit'),
+ class: 'btn btn-default formSubmitr',
+ data: { disable_with: t('actions.processing') },
+ form: 'referential_form'
diff --git a/app/views/referentials/_overview.html.slim b/app/views/referentials/_overview.html.slim
new file mode 100644
index 000000000..143784800
--- /dev/null
+++ b/app/views/referentials/_overview.html.slim
@@ -0,0 +1,75 @@
+.referential-overview id=overview.pagination_param_name
+ .filters
+ = search_form_for overview.search, as: overview.search_param_name, url: "##{overview.pagination_param_name}", html: {method: :get}, class: 'form form-filter' do |f|
+ .ffg-row
+ .form-group.input-group.search_bar
+ = f.search_field :name_or_number_or_objectid_cont, placeholder: t('lines.index.name_or_number_or_objectid'), class: 'form-control'
+ span.input-group-btn
+ button.btn.btn-default#search-btn type='submit'
+ span.fa.fa-search
+ .form-group.togglable
+ = f.label Chouette::Line.human_attribute_name(:company_id), required: false, class: 'control-label'
+ = f.input :company_id_eq_any, collection: overview.referential_lines.map(&:company).uniq.sort_by(&:name), as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + l.name + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'}
+
+ .form-group.togglable
+ = f.label Chouette::Line.human_attribute_name(:transport_mode), required: false, class: 'control-label'
+ = f.input :transport_mode_eq_any, collection: overview.referential_lines.map(&:transport_mode).uniq.sort, as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + t("enumerize.transport_mode.#{l}") + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'}
+
+ .actions
+ = link_to 'Effacer', url_for() + "##{overview.pagination_param_name}", class: 'btn btn-link'
+ = f.submit 'Filtrer', class: 'btn btn-default'
+
+ .time-travel
+ .btn-group
+ = link_to I18n.t("referentials.overview.head.prev_page"), '#', class: "prev-page btn btn-default disabled"
+ = link_to I18n.t("referentials.overview.head.today"), '#', class: "today btn btn-default #{overview.includes_today? ? '' : 'disabled'}"
+ = link_to I18n.t("referentials.overview.head.next_page"), '#', class: "next-page btn btn-default"
+ .form-group
+ input.date-search type="date" min=overview.period.first max=overview.period.last
+ a.search-date href='#'
+ span.fa.fa-search
+ .overview-table
+ .left
+ .head
+ .dates= I18n.t("referentials.overview.head.dates")
+ .lines= I18n.t("referentials.overview.head.lines")
+ .lines
+ - overview.lines.each do |line|
+ .line
+ a.number style="background-color: #{line.color.present? ? "##{line.color}" : 'whitesmoke'}" title=line.name
+ = line.number
+ .company= line.company&.name
+ .mode= t("enumerize.transport_mode.#{line.transport_mode}")
+ .right
+ .inner
+ .head
+ - overview.weeks.each do |week|
+ .week
+ .week-span= week.span
+ .week-number= week.number
+ .days
+ - week.days.each do |day|
+ .day style=day.html_style class=day.html_class
+ .name= day.short_name
+ .number= day.number
+ .lines
+ - if overview.lines.any?
+ - overview.lines.each do |line|
+ .line style=line.html_style class=line.html_class
+ - line.periods.each do |period|
+ .period style=period.html_style class=period.html_class
+ .title=period.title
+ - else
+ = replacement_msg t('referential_lines.search_no_results')
+
+ = new_pagination overview, 'pull-right'
+
+- content_for :javascript do
+ = javascript_pack_tag 'referential_overview/overview.js'
+
+ javascript:
+ overview_id = "#{overview.pagination_param_name}";
+
+ coffee:
+ $ =>
+ new ReferentialOverview("##{overview_id}")
diff --git a/app/views/referentials/new.html.slim b/app/views/referentials/new.html.slim
index 2bed9f912..13d58ee71 100644
--- a/app/views/referentials/new.html.slim
+++ b/app/views/referentials/new.html.slim
@@ -1,3 +1,5 @@
+- breadcrumb :referentials
+
.page_content
.container-fluid
.row
diff --git a/app/views/referentials/show.html.slim b/app/views/referentials/show.html.slim
index 9852fb0a3..289e802d7 100644
--- a/app/views/referentials/show.html.slim
+++ b/app/views/referentials/show.html.slim
@@ -1,31 +1,16 @@
- breadcrumb @referential
- page_header_content_for @referential
-- content_for :page_header_actions do
- - unless (@referential.archived? || !policy(@referential).edit?)
- = link_to(t('actions.edit'), edit_referential_path(@referential), class: 'btn btn-default')
-
-- content_for :page_header_content do
- .row.mb-sm
- .col-lg-12.text-right
- - @referential.action_links.each do |link|
- - if link.is_a?(HTMLElement)
- = link.to_html(class: 'btn btn-primary')
- - else
- = link_to link.href,
- method: link.method,
- data: link.data,
- class: 'btn btn-primary' do
- = link.content
.page_content
.container-fluid
.row
.col-lg-6.col-md-6.col-sm-12.col-xs-12
- = definition_list t('metadatas'),
- { t('activerecord.attributes.referential.status') => @referential.archived? ? "<div class='td-block'><span class='fa fa-archive'></span><span>#{t('activerecord.attributes.referential.archived_at')}</span></div>".html_safe : "<div class='td-block'><span class='sb sb-lg sb-preparing'></span><span>#{t('activerecord.attributes.referential.archived_at_null')}</span></div>".html_safe,
- @referential.human_attribute_name(:validity_period) => (@referential.validity_period.present? ? t('validity_range', debut: l(@referential.try(:validity_period).try(:begin), format: :short), end: l(@referential.try(:validity_period).try(:end), format: :short)) : '-'),
- @referential.human_attribute_name(:organisation) => @referential.organisation.name,
- @referential.human_attribute_name(:published_at) => '-' }
+ - attributes = {}
+ - attributes[@referential.human_attribute_name(:status)] = @referential.referential_read_only? ? "<div class='td-block'><span class='fa fa-archive'></span><span>#{t('activerecord.attributes.referential.archived_at')}</span></div>".html_safe : "<div class='td-block'><span class='sb sb-lg sb-preparing'></span><span>#{t('activerecord.attributes.referential.archived_at_null')}</span></div>".html_safe unless @referential.in_referential_suite?
+ - attributes[@referential.human_attribute_name(:validity_period)] = (@referential.validity_period.present? ? t('validity_range', debut: l(@referential.try(:validity_period).try(:begin), format: :short), end: l(@referential.try(:validity_period).try(:end), format: :short)) : '-')
+ - attributes[@referential.human_attribute_name(:organisation)] = @referential.organisation.name
+ - attributes[@referential.human_attribute_name(:merged_at)] = @referential.merged_at ? l(@referential.merged_at, format: :short) : '-' unless @referential.in_referential_suite?
+ = definition_list t('metadatas'), attributes
- if params[:q].present? or @reflines.any?
.row
@@ -68,11 +53,11 @@
), \
TableBuilderHelper::Column.new( \
key: 'companies.name', \
- attribute: Proc.new { |n| n.try(:company).try(:name) } \
+ attribute: Proc.new { |n| n&.company&.name || "-" } \
) \
],
- links: [:show],
- cls: 'table has-filter has-search'
+ cls: 'table has-filter has-search',
+ action: :index
= new_pagination @reflines, 'pull-right'
@@ -82,6 +67,8 @@
= replacement_msg t('referential_lines.search_no_results')
+ = referential_overview resource
+
/ Modal(s)
= modalbox 'purgeModal' do
= simple_form_for [@referential, CleanUp.new] do |f|
@@ -102,5 +89,5 @@
.modal-footer
button.btn.btn-link type='button' data-dismiss='modal' #{t('cancel')}
- - unless policy(@referential).archived?
+ - unless policy(@referential).referential_read_only?
= f.button :submit, t('actions.clean_up') , class: 'btn btn-primary'
diff --git a/app/views/routes/_form.html.slim b/app/views/routes/_form.html.slim
index 29e5be3d2..81f719437 100644
--- a/app/views/routes/_form.html.slim
+++ b/app/views/routes/_form.html.slim
@@ -27,7 +27,7 @@
// Get JSON data for route stop points
= javascript_tag do
| window.itinerary_stop = "#{URI.escape(route_json_for_edit(@route))}";
- | window.I18n = #{(I18n.backend.send(:translations)[I18n.locale].to_json).html_safe};
+ // | window.I18n = #{(I18n.backend.send(:translations)[I18n.locale].to_json).html_safe};
/ StopPoints Reactux component
= javascript_pack_tag 'routes/edit.js'
diff --git a/app/views/routes/show.html.slim b/app/views/routes/show.html.slim
index 3adf3e2f6..375d7c57b 100644
--- a/app/views/routes/show.html.slim
+++ b/app/views/routes/show.html.slim
@@ -1,18 +1,5 @@
- breadcrumb :route, @referential, @route
- page_header_content_for @route
-- content_for :page_header_actions do
- - if policy(@route).edit?
- = link_to(t('actions.edit'), edit_referential_line_route_path(@referential, @line, @route), class: 'btn btn-default')
-
-- content_for :page_header_content do
- .row.mb-sm
- .col-lg-12.text-right
- - @route.action_links.each do |link|
- = link_to link.href,
- method: link.method,
- data: link.data,
- class: 'btn btn-primary' do
- = link.content
.page_content
.container-fluid
@@ -22,7 +9,8 @@
{ t('objectid') => @route.get_objectid.short_id,
t('activerecord.attributes.route.published_name') => (@route.published_name ? @route.published_name : '-'),
@route.human_attribute_name(:wayback) => (@route.wayback ? @route.wayback_text : '-' ),
- @route.human_attribute_name(:opposite_route) => (@route.opposite_route ? @route.opposite_route.name : '-') }
+ @route.human_attribute_name(:opposite_route) => (@route.opposite_route ? @route.opposite_route.name : '-'),
+ @route.human_attribute_name(:checksum) => @route.checksum }
- if @route_sp.any?
.col-lg-6.col-md-6.col-sm-12.col-xs-12
@@ -40,7 +28,7 @@
), \
TableBuilderHelper::Column.new( \
key: :name, \
- attribute: Proc.new {|s| s.try(:stop_area).try(:name)}, \
+ attribute: Proc.new { |s| content_tag :span, s.stop_area&.name, class: s.stop_area&.area_type }, \
link_to: lambda do |stop_point| \
referential_stop_area_path(@referential, stop_point.stop_area) \
end \
@@ -66,9 +54,9 @@
attribute: Proc.new { |s| t("stop_points.stop_point.for_alighting.#{s.for_alighting}") } \
) \
],
- links: [:show],
sortable: false,
- cls: 'table has-stoppoints'
+ cls: 'table has-stoppoints',
+ action: :index
- else
= replacement_msg t('stop_areas.search_no_results')
diff --git a/app/views/routing_constraint_zones/_filters.html.slim b/app/views/routing_constraint_zones/_filters.html.slim
index 433dde4ab..74e299a8b 100644
--- a/app/views/routing_constraint_zones/_filters.html.slim
+++ b/app/views/routing_constraint_zones/_filters.html.slim
@@ -1,13 +1,13 @@
= search_form_for @q, url: referential_line_routing_constraint_zones_path(@referential, @line), class: 'form form-filter' do |f|
.ffg-row
- .input-group.search_bar
+ .input-group.search_bar class=filter_item_class(params[:q], :name_or_objectid_cont)
= f.search_field :name_or_objectid_cont, class: 'form-control', placeholder: "Indiquez un nom d'ITL ou un ID..."
span.input-group-btn
button.btn.btn-default#search-btn type='submit'
span.fa.fa-search
.ffg-row
- .form-group
+ .form-group class=filter_item_class(params[:q], :route_id_eq)
= f.label 'Itinéraire associé', required: false, class: 'control-label'
= f.input :route_id_eq, as: :select, collection: @line.routing_constraint_zones.pluck(:route_id).uniq, label: false, label_method: lambda { |r| @line.routing_constraint_zones.find_by(route_id: r).route_name }, input_html: { 'data-select2ed': 'true', 'data-select2ed-placeholder': 'Indiquez un itinéraire...' }, wrapper_html: { class: 'select2ed'}
diff --git a/app/views/routing_constraint_zones/index.html.slim b/app/views/routing_constraint_zones/index.html.slim
index 7c54fca68..2f67b467e 100644
--- a/app/views/routing_constraint_zones/index.html.slim
+++ b/app/views/routing_constraint_zones/index.html.slim
@@ -1,7 +1,4 @@
- breadcrumb :routing_constraint_zones, @referential, @line
-- content_for :page_header_actions do
- - if (policy(Chouette::RoutingConstraintZone).create? && @referential.organisation == current_organisation)
- = link_to(t('actions.new'), new_referential_line_routing_constraint_zone_path(@referential, @line), class: 'btn btn-primary')
.page_content
.container-fluid
diff --git a/app/views/routing_constraint_zones/show.html.slim b/app/views/routing_constraint_zones/show.html.slim
index 6235ade68..8c8e9b17a 100644
--- a/app/views/routing_constraint_zones/show.html.slim
+++ b/app/views/routing_constraint_zones/show.html.slim
@@ -1,14 +1,5 @@
- breadcrumb :routing_constraint_zone, @referential, @line, @routing_constraint_zone
- page_header_content_for @routing_constraint_zone
-- content_for :page_header_content do
- .row
- .col-lg-12.text-right.mb-sm
- - @routing_constraint_zone.action_links.each do |link|
- = link_to link.href,
- method: link.method,
- data: link.data,
- class: 'btn btn-primary' do
- = link.content
.page_content
.container-fluid
@@ -17,7 +8,8 @@
= definition_list t('metadatas'),
{ @routing_constraint_zone.human_attribute_name(:name) => @routing_constraint_zone.try(:name),
@routing_constraint_zone.human_attribute_name(:route) => link_to(@routing_constraint_zone.try(:route_name), [@referential, @line, @routing_constraint_zone.route]),
- @routing_constraint_zone.human_attribute_name(:line) => link_to(@line.name, [@referential, @line])}
+ @routing_constraint_zone.human_attribute_name(:line) => link_to(@line.name, [@referential, @line]),
+ @routing_constraint_zone.human_attribute_name(:checksum) => @routing_constraint_zone.checksum}
.row
.col-lg-12
diff --git a/app/views/shared/_development_toolbar.html.slim b/app/views/shared/_development_toolbar.html.slim
new file mode 100644
index 000000000..aafd37885
--- /dev/null
+++ b/app/views/shared/_development_toolbar.html.slim
@@ -0,0 +1,53 @@
+- if Rails.application.config.development_toolbar
+ = modalbox 'development-toolbar' do
+ = form_tag development_toolbar_update_settings_path, authenticity_token: true do
+ .modal-header
+ h3= "Toolbar"
+
+ .inner
+ .col.features
+ h4
+ = "Features"
+ .toggles
+ = link_to 'all', '#', data: {mask: 'features', val: true}
+ = link_to 'none', '#', data: {mask: 'features', val: false}
+ ul
+ - Rails.application.config.development_toolbar.available_features.sort.each do |feature|
+ li
+ = hidden_field_tag "features[#{feature}]", false, id: ""
+ = check_box_tag "features[#{feature}]", true, has_feature?(feature)
+ = label :features, feature
+ - if Rails.application.config.development_toolbar.features_doc_url
+ = link_to "#{Rails.application.config.development_toolbar.features_doc_url}##{feature}", target: :blank do
+ .fa.fa-question-circle
+ .col.permissions
+ h4
+ = "Permissions"
+ .toggles
+ = link_to 'all', '#', data: {mask: 'permissions', val: true}
+ = link_to 'none', '#', data: {mask: 'permissions', val: false}
+ - model = ""
+ - Rails.application.config.development_toolbar.available_permissions.sort.each do |permission|
+ - if permission.split('.').first != model
+ - model = permission.split('.').first
+ </ul>
+ h5
+ = model
+ .toggles
+ = link_to 'all', '#', data: {mask: "permissions[#{model}", val: true}
+ = link_to 'none', '#', data: {mask: "permissions[#{model}", val: false}
+ <ul>
+ li
+ = hidden_field_tag "permissions[#{permission}]", false, id: ""
+ = check_box_tag "permissions[#{permission}]", true, current_user.has_permission?(permission)
+ = label :permissions, permission, permission.split('.').last
+ .modal-footer
+ button.btn.btn-link type='button' data-dismiss='modal' #{t('cancel')}
+ = submit_tag t("actions.submit"), class: 'btn btn-primary'
+
+ - content_for :javascript do
+ coffee:
+ $('#development-toolbar .toggles a').click (e)->
+ $('#development-toolbar').find("[name^=\"#{e.currentTarget.dataset.mask}\"]").attr "checked", e.currentTarget.dataset.val == 'true'
+ e.preventDefault()
+ false
diff --git a/app/views/stif/dashboards/_dashboard.html.slim b/app/views/stif/dashboards/_dashboard.html.slim
index f3cd01f46..c28696a94 100644
--- a/app/views/stif/dashboards/_dashboard.html.slim
+++ b/app/views/stif/dashboards/_dashboard.html.slim
@@ -56,17 +56,16 @@
.panel.panel-default
.panel-heading
h3.panel-title.with_actions
- div
- = t('.calendars')
- span.badge.ml-xs = @dashboard.calendars.count if @dashboard.calendars.present?
+ = I18n.t("calendars.index.title")
+ span.badge.ml-xs = @dashboard.calendars.count if @dashboard.calendars.present?
div
- = link_to '', calendars_path, class: ' fa fa-chevron-right pull-right', title: t('.see')
+ = link_to '', workgroup_calendars_path(current_workgroup), class: ' fa fa-chevron-right pull-right', title: t('.see')
- if @dashboard.calendars.present?
.list-group
- @dashboard.calendars.first(5).each_with_index do |calendar, i|
- = link_to calendar.name, calendar_path(calendar), class: 'list-group-item' if i < 6
+ = link_to calendar.name, workgroup_calendar_path(current_workgroup, calendar), class: 'list-group-item' if i < 6
- else
.panel-body
diff --git a/app/views/stop_area_referentials/show.html.slim b/app/views/stop_area_referentials/show.html.slim
index d43333fd9..911006c39 100644
--- a/app/views/stop_area_referentials/show.html.slim
+++ b/app/views/stop_area_referentials/show.html.slim
@@ -1,13 +1,14 @@
- breadcrumb :stop_area_referential, @stop_area_referential
-- content_for :page_header_actions do
- = link_to(t('actions.sync'), sync_stop_area_referential_path(@stop_area_referential), method: :post, class: 'btn btn-default')
+- if policy(@stop_area_referential).synchronize?
+ - content_for :page_header_actions do
+ = link_to(t('actions.sync'), sync_stop_area_referential_path(@stop_area_referential), method: :post, class: 'btn btn-default')
- content_for :page_header_content do
.row.mb-md
.col-lg-12.text-right
= link_to stop_area_referential_stop_areas_path(@stop_area_referential), class: 'btn btn-primary' do
= Referential.human_attribute_name(:stop_areas)
- em.small = " (#{@stop_area_referential.stop_areas.size})"
+ em.small = " (#{@stop_area_referential.stop_areas.count})"
- page_header_content_for @stop_area_referential
.page_content
diff --git a/app/views/stop_areas/_filters.html.slim b/app/views/stop_areas/_filters.html.slim
index 3b99f377c..00369d3ed 100644
--- a/app/views/stop_areas/_filters.html.slim
+++ b/app/views/stop_areas/_filters.html.slim
@@ -1,18 +1,18 @@
= search_form_for @q, url: stop_area_referential_stop_areas_path(@stop_area_referential), html: {method: :get}, class: 'form form-filter' do |f|
.ffg-row
- .input-group.search_bar
+ .input-group.search_bar class=filter_item_class(params[:q], :name_or_objectid_cont)
= f.search_field :name_or_objectid_cont, placeholder: t('.name_or_objectid'), class: 'form-control'
span.input-group-btn
button.btn.btn-default#search-btn type='submit'
span.fa.fa-search
.ffg-row
- = f.input :zip_code_cont, placeholder: t('.zip_code'), label: Chouette::StopArea.human_attribute_name(:zip_code), required: false
- = f.input :city_name_cont, placeholder: t('.city_name'), label: Chouette::StopArea.human_attribute_name(:city_name), required: false
+ = f.input :zip_code_cont, placeholder: t('.zip_code'), label: Chouette::StopArea.human_attribute_name(:zip_code), required: false, wrapper_html: {class: filter_item_class(params[:q], :zip_code_cont)}
+ = f.input :city_name_cont, placeholder: t('.city_name'), label: Chouette::StopArea.human_attribute_name(:city_name), required: false, wrapper_html: {class: filter_item_class(params[:q], :city_name_cont)}
- .form-group.togglable
+ .form-group.togglable class=filter_item_class(params[:q], :area_type_eq_any)
= f.label Chouette::StopArea.human_attribute_name(:area_type), required: false, class: 'control-label'
- = f.input :area_type_eq_any, collection: Chouette::StopArea.area_type.options.sort, as: :check_boxes, label: false, label_method: lambda{|w| ("<span>" + t("enumerize.stop_area.area_type.#{w[1]}") + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list' }
+ = f.input :area_type_eq_any, checked: params[:q] && params[:q][:area_type_eq_any], collection: Chouette::AreaType.options, as: :check_boxes, label: false, label_method: lambda{|w| ("<span>" + w[0] + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list' }
.actions
= link_to 'Effacer', @workbench, class: 'btn btn-link'
diff --git a/app/views/stop_areas/_form.html.slim b/app/views/stop_areas/_form.html.slim
index 20c7c0468..bb1fbe1e9 100644
--- a/app/views/stop_areas/_form.html.slim
+++ b/app/views/stop_areas/_form.html.slim
@@ -6,7 +6,26 @@
/= @map.to_html
= f.input :id, as: :hidden
= f.input :name, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.name")}
- = f.input :area_type, as: :select, :input_html => {:disabled => !@stop_area.new_record?}, :collection => Chouette::StopArea.area_type.options, :include_blank => false
+ - if has_feature?(:stop_area_localized_names)
+ .form-group
+ .col-sm-3.col-xs-5
+ .col-sm-9.col-xs-7
+ - f.object.localized_names.each do |k, v|
+ .col-md-6= f.input "localized_names[#{k}]", input_html: {value: v}, label: label_for_country(k)
+
+ = f.input :kind,
+ as: :radio_buttons,
+ checked: @stop_area.new_record? ? :commercial : @stop_area.kind,
+ input_html: { disabled: !@stop_area.new_record? },
+ include_blank: false,
+ item_wrapper_class: 'radio-inline',
+ wrapper: :horizontal_form,
+ disabled: !@stop_area.new_record?
+ .slave data-master="[name='stop_area[kind]']" data-value="commercial"
+ = f.input :parent_id, as: :select, :collection => [f.object.parent_id], input_html: { data: { select2_ajax: 'true', url: autocomplete_stop_area_referential_stop_areas_path(@stop_area_referential), initvalue: {id: f.object.parent_id, text: f.object.parent.try(:full_name)}}}
+ - %i(non_commercial commercial).each do |kind|
+ .slave data-master="[name='stop_area[kind]']" data-value=kind
+ = f.input :area_type, as: :select, :input_html => {id: kind, :disabled => !@stop_area.new_record?}, :collection => Chouette::AreaType.options(kind), :include_blank => false, disabled: !@stop_area.new_record?
.location_info
h3 = t("stop_areas.stop_area.localisation")
@@ -17,15 +36,18 @@
- unless @stop_area.projection.blank? or @stop_area.projection_type_label.empty?
= f.input :projection_xy, :label => t("activerecord.attributes.stop_area.projection_xy", :projection => @referential.projection_type_label), :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.projection_xy")}
- = f.input :coordinates, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.coordinates")}, required: true
+ = f.input :coordinates, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.coordinates")}
= f.input :street_name
- /= f.input :country_code, required: format_restriction_for_locales(@referential) == '.hub'
= f.input :zip_code, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.zip_code")}
= f.input :city_name, required: format_restriction_for_locales(@referential) == '.hub', :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.city_name")}
+ = f.input :country_code, as: :country, priority: ['FR', 'GB', 'DE', 'ES'], :include_blank => true
.stop_areas.stop_area.general_info
h3 = t("stop_areas.stop_area.general")
+ - if has_feature?(:stop_area_waiting_time)
+ = f.input :waiting_time, input_html: { min: 0 }
+
= f.input :registration_number, required: format_restriction_for_locales(@referential) == '.hub', :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.registration_number")}
= f.input :fare_code
= f.input :nearest_topic_name, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.nearest_topic_name")}
@@ -43,3 +65,5 @@
.separator
= f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'stop_area_form'
+
+= javascript_pack_tag "stop_areas/new"
diff --git a/app/views/stop_areas/autocomplete.rabl b/app/views/stop_areas/autocomplete.rabl
new file mode 100644
index 000000000..a5f0bd5ec
--- /dev/null
+++ b/app/views/stop_areas/autocomplete.rabl
@@ -0,0 +1,24 @@
+collection @stop_areas
+
+node do |stop_area|
+ {
+ :id => stop_area.id,
+ :registration_number => stop_area.registration_number || "",
+ :short_registration_number => truncate(stop_area.registration_number, :length => 10) || "",
+ :name => stop_area.name || "",
+ :short_name => truncate(stop_area.name, :length => 30) || "",
+ :zip_code => stop_area.zip_code || "",
+ :city_name => stop_area.city_name || "",
+ :short_city_name => truncate(stop_area.city_name, :length => 15) || "",
+ :user_objectid => stop_area.user_objectid,
+ :longitude => stop_area.longitude,
+ :latitude => stop_area.latitude,
+ :area_type => stop_area.area_type,
+ :comment => stop_area.comment,
+ :text => "<span class='small label label-info'>#{I18n.t("area_types.label.#{stop_area.area_type}")}</span>#{stop_area.full_name}"
+ }
+end
+
+node(:stop_area_path) { |stop_area|
+ stop_area_picture_url(stop_area) || ""
+}
diff --git a/app/views/stop_areas/index.html.slim b/app/views/stop_areas/index.html.slim
index c4d880081..71c7f995c 100644
--- a/app/views/stop_areas/index.html.slim
+++ b/app/views/stop_areas/index.html.slim
@@ -1,7 +1,4 @@
- breadcrumb :stop_areas, @stop_area_referential
-- content_for :page_header_actions do
- - if policy(Chouette::StopArea).create?
- = link_to(t('stop_areas.actions.new'), new_stop_area_referential_stop_area_path(@stop_area_referential), class: 'btn btn-primary')
.page_content
.container-fluid
@@ -24,7 +21,7 @@
key: :name, \
attribute: 'name', \
link_to: lambda do |stop_area| \
- referential_stop_area_path( \
+ stop_area_referential_stop_area_path( \
@stop_area_referential, \
stop_area \
) \
@@ -48,10 +45,9 @@
), \
TableBuilderHelper::Column.new( \
key: :area_type, \
- attribute: Proc.new { |s| (s.area_type.nil? ? '-' : t("enumerize.stop_area.area_type.#{s.try(:area_type)}")) } \
+ attribute: Proc.new { |s| Chouette::AreaType.find(s.area_type).try :label } \
), \
],
- links: [:show, :edit, :delete],
cls: 'table has-filter has-search'
= new_pagination @stop_areas, 'pull-right'
diff --git a/app/views/stop_areas/show.html.slim b/app/views/stop_areas/show.html.slim
index af673bb25..a4e14a272 100644
--- a/app/views/stop_areas/show.html.slim
+++ b/app/views/stop_areas/show.html.slim
@@ -1,26 +1,26 @@
- breadcrumb :stop_area, @stop_area_referential, @stop_area
- page_header_content_for @stop_area
-- content_for :page_header_content do
- .row
- .col-lg-12.text-right.mb-sm
- - @stop_area.action_links.each do |link|
- = link_to link.href,
- method: link.method,
- data: link.data,
- class: 'btn btn-primary' do
- = link.content
/ PageContent
.page_content
.container-fluid
.row
.col-lg-6.col-md-6.col-sm-12.col-xs-12
- = definition_list t('metadatas'),
- { t('id_reflex') => @stop_area.get_objectid.short_id,
- @stop_area.human_attribute_name(:stop_area_type) => t("area_types.label.#{@stop_area.stop_area_type}"),
+ - attributes = { t('id_reflex') => @stop_area.get_objectid.short_id }
+
+ - if has_feature?(:stop_area_localized_names)
+ - @stop_area.localized_names.each do |k, v|
+ - attributes.merge!({label_for_country(k, @stop_area.human_attribute_name(:name)) => v }) if v.present?
+ - attributes.merge!({ @stop_area.human_attribute_name(:parent) => @stop_area.parent ? link_to(@stop_area.parent.name, stop_area_referential_stop_area_path(@stop_area_referential, @stop_area.parent)) : "-" }) if @stop_area.commercial?
+ - attributes.merge!({ @stop_area.human_attribute_name(:stop_area_type) => Chouette::AreaType.find(@stop_area.area_type).try(:label),
@stop_area.human_attribute_name(:registration_number) => @stop_area.registration_number,
- 'Coordonnées' => geo_data(@stop_area, @stop_area_referential),
+ })
+ - attributes.merge!(@stop_area.human_attribute_name(:waiting_time) => @stop_area.waiting_time_text) if has_feature?(:stop_area_waiting_time)
+ - attributes.merge!({ "Coordonnées" => geo_data(@stop_area, @stop_area_referential),
@stop_area.human_attribute_name(:zip_code) => @stop_area.zip_code,
@stop_area.human_attribute_name(:city_name) => @stop_area.city_name,
+ @stop_area.human_attribute_name(:country_code) => @stop_area.country_code.presence || '-',
'Etat' => (@stop_area.deleted_at ? 'Supprimé' : 'Actif'),
- @stop_area.human_attribute_name(:comment) => @stop_area.try(:comment) }
+ @stop_area.human_attribute_name(:comment) => @stop_area.try(:comment),
+ })
+ = definition_list t('metadatas'), attributes
diff --git a/app/views/stop_points/_stop_point.html.slim b/app/views/stop_points/_stop_point.html.slim
index ca86e339a..e54158cef 100644
--- a/app/views/stop_points/_stop_point.html.slim
+++ b/app/views/stop_points/_stop_point.html.slim
@@ -5,7 +5,7 @@
= link_to [@referential, stop_point.stop_area], class: "preview", title: "#{Chouette::StopArea.model_name.human.capitalize} #{stop_point.stop_area.name}" do
span.name
span.label.label-primary = stop_point.position + 1
- = image_tag "map/" + stop_point.stop_area.stop_area_type + ".png"
+ = image_tag "map/" + stop_point.stop_area.area_type + ".png"
= truncate(stop_point.stop_area.name, length: 20)
.panel-body
@@ -27,4 +27,4 @@
= t(".no_object")
- else
- stop_point.stop_area.lines.each do |line|
- span.label.label-default.line = line.number || truncate( line.name, length: 4 ) \ No newline at end of file
+ span.label.label-default.line = line.number || truncate( line.name, length: 4 )
diff --git a/app/views/time_tables/_filter.html.slim b/app/views/time_tables/_filter.html.slim
index 11e9987c4..030036a13 100644
--- a/app/views/time_tables/_filter.html.slim
+++ b/app/views/time_tables/_filter.html.slim
@@ -1,17 +1,17 @@
= search_form_for @q, url: referential_time_tables_path(@referential), html: { method: :get, class: 'form form-filter' } do |f|
.ffg-row
- .input-group.search_bar
+ .input-group.search_bar class=filter_item_class(params[:q], :comment_cont)
= f.text_field :comment_cont, :placeholder => "#{t('time_tables.index.comment')}", class: 'form-control'
span.input-group-btn
button.btn.btn-default type='submit'
span.fa.fa-search
.ffg-row
- .form-group
+ .form-group class=filter_item_class(params[:q], :tag_search)
= f.label Chouette::TimeTable.human_attribute_name(:tag_search), required: false, class: 'control-label'
= f.input :tag_search, as: :tags, collection: Chouette::TimeTable.tags_on(:tags).pluck(:name), label: false, input_html: { 'data-select2ed': 'true', 'data-select2ed-placeholder': 'Indiquez une étiquette...' }, wrapper_html: { class: 'select2ed'}, include_blank: false, selected: params[:q] ? params[:q]['tag_search'] : nil
- .form-group.togglable
+ .form-group.togglable class=filter_item_class(params[:q], :bounding_dates)
= f.label Chouette::TimeTable.human_attribute_name(:bounding_dates), required: false, class: 'control-label'
.filter_menu
= f.simple_fields_for :bounding_dates do |p|
diff --git a/app/views/time_tables/_form.html.slim b/app/views/time_tables/_form.html.slim
index d06fdf444..007044e65 100644
--- a/app/views/time_tables/_form.html.slim
+++ b/app/views/time_tables/_form.html.slim
@@ -5,7 +5,7 @@
= form.input :comment, :input_html => { :title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.time_table.comment")}
- if @time_table.new_record? && !@time_table.created_from
- = form.input :calendar_id, as: :select, input_html: { class: 'tt_target', style: "width: 100%", data: { 'select2-ajax': 'true', 'select2ed-placeholder': 'Indiquez un modèle de calendrier...', term: 'name_cont', url: autocomplete_calendars_path}}
+ = form.input :calendar_id, as: :select, input_html: { class: 'tt_target', style: "width: 100%", data: { 'select2-ajax': 'true', 'select2ed-placeholder': 'Indiquez un modèle de calendrier...', term: 'name_cont', url: autocomplete_workgroup_calendars_path(current_workgroup)}}
- if @time_table.created_from
= form.input :created_from, disabled: true, input_html: { value: @time_table.created_from.comment }
diff --git a/app/views/time_tables/_show_time_table.html.slim b/app/views/time_tables/_show_time_table.html.slim
index ebfe9d283..102dbfad7 100644
--- a/app/views/time_tables/_show_time_table.html.slim
+++ b/app/views/time_tables/_show_time_table.html.slim
@@ -2,24 +2,10 @@
- (1..12).each do |month|
.col-lg-3.col-md-4.col-sm-4.col-xs-6
= new_alt_calendar(year: @year, month: month, first_day_of_week: 1, calendar_title: "#{I18n.t("date.month_names")[month]}", show_today: false) do |d|
- / - if @time_table.excluded_date?(d)
- / - [link_to(d.mday, edit_referential_time_table_path(@referential, @time_table) ), {class: "day excluded_date"}]
- - if @time_table.include_in_overlap_dates?(d)
- - [link_to(d.mday, edit_referential_time_table_path(@referential, @time_table) ), {class: "day overlaped_date", title: 'Voir'}]
- - elsif @time_table.include_in_dates?(d)
- - [link_to(d.mday, edit_referential_time_table_path(@referential, @time_table) ), {class: "day selected_date", title: 'Voir'}]
- - elsif @time_table.include_in_periods?(d)
- - [link_to(d.mday, edit_referential_time_table_path(@referential, @time_table) ), {class: "day selected_period", title: 'Voir'}]
-
-/ .row
-/ .col-lg-12
-/ / wip
-/ - if @time_table.dates.where("in_out = true").present?
-/ h3.time_table_dates = @time_table.human_attribute_name("dates")
-/ .dates.content
-/ == render "time_tables/dates"
-/
-/ - if @time_table.dates.where("in_out = false").present?
-/ h3.time_table_dates = @time_table.human_attribute_name("excluded_dates")
-/ .excluded_dates.content
-/ == render "time_tables/excluded_dates"
+ - edit_url = [:edit, @referential, time_table].compact
+ - if time_table.include_in_overlap_dates?(d)
+ - [link_to(d.mday, edit_url), {class: "day overlaped_date", title: 'Voir'}]
+ - elsif time_table.include_in_dates?(d)
+ - [link_to(d.mday, edit_url), {class: "day selected_date", title: 'Voir'}]
+ - elsif time_table.include_in_periods?(d)
+ - [link_to(d.mday, edit_url), {class: "day selected_period", title: 'Voir'}]
diff --git a/app/views/time_tables/edit.html.slim b/app/views/time_tables/edit.html.slim
index e1c566ff4..d8cffb1b0 100644
--- a/app/views/time_tables/edit.html.slim
+++ b/app/views/time_tables/edit.html.slim
@@ -8,6 +8,6 @@
= javascript_tag do
| window.actionType = "#{raw params[:action]}";
- | window.I18n = #{(I18n.backend.send(:translations)[I18n.locale].to_json).html_safe};
+ // | window.I18n = #{(I18n.backend.send(:translations)[I18n.locale].to_json).html_safe};
= javascript_pack_tag 'time_tables/edit.js'
diff --git a/app/views/time_tables/index.html.slim b/app/views/time_tables/index.html.slim
index b684b0bcb..6913712a0 100644
--- a/app/views/time_tables/index.html.slim
+++ b/app/views/time_tables/index.html.slim
@@ -1,7 +1,4 @@
- breadcrumb :time_tables, @referential
-- content_for :page_header_actions do
- - if (policy(Chouette::TimeTable).create? && @referential.organisation == current_organisation)
- = link_to(t('actions.add'), new_referential_time_table_path(@referential), class: 'btn btn-default')
.page_content
.container-fluid
@@ -54,7 +51,6 @@
attribute: Proc.new { |tt| l(tt.updated_at, format: :short) } \
) \
],
- links: [:show, :edit],
cls: 'table has-search'
= new_pagination @time_tables, 'pull-right'
@@ -65,6 +61,6 @@
= replacement_msg t('time_tables.search_no_results')
= javascript_tag do
- | window.I18n = #{(I18n.backend.send(:translations).to_json).html_safe};
+ // | window.I18n = #{(I18n.backend.send(:translations).to_json).html_safe};
= javascript_pack_tag 'date_filters'
diff --git a/app/views/time_tables/show.html.slim b/app/views/time_tables/show.html.slim
index 036581268..e038b73ca 100644
--- a/app/views/time_tables/show.html.slim
+++ b/app/views/time_tables/show.html.slim
@@ -4,21 +4,6 @@
- content_for :page_header_title, t('time_tables.show.title', name: @time_table.comment), flush: true
-- content_for :page_header_actions do
- - if policy(@time_table).edit?
- = link_to(t('actions.edit'), edit_referential_time_table_path(@referential, @time_table), class: 'btn btn-default')
-
-- content_for :page_header_content do
- .row.mb-sm
- .col-lg-12.text-right
- - @time_table.action_links.each do |link|
- = link_to link.href,
- method: link.method,
- data: link.data,
- class: 'btn btn-primary' do
- = link.content
-
-
.page_content
.container-fluid
.row
@@ -28,7 +13,8 @@
'Couleur associée' => (@time_table.color.nil? ? '-' : content_tag(:span, '', class: 'fa fa-circle', style: "color:#{@time_table.try(:color)}")),
'Etiquettes' => @time_table.tag_list,
'Modèle de calendrier' => (@time_table.calendar ? link_to(@time_table.calendar.name, @time_table.calendar) : '-'),
- "Journées d'application pour les périodes ci-dessous" => %w(monday tuesday wednesday thursday friday saturday sunday).collect{ |d| content_tag(:span, t("calendars.days.#{d}"), class: "label label-default #{@time_table.send(d) ? '' : 'disabled'}") }.join.html_safe }
+ "Journées d'application pour les périodes ci-dessous" => %w(monday tuesday wednesday thursday friday saturday sunday).collect{ |d| content_tag(:span, t("calendars.days.#{d}"), class: "label label-default #{@time_table.send(d) ? '' : 'disabled'}") }.join.html_safe,
+ Chouette::TimeTable.human_attribute_name(:checksum) => @time_table.checksum}
.row
.col-lg-12.mb-sm
@@ -38,4 +24,4 @@
= link_to '', referential_time_table_path(@referential, @time_table, year: (@year - 1)), class: 'previous_page'
= link_to '', referential_time_table_path(@referential, @time_table, year: (@year + 1)), class: 'next_page'
- = render 'show_time_table'
+ = render 'show_time_table', time_table: @time_table
diff --git a/app/views/vehicle_journeys/index.html.slim b/app/views/vehicle_journeys/index.html.slim
index 52c1a9728..d53d8b50c 100644
--- a/app/views/vehicle_journeys/index.html.slim
+++ b/app/views/vehicle_journeys/index.html.slim
@@ -1,5 +1,11 @@
- breadcrumb :vehicle_journeys, @referential, @route
- content_for :page_header_title, t('vehicle_journeys.index.title', route: @route.name)
+- if @route.opposite_route.present?
+ - content_for :page_header_content do
+ .row.mb-sm
+ .col-lg-12.text-right
+ = link_to(t('routes.actions.opposite_route_timetable'), [@referential, @route.line, @route.opposite_route, :vehicle_journeys], class: 'btn btn-primary sticky-action')
+
.page_content
.container-fluid
@@ -11,7 +17,8 @@
= javascript_tag do
| window.route_id = #{params[:route_id]};
| window.stopPoints = #{(@stop_points_list.to_json).html_safe};
- | window.jpOrigin = #{(@jp_origin.to_json).html_safe};
+ | window.returnStopPoints = #{(@return_stop_points_list.to_json).html_safe};
+ | window.jpOrigin = #{(@jp_origin.present? ? @jp_origin.attributes.update({full_schedule: @jp_origin.full_schedule?}).to_json : "null").html_safe};
| window.jpOriginStopPoints = #{(@jp_origin_stop_points.to_json).html_safe};
| window.transportMode = #{(@transport_mode.to_json).html_safe};
| window.transportSubmode = #{(@transport_submode.to_json).html_safe};
@@ -19,6 +26,14 @@
| window.vehicleJourneysPerPage = #{@ppage};
| window.line_footnotes = #{raw @footnotes};
| window.perms = #{raw @perms};
- | window.I18n = #{(I18n.backend.send(:translations).to_json).html_safe};
+ | window.features = #{raw @features};
+ | window.all_missions = #{(@all_missions.to_json).html_safe};
+ | window.custom_fields = #{(@custom_fields.to_json).html_safe};
+ // | window.I18n = #{(I18n.backend.send(:translations).to_json).html_safe};
+
+- if has_feature?(:vehicle_journeys_return_route)
+ = javascript_tag do
+ | window.returnRouteUrl = "#{(@route.opposite_route && url_for([@referential, @route.line, @route.opposite_route, :vehicle_journeys]) || "").html_safe}";
+
= javascript_pack_tag 'vehicle_journeys/index.js'
diff --git a/app/views/vehicle_journeys/show.rabl b/app/views/vehicle_journeys/show.rabl
index 830dee8bd..546c851a4 100644
--- a/app/views/vehicle_journeys/show.rabl
+++ b/app/views/vehicle_journeys/show.rabl
@@ -1,6 +1,6 @@
object @vehicle_journey
-[:objectid, :published_journey_name, :published_journey_identifier, :company_id].each do |attr|
+[:objectid, :published_journey_name, :published_journey_identifier, :company_id, :comment, :checksum, :custom_fields].each do |attr|
attributes attr, :unless => lambda { |m| m.send( attr).nil?}
end
@@ -28,13 +28,22 @@ child(:time_tables, :object_root => false) do |time_tables|
end
end
+if has_feature? :purchase_windows
+ child(:purchase_windows, :object_root => false) do |purchase_windows|
+ attributes :id, :objectid, :name, :color
+ end
+end
+
child :footnotes, :object_root => false do |footnotes|
attributes :id, :code, :label
end
child(:vehicle_journey_at_stops_matrix, :object_root => false) do |vehicle_stops|
+ attributes :id, :connecting_service_id, :boarding_alighting_possibility
node do |vehicle_stop|
+
node(:dummy) { vehicle_stop.dummy }
+ node(:area_kind) { vehicle_stop.stop_point.stop_area.kind }
node(:stop_area_object_id) do
vehicle_stop.stop_point.stop_area.objectid
@@ -49,15 +58,11 @@ child(:vehicle_journey_at_stops_matrix, :object_root => false) do |vehicle_stops
vehicle_stop.stop_point.stop_area.city_name
end
- [:id, :connecting_service_id, :boarding_alighting_possibility].map do |att|
- node(att) { vehicle_stop.send(att) ? vehicle_stop.send(att) : nil }
- end
-
- [:arrival_time, :departure_time].map do |att|
- node(att) do |vs|
+ [:arrival, :departure].each do |att|
+ node("#{att}_time") do |vs|
{
- hour: vs.send(att).try(:strftime, '%H'),
- minute: vs.send(att).try(:strftime, '%M')
+ hour: vs.send("#{att}_local_time").try(:strftime, '%H'),
+ minute: vs.send("#{att}_local_time").try(:strftime, '%M')
}
end
end
diff --git a/app/views/workbench_outputs/show.html.slim b/app/views/workbench_outputs/show.html.slim
new file mode 100644
index 000000000..a9e106dbb
--- /dev/null
+++ b/app/views/workbench_outputs/show.html.slim
@@ -0,0 +1,40 @@
+/ PageHeader
+
+- breadcrumb :workbench_output, @workbench
+- content_for :page_header_title, t('.title')
+- content_for :page_header_content do
+ .row.mb-sm
+ .col-lg-12.text-right
+ = link_to t('.see_current_output'), referential_path(@workbench.output.current), class: 'btn btn-primary' if @workbench.output&.current
+ = link_to t('merges.actions.create'), new_workbench_merge_path(@workbench), class: 'btn btn-primary'
+
+.page_content
+ .container-fluid
+ .row
+ .col-lg-12
+ = table_builder_2 @workbench_merges,
+ [ \
+ TableBuilderHelper::Column.new( \
+ key: :status, \
+ attribute: Proc.new { |n| import_status(n.status) }, \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :name, \
+ attribute: 'full_names', \
+ link_to: lambda do |merge| \
+ workbench_merge_path merge.workbench, merge \
+ end \
+ ), \
+ TableBuilderHelper::Column.new( \
+ name: t(".table_headers.ended_at"), \
+ attribute: Proc.new { |n| l(n.ended_at, format: :long) if n.ended_at }, \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :creator, \
+ attribute: 'creator' \
+ ) \
+ ],
+ links: [],
+ cls: 'table has-search'
+
+ = new_pagination @workbench_merges, 'pull-right'
diff --git a/app/views/workbenches/_filters.html.slim b/app/views/workbenches/_filters.html.slim
index 4d9e0066c..c9dd13d96 100644
--- a/app/views/workbenches/_filters.html.slim
+++ b/app/views/workbenches/_filters.html.slim
@@ -1,27 +1,33 @@
= search_form_for @q_for_form, url: workbench_path(@workbench.id), builder: SimpleForm::FormBuilder, class: 'form form-filter' do |f|
.ffg-row
- .input-group.search_bar
+ .input-group.search_bar class=filter_item_class(params[:q], :name_cont)
= f.search_field :name_cont, class: 'form-control', placeholder: t('referentials.filters.name')
span.input-group-btn
button.btn.btn-default type='submit'
span.fa.fa-search
.ffg-row
- .form-group
+ .form-group class=filter_item_class(params[:q], :associated_lines_id_eq)
= f.label t('activerecord.models.line.one').upcase, required: false, class: 'control-label'
= f.input :associated_lines_id_eq, as: :select, collection: @workbench.lines.includes(:company).order(:name), input_html: { 'data-select2ed': 'true', 'data-select2ed-placeholder': t('referentials.filters.line') }, label: false, label_method: :display_name, wrapper_html: { class: 'select2ed'}
- .form-group.togglable
+ .form-group.togglable class=filter_item_class(params[:q], :archived_at_not_null)
= f.label Referential.human_attribute_name(:status), required: false, class: 'control-label'
.form-group.checkbox_list
= f.input :archived_at_not_null, label: ("<span>#{t('activerecord.attributes.referential.archived_at')}<span class='fa fa-archive'></span></span>").html_safe, as: :boolean, wrapper_html: { class: 'checkbox-wrapper' }
= f.input :archived_at_null, label: ("<span>#{t('activerecord.attributes.referential.archived_at_null')}<span class='sb sb-lg sb-preparing'></span></span>").html_safe, as: :boolean, wrapper_html: { class: 'checkbox-wrapper' }
- .form-group.togglable
+ .form-group.togglable class=filter_item_class(params[:q], :organisation_name_eq_any)
= f.label t('activerecord.models.organisation.one'), required: false, class: 'control-label'
- = f.input :organisation_name_eq_any, collection: Organisation.order('name').pluck(:name), as: :check_boxes, label: false, label_method: lambda{|w| ("<span>#{w}</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list' }
+ = f.input :organisation_name_eq_any,
+ collection: @workbench.workgroup.organisations.order('name').pluck(:name),
+ as: :check_boxes,
+ label: false,
+ label_method: lambda { |w| ("<span>#{w}</span>").html_safe },
+ required: false,
+ wrapper_html: { class: 'checkbox_list' }
- .form-group.togglable
+ .form-group.togglable class=filter_item_class(params[:q], :validity_period)
= f.label Referential.human_attribute_name(:validity_period), required: false, class: 'control-label'
.filter_menu
= f.simple_fields_for :validity_period do |p|
diff --git a/app/views/workbenches/show.html.slim b/app/views/workbenches/show.html.slim
index af312fc08..aae34c51b 100644
--- a/app/views/workbenches/show.html.slim
+++ b/app/views/workbenches/show.html.slim
@@ -6,6 +6,7 @@
- if policy(Referential).create?
= link_to t('actions.import'), workbench_imports_path(@workbench), class: 'btn btn-primary'
= link_to t('actions.add'), new_referential_path(workbench_id: @workbench), class: 'btn btn-primary'
+ = link_to t('workbenches.actions.show_output'), workbench_output_path(@workbench), class: 'btn btn-primary'
.page_content
.container-fluid
@@ -29,7 +30,7 @@
), \
TableBuilderHelper::Column.new( \
key: :status, \
- attribute: Proc.new {|w| w.archived? ? ("<div class='td-block'><span class='fa fa-archive'></span><span>#{t('activerecord.attributes.referential.archived_at')}</span></div>").html_safe : ("<div class='td-block'><span class='sb sb-lg sb-preparing'></span><span>#{t('activerecord.attributes.referential.archived_at_null')}</span></div>").html_safe} \
+ attribute: Proc.new {|w| w.referential_read_only? ? ("<div class='td-block'><span class='fa fa-archive'></span><span>#{t('activerecord.attributes.referential.archived_at')}</span></div>").html_safe : ("<div class='td-block'><span class='sb sb-lg sb-preparing'></span><span>#{t('activerecord.attributes.referential.archived_at_null')}</span></div>").html_safe} \
), \
TableBuilderHelper::Column.new( \
key: :organisation, \
@@ -53,13 +54,13 @@
attribute: Proc.new {|w| l(w.updated_at, format: :short)} \
), \
TableBuilderHelper::Column.new( \
- key: :published_at, \
- attribute: '' \
+ key: :merged_at, \
+ attribute: Proc.new {|w| w.merged_at ? l(w.merged_at, format: :short) : '-'} \
) \
],
- selectable: true,
- links: [:show, :edit],
- cls: 'table has-filter has-search'
+ selectable: ->(ref){ @workbench.referentials.include?(ref) },
+ cls: 'table has-filter has-search',
+ action: :index
= multiple_selection_toolbox([:delete], collection_name: 'referentials')
@@ -71,6 +72,6 @@
= replacement_msg t('referentials.search_no_results')
= javascript_tag do
- | window.I18n = #{(I18n.backend.send(:translations).to_json).html_safe};
+ // | window.I18n = #{(I18n.backend.send(:translations).to_json).html_safe};
= javascript_pack_tag 'date_filters'
diff --git a/app/workers/clean_up_worker.rb b/app/workers/clean_up_worker.rb
index 2d76b3a68..9a7c3aa5a 100644
--- a/app/workers/clean_up_worker.rb
+++ b/app/workers/clean_up_worker.rb
@@ -1,6 +1,5 @@
class CleanUpWorker
include Sidekiq::Worker
- sidekiq_options :retry => false
def perform(id)
cleaner = CleanUp.find id
diff --git a/app/workers/line_referential_sync_worker.rb b/app/workers/line_referential_sync_worker.rb
index 253b8a53c..1303a63fd 100644
--- a/app/workers/line_referential_sync_worker.rb
+++ b/app/workers/line_referential_sync_worker.rb
@@ -1,6 +1,6 @@
class LineReferentialSyncWorker
include Sidekiq::Worker
- sidekiq_options :retry => false
+ sidekiq_options retry: true
def process_time
Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
diff --git a/app/workers/merge_worker.rb b/app/workers/merge_worker.rb
new file mode 100644
index 000000000..8a085a25a
--- /dev/null
+++ b/app/workers/merge_worker.rb
@@ -0,0 +1,7 @@
+class MergeWorker
+ include Sidekiq::Worker
+
+ def perform(id)
+ Merge.find(id).merge!
+ end
+end
diff --git a/app/workers/referential_cloning_worker.rb b/app/workers/referential_cloning_worker.rb
index 6592160ec..e24baa90c 100644
--- a/app/workers/referential_cloning_worker.rb
+++ b/app/workers/referential_cloning_worker.rb
@@ -1,32 +1,7 @@
class ReferentialCloningWorker
include Sidekiq::Worker
- # Replace default apartment created schema with clone schema from source referential
def perform(id)
- ref_cloning = ReferentialCloning.find id
-
- source_schema = ref_cloning.source_referential.slug
- target_schema = ref_cloning.target_referential.slug
-
- clone_schema ref_cloning, source_schema, target_schema
- end
-
- private
-
- def clone_schema ref_cloning, source_schema, target_schema
- ref_cloning.run!
-
- AF83::SchemaCloner
- .new(source_schema, target_schema)
- .clone_schema
-
- ref_cloning.successful!
- rescue Exception => e
- Rails.logger.error "ReferentialCloningWorker : #{e}"
- ref_cloning.failed!
- end
-
- def execute_sql sql
- ActiveRecord::Base.connection.execute sql
+ ReferentialCloning.find(id).clone_with_status!
end
end
diff --git a/app/workers/stop_area_referential_sync_worker.rb b/app/workers/stop_area_referential_sync_worker.rb
index 08bcf4f5f..3de351a91 100644
--- a/app/workers/stop_area_referential_sync_worker.rb
+++ b/app/workers/stop_area_referential_sync_worker.rb
@@ -1,6 +1,6 @@
class StopAreaReferentialSyncWorker
include Sidekiq::Worker
- sidekiq_options :retry => false
+ sidekiq_options retry: true
def process_time
Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
diff --git a/app/workers/workbench_import_worker.rb b/app/workers/workbench_import_worker.rb
index de51efded..6420be835 100644
--- a/app/workers/workbench_import_worker.rb
+++ b/app/workers/workbench_import_worker.rb
@@ -3,29 +3,25 @@ class WorkbenchImportWorker
include Rails.application.routes.url_helpers
include Configurable
+ include ObjectStateUpdater
+
+ attr_reader :entries, :workbench_import
+
# Workers
# =======
def perform(import_id)
- @workbench_import = WorkbenchImport.find(import_id)
- @response = nil
- @workbench_import.update(status: 'running', started_at: Time.now)
- downloaded = download
- zip_service = ZipService.new(downloaded)
+ @entries = 0
+ @workbench_import ||= WorkbenchImport.find(import_id)
+
+ workbench_import.update(status: 'running', started_at: Time.now)
+ zip_service = ZipService.new(downloaded, allowed_lines)
upload zip_service
- @workbench_import.update(ended_at: Time.now)
+ workbench_import.update(ended_at: Time.now)
rescue Zip::Error
handle_corrupt_zip_file
end
- def download
- logger.info "HTTP GET #{import_url}"
- HTTPService.get_resource(
- host: import_host,
- path: import_path,
- params: {token: @workbench_import.token_download}).body
- end
-
def execute_post eg_name, eg_file
logger.info "HTTP POST #{export_url} (for #{complete_entry_group_name(eg_name)})"
HTTPService.post_resource(
@@ -35,48 +31,43 @@ class WorkbenchImportWorker
end
def handle_corrupt_zip_file
- @workbench_import.messages.create(criticity: :error, message_key: 'corrupt_zip_file', message_attributes: {source_filename: @workbench_import.file.file.file})
+ workbench_import.messages.create(criticity: :error, message_key: 'corrupt_zip_file', message_attributes: {source_filename: workbench_import.file.file.file})
end
def upload zip_service
entry_group_streams = zip_service.subdirs
- @workbench_import.update total_steps: entry_group_streams.size
entry_group_streams.each_with_index(&method(:upload_entry_group))
+ workbench_import.update total_steps: @entries
rescue Exception => e
logger.error e.message
- @workbench_import.update( current_step: entry_group_streams.size, status: 'failed' )
+ workbench_import.update( current_step: @entries, status: 'failed' )
raise
end
- def update_object_state entry, count
- @workbench_import.update( current_step: count )
- unless entry.spurious.empty?
- @workbench_import.messages.create(
- criticity: :warning,
- message_key: 'inconsistent_zip_file',
- message_attributes: {
- 'source_filename' => @workbench_import.file.file.file,
- 'spurious_dirs' => entry.spurious.join(', ')
- })
- end
- end
def upload_entry_group entry, element_count
update_object_state entry, element_count.succ
+ return unless entry.ok?
# status = retry_service.execute(&upload_entry_group_proc(entry))
- eg_name = entry.name
- eg_stream = entry.stream
+ upload_entry_group_stream entry.name, entry.stream
+ end
+ def upload_entry_group_stream eg_name, eg_stream
FileUtils.mkdir_p(Rails.root.join('tmp', 'imports'))
- eg_file = File.new(Rails.root.join('tmp', 'imports', "WorkbenchImport_#{eg_name}_#{$$}.zip"), 'wb').tap do |file|
+ File.open(Rails.root.join('tmp', 'imports', "WorkbenchImport_#{eg_name}_#{$$}.zip"), 'wb') do |file|
eg_stream.rewind
file.write eg_stream.read
end
- eg_file.close
- eg_file = File.new(Rails.root.join('tmp', 'imports', "WorkbenchImport_#{eg_name}_#{$$}.zip"))
+
+ upload_entry_group_tmpfile eg_name, File.new(Rails.root.join('tmp', 'imports', "WorkbenchImport_#{eg_name}_#{$$}.zip"))
+ end
+
+ def upload_entry_group_tmpfile eg_name, eg_file
result = execute_post eg_name, eg_file
if result && result.status < 400
+ @entries += 1
+ workbench_import.update( current_step: @entries )
result
else
raise StopIteration, result.body
@@ -91,7 +82,7 @@ class WorkbenchImportWorker
# =======
def complete_entry_group_name entry_group_name
- [@workbench_import.name, entry_group_name].join("--")
+ [workbench_import.name, entry_group_name].join("--")
end
# Constants
@@ -111,7 +102,7 @@ class WorkbenchImportWorker
Rails.application.config.rails_host
end
def import_path
- @__import_path__ ||= download_workbench_import_path(@workbench_import.workbench, @workbench_import)
+ @__import_path__ ||= download_workbench_import_path(workbench_import.workbench, workbench_import)
end
def import_url
@__import_url__ ||= File.join(import_host, import_path)
@@ -119,10 +110,29 @@ class WorkbenchImportWorker
def params file, name
{ netex_import:
- { parent_id: @workbench_import.id,
- parent_type: @workbench_import.class.name,
- workbench_id: @workbench_import.workbench_id,
- name: name,
- file: HTTPService.upload(file, 'application/zip', "#{name}.zip") } }
+ { parent_id: workbench_import.id,
+ parent_type: workbench_import.class.name,
+ workbench_id: workbench_import.workbench_id,
+ name: name,
+ file: HTTPService.upload(file, 'application/zip', "#{name}.zip") } }
+ end
+
+ # Lazy Values
+ # ===========
+
+ def allowed_lines
+ @__allowed_lines__ ||= workbench_import.workbench.organisation.lines_set
end
+ def downloaded
+ @__downloaded__ ||= download_response.body
+ end
+ def download_response
+ @__download_response__ ||= HTTPService.get_resource(
+ host: import_host,
+ path: import_path,
+ params: {token: workbench_import.token_download}).tap do
+ logger.info "HTTP GET #{import_url}"
+ end
+ end
+
end
diff --git a/app/workers/workbench_import_worker/object_state_updater.rb b/app/workers/workbench_import_worker/object_state_updater.rb
new file mode 100644
index 000000000..67bdc0654
--- /dev/null
+++ b/app/workers/workbench_import_worker/object_state_updater.rb
@@ -0,0 +1,36 @@
+
+class WorkbenchImportWorker
+ module ObjectStateUpdater
+
+ def update_object_state entry, count
+ workbench_import.update( total_steps: count )
+ update_spurious entry
+ update_foreign_lines entry
+ end
+
+
+ private
+
+ def update_foreign_lines entry
+ return if entry.foreign_lines.empty?
+ workbench_import.messages.create(
+ criticity: :error,
+ message_key: 'foreign_lines_in_referential',
+ message_attributes: {
+ 'source_filename' => workbench_import.file.file.file,
+ 'foreign_lines' => entry.foreign_lines.join(', ')
+ })
+ end
+
+ def update_spurious entry
+ return if entry.spurious.empty?
+ workbench_import.messages.create(
+ criticity: :error,
+ message_key: 'inconsistent_zip_file',
+ message_attributes: {
+ 'source_filename' => workbench_import.file.file.file,
+ 'spurious_dirs' => entry.spurious.join(', ')
+ })
+ end
+ end
+end