simple event list and fix tables on settings
This commit is contained in:
@@ -16,12 +16,13 @@ Mixan is a simple analytics tool for logging events on web and react-native. My
|
|||||||
|
|
||||||
### GUI
|
### GUI
|
||||||
|
|
||||||
* [ ] Fix tables on settings
|
* [X] Fix tables on settings
|
||||||
* [ ] Rename event label
|
* [ ] Rename event label
|
||||||
* [ ] Real time data (mostly screen_views stats)
|
* [ ] Real time data (mostly screen_views stats)
|
||||||
* [ ] Active users (5min, 10min, 30min)
|
* [ ] Active users (5min, 10min, 30min)
|
||||||
* [X] Save report to a specific dashboard
|
* [X] Save report to a specific dashboard
|
||||||
* [ ] View events in a list
|
* [X] View events in a list
|
||||||
|
* [ ] Simple filters
|
||||||
* [ ] View profiles in a list
|
* [ ] View profiles in a list
|
||||||
* [ ] Invite users
|
* [ ] Invite users
|
||||||
* [ ] Drag n Drop reports on dashboard
|
* [ ] Drag n Drop reports on dashboard
|
||||||
|
|||||||
@@ -1,66 +1,75 @@
|
|||||||
import { type ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"
|
import {
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./ui/table"
|
type ColumnDef,
|
||||||
|
flexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
useReactTable,
|
||||||
|
} from "@tanstack/react-table";
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "./ui/table";
|
||||||
|
|
||||||
interface DataTableProps<TData, TValue> {
|
interface DataTableProps<TData> {
|
||||||
columns: ColumnDef<TData, TValue>[]
|
columns: ColumnDef<TData, any>[];
|
||||||
data: TData[]
|
data: TData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DataTable<TData, TValue>({
|
export function DataTable<TData>({
|
||||||
columns,
|
columns,
|
||||||
data,
|
data,
|
||||||
}: DataTableProps<TData, TValue>) {
|
}: DataTableProps<TData>) {
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data,
|
data,
|
||||||
columns,
|
columns,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
})
|
});
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="rounded-md border">
|
|
||||||
<Table>
|
|
||||||
<TableHeader>
|
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
|
||||||
<TableRow key={headerGroup.id}>
|
|
||||||
{headerGroup.headers.map((header) => {
|
|
||||||
return (
|
|
||||||
<TableHead key={header.id}>
|
|
||||||
{header.isPlaceholder
|
|
||||||
? null
|
|
||||||
: flexRender(
|
|
||||||
header.column.columnDef.header,
|
|
||||||
header.getContext()
|
|
||||||
)}
|
|
||||||
</TableHead>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{table.getRowModel().rows?.length ? (
|
|
||||||
table.getRowModel().rows.map((row) => (
|
|
||||||
<TableRow
|
|
||||||
key={row.id}
|
|
||||||
data-state={row.getIsSelected() && "selected"}
|
|
||||||
>
|
|
||||||
{row.getVisibleCells().map((cell) => (
|
|
||||||
<TableCell key={cell.id}>
|
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
|
||||||
No results.
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
<TableRow key={headerGroup.id}>
|
||||||
|
{headerGroup.headers.map((header) => {
|
||||||
|
return (
|
||||||
|
<TableHead key={header.id}>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: flexRender(
|
||||||
|
header.column.columnDef.header,
|
||||||
|
header.getContext(),
|
||||||
|
)}
|
||||||
|
</TableHead>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{table.getRowModel().rows?.length ? (
|
||||||
|
table.getRowModel().rows.map((row) => (
|
||||||
|
<TableRow
|
||||||
|
key={row.id}
|
||||||
|
data-state={row.getIsSelected() && "selected"}
|
||||||
|
>
|
||||||
|
{row.getVisibleCells().map((cell) => (
|
||||||
|
<TableCell key={cell.id}>
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||||
|
No results.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
40
apps/web/src/components/Pagination.tsx
Normal file
40
apps/web/src/components/Pagination.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { useMemo, useState } from "react";
|
||||||
|
import { Button } from "./ui/button";
|
||||||
|
|
||||||
|
export function usePagination(take = 100) {
|
||||||
|
const [skip, setSkip] = useState(0);
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
skip,
|
||||||
|
next: () => setSkip((p) => p + take),
|
||||||
|
prev: () => setSkip((p) => Math.max(p - take)),
|
||||||
|
take,
|
||||||
|
canPrev: skip > 0,
|
||||||
|
canNext: true,
|
||||||
|
}),
|
||||||
|
[skip, setSkip, take],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Pagination(props: ReturnType<typeof usePagination>) {
|
||||||
|
return (
|
||||||
|
<div className="flex select-none items-center justify-end space-x-2 py-4">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => props.prev()}
|
||||||
|
disabled={!props.canPrev}
|
||||||
|
>
|
||||||
|
Previous
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => props.next()}
|
||||||
|
disabled={!props.canNext}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -10,11 +10,12 @@ import {
|
|||||||
getSortedRowModel,
|
getSortedRowModel,
|
||||||
type SortingState,
|
type SortingState,
|
||||||
} from "@tanstack/react-table";
|
} from "@tanstack/react-table";
|
||||||
import { memo, useEffect, useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { useElementSize } from "usehooks-ts";
|
import { useElementSize } from "usehooks-ts";
|
||||||
import { useChartContext } from "./ChartProvider";
|
import { useChartContext } from "./ChartProvider";
|
||||||
import { ChevronDown, ChevronUp } from "lucide-react";
|
import { ChevronDown, ChevronUp } from "lucide-react";
|
||||||
import { cn } from "@/utils/cn";
|
import { cn } from "@/utils/cn";
|
||||||
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||||
|
|
||||||
const columnHelper =
|
const columnHelper =
|
||||||
createColumnHelper<RouterOutputs["chart"]["chart"]["series"][number]>();
|
createColumnHelper<RouterOutputs["chart"]["chart"]["series"][number]>();
|
||||||
@@ -109,7 +110,7 @@ export function ReportBarChart({ data }: ReportBarChartProps) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<table
|
<Table
|
||||||
{...{
|
{...{
|
||||||
className: editMode ? '' : 'mini',
|
className: editMode ? '' : 'mini',
|
||||||
style: {
|
style: {
|
||||||
@@ -117,11 +118,11 @@ export function ReportBarChart({ data }: ReportBarChartProps) {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<thead>
|
<TableHeader>
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<tr key={headerGroup.id}>
|
<TableRow key={headerGroup.id}>
|
||||||
{headerGroup.headers.map((header) => (
|
{headerGroup.headers.map((header) => (
|
||||||
<th
|
<TableHead
|
||||||
key={header.id}
|
key={header.id}
|
||||||
{...{
|
{...{
|
||||||
colSpan: header.colSpan,
|
colSpan: header.colSpan,
|
||||||
@@ -161,16 +162,16 @@ export function ReportBarChart({ data }: ReportBarChartProps) {
|
|||||||
}
|
}
|
||||||
: {})}
|
: {})}
|
||||||
/>
|
/>
|
||||||
</th>
|
</TableHead>
|
||||||
))}
|
))}
|
||||||
</tr>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</thead>
|
</TableHeader>
|
||||||
<tbody>
|
<TableBody>
|
||||||
{table.getRowModel().rows.map((row) => (
|
{table.getRowModel().rows.map((row) => (
|
||||||
<tr key={row.id}>
|
<TableRow key={row.id}>
|
||||||
{row.getVisibleCells().map((cell) => (
|
{row.getVisibleCells().map((cell) => (
|
||||||
<td
|
<TableCell
|
||||||
key={cell.id}
|
key={cell.id}
|
||||||
{...{
|
{...{
|
||||||
style: {
|
style: {
|
||||||
@@ -179,12 +180,12 @@ export function ReportBarChart({ data }: ReportBarChartProps) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
</td>
|
</TableCell>
|
||||||
))}
|
))}
|
||||||
</tr>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</TableBody>
|
||||||
</table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const Avatar = React.forwardRef<
|
|||||||
<AvatarPrimitive.Root
|
<AvatarPrimitive.Root
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
|
"relative flex h-9 w-9 shrink-0 overflow-hidden rounded-full",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -37,7 +37,7 @@ const AvatarFallback = React.forwardRef<
|
|||||||
<AvatarPrimitive.Fallback
|
<AvatarPrimitive.Fallback
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-full w-full items-center justify-center rounded-full bg-muted",
|
"flex h-full w-full items-center justify-center rounded-full bg-primary text-white",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -45,4 +45,4 @@ const AvatarFallback = React.forwardRef<
|
|||||||
))
|
))
|
||||||
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
|
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
|
||||||
|
|
||||||
export { Avatar, AvatarImage, AvatarFallback }
|
export { Avatar, AvatarImage, AvatarFallback }
|
||||||
@@ -6,13 +6,16 @@ const Table = React.forwardRef<
|
|||||||
HTMLTableElement,
|
HTMLTableElement,
|
||||||
React.HTMLAttributes<HTMLTableElement>
|
React.HTMLAttributes<HTMLTableElement>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<div className="relative w-full overflow-auto">
|
<div className="border border-border rounded-md">
|
||||||
|
<div className="relative w-full overflow-auto ">
|
||||||
<table
|
<table
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("w-full caption-bottom text-sm", className)}
|
className={cn("w-full caption-bottom text-sm [&.mini]:text-xs", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
))
|
))
|
||||||
Table.displayName = "Table"
|
Table.displayName = "Table"
|
||||||
|
|
||||||
@@ -70,7 +73,7 @@ const TableHead = React.forwardRef<
|
|||||||
<th
|
<th
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
"p-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 shadow-[0_0_0_0.5px] shadow-border [.mini_&]:p-2",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -84,7 +87,7 @@ const TableCell = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<td
|
<td
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0 shadow-[0_0_0_0.5px] shadow-border [.mini_&]:p-2", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { Chart } from "@/components/report/chart";
|
|||||||
import { timeRanges } from "@/utils/constants";
|
import { timeRanges } from "@/utils/constants";
|
||||||
import { type IChartRange } from "@/types";
|
import { type IChartRange } from "@/types";
|
||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
import { cn } from "@/utils/cn";
|
|
||||||
import { getRangeLabel } from "@/utils/getRangeLabel";
|
import { getRangeLabel } from "@/utils/getRangeLabel";
|
||||||
|
|
||||||
export const getServerSideProps = createServerSideProps();
|
export const getServerSideProps = createServerSideProps();
|
||||||
|
|||||||
109
apps/web/src/pages/[organization]/[project]/events.tsx
Normal file
109
apps/web/src/pages/[organization]/[project]/events.tsx
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import { Container } from "@/components/Container";
|
||||||
|
import { DataTable } from "@/components/DataTable";
|
||||||
|
import { PageTitle } from "@/components/PageTitle";
|
||||||
|
import { Pagination, usePagination } from "@/components/Pagination";
|
||||||
|
import { MainLayout } from "@/components/layouts/MainLayout";
|
||||||
|
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||||
|
import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table";
|
||||||
|
import { useOrganizationParams } from "@/hooks/useOrganizationParams";
|
||||||
|
import { type RouterOutputs, api } from "@/utils/api";
|
||||||
|
import { formatDateTime } from "@/utils/date";
|
||||||
|
import { toDots } from "@/utils/object";
|
||||||
|
import { AvatarImage } from "@radix-ui/react-avatar";
|
||||||
|
import { createColumnHelper } from "@tanstack/react-table";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
const columnHelper =
|
||||||
|
createColumnHelper<RouterOutputs["event"]["list"][number]>();
|
||||||
|
|
||||||
|
export default function Events() {
|
||||||
|
const pagination = usePagination();
|
||||||
|
const params = useOrganizationParams();
|
||||||
|
const eventsQuery = api.event.list.useQuery(
|
||||||
|
{
|
||||||
|
projectSlug: params.project,
|
||||||
|
...pagination,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
keepPreviousData: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const events = useMemo(() => eventsQuery.data ?? [], [eventsQuery]);
|
||||||
|
const columns = useMemo(() => {
|
||||||
|
return [
|
||||||
|
columnHelper.accessor((row) => row.createdAt, {
|
||||||
|
id: "createdAt",
|
||||||
|
header: () => "Created At",
|
||||||
|
cell(info) {
|
||||||
|
return formatDateTime(info.getValue());
|
||||||
|
},
|
||||||
|
footer: () => "Created At",
|
||||||
|
}),
|
||||||
|
columnHelper.accessor((row) => row.name, {
|
||||||
|
id: "event",
|
||||||
|
header: () => "Event",
|
||||||
|
cell(info) {
|
||||||
|
return <span className="font-medium">{info.getValue()}</span>;
|
||||||
|
},
|
||||||
|
footer: () => "Created At",
|
||||||
|
}),
|
||||||
|
columnHelper.accessor((row) => row.profile, {
|
||||||
|
id: "profile",
|
||||||
|
header: () => "Profile",
|
||||||
|
cell(info) {
|
||||||
|
const profile = info.getValue();
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Avatar className="h-6 w-6">
|
||||||
|
{profile?.avatar && <AvatarImage src={profile.avatar} />}
|
||||||
|
<AvatarFallback className="text-xs">
|
||||||
|
{profile?.first_name?.at(0)}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
{`${profile?.first_name} ${profile?.last_name ?? ""}`}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
footer: () => "Created At",
|
||||||
|
}),
|
||||||
|
columnHelper.accessor((row) => row.properties, {
|
||||||
|
id: "properties",
|
||||||
|
header: () => "Properties",
|
||||||
|
cell(info) {
|
||||||
|
const dots = toDots(info.getValue() as Record<string, any>);
|
||||||
|
return (
|
||||||
|
<Table className="mini">
|
||||||
|
<TableBody>
|
||||||
|
{Object.keys(dots).map((key) => {
|
||||||
|
return (
|
||||||
|
<TableRow key={key}>
|
||||||
|
<TableCell className="font-medium">{key}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{typeof dots[key] === "boolean"
|
||||||
|
? dots[key]
|
||||||
|
? "true"
|
||||||
|
: "false"
|
||||||
|
: dots[key]}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
footer: () => "Created At",
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<MainLayout>
|
||||||
|
<Container>
|
||||||
|
<PageTitle>Events</PageTitle>
|
||||||
|
<Pagination {...pagination} />
|
||||||
|
<DataTable data={events} columns={columns}></DataTable>
|
||||||
|
<Pagination {...pagination} />
|
||||||
|
</Container>
|
||||||
|
</MainLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ export default function Home() {
|
|||||||
return (
|
return (
|
||||||
<MainLayout>
|
<MainLayout>
|
||||||
<Container>
|
<Container>
|
||||||
<PageTitle>Reports</PageTitle>
|
<PageTitle>Projects</PageTitle>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
{projects.map((item) => (
|
{projects.map((item) => (
|
||||||
<Card key={item.id}>
|
<Card key={item.id}>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { userRouter } from "./routers/user";
|
|||||||
import { projectRouter } from "./routers/project";
|
import { projectRouter } from "./routers/project";
|
||||||
import { clientRouter } from "./routers/client";
|
import { clientRouter } from "./routers/client";
|
||||||
import { dashboardRouter } from "./routers/dashboard";
|
import { dashboardRouter } from "./routers/dashboard";
|
||||||
|
import { eventRouter } from "./routers/event";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the primary router for your server.
|
* This is the primary router for your server.
|
||||||
@@ -20,6 +21,7 @@ export const appRouter = createTRPCRouter({
|
|||||||
user: userRouter,
|
user: userRouter,
|
||||||
project: projectRouter,
|
project: projectRouter,
|
||||||
client: clientRouter,
|
client: clientRouter,
|
||||||
|
event: eventRouter,
|
||||||
});
|
});
|
||||||
|
|
||||||
// export type definition of API
|
// export type definition of API
|
||||||
|
|||||||
41
apps/web/src/server/api/routers/event.ts
Normal file
41
apps/web/src/server/api/routers/event.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
||||||
|
import { db } from "@/server/db";
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
api: {
|
||||||
|
responseLimit: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const eventRouter = createTRPCRouter({
|
||||||
|
list: protectedProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
cursor: z.string().optional(),
|
||||||
|
projectSlug: z.string(),
|
||||||
|
take: z.number().default(100),
|
||||||
|
skip: z.number().default(0),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.query(async ({ input: { take, skip, projectSlug } }) => {
|
||||||
|
const project = await db.project.findUniqueOrThrow({
|
||||||
|
where: {
|
||||||
|
slug: projectSlug,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return db.event.findMany({
|
||||||
|
take,
|
||||||
|
skip,
|
||||||
|
where: {
|
||||||
|
project_id: project.id,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: "desc",
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
profile: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
});
|
||||||
@@ -120,24 +120,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
|
||||||
@apply w-fit border border-border;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.mini {
|
|
||||||
@apply text-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
th,
|
|
||||||
td {
|
|
||||||
@apply border border-border px-4 py-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
/* relative is for resizing */
|
|
||||||
@apply relative text-left font-medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resizer {
|
.resizer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
|||||||
@@ -10,13 +10,23 @@ export function dateDifferanceInDays(date1: Date, date2: Date) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getLocale() {
|
export function getLocale() {
|
||||||
if(typeof navigator === 'undefined') {
|
if (typeof navigator === "undefined") {
|
||||||
return 'en-US'
|
return "en-US";
|
||||||
}
|
}
|
||||||
|
|
||||||
return navigator.language ?? 'en-US';
|
return navigator.language ?? "en-US";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatDate(date: Date) {
|
export function formatDate(date: Date) {
|
||||||
return new Intl.DateTimeFormat(getLocale()).format(date)
|
return new Intl.DateTimeFormat(getLocale()).format(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatDateTime(date: Date) {
|
||||||
|
return new Intl.DateTimeFormat(getLocale(), {
|
||||||
|
day: "numeric",
|
||||||
|
month: "numeric",
|
||||||
|
year: "numeric",
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "numeric",
|
||||||
|
}).format(date);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user