Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/components/landing/header/header.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Link } from 'react-router'
// import { ModeToggle } from "../../mode-toggle";
import LanguageSelector from './language-selector'
import { useTranslation } from 'react-i18next'

const links = [
{
Expand Down
131 changes: 83 additions & 48 deletions app/components/mydevices/dt/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export type SenseBox = {
name: string
exposure: Device['exposure']
createdAt: Date
archivedAt: Date | null
// model: string;
}

Expand All @@ -31,20 +32,37 @@ export function getColumns(
const { t } = useTranslation
const isOwner = opts?.isOwner ?? false
return [
{
{
accessorKey: 'name',
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
className={colStyle}
>
{t('name')}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
)
},
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
className={colStyle}
>
{t('name')}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
)
},
cell: ({ row }) => {
const device = row.original
const isArchived = !!device.archivedAt

return (
<div className="flex items-center gap-2">
<span className={isArchived ? 'text-muted-foreground line-through opacity-70' : ''}>
{device.name}
</span>
{isArchived ? (
<span className="rounded-md border px-2 py-0.5 text-xs text-muted-foreground">
{t('archived')}
</span>
) : null}
</div>
)
},
},
{
accessorKey: 'createdAt',
Expand Down Expand Up @@ -102,16 +120,15 @@ export function getColumns(
<div className="pl-0 dark:text-white">{t('sensebox_id')}</div>
),
cell: ({ row }) => {
const senseBox = row.original
const device = row.original

return (
// <div className="text-right font-medium">
<div className="flex items-center">
<code className="rounded-sm bg-[#f9f2f4] px-1 py-[2px] text-[#c7254e]">
{senseBox?.id}
{device?.id}
</code>
<ClipboardCopy
onClick={() => navigator.clipboard.writeText(senseBox?.id)}
onClick={() => navigator.clipboard.writeText(device?.id)}
className="ml-[6px] mr-1 inline-block h-4 w-4 cursor-pointer align-text-bottom text-[#818a91] dark:text-white"
/>
</div>
Expand All @@ -124,7 +141,8 @@ export function getColumns(
<div className="text-center dark:text-white">{t('actions')}</div>
),
cell: ({ row }) => {
const senseBox = row.original
const device = row.original
const isArchived = !!device.archivedAt

return (
<DropdownMenu>
Expand All @@ -134,49 +152,66 @@ export function getColumns(
<Ellipsis className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>

<DropdownMenuContent
align="end"
className="dark:bg-dark-background dark:text-dark-text"
>
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuLabel>{t('actions')}</DropdownMenuLabel>
<DropdownMenuSeparator />

<DropdownMenuItem asChild>
<a href={`/device/${senseBox.id}/overview`}>{t('overview')}</a>
<a href={`/device/${device.id}/overview`}>{t('overview')}</a>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<a href={`/explore/${senseBox.id}`}>{t('show_on_map')}</a>

<DropdownMenuItem disabled={isArchived} asChild>
<a href={`/explore/${device.id}`}>{t('show_on_map')}</a>
</DropdownMenuItem>

{isOwner ? (
<>
<DropdownMenuItem asChild>
<a href={`/device/${senseBox.id}/edit/general`}>{t('edit')}</a>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<a href={`/device/${senseBox.id}/dataupload`}>
{t('data_upload')}
</a>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<a
href="https://sensebox.de/de/go-home"
target="_blank"
rel="noopener noreferrer"
>
{t('support')}
</a>
</DropdownMenuItem>
<DropdownMenuItem asChild
onClick={() => navigator.clipboard.writeText(senseBox?.id)}
className="cursor-pointer"
>
{t('copy_id')}
</DropdownMenuItem>
</>
): null }
<DropdownMenuItem disabled={isArchived} asChild={!isArchived}>
{isArchived ? (
<span>{t('edit')}</span>
) : (
<a href={`/device/${device.id}/edit/general`}>
{t('edit')}
</a>
)}
</DropdownMenuItem>

<DropdownMenuItem disabled={isArchived} asChild={!isArchived}>
{isArchived ? (
<span>{t('data_upload')}</span>
) : (
<a href={`/device/${device.id}/dataupload`}>
{t('data_upload')}
</a>
)}
</DropdownMenuItem>

<DropdownMenuItem disabled={isArchived} asChild>
<a
href="https://sensebox.de/de/go-home"
target="_blank"
rel="noopener noreferrer"
>
{t('support')}
</a>
</DropdownMenuItem>

<DropdownMenuItem
onClick={() => navigator.clipboard.writeText(device.id)}
className="cursor-pointer"
>
{t('copy_id')}
</DropdownMenuItem>
</>
) : null}
</DropdownMenuContent>
</DropdownMenu>
)
},
},
}
]
}
31 changes: 16 additions & 15 deletions app/components/mydevices/dt/data-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,13 @@ import {
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
getRowClassName?: (row: TData) => string
}

