Skip to content

GitLab

  • Menu
Projects Groups Snippets
    • Loading...
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
  • Sign in
  • F frontend
  • Project information
    • Project information
    • Activity
    • Labels
    • Planning hierarchy
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
  • Issues 108
    • Issues 108
    • List
    • Boards
    • Service Desk
    • Milestones
  • Merge requests 5
    • Merge requests 5
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
  • Deployments
    • Deployments
    • Environments
    • Releases
  • Monitor
    • Monitor
    • Incidents
  • Packages & Registries
    • Packages & Registries
    • Package Registry
    • Infrastructure Registry
  • Analytics
    • Analytics
    • Value stream
    • CI/CD
    • Repository
  • Wiki
    • Wiki
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • Pró-Mata
  • frontend
  • Merge requests
  • !90

Open
Created Oct 12, 2025 by André Sacilotto Santos@andre.santosOwner
  • Report abuse
Report abuse

32 page experiências admin

  • Overview 0
  • Commits 0
  • Changes 0

Mudanças

Foi criado uma rota nova de reservation-info na page de admin, ela retorna as experiencias e pessoas daquela reserva, também foi ajustado uma pequena mudança no componente ReservationCard, onde é ilustrado a conversa do usuário e admin, adicionado max-full, pois estava com limite de tela. Usei a mesma coisa feita em reserveSummary para o componente de mostrar as pessoas e as experiencias. A api foi implementada um exemplo, pois no back ainda não tem rota, então adicionei alguns dados mockados na classe da rota. Tem que ser adicionado um button de actions na tabela onde mostra todas as reservas, para assim conseguir entrar em uma unica reserva

Como testar

http://localhost:3002/admin/requests/reservation-info

Classe onde está chamando todos os componentes

import { useMemo } from "react";
import { ChevronLeft } from "lucide-react";
import { useTranslation } from "react-i18next";

import { Button } from "@/components/buttons/defaultButton";
import { HiUsers } from "react-icons/hi";
import CanvasCard from "@/components/cards/canvasCard";
import { ReserveSummaryExperienceCard } from "@/components/cards/reserveSummaryExperienceCard";
import { ReserveParticipantInputs } from "@/components/layouts/reserve/ReserveParticipantInputs";
import { Textarea } from "@/components/ui/textarea";
import { ReservationsLayout } from "@/components/display/reservationEvents";

import type {
  ReserveParticipant,
  ReserveSummaryExperience,
} from "@/types/reserve";
import { Typography } from "@/components/typography/typography";
import { cn } from "@/lib/utils";
import type { ReservationEvent } from "@/components/display/reservationEvents";

export type ReserveInfoProps = {
  title?: string;
  participants: ReserveParticipant[];
  experiences: ReserveSummaryExperience[];
  events?: ReservationEvent[];
  notes?: string;
  onBack?: () => void;
  className?: string;
};

export function ReserveInfo({
  title,
  participants,
  experiences,
  events,
  notes,
  onBack,
  className,
}: ReserveInfoProps) {
  const { t } = useTranslation();

  const headerTitle = title ?? t("reservationInfo.title");
  const genderCounts = useMemo(() => {
    const counts = { men: 0, women: 0, other: 0 };
    (participants || []).forEach((p) => {
      const g = String((p as any).gender || "").toUpperCase();
      if (g === "MALE" || g === "M") counts.men += 1;
      else if (g === "FEMALE" || g === "F") counts.women += 1;
      else counts.other += 1;
    });
    return counts;
  }, [participants]);

  const experiencesToRender = useMemo(() => experiences ?? [], [experiences]);
  const eventsToRender = useMemo(() => events ?? [], [events]);

  return (
    <section className={cn("min-h-screen py-5", className)}>
      <div className="mx-auto flex w-full max-w-7xl flex-col gap-8 px-2 sm:px-6 lg:px-8">
        <header className="flex items-center gap-6">
          <div className="flex items-center gap-6">
            <Typography variant="h1" className="text-3xl font-bold text-main-dark-green md:text-4xl">
              {headerTitle}
            </Typography>

            <div className="hidden sm:flex items-center gap-5 text-sm text-foreground/80">
              <span className="inline-flex items-center gap-2">
                <HiUsers className="h-5 w-5 text-main-dark-green" />
                <span className="font-medium">{t("reservationInfo.counts.men")}:</span>&nbsp;{genderCounts.men}
              </span>
              <span className="inline-flex items-center gap-2">
                <span className="font-medium">{t("reservationInfo.counts.women")}:</span>&nbsp;{genderCounts.women}
              </span>
              <span className="inline-flex items-center gap-2">
                <span className="font-medium">{t("reservationInfo.counts.other")}:</span>&nbsp;{genderCounts.other}
              </span>
            </div>
          </div>
        </header>

        <CanvasCard className="w-full border border-dark-gray/20 bg-white p-6 shadow-sm">
          <div className="flex flex-col gap-6">
            {participants && participants.length > 0 ? (
              <div className="flex flex-col gap-6">
                <Typography variant="h3" className="text-2xl font-semibold text-main-dark-green">
                  {t("reserveSummary.people.title")}
                </Typography>

                <div className="flex flex-col gap-5">
                  {participants.map((person, index) => (
                    <section key={person.id} className="rounded-2xl border border-dark-gray/20 bg-soft-white p-5 shadow-xs">
                      <header className="mb-4 flex items-center justify-between">
                        <Typography className="text-sm font-semibold uppercase tracking-[0.12em] text-main-dark-green/70">
                          {t("reserveSummary.people.personBadge", { index: index + 1 })}
                        </Typography>
                      </header>
                      <ReserveParticipantInputs person={person} readOnly className="mt-1" />
                    </section>
                  ))}
                </div>

                {notes ? (
                  <div className="flex flex-col gap-2">
                    <Typography className="text-sm font-semibold text-main-dark-green">
                      {t("reserveSummary.people.notes")}
                    </Typography>
                    <Textarea
                      value={notes}
                      readOnly
                      className="min-h-[160px] resize-none border border-dark-gray/30 bg-soft-white/80 text-sm text-foreground"
                    />
                  </div>
                ) : null}
              </div>
            ) : null}

            <div className="flex flex-col gap-3">
              <Typography variant="h3" className="text-2xl font-semibold text-main-dark-green">
              </Typography>

              <div className="grid grid-cols-1 gap-5 md:grid-cols-2">
                {experiencesToRender.map((experience) => (
                  <ReserveSummaryExperienceCard key={`${experience.title}-${experience.startDate}`} {...experience} />
                ))}
              </div>
            </div>
          </div>
        </CanvasCard>

        {eventsToRender && eventsToRender.length > 0 ? (
          <ReservationsLayout events={eventsToRender} />
        ) : null}

        <div className="flex justify-end">
          <Button
            variant="ghost"
            label={
              <span className="flex items-center gap-2">
                <ChevronLeft className="h-4 w-4" />
                {t("reserveSummary.actions.back")}
              </span>
            }
            onClick={onBack}
            className="text-main-dark-green hover:bg-main-dark-green/10"
          />
        </div>
      </div>
    </section>
  );
}

