| import React, { useState } from "react"; | |
| import { PolicyData } from "../types"; | |
| interface AIPoliciesTableProps { | |
| policies: PolicyData[]; | |
| } | |
| const AIPoliciesTable: React.FC<AIPoliciesTableProps> = ({ policies }) => { | |
| const initialOpenYears = policies.reduce((acc, policy) => { | |
| const year = new Date(policy.releaseDate).getFullYear(); | |
| acc[year] = true; | |
| return acc; | |
| }, {} as { [key: number]: boolean }); | |
| const [openYears, setOpenYears] = useState<{ [key: number]: boolean }>(initialOpenYears); | |
| const toggleYear = (year: number) => { | |
| setOpenYears((prev) => ({ ...prev, [year]: !prev[year] })); | |
| }; | |
| const uniqueTags = Array.from(new Set(policies.flatMap(policy => policy.tags.map(tag => tag.toLowerCase())))).map(tag => tag.charAt(0).toUpperCase() + tag.slice(1)); | |
| const [selectedTags, setSelectedTags] = useState<string[]>(uniqueTags); | |
| const handleTagChange = (tag: string) => { | |
| if (selectedTags.includes(tag)) { | |
| setSelectedTags(selectedTags.filter(t => t !== tag)); | |
| } else { | |
| setSelectedTags([...selectedTags, tag]); | |
| } | |
| }; | |
| const filteredPolicies = selectedTags.length === 0 | |
| ? policies | |
| : policies.filter(policy => policy.tags.some(tag => selectedTags.includes(tag.charAt(0).toUpperCase() + tag.slice(1)))); | |
| const groupedPolicies = filteredPolicies.reduce((acc, policy) => { | |
| const year = new Date(policy.releaseDate).getFullYear(); | |
| if (!acc[year]) { | |
| acc[year] = []; | |
| } | |
| acc[year].push(policy); | |
| return acc; | |
| }, {} as { [key: number]: PolicyData[] }); | |
| const sortedYears = Object.keys(groupedPolicies) | |
| .map(Number) | |
| .sort((a, b) => b - a); | |
| const formatDate = (dateString: string) => { | |
| const date = new Date(dateString); | |
| const options: Intl.DateTimeFormatOptions = { month: 'short', day: 'numeric', year: 'numeric' }; | |
| return date.toLocaleDateString('en-US', options); | |
| }; | |
| return ( | |
| <div className="container mx-auto p-6"> | |
| <div className="mb-6 flex justify-center"> | |
| {uniqueTags.map(tag => ( | |
| <label key={tag} className="inline-flex items-center mr-4"> | |
| <input | |
| type="checkbox" | |
| className="form-checkbox h-5 w-5 text-blue-600" | |
| checked={selectedTags.includes(tag)} | |
| onChange={() => handleTagChange(tag)} | |
| /> | |
| <span className="ml-2 text-gray-700 dark:text-gray-400">{tag}</span> | |
| </label> | |
| ))} | |
| </div> | |
| {sortedYears.map((year) => ( | |
| <div key={year} className="mb-12"> | |
| <button | |
| onClick={() => toggleYear(year)} | |
| className="text-2xl font-semibold mb-6 focus:outline-none text-gray-800 dark:text-white" | |
| > | |
| {year} {openYears[year] ? "▲" : "▼"} | |
| </button> | |
| {openYears[year] && ( | |
| <table className="w-full border-collapse table-auto shadow-lg rounded-lg"> | |
| <tbody> | |
| {groupedPolicies[year].map((policy, index) => ( | |
| <tr | |
| key={`${year}-${index}`} | |
| className={`${ | |
| index % 2 === 0 ? "bg-gray-300" : "bg-gray-500" | |
| } dark:${ | |
| index % 2 === 0 ? "bg-gray-700" : "bg-gray-900" | |
| } border-b border-gray-200 dark:border-gray-700`} | |
| > | |
| <td className="py-6 px-6 text-gray-900 dark:text-white"> | |
| <div className="text-lg font-medium">{policy.zh}</div> | |
| <div className="text-sm text-gray-500 dark:text-gray-400 mt-2"> | |
| {policy.en} | |
| </div> | |
| <div className="text-xs text-gray-400 dark:text-gray-500 mt-2"> | |
| {formatDate(policy.releaseDate)} | |
| </div> | |
| <div className="text-xs text-gray-400 dark:text-gray-500 mt-2"> | |
| {policy.tags.map(tag => tag.charAt(0).toUpperCase() + tag.slice(1)).join(', ')} | |
| </div> | |
| </td> | |
| <td className="py-6 px-6 text-right"> | |
| {policy.link.zh && ( | |
| <a | |
| href={policy.link.zh} | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="text-blue-500 hover:underline dark:text-blue-400 mr-4" | |
| > | |
| 中文 | |
| </a> | |
| )} | |
| {policy.link.en && ( | |
| <a | |
| href={policy.link.en} | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="text-blue-500 hover:underline dark:text-blue-400 mr-4" | |
| > | |
| English | |
| </a> | |
| )} | |
| {policy.link.fr && ( | |
| <a | |
| href={policy.link.fr} | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="text-blue-500 hover:underline dark:text-blue-400 mr-4" | |
| > | |
| Français | |
| </a> | |
| )} | |
| </td> | |
| </tr> | |
| ))} | |
| </tbody> | |
| </table> | |
| )} | |
| </div> | |
| ))} | |
| </div> | |
| ); | |
| }; | |
| export default AIPoliciesTable; | |