export function DataTable<TData, TValue>({
columns,
data,
getRowClassName,
}: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = React.useState<SortingState>([
{ id: 'createdAt', desc: true },
Expand Down Expand Up @@ -72,8 +74,8 @@ export function DataTable<TData, TValue>({
},
},
})
const tableColsWidth = [30, 30, 30, 40]

const tableColsWidth = [30, 30, 30, 40]
const { t } = useTranslation('data-table')

return (
Expand All @@ -94,27 +96,27 @@ export function DataTable<TData, TValue>({
<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>
)
})}
{headerGroup.headers.map((header) => (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
))}
</TableRow>
))}
</TableHeader>

<TableBody className="dark:text-dark-text">
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && 'selected'}
className={getRowClassName?.(row.original) ?? ''}
>
{row.getVisibleCells().map((cell, index) => (
<TableCell
Expand Down Expand Up @@ -152,7 +154,6 @@ export function DataTable<TData, TValue>({
onValueChange={(value) => {
table.setPageSize(Number(value))
}}
// disabled={isPending}
>
<SelectTrigger className="h-8 w-16 dark:border-dark-text">
<SelectValue />
Expand Down Expand Up @@ -218,4 +219,4 @@ export function DataTable<TData, TValue>({
</div>
</div>
)
}
}
24 changes: 24 additions & 0 deletions app/models/device.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import {
arrayContains,
and,
between,
isNull,
type ExtractTablesWithRelations,
isNotNull,
} from 'drizzle-orm'
import { type PgTransaction } from 'drizzle-orm/pg-core'
import { type PostgresJsQueryResultHKT } from 'drizzle-orm/postgres-js'
Expand Down Expand Up @@ -47,6 +49,7 @@ const BASE_DEVICE_COLUMNS = {
status: true,
createdAt: true,
updatedAt: true,
archivedAt: true,
expiresAt: true,
useAuth: true,
apiKey: true,
Expand Down Expand Up @@ -433,6 +436,7 @@ export async function getDevices(

export async function getDevices(format: DevicesFormat = 'json') {
const devices = await drizzleClient.query.device.findMany({
where: (device) => isNull(device.archivedAt),
columns: {
id: true,
name: true,
Expand Down Expand Up @@ -463,6 +467,24 @@ export async function getDevices(format: DevicesFormat = 'json') {
return devices
}

export async function getArchivedDevices(){
const devices = await drizzleClient.query.device.findMany({
where: (device) => isNotNull(device.archivedAt),
columns: {
id: true,
name: true,
latitude: true,
longitude: true,
exposure: true,
status: true,
createdAt: true,
tags: true,
archivedAt: true
},
})
return devices
}

export async function getDevicesWithSensors() {
const rows = await drizzleClient
.select({
Expand All @@ -476,6 +498,8 @@ export async function getDevicesWithSensors() {
})
.from(device)
.leftJoin(sensor, eq(sensor.deviceId, device.id))
.where(isNull(device.archivedAt))

const geojson: GeoJSON.FeatureCollection<Point, any> = {
type: 'FeatureCollection',
features: [],
Expand Down
5 changes: 5 additions & 0 deletions app/routes/profile.$username.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@ export default function ProfilePage() {
<DataTable
columns={getColumns(columnsTranslation, { isOwner })}
data={profile.user.devices}
getRowClassName={(device) =>
device.archivedAt
? 'opacity-60 bg-slate-100 dark:bg-slate-900/40'
: ''
}
/>
)}
</div>
Expand Down
1 change: 1 addition & 0 deletions app/schema/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const device = pgTable('device', {
public: boolean('public').default(false),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
archivedAt: timestamp('archived_at'),
expiresAt: date('expires_at', { mode: 'date' }),
latitude: doublePrecision('latitude').notNull(),
longitude: doublePrecision('longitude').notNull(),
Expand Down
1 change: 1 addition & 0 deletions drizzle/0034_mute_ultragirl.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE "device" ADD COLUMN "archived_at" timestamp;
23 changes: 23 additions & 0 deletions drizzle/0035_handy_rawhide_kid.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
CREATE OR REPLACE PROCEDURE archive_inactive_devices()
LANGUAGE SQL
AS $$
UPDATE device d
SET
archived_at = now(),
updated_at = now()
WHERE d.archived_at IS NULL
AND d.created_at < now() - interval '12 months'
AND NOT EXISTS (
SELECT 1
FROM sensor s
JOIN measurement m ON m.sensor_id = s.id
WHERE s.device_id = d.id
AND m.time >= now() - interval '12 months'
);
$$;

SELECT cron.schedule(
'device-archive-inactive',
'0 2 * * *',
'CALL archive_inactive_devices()'
);
Loading
Loading