api-platform / create-client

Generate React or Vue.js-based Progressive Web Apps from an Hydra-enabled API. Also support React Native.
https://api-platform.com/docs/client-generator/
MIT License
369 stars 132 forks source link

Next gen ManyToOne error #374

Open amoulin974 opened 7 months ago

amoulin974 commented 7 months ago

API Platform version(s) affected: 3.2.11

Description
If an entity contains a manytoone relation, the pwa/component/nameOfEntity/list.tsx generate by Next gen contains error

I have two entity : Entreprise and Meet.

Class Entreprise

<?php

namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use App\Repository\EntrepriseRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use ApiPlatform\Metadata\Delete;
use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: EntrepriseRepository::class)]
#[ApiResource(

    normalizationContext: ['groups' => ['entreprise:read']],
    denormalizationContext: ['groups' => ['entreprise:write']],
)]
class Entreprise
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    #[Groups(['entreprise:read'])]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    #[Groups(['entreprise:read', 'entreprise:write', 'user:read'])]
    private ?string $name = null;

    #[ORM\Column(length: 255, nullable: true)]
    #[Groups(['entreprise:read', 'entreprise:write', 'user:read'])]
    private ?string $address_street = null;

    #[ORM\Column(length: 255, nullable: true)]
    #[Groups(['entreprise:read', 'entreprise:write', 'user:read'])]
    private ?string $address_city = null;

    #[ORM\Column(length: 255, nullable: true)]
    #[Groups(['entreprise:read', 'entreprise:write', 'user:read'])]
    private ?string $address_cp = null;

    #[ORM\Column(options: ['default' => 0])]
    #[Groups(['entreprise:read', 'entreprise:write', 'user:read'])]
    private ?int $type = null;

    #[ORM\OneToMany(mappedBy: 'entreprise_id', targetEntity: Meet::class, orphanRemoval: true)]
    #[Groups(['entreprise:read', 'user:read'])]
    private Collection $meets;

    #[ORM\OneToMany(mappedBy: 'entreprise_id', targetEntity: Customer::class, orphanRemoval: true)]
    #[Groups(['entreprise:read', 'user:read'])]
    private Collection $customers;

    #[ORM\OneToMany(mappedBy: 'entreprise_id', targetEntity: Employee::class, orphanRemoval: true)]
    #[Groups(['entreprise:read', 'user:read'])]
    private Collection $employees;

    #[ORM\OneToMany(mappedBy: 'entreprise_id', targetEntity: User::class)]
    #[Groups(['entreprise:read'])]
    private Collection $users;

Class Meet

<?php

namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use App\Repository\MeetRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use ApiPlatform\Metadata\Delete;
use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: MeetRepository::class)]
#[ApiResource(
    normalizationContext: ['groups' => ['meet:read']],
    denormalizationContext: ['groups' => ['meet:write']],
)]
class Meet
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    #[Groups(['meet:read', 'entreprise:read'])]
    private ?int $id = null;

    #[ORM\Column(type: Types::DATETIME_MUTABLE)]
    #[Groups(['meet:read', 'meet:write', 'entreprise:read'])]
    private ?\DateTimeInterface $date_start = null;

    #[ORM\Column]
    #[Groups(['meet:read', 'meet:write', 'entreprise:read'])]
    private ?int $duration = null;

    #[ORM\ManyToOne(inversedBy: 'meets')]
    #[ORM\JoinColumn(nullable: false)]
    #[Groups(['meet:read', 'meet:write'])]
    private ?Entreprise $entreprise_id = null;

    #[ORM\OneToMany(mappedBy: 'meet_id', targetEntity: Notification::class, orphanRemoval: true)]
    #[Groups(['meet:read', 'meet:write', 'entreprise:read'])]
    private Collection $notifications;

    #[ORM\ManyToMany(targetEntity: Employee::class, mappedBy: 'meets')]
    #[Groups(['meet:read', 'meet:write', 'entreprise:read'])]
    private Collection $employees;

    #[ORM\ManyToMany(targetEntity: Customer::class, inversedBy: 'meets')]
    #[Groups(['meet:read', 'meet:write', 'entreprise:read'])]
    private Collection $customers;

    public function __construct()
    {
        $this->notifications = new ArrayCollection();
        $this->employees = new ArrayCollection();
        $this->customers = new ArrayCollection();
    }

    public function getId(): ?int
    {

        return $this->id;
    }

    public function getDateStart(): ?\DateTimeInterface
    {

        return $this->date_start;
    }

    public function setDateStart(\DateTimeInterface $date_start): static
    {
        $this->date_start = $date_start;

        return $this;
    }

    public function getDuration(): ?int
    {
        return $this->duration;
    }

    public function setDuration(int $duration): static
    {
        $this->duration = $duration;

        return $this;
    }

    public function getEntrepriseId(): ?Entreprise
    {
        return $this->entreprise_id;
    }

    public function setEntrepriseId(?Entreprise $entreprise_id): static
    {
        $this->entreprise_id = $entreprise_id;

        return $this;
    }

    /**
     * @return Collection<int, Notification>
     */
    public function getNotifications(): Collection
    {
        return $this->notifications;
    }

    public function addNotification(Notification $notification): static
    {
        if (!$this->notifications->contains($notification)) {
            $this->notifications->add($notification);
            $notification->setMeetId($this);
        }

        return $this;
    }

    public function removeNotification(Notification $notification): static
    {
        if ($this->notifications->removeElement($notification)) {
            // set the owning side to null (unless already changed)
            if ($notification->getMeetId() === $this) {
                $notification->setMeetId(null);
            }
        }

        return $this;
    }

    /**
     * @return Collection<int, Employee>
     */
    public function getEmployees(): Collection
    {
        return $this->employees;
    }

    public function addEmployee(Employee $employee): static
    {
        if (!$this->employees->contains($employee)) {
            $this->employees->add($employee);
            $employee->addMeet($this);
        }

        return $this;
    }

    public function removeEmployee(Employee $employee): static
    {
        if ($this->employees->removeElement($employee)) {
            $employee->removeMeet($this);
        }

        return $this;
    }

    /**
     * @return Collection<int, Customer>
     */
    public function getCustomers(): Collection
    {
        return $this->customers;
    }

    public function addCustomer(Customer $customer): static
    {
        if (!$this->customers->contains($customer)) {
            $this->customers->add($customer);
        }

        return $this;
    }

    public function removeCustomer(Customer $customer): static
    {
        $this->customers->removeElement($customer);

        return $this;
    }
}

pwa/components/meet/list.tsx

import { FunctionComponent } from "react";
import Link from "next/link";

import ReferenceLinks from "../common/ReferenceLinks";
import { getItemPath } from "../../utils/dataAccess";
import { Meet } from "../../types/Meet";

interface Props {
  meets: Meet[];
}

