Skip to content

Instantly share code, notes, and snippets.

@dedemenezes
Created September 10, 2025 14:45
Show Gist options
  • Select an option

  • Save dedemenezes/12edd90a94f733d9abf638c8ba075e6e to your computer and use it in GitHub Desktop.

Select an option

Save dedemenezes/12edd90a94f733d9abf638c8ba075e6e to your computer and use it in GitHub Desktop.

diff --git a/.gitignore b/.gitignore index 9b6465f..2741f47 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ node_modules .local .env msg.md +conversation.md diff --git a/.lefthook.yml b/.lefthook.yml deleted file mode 100644 index de23924..0000000 --- a/.lefthook.yml +++ /dev/null @@ -1,7 +0,0 @@ -pre-push:

  • parallel: true
  • commands:
  • rubocop:
  •  run: bundle exec rubocop
    
  • brakeman:
  •  run: bin/brakeman --no-pager
    

diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..2bd5a0a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 diff --git a/Gemfile b/Gemfile index 47f42e7..8e468e9 100644 --- a/Gemfile +++ b/Gemfile @@ -52,9 +52,6 @@ end group :development do

Use console on exceptions pages [https://github.com/rails/web-console]

gem "web-console"

  • Git workflow integrated locally

  • gem "lefthook", require: false end

group :test do diff --git a/Gemfile.lock b/Gemfile.lock index 84cff11..a326b00 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -141,7 +141,6 @@ GEM thor (~> 1.3) zeitwerk (>= 2.6.18, < 3.0) language_server-protocol (3.17.0.5)

  • lefthook (1.12.3) lint_roller (1.1.0) logger (1.7.0) loofah (2.24.1) @@ -375,7 +374,6 @@ DEPENDENCIES inertia_rails (~> 3.11) jbuilder kamal
  • lefthook mysql2 (> 0.5) pagy (> 9.3) puma (>= 5.0) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 3aed7a4..71dc3bc 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -62,7 +62,7 @@ class ApplicationController < ActionController::Base }, { description: "Mostras",
  •      path: ""
    
  •      path: mostras_path
       },
       {
         description: "Cinemas",
    

@@ -98,7 +98,7 @@ class ApplicationController < ActionController::Base "Notícias": [ { description: "Todas as notícias",

  •      path: noticias_url
    
  •      path: noticias_path
       }
     ],
     "Mídias": [
    

diff --git a/app/controllers/mostras_controller.rb b/app/controllers/mostras_controller.rb new file mode 100644 index 0000000..e81c7df --- /dev/null +++ b/app/controllers/mostras_controller.rb @@ -0,0 +1,22 @@ +class MostrasController < ApplicationController

  • def index
  • @edicao_atual = Edicao.find_by(descricao: "2024") # TODO: Change to 2025
  • @mostras = @edicao_atual.mostras.group_by(&:display_name)
  • @categorias = @mostras.map { |categoria, mostras| { name: categoria, class: mostras.first.tag_class } }
  • items = [
  •  # Links tem que vir do controller para incluir localizacao. Textos também
    
  •  { name: "Todos os Filmes", route: "/", icon: "program" },
    
  •  { name: "Mostras", route: mostras_url, icon: "user" },
    
  •  { name: "Cinemas", route: "/", icon: "change" },
    
  •  { name: "Juri", route: "/", icon: "clock" },
    
  •  { name: "Equipe", route: "/", icon: "clock" }
    
  • ]
  • render inertia: "Mostras/Index", props: {
  •  rootUrl: @root_url,
    
  •  items: items,
    
  •  categorias: @categorias.as_json
    
  • }
  • end +end diff --git a/app/controllers/programs_controller.rb b/app/controllers/programs_controller.rb index 9bb3a02..6642f62 100644 --- a/app/controllers/programs_controller.rb +++ b/app/controllers/programs_controller.rb @@ -21,41 +21,72 @@ class ProgramsController < ApplicationController base_scope = Programacao .joins(pelicula: :mostra) .includes(:cinema, pelicula: :mostra)
  •  .where(importacoesprog_id: last_import_id) # ALTERAR PARA EDICAO ID
    
  •  .where(importacoesprog_id: last_import_id, edicao_id: EDICAO_ATUAL) # TODO: remover importacoes?
    
  • set_filter_options(base_scope)

    Filtering SEARCH INPUT

    if params[:query].present? term = "%#{params[:query].downcase}%"

  •  base_scope = base_scope.where(
    
  •    "LOWER(peliculas.titulo_ingles_coord_int) LIKE :query OR
    
  •    LOWER(peliculas.titulo_original_coord_int) LIKE :query OR
    
  •    LOWER(peliculas.titulo_portugues_coord_int) LIKE :query OR
    
  •    LOWER(peliculas.titulo_ingles_semartigo) LIKE :query OR
    
  •    LOWER(peliculas.titulo_portugues_semartigo) LIKE :query",
    
  •    query: term
    
  •  )
    
  •  selected_filters[:query] = params[:quer]
    
  •  pelicula_ids = Pelicula.where(edicao_id: EDICAO_ATUAL).where(
    
  •    "LOWER(titulo_ingles_coord_int) LIKE :term OR
    
  •    LOWER(titulo_original_coord_int) LIKE :term OR
    
  •    LOWER(titulo_portugues_coord_int) LIKE :term OR
    
  •    LOWER(titulo_ingles_semartigo) LIKE :term OR
    
  •    LOWER(titulo_portugues_semartigo) LIKE :term",
    
  •    term: term
    
  •  ).pluck(:id)
    
  •  base_scope = base_scope.where(pelicula_id: pelicula_ids)
    
  •  selected_filters[:query] = params[:query]
    
    end
  • Gathering filter options

  • mostras_filter = Mostra.where(edicao_id: 12).to_a.uniq { |m| m.id }.sort_by { _1.permalink_pt }.as_json(
  •  only: %i[id permalink_pt nome_abreviado],
    
  •  methods: [ :tag_class, :display_name ]
    
  • )
  • if params[:mostrasFilter].present?
  •  selected_mostra = mostras_filter.find { |c| c["permalink_pt"] == params[:mostrasFilter] }
    
  •  selected_mostra = @mostras_filter.find { |c| c["permalink_pt"] == params[:mostrasFilter] }
     selected_filters[:mostrasFilter] = selected_mostra if selected_mostra
    
     if selected_mostra
       base_scope = base_scope.where(mostras: { permalink_pt: selected_filters[:mostrasFilter]["permalink_pt"] })
     end
    
    end
  • raise

  • if params[:cinemasFilter]

  •  selected_cinema = @cinemas_filter.find do |cinema_filter|
    
  •    (cinema_filter["id"].to_s === params[:cinemasFilter]) && (cinema_filter["edicao_id"] == EDICAO_ATUAL)
    
  •  end
    
  •  if selected_cinema
    
  •    selected_filters[:cinemasFilter] = selected_cinema
    
  •    base_scope = base_scope.where(cinema_id: selected_cinema["id"])
    
  •  end
    
  • end

  • if params[:paisesFilter]

  •  selected_pais = @paises_filter.find do |pais_filter|
    
  •    (pais_filter["id"].to_s === params[:paisesFilter])
    
  •  end
    
  •  if selected_pais
    
  •    selected_filters[:paisesFilter] = selected_pais
    
  •    # raise
    
  •    # base_scope = base_scope.where(paises_id: selected_pais["id"])
    
  •    base_scope = base_scope.joins(pelicula: :paises).where(pelicula: { paises: { id: selected_pais["id"] } })
    
  •  end
    
  • end

  • if params[:sessao].present?

  •  # raise
    
  •  selected_sessao = @sessoes.find { |sessao| (sessao["filter_value"] === params[:sessao]) }
    
  •  if selected_sessao
    
  •    selected_filters[:sessao] = selected_sessao
    
  •    base_scope = base_scope.where(sessao: params[:sessao]..)
    
  •  end
    
  • end

  • Get Scope Available dates

    available_dates = base_scope.distinct.pluck(:data).sort selected_date = available_dates.first

  • Filter by date

    if params[:date].present? parsed_date = Date.parse(params[:date]) rescue nil if parsed_date&.in?(available_dates) @@ -70,18 +101,19 @@ class ProgramsController < ApplicationController

    @pagy, @programacoes = pagy_infinite(programacoes_for_date, current_page)

  • @programacoes = @programacoes.map do |p|
  • @programacoes = @programacoes.map do |programacao| {
  •    id: p.id,
    
  •    data: p.data,
    
  •    sessao: [ p.display_sessao ],
    
  •    cinema: p.cinema&.nome,
    
  •    titulo: p.pelicula&.titulo_portugues_coord_int,
    
  •    duracao: p.pelicula&.duracao_coord_int,
    
  •    imagem: p.pelicula&.imagem,
    
  •    genero: p.pelicula&.genre,
    
  •    mostra: p.pelicula&.mostra&.display_name,
    
  •    mostra_tag_class: p.pelicula&.mostra&.tag_class
    
  •    id: programacao.id,
    
  •    data: programacao.data,
    
  •    sessao: [ programacao.display_sessao ],
    
  •    cinema: programacao.cinema&.nome,
    
  •    titulo: programacao.pelicula&.titulo_portugues_coord_int,
    
  •    duracao: programacao.pelicula&.duracao_coord_int,
    
  •    imagem: programacao.pelicula&.imagem,
    
  •    genero: programacao.pelicula&.genre,
    
  •    paises: programacao.pelicula&.display_paises,
    
  •    mostra: programacao.pelicula&.mostra&.display_name,
    
  •    mostra_tag_class: programacao.pelicula&.mostra&.tag_class
     }
    
    end

@@ -95,18 +127,24 @@ class ProgramsController < ApplicationController end

 # Build breadcrumbs
  • raise

    render inertia: "ProgramPage", props: { rootUrl: @root_url, tabBaseUrl: program_url, items:, elements: @programacoes, pagy: @pagy,
  •  mostrasFilter: mostras_filter,
    
  •  mostrasFilter: @mostras_filter,
    
  •  cinemasFilter: @cinemas_filter,
    
  •  paisesFilter: @paises_filter,
    
  •  sessoes: @sessoes,
     menuTabs: @menu_tabs,
    
  •  current_filters: {
    
  •  current_filters: { # those are the ones used as modelValue
       query: params[:query],
    
  •    mostrasFilter: selected_mostra
    
  •    mostrasFilter: selected_mostra,
    
  •    cinemasFilter: selected_cinema,
    
  •    paisesFilter: selected_pais,
    
  •    sessao: selected_sessao
     },
     has_active_filters: params.permit(:query, :mostrasFilter).to_h.values.any?(&:present?),
     crumbs: breadcrumbs(
    

@@ -152,9 +190,43 @@ class ProgramsController < ApplicationController def build_tab_url(date, filters) query_params = {} query_params[:mostrasFilter]= filters[:mostrasFilter]["permalink_pt"] if filters[:mostrasFilter].present?

  • query_params[:cinemasFilter]= filters[:cinemasFilter]["id"] if filters[:cinemasFilter].present?
  • query_params[:paisesFilter]= filters[:paisesFilter]["id"] if filters[:paisesFilter].present? query_params[:date] = date
  • raise

  • raise

    url_for(params: query_params, only_path: true) end
  • def set_filter_options(base_scope)
  • @paises_filter = base_scope.includes(pelicula: :paises)
  •                            .map { _1.pelicula.paises }
    
  •                            .flatten
    
  •                            .uniq
    
  •                            .sort_by { |it| it.nome_pais }
    
  •                            .as_json(
    
  •                              only: %i[id nome_pais],
    
  •                              methods: %i[filter_display filter_value]
    
  •                            )
    
  • @mostras_filter = Mostra.where(edicao_id: EDICAO_ATUAL)
  •                        .to_a
    
  •                        .uniq { |m| m.id }
    
  •                        .sort_by { |it| it.permalink_pt }
    
  •                        .as_json(
    
  •                          only: %i[id permalink_pt nome_abreviado],
    
  •                          methods: [ :tag_class, :display_name, :filter_value, :filter_display]
    
  •                        )
    
  • @cinemas_filter = Cinema.where(edicao_id: EDICAO_ATUAL)
  •                        .to_a
    
  •                        .uniq { |m| m.id }
    
  •                        .sort_by { |it| it.nome }
    
  •                        .as_json(
    
  •                          only: %i[id nome endereco edicao_id],
    
  •                          methods: %i[filter_display filter_value]
    
  •                        )
    
  • @sessoes = Programacao.where(edicao_id: EDICAO_ATUAL).to_a.uniq { |p| p.sessao }.sort.as_json(
  •  only: %i[sessao],
    
  •  methods: %i[display_sessao filter_value filter_display]
    
  • )
  • end end diff --git a/app/frontend/assets/css/safelist.tailwind.txt b/app/frontend/assets/css/safelist.tailwind.txt new file mode 100644 index 0000000..325fecb --- /dev/null +++ b/app/frontend/assets/css/safelist.tailwind.txt @@ -0,0 +1,12 @@ +border-tag-premiere-brasil +border-tag-premiere-latina +border-tag-gala-abertura +border-tag-gala-encerramento +border-tag-resistencias +border-tag-cine-memoria +border-tag-midnight-movies +border-tag-cinema-capacete +border-tag-classicos-cults +border-tag-expectativas +border-tag-itinerarios-unicos +border-tag-panorama-mundial diff --git a/app/frontend/assets/css/tailwind-config.css b/app/frontend/assets/css/tailwind-config.css index eb00df2..68f99b5 100644 --- a/app/frontend/assets/css/tailwind-config.css +++ b/app/frontend/assets/css/tailwind-config.css @@ -44,13 +44,13 @@ --color-laranja-400: #FFB46A; --color-laranja-600: #FF7F00; --color-laranja-800: #C06103;
  • --color-laranja-1000: #8A480;
  • --color-laranja-1000: #8A4805;

    --color-magenta-200: #FFD4E9; --color-magenta-400: #FF6FB7; --color-magenta-600: #FF007F; --color-magenta-800: #B20059;

  • --color-magenta-1000: #6A003;
  • --color-magenta-1000: #6A0035;

    --color-vermelho-100: #FFCCCC; --color-vermelho-200: #FF7E7E; @@ -58,24 +58,24 @@ --color-vermelho-600: #FF0000; --color-vermelho-1000: #5E0000;

  • --color-violeta-100: #da9fe0;
  • --color-violeta-100: #DA9FE0; --color-violeta-200: #DA9FE0; --color-violeta-400: #BF68C8; --color-violeta-600: #7F008C; --color-violeta-800: #580161;
  • --color-violeta-1000: #2D003;
  • --color-violeta-1000: #2D0032;

    --color-amarelo-200: #FFF8DF; --color-amarelo-400: #FFECAB; --color-amarelo-600: #FFDB5C; --color-amarelo-800: #FFC700;

  • --color-amarelo-1000: #D4A50;
  • --color-amarelo-1000: #D4A500;

    --color-verde-200: #CAFFCF; --color-verde-400: #9EF4A6; --color-verde-600: #1BBC2B; --color-verde-800: #11861D;

  • --color-verde-1000: #094A0;
  • --color-verde-1000: #094A0F;

    --color-azul-400: #94BBE2; --color-azul-600: #1881F1; @@ -140,3 +140,18 @@ --breakpoint-2xl: 1440px; --breakpoint-3xl: 1920px; }

+@theme static {

  • --color-tag-premiere-brasil: var(--color-laranja-600);
  • --color-tag-premiere-latina: var(--color-amarelo-800);
  • --color-tag-gala-abertura: var(--color-magenta-600);
  • --color-tag-gala-encerramento: var(--color-magenta-800);
  • --color-tag-resistencias: var(--color-neutrals-200);
  • --color-tag-cine-memoria: var(--color-violeta-100);
  • --color-tag-midnight-movies: var(--color-violeta-600);
  • --color-tag-cinema-capacete: var(--color-neutrals-400);
  • --color-tag-classicos-cults: var(--color-neutrals-900);
  • --color-tag-expectativas: var(--color-azul-600);
  • --color-tag-itinerarios-unicos: var(--color-verde-600);
  • --color-tag-panorama-mundial: var(--color-vermelho-600); +} diff --git a/app/frontend/assets/index.css b/app/frontend/assets/index.css index 9023c45..4fd5592 100644 --- a/app/frontend/assets/index.css +++ b/app/frontend/assets/index.css @@ -1,4 +1,3 @@ -@import "./css/tailwind-config.css"; @import "./css/typography.css"; @import "./css/animation.css"; @import "./css/tag_base.css"; diff --git a/app/frontend/components/common/cards/SessionCard.vue b/app/frontend/components/common/cards/SessionCard.vue index 93a15d3..abb88ed 100644 --- a/app/frontend/components/common/cards/SessionCard.vue +++ b/app/frontend/components/common/cards/SessionCard.vue @@ -64,9 +64,7 @@ const movieGenre = computed(() => { {{ props.session.titulo }}
  •      <span class="text-overline text-on-dark-secondary">{{
    
  •        "TBD"
    
  •      }}</span>
    
  •      <span class="text-overline text-on-dark-secondary">{{ props.session.paises }}</span>
         <img
           src="@assets/icons/divisor.svg"
           alt="divisor"
    

diff --git a/app/frontend/components/common/icons/index.js b/app/frontend/components/common/icons/index.js index e267dfa..7c5a4eb 100644 --- a/app/frontend/components/common/icons/index.js +++ b/app/frontend/components/common/icons/index.js @@ -9,7 +9,7 @@ export { default as IconSearch } from "./actions/IconSearch.vue"; // Navigation export { default as IconCarretUp } from "./navigation/IconCarretUp.vue"; export { default as IconHome } from "./navigation/IconHome.vue"; -// export { default as IconChevronLeft } from "./navigation/IconChevronLeft.vue"; +export { default as IconChevronLeft } from "./navigation/IconChevronLeft.vue"; export { default as IconChevronRight } from "./navigation/IconChevronRight.vue"; // export { default as IconMenu } from "./navigation/IconMenu.vue";

diff --git a/app/frontend/components/common/icons/navigation/IconChevronLeft.vue b/app/frontend/components/common/icons/navigation/IconChevronLeft.vue new file mode 100644 index 0000000..2c21cef --- /dev/null +++ b/app/frontend/components/common/icons/navigation/IconChevronLeft.vue @@ -0,0 +1,25 @@ +<script setup> +import { BaseIcon } from "@components/common/icons" + +const props = defineProps({

  • color: { type: String, default: undefined },
  • active: { type: Boolean, default: false },
  • width: { type: String, default: "20" },
  • height: { type: String, default: "20" } +}); +</script>

+

  • <template #default="{ fill }">
  •  <path
    
  •    fill-rule="evenodd"
    
  •    clip-rule="evenodd"
    
  •    d="M14.3616 2.0574C14.4198 2.11545 14.466 2.18442 14.4975 2.26035C14.529 2.33629 14.5452 2.41769 14.5452 2.4999C14.5452 2.58211 14.529 2.66351 14.4975 2.73944C14.466 2.81537 14.4198 2.88434 14.3616 2.9424L7.30284 9.9999L14.3616 17.0574C14.4789 17.1748 14.5449 17.3339 14.5449 17.4999C14.5449 17.6659 14.4789 17.825 14.3616 17.9424C14.2442 18.0598 14.0851 18.1257 13.9191 18.1257C13.7531 18.1257 13.5939 18.0598 13.4766 17.9424L5.97659 10.4424C5.91838 10.3843 5.8722 10.3154 5.84069 10.2394C5.80919 10.1635 5.79297 10.0821 5.79297 9.9999C5.79297 9.91769 5.80919 9.83629 5.84069 9.76035C5.8722 9.68442 5.91838 9.61545 5.97659 9.5574L13.4766 2.0574C13.5346 1.99919 13.6036 1.95301 13.6795 1.92151C13.7555 1.89 13.8369 1.87378 13.9191 1.87378C14.0013 1.87378 14.0827 1.89 14.1586 1.92151C14.2346 1.95301 14.3035 1.99919 14.3616 2.0574Z"
    
  •    :fill="fill"
    
  •  />
    

  • + + +<style scoped></style> diff --git a/app/frontend/components/features/filters/ProgramsFilterForm.vue b/app/frontend/components/features/filters/ProgramsFilterForm.vue index 35b765f..4596b3b 100644 --- a/app/frontend/components/features/filters/ProgramsFilterForm.vue +++ b/app/frontend/components/features/filters/ProgramsFilterForm.vue @@ -3,11 +3,15 @@ import { computed } from "vue"; import AccordionGroup from "@/components/AccordionGroup.vue"; import ComboboxComponent from "@/components/ui/ComboboxComponent.vue"; import SearchBar from "@/components/features/filters/SearchBar.vue"; +import SelectComponent from "@/components/ui/SelectComponent.vue";

    const props = defineProps({ modelValue: { type: Object, required: true }, updateField: { type: Function, required: true }, mostrasFilter: { type: Array, default: () => [] }, // Program-specific prop

    • cinemasFilter: { type: Array, default: () => [] }, // Program-specific prop
    • paisesFilter: { type: Array, default: () => [] },
    • sessoes: { type: Array, default: () => [] }, });

    // Transform cadernos prop for ComboboxComponent format @@ -17,10 +21,41 @@ const mostrasFilterOptions = computed(() => { value: caderno.permalink_pt, })); }); +// Transform cinema prop for ComboboxComponent format +const cinemasFilterOptions = computed(() => {

    • return props.cinemasFilter.map(cinema => ({
    • label: cinema.nome,
    • value: cinema.id,
    • })); +}); +// Transform cinema prop for ComboboxComponent format +const paisesFilterOptions = computed(() => {
    • return props.paisesFilter.map(pais => ({
    • label: pais.nome_pais,
    • value: pais.id,
    • })); +}); +// Transform cinema prop for ComboboxComponent format +const sessoesFilterOptions = computed(() => {
    • return props.sessoes.map(sessao => ({
    • label: Início às ${sessao.filter_display},
    • value: sessao.filter_value,
    • })); +});

    -const getMostraObjectFromTagClas = (permalink_pt) => {

    • return props.mostrasFilter.find(c => c.permalink_pt === permalink_pt) || null; +const getMostraObjectFromTagClas = (filter_value) => {
    • return props.mostrasFilter.find(c => c.filter_value === filter_value) || null; +}; +const getSessaoObject = (filter_value) => {
    • return props.sessoes.find(c => c.filter_value === filter_value) || null; }; +const getCinemaObject = (filter_value) => {
    • return props.cinemasFilter.find(c => c.filter_value === filter_value) || null; +};

    +const getPaisObject = (filter_value) => {

    • return props.paisesFilter.find(c => c.filter_value === filter_value) || null; +}
    </script> @@ -31,37 +66,66 @@ const getMostraObjectFromTagClas = (permalink_pt) => { @update:modelValue="(val) => props.updateField('query', val)" /> - + + + + + + + + + + + +
    + +
    + + + +
    • <AccordionGroup
    • text="Categoria"
    • :isOpen="props.modelValue.categoria != null"
    • text="Pais"
    • :isOpen="!!props.modelValue.paisesFilter"
     <template v-slot:content>
    
    •  <div class="pt-400 overflow-hidden">
      
    •  <div class="overflow-hidden w-full">
         <ComboboxComponent
      
    •      :collection="categorias"
      
    •      :modelValue="props.modelValue.categoria"
      
    •      @update:modelValue="(val) => props.updateField('categoria', val)"
      
    •    :collection="paisesFilterOptions"
      
    •    :modelValue="props.modelValue.paisesFilter?.filter_value || null"
      
    •    @update:modelValue="(val) => props.updateField('paisesFilter', getPaisObject(val))"
         />
       </div>
      
    • -->
    diff --git a/app/frontend/components/features/filters/ResponsiveFilterMenu.vue b/app/frontend/components/features/filters/ResponsiveFilterMenu.vue index 44d204f..f196cf0 100644 --- a/app/frontend/components/features/filters/ResponsiveFilterMenu.vue +++ b/app/frontend/components/features/filters/ResponsiveFilterMenu.vue @@ -68,7 +68,7 @@ const emit = defineEmits([
    diff --git a/app/frontend/components/ui/ComboboxComponent.vue b/app/frontend/components/ui/ComboboxComponent.vue index a4073c9..9fde1d0 100644 --- a/app/frontend/components/ui/ComboboxComponent.vue +++ b/app/frontend/components/ui/ComboboxComponent.vue @@ -30,7 +30,7 @@ const open = ref(false); // Props definition for v-model support const props = defineProps({ collection: { type: Array, required: true }, - modelValue: { type: String, default: "" }, // v-model prop + modelValue: { type: [String, Number], default: "" }, // v-model prop placeholder: { type: String, default: "placeholder.select" }, withIcon: { type: Boolean, default: false }, }); diff --git a/app/frontend/components/ui/SelectComponent.vue b/app/frontend/components/ui/SelectComponent.vue new file mode 100644 index 0000000..005f72f --- /dev/null +++ b/app/frontend/components/ui/SelectComponent.vue @@ -0,0 +1,144 @@ +<script setup> +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" + +// Define props and emits for v-model +const props = defineProps({ + modelValue: { + type: String, + default: '' + }, + collection: { + type: Array, + default: [ + {label: "03:30", value: "03:30"}, + {label: "22:00", value: "22:00"} + ] + } +}) + +const emit = defineEmits(['update:modelValue']) + +const handleValueChange = (value) => { + // let time = new Date(); + // const [ hour, minute ] = value.split(":") + // const timeValue = time.setHours(hour, minute, 0, 0); + emit('update:modelValue', value) +} +</script> + + + + + + + + + + + {{ sessao.label }} + + + + + + + diff --git a/app/frontend/components/ui/select/Select.vue b/app/frontend/components/ui/select/Select.vue new file mode 100644 index 0000000..b2cab62 --- /dev/null +++ b/app/frontend/components/ui/select/Select.vue @@ -0,0 +1,26 @@ +<script setup> +import { SelectRoot, useForwardPropsEmits } from "reka-ui"; + +const props = defineProps({ + open: { type: Boolean, required: false }, + defaultOpen: { type: Boolean, required: false }, + defaultValue: { type: null, required: false }, + modelValue: { type: null, required: false }, + by: { type: [String, Function], required: false }, + dir: { type: String, required: false }, + multiple: { type: Boolean, required: false }, + autocomplete: { type: String, required: false }, + disabled: { type: Boolean, required: false }, + name: { type: String, required: false }, + required: { type: Boolean, required: false }, +}); +const emits = defineEmits(["update:modelValue", "update:open"]); + +const forwarded = useForwardPropsEmits(props, emits); +</script> + + + + + + diff --git a/app/frontend/components/ui/select/SelectContent.vue b/app/frontend/components/ui/select/SelectContent.vue new file mode 100644 index 0000000..459a179 --- /dev/null +++ b/app/frontend/components/ui/select/SelectContent.vue @@ -0,0 +1,81 @@ +<script setup> +import { reactiveOmit } from "@vueuse/core"; +import { + SelectContent, + SelectPortal, + SelectViewport, + useForwardPropsEmits, +} from "reka-ui"; +import { cn } from "@/lib/utils"; +import { SelectScrollDownButton, SelectScrollUpButton } from "."; + +defineOptions({ + inheritAttrs: false, +}); + +const props = defineProps({ + forceMount: { type: Boolean, required: false }, + position: { type: String, required: false, default: "popper" }, + bodyLock: { type: Boolean, required: false }, + side: { type: null, required: false }, + sideOffset: { type: Number, required: false }, + sideFlip: { type: Boolean, required: false }, + align: { type: null, required: false }, + alignOffset: { type: Number, required: false }, + alignFlip: { type: Boolean, required: false }, + avoidCollisions: { type: Boolean, required: false }, + collisionBoundary: { type: null, required: false }, + collisionPadding: { type: [Number, Object], required: false }, + arrowPadding: { type: Number, required: false }, + sticky: { type: String, required: false }, + hideWhenDetached: { type: Boolean, required: false }, + positionStrategy: { type: String, required: false }, + updatePositionStrategy: { type: String, required: false }, + disableUpdateOnLayoutShift: { type: Boolean, required: false }, + prioritizePosition: { type: Boolean, required: false }, + reference: { type: null, required: false }, + asChild: { type: Boolean, required: false }, + as: { type: null, required: false }, + class: { type: null, required: false }, +}); +const emits = defineEmits([ + "closeAutoFocus", + "escapeKeyDown", + "pointerDownOutside", +]); + +const delegatedProps = reactiveOmit(props, "class"); + +const forwarded = useForwardPropsEmits(delegatedProps, emits); +</script> + + + + + + + + + + + + diff --git a/app/frontend/components/ui/select/SelectGroup.vue b/app/frontend/components/ui/select/SelectGroup.vue new file mode 100644 index 0000000..eecc39b --- /dev/null +++ b/app/frontend/components/ui/select/SelectGroup.vue @@ -0,0 +1,14 @@ +<script setup> +import { SelectGroup } from "reka-ui"; + +const props = defineProps({ + asChild: { type: Boolean, required: false }, + as: { type: null, required: false }, +}); +</script> + + + + + + diff --git a/app/frontend/components/ui/select/SelectItem.vue b/app/frontend/components/ui/select/SelectItem.vue new file mode 100644 index 0000000..18edeca --- /dev/null +++ b/app/frontend/components/ui/select/SelectItem.vue @@ -0,0 +1,47 @@ +<script setup> +import { reactiveOmit } from "@vueuse/core"; +import { Check } from "lucide-vue-next"; +import { + SelectItem, + SelectItemIndicator, + SelectItemText, + useForwardProps, +} from "reka-ui"; +import { cn } from "@/lib/utils"; + +const props = defineProps({ + value: { type: null, required: true }, + disabled: { type: Boolean, required: false }, + textValue: { type: String, required: false }, + asChild: { type: Boolean, required: false }, + as: { type: null, required: false }, + class: { type: null, required: false }, +}); + +const delegatedProps = reactiveOmit(props, "class"); + +const forwardedProps = useForwardProps(delegatedProps); +</script> + + + + + + + + + + + + + + diff --git a/app/frontend/components/ui/select/SelectItemText.vue b/app/frontend/components/ui/select/SelectItemText.vue new file mode 100644 index 0000000..eb87548 --- /dev/null +++ b/app/frontend/components/ui/select/SelectItemText.vue @@ -0,0 +1,14 @@ +<script setup> +import { SelectItemText } from "reka-ui"; + +const props = defineProps({ + asChild: { type: Boolean, required: false }, + as: { type: null, required: false }, +}); +</script> + + + + + + diff --git a/app/frontend/components/ui/select/SelectLabel.vue b/app/frontend/components/ui/select/SelectLabel.vue new file mode 100644 index 0000000..adb66ad --- /dev/null +++ b/app/frontend/components/ui/select/SelectLabel.vue @@ -0,0 +1,20 @@ +<script setup> +import { SelectLabel } from "reka-ui"; +import { cn } from "@/lib/utils"; + +const props = defineProps({ + for: { type: String, required: false }, + asChild: { type: Boolean, required: false }, + as: { type: null, required: false }, + class: { type: null, required: false }, +}); +</script> + + + + + + diff --git a/app/frontend/components/ui/select/SelectScrollDownButton.vue b/app/frontend/components/ui/select/SelectScrollDownButton.vue new file mode 100644 index 0000000..6932136 --- /dev/null +++ b/app/frontend/components/ui/select/SelectScrollDownButton.vue @@ -0,0 +1,30 @@ +<script setup> +import { reactiveOmit } from "@vueuse/core"; +import { ChevronDown } from "lucide-vue-next"; +import { SelectScrollDownButton, useForwardProps } from "reka-ui"; +import { cn } from "@/lib/utils"; + +const props = defineProps({ + asChild: { type: Boolean, required: false }, + as: { type: null, required: false }, + class: { type: null, required: false }, +}); + +const delegatedProps = reactiveOmit(props, "class"); + +const forwardedProps = useForwardProps(delegatedProps); +</script> + + + + + + + + diff --git a/app/frontend/components/ui/select/SelectScrollUpButton.vue b/app/frontend/components/ui/select/SelectScrollUpButton.vue new file mode 100644 index 0000000..c7a493f --- /dev/null +++ b/app/frontend/components/ui/select/SelectScrollUpButton.vue @@ -0,0 +1,30 @@ +<script setup> +import { reactiveOmit } from "@vueuse/core"; +import { ChevronUp } from "lucide-vue-next"; +import { SelectScrollUpButton, useForwardProps } from "reka-ui"; +import { cn } from "@/lib/utils"; + +const props = defineProps({ + asChild: { type: Boolean, required: false }, + as: { type: null, required: false }, + class: { type: null, required: false }, +}); + +const delegatedProps = reactiveOmit(props, "class"); + +const forwardedProps = useForwardProps(delegatedProps); +</script> + + + + + + + + diff --git a/app/frontend/components/ui/select/SelectSeparator.vue b/app/frontend/components/ui/select/SelectSeparator.vue new file mode 100644 index 0000000..34f2f59 --- /dev/null +++ b/app/frontend/components/ui/select/SelectSeparator.vue @@ -0,0 +1,21 @@ +<script setup> +import { reactiveOmit } from "@vueuse/core"; +import { SelectSeparator } from "reka-ui"; +import { cn } from "@/lib/utils"; + +const props = defineProps({ + asChild: { type: Boolean, required: false }, + as: { type: null, required: false }, + class: { type: null, required: false }, +}); + +const delegatedProps = reactiveOmit(props, "class"); +</script> + + + + diff --git a/app/frontend/components/ui/select/SelectTrigger.vue b/app/frontend/components/ui/select/SelectTrigger.vue new file mode 100644 index 0000000..398cac0 --- /dev/null +++ b/app/frontend/components/ui/select/SelectTrigger.vue @@ -0,0 +1,37 @@ +<script setup> +import { reactiveOmit } from "@vueuse/core"; +import { ChevronDown } from "lucide-vue-next"; +import { SelectIcon, SelectTrigger, useForwardProps } from "reka-ui"; +import { cn } from "@/lib/utils"; + +const props = defineProps({ + disabled: { type: Boolean, required: false }, + reference: { type: null, required: false }, + asChild: { type: Boolean, required: false }, + as: { type: null, required: false }, + class: { type: null, required: false }, + size: { type: String, required: false, default: "default" }, +}); + +const delegatedProps = reactiveOmit(props, "class", "size"); +const forwardedProps = useForwardProps(delegatedProps); +</script> + + + + + + + + + diff --git a/app/frontend/components/ui/select/SelectValue.vue b/app/frontend/components/ui/select/SelectValue.vue new file mode 100644 index 0000000..db9807f --- /dev/null +++ b/app/frontend/components/ui/select/SelectValue.vue @@ -0,0 +1,15 @@ +<script setup> +import { SelectValue } from "reka-ui"; + +const props = defineProps({ + placeholder: { type: String, required: false }, + asChild: { type: Boolean, required: false }, + as: { type: null, required: false }, +}); +</script> + + + + + + diff --git a/app/frontend/components/ui/select/index.js b/app/frontend/components/ui/select/index.js new file mode 100644 index 0000000..d911c4e --- /dev/null +++ b/app/frontend/components/ui/select/index.js @@ -0,0 +1,11 @@ +export { default as Select } from "./Select.vue"; +export { default as SelectContent } from "./SelectContent.vue"; +export { default as SelectGroup } from "./SelectGroup.vue"; +export { default as SelectItem } from "./SelectItem.vue"; +export { default as SelectItemText } from "./SelectItemText.vue"; +export { default as SelectLabel } from "./SelectLabel.vue"; +export { default as SelectScrollDownButton } from "./SelectScrollDownButton.vue"; +export { default as SelectScrollUpButton } from "./SelectScrollUpButton.vue"; +export { default as SelectSeparator } from "./SelectSeparator.vue"; +export { default as SelectTrigger } from "./SelectTrigger.vue"; +export { default as SelectValue } from "./SelectValue.vue"; diff --git a/app/frontend/entrypoints/application.css b/app/frontend/entrypoints/application.css index 5f84944..fe38a6d 100644 --- a/app/frontend/entrypoints/application.css +++ b/app/frontend/entrypoints/application.css @@ -1,7 +1,15 @@ @import url("https://fonts.googleapis.com/css2?family=Fira+Sans:wght@100;200;300;400;500;600;700;800;900&family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap"); @import "tailwindcss"; -@import "tw-animate-css";

    +@source "../../views//*.{erb,html,haml,slim}"; +@source "../../helpers//.rb"; +@source "../**/.{js,ts,jsx,tsx,vue}"; +@source "../**/*.{erb,html,rb}"; + +@source "../assets/css/safelist.tailwind.txt"; + +@import "tw-animate-css"; +@import "../assets/css/tailwind-config.css";

    @custom-variant dark (&:is(.dark *));

    @@ -28,7 +36,7 @@ --color-accent: var(--accent); --color-accent-foreground: var(--accent-foreground); --color-destructive: var(--destructive);

    • --color-border: var(--color-amarelo-800);
    • --color-border: var(--color-neutrals-300); --color-input: var(--input); --color-ring: var(--ring); --color-chart-1: var(--chart-1); diff --git a/app/frontend/pages/Mostras/Index.vue b/app/frontend/pages/Mostras/Index.vue new file mode 100644 index 0000000..f915078 --- /dev/null +++ b/app/frontend/pages/Mostras/Index.vue @@ -0,0 +1,56 @@ +<script setup> +import { IconProgram, IconClock, IconChange, IconNewUser } from "@components/common/icons"; +import MenuContext from "@/components/layout/navbar/MenuContext.vue"; +import TwContainer from "@/components/layout/TwContainer.vue";

    +const props = defineProps({

    • items: { type: Array, required: true },
    • categorias: { type: Array, default: () => []} +})

    +// TODO: alterar icones +const iconMapping = {

    • "program": IconProgram,
    • "user": IconNewUser,
    • "change": IconChange,
    • "clock": IconClock +}; +</script>

    +

    • <MenuContext
    • :items="props.items"
    • :icon-mapping="iconMapping"
    • />

      •  <li v-for="categoria in props.categorias" :class="`border-s-30 border-${categoria.class} rounded-lg overflow-hidden lg:h-[320px] h-[206px]`">
        
      •    <figure class="relative m-0 h-full p-600 flex flex-col justify-end">
        
      •      <img
        
      •        class="absolute inset-0 w-full h-full object-cover -z-1"
        
      •        src="https://s3.amazonaws.com/festivaldorio/2024/site/peliculas/original/mals_f01cor_2024113147.jpg"
        
      •        :alt="categoria.name"
        
      •      >
        
      •      <h1 class="text-white-transp-1000 mt-max text-header-regular-md z-1">{{ categoria.name }}</h1>
        
      •    </figure>
        
      •  </li>
        

    +

    + +<style scoped> +figure::after {

    • position: absolute;
    • content: "";
    • height: 100%;
    • width: 100%;
    • top: 0;
    • left: 0;
    • background: linear-gradient(180deg, rgba(0, 0, 0, 0.30) 73.08%, rgba(0, 0, 0, 0.64) 100%);
    • } +</style> diff --git a/app/frontend/pages/ProgramPage.vue b/app/frontend/pages/ProgramPage.vue index b3bfbbc..c4ab1a4 100644 --- a/app/frontend/pages/ProgramPage.vue +++ b/app/frontend/pages/ProgramPage.vue @@ -16,6 +16,7 @@ import MenuTabs from "@/components/layout/navbar/MenuTabs.vue"; import MobileTrigger from "@/components/features/filters/MobileTrigger.vue"; import ProgramsFilterForm from "@/components/features/filters/ProgramsFilterForm.vue"; import SessionCard from "@/components/common/cards/SessionCard.vue"; +import TagFilter from "@/components/common/tags/TagFilter.vue";

    import { useMobileTrigger } from "@/components/features/filters/composables/useMobileTrigger"; import { useStickyMenuTabs } from "@/components/layout/navbar/composables/useStickyMenuTabs"; @@ -31,6 +32,9 @@ const props = defineProps({ ,elements: { type: Object, required: true } ,pagy: { type: Object, required: true } ,mostrasFilter: { type: Array, default: () => [] }

    • ,cinemasFilter: { type: Array, default: () => [] }

    • ,paisesFilter: { type: Array, default: () => [] }

    • ,sessoes: { type: Array, default: () => [] } // NEW LIFE ,menuTabs: { type: Array, required: true } ,current_filters: { type: Object, default: () => ({}) } @@ -60,10 +64,18 @@ const filterSearch = (filtersFromChild) => { const cleanedFilters = {}

      for (const [key, value] of Object.entries(filtersFromChild)) {

    • if (key === 'mostrasFilter' && typeof value === 'object' && value?.permalink_pt) {
    •  cleanedFilters[key] = value.permalink_pt  // Extract just the string
      
    • } else {
    •  cleanedFilters[key] = value
      
    • if (value != null) {

    •  if (key === 'mostrasFilter' && typeof value === 'object' && value?.filter_value) {
      
    •    cleanedFilters[key] = value.filter_value  // Extract just the string
      
    •  } else if (key === 'sessao' && typeof value === 'object' && value?.filter_value) {
      
    •    cleanedFilters[key] = value.filter_value  // Extract just the string
      
    •  } else if (key === 'paisesFilter' && typeof value === 'object' && value?.filter_value) {
      
    •    cleanedFilters[key] = value.filter_value  // Extract just the string
      
    •  } else if (key === 'cinemasFilter' && typeof value === 'object' && value?.filter_value) {
      
    •    cleanedFilters[key] = value.filter_value  // Extract just the string
      
    •  } else {
      
    •    cleanedFilters[key] = value
      
    •  }
      

      } } router.get(props.tabBaseUrl, cleanedFilters, { @@ -100,6 +112,30 @@ const { sentinel, isSticky } = useStickyMenuTabs() <MobileTrigger @open-menu="openMenu" />

  • <!-- { "query": null,
    
  •   "mostrasFilter": null,
    
  •   "cinemasFilter": null,
    
  •   "paisesFilter": null,
    
  •   "sessao": { "sessao": "2000-01-01T19:00:00.000Z",
    
  •   "display_sessao": "19:00",
    
  •   "filter_value": "19h00",
    
  •   "filter_display": "19h00" }
    
  •  } -->
    
  •  class="flex gap-300 pt-200 pb-300"
    
  •  v-if="Object.values(props.current_filters).some((item) => item !== null)"
    
  •  <TagFilter
    
  •    v-for="[key, value] in Object.entries(props.current_filters).filter(([k, v]) => v !== null)"
    
  •    :key="key"
    
  •    :filter="{ label: value.filter_display, value: value.filter_value }"
    
  •    :text="value.filter_display"
    
  •    @remove-filter="removeQuery"
    
  •    />
    
  • @@ -132,6 +168,9 @@ const { sentinel, isSticky } = useStickyMenuTabs() :model-value="modelValue" :update-field="updateField" :mostrasFilter="props.mostrasFilter"

    •          :cinemasFilter="props.cinemasFilter"
      
    •          :paisesFilter="props.paisesFilter"
      
    •          :sessoes="props.sessoes"
             />
           </template>
         </ResponsiveFilterMenu>
      

    diff --git a/app/models/cinema.rb b/app/models/cinema.rb index 2c0c420..9ca1bd6 100644 --- a/app/models/cinema.rb +++ b/app/models/cinema.rb @@ -2,4 +2,12 @@ class Cinema < ApplicationRecord belongs_to :bairro belongs_to :edicao has_many :programacoes +

    • def filter_value
    • id
    • end
    • def filter_display
    • nome
    • end end diff --git a/app/models/mostra.rb b/app/models/mostra.rb index 7dee645..034b02a 100644 --- a/app/models/mostra.rb +++ b/app/models/mostra.rb @@ -8,7 +8,7 @@ class Mostra < ApplicationRecord "cine-memoria": "tag-cine-memoria", "midnight-movies": "tag-midnight-movies", "cinema-capacete": "tag-cinema-capacete",
    • "classicos-cults": "tag-classicos--cults",
    • "classicos-cults": "tag-classicos-cults", "expectativa": "tag-expectativas", "itinerarios-unicos": "tag-itinerarios-unicos", "panorama-mundial": "tag-panorama-mundial" @@ -25,4 +25,12 @@ class Mostra < ApplicationRecord def display_name nome_abreviado.split(/[-:]/).first end

    • def filter_value

    • permalink_pt

    • end

    • def filter_display

    • display_name

    • end end diff --git a/app/models/pais.rb b/app/models/pais.rb index f3cb26f..c382cd2 100644 --- a/app/models/pais.rb +++ b/app/models/pais.rb @@ -1,3 +1,11 @@ class Pais < ApplicationRecord has_many :paises_peliculas

    • def filter_value

    • id

    • end

    • def filter_display

    • nome_pais

    • end end diff --git a/app/models/pelicula.rb b/app/models/pelicula.rb index abc6681..48ec6c1 100644 --- a/app/models/pelicula.rb +++ b/app/models/pelicula.rb @@ -1,4 +1,6 @@ class Pelicula < ApplicationRecord

    • include ActionView::Helpers::TextHelper

    • belongs_to :importacao belongs_to :edicao belongs_to :mostra @@ -18,4 +20,15 @@ class Pelicula < ApplicationRecord } catalogo_ficha_2007.split(" ")&.first.split("/")[locales[I18n.locale]] || "TBD" end

    • def display_paises

    • all_paises = paises.map { |it| it.nome_pais }

    • TODO: ORDER HOW?

    • counter = all_paises.length

    • if counter > 1

    •  "#{all_paises.first} + #{pluralize(counter - 1, "país", "países")}"
      
    • else

    •  all_paises.first
      
    • end

    • end end diff --git a/app/models/programacao.rb b/app/models/programacao.rb index 9a83441..639fa4b 100644 --- a/app/models/programacao.rb +++ b/app/models/programacao.rb @@ -10,4 +10,12 @@ class Programacao < ApplicationRecord def display_sessao sessao.strftime("%H:%M") end

    • def filter_value

    • sessao.strftime("%Hh%M")

    • end

    • def filter_display

    • sessao.strftime("%Hh%M")

    • end end diff --git a/config/routes.rb b/config/routes.rb index c88daeb..49357fd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -8,6 +8,7 @@ Rails.application.routes.draw do

      other localized routes

      resources :edicoes_anteriores, only: [ :index ]

    • resources :mostras, only: [ :index ] end

    diff --git a/test/controllers/programs_controller_test.rb b/test/controllers/programs_controller_test.rb index 8c23ecf..a94cd03 100644 --- a/test/controllers/programs_controller_test.rb +++ b/test/controllers/programs_controller_test.rb @@ -462,7 +462,7 @@ class ProgramsControllerTest < ActionDispatch::IntegrationTest props = inertia_props

     mostras_filter = props["mostrasFilter"]
    
    • assert_equal 3, mostras_filter.length
    • assert_equal 4, mostras_filter.length

      Check that all mostras are included

      permalinks = mostras_filter.map { |m| m["permalink_pt"] } @@ -476,4 +476,183 @@ class ProgramsControllerTest < ActionDispatch::IntegrationTest assert mostra["permalink_pt"].present? assert mostra["nome_abreviado"].present? end

    • test "provides correct cinemas filters options" do

    • get program_url

    • assert_response :success

    • props = inertia_props

    • cinema_options = props["cinemasFilter"]

    • assert_equal 2, cinema_options.length

    • end

    • test "filters by cinema - cine brasilia" do

    • cine_brasilia = cinemas(:cine_brasilia)

    • get program_url, params: { cinemasFilter: cine_brasilia.id }

    • assert_response :success

    • props = inertia_props

    • 17 total only 5 first page

    • elements = props["elements"]

    • assert_equal 5, elements.length

    • 17 total so 5 pages

    • total_elements = props["pagy"]["count"]

    • assert_equal 17, total_elements

    • end

    • test "filters by cinema - cinepolis" do

    • cinepolis = cinemas(:cinepolis)

    • get program_url, params: { cinemasFilter: cinepolis.id }

    • assert_response :success

    • props = inertia_props

    • elements = props["elements"]

    • titles = elements.map { |e| e["titulo"] }

    • Movies associated with cinepolis only

    • expected_titles = [ "Cidade Perdida", "Cidade em Transformação", "Amor em Brasília", "Berlin Nights", "São Paulo" ]

    • expected_titles.each { |title| assert_includes titles, title }

    • end

    • test "handles invalid cinema filter gracefully" do

    • get program_url, params: { cinemasFilter: 999_999 }

    • assert_response :success

    • props = inertia_props

    • Should fallback to all movies (no filter)

    • assert props["elements"].length > 0

    • selected_filters = props["current_filters"]

    • assert_nil selected_filters["cinemasFilter"]

    • end

    • test "combines search query and cinema filter" do

    • cinepolis = cinemas(:cinepolis)

    • get program_url, params: {

    •  query: "Batman",
      
    •  cinemasFilter: cinepolis.id
      
    • }

    • assert_response :success

    • props = inertia_props

    • elements = props["elements"]

    • assert_equal 1, elements.length

    • assert_equal "Batman", elements.first["titulo"]

    • Filters preserved

    • assert_equal "Batman", props["current_filters"]["query"]

    • assert_equal cinepolis.id, props["current_filters"]["cinemasFilter"]["id"]

    • end

    • test "combines search query and cinema filter with no results" do

    • cine_brasilia = cinemas(:cine_brasilia)

    • get program_url, params: {

    •  query: "Batman",
      
    •  cinemasFilter: cine_brasilia.id
      
    • }

    • assert_response :success

    • props = inertia_props

    • assert_equal 0, props["elements"].length

    • Filters preserved

    • assert_equal "Batman", props["current_filters"]["query"]

    • assert_equal cine_brasilia.id, props["current_filters"]["cinemasFilter"]["id"]

    • end

    • test "search finds movies across different cinemas" do

    • get program_url, params: { query: "Cidade" }

    • assert_response :success

    • props = inertia_props

    • elements = props["elements"]

    • titles = elements.map { |e| e["titulo"] }

    • assert_includes titles, "Cidade Perdida"

    • assert_includes titles, "Cidade em Transformação"

    • end

    • test "cinema filter affects available dates" do

    • cine_brasilia = cinemas(:cine_brasilia)

    • get program_url, params: { cinemasFilter: cine_brasilia.id }

    • assert_response :success

    • props = inertia_props

    • available_dates = props["menuTabs"].map { _1["date"] }

    • assert_equal [ "2024-10-07" ], available_dates.uniq

    • end

    • test "preserves cinema filter when navigating dates" do

    • cinepolis = cinemas(:cinepolis)

    • get program_url, params: {

    •  cinemasFilter: cinepolis.id,
      
    •  date: "2024-10-06"
      
    • }

    • assert_response :success

    • props = inertia_props

    • elements = props["elements"]

    • titles = elements.map { |e| e["titulo"] }

    • assert_includes titles, "Amazônia Selvagem"

    • assert_includes titles, "Matrix"

    • assert_equal cinepolis.id, props["current_filters"]["cinemasFilter"]["id"]

    • end

    • test "combines all filters - search, cinema, and date" do

    • cinepolis = cinemas(:cinepolis)

    • get program_url, params: {

    •  query: "Cidade",
      
    •  cinemasFilter: cinepolis.id,
      
    •  date: "2024-10-07"
      
    • }

    • assert_response :success

    • props = inertia_props

    • elements = props["elements"]

    • assert_equal 1, elements.length

    • assert_equal "Cidade em Transformação", elements.first["titulo"]

    • assert_equal "Cidade", props["current_filters"]["query"]

    • assert_equal cinepolis.id, props["current_filters"]["cinemasFilter"]["id"]

    • assert_includes props["menuTabs"].map { _1["date"] }, "2024-10-07"

    • end

    • test "cinema filter with pagination" do

    • cine_brasilia = cinemas(:cine_brasilia)

    • get program_url, params: { cinemasFilter: cine_brasilia.id }

    • assert_response :success

    • props = inertia_props

    • assert_equal 5, props["elements"].length

    • assert_equal 1, props["pagy"]["page"]

    • assert_equal 17, props["pagy"]["count"]

    • end

    • test "returns correct cinemasFilter options in props" do

    • get program_url

    • assert_response :success

    • props = inertia_props

    • cinemas_filter = props["cinemasFilter"]

    • assert_equal 2, cinemas_filter.length

    • names = cinemas_filter.map { _1["nome"] }

    • assert_includes names, "Cinépolis Lagoon"

    • assert_includes names, "Cine Brasília"

    • Ensure structure

    • cinema = cinemas_filter.first

    • assert cinema["id"].present?

    • assert cinema["nome"].present?

    • end end diff --git a/test/fixtures/cinemas.yml b/test/fixtures/cinemas.yml index b28df45..9045272 100644 --- a/test/fixtures/cinemas.yml +++ b/test/fixtures/cinemas.yml @@ -2,7 +2,7 @@ cinepolis: nome: "Cinépolis Lagoon" bairro_id: 1

    • edicao_id: 1
    • edicao_id: 12 endereco: "Av. Borges de Medeiros, 1424 - Lagoa" telefone: "(21) 99999-9999" capacidade: "300" diff --git a/test/fixtures/mostras.yml b/test/fixtures/mostras.yml index f6c6b41..3aa744e 100644 --- a/test/fixtures/mostras.yml +++ b/test/fixtures/mostras.yml @@ -3,7 +3,7 @@ sci_fi: permalink_pt: "sci-fi" permalink_en: "sci-fi" importacao_id: 1
    • edicao_id: 1
    • edicao_id: 12 nome_pt: "Ficção Científica" nome_en: "Science Fiction" chamada_pt: "Mostra de filmes de ficção científica" diff --git a/test/fixtures/paises.yml b/test/fixtures/paises.yml new file mode 100644 index 0000000..fb85c0b --- /dev/null +++ b/test/fixtures/paises.yml @@ -0,0 +1,10 @@ +usa:
    • nome_pais: Estados Unidos +br:
    • nome_pais: Brasil +ar:
    • nome_pais: Argentina +fr:
    • nome_pais: França +ger:
    • nome_pais: Alemanha diff --git a/test/fixtures/paises_peliculas.yml b/test/fixtures/paises_peliculas.yml new file mode 100644 index 0000000..1c37ed0 --- /dev/null +++ b/test/fixtures/paises_peliculas.yml @@ -0,0 +1,46 @@ +batman_usa:
    • pais: usa
    • pelicula: batman

    +special_char_br:

    • pais: br
    • pelicula: special_char

    +matrix_usa:

    • pais: usa
    • pelicula: matrix

    +cidade_perdida_ar:

    • pais: ar
    • pelicula: cidade_perdida

    +cidade_perdida_br:

    • pais: br
    • pelicula: cidade_perdida

    +amor_brasilia_usa:

    • pais: usa
    • pelicula: amor_brasilia

    +berlin_nights_ger:

    • pais: ger
    • pelicula: berlin_nights

    +paris_stories_fr:

    • pais: fr
    • pelicula: paris_stories

    +amazonia_selvagem_br:

    • pais: br
    • pelicula: amazonia_selvagem

    +cidade_transformacao_br:

    • pais: br
    • pelicula: cidade_transformacao

    +<% 17.times do |i| %> +paises_peliculas_<%= i %>:

    • pais: br
    • pelicula: test_<%= i %> +<% end %> diff --git a/test/fixtures/peliculas.yml b/test/fixtures/peliculas.yml index ed0b58e..aa89741 100644 --- a/test/fixtures/peliculas.yml +++ b/test/fixtures/peliculas.yml @@ -6,7 +6,7 @@ batman: titulo_portugues_semartigo: "Batman" permalink: "batman-link" importacao_id: 1
    • edicao_id: 1
    • edicao_id: 12 mostra: sci_fi imagem: "batman.jpg" duracao_coord_int: 140 @@ -26,7 +26,7 @@ special_char: titulo_portugues_semartigo: "São Paulo" permalink: "sao-paulo-link" importacao_id: 1
    • edicao_id: 1
    • edicao_id: 12 mostra: sci_fi imagem: "sao-paulo.jpg" duracao_coord_int: 140 @@ -46,7 +46,7 @@ matrix: titulo_portugues_semartigo: "Matrix" permalink: "matrix-link" importacao_id: 1
    • edicao_id: 1
    • edicao_id: 12 mostra: sci_fi imagem: "matrix.jpg" duracao_coord_int: 136 @@ -67,7 +67,7 @@ test_<%= i %>: titulo_portugues_semartigo: "test_<%= i %>" permalink: "test-<%= i %>-link" importacao_id: 1
    • edicao_id: 1
    • edicao_id: 12 mostra: sci_fi imagem: "matrix.jpg" duracao_coord_int: 136 diff --git a/test/fixtures/programacoes.yml b/test/fixtures/programacoes.yml index 301648b..550e637 100644 --- a/test/fixtures/programacoes.yml +++ b/test/fixtures/programacoes.yml @@ -81,8 +81,8 @@ program_matrix:

    paginated program related with peliculas

    <% 17.times do |i| %> -program_batman_<%= i %>:

    • cinema: cinepolis +program_test_<%= i %>:
    • cinema: cine_brasilia pelicula: test_<%= i %> importacoesprog_id: 123 edicao_id: 12 @@ -145,7 +145,7 @@ programa_amor_brasilia_1: updated: <%= Date.yesterday %>

    programa_berlin_nights_1:

    • cinema: cine_brasilia
    • cinema: cinepolis pelicula: berlin_nights importacoesprog_id: 123 edicao_id: 12 @@ -165,7 +165,7 @@ programa_berlin_nights_1: updated: <%= Date.yesterday %>

    programa_paris_stories_1:

    • cinema: cine_brasilia
    • cinema: cinepolis pelicula: paris_stories importacoesprog_id: 123 edicao_id: 12 @@ -205,7 +205,7 @@ programa_amazonia_selvagem_1: updated: <%= Date.yesterday %>

    programa_cidade_transformacao_0:

    • cinema: cine_brasilia
    • cinema: cinepolis pelicula: cidade_transformacao importacoesprog_id: 123 edicao_id: 12 @@ -225,7 +225,7 @@ programa_cidade_transformacao_0: updated: <%= Date.yesterday %>

    programa_cidade_transformacao_1:

    • cinema: cine_brasilia
    • cinema: cinepolis pelicula: cidade_transformacao importacoesprog_id: 123 edicao_id: 12 @@ -285,7 +285,7 @@ programa_berlin_nights_2: updated: <%= Date.yesterday %>

    programa_cidade_transformacao_2:

    • cinema: cine_brasilia
    • cinema: cinepolis pelicula: cidade_transformacao importacoesprog_id: 123 edicao_id: 12
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment