feat(app): minor improvements
Build and Push Docker image / build-and-push (push) Successful in 5m0s Details

added sender and sendTime to announcements
added sendTime as duration to message
improved duration function (no more 59Min instead of 1Std)
This commit is contained in:
Valentin Kolb 2024-06-12 00:54:36 +02:00
parent ed2056fded
commit 841be395f9
8 changed files with 73 additions and 60 deletions

View File

@ -57,11 +57,15 @@ function getLocalizedNowWord(locale: string) {
return translations[locale as keyof typeof translations] || 'now'; return translations[locale as keyof typeof translations] || 'now';
} }
export const humanDeltaFromNow = (start: string | Date | Dayjs, end: string | Date | Dayjs): { export const humanDeltaFromNow = (start: string | Date | Dayjs, end?: string | Date | Dayjs): {
message: string, message: string,
delta: "PAST" | "FUTURE" | "NOW" delta: "PAST" | "FUTURE" | "NOW"
} => { } => {
if (!end) {
end = start
}
// check if end is in the past // check if end is in the past
if (dayjs(end).isBefore(dayjs())) { if (dayjs(end).isBefore(dayjs())) {
return { return {
@ -112,29 +116,27 @@ export const pprintDateRange = (d1: string | Date | Dayjs, d2: string | Date | D
* @param date2 * @param date2
*/ */
export function formatDuration(date1: string | Date | Dayjs, date2: string | Date | Dayjs) { export function formatDuration(date1: string | Date | Dayjs, date2: string | Date | Dayjs) {
const start = dayjs(date1)
const end = dayjs(date2)
const diff = end.diff(start)
const duration = dayjs.duration(diff)
const weeks = Math.floor(duration.asDays() / 7);
const days = duration.days() % 7;
const hours = duration.hours();
const minutes = duration.minutes();
const parts = []; // ignore seconds and milliseconds
date1 = dayjs(date1).startOf('minute');
date2 = dayjs(date2).startOf('minute');
if (weeks > 0) { // get the difference in milliseconds
parts.push(`${weeks} Wo`); const diff = dayjs(date2).diff(dayjs(date1));
} const durationObj = dayjs.duration(diff);
if (days > 0 || weeks > 0) {
parts.push(`${days} Tag${days > 1 ? 'e' : ''}`);
}
if (hours > 0) {
parts.push(`${hours} Std`);
}
if (minutes > 0) {
parts.push(`${minutes} Min`);
}
return parts.join(', '); // get the duration in weeks, days, hours and minutes
const weeks = Math.floor(durationObj.asWeeks());
const days = durationObj.days();
const hours = durationObj.hours();
const minutes = durationObj.minutes();
// create a string array with the duration parts
const result: string[] = [];
if (weeks > 0) result.push(`${weeks} Wo`);
if (days > 0) result.push(`${days} Tag${days > 1 ? 'e' : ''}`);
if (hours > 0) result.push(`${hours} Std`);
if (minutes > 0) result.push(`${minutes} Min`);
return result.join(', ');
} }

View File

@ -3,17 +3,14 @@
position: relative; position: relative;
flex-direction: column; flex-direction: column;
box-shadow: var(--shadow);
background-color: var(--mantine-color-body); background-color: var(--mantine-color-body);
padding: var(--padding); padding: var(--padding);
border: var(--border); border: var(--border);
border-color: var(--mantine-primary-color-5); border-color: var(--mantine-primary-color-5);
border-radius: var(--mantine-radius-lg); border-radius: var(--border-radius);
margin: var(--gap);
max-width: 100%; max-width: 100%;
word-break: break-all; word-break: break-all;
@ -24,4 +21,11 @@
font-size: var(--mantine-font-size-lg); font-size: var(--mantine-font-size-lg);
font-weight: bold; font-weight: bold;
color: var(--mantine-primary-color-5); color: var(--mantine-primary-color-5);
margin: 0;
}
.subjectStack {
display: flex;
flex-direction: column;
justify-content: center;
} }

View File

@ -1,26 +1,37 @@
import InnerHtml from "@/components/InnerHtml"; import InnerHtml from "@/components/InnerHtml";
import classes from './Announcement.module.css' import classes from './Announcement.module.css'
import {Group, ThemeIcon} from "@mantine/core"; import {Group, Text, ThemeIcon, Tooltip} from "@mantine/core";
import {IconSpeakerphone} from "@tabler/icons-react"; import {IconSpeakerphone} from "@tabler/icons-react";
import {MessagesModel} from "@/models/MessageTypes.ts";
import {getUserName} from "@/components/users/modals/util.tsx";
import {humanDeltaFromNow, pprintDate} from "@/lib/datetime.ts";
export default function Announcement({subject, content}: { export default function Announcement({announcement}: {
subject: string | null, announcement: MessagesModel
content: string,
}) { }) {
const senderName = getUserName(announcement.expand.sender)
return <div className={classes.announcement}> return <div className={classes.announcement}>
<Group justify={"space-between"} wrap={"nowrap"} align={"top"} mb={"md"}> <Group justify={"space-between"} wrap={"nowrap"} align={"top"} mb={"md"}>
{subject && <div className={classes.subject}> <div className={classes.subjectStack}>
{subject} {announcement.subject && <div className={`${classes.subject} wrapWords`}>
{announcement.subject}
</div>} </div>}
<Text size={"xs"} c={"dimmed"} className={"wrapWords"}>
{senderName} {pprintDate(announcement.created)} {humanDeltaFromNow(announcement.created).message}
</Text>
</div>
<Tooltip label={`Ankündigung von ${getUserName(announcement.expand.sender)}`} withArrow>
<ThemeIcon <ThemeIcon
className={classes.icon} className={classes.icon}
variant={"transparent"} size={"sm"} variant={"transparent"} size={"sm"}
> >
<IconSpeakerphone/> <IconSpeakerphone/>
</ThemeIcon> </ThemeIcon>
</Tooltip>
</Group> </Group>
<InnerHtml html={content}/> <InnerHtml html={announcement.content}/>
</div> </div>
} }

View File

@ -2,5 +2,6 @@
flex-grow: 1; flex-grow: 1;
overflow-y: auto; overflow-y: auto;
display: flex; display: flex;
flex-direction: column-reverse; flex-direction: column;
gap: var(--gap);
} }

View File

@ -2,7 +2,7 @@ import {useInfiniteQuery} from "@tanstack/react-query";
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx"; import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
import {Button, Center, Loader, Text} from "@mantine/core"; import {Button, Center, Loader, Text} from "@mantine/core";
import classes from './Announcements.module.css' import classes from './Announcements.module.css'
import {IconMessageCircleUp} from "@tabler/icons-react"; import {IconMessageCircleDown} from "@tabler/icons-react";
import Announcement from "@/pages/chat/components/Announcement.tsx"; import Announcement from "@/pages/chat/components/Announcement.tsx";
export default function Announcements() { export default function Announcements() {
@ -11,9 +11,10 @@ export default function Announcements() {
const query = useInfiniteQuery({ const query = useInfiniteQuery({
queryKey: ["announcements"], queryKey: ["announcements"],
queryFn: async ({pageParam}) => ( queryFn: async ({pageParam}) => (
await pb.collection("messages").getList(pageParam, 100, { await pb.collection("messages").getList(pageParam, 50, {
filter: `isAnnouncement=true&&sender!='${user?.id}'`, filter: `isAnnouncement=true&&sender!='${user?.id}'`,
sort: "-created" sort: "-created",
expand: "sender"
}) })
), ),
getNextPageParam: (lastPage) => getNextPageParam: (lastPage) =>
@ -39,8 +40,7 @@ export default function Announcements() {
{announcements.map((announcement) => ( {announcements.map((announcement) => (
<Announcement <Announcement
key={announcement.id} key={announcement.id}
subject={announcement.subject} announcement={announcement}
content={announcement.content}
/> />
))} ))}
@ -50,21 +50,18 @@ export default function Announcements() {
variant={"transparent"} color={"blue"} variant={"transparent"} color={"blue"}
radius={"xl"} radius={"xl"}
onClick={() => query.fetchNextPage()} onClick={() => query.fetchNextPage()}
leftSection={<IconMessageCircleUp/>} leftSection={<IconMessageCircleDown/>}
loading={query.isFetchingNextPage} loading={query.isFetchingNextPage}
> >
Mehr laden Mehr laden
</Button> </Button>
</Center> </Center>
) : <div className={classes.text}> ) : announcements.length === 0 ? <div className={classes.text}>
<Text ta={"center"} size={"xs"} c={"dimmed"}> <Text ta={"center"} size={"xs"} c={"dimmed"}>
{ "Noch keine Ankündigungen"
announcements.length > 0 ?
"Keine weiteren Ankündigungen"
: "Noch keine Ankündigungen"
}
</Text> </Text>
</div>} </div> : null
}
</div> </div>
</div> </div>
} }

View File

@ -17,8 +17,6 @@
position: relative; position: relative;
flex-direction: column; flex-direction: column;
box-shadow: var(--shadow);
background-color: var(--mantine-color-body); background-color: var(--mantine-color-body);
padding: var(--padding); padding: var(--padding);

View File

@ -8,7 +8,7 @@ import {useInfiniteQuery, useMutation} from "@tanstack/react-query";
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx"; import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
import InnerHtml from "@/components/InnerHtml"; import InnerHtml from "@/components/InnerHtml";
import {getUserName} from "@/components/users/modals/util.tsx"; import {getUserName} from "@/components/users/modals/util.tsx";
import {pprintDateTime} from "@/lib/datetime.ts"; import {humanDeltaFromNow, pprintDateTime} from "@/lib/datetime.ts";
import {EventListModel} from "@/models/EventTypes.ts"; import {EventListModel} from "@/models/EventTypes.ts";
export default function Messages({eventList}: { export default function Messages({eventList}: {
@ -87,7 +87,7 @@ export default function Messages({eventList}: {
<InnerHtml html={message.content}/> <InnerHtml html={message.content}/>
<div className={classes.messageSender}> <div className={classes.messageSender}>
{pprintDateTime(message.created)} {humanDeltaFromNow(message.created).message} {pprintDateTime(message.created)}
</div> </div>
</div> </div>
))} ))}

View File

@ -1,5 +1,5 @@
.announcementsContainer { .announcementsContainer {
max-height: 60vh; max-height: 40vh;
display: flex; display: flex;
& > * { & > * {
max-height: 100%; max-height: 100%;