feat(frontend): make field entries sortable
This commit is contained in:
parent
66a0830ef2
commit
11363d6769
|
@ -7,7 +7,6 @@ import (
|
||||||
"codeberg.org/u1f320/pronouns.cc/backend/db/queries"
|
"codeberg.org/u1f320/pronouns.cc/backend/db/queries"
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
"github.com/georgysavva/scany/pgxscan"
|
|
||||||
"github.com/jackc/pgconn"
|
"github.com/jackc/pgconn"
|
||||||
"github.com/jackc/pgx/v4"
|
"github.com/jackc/pgx/v4"
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
|
@ -89,20 +88,18 @@ func (db *DB) CreateUser(ctx context.Context, tx pgx.Tx, username string) (u Use
|
||||||
|
|
||||||
// DiscordUser fetches a user by Discord user ID.
|
// DiscordUser fetches a user by Discord user ID.
|
||||||
func (db *DB) DiscordUser(ctx context.Context, discordID string) (u User, err error) {
|
func (db *DB) DiscordUser(ctx context.Context, discordID string) (u User, err error) {
|
||||||
sql, args, err := sq.Select("*").From("users").Where("discord = ?", discordID).ToSql()
|
sql, args, err := sq.Select("id").From("users").Where("discord = ?", discordID).ToSql()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return u, errors.Wrap(err, "building sql")
|
return u, errors.Wrap(err, "building sql")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = pgxscan.Get(ctx, db, &u, sql, args...)
|
var id xid.ID
|
||||||
|
err = db.QueryRow(ctx, sql, args...).Scan(&id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Cause(err) == pgx.ErrNoRows {
|
return u, errors.Wrap(err, "executing id query")
|
||||||
return u, ErrUserNotFound
|
|
||||||
}
|
|
||||||
return u, errors.Cause(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return u, nil
|
return db.getUser(ctx, db, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) UpdateFromDiscord(ctx context.Context, db querier, du *discordgo.User) error {
|
func (u *User) UpdateFromDiscord(ctx context.Context, db querier, du *discordgo.User) error {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
Heart,
|
Heart,
|
||||||
People,
|
People,
|
||||||
Plus,
|
Plus,
|
||||||
|
ThreeDotsVertical,
|
||||||
Trash3,
|
Trash3,
|
||||||
} from "react-bootstrap-icons";
|
} from "react-bootstrap-icons";
|
||||||
|
|
||||||
|
@ -13,11 +14,18 @@ import TextInput from "./TextInput";
|
||||||
import Button, { ButtonStyle } from "./Button";
|
import Button, { ButtonStyle } from "./Button";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { WordStatus } from "../lib/api-fetch";
|
import { WordStatus } from "../lib/api-fetch";
|
||||||
|
import { ReactSortable } from "react-sortablejs";
|
||||||
|
|
||||||
export interface EditField {
|
export interface EditField {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
values: Array<{ value: string; status: WordStatus }>;
|
values: EditFieldValue[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EditFieldValue {
|
||||||
|
id: number;
|
||||||
|
value: string;
|
||||||
|
status: WordStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
type EditableCardProps = {
|
type EditableCardProps = {
|
||||||
|
@ -35,6 +43,7 @@ type EditableCardProps = {
|
||||||
onChangeFriends(e: React.MouseEvent<HTMLButtonElement>, index: number): void;
|
onChangeFriends(e: React.MouseEvent<HTMLButtonElement>, index: number): void;
|
||||||
onChangeAvoid(e: React.MouseEvent<HTMLButtonElement>, index: number): void;
|
onChangeAvoid(e: React.MouseEvent<HTMLButtonElement>, index: number): void;
|
||||||
onClickDelete: React.MouseEventHandler<HTMLButtonElement>;
|
onClickDelete: React.MouseEventHandler<HTMLButtonElement>;
|
||||||
|
onChangeOrder(newState: EditFieldValue[]): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function EditableCard(props: EditableCardProps) {
|
export function EditableCard(props: EditableCardProps) {
|
||||||
|
@ -68,16 +77,21 @@ export function EditableCard(props: EditableCardProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card title={props.field.name} draggable footer={footer}>
|
<Card title={props.field.name} draggable footer={footer}>
|
||||||
<ul>
|
<ReactSortable
|
||||||
|
handle=".entry-handle"
|
||||||
|
list={props.field.values}
|
||||||
|
setList={props.onChangeOrder}
|
||||||
|
>
|
||||||
{props.field.values.map((value, index) => {
|
{props.field.values.map((value, index) => {
|
||||||
return (
|
return (
|
||||||
<li className="flex justify-between my-1 items-center" key={index}>
|
<li className="flex justify-between my-1 items-center" key={index}>
|
||||||
|
<ThreeDotsVertical className="entry-handle hover:cursor-grab" />
|
||||||
<TextInput
|
<TextInput
|
||||||
value={value.value}
|
value={value.value}
|
||||||
prevValue={value.value}
|
prevValue={value.value}
|
||||||
onChange={props.onChangePronoun}
|
onChange={props.onChangePronoun}
|
||||||
/>
|
/>
|
||||||
<div className="rounded-md">
|
<div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={(e) => props.onChangeFavourite(e, index)}
|
onClick={(e) => props.onChangeFavourite(e, index)}
|
||||||
|
@ -144,7 +158,7 @@ export function EditableCard(props: EditableCardProps) {
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ReactSortable>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,11 @@ import { ReactSortable } from "react-sortablejs";
|
||||||
import { useRecoilState, useRecoilValue } from "recoil";
|
import { useRecoilState, useRecoilValue } from "recoil";
|
||||||
|
|
||||||
import Button, { ButtonStyle } from "../../components/Button";
|
import Button, { ButtonStyle } from "../../components/Button";
|
||||||
import { EditableCard, EditField } from "../../components/Editable";
|
import {
|
||||||
|
EditableCard,
|
||||||
|
EditField,
|
||||||
|
EditFieldValue,
|
||||||
|
} from "../../components/Editable";
|
||||||
import Loading from "../../components/Loading";
|
import Loading from "../../components/Loading";
|
||||||
import { fetchAPI, Field, MeUser, WordStatus } from "../../lib/api-fetch";
|
import { fetchAPI, Field, MeUser, WordStatus } from "../../lib/api-fetch";
|
||||||
import { themeState, userState } from "../../lib/state";
|
import { themeState, userState } from "../../lib/state";
|
||||||
|
@ -30,8 +34,8 @@ export default function Index() {
|
||||||
values: [],
|
values: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
f.entries?.forEach((entry) => {
|
f.entries?.forEach((entry, idx) => {
|
||||||
field.values.push(entry);
|
field.values.push({ ...entry, id: idx });
|
||||||
});
|
});
|
||||||
|
|
||||||
return field;
|
return field;
|
||||||
|
@ -168,7 +172,15 @@ export default function Index() {
|
||||||
setFields([...fields]);
|
setFields([...fields]);
|
||||||
}}
|
}}
|
||||||
onAddPronoun={(pronoun) => {
|
onAddPronoun={(pronoun) => {
|
||||||
field.values.push({ value: pronoun, status: WordStatus.Okay });
|
field.values.push({
|
||||||
|
id: field.values.length + 1,
|
||||||
|
value: pronoun,
|
||||||
|
status: WordStatus.Okay,
|
||||||
|
});
|
||||||
|
setFields([...fields]);
|
||||||
|
}}
|
||||||
|
onChangeOrder={(newState: EditFieldValue[]) => {
|
||||||
|
field.values = newState;
|
||||||
setFields([...fields]);
|
setFields([...fields]);
|
||||||
}}
|
}}
|
||||||
onDeletePronoun={(e, index) => {
|
onDeletePronoun={(e, index) => {
|
||||||
|
|
Loading…
Reference in New Issue