mirror of
https://github.com/linkwarden/linkwarden.git
synced 2026-03-03 03:47:02 +00:00
347 lines
11 KiB
TypeScript
347 lines
11 KiB
TypeScript
import {
|
|
FlatList,
|
|
Text,
|
|
TouchableOpacity,
|
|
View,
|
|
ViewToken,
|
|
} from "react-native";
|
|
import React from "react";
|
|
import clsx from "clsx";
|
|
import DashboardItem from "@/components/DashboardItem";
|
|
import { rawTheme, ThemeName } from "@/lib/colors";
|
|
import {
|
|
Clock8,
|
|
ChevronRight,
|
|
Pin,
|
|
Folder,
|
|
Hash,
|
|
Link,
|
|
} from "lucide-react-native";
|
|
import { LinkIncludingShortenedCollectionAndTags } from "@linkwarden/types";
|
|
import LinkListing from "@/components/LinkListing";
|
|
import { useColorScheme } from "nativewind";
|
|
import { useRouter } from "expo-router";
|
|
|
|
// Don't use prisma client's DashboardSectionType, it'll crash in production (React Native)
|
|
type DashboardSectionType =
|
|
| "STATS"
|
|
| "RECENT_LINKS"
|
|
| "PINNED_LINKS"
|
|
| "COLLECTION";
|
|
|
|
type DashboardSectionProps = {
|
|
sectionData: { type: DashboardSectionType };
|
|
collection?: any;
|
|
links?: any[];
|
|
tagsLength: number;
|
|
numberOfLinks: number;
|
|
collectionsLength: number;
|
|
numberOfPinnedLinks: number;
|
|
dashboardData: {
|
|
isLoading: boolean;
|
|
refetch: Function;
|
|
isRefetching: boolean;
|
|
};
|
|
collectionLinks?: any[];
|
|
};
|
|
|
|
const DashboardSection: React.FC<DashboardSectionProps> = ({
|
|
sectionData,
|
|
collection,
|
|
links = [],
|
|
tagsLength,
|
|
numberOfLinks,
|
|
collectionsLength,
|
|
numberOfPinnedLinks,
|
|
dashboardData,
|
|
collectionLinks = [],
|
|
}) => {
|
|
const { colorScheme } = useColorScheme();
|
|
|
|
const router = useRouter();
|
|
|
|
switch (sectionData.type) {
|
|
case "STATS":
|
|
return (
|
|
<View className="flex-col gap-4 max-w-full px-5">
|
|
<View className="flex-row gap-4">
|
|
<DashboardItem
|
|
name={numberOfLinks === 1 ? "Link" : "Links"}
|
|
value={numberOfLinks}
|
|
icon={<Link size={23} color="white" />}
|
|
color="#9c00cc"
|
|
/>
|
|
<DashboardItem
|
|
name={collectionsLength === 1 ? "Collection" : "Collections"}
|
|
value={collectionsLength}
|
|
icon={<Folder size={23} color="white" fill="white" />}
|
|
color="#0096cc"
|
|
/>
|
|
</View>
|
|
<View className="flex-row gap-4">
|
|
<DashboardItem
|
|
name={tagsLength === 1 ? "Tag" : "Tags"}
|
|
value={tagsLength}
|
|
icon={<Hash size={23} color="white" />}
|
|
color="#00cc99"
|
|
/>
|
|
<DashboardItem
|
|
name={"Pinned Links"}
|
|
value={numberOfPinnedLinks}
|
|
icon={<Pin size={23} color="white" fill="white" />}
|
|
color="#cc6d00"
|
|
/>
|
|
</View>
|
|
</View>
|
|
);
|
|
|
|
case "RECENT_LINKS":
|
|
return (
|
|
<>
|
|
<View className="flex-row justify-between items-center px-5">
|
|
<View className="flex-row gap-2 items-center">
|
|
<View className={"flex-row items-center gap-2"}>
|
|
<Clock8
|
|
size={30}
|
|
color={rawTheme[colorScheme as ThemeName].primary}
|
|
/>
|
|
<Text className="text-2xl capitalize text-base-content">
|
|
Recent Links
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
<TouchableOpacity
|
|
className="flex-row items-center text-sm gap-1"
|
|
onPress={() => router.navigate("/(tabs)/dashboard/recent-links")}
|
|
>
|
|
<Text className="text-primary">View All</Text>
|
|
<ChevronRight
|
|
size={15}
|
|
color={rawTheme[colorScheme as ThemeName].primary}
|
|
/>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{dashboardData.isLoading ||
|
|
(links.length > 0 && !dashboardData.isLoading) ? (
|
|
<FlatList
|
|
horizontal
|
|
showsHorizontalScrollIndicator={false}
|
|
directionalLockEnabled
|
|
data={links || []}
|
|
refreshing={dashboardData.isLoading}
|
|
initialNumToRender={2}
|
|
keyExtractor={(item) => item.id?.toString() || ""}
|
|
renderItem={({ item }) => (
|
|
<RenderItem item={item} key={item.id?.toString()} />
|
|
)}
|
|
ItemSeparatorComponent={() => <View style={{ width: 10 }} />}
|
|
contentContainerStyle={{
|
|
paddingHorizontal: 20,
|
|
}}
|
|
onViewableItemsChanged={({
|
|
viewableItems,
|
|
}: {
|
|
viewableItems: ViewToken[];
|
|
}) => {
|
|
const links = viewableItems.map(
|
|
(e) => e.item
|
|
) as LinkIncludingShortenedCollectionAndTags[];
|
|
|
|
if (
|
|
!dashboardData.isRefetching &&
|
|
links.some((e) => e.id && !e.preview)
|
|
) {
|
|
dashboardData.refetch();
|
|
}
|
|
}}
|
|
viewabilityConfig={{ itemVisiblePercentThreshold: 50 }}
|
|
/>
|
|
) : (
|
|
<View className="flex-col gap-2 justify-center items-center h-40 p-10 rounded-xl bg-base-200 mx-5">
|
|
<Clock8
|
|
size={40}
|
|
color={rawTheme[colorScheme as ThemeName].primary}
|
|
/>
|
|
<Text className="text-center text-xl text-neutral">
|
|
No Recent Links
|
|
</Text>
|
|
|
|
{/* <View className="text-center w-full mt-4 flex-row flex-wrap gap-4 justify-center">
|
|
<Button onPress={() => setNewLinkModal(true)} variant="accent">
|
|
<Icon name="bi-plus-lg" className="text-xl" />
|
|
<Text>{t("add_link")}</Text>
|
|
</Button>
|
|
<ImportDropdown />
|
|
</View> */}
|
|
</View>
|
|
)}
|
|
</>
|
|
);
|
|
|
|
case "PINNED_LINKS":
|
|
return (
|
|
<>
|
|
<View className="flex-row justify-between items-center px-5">
|
|
<View className="flex-row gap-2 items-center">
|
|
<View className={"flex-row items-center gap-2"}>
|
|
<Pin
|
|
size={30}
|
|
color={rawTheme[colorScheme as ThemeName].primary}
|
|
/>
|
|
<Text className="text-2xl capitalize text-base-content">
|
|
Pinned Links
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
<TouchableOpacity
|
|
className="flex-row items-center text-sm gap-1"
|
|
onPress={() => router.navigate("/(tabs)/dashboard/pinned-links")}
|
|
>
|
|
<Text className="text-primary">View All</Text>
|
|
<ChevronRight
|
|
size={15}
|
|
color={rawTheme[colorScheme as ThemeName].primary}
|
|
/>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{dashboardData.isLoading ||
|
|
links?.some((e: any) => e.pinnedBy && e.pinnedBy[0]) ? (
|
|
<FlatList
|
|
horizontal
|
|
showsHorizontalScrollIndicator={false}
|
|
data={links.filter((e: any) => e.pinnedBy && e.pinnedBy[0]) || []}
|
|
refreshing={dashboardData.isLoading}
|
|
initialNumToRender={2}
|
|
keyExtractor={(item) => item.id?.toString() || ""}
|
|
renderItem={({ item }) => (
|
|
<RenderItem item={item} key={item.id?.toString()} />
|
|
)}
|
|
ItemSeparatorComponent={() => <View style={{ width: 10 }} />}
|
|
contentContainerStyle={{
|
|
paddingHorizontal: 20,
|
|
}}
|
|
onViewableItemsChanged={({
|
|
viewableItems,
|
|
}: {
|
|
viewableItems: ViewToken[];
|
|
}) => {
|
|
const links = viewableItems.map(
|
|
(e) => e.item
|
|
) as LinkIncludingShortenedCollectionAndTags[];
|
|
|
|
if (
|
|
!dashboardData.isRefetching &&
|
|
links.some((e) => e.id && !e.preview)
|
|
) {
|
|
dashboardData.refetch();
|
|
}
|
|
}}
|
|
viewabilityConfig={{ itemVisiblePercentThreshold: 50 }}
|
|
/>
|
|
) : (
|
|
<View className="flex-col gap-2 justify-center items-center h-40 p-10 rounded-xl bg-base-200 mx-5">
|
|
<Pin
|
|
size={40}
|
|
color={rawTheme[colorScheme as ThemeName].primary}
|
|
/>
|
|
<Text className="text-center text-xl text-neutral">
|
|
No Pinned Links
|
|
</Text>
|
|
</View>
|
|
)}
|
|
</>
|
|
);
|
|
|
|
case "COLLECTION":
|
|
return collection?.id ? (
|
|
<>
|
|
<View className="flex-row justify-between items-center px-5">
|
|
<View className="flex-row gap-2 items-center max-w-[60%]">
|
|
<View className={clsx("flex-row items-center gap-2")}>
|
|
<Folder
|
|
size={30}
|
|
fill={collection.color || "#0ea5e9"}
|
|
color={collection.color || "#0ea5e9"}
|
|
/>
|
|
<Text
|
|
className="text-2xl capitalize w-full text-base-content"
|
|
numberOfLines={1}
|
|
>
|
|
{collection.name}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
<TouchableOpacity
|
|
className="flex-row items-center text-sm gap-1 whitespace-nowrap"
|
|
onPress={() =>
|
|
router.navigate(
|
|
`/(tabs)/dashboard/collection?collectionId=${collection.id}`
|
|
)
|
|
}
|
|
>
|
|
<Text className="text-primary">View All</Text>
|
|
<ChevronRight
|
|
size={15}
|
|
color={rawTheme[colorScheme as ThemeName].primary}
|
|
/>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{dashboardData.isLoading || collectionLinks.length > 0 ? (
|
|
<FlatList
|
|
horizontal
|
|
showsHorizontalScrollIndicator={false}
|
|
data={collectionLinks || []}
|
|
refreshing={dashboardData.isLoading}
|
|
initialNumToRender={2}
|
|
keyExtractor={(item) => item.id?.toString() || ""}
|
|
renderItem={({ item }) => (
|
|
<RenderItem item={item} key={item.id?.toString()} />
|
|
)}
|
|
ItemSeparatorComponent={() => <View style={{ width: 10 }} />}
|
|
contentContainerStyle={{
|
|
paddingHorizontal: 20,
|
|
}}
|
|
onViewableItemsChanged={({
|
|
viewableItems,
|
|
}: {
|
|
viewableItems: ViewToken[];
|
|
}) => {
|
|
const links = viewableItems.map(
|
|
(e) => e.item
|
|
) as LinkIncludingShortenedCollectionAndTags[];
|
|
|
|
if (
|
|
!dashboardData.isRefetching &&
|
|
links.some((e) => e.id && !e.preview)
|
|
) {
|
|
dashboardData.refetch();
|
|
}
|
|
}}
|
|
viewabilityConfig={{ itemVisiblePercentThreshold: 50 }}
|
|
/>
|
|
) : (
|
|
<View className="flex-col gap-2 justify-center items-center h-40 p-10 rounded-xl bg-base-200 mx-5">
|
|
<Text className="text-center text-xl text-neutral">
|
|
Empty Collection
|
|
</Text>
|
|
</View>
|
|
)}
|
|
</>
|
|
) : null;
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
};
|
|
|
|
export default DashboardSection;
|
|
|
|
const RenderItem = React.memo(
|
|
({ item }: { item: LinkIncludingShortenedCollectionAndTags }) => {
|
|
return <LinkListing link={item} dashboard />;
|
|
}
|
|
);
|