export const List: FunctionComponent<Props> = ({ meets }) => (
  <div className="p-4">
    <div className="flex justify-between items-center">
      <h1 className="text-3xl mb-2">Meet List</h1>
      <Link
        href="/meets/create"
        className="bg-cyan-500 hover:bg-cyan-700 text-white text-sm font-bold py-2 px-4 rounded"
      >
        Create
      </Link>
    </div>
    <table
      cellPadding={10}
      className="shadow-md table border-collapse min-w-full leading-normal table-auto text-left my-3"
    >
      <thead className="w-full text-xs uppercase font-light text-gray-700 bg-gray-200 py-2 px-4">
        <tr>
          <th>id</th>
          <th>date_start</th>
          <th>duration</th>
          <th>entreprise_id</th>
          <th>notifications</th>
          <th>employees</th>
          <th>customers</th>
          <th colSpan={2} />
        </tr>
      </thead>
      <tbody className="text-sm divide-y divide-gray-200">
        {meets &&
          meets.length !== 0 &&
          meets.map(
            (meet) =>
              meet["@id"] && (
                <tr className="py-2" key={meet["@id"]}>
                  <th scope="row">
                    <ReferenceLinks
                      items={{
                        href: getItemPath(meet["@id"], "/meets/[id]"),
                        name: meet["@id"],
                      }}
                    />
                  </th>
                  <td>{meet["date_start"]?.toLocaleString()}</td>
                  <td>{meet["duration"]}</td>
                  <td>
                    {console.log()}

                    <ReferenceLinks
                      items={meet["entreprise_id"].map((ref: any) => ({
                        href: getItemPath(ref, "/entreprises/[id]"),
                        name: ref,
                      }))}/>
                  </td>
                  <td>
                    <ReferenceLinks
                      items={meet["notifications"].map((emb: any) => ({
                        href: getItemPath(emb["@id"], "/notifications/[id]"),
                        name: emb["@id"],
                      }))}
                    />
                  </td>
                  <td>
                    <ReferenceLinks
                      items={meet["employees"].map((ref: any) => ({
                        href: getItemPath(ref, "/employees/[id]"),
                        name: ref,
                      }))}
                    />
                  </td>
                  <td>
                    <ReferenceLinks
                      items={meet["customers"].map((ref: any) => ({
                        href: getItemPath(ref, "/customers/[id]"),
                        name: ref,
                      }))}
                    />
                  </td>
                  <td className="w-8">
                    <Link
                      href={getItemPath(meet["@id"], "/meets/[id]")}
                      className="text-cyan-500"
                    >
                      Show
                      <svg
                        xmlns="http://www.w3.org/2000/svg"
                        viewBox="0 0 24 24"
                        fill="currentColor"
                        className="w-6 h-6"
                      >
                        <path d="M12 15a3 3 0 100-6 3 3 0 000 6z" />
                        <path
                          fillRule="evenodd"
                          d="M1.323 11.447C2.811 6.976 7.028 3.75 12.001 3.75c4.97 0 9.185 3.223 10.675 7.69.12.362.12.752 0 1.113-1.487 4.471-5.705 7.697-10.677 7.697-4.97 0-9.186-3.223-10.675-7.69a1.762 1.762 0 010-1.113zM17.25 12a5.25 5.25 0 11-10.5 0 5.25 5.25 0 0110.5 0z"
                          clipRule="evenodd"
                        />
                      </svg>
                    </Link>
                  </td>
                  <td className="w-8">
                    <Link
                      href={getItemPath(meet["@id"], "/meets/[id]/edit")}
                      className="text-cyan-500"
                    >
                      Edit
                      <svg
                        xmlns="http://www.w3.org/2000/svg"
                        viewBox="0 0 24 24"
                        fill="currentColor"
                        className="w-6 h-6"
                      >
                        <path d="M21.731 2.269a2.625 2.625 0 00-3.712 0l-1.157 1.157 3.712 3.712 1.157-1.157a2.625 2.625 0 000-3.712zM19.513 8.199l-3.712-3.712-8.4 8.4a5.25 5.25 0 00-1.32 2.214l-.8 2.685a.75.75 0 00.933.933l2.685-.8a5.25 5.25 0 002.214-1.32l8.4-8.4z" />
                        <path d="M5.25 5.25a3 3 0 00-3 3v10.5a3 3 0 003 3h10.5a3 3 0 003-3V13.5a.75.75 0 00-1.5 0v5.25a1.5 1.5 0 01-1.5 1.5H5.25a1.5 1.5 0 01-1.5-1.5V8.25a1.5 1.5 0 011.5-1.5h5.25a.75.75 0 000-1.5H5.25z" />
                      </svg>
                    </Link>
                  </td>
                </tr>
              )
          )}
      </tbody>
    </table>
  </div>
);

Possible Solution
Error TypeError: meet.entreprise_id.map is not a function come from this code

<ReferenceLinks
                      items={meet["entreprise_id"].map((ref: any) => ({
                        href: getItemPath(ref, "/entreprises/[id]"),
                        name: ref,
                      }))}/>

meet["entreprise_id"] is not an array so it's impossible to map it

It's possible to solve with


                      items={[meet["entreprise_id"]].map((ref: any) => ({
                        href: getItemPath(ref, "/entreprises/[id]"),
                        name: ref,
                      }))}/>```
**Additional Context**