Vueでモーダルコンポーネントの操作１


# 呼び出し側のコード
```TaskView.vue
<template>
  <div class="task-view">
    <div class="task-header">
      <h2 class="text-2xl font-bold mb-4">タスク詳細</h2>
      <div class="flex space-x-4">

        <button
          @click="showEditModal = true"
        >
          編集
        </button>

      </div>
    </div>


    <!-- 編集モーダル -->
    <TaskModal
      v-if="showEditModal"
      :show="showEditModal"
      :editing-task="task"
      :categories="categories"
      @submit="handleTaskSubmit"
      @cancel="showEditModal = false"
    />
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { db } from '../firebase_settings/index.js';
import { doc, getDoc, updateDoc, deleteDoc, collection, getDocs } from 'firebase/firestore';
import TaskModal from '../Modals/TaskModal.vue';

const route = useRoute();
const router = useRouter();
const task = ref(null);
const categories = ref([]);
const showEditModal = ref(false);//タスクモーダルの表示の有無のフラグ


// タスクモーダルから emit('submit') が実行されたときに実行する関数
const handleTaskSubmit = async (taskData) => {//taskDataはタスクモーダルからの引数
  try {
    const taskRef = doc(db, 'tasks', task.value.id);
    await updateDoc(taskRef, {
      ...taskData,
      categoryId: taskData.categoryId || taskData.projectId, // 既存のcategoryIdを保持
      updated_at: new Date()
    });
    
    await fetchTask();
    showEditModal.value = false;
  } catch (error) {
    console.error('タスクの更新に失敗しました:', error);
  }
};
</script>
```
# ポイント
「:show="変数名"」はモーダル側に渡す変数
「@submit="handleTaskSubmit"」は モーダル側で「emit('submit', taskData);」が実行されたときに実行する関数名
「@cancel="showEditModal = false"」は モーダル側で「emit('cancel')」が実行された時に「showEditModal」変数に false を渡してモーダルを非表示にする処理が実行されるという意味
```
    <!-- 編集モーダル -->
    <TaskModal
      v-if="showEditModal"
      :show="showEditModal"
      :editing-task="task"
      :categories="categories"
      @submit="handleTaskSubmit"
      @cancel="showEditModal = false"
    />
```

# モーダル画面のコード
```TaskModal.vue
<template>
  <div v-if="show" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-[70]">
    <div class="relative mx-auto p-5 border shadow-lg rounded-md bg-white modal-container">
      <div class="mt-3">
        <h3 class="text-lg font-medium leading-6 text-gray-900 mb-4">
          {{ editingTask ? 'タスクの編集' : '新規タスクの追加' }}
        </h3>
        <form @submit.prevent="handleSubmit">
          <div class="mb-4">
            <label class="block text-gray-700 text-sm font-bold mb-2" for="title">
              タイトル
            </label>
            <input
              id="title"
              v-model="formData.title"
              type="text"
              class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
              required
            />
          </div>

          <div class="mb-4">
            <label class="block text-gray-700 text-sm font-bold mb-2" for="content">
              内容
            </label>
            <textarea
              id="content"
              v-model="formData.content"
              class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
              rows="4"
              required
            ></textarea>
          </div>

          <div class="mb-4">
            <label class="block text-gray-700 text-sm font-bold mb-2" for="category">
              カテゴリ
            </label>
            <select
              id="category"
              v-model="formData.projectId"
              class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
            >
              <option value="">カテゴリを選択</option>
              <option
                v-for="category in categories"
                :key="category.id"
                :value="category.id"
              >
                {{ category.title }}
              </option>
            </select>
          </div>

          <div class="mb-4">
            <label class="block text-gray-700 text-sm font-bold mb-2" for="priority">
              優先度
            </label>
            <select
              id="priority"
              v-model="formData.priority"
              class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
            >
              <option value="0">低</option>
              <option value="1">中</option>
              <option value="2">高</option>
              <option value="3">緊急</option>
            </select>
          </div>

          <div class="flex justify-end space-x-2">
            <button
              type="button"
              @click="$emit('cancel')"
              class="bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
            >
              キャンセル
            </button>
            <button
              type="submit"
              class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
            >
              {{ editingTask ? '更新' : '追加' }}
            </button>
          </div>
        </form>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, watch } from 'vue';
import { getFirestore, collection, doc, setDoc } from 'firebase/firestore';
import { v4 as uuidv4 } from 'uuid';

const props = defineProps({
  show: {
    type: Boolean,
    required: true
  },
  editingTask: {
    type: Object,
    default: null
  },
  categories: {
    type: Array,
    required: true,
    default: () => []
  },
  settings: {
    type: Object,
    required: true
  }
});

const emit = defineEmits(['submit', 'cancel']);

const formData = ref({
  title: '',
  content: '',
  projectId: '',
  priority: 0
});

// 編集モードの場合、フォームに既存のデータを設定
watch(() => props.editingTask, (newTask) => {
  if (newTask) {
    formData.value = {
      title: newTask.title,
      content: newTask.content,
      projectId: newTask.projectId,
      priority: newTask.priority
    };
  } else {
    // 新規作成時はフォームをリセットし、デフォルトカテゴリを設定
    formData.value = {
      title: '',
      content: '',
      projectId: props.settings.active_category_id?.categoryId || '',
      priority: 0
    };
  }
}, { immediate: true });

const handleSubmit = async () => {
  try {
    // ドキュメントIDを生成
    const docId = uuidv4();
    
    // Firestoreに直接保存
    const db = getFirestore();
    const taskRef = doc(db, 'tasks', docId);
    
    const taskData = {
      id: docId, // ドキュメントIDをフィールドにも追加
      title: formData.value.title,
      content: formData.value.content,
      projectId: formData.value.projectId,
      priority: parseInt(formData.value.priority),
      type: 'task',
      categoryId: formData.value.projectId,
      created_at: new Date(),
      updated_at: new Date(),
      checked: false // デフォルトで未完了状態
    };
    
    await setDoc(taskRef, taskData);
    
    // 親コンポーネントに通知
    emit('submit', taskData);
  } catch (error) {
    console.error('タスクの保存に失敗しました:', error);
    // エラーハンドリングを追加する場合はここに実装
  }
};
</script> 
```
# ポイント
propsで親のコンポーネントから値を受け取る
emit()関数で親側に通知ができ、引数を渡すこともできる
```
const props = defineProps({
  show: {
    type: Boolean,
    required: true
  },
  editingTask: {
    type: Object,
    default: null
  },
  categories: {
    type: Array,
    required: true,
    default: () => []
  },
  settings: {
    type: Object,
    required: true
  }
});
```