cgeorgiaw HF Staff commited on
Commit
0b13486
·
1 Parent(s): 5d7ae51

adding filtering by recency

Browse files
src/components/SortToggle.tsx ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from "react";
2
+
3
+ export type SortMethod = "activity" | "recent";
4
+
5
+ interface SortToggleProps {
6
+ sortMethod: SortMethod;
7
+ onToggle: (method: SortMethod) => void;
8
+ }
9
+
10
+ export default function SortToggle({ sortMethod, onToggle }: SortToggleProps) {
11
+ return (
12
+ <div className="flex items-center justify-center gap-2 mb-8">
13
+ <span className="text-sm text-muted-foreground">Sort by:</span>
14
+ <div className="inline-flex rounded-lg border border-border bg-muted p-1">
15
+ <button
16
+ onClick={() => onToggle("activity")}
17
+ className={`px-4 py-2 text-sm font-medium rounded-md transition-all duration-200 ${
18
+ sortMethod === "activity"
19
+ ? "bg-background text-foreground shadow-sm"
20
+ : "text-muted-foreground hover:text-foreground"
21
+ }`}
22
+ >
23
+ Most Active
24
+ </button>
25
+ <button
26
+ onClick={() => onToggle("recent")}
27
+ className={`px-4 py-2 text-sm font-medium rounded-md transition-all duration-200 ${
28
+ sortMethod === "recent"
29
+ ? "bg-background text-foreground shadow-sm"
30
+ : "text-muted-foreground hover:text-foreground"
31
+ }`}
32
+ >
33
+ Most Recent
34
+ </button>
35
+ </div>
36
+ </div>
37
+ );
38
+ }
src/pages/index.tsx CHANGED
@@ -4,7 +4,8 @@ import OrganizationButton from "../components/OrganizationButton";
4
  import HeatmapGrid from "../components/HeatmapGrid";
5
  import Navbar from "../components/Navbar";
6
  import TagSelector from "../components/TagSelector";
7
- import { getProviders } from "../utils/ranking";
 
8
  import { ORGANIZATIONS, SCIENTIFIC_TAGS } from "../constants/organizations";
9
 
