Skip to content

Commit

Permalink
feat: register members
Browse files Browse the repository at this point in the history
  • Loading branch information
sverben committed May 25, 2024
1 parent 065e599 commit 857ea8f
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 49 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"react-native-maps": "1.15.3",
"react-native-pager-view": "6.3.1",
"react-native-paper": "^5.12.3",
"react-native-paper-select": "^1.1.1",
"react-native-safe-area-context": "4.10.1",
"react-native-screens": "~3.31.1",
"react-native-vector-icons": "^10.0.3",
Expand Down
16 changes: 9 additions & 7 deletions src/components/register/listing.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useContext, useEffect } from "react";
import AuthContext, { Authed } from "../../auth";
import { useAtom } from "jotai";
import { getSlots, Slot, slotsAtom } from "../../stores/register";
import { getSlots, membersAtom, Slot, slotsAtom } from "../../stores/register";
import {
ActivityIndicator,
Avatar,
Expand Down Expand Up @@ -39,16 +39,18 @@ function SlotListing({ slot, index }: { slot: Slot; index: number }) {
export default function Listing() {
const authState = useContext(AuthContext);
const [slots, setSlots] = useAtom(slotsAtom);
const [_members, setMembers] = useAtom(membersAtom);

useEffect(() => {
async function fetchDays() {
setSlots(
await getSlots(
authState.authenticated === Authed.AUTHENTICATED
? await authState.token
: null,
),
const { slots, members } = await getSlots(
authState.authenticated === Authed.AUTHENTICATED
? await authState.token
: null,
);

setSlots(slots);
setMembers(members || []);
}

fetchDays().then();
Expand Down
59 changes: 59 additions & 0 deletions src/components/register/presence.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
getSlots,
Presence as PresenceType,
Slot,
slotsAtom,
} from "../../stores/register";
import { ActivityIndicator, Checkbox, Switch, Text } from "react-native-paper";
import { StyleSheet, TouchableOpacity } from "react-native";
import { useContext, useState } from "react";
import AuthContext, { Authed } from "../../auth";
import { useAtom } from "jotai";
import { Platform } from "react-native";

export default function Presence({
presence,
slot,
}: {
presence: PresenceType;
slot: Slot;
}) {
const [slots, setSlots] = useAtom(slotsAtom);
const [seen, setSeen] = useState(presence.seen);
const authState = useContext(AuthContext);

async function markSeen() {
if (authState.authenticated !== Authed.AUTHENTICATED) return;
if (!slots) return;

presence.seen = !presence.seen;
setSeen(presence.seen);
setSlots(slots);

const token = await authState.token;
await fetch(
`https://aanmelden.djoamersfoort.nl/api/v1/seen/${presence.id}/${presence.seen ? "true" : "false"}`,
{
headers: {
authorization: `Bearer ${token}`,
},
},
);
}

return (
<TouchableOpacity style={styles.presence} onPress={markSeen}>
<Switch value={seen} onChange={markSeen} />
<Text>{presence.name}</Text>
</TouchableOpacity>
);
}

const styles = StyleSheet.create({
presence: {
display: "flex",
flexDirection: "row",
alignItems: "center",
gap: 10,
},
});
7 changes: 5 additions & 2 deletions src/screens/feed/feed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
getRSSFeed,
sortFeeds,
} from "../../stores/feed";
import { getSlots, slotsAtom } from "../../stores/register";
import { getSlots, slotsAtom, membersAtom } from "../../stores/register";
import AuthContext, { Authed } from "../../auth";
import { NativeStackNavigationProp } from "react-native-screens/native-stack";
import { StackParamList } from "../../../App";
Expand All @@ -25,6 +25,7 @@ export default function FeedScreen() {
const authState = useContext(AuthContext);
const [_feed, setFeed] = useAtom(feedAtom);
const [_slots, setSlots] = useAtom(slotsAtom);
const [_members, setMembers] = useAtom(membersAtom);

async function refresh() {
setRefreshing(true);
Expand All @@ -35,8 +36,10 @@ export default function FeedScreen() {
await Promise.all([
new Promise<void>(async (resolve) => {
setSlots(null);
const slots = await getSlots(token);
const { slots, members } = await getSlots(token);
setSlots(slots);
setMembers(members || []);

resolve();
}),
new Promise<void>(async (resolve) => {
Expand Down
157 changes: 120 additions & 37 deletions src/screens/feed/slot.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
import { StackScreenProps } from "@react-navigation/stack";
import { Alert, StyleSheet, View } from "react-native";
import { Button, Card, Chip, Text } from "react-native-paper";
import {
Alert,
ScrollView,
StyleSheet,
TouchableOpacity,
View,
} from "react-native";
import {
Button,
Card,
Checkbox,
Chip,
Text,
useTheme,
} from "react-native-paper";
import { useContext, useEffect, useState } from "react";
import { getSlots, Slot, slotsAtom } from "../../stores/register";
import { getSlots, membersAtom, Slot, slotsAtom } from "../../stores/register";
import { useAtom } from "jotai";
import { StackParamList } from "../../../App";
import AuthContext, { Authed } from "../../auth";
import Presence from "../../components/register/presence";
import { PaperSelect } from "react-native-paper-select";

type Props = StackScreenProps<StackParamList, "Slot">;

Expand All @@ -15,9 +30,27 @@ export default function SlotScreen({ route, navigation }: Props) {

const [slot, setSlot] = useState<Slot>(slots[route.params.slot]);
const [loading, setLoading] = useState(false);
const [members] = useAtom(membersAtom);
const theme = useTheme();

const authState = useContext(AuthContext);

async function registerManual(user: string) {
if (authState.authenticated !== Authed.AUTHENTICATED) return;

const token = await authState.token;
await fetch(
`https://aanmelden.djoamersfoort.nl/api/v1/register_manual/${slot.name}/${slot.pod}/${user}`,
{
headers: {
authorization: `Bearer ${token}`,
},
},
);

setSlots((await getSlots(token)).slots);
}

async function register() {
setLoading(true);
const token =
Expand Down Expand Up @@ -49,7 +82,7 @@ export default function SlotScreen({ route, navigation }: Props) {
slot.is_registered = !slot.is_registered;
}

setSlots(await getSlots(token));
setSlots((await getSlots(token)).slots);
setLoading(false);
}

Expand All @@ -61,39 +94,83 @@ export default function SlotScreen({ route, navigation }: Props) {

return (
<View style={styles.slot}>
<View style={styles.info}>
{slot.announcement && (
<>
<Text variant={"titleMedium"}>Aankondiging</Text>
<Card>
<Card.Content>
<Text variant={"titleSmall"}>{slot.announcement}</Text>
</Card.Content>
</Card>
</>
)}
<Text variant={"titleMedium"}>Beschikbaarheid</Text>
<Card>
<Card.Content>
<Text variant={"titleSmall"}>
Er zijn {slot.available}/{slot.available + slot.taken} plekken
beschikbaar.
</Text>
</Card.Content>
</Card>

<Text variant={"titleMedium"}>Begeleiders</Text>
<Card>
<Card.Content style={styles.chips}>
{slot.tutors.length === 0 && (
<Text>Er zijn nog geen begeleiders aangemeld</Text>
)}
{slot.tutors.map((tutor) => (
<Chip key={tutor}>{tutor}</Chip>
))}
</Card.Content>
</Card>
</View>
<ScrollView>
<View style={styles.info}>
{slot.announcement && (
<>
<Text variant={"titleMedium"}>Aankondiging</Text>
<Card>
<Card.Content>
<Text variant={"titleSmall"}>{slot.announcement}</Text>
</Card.Content>
</Card>
</>
)}
<Text variant={"titleMedium"}>Beschikbaarheid</Text>
<Card>
<Card.Content>
<Text variant={"titleSmall"}>
Er zijn {slot.available}/{slot.available + slot.taken} plekken
beschikbaar.
</Text>
</Card.Content>
</Card>

<Text variant={"titleMedium"}>Begeleiders</Text>
<Card>
<Card.Content style={styles.chips}>
{slot.tutors.length === 0 && (
<Text>Er zijn nog geen begeleiders aangemeld</Text>
)}
{slot.tutors.map((tutor) => (
<Chip key={tutor}>{tutor}</Chip>
))}
</Card.Content>
</Card>

{slot.presence && (
<>
<Text variant={"titleMedium"}>Leden</Text>
<Card>
<Card.Content>
<PaperSelect
label={"Lid handmatig aanmelden"}
arrayList={members.sort((a, b) => a.name < b.name ? -1 : 1) .map(({ id, name }) => ({
_id: id.toString(),
value: name,
}))}
selectedArrayList={[]}
multiEnable={false}
value={""}
onSelection={async (selection) => {
if (!selection.selectedList[0]) return;
await registerManual(selection.selectedList[0]._id);
}}
theme={theme}
textInputStyle={{
backgroundColor: theme.colors.elevation.level5,
color: theme.colors.onPrimaryContainer,
}}
searchStyle={{
backgroundColor: theme.colors.elevation.level5,
}}
textColor={theme.colors.onPrimary}
/>
<View style={styles.presence}>
{slot.presence.map((presence) => (
<Presence
key={presence.id}
presence={presence}
slot={slot}
/>
))}
</View>
</Card.Content>
</Card>
</>
)}
</View>
</ScrollView>
<View>
<Button
style={styles.button}
Expand Down Expand Up @@ -125,9 +202,12 @@ const styles = StyleSheet.create({
flexDirection: "column",
justifyContent: "space-between",
padding: 10,
paddingTop: 0,
},
info: {
gap: 10,
paddingTop: 10,
paddingBottom: 10,
},
chips: {
flexDirection: "row",
Expand All @@ -137,4 +217,7 @@ const styles = StyleSheet.create({
button: {
borderRadius: 25,
},
presence: {
gap: 5,
},
});
19 changes: 16 additions & 3 deletions src/stores/register.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import { atom } from "jotai";
import { nextFriday, nextSaturday } from "date-fns";

export interface Presence {
id: number;
seen: boolean;
name: string;
}

export interface Member {
id: number;
name: string;
}

export interface Slot {
name: string;
pod: string;
description: string;
announcement: string;
tutors: string[];
date: string;
presence?: Presence[];

available: number;
taken: number;
Expand Down Expand Up @@ -42,16 +54,17 @@ export const demoSlots = [
];

export const slotsAtom = atom<Slot[] | null>([]);
export const membersAtom = atom<Member[]>([]);
export async function getSlots(token: string | null) {
if (!token) {
await new Promise((resolve) =>
setTimeout(resolve, Math.random() * 500 + 250),
);

return demoSlots;
return { slots: demoSlots };
}

const { slots }: { slots: Slot[] } = await fetch(
const { slots, members }: { slots: Slot[]; members?: Member[] } = await fetch(
"https://aanmelden.djoamersfoort.nl/api/v1/slots",
{
headers: {
Expand All @@ -60,5 +73,5 @@ export async function getSlots(token: string | null) {
},
).then((res) => res.json());

return slots;
return { slots, members };
}
Loading

0 comments on commit 857ea8f

Please sign in to comment.