File size: 4,664 Bytes
f46336a
 
 
38de65f
 
 
6decbb1
 
255baa6
 
de1321f
38de65f
6decbb1
38de65f
 
 
 
 
 
 
 
 
6decbb1
 
 
 
 
de1321f
 
 
38de65f
 
 
6decbb1
de1321f
38de65f
6decbb1
 
 
 
 
de1321f
38de65f
1685b02
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38de65f
 
 
 
255baa6
f46336a
0edd5f0
 
 
 
6decbb1
07ed8c2
7033dcf
 
1685b02
7033dcf
6decbb1
 
 
 
 
 
 
 
 
 
 
 
f46336a
 
38de65f
 
f46336a
 
 
255baa6
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import Header from "@/components/Header";
import FilterBar from "@/components/FilterBar";
import ConferenceCard from "@/components/ConferenceCard";
import conferencesData from "@/data/conferences.yml";
import { Conference } from "@/types/conference";
import { useState, useMemo, useEffect } from "react";
import { Switch } from "@/components/ui/switch"
import { parseISO, isValid, isPast } from "date-fns";

const Index = () => {
  const [selectedTags, setSelectedTags] = useState<Set<string>>(new Set());
  const [searchQuery, setSearchQuery] = useState("");
  const [showPastConferences, setShowPastConferences] = useState(false);

  const filteredConferences = useMemo(() => {
    if (!Array.isArray(conferencesData)) {
      console.error("Conferences data is not an array:", conferencesData);
      return [];
    }

    return conferencesData
      .filter((conf: Conference) => {
        // Filter by deadline (past/future)
        const deadlineDate = conf.deadline && conf.deadline !== 'TBD' ? parseISO(conf.deadline) : null;
        const isUpcoming = !deadlineDate || !isValid(deadlineDate) || !isPast(deadlineDate);
        if (!showPastConferences && !isUpcoming) return false;

        // Filter by tags and search query
        const matchesTags = selectedTags.size === 0 || 
          (Array.isArray(conf.tags) && conf.tags.some(tag => selectedTags.has(tag)));
        const matchesSearch = searchQuery === "" || 
          conf.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
          (conf.full_name && conf.full_name.toLowerCase().includes(searchQuery.toLowerCase()));
        
        return matchesTags && matchesSearch;
      })
      .sort((a: Conference, b: Conference) => {
        const dateA = a.deadline && a.deadline !== 'TBD' ? parseISO(a.deadline).getTime() : Infinity;
        const dateB = b.deadline && b.deadline !== 'TBD' ? parseISO(b.deadline).getTime() : Infinity;
        return dateA - dateB;
      });
  }, [selectedTags, searchQuery, showPastConferences]);

  // Update handleTagsChange to handle multiple tags
  const handleTagsChange = (newTags: Set<string>) => {
    setSelectedTags(newTags);
    const searchParams = new URLSearchParams(window.location.search);
    if (newTags.size > 0) {
      searchParams.set('tags', Array.from(newTags).join(','));
    } else {
      searchParams.delete('tags');
    }
    const newUrl = `${window.location.pathname}${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
    window.history.pushState({}, '', newUrl);
  };

  useEffect(() => {
    const handleUrlChange = (event: CustomEvent) => {
      const { tag } = event.detail;
      // Create new Set with existing tags plus the new one
      const newTags = new Set(selectedTags);
      if (newTags.has(tag)) {
        newTags.delete(tag);
      } else {
        newTags.add(tag);
      }
      handleTagsChange(newTags);
    };

    window.addEventListener('urlchange', handleUrlChange as EventListener);
    
    // Check URL params on mount
    const params = new URLSearchParams(window.location.search);
    const tagsParam = params.get('tags');
    if (tagsParam) {
      setSelectedTags(new Set(tagsParam.split(',')));
    }

    return () => {
      window.removeEventListener('urlchange', handleUrlChange as EventListener);
    };
  }, [selectedTags]); // Add selectedTags as dependency

  if (!Array.isArray(conferencesData)) {
    return <div>Loading conferences...</div>;
  }

  return (
    <div className="min-h-screen bg-neutral-light">
      <Header 
        onSearch={setSearchQuery} 
        showEmptyMessage={selectedTags.size > 0 && filteredConferences.length === 0 && !showPastConferences}
      />
      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <div className="space-y-4 py-4">
          <FilterBar 
            selectedTags={selectedTags}
            onTagSelect={handleTagsChange}
          />
          <div className="flex items-center gap-2">
            <label htmlFor="show-past" className="text-sm text-neutral-600">
              Show past conferences
            </label>
            <Switch
              id="show-past"
              checked={showPastConferences}
              onCheckedChange={setShowPastConferences}
            />
          </div>
        </div>
      </div>
      <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
          {filteredConferences.map((conference: Conference) => (
            <ConferenceCard key={conference.id} {...conference} />
          ))}
        </div>
      </main>
    </div>
  );
};

export default Index;