10
  interface PageProps {
@@ -19,6 +20,7 @@ function Page({
19
  const [isLoading, setIsLoading] = useState(true);
20
  const [selectedTags, setSelectedTags] = useState<string[]>([]);
21
  const [showAllOrgs, setShowAllOrgs] = useState(false);
 
22
 
23
  useEffect(() => {
24
  if (calendarData && Object.keys(calendarData).length > 0) {
@@ -31,13 +33,23 @@ function Page({
31
  if (selectedTags.length === 0) {
32
  return providers;
33
  }
34
-
35
  return providers.filter(provider => {
36
  if (!provider.tags) return false;
37
  return selectedTags.some(tag => provider.tags!.includes(tag));
38
  });
39
  }, [providers, selectedTags]);
40
 
 
 
 
 
 
 
 
 
 
 
41
  const handleTagToggle = (tagId: string) => {
42
  setSelectedTags(prev => {
43
  if (prev.includes(tagId)) {
@@ -78,6 +90,12 @@ function Page({
78
  />
79
  </div>
80
 
 
 
 
 
 
 
81
  <div className="mb-16 mx-auto max-w-full">
82
  {/* Organization Buttons with Horizontal Scroll */}
83
  <div className="relative px-4">
@@ -90,7 +108,7 @@ function Page({
90
  }}
91
  >
92
  <div className="flex gap-6 py-2 w-max mx-auto">
93
- {(showAllOrgs ? filteredProviders : filteredProviders.slice(0, 10)).map((provider, index) => (
94
  <OrganizationButton
95
  key={provider.fullName || provider.authors[0]}
96
  provider={provider}
@@ -105,22 +123,22 @@ function Page({
105
  <div className="absolute left-0 top-0 bottom-4 w-8 bg-gradient-to-r from-background via-background/80 to-transparent pointer-events-none" />
106
  <div className="absolute right-0 top-0 bottom-4 w-8 bg-gradient-to-l from-background via-background/80 to-transparent pointer-events-none" />
107
  </div>
108
-
109
  {/* Show More/Less Button */}
110
- {filteredProviders.length > 10 && (
111
  <div className="flex justify-center mt-4">
112
  <button
113
  onClick={() => setShowAllOrgs(!showAllOrgs)}
114
  className="px-6 py-2 text-sm font-medium text-foreground bg-muted hover:bg-muted/80 rounded-full transition-colors duration-200 border border-border"
115
  >
116
- {showAllOrgs ? `Show Less (Top 10)` : `Show All ${filteredProviders.length} Organizations`}
117
  </button>
118
  </div>
119
  )}
120
  </div>
121
-
122
- <HeatmapGrid
123
- sortedProviders={showAllOrgs ? filteredProviders : filteredProviders.slice(0, 10)}
124
  calendarData={calendarData}
125
  isLoading={isLoading}
126
  />
 
4
  import HeatmapGrid from "../components/HeatmapGrid";
5
  import Navbar from "../components/Navbar";
6
  import TagSelector from "../components/TagSelector";
7
+ import SortToggle, { SortMethod } from "../components/SortToggle";
8
+ import { getProviders, sortProvidersByActivity, sortProvidersByRecentRelease } from "../utils/ranking";
9
  import { ORGANIZATIONS, SCIENTIFIC_TAGS } from "../constants/organizations";
10
 
11
  interface PageProps {
 
20
  const [isLoading, setIsLoading] = useState(true);
21
  const [selectedTags, setSelectedTags] = useState<string[]>([]);
22
  const [showAllOrgs, setShowAllOrgs] = useState(false);
23
+ const [sortMethod, setSortMethod] = useState<SortMethod>("activity");
24
 
25
  useEffect(() => {
26
  if (calendarData && Object.keys(calendarData).length > 0) {
 
33
  if (selectedTags.length === 0) {
34
  return providers;
35
  }
36
+
37
  return providers.filter(provider => {
38
  if (!provider.tags) return false;
39
  return selectedTags.some(tag => provider.tags!.includes(tag));
40
  });
41
  }, [providers, selectedTags]);
42
 
43
+ // Sort providers based on selected sort method
44
+ const sortedProviders = useMemo(() => {
45
+ const providersCopy = [...filteredProviders];
46
+ if (sortMethod === "activity") {
47
+ return sortProvidersByActivity(providersCopy, calendarData);
48
+ } else {
49
+ return sortProvidersByRecentRelease(providersCopy, calendarData);
50
+ }
51
+ }, [filteredProviders, sortMethod, calendarData]);
52
+
53
  const handleTagToggle = (tagId: string) => {
54
  setSelectedTags(prev => {
55
  if (prev.includes(tagId)) {
 
90
  />
91
  </div>
92
 
93
+ {/* Sort Toggle */}
94
+ <SortToggle
95
+ sortMethod={sortMethod}
96
+ onToggle={setSortMethod}
97
+ />
98
+
99
  <div className="mb-16 mx-auto max-w-full">
100
  {/* Organization Buttons with Horizontal Scroll */}
101
  <div className="relative px-4">
 
108
  }}
109
  >
110
  <div className="flex gap-6 py-2 w-max mx-auto">
111
+ {(showAllOrgs ? sortedProviders : sortedProviders.slice(0, 10)).map((provider, index) => (
112
  <OrganizationButton
113
  key={provider.fullName || provider.authors[0]}
114
  provider={provider}
 
123
  <div className="absolute left-0 top-0 bottom-4 w-8 bg-gradient-to-r from-background via-background/80 to-transparent pointer-events-none" />
124
  <div className="absolute right-0 top-0 bottom-4 w-8 bg-gradient-to-l from-background via-background/80 to-transparent pointer-events-none" />
125
  </div>
126
+
127
  {/* Show More/Less Button */}
128
+ {sortedProviders.length > 10 && (
129
  <div className="flex justify-center mt-4">
130
  <button
131
  onClick={() => setShowAllOrgs(!showAllOrgs)}
132
  className="px-6 py-2 text-sm font-medium text-foreground bg-muted hover:bg-muted/80 rounded-full transition-colors duration-200 border border-border"
133
  >
134
+ {showAllOrgs ? `Show Less (Top 10)` : `Show All ${sortedProviders.length} Organizations`}
135
  </button>
136
  </div>
137
  )}
138
  </div>
139
+
140
+ <HeatmapGrid
141
+ sortedProviders={showAllOrgs ? sortedProviders : sortedProviders.slice(0, 10)}
142
  calendarData={calendarData}
143
  isLoading={isLoading}
144
  />
src/types/heatmap.ts CHANGED
@@ -11,6 +11,7 @@ export interface ProviderInfo {
11
  numDatasets?: number;
12
  numFollowers?: number;
13
  numUsers?: number;
 
14
  authorsData?: {
15
  author: string;
16
  fullName: string;
 
11
  numDatasets?: number;
12
  numFollowers?: number;
13
  numUsers?: number;
14
+ mostRecentRelease?: string | null;
15
  authorsData?: {
16
  author: string;
17
  fullName: string;
src/utils/ranking.ts CHANGED
@@ -37,34 +37,78 @@ export const extractUniqueAuthors = (providers: ProviderInfo[]): string[] => {
37
  };
38
 
39
  export const sortProvidersByActivity = (
40
- providers: ProviderInfo[],
41
  calendarData: CalendarData
42
  ): ProviderInfo[] => {
43
  return providers.sort((providerA, providerB) => {
44
  const providerAData = calendarData[providerA.authors[0]] || [];
45
  const providerBData = calendarData[providerB.authors[0]] || [];
46
-
47
  const providerAActivityCount = providerAData.reduce(
48
- (totalCount, day) => totalCount + day.count,
49
  0
50
  );
51
  const providerBActivityCount = providerBData.reduce(
52
- (totalCount, day) => totalCount + day.count,
53
  0
54
  );
55
-
56
  return providerBActivityCount - providerAActivityCount;
57
  });
58
  };
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  export const getProviders = async (providers: ProviderInfo[]) => {
61
  const uniqueAuthors = extractUniqueAuthors(providers);
62
-
63
  const authorModelsData: ModelData[] = await fetchAllAuthorsData(uniqueAuthors);
64
  const providersWithMetadata = await fetchAllProvidersData(providers);
65
 
66
  const calendarData = generateCalendarData(authorModelsData, providersWithMetadata);
67
- const sortedProvidersByActivity = sortProvidersByActivity(providersWithMetadata, calendarData);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
  return {
70
  calendarData,
 
37
  };
38
 
39
  export const sortProvidersByActivity = (
40
+ providers: ProviderInfo[],
41
  calendarData: CalendarData
42
  ): ProviderInfo[] => {
43
  return providers.sort((providerA, providerB) => {
44
  const providerAData = calendarData[providerA.authors[0]] || [];
45
  const providerBData = calendarData[providerB.authors[0]] || [];
46
+
47
  const providerAActivityCount = providerAData.reduce(
48
+ (totalCount, day) => totalCount + day.count,
49
  0
50
  );
51
  const providerBActivityCount = providerBData.reduce(
52
+ (totalCount, day) => totalCount + day.count,
53
  0
54
  );
55
+
56
  return providerBActivityCount - providerAActivityCount;
57
  });
58
  };
59
 
60
+ export const sortProvidersByRecentRelease = (
61
+ providers: ProviderInfo[],
62
+ calendarData: CalendarData
63
+ ): ProviderInfo[] => {
64
+ return providers.sort((providerA, providerB) => {
65
+ const providerAData = calendarData[providerA.authors[0]] || [];
66
+ const providerBData = calendarData[providerB.authors[0]] || [];
67
+
68
+ const getLatestDate = (data: typeof providerAData) => {
69
+ if (data.length === 0) return null;
70
+ const datesWithActivity = data.filter(day => day.count > 0);
71
+ if (datesWithActivity.length === 0) return null;
72
+ return datesWithActivity.reduce((latest, day) => {
73
+ return new Date(day.date) > new Date(latest.date) ? day : latest;
74
+ }).date;
75
+ };
76
+
77
+ const latestA = getLatestDate(providerAData);
78
+ const latestB = getLatestDate(providerBData);
79
+
80
+ if (!latestA && !latestB) return 0;
81
+ if (!latestA) return 1;
82
+ if (!latestB) return -1;
83
+
84
+ return new Date(latestB).getTime() - new Date(latestA).getTime();
85
+ });
86
+ };
87
+
88
  export const getProviders = async (providers: ProviderInfo[]) => {
89
  const uniqueAuthors = extractUniqueAuthors(providers);
90
+
91
  const authorModelsData: ModelData[] = await fetchAllAuthorsData(uniqueAuthors);
92
  const providersWithMetadata = await fetchAllProvidersData(providers);
93
 
94
  const calendarData = generateCalendarData(authorModelsData, providersWithMetadata);
95
+
96
+ // Calculate and store most recent release date for each provider
97
+ const providersWithReleaseDate = providersWithMetadata.map(provider => {
98
+ const providerData = calendarData[provider.authors[0]] || [];
99
+ const datesWithActivity = providerData.filter(day => day.count > 0);
100
+
101
+ if (datesWithActivity.length > 0) {
102
+ const latestActivity = datesWithActivity.reduce((latest, day) => {
103
+ return new Date(day.date) > new Date(latest.date) ? day : latest;
104
+ });
105
+ return { ...provider, mostRecentRelease: latestActivity.date };
106
+ }
107
+
108
+ return { ...provider, mostRecentRelease: null };
109
+ });
110
+
111
+ const sortedProvidersByActivity = sortProvidersByActivity(providersWithReleaseDate, calendarData);
112
 
113
  return {
114
  calendarData,