Weird stopping point but whatever

This commit is contained in:
2025-09-05 16:07:38 -05:00
parent 5ca78bd92c
commit 3032d76e43
6 changed files with 101 additions and 43 deletions

View File

@@ -26,6 +26,12 @@ type StatusRow = {
} | null,
};
type Paginated<T> = {
page: T[],
isDone: boolean,
continueCursor: string | null,
};
// CHANGED: typed helpers
const ensureUser = async (ctx: RWCtx, userId: Id<'users'>) => {
const user = await ctx.db.get(userId);
@@ -228,35 +234,76 @@ export const getCurrentForAll = query({
});
/**
* Paginated history for a specific user (newest first).
* Paginated history for all users or for a specific user.
*/
export const listHistoryByUser = query({
export const listHistory = query({
args: {
userId: v.id('users'),
userId: v.optional(v.id('users')),
paginationOpts: paginationOptsValidator,
},
handler: async (ctx, { userId, paginationOpts }) => {
await ensureUser(ctx, userId);
return await ctx.db
handler: async (ctx, { userId, paginationOpts }): Promise<
Paginated<StatusRow>
> => {
// Query statuses newest-first, optionally filtered by user
const result = userId
? await ctx.db
.query('statuses')
.withIndex('by_user_updatedAt', (q) => q.eq('userId', userId))
.order('desc')
.paginate(paginationOpts);
},
});
/**
* Global paginated history (all users, newest first).
* - Add an index on updatedAt if you want to avoid full-table scans
* when the collection grows large.
*/
export const listHistoryAll = query({
args: { paginationOpts: paginationOptsValidator },
handler: async (ctx, { paginationOpts }) => {
return await ctx.db
.paginate(paginationOpts)
: await ctx.db
.query('statuses')
.order('desc')
.paginate(paginationOpts);
// Cache user display objects to avoid refetching repeatedly
const displayCache = new Map<string, StatusRow['user']>();
const getDisplay = async (
uid: Id<'users'>,
): Promise<StatusRow['user']> => {
const key = uid as unknown as string;
const cached = displayCache.get(key);
if (cached) return cached;
const user = await ctx.db.get(uid);
if (!user) throw new ConvexError('User not found.');
const imgId = getImageId(user);
const imgUrl = imgId ? await ctx.storage.getUrl(imgId) : null;
const display: StatusRow['user'] = {
id: user._id,
name: getName(user),
imageUrl: imgUrl,
};
displayCache.set(key, display);
return display;
};
const page: StatusRow[] = [];
for (const s of result.page) {
const owner = await getDisplay(s.userId);
const updatedBy =
s.updatedBy !== s.userId ? await getDisplay(s.updatedBy) : null;
page.push({
user: owner,
status: {
id: s._id,
message: s.message,
updatedAt: s.updatedAt,
updatedBy,
},
});
}
return {
page,
isDone: result.isDone,
continueCursor: result.continueCursor,
};
},
});

View File

@@ -24,7 +24,7 @@ import {
TabsTrigger,
} from '@/components/ui';
import { toast } from 'sonner';
import { PASSWORD_MIN, PASSWORD_MAX, PASSWORD_REGEX } from '~/convex/auth';
import { PASSWORD_MIN, PASSWORD_MAX, PASSWORD_REGEX } from '@/lib/utils';
const signInFormSchema = z.object({
email: z.email({

View File

@@ -21,7 +21,7 @@ import {
SubmitButton,
} from '@/components/ui';
import { toast } from 'sonner';
import { PASSWORD_MIN, PASSWORD_MAX, PASSWORD_REGEX } from '~/convex/auth';
import { PASSWORD_MIN, PASSWORD_MAX, PASSWORD_REGEX } from '@/lib/utils';
const formSchema = z
.object({

View File

@@ -29,15 +29,14 @@ import {
import { toast } from 'sonner';
type StatusHistoryProps = {
preloadedUser: Preloaded<typeof api.auth.getUser>;
userId?: Id<'users'>;
};
export const StatusHistory = ({
preloadedUser,
userId,
}: StatusHistoryProps) => {
const user = usePreloadedQuery(preloadedUser);
return (
<div/>
);
};

View File

@@ -18,6 +18,7 @@ import {
import { toast } from 'sonner';
import { ccn, formatTime, formatDate } from '@/lib/utils';
import { Clock, Calendar, CheckCircle2 } from 'lucide-react';
import { StatusHistory } from '@/components/layout/status';
type StatusListProps = {
preloadedUser: Preloaded<typeof api.auth.getUser>;
@@ -36,6 +37,7 @@ export const StatusList = ({
const [selectAll, setSelectAll] = useState(false);
const [statusInput, setStatusInput] = useState('');
const [updatingStatus, setUpdatingStatus] = useState(false);
const [selectedHistoryUserId, setSelectedHistoryUserId] = useState<Id<'users'>>();
const bulkCreate = useMutation(api.statuses.bulkCreate);
@@ -155,19 +157,25 @@ export const StatusList = ({
)}
<CardContent className='p-0'>
<div className='flex items-start gap-3'>
<Drawer>
<DrawerTrigger asChild>
<div
data-profile-trigger
className='flex-shrink-0 cursor-pointer
hover:opacity-80 transition-opacity'
// TODO: open history drawer
onClick={() => setSelectedHistoryUserId(u.id)}
>
<BasedAvatar
// Swap to a URL once you resolve storage URLs
src={u.imageUrl}
fullName={u.name ?? 'Technician'}
className={tvMode ? 'w-16 h-16' : 'w-12 h-12'}
/>
</div>
</DrawerTrigger>
{selectedHistoryUserId === u.id && (
<StatusHistory userId={u.id} />
)}
</Drawer>
<div className='flex-1'>
<div className='flex items-start justify-between mb-2'>

View File

@@ -19,6 +19,10 @@ export const ccn = ({
return twMerge(className, context ? on : off);
};
export const PASSWORD_MIN = 8;
export const PASSWORD_MAX = 100;
export const PASSWORD_REGEX = /^(?=.{8,100}$)(?!.*\s)(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[\p{P}\p{S}]).*$/u;
type Timestamp = number | string | Date;
const toDate = (ts: Timestamp): Date | null => {