mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-07-14 18:15:56 -04:00
feat: redesign dashboard stats cards with sparklines, icons, and period selection
- Replace horizontal scrolling stats list on native with a 2×2 grid layout; each card gets a color-coded icon, tint background, and an inline sparkline - Add `Sparkline` component to `apps/native/src/components/ui/` (path-based SVG rendered via `react-native-svg`) - Upgrade `StatsCard` (native) to support `icon`, `color`/`tintColor`/`bgColor` theming, `sparklineData`, and a `zeego` dropdown menu for switching the time period (today / this week / this month / this year) - Wire `moviePeriod` / `episodePeriod` state in the home screen and fetch `dashboard.watchHistory` for each so counts and sparklines react to the selected period - Rewrite `apps/web/src/components/dashboard/sparkline.tsx` as a self-contained SVG sparkline (no Recharts), and update `stats-display.tsx` accordingly - Remove `apps/web/src/components/ui/chart.tsx` (Recharts wrapper) and drop the `recharts` dependency from `apps/web/package.json`
This commit is contained in:
@@ -385,24 +385,6 @@ export default function SettingsScreen() {
|
|||||||
icon={IconLanguage}
|
icon={IconLanguage}
|
||||||
onPress={() => setLanguageModalOpen(true)}
|
onPress={() => setLanguageModalOpen(true)}
|
||||||
/>
|
/>
|
||||||
<SelectModal
|
|
||||||
open={languageModalOpen}
|
|
||||||
onOpenChange={setLanguageModalOpen}
|
|
||||||
label={t`Language`}
|
|
||||||
icon={IconLanguage}
|
|
||||||
selection={i18n.locale}
|
|
||||||
options={LOCALE_INFO.map((info) => ({
|
|
||||||
value: info.code,
|
|
||||||
label: info.nativeName,
|
|
||||||
}))}
|
|
||||||
onSelect={(locale) => {
|
|
||||||
setLanguageModalOpen(false);
|
|
||||||
activateLocale(locale as SupportedLocale).then(
|
|
||||||
() => setPersistedLocale(locale as SupportedLocale),
|
|
||||||
() => {},
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<SettingsRow
|
<SettingsRow
|
||||||
label={t`Anonymous usage reporting`}
|
label={t`Anonymous usage reporting`}
|
||||||
icon={IconChartBar}
|
icon={IconChartBar}
|
||||||
@@ -517,6 +499,26 @@ export default function SettingsScreen() {
|
|||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
|
|
||||||
|
{/* Language Modal */}
|
||||||
|
<SelectModal
|
||||||
|
open={languageModalOpen}
|
||||||
|
onOpenChange={setLanguageModalOpen}
|
||||||
|
label={t`Language`}
|
||||||
|
icon={IconLanguage}
|
||||||
|
selection={i18n.locale}
|
||||||
|
options={LOCALE_INFO.map((info) => ({
|
||||||
|
value: info.code,
|
||||||
|
label: info.nativeName,
|
||||||
|
}))}
|
||||||
|
onSelect={(locale) => {
|
||||||
|
setLanguageModalOpen(false);
|
||||||
|
activateLocale(locale as SupportedLocale).then(
|
||||||
|
() => setPersistedLocale(locale as SupportedLocale),
|
||||||
|
() => {},
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Version */}
|
{/* Version */}
|
||||||
<Animated.View entering={FadeInDown.duration(300).delay(400)} className="mt-6 items-center">
|
<Animated.View entering={FadeInDown.duration(300).delay(400)} className="mt-6 items-center">
|
||||||
<Text className="text-muted-foreground text-xs">
|
<Text className="text-muted-foreground text-xs">
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import { NativeTabBar } from "@/components/navigation/native-tab-bar";
|
|||||||
import { orpc } from "@/lib/orpc";
|
import { orpc } from "@/lib/orpc";
|
||||||
import { authClient } from "@/lib/server";
|
import { authClient } from "@/lib/server";
|
||||||
|
|
||||||
|
export const unstable_settings = {
|
||||||
|
initialRouteName: "(home)",
|
||||||
|
};
|
||||||
|
|
||||||
export default function TabLayout() {
|
export default function TabLayout() {
|
||||||
const { data: session } = authClient.useSession();
|
const { data: session } = authClient.useSession();
|
||||||
const isAdmin = session?.user?.role === "admin";
|
const isAdmin = session?.user?.role === "admin";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Icon } from "@tabler/icons-react-native";
|
import type { Icon } from "@tabler/icons-react-native";
|
||||||
import { Children, Fragment, isValidElement, type ReactNode } from "react";
|
import { Children, Fragment, isValidElement, type ReactNode } from "react";
|
||||||
import { View } from "react-native";
|
import { StyleSheet, View } from "react-native";
|
||||||
|
|
||||||
import { SectionHeader } from "@/components/ui/section-header";
|
import { SectionHeader } from "@/components/ui/section-header";
|
||||||
import { Text } from "@/components/ui/text";
|
import { Text } from "@/components/ui/text";
|
||||||
@@ -55,7 +55,7 @@ export function SettingsSection({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment key={key}>
|
<Fragment key={key}>
|
||||||
{i > 0 && <View className="border-border border-t" style={{ borderTopWidth: 0.5 }} />}
|
{i > 0 && <View className="bg-border" style={styles.separator} />}
|
||||||
{child}
|
{child}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
@@ -64,3 +64,9 @@ export function SettingsSection({
|
|||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
separator: {
|
||||||
|
height: StyleSheet.hairlineWidth,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user