Add half completed Plans
This commit is contained in:
parent
e4cc893bfc
commit
43405269df
3
App.tsx
3
App.tsx
|
@ -10,6 +10,7 @@ import {StatusBar, useColorScheme} from 'react-native';
|
|||
import {setupSchema} from './db';
|
||||
import Exercises from './Exercises';
|
||||
import Home from './Home';
|
||||
import Plans from './Plans';
|
||||
import Settings from './Settings';
|
||||
|
||||
const Tab = createMaterialTopTabNavigator<RootStackParamList>();
|
||||
|
@ -17,6 +18,7 @@ export type RootStackParamList = {
|
|||
Home: {};
|
||||
Settings: {};
|
||||
Exercises: {};
|
||||
Plans: {};
|
||||
};
|
||||
|
||||
setupSchema();
|
||||
|
@ -35,6 +37,7 @@ const App = () => {
|
|||
<StatusBar barStyle={dark ? 'light-content' : 'dark-content'} />
|
||||
<Tab.Navigator>
|
||||
<Tab.Screen name="Home" component={Home} />
|
||||
<Tab.Screen name="Plans" component={Plans} />
|
||||
<Tab.Screen name="Exercises" component={Exercises} />
|
||||
<Tab.Screen name="Settings" component={Settings} />
|
||||
</Tab.Navigator>
|
||||
|
|
63
DayMenu.tsx
Normal file
63
DayMenu.tsx
Normal file
|
@ -0,0 +1,63 @@
|
|||
import {useState} from 'react';
|
||||
import React from 'react';
|
||||
import {Button, Divider, Menu} from 'react-native-paper';
|
||||
|
||||
const days = [
|
||||
'Monday',
|
||||
'Tuesday',
|
||||
'Wednesday',
|
||||
'Thursday',
|
||||
'Friday',
|
||||
'Saturday',
|
||||
'Sunday',
|
||||
];
|
||||
|
||||
export default function DayMenu({
|
||||
onSelect,
|
||||
onDelete,
|
||||
onAdd,
|
||||
selected,
|
||||
index,
|
||||
}: {
|
||||
onSelect: (option: string) => void;
|
||||
onDelete: () => void;
|
||||
onAdd: () => void;
|
||||
selected: string;
|
||||
index: number;
|
||||
}) {
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
const add = () => {
|
||||
onAdd();
|
||||
setShow(false);
|
||||
};
|
||||
|
||||
const select = (day: string) => {
|
||||
onSelect(day);
|
||||
setShow(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu
|
||||
visible={show}
|
||||
onDismiss={() => setShow(false)}
|
||||
anchor={
|
||||
<Button icon="today" onPress={() => setShow(true)}>
|
||||
{selected}
|
||||
</Button>
|
||||
}>
|
||||
{days.map(day => (
|
||||
<Menu.Item
|
||||
icon={selected === day ? 'checkmark' : ''}
|
||||
onPress={() => select(day)}
|
||||
title={day}
|
||||
/>
|
||||
))}
|
||||
<Menu.Item icon="add" title="Add" onPress={add} />
|
||||
<Divider />
|
||||
{index > 0 && (
|
||||
<Menu.Item icon="trash" title="Delete" onPress={onDelete} />
|
||||
)}
|
||||
</Menu>
|
||||
);
|
||||
}
|
126
EditPlan.tsx
Normal file
126
EditPlan.tsx
Normal file
|
@ -0,0 +1,126 @@
|
|||
import {setDay} from 'date-fns';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {StyleSheet, Text, View} from 'react-native';
|
||||
import {Button, Modal, Portal, TextInput} from 'react-native-paper';
|
||||
import DayMenu from './DayMenu';
|
||||
import {getDb} from './db';
|
||||
import {Plan} from './plan';
|
||||
import Set from './set';
|
||||
|
||||
export default function EditPlan({
|
||||
id,
|
||||
onSave,
|
||||
show,
|
||||
setShow,
|
||||
clearId,
|
||||
}: {
|
||||
id?: number;
|
||||
clearId: () => void;
|
||||
onSave: () => void;
|
||||
show: boolean;
|
||||
setShow: (visible: boolean) => void;
|
||||
}) {
|
||||
const [days, setDays] = useState('');
|
||||
const [workouts, setWorkouts] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
getDb().then(async db => {
|
||||
const [result] = await db.executeSql(`SELECT * FROM plans WHERE id = ?`, [
|
||||
id,
|
||||
]);
|
||||
if (!result.rows.item(0)) throw new Error("Can't find specified Set.");
|
||||
const set: Plan = result.rows.item(0);
|
||||
setDays(set.days);
|
||||
setWorkouts(set.workouts);
|
||||
});
|
||||
}, [id]);
|
||||
|
||||
const save = async () => {
|
||||
if (!days || !workouts) return;
|
||||
const db = await getDb();
|
||||
if (!id)
|
||||
await db.executeSql(`INSERT INTO plans(days, workouts) VALUES (?, ?)`, [
|
||||
days,
|
||||
workouts,
|
||||
]);
|
||||
else
|
||||
await db.executeSql(
|
||||
`UPDATE plans SET days = ?, workouts = ? WHERE id = ?`,
|
||||
[days, workouts, id],
|
||||
);
|
||||
setShow(false);
|
||||
onSave();
|
||||
};
|
||||
|
||||
const selectDay = (day: string, index: number) => {
|
||||
const newDays = days.split(',');
|
||||
newDays[index] = day;
|
||||
setDays(newDays.join(','));
|
||||
};
|
||||
|
||||
const removeDay = (index: number) => {
|
||||
const newDays = days.split(',');
|
||||
delete newDays[index];
|
||||
setDays(newDays.filter(day => day).join(','));
|
||||
};
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Modal
|
||||
visible={show}
|
||||
contentContainerStyle={styles.modal}
|
||||
onDismiss={() => setShow(false)}>
|
||||
<Text style={styles.title}>{id ? `Edit "${days}"` : 'Add a plan'}</Text>
|
||||
<View style={{alignItems: 'flex-start'}}>
|
||||
{days.split(',').map((day, index) => (
|
||||
<DayMenu
|
||||
index={index}
|
||||
onDelete={() => removeDay(index)}
|
||||
onSelect={option => selectDay(option, index)}
|
||||
onAdd={() => setDays(days + ',Monday')}
|
||||
selected={day}
|
||||
key={index}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
<TextInput
|
||||
style={styles.text}
|
||||
label="Workouts *"
|
||||
value={workouts}
|
||||
onChangeText={setWorkouts}
|
||||
/>
|
||||
<View style={styles.bottom}>
|
||||
<Button mode="contained" icon="save" onPress={save}>
|
||||
Save
|
||||
</Button>
|
||||
<Button icon="close" onPress={() => setShow(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
{id && (
|
||||
<Button icon="copy" onPress={clearId}>
|
||||
Duplicate
|
||||
</Button>
|
||||
)}
|
||||
</View>
|
||||
</Modal>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
modal: {
|
||||
backgroundColor: 'black',
|
||||
padding: 20,
|
||||
},
|
||||
text: {
|
||||
marginBottom: 10,
|
||||
},
|
||||
bottom: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
title: {
|
||||
fontSize: 20,
|
||||
marginBottom: 10,
|
||||
},
|
||||
});
|
52
PlanItem.tsx
Normal file
52
PlanItem.tsx
Normal file
|
@ -0,0 +1,52 @@
|
|||
import React, {useState} from 'react';
|
||||
import {IconButton, List, Menu} from 'react-native-paper';
|
||||
import {getDb} from './db';
|
||||
import {Plan} from './plan';
|
||||
|
||||
export default function PlanItem({
|
||||
item,
|
||||
setId,
|
||||
setShowEdit,
|
||||
onRemove,
|
||||
}: {
|
||||
item: Plan;
|
||||
setId: (id: number) => void;
|
||||
setShowEdit: (show: boolean) => void;
|
||||
onRemove: () => void;
|
||||
}) {
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
const remove = async () => {
|
||||
const db = await getDb();
|
||||
await db.executeSql(`DELETE FROM plans WHERE id = ?`, [item.id]);
|
||||
setShow(false);
|
||||
onRemove();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<List.Item
|
||||
onPress={() => {
|
||||
setId(item.id);
|
||||
setShowEdit(true);
|
||||
}}
|
||||
title={item.days}
|
||||
description={item.workouts}
|
||||
onLongPress={() => setShow(true)}
|
||||
right={() => (
|
||||
<Menu
|
||||
anchor={
|
||||
<IconButton
|
||||
icon="ellipsis-vertical"
|
||||
onPress={() => setShow(true)}
|
||||
/>
|
||||
}
|
||||
visible={show}
|
||||
onDismiss={() => setShow(false)}>
|
||||
<Menu.Item icon="trash" onPress={remove} title="Delete" />
|
||||
</Menu>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
67
Plans.tsx
Normal file
67
Plans.tsx
Normal file
|
@ -0,0 +1,67 @@
|
|||
import React, {useEffect, useState} from 'react';
|
||||
import {FlatList, View} from 'react-native';
|
||||
import {AnimatedFAB, Searchbar} from 'react-native-paper';
|
||||
import {getPlans} from './db';
|
||||
import EditPlan from './EditPlan';
|
||||
import {Plan} from './plan';
|
||||
import PlanItem from './PlanItem';
|
||||
|
||||
export default function Plans() {
|
||||
const [search, setSearch] = useState('');
|
||||
const [plans, setPlans] = useState<Plan[]>([]);
|
||||
const [refreshing, setRefresing] = useState(false);
|
||||
const [id, setId] = useState<number>();
|
||||
const [showEdit, setShowEdit] = useState(false);
|
||||
|
||||
const refresh = async () => {
|
||||
const [result] = await getPlans({search});
|
||||
setPlans(result.rows.raw());
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
}, []);
|
||||
|
||||
const renderItem = ({item}: {item: Plan}) => (
|
||||
<PlanItem
|
||||
item={item}
|
||||
key={item.id}
|
||||
setShowEdit={setShowEdit}
|
||||
setId={setId}
|
||||
onRemove={refresh}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={{padding: 10}}>
|
||||
<Searchbar value={search} onChangeText={setSearch} placeholder="Search" />
|
||||
<FlatList
|
||||
style={{height: '90%'}}
|
||||
data={plans}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={set => set.id.toString()}
|
||||
refreshing={refreshing}
|
||||
onRefresh={refresh}
|
||||
/>
|
||||
|
||||
<EditPlan
|
||||
clearId={() => setId(undefined)}
|
||||
onSave={refresh}
|
||||
setShow={setShowEdit}
|
||||
show={showEdit}
|
||||
id={id}
|
||||
/>
|
||||
|
||||
<AnimatedFAB
|
||||
extended={false}
|
||||
label="Add"
|
||||
icon="add"
|
||||
style={{position: 'absolute', right: 20, bottom: 20}}
|
||||
onPress={() => {
|
||||
setId(undefined);
|
||||
setShowEdit(true);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
30
db.ts
30
db.ts
|
@ -3,7 +3,7 @@ import {enablePromise, openDatabase} from 'react-native-sqlite-storage';
|
|||
enablePromise(true);
|
||||
export const getDb = () => openDatabase({name: 'massive.db'});
|
||||
|
||||
const schema = `
|
||||
const createSets = `
|
||||
CREATE TABLE IF NOT EXISTS sets (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
|
@ -14,9 +14,30 @@ const schema = `
|
|||
);
|
||||
`;
|
||||
|
||||
export const setupSchema = () => getDb().then(db => db.executeSql(schema));
|
||||
const createPlans = `
|
||||
CREATE TABLE IF NOT EXISTS plans (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
days TEXT NOT NULL,
|
||||
workouts TEXT NOT NULL
|
||||
);
|
||||
`;
|
||||
|
||||
const select = `
|
||||
export const setupSchema = () =>
|
||||
getDb().then(db => {
|
||||
db.executeSql(createSets);
|
||||
db.executeSql(createPlans);
|
||||
});
|
||||
|
||||
const selectPlans = `
|
||||
SELECT * from plans
|
||||
WHERE days LIKE ? OR workouts LIKE ?
|
||||
`;
|
||||
export const getPlans = ({search}: {search: string}) =>
|
||||
getDb().then(db =>
|
||||
db.executeSql(selectPlans, [`%${search}%`, `%${search}%`]),
|
||||
);
|
||||
|
||||
const selectSets = `
|
||||
SELECT * from sets
|
||||
WHERE name LIKE ?
|
||||
ORDER BY created DESC
|
||||
|
@ -31,4 +52,5 @@ export const getSets = ({
|
|||
search: string;
|
||||
limit: number;
|
||||
offset: number;
|
||||
}) => getDb().then(db => db.executeSql(select, [`%${search}%`, limit, offset]));
|
||||
}) =>
|
||||
getDb().then(db => db.executeSql(selectSets, [`%${search}%`, limit, offset]));
|
||||
|
|
Loading…
Reference in New Issue
Block a user