Classe onde é criado a rota e os mocks

import { createFileRoute, useNavigate } from "@tanstack/react-router";

import { ReserveInfo } from "@/components/layouts/reserve/ReserveInfo";
import type { ReservationEvent } from "@/components/display/reservationEvents";
import type {
  ReserveParticipant,
  ReserveSummaryExperience,
} from "@/types/reserve";

export const Route = createFileRoute("/admin/requests/reservation-info/")({
  component: ReserveInfoPage,
});

const mockPeople: ReserveParticipant[] = [
  {
    id: "1",
    name: "Usuário 1",
    phone: "(51) 99999-9999",
    birthDate: "2000-10-11",
    cpf: "123.456.789-00",
    gender: "MALE",
  },
  {
    id: "2",
    name: "Usuário 2",
    phone: "(51) 99999-9999",
    birthDate: "1999-02-20",
    cpf: "123.456.789-00",
    gender: "FEMALE",
  },
  {
    id: "3",
    name: "Usuário 3",
    phone: "(51) 99999-9999",
    birthDate: "1995-07-03",
    cpf: "123.456.789-00",
    gender: "MALE",
  },
];

const mockExperiences: ReserveSummaryExperience[] = [
  {
    title: "Experiência X",
    startDate: "2025-08-11",
    endDate: "2025-08-15",
    price: 356.9,
    peopleCount: 10,
    imageUrl: "/public/mock/landscape-2.webp",
  },
  {
    title: "Experiência Y",
    startDate: "2025-08-18",
    endDate: "2025-08-20",
    price: 420,
    peopleCount: 8,
    imageUrl: "/public/mock/landscape-4.webp",
  },
  {
    title: "Experiência Z",
    startDate: "2025-09-01",
    endDate: "2025-09-03",
    price: 280,
    peopleCount: 6,
    imageUrl: "/public/mock/landscape-5.jpg",
  },
];

function ReserveInfoPage() {
  const navigate = useNavigate();

  const mockEvents: ReservationEvent[] = [
    {
      id: "e1",
      user: "Usuário",
      status: "Solicitação enviada\nAguardando confirmação",
      date: "12/10/2025",
      time: "14:00",
      avatarUrl: undefined,
    },
    {
      id: "e2",
      user: "Você",
      status: "Reserva aceita\nInstruções enviadas",
      date: "13/10/2025",
      time: "09:30",
      avatarUrl: undefined,
    },
  ];

  return (
    <ReserveInfo
      participants={mockPeople}
      experiences={mockExperiences}
      events={mockEvents}
      notes="Sem observações"
      onBack={() => navigate({ to: "/" })}
    />
  );
}

-->

Acceptance Criteria

Não Havia nenhum critério

Screenshots da tela/componente desenvolvido

image image image

Observações

Rota não implementada no back e button action da tabela para levar a cada reserva também não existe


🔄 Sincronizado do GitHub

  • 🔗 PR original: https://github.com/AGES-Pro-Mata/frontend/pull/131
  • 👤 Autor: @JaymeFortes
  • 📅 Criado: 2025-10-12T20:33:40Z
  • 🔢 ID GitHub: #131
  • 🌿 Branches: 32-page-experiências-admin → dev
  • 📊 Estado: open
  • 🔀 Mergeable: unknown

Sincronizado automaticamente do GitHub para GitLab AGES

Assignee
Assign to
Reviewer
Request review from
Time tracking
Source branch: 32-page-experiências-admin