added all fields to group in collapsable

This commit is contained in:
Akito123321 2024-06-18 02:10:22 +02:00
parent a6045a8ba6
commit 34f38e90d5
16 changed files with 378 additions and 60 deletions

9
package-lock.json generated
View File

@ -10,6 +10,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@solidjs/router": "^0.13.5", "@solidjs/router": "^0.13.5",
"solid-collapse": "^1.1.0",
"solid-icons": "^1.1.0", "solid-icons": "^1.1.0",
"solid-js": "^1.8.11" "solid-js": "^1.8.11"
}, },
@ -1750,6 +1751,14 @@
"seroval": "^1.0" "seroval": "^1.0"
} }
}, },
"node_modules/solid-collapse": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/solid-collapse/-/solid-collapse-1.1.0.tgz",
"integrity": "sha512-QAnJnpJ2gCfQaq6jpXUD76talS80upxcSCM9DTeymob+WlBgVFK6nPxsDqjd1dgSiVmXtkO05EO+hTjoOEXfXw==",
"peerDependencies": {
"solid-js": ">=1.2.0"
}
},
"node_modules/solid-devtools": { "node_modules/solid-devtools": {
"version": "0.29.3", "version": "0.29.3",
"resolved": "https://registry.npmjs.org/solid-devtools/-/solid-devtools-0.29.3.tgz", "resolved": "https://registry.npmjs.org/solid-devtools/-/solid-devtools-0.29.3.tgz",

View File

@ -16,6 +16,7 @@
}, },
"dependencies": { "dependencies": {
"@solidjs/router": "^0.13.5", "@solidjs/router": "^0.13.5",
"solid-collapse": "^1.1.0",
"solid-icons": "^1.1.0", "solid-icons": "^1.1.0",
"solid-js": "^1.8.11" "solid-js": "^1.8.11"
} }

View File

@ -60,7 +60,6 @@ const AppLayout: Component<RouteSectionProps> = (props) => {
<header class={styles.header}> <header class={styles.header}>
<div></div> <div></div>
<nav class={styles.navigation}> <nav class={styles.navigation}>
<A href='/wishlists'><BsBookmarkHeartFill class={styles.icon} />Wishlists</A>
<A href='/groups'><BsPeopleFill class={styles.icon} /> Groups</A> <A href='/groups'><BsPeopleFill class={styles.icon} /> Groups</A>
</nav> </nav>
<div class={styles.loggedIn}> <div class={styles.loggedIn}>

View File

@ -23,6 +23,9 @@ export interface Group {
owner: User, owner: User,
members: User[], members: User[],
strats: Strat[], strats: Strat[],
maps: Map[],
profiles: Profile[],
stratTypes: string[],
} }
export interface Strat { export interface Strat {
@ -180,6 +183,18 @@ export class API {
return await response.json(); return await response.json();
} }
public async removeGroup(groupId: string): Promise<void> {
const response = await fetch(this.apiURL + '/group/' + groupId, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + this.ensureLoggedIn().token
},
});
await this.checkResponse(response);
}
public async updateGroup(id: string, group: GroupRequest): Promise<Group> { public async updateGroup(id: string, group: GroupRequest): Promise<Group> {
const response = await fetch(this.apiURL + '/group/' + id, { const response = await fetch(this.apiURL + '/group/' + id, {
method: 'PUT', method: 'PUT',
@ -336,7 +351,7 @@ export class API {
return await response.json(); return await response.json();
} }
public async removeProfileToGroup(groupId: string, profileId: string): Promise<void> { public async removeProfileFromGroup(groupId: string, profileId: string): Promise<void> {
const response = await fetch(this.apiURL + '/group/' + groupId + '/profile/' + profileId, { const response = await fetch(this.apiURL + '/group/' + groupId + '/profile/' + profileId, {
method: 'DELETE', method: 'DELETE',
headers: { headers: {

View File

View File

@ -0,0 +1,55 @@
import styles from './MapItem.module.css';
import { Component } from "solid-js";
import { Group, Map, api } from "../api";
import { showDialog, showMessageDialog } from "../state";
import { errorToString } from "../util";
import { BsTrash } from 'solid-icons/bs';
export interface MapItemProps {
group: Group,
map: Map,
onDelete: (m: Map) => void
}
const MapItem: Component<MapItemProps> = (props) => {
const deleteMap = async () => {
showDialog({
title: 'Remove map',
text: 'Do you want to remove this map?',
buttons: [
{
name: 'Remove',
type: 'danger',
action: async () => {
try {
await api().removeMap(props.group.id, props.map.id);
props.onDelete(props.map);
} catch (e) {
showMessageDialog('Failed to remove map', errorToString(e));
}
}
},
{
name: 'Cancel',
action: () => { }
}
]
});
};
return (
<div class={styles.map}>
<div class={styles.mapDetails}>
<div class={styles.mapName}>
<span innerText={props.map.name} />
</div>
<div>
<button onClick={deleteMap}><BsTrash /></button>
</div>
</div>
</div >
)
}
export default MapItem;

View File

View File

@ -0,0 +1,55 @@
import styles from './MemberItem.module.css';
import { Component } from "solid-js";
import { Group, User, api } from "../api";
import { showDialog, showMessageDialog } from "../state";
import { errorToString } from "../util";
import { BsTrash } from 'solid-icons/bs';
export interface MemberItemProps {
group: Group,
member: User,
onDelete: (m: User) => void
}
const MemberItem: Component<MemberItemProps> = (props) => {
const deleteMember = async () => {
showDialog({
title: 'Remove member',
text: 'Do you want to remove this member?',
buttons: [
{
name: 'Remove',
type: 'danger',
action: async () => {
try {
await api().removeMember(props.group.id, props.member.username);
props.onDelete(props.member);
} catch (e) {
showMessageDialog('Failed to remove member', errorToString(e));
}
}
},
{
name: 'Cancel',
action: () => { }
}
]
});
};
return (
<div class={styles.member}>
<div class={styles.memberDetails}>
<div class={styles.memberName}>
<span innerText={props.member.displayName} />
</div>
<div>
<button onClick={deleteMember}><BsTrash /></button>
</div>
</div>
</div >
)
}
export default MemberItem;

View File

View File

@ -0,0 +1,55 @@
import styles from './ProfileItem.module.css';
import { Component } from "solid-js";
import { showDialog, showMessageDialog } from "../state";
import { errorToString } from "../util";
import { BsTrash } from 'solid-icons/bs';
import { Group, Profile, api } from '../api';
export interface ProfileItemProps {
group: Group,
profile: Profile,
onDelete: (p: Profile) => void
}
const ProfileItem: Component<ProfileItemProps> = (props) => {
const deleteProfile = async () => {
showDialog({
title: 'Remove profile',
text: 'Do you want to remove this profile?',
buttons: [
{
name: 'Remove',
type: 'danger',
action: async () => {
try {
await api().removeProfileFromGroup(props.group.id, props.profile.id);
props.onDelete(props.profile);
} catch (e) {
showMessageDialog('Failed to remove profile', errorToString(e));
}
}
},
{
name: 'Cancel',
action: () => { }
}
]
});
};
return (
<div class={styles.profile}>
<div class={styles.profileDetails}>
<div class={styles.profileName}>
<span innerText={props.profile.name} />
</div>
<div>
<button onClick={deleteProfile}><BsTrash /></button>
</div>
</div>
</div >
)
}
export default ProfileItem;

View File

View File

@ -0,0 +1,57 @@
import styles from './StratItem.module.css';
import { Component } from "solid-js";
import { Group, Strat, api } from "../api";
import { showDialog, showMessageDialog } from "../state";
import { errorToString } from "../util";
import { BsTrash } from 'solid-icons/bs';
export interface StratItemProps {
group: Group,
strat: Strat,
onDelete: (m: Strat) => void
}
const StratItem: Component<StratItemProps> = (props) => {
const deleteMember = async () => {
showDialog({
title: 'Remove strat',
text: 'Do you want to remove this strat?',
buttons: [
{
name: 'Remove',
type: 'danger',
action: async () => {
try {
await api().removeStrat(props.group.id, props.strat.id);
props.onDelete(props.strat);
} catch (e) {
showMessageDialog('Failed to remove strat', errorToString(e));
}
}
},
{
name: 'Cancel',
action: () => { }
}
]
});
};
return (
<div class={styles.member}>
<div class={styles.memberDetails}>
<div class={styles.memberName}>
<span innerText={props.strat.title} />
<span innerText={props.strat.stratType} />
<span innerText={props.strat.map.name} />
</div>
<div>
<button onClick={deleteMember}><BsTrash /></button>
</div>
</div>
</div >
)
}
export default StratItem;

View File

View File

@ -0,0 +1,55 @@
import styles from './MapItem.module.css';
import { Component, createSignal } from "solid-js";
import { Group, Map, User, api } from "../api";
import { showDialog, showMessageDialog } from "../state";
import { errorToString } from "../util";
import { BsTrash } from 'solid-icons/bs';
export interface StratTypeItem {
group: Group,
stratType: string,
onDelete: (m: string) => void
}
const StratTypeItem: Component<StratTypeItem> = (props) => {
const deleteStratType = async () => {
showDialog({
title: 'Remove strat type',
text: 'Do you want to remove this strat type?',
buttons: [
{
name: 'Remove',
type: 'danger',
action: async () => {
try {
await api().removeStratType(props.group.id, props.stratType);
props.onDelete(props.stratType);
} catch (e) {
showMessageDialog('Failed to remove strat type', errorToString(e));
}
}
},
{
name: 'Cancel',
action: () => { }
}
]
});
};
return (
<div class={styles.stratType}>
<div class={styles.stratTypeDetails}>
<div class={styles.stratTypeName}>
<span innerText={props.stratType} />
</div>
<div>
<button onClick={deleteStratType}><BsTrash /></button>
</div>
</div>
</div >
)
}
export default StratTypeItem;

View File

@ -2,10 +2,16 @@ import { useNavigate, useParams } from "@solidjs/router";
import { Component, Show, createEffect, createSignal } from "solid-js"; import { Component, Show, createEffect, createSignal } from "solid-js";
import styles from './GroupPage.module.css' import styles from './GroupPage.module.css'
import { Group, User, api } from "../api"; import { Group, Map, Profile, Strat, User, api } from "../api";
import { showDialog, showInputDialog, showMessageDialog } from "../state"; import { showDialog, showInputDialog, showMessageDialog } from "../state";
import { errorToString, normalize } from "../util"; import { errorToString, normalize } from "../util";
import { BsCheck2, BsPencil, BsTrash } from "solid-icons/bs"; import { BsCheck2, BsPencil, BsTrash } from "solid-icons/bs";
import { Collapse } from "solid-collapse";
import MemberItem from "../components/MemberItem";
import MapItem from "../components/MapItem";
import StratItem from "../components/StratItem";
import StratTypeItem from "../components/StratTypeItem";
import ProfileItem from "../components/ProfileItem";
const GroupPage: Component = () => { const GroupPage: Component = () => {
const params = useParams(); const params = useParams();
@ -16,6 +22,12 @@ const GroupPage: Component = () => {
const [editing, setEditing] = createSignal(false); const [editing, setEditing] = createSignal(false);
const [name, setName] = createSignal(''); const [name, setName] = createSignal('');
const [membersIsExpanded, setMembersIsExpanded] = createSignal(false);
const [mapsIsExpanded, setMapsIsExpanded] = createSignal(false);
const [stratsIsExpanded, setStratsIsExpanded] = createSignal(false);
const [profilesIsExpanded, setProfilesIsExpanded] = createSignal(false);
const [stratTypesIsExpanded, setStratTypesIsExpanded] = createSignal(false);
createEffect(async () => { createEffect(async () => {
try { try {
setGroup(await api().getGroup(params.id)); setGroup(await api().getGroup(params.id));
@ -45,7 +57,7 @@ const GroupPage: Component = () => {
type: 'danger', type: 'danger',
action: async () => { action: async () => {
try { try {
await api().deleteGroup(group().id); await api().removeGroup(group().id);
navigate('/groups'); navigate('/groups');
} catch (e) { } catch (e) {
showMessageDialog('Failed to delete group', errorToString(e)); showMessageDialog('Failed to delete group', errorToString(e));
@ -72,41 +84,6 @@ const GroupPage: Component = () => {
}; };
const addWishlist = async () => {
const userWishlists = await api().getWishlists(api().ensureLoggedIn().username);
showDialog({
title: 'Add Wishlist', text: 'add wishlist to group', buttons:
[...userWishlists.map(w => {
return {
name: w.title, action: async () => {
try {
const newGroup = await api().addWishlistToGroup(group().id, w.id);
setGroup(newGroup);
} catch (e) {
showMessageDialog('Failed to add wishlist', errorToString(e));
}
}
}
}),
'cancel'
]
})
};
const removeWishlist = async (wishlistId: string) => {
showDialog({
title: 'Remove Wishlist', text: 'remove wishlist from group', buttons:
[{
name: 'remove', action: async () => {
const newGroup = await api().removeWishlistFromGroup(group().id, wishlistId);
setGroup(newGroup);
}
},
'cancel'
]
})
};
const toggleEdit = async () => { const toggleEdit = async () => {
if (!editing()) { if (!editing()) {
setEditing(true); setEditing(true);
@ -121,6 +98,22 @@ const GroupPage: Component = () => {
setGroup({ ...group(), members: [...group().members.filter(m => m.username !== member.username)] }); setGroup({ ...group(), members: [...group().members.filter(m => m.username !== member.username)] });
}; };
const onDeleteMap = async (map: Map) => {
setGroup({ ...group(), maps: [...group().maps.filter(m => m.id !== map.id)] });
};
const onDeleteStrat = async (strat: Strat) => {
setGroup({ ...group(), strats: [...group().strats.filter(s => s.id !== strat.id)] });
};
const onDeleteStratType = async (stratType: string) => {
setGroup({ ...group(), stratTypes: [...group().stratTypes.filter(s => s !== stratType)] });
};
const onDeleteProfile = async (profile: Profile) => {
setGroup({ ...group(), profiles: [...group().profiles.filter(p => p.id !== profile.id)] });
};
return ( return (
<> <>
<Show when={!loading()} fallback={<div>Loading...</div>}> <Show when={!loading()} fallback={<div>Loading...</div>}>
@ -129,20 +122,42 @@ const GroupPage: Component = () => {
{editing() && <span class={styles.editCount}>{name().length}/50</span>} {editing() && <span class={styles.editCount}>{name().length}/50</span>}
</div> </div>
<div class={styles.groupButtons}> <div class={styles.groupButtons}>
<button class={styles.wishlistButton} onClick={deleteGroup}>Delete group</button> <button class={styles.deleteButton} onClick={deleteGroup}>Delete group</button>
<button class={styles.editButton} onClick={toggleEdit}>{editing() ? <BsCheck2 /> : <BsPencil />}</button> <button class={styles.editButton} onClick={toggleEdit}>{editing() ? <BsCheck2 /> : <BsPencil />}</button>
</div> </div>
<hr /> <hr />
<button class={styles.groupButton} onClick={addMember}>Add Member</button> <div>
<div class={styles.groupMembers}> <button onClick={() => setMembersIsExpanded(!membersIsExpanded())}>Members</button>
{group().members?.map(m => <MemberItem group={group()} member={m} onDelete={onDeleteMember} />)} <Collapse value={membersIsExpanded()} class={styles.members}>
</div> <button class={styles.addMemberButton} onClick={addMember}>Add Member</button>
<button class={styles.wishlistButton} onClick={addWishlist}>Add Wishlist</button> <div class={styles.groupMembers}>
<div class={styles.groupWishlists}> {group().members?.map(m => <MemberItem group={group()} member={m} onDelete={onDeleteMember} />)}
{group().wishlists?.map(w => <div> </div>
<WishlistItem wishlist={w} /> </Collapse>
<button class={styles.removeWishlistButton} onClick={() => removeWishlist(w.id)}><BsTrash /></button> <button onClick={() => setStratsIsExpanded(!stratsIsExpanded())}>strats</button>
</div>)} <Collapse value={stratsIsExpanded()} class={styles.strats}>
<div class={styles.strats}>
{group().strats?.map(s => <StratItem group={group()} strat={s} onDelete={onDeleteStrat} />)}
</div>
</Collapse>
<button onClick={() => setMapsIsExpanded(!mapsIsExpanded())}>maps</button>
<Collapse value={mapsIsExpanded()} class={styles.maps}>
<div class={styles.maps}>
{group().maps?.map(m => <MapItem group={group()} map={m} onDelete={onDeleteMap} />)}
</div>
</Collapse>
<button onClick={() => setStratTypesIsExpanded(!stratTypesIsExpanded())}>strat types</button>
<Collapse value={stratTypesIsExpanded()} class={styles.stratTypes}>
<div class={styles.stratTypes}>
{group().stratTypes?.map(s => <StratTypeItem group={group()} stratType={s} onDelete={onDeleteStratType} />)}
</div>
</Collapse>
<button onClick={() => setProfilesIsExpanded(!profilesIsExpanded())}>profiles</button>
<Collapse value={profilesIsExpanded()} class={styles.profiles}>
<div class={styles.profiles}>
{group().profiles?.map(p => <ProfileItem group={group()} profile={p} onDelete={onDeleteProfile} />)}
</div>
</Collapse>
</div> </div>
</Show> </Show>
</> </>

View File

@ -1,4 +1,4 @@
import { Component, For, createSignal } from "solid-js"; import { Component, For, Show, createSignal } from "solid-js";
import GroupItem from '../components/GroupItem' import GroupItem from '../components/GroupItem'
@ -35,16 +35,18 @@ const Groups: Component = () => {
}; };
return ( return (
<div class={styles.page}> <Show when={!loading()} fallback={<div>Loading...</div>}>
<h1>Your Groups</h1> <div class={styles.page}>
<button onclick={createGroup}>Create Group</button> <h1>Your Groups</h1>
<hr /> <button onclick={createGroup}>Create Group</button>
<div class={styles.groups}> <hr />
<For each={groups()}> <div class={styles.groups}>
{g => <GroupItem group={g} />} <For each={groups()}>
</For> {g => <GroupItem group={g} />}
</For>
</div>
</div> </div>
</div> </Show >
) )
} }