User: Я тебе кину 2 компонента, надо сделать форму из них, первый это список контактов, 2 компонент это сам контакт 
Assistant:  КонКонечно! Кинь, пожалуйста, свои два компонента, и я помогу сделать из них форму.
User: это список контактов , щас кину сам контакт 
<script lang="ts" setup>
import { ref } from "vue";

const searchTerm = ref("");

const isEdit = ref(false);

const data = [
  {
    phoneNumber: "+7 (999) 999 99 99",
    email: "Lishin2212318@mail.com",
    link: "@LishinPavel2123128",
    comment: "Отвечает с 10 до 19",
    fullName: "Фамилия Имя Отчество",
    position: "Должность",
  },
];
</script>

<template>
  <NModal title="Контакты">
    <template #headerButton>
      <div
        @click="() => (isEdit = !isEdit)"
        class="hover:bg-white-trans transition-all rounded-lg p-2 cursor-pointer"
      >
        <NIcon name="pencil" size="24" class="text-white" />
      </div>
    </template>
    <div class="p-5 flex flex-col gap-3">
      <div class="flex flex-row gap-2">
        <NSearch
          v-model="searchTerm"
          param-name="q-contacts"
          input-class="w-full"
          placeholder="Поиск..."
        />
        <NButton v-if="isEdit" variant="secondary" size="sm" icon="plus"
          >Создать</NButton
        >
      </div>
      <div v-for="(item, i) in data" :key="i">
        <ContactCard :item="item" :isEdit="isEdit" />
      </div>
    </div>
  </NModal>
</template>

Assistant:  СпасибоСпасибо! Теперь жду второй компонент — сам контакт (ContactCard), чтобы объединить и сделать из них форму. Пожалуйста, пришли его.
User: <script lang="ts" setup>
import { useClipboard } from "@vueuse/core";

export type Contact = {
  phoneNumber: string;
  email: string;
  link: string;
  comment: string;
  fullName: string;
  position: string;
};

type Props = {
  item: Contact;
  isEdit: boolean;
};

const snackbar = useSnackbar();

const props = defineProps<Props>();

const { copy } = useClipboard();

const handleCopy = async (value: string) => {
  try {
    await copy(value);
    snackbar.add({ type: "success", text: "Скопировано" });
  } catch (e) {
    console.error(e);
  }
};
</script>

<template>
  <div class="bg-dark-black rounded-xl p-4 flex flex-col gap-4">
    <div class="flex flex-row justify-between items-center">
      <div class="flex flex-col gap-2 text-start">
        <NText class="text-lg font-semibold">{{ item.fullName }}</NText>
        <NText class="text-primary" size="sm">{{ item.position }}</NText>
      </div>
      <div v-if="isEdit" class="bg-danger/10 p-2 rounded-md flex-shrink-0">
        <NIcon name="trash" class="text-danger w-6 h-6" size="24" />
      </div>
    </div>

    <div class="flex flex-col gap-2">
      <div
        class="flex flex-row gap-2 py-1 pr-2 pl-1 items-center bg-[#1D1F22] rounded-lg cursor-pointer"
      >
        <div class="bg-primary p-1 rounded-lg">
          <NIcon name="phone" class="text-white" size="20" />
        </div>
        <NText size="sm">{{ item.phoneNumber }}</NText>
        <div @click="handleCopy(item.phoneNumber)" class="ml-auto">
          <NIcon name="copy" class="text-white hover:text-primary" size="20" />
        </div>
      </div>

      <div
        class="flex flex-row gap-2 py-1 pr-2 pl-1 items-center bg-[#1D1F22] rounded-lg cursor-pointer"
      >
        <div class="bg-primary p-1 rounded-lg">
          <NIcon name="mail" class="text-white" size="20" />
        </div>
        <NText size="sm">{{ item.email }}</NText>
        <div @click="handleCopy(item.email)" class="ml-auto">
          <NIcon name="copy" class="text-white hover:text-primary" size="20" />
        </div>
      </div>

      <div
        class="flex flex-row gap-2 py-1 pr-2 pl-1 items-center bg-[#1D1F22] rounded-lg cursor-pointer"
      >
        <div class="bg-primary p-1 rounded-lg">
          <NIcon name="message" class="text-white" size="20" />
        </div>
        <NText size="sm">{{ item.link }}</NText>
        <div @click="handleCopy(item.link)" class="ml-auto">
          <NIcon name="copy" class="text-white hover:text-primary" size="20" />
        </div>
      </div>
      <div
        v-if="item.comment"
        class="py-1 pr-2 pl-1 bg-[#1D1F22] rounded-lg cursor-pointer"
      >
        <NText size="sm">{{ item.comment }}</NText>
      </div>
    </div>
  </div>
</template>

Assistant:  ОтОтлично! У тебя есть два компонента:

1. Контакт-лист — с модальным окном, поиском, кнопкой и перечислением контактов.
2. Карточка контакта — отображение информации по контакту с функцией копирования и возможностью редактирования (удалять пока не реализовано, только иконка).

---

Сейчас я помогу объединить их в форму, где при переходе в режим редактирования (`isEdit`) ты сможешь не только просматривать, но и править данные каждого контакта (например, редактировать в поля ввода прямо внутри `ContactCard`), а также создавать новые и удалять существующие.

---

### Что сделаю:

- Сделаю `ContactCard` с двумя режимами — просмотр и редактирование.
- Из `ContactList` передам методы для обновления, создания и удаления контактов.
- Управление состоянием списка контактов в родительском компоненте.
- Добавлю базовую валидацию и сохранение изменений.

---

### Исправленная и расширенная версия:

---

#### ContactList.vue

```vue
<script lang="ts" setup>
import { ref, reactive } from "vue";
import ContactCard from "./ContactCard.vue";

type Contact = {
  phoneNumber: string;
  email: string;
  link: string;
  comment: string;
  fullName: string;
  position: string;
};

const searchTerm = ref("");
const isEdit = ref(false);

// Сделаем данные реактивными, чтобы редактировать и обновлять
const data = reactive<Contact[]>([
  {
    phoneNumber: "+7 (999) 999 99 99",
    email: "Lishin2212318@mail.com",
    link: "@LishinPavel2123128",
    comment: "Отвечает с 10 до 19",
    fullName: "Фамилия Имя Отчество",
    position: "Должность",
  },
]);

// Фильтрация по поиску (по имени и позиции)
const filteredData = computed(() =>
  data.filter(
    (item) =>
      item.fullName.toLowerCase().includes(searchTerm.value.toLowerCase()) ||
      item.position.toLowerCase().includes(searchTerm.value.toLowerCase())
  )
);

function addContact() {
  data.push({
    phoneNumber: "",
    email: "",
    link: "",
    comment: "",
    fullName: "",
    position: "",
  });
}

function removeContact(index: number) {
  data.splice(index, 1);
}

// Для обновления контакта
function updateContact(index: number, newData: Contact) {
  data[index] = { ...newData };
}
</script>

<template>
  <NModal title="Контакты" width="600px">
    <template #headerButton>
      <div
        @click="() => (isEdit.value = !isEdit.value)"
        class="hover:bg-white-trans transition-all rounded-lg p-2 cursor-pointer"
      >
        <NIcon name="pencil" size="24" class="text-white" />
      </div>
    </template>

    <div class="p-5 flex flex-col gap-3">
      <div class="flex flex-row gap-2">
        <NSearch
          v-model="searchTerm"
          param-name="q-contacts"
          input-class="w-full"
          placeholder="Поиск..."
        />

        <NButton v-if="isEdit" variant="secondary" size="sm" icon="plus" @click="addContact">
          Создать
        </NButton>
      </div>

      <div v-for="(item, i) in filteredData" :key="i">
        <ContactCard
          :item="item"
          :isEdit="isEdit"
          @update="(newData) => updateContact(i, newData)"
          @remove="() => removeContact(i)"
        />
      </div>
    </div>
  </NModal>
</template>
```

---

#### ContactCard.vue

```vue
<script lang="ts" setup>
import { ref, reactive, watch } from "vue";
import { useClipboard } from "@vueuse/core";

export type Contact = {
  phoneNumber: string;
  email: string;
  link: string;
  comment: string;
  fullName: string;
  position: string;
};

type Props = {
  item: Contact;
  isEdit: boolean;
};

const props = defineProps<Props>();
const emit = defineEmits<{
  (e: "update", newData: Contact): void;
  (e: "remove"): void;
}>();

const snackbar = useSnackbar();
const { copy } = useClipboard();

// Для редактирования сделаем копию объекта
const editableContact = reactive({ ...props.item });

// Отслеживаем изменения из родителя и синхронизируем локальный объект
watch(
  () => props.item,
  (newVal) => {
    Object.assign(editableContact, newVal);
  }
);

const handleCopy = async (value: string) => {
  try {
    await copy(value);
    snackbar.add({ type: "success", text: "Скопировано" });
  } catch (e) {
    console.error(e);
  }
};

function save() {
  // Можно сделать валидацию
  // Например, чтобы fullName и phoneNumber не были пустыми
  if (!editableContact.fullName.trim()) {
    snackbar.add({ type: "error", text: "ФИО не может быть пустым" });
    return;
  }
  if (!editableContact.phoneNumber.trim()) {
    snackbar.add({ type: "error", text: "Телефон не может быть пустым" });
    return;
  }
  emit("update", { ...editableContact });
}

function remove() {
  // Потверждение удаления
  if (confirm("Удалить этот контакт?")) {
    emit("remove");
  }
}
</script>

<template>
  <div class="bg-dark-black rounded-xl p-4 flex flex-col gap-4">
    <div class="flex flex-row justify-between items-center">
      <div class="flex flex-col gap-2 text-start w-full">
        <template v-if="isEdit">
          <NInput
            v-model="editableContact.fullName"
            placeholder="Фамилия Имя Отчество"
            clearable
            size="medium"
          />
          <NInput
            v-model="editableContact.position"
            placeholder="Должность"
            clearable
            size="small"
          />
        </template>
        <template v-else>
          <NText class="text-lg font-semibold">{{ editableContact.fullName }}</NText>
          <NText class="text-primary" size="sm">{{ editableContact.position }}</NText>
        </template>
      </div>

      <div v-if="isEdit" class="flex gap-2 flex-shrink-0 items-center">
        <NButton size="small" variant="error" onClick="remove">
          <NIcon name="trash" class="text-danger" size="20" />
        </NButton>
        <NButton size="small" variant="success" @click="save">Сохранить</NButton>
      </div>
    </div>

    <div class="flex flex-col gap-2">
      <template v-if="isEdit">
        <NInput
          v-model="editableContact.phoneNumber"
          ●
User: используй vee validate typescript 
Assistant: