From 9bcd17085cae34fb337ddf45a5968b53d14da3a1 Mon Sep 17 00:00:00 2001
From: Luisfp0 <luis.oliveirabr1@gmail.com>
Date: Fri, 1 Nov 2024 16:00:35 -0300
Subject: [PATCH] feat: skillsFilter on applicationManagement

---
 .../[roleId]/ApplicationManagement.tsx        | 162 ++++++++++++------
 .../[id]/applications/[roleId]/page.tsx       |  20 ++-
 apps/web/app/components/SkillsFilter.tsx      | 106 ++++++++++++
 3 files changed, 231 insertions(+), 57 deletions(-)
 create mode 100644 apps/web/app/components/SkillsFilter.tsx

diff --git a/apps/web/app/(roles)/dashboard/[id]/applications/[roleId]/ApplicationManagement.tsx b/apps/web/app/(roles)/dashboard/[id]/applications/[roleId]/ApplicationManagement.tsx
index 84e2bcc9..f8616269 100644
--- a/apps/web/app/(roles)/dashboard/[id]/applications/[roleId]/ApplicationManagement.tsx
+++ b/apps/web/app/(roles)/dashboard/[id]/applications/[roleId]/ApplicationManagement.tsx
@@ -3,6 +3,7 @@
 import React, { useEffect, useState } from 'react'
 import { useRouter } from 'next/navigation'
 import { Eye, ChevronLeft, Filter, ChevronDown } from 'lucide-react'
+import SkillsFilter from 'app/components/SkillsFilter'
 
 const statusColors = {
   pending: 'bg-yellow-100 text-yellow-800',
@@ -31,6 +32,7 @@ export default function ApplicationsManagement({
   roleId,
   applications: initialApplications,
   userId,
+  allSkills,
 }) {
   const router = useRouter()
   const [applications, setApplications] = useState(initialApplications)
@@ -39,6 +41,16 @@ export default function ApplicationsManagement({
     englishLevel: '',
     yearsOfExperience: '',
   })
+  const [selectedSkills, setSelectedSkills] = useState([])
+
+  const filterApplicationsBySkills = (applications) => {
+    if (selectedSkills.length === 0) return applications
+
+    return applications.filter((application) => {
+      const applicantSkills = application.Subscribers.skillsId || []
+      return selectedSkills.some((skillId) => applicantSkills.includes(skillId))
+    })
+  }
 
   const calculateExperience = (startDate: string) => {
     const start = new Date(startDate)
@@ -65,12 +77,22 @@ export default function ApplicationsManagement({
       })
     }
 
+    // Adicione o filtro de skills
+    if (selectedSkills.length > 0) {
+      filtered = filtered.filter((application) => {
+        const applicantSkills = application.Subscribers.skillsId || []
+        return selectedSkills.some((skillId) =>
+          applicantSkills.includes(skillId)
+        )
+      })
+    }
+
     setApplications(filtered)
   }
 
   useEffect(() => {
     applyFilters()
-  }, [filters])
+  }, [filters, selectedSkills])
 
   return (
     <div className="container mx-auto px-4 py-8">
@@ -96,12 +118,10 @@ export default function ApplicationsManagement({
         </div>
 
         <div className="p-4">
-          <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
-            <div className="flex flex-col gap-1.5">
-              <label
-                htmlFor="englishLevel"
-                className="text-sm font-medium text-gray-700"
-              >
+          {/* Grid de filtros */}
+          <div className="grid grid-cols-1 gap-6 sm:grid-cols-3">
+            <div>
+              <label className="mb-1.5 block text-sm font-medium text-gray-700">
                 Nível de Inglês
               </label>
               <div className="relative">
@@ -127,11 +147,8 @@ export default function ApplicationsManagement({
               </div>
             </div>
 
-            <div className="flex flex-col gap-1.5">
-              <label
-                htmlFor="experience"
-                className="text-sm font-medium text-gray-700"
-              >
+            <div>
+              <label className="mb-1.5 block text-sm font-medium text-gray-700">
                 Experiência Mínima
               </label>
               <div className="relative">
@@ -157,57 +174,90 @@ export default function ApplicationsManagement({
               </div>
             </div>
 
-            <div className="flex items-end lg:col-span-2">
-              {filters.englishLevel || filters.yearsOfExperience ? (
-                <div className="flex w-full flex-wrap items-center justify-between gap-2">
-                  <div className="flex flex-wrap gap-2">
-                    {filters.englishLevel && (
-                      <span className="inline-flex items-center gap-1 rounded-full bg-indigo-50 px-3 py-1 text-sm font-medium text-indigo-600">
-                        {englishLevelTranslations[filters.englishLevel]}
-                        <button
-                          onClick={() =>
-                            setFilters((prev) => ({
-                              ...prev,
-                              englishLevel: '',
-                            }))
-                          }
-                          className="ml-1 rounded-full p-0.5 hover:bg-indigo-100"
-                        >
-                          ×
-                        </button>
-                      </span>
-                    )}
-                    {filters.yearsOfExperience && (
-                      <span className="inline-flex items-center gap-1 rounded-full bg-indigo-50 px-3 py-1 text-sm font-medium text-indigo-600">
-                        {filters.yearsOfExperience}+ anos
-                        <button
-                          onClick={() =>
-                            setFilters((prev) => ({
-                              ...prev,
-                              yearsOfExperience: '',
-                            }))
-                          }
-                          className="ml-1 rounded-full p-0.5 hover:bg-indigo-100"
-                        >
-                          ×
-                        </button>
-                      </span>
-                    )}
-                  </div>
+            <SkillsFilter
+              allSkills={allSkills}
+              applications={initialApplications}
+              selectedSkills={selectedSkills}
+              onFilterChange={(newSkills) => {
+                setSelectedSkills(newSkills)
+              }}
+              onSelectedSkillRemove={(skillId) => {
+                setSelectedSkills((prev) => prev.filter((id) => id !== skillId))
+              }}
+            />
+          </div>
+
+          {/* Tags dos filtros selecionados */}
+          {(filters.englishLevel ||
+            filters.yearsOfExperience ||
+            selectedSkills.length > 0) && (
+            <div className="mt-4 flex flex-wrap items-center gap-2">
+              {filters.englishLevel && (
+                <span className="inline-flex items-center gap-1 rounded-full bg-indigo-50 px-3 py-1 text-sm font-medium text-indigo-600">
+                  {englishLevelTranslations[filters.englishLevel]}
                   <button
                     onClick={() =>
-                      setFilters({ englishLevel: '', yearsOfExperience: '' })
+                      setFilters((prev) => ({
+                        ...prev,
+                        englishLevel: '',
+                      }))
                     }
-                    className="text-sm font-medium text-gray-500 hover:text-gray-700"
+                    className="ml-1 rounded-full p-0.5 hover:bg-indigo-100"
                   >
-                    Limpar todos
+                    ×
                   </button>
-                </div>
-              ) : (
-                <></>
+                </span>
               )}
+              {filters.yearsOfExperience && (
+                <span className="inline-flex items-center gap-1 rounded-full bg-indigo-50 px-3 py-1 text-sm font-medium text-indigo-600">
+                  {filters.yearsOfExperience}+ anos
+                  <button
+                    onClick={() =>
+                      setFilters((prev) => ({
+                        ...prev,
+                        yearsOfExperience: '',
+                      }))
+                    }
+                    className="ml-1 rounded-full p-0.5 hover:bg-indigo-100"
+                  >
+                    ×
+                  </button>
+                </span>
+              )}
+              {selectedSkills.map((skillId) => {
+                const skill = allSkills.find((s) => s.id.toString() === skillId)
+                if (!skill) return null
+
+                return (
+                  <span
+                    key={skillId}
+                    className="inline-flex items-center gap-1 rounded-full bg-indigo-50 px-3 py-1 text-sm font-medium text-indigo-600"
+                  >
+                    {skill.emoji} {skill.name}
+                    <button
+                      onClick={() =>
+                        setSelectedSkills((prev) =>
+                          prev.filter((id) => id !== skillId)
+                        )
+                      }
+                      className="ml-1 rounded-full p-0.5 hover:bg-indigo-100"
+                    >
+                      ×
+                    </button>
+                  </span>
+                )
+              })}
+              <button
+                onClick={() => {
+                  setFilters({ englishLevel: '', yearsOfExperience: '' })
+                  setSelectedSkills([])
+                }}
+                className="text-sm font-medium text-gray-500 hover:text-gray-700"
+              >
+                Limpar todos
+              </button>
             </div>
-          </div>
+          )}
         </div>
       </div>
 
diff --git a/apps/web/app/(roles)/dashboard/[id]/applications/[roleId]/page.tsx b/apps/web/app/(roles)/dashboard/[id]/applications/[roleId]/page.tsx
index 45ffddef..68096c8a 100644
--- a/apps/web/app/(roles)/dashboard/[id]/applications/[roleId]/page.tsx
+++ b/apps/web/app/(roles)/dashboard/[id]/applications/[roleId]/page.tsx
@@ -1,7 +1,6 @@
 import { getSupabaseClient } from 'db'
 import { notFound } from 'next/navigation'
 import ApplicationsManagement from './ApplicationManagement'
-
 interface PageProps {
   params: {
     id: string
@@ -40,6 +39,22 @@ async function getApplications(roleId: string) {
   return applications
 }
 
+async function getAllSkills() {
+  const supabase = getSupabaseClient()
+
+  const { data: skills, error } = await supabase
+    .from('Skills')
+    .select('*')
+    .order('name')
+
+  if (error) {
+    console.error('Error fetching skills:', error)
+    return null
+  }
+
+  return skills
+}
+
 async function verifyRoleOwnership(roleId: string, userId: string) {
   const supabase = getSupabaseClient()
 
@@ -70,11 +85,14 @@ export default async function ApplicationsPage({ params }: PageProps) {
     notFound()
   }
 
+  const allSkills = await getAllSkills()
+
   return (
     <ApplicationsManagement
       roleId={roleId}
       userId={userId}
       applications={applications}
+      allSkills={allSkills}
     />
   )
 }
diff --git a/apps/web/app/components/SkillsFilter.tsx b/apps/web/app/components/SkillsFilter.tsx
new file mode 100644
index 00000000..e8cf02a0
--- /dev/null
+++ b/apps/web/app/components/SkillsFilter.tsx
@@ -0,0 +1,106 @@
+import React, { useState, useEffect, useRef, useMemo } from 'react'
+import { ChevronDown } from 'lucide-react'
+
+const SkillsFilter = ({
+  allSkills,
+  applications,
+  selectedSkills = [],
+  onFilterChange,
+  onSelectedSkillRemove,
+}) => {
+  const [isOpen, setIsOpen] = useState(false)
+  const dropdownRef = useRef(null)
+
+  // Filtrar apenas as skills que existem entre os candidatos
+  const availableSkills = useMemo(() => {
+    // Coletar todas as skills de todos os candidatos
+    const candidateSkillIds = new Set()
+    applications.forEach((application) => {
+      const skills = application.Subscribers?.skillsId || []
+      skills.forEach((skillId) => candidateSkillIds.add(skillId))
+    })
+
+    // Filtrar o array de todas as skills para incluir apenas as que existem nos candidatos
+    return allSkills
+      .filter((skill) => candidateSkillIds.has(skill.id.toString()))
+      .sort((a, b) => a.name.localeCompare(b.name))
+  }, [applications, allSkills])
+
+  useEffect(() => {
+    const handleClickOutside = (event) => {
+      if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
+        setIsOpen(false)
+      }
+    }
+
+    document.addEventListener('mousedown', handleClickOutside)
+    return () => document.removeEventListener('mousedown', handleClickOutside)
+  }, [])
+
+  const handleSkillSelect = (skillId) => {
+    const isSelected = selectedSkills.includes(skillId)
+    let newSelectedSkills
+
+    if (isSelected) {
+      newSelectedSkills = selectedSkills.filter((id) => id !== skillId)
+    } else {
+      newSelectedSkills = [...selectedSkills, skillId]
+    }
+
+    onFilterChange(newSelectedSkills)
+  }
+
+  const getDisplayText = () => {
+    if (selectedSkills.length === 0) {
+      return 'Selecionar skills'
+    }
+    return `${selectedSkills.length} skill${
+      selectedSkills.length === 1 ? '' : 's'
+    } selecionada${selectedSkills.length === 1 ? '' : 's'}`
+  }
+
+  return (
+    <div ref={dropdownRef} className="relative">
+      <label className="mb-1.5 block text-sm font-medium text-gray-700">
+        Skills
+      </label>
+      <div className="relative">
+        <button
+          onClick={() => setIsOpen(!isOpen)}
+          className="relative h-10 w-full appearance-none rounded-lg border border-gray-300 bg-white px-3 py-2 text-left text-sm text-gray-900 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
+        >
+          <span className="block truncate">{getDisplayText()}</span>
+          <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
+            <ChevronDown className="h-4 w-4 text-gray-900" />
+          </span>
+        </button>
+
+        {isOpen && availableSkills.length > 0 && (
+          <div className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5">
+            <div className="divide-y divide-gray-100">
+              {availableSkills.map((skill) => (
+                <div
+                  key={skill.id}
+                  onClick={() => handleSkillSelect(skill.id.toString())}
+                  className={`flex cursor-pointer items-center px-4 py-2 text-sm hover:bg-gray-50 ${
+                    selectedSkills.includes(skill.id.toString())
+                      ? 'bg-indigo-50'
+                      : ''
+                  }`}
+                >
+                  <span className="mr-2">{skill.emoji}</span>
+                  <span>{skill.name}</span>
+                  {selectedSkills.includes(skill.id.toString()) && (
+                    <span className="ml-auto text-indigo-600">✓</span>
+                  )}
+                </div>
+              ))}
+            </div>
+          </div>
+        )}
+      </div>
+    </div>
+  )
+}
+
+export default SkillsFilter