Peace 3 weeks ago
parent 99a2893d51
commit 726d1f9ac3
  1. 2
      backend/dist/tsconfig.build.tsbuildinfo
  2. 1
      backend/src/sensors/sensors.controller.ts
  3. 1
      web/next.config.ts
  4. 2
      web/src/app/layout.tsx
  5. 4
      web/src/app/login/page.tsx
  6. 48
      web/src/app/me/page.tsx
  7. 2
      web/src/app/page.tsx
  8. 84
      web/src/app/sensor-groups/page.tsx
  9. 74
      web/src/app/sensor-groups/sensor/innerPage.tsx
  10. 12
      web/src/app/sensor-groups/sensor/page.tsx
  11. 20
      web/src/app/sensor-groups/sensors/innerPage.tsx
  12. 12
      web/src/app/sensor-groups/sensors/page.tsx
  13. 21
      web/src/app/sensors/[sensorId]/page.tsx
  14. 4
      web/src/app/shadcn/alert-dialog/page.tsx
  15. 4
      web/src/app/shadcn/badge/page.tsx
  16. 4
      web/src/app/shadcn/calendar/page.tsx
  17. 4
      web/src/app/signup/page.tsx
  18. 4
      web/src/components/app-header.tsx
  19. 2
      web/src/hooks/useSensorData.ts

File diff suppressed because one or more lines are too long

@ -12,7 +12,6 @@ import {
} from '@nestjs/common';
import { SensorsService } from './sensors.service';
import { ApiBearerAuth, ApiOkResponse, ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
import { Sensor } from './entities/sensor.entity';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';
import { SensorGroupResponseDto } from './dto/sensor-group-response.dto';
import { AuthRequest } from 'src/common/interfaces/auth-request.interface';

@ -2,6 +2,7 @@ import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
/* config options here */
output: 'export',
};
export default nextConfig;

@ -19,7 +19,7 @@ export default function RootLayout({
<Providers>
<div className="flex min-h-screen flex-col">
<AppHeader />
<main className="flex flex-1 flex-col items-center justify-center">{children}</main>
<main className="flex w-full flex-1 flex-col px-6 py-8">{children}</main>
</div>
</Providers>
</body>

@ -70,8 +70,8 @@ export default function LoginPage() {
};
return (
<div className="mx-auto my-auto w-full max-w-sm">
<Card className="shadow-lg">
<div className="flex h-full w-full flex-1 flex-col items-center justify-center space-y-10">
<Card className="min-w-sm shadow-lg">
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>

@ -9,31 +9,33 @@ export default function MePage() {
return (
<Guard>
{isLoading && <p> </p>}
{error && <p className="text-red-600"> </p>}
<div className="flex h-full w-full flex-1 flex-col items-center justify-center space-y-10">
{isLoading && <p> </p>}
{error && <p className="text-red-600"> </p>}
{!isLoading && !error && data && (
<Card className="w-full max-w-sm shadow-lg">
<CardHeader className="flex flex-row items-center gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-gray-300 text-xl font-bold text-gray-500">
{data.name?.[0]}
</div>
<div>
<CardTitle>{data.name ?? '이름 없음'}</CardTitle>
<div className="text-xs text-gray-400">ID: {data.id}</div>
</div>
</CardHeader>
<CardContent className="space-y-3">
{data.email && (
<div className="flex items-center gap-2 text-gray-700">
<span className="font-medium"></span>
<span className="truncate">{data.email}</span>
{!isLoading && !error && data && (
<Card className="w-full max-w-sm shadow-lg">
<CardHeader className="flex flex-row items-center gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-gray-300 text-xl font-bold text-gray-500">
{data.name?.[0]}
</div>
<div>
<CardTitle>{data.name ?? '이름 없음'}</CardTitle>
<div className="text-xs text-gray-400">ID: {data.id}</div>
</div>
)}
</CardContent>
</Card>
)}
</CardHeader>
<CardContent className="space-y-3">
{data.email && (
<div className="flex items-center gap-2 text-gray-700">
<span className="font-medium"></span>
<span className="truncate">{data.email}</span>
</div>
)}
</CardContent>
</Card>
)}
</div>
</Guard>
);
}

@ -31,7 +31,7 @@ export default function Home() {
const localNow = timezone ? now.setZone(timezone) : now;
return (
<div className="space-y-10">
<div className="flex h-full w-full flex-1 flex-col items-center justify-center space-y-10">
<Card className="w-full max-w-md">
<CardHeader className="flex justify-center">
<CardTitle className="text-3xl font-bold">DateTime UI Sample</CardTitle>

@ -18,49 +18,53 @@ export default function SensorGroupPage() {
return (
<Guard>
<div className="mb-4 flex items-center justify-between">
<h1 className="text-xl font-semibold"> </h1>
<Button onClick={() => refetch()}></Button>
</div>
<div className="flex h-full flex-col">
<div className="mb-4 flex items-center justify-between">
<h1 className="text-xl font-semibold"> </h1>
<Button onClick={() => refetch()}></Button>
</div>
{isLoading && <p className="text-gray-500"> ...</p>}
{isError && <p className="text-red-600"> .</p>}
{!isLoading && !isError && (
<div className="overflow-x-auto rounded border">
<Table>
<TableHeader>
<TableRow>
<TableHead className="px-4 py-2 text-center">ID</TableHead>
<TableHead className="px-4 py-2 text-center"></TableHead>
<TableHead className="px-4 py-2 text-center"></TableHead>
<TableHead className="px-4 py-2 text-center"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data && data.length > 0 ? (
data.map((g) => (
<TableRow key={g.id}>
<TableCell className="px-4 py-2 text-center">{g.id}</TableCell>
<TableCell className="px-4 py-2 text-center">{g.name}</TableCell>
<TableCell className="px-4 py-2 text-center">{g.description ?? '-'}</TableCell>
<TableCell className="flex items-center justify-center px-4 py-2">
<Button asChild variant="outline">
<Link href={`/sensor-groups/${g.id}`}></Link>
</Button>
</TableCell>
</TableRow>
))
) : (
{isLoading && <p className="text-gray-500"> ...</p>}
{isError && <p className="text-red-600"> .</p>}
{!isLoading && !isError && (
<div className="overflow-x-auto rounded border">
<Table>
<TableHeader>
<TableRow>
<TableCell colSpan={4} className="px-4 py-6 text-center text-gray-500">
.
</TableCell>
<TableHead className="px-4 py-2 text-center">ID</TableHead>
<TableHead className="px-4 py-2 text-center"></TableHead>
<TableHead className="px-4 py-2 text-center"></TableHead>
<TableHead className="px-4 py-2 text-center"></TableHead>
</TableRow>
)}
</TableBody>
</Table>
</div>
)}
</TableHeader>
<TableBody>
{data && data.length > 0 ? (
data.map((g) => (
<TableRow key={g.id}>
<TableCell className="px-4 py-2 text-center">{g.id}</TableCell>
<TableCell className="px-4 py-2 text-center">{g.name}</TableCell>
<TableCell className="px-4 py-2 text-center">
{g.description ?? '-'}
</TableCell>
<TableCell className="flex items-center justify-center px-4 py-2">
<Button asChild variant="outline">
<Link href={`/sensor-groups/sensors?groupId=${g.id}`}></Link>
</Button>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={4} className="px-4 py-6 text-center text-gray-500">
.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
)}
</div>
</Guard>
);
}

@ -0,0 +1,74 @@
'use client';
import Guard from '@/components/guard';
import { Button } from '@/components/ui/button';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { useSensorData } from '@/hooks/useSensorData';
import { useRouter, useSearchParams } from 'next/navigation';
export default function InnerSensorDataPage() {
const searchParams = useSearchParams();
const rawSensorId = searchParams.get('sensorId');
const router = useRouter();
const sensorId = (() => {
if (!rawSensorId) return 0;
const n = Number(rawSensorId);
return Number.isNaN(n) || !Number.isFinite(n) || !Number.isInteger(n) || n <= 0 ? 0 : n;
})();
const { data, isLoading, isError, refetch } = useSensorData(sensorId);
return (
<Guard>
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<Button variant="ghost" onClick={() => router.back()}>
</Button>
<h1 className="text-xl font-semibold">{sensorId} </h1>
</div>
<Button onClick={() => refetch()}></Button>
</div>
{isLoading && <p className="text-gray-500"> ...</p>}
{isError && <p className="text-red-600"> .</p>}
{!isLoading && !isError && (
<div className="overflow-x-auto rounded border">
<Table>
<TableHeader>
<TableRow>
<TableHead className="px-4 py-2 text-center">ID</TableHead>
<TableHead className="px-4 py-2 text-center"></TableHead>
<TableHead className="px-4 py-2 text-center"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data && data.length > 0 ? (
data.map((s) => (
<TableRow key={s.id}>
<TableCell className="px-4 py-2 text-center">{s.id}</TableCell>
<TableCell className="px-4 py-2 text-center">{s.value}</TableCell>
<TableCell className="px-4 py-2 text-center">{s.recordedAt ?? '-'}</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={4} className="px-4 py-6 text-center text-gray-500">
.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
)}
</Guard>
);
}

@ -0,0 +1,12 @@
import { Suspense } from 'react';
import InnerSensorDataPage from './innerPage';
export default function SensorDataPage() {
return (
<Suspense
fallback={<div className="mb-4 flex items-center justify-between"> ...</div>}
>
<InnerSensorDataPage />
</Suspense>
);
}

@ -12,22 +12,30 @@ import {
} from '@/components/ui/table';
import { useGroupSensors } from '@/hooks/useGroupSensor';
import Link from 'next/link';
import { useParams, useRouter } from 'next/navigation';
import { useRouter, useSearchParams } from 'next/navigation';
import { Suspense } from 'react';
export default function GroupSensorsPage() {
const params = useParams();
const groupId = Number(params.groupId);
export default function InnerGroupSensorsPage() {
const searchParams = useSearchParams();
const rawGroupId = searchParams.get('groupId');
const router = useRouter();
const groupId = (() => {
if (!rawGroupId) return 0;
const n = Number(rawGroupId);
return Number.isNaN(n) || !Number.isFinite(n) || !Number.isInteger(n) || n <= 0 ? 0 : n;
})();
const { data, isLoading, isError, refetch } = useGroupSensors(groupId);
return (
<Guard>
<Suspense fallback={<div> ...</div>}></Suspense>
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<Button variant="ghost" onClick={() => router.back()}>
</Button>
<h1 className="text-xl font-semibold"> </h1>
<h1 className="text-xl font-semibold">{groupId} </h1>
</div>
<Button onClick={() => refetch()}></Button>
</div>
@ -54,7 +62,7 @@ export default function GroupSensorsPage() {
<TableCell className="px-4 py-2 text-center">{s.unit ?? '-'}</TableCell>
<TableCell className="flex items-center justify-center px-4 py-2 text-center">
<Button asChild variant="outline">
<Link href={`/sensors/${s.id}`}></Link>
<Link href={`/sensor-groups/sensor?sensorId=${s.id}`}></Link>
</Button>
</TableCell>
</TableRow>

@ -0,0 +1,12 @@
import { Suspense } from 'react';
import InnerGroupSensorsPage from './innerPage';
export default function GroupSensorsPage() {
return (
<Suspense
fallback={<div className="mb-4 flex items-center justify-between"> ...</div>}
>
<InnerGroupSensorsPage />
</Suspense>
);
}

@ -1,21 +0,0 @@
'use client';
import Guard from '@/components/guard';
import { useParams, useRouter, useSearchParams } from 'next/navigation';
export default function SensorDetailPage() {
const params = useParams();
const router = useRouter();
const search = useSearchParams();
const sensorId = Number(params?.sensorId);
const limit = search.get('limit') ? Number(search.get('limit')) : undefined;
return (
<Guard>
<main>
<div></div>
</main>
</Guard>
);
}

@ -26,7 +26,7 @@ export default function AlertDialogPage() {
}
return (
<>
<div className="flex h-full w-full flex-1 flex-col items-center justify-center space-y-10">
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline"> </Button>
@ -45,6 +45,6 @@ export default function AlertDialogPage() {
</AlertDialogContent>
</AlertDialog>
{dialogResult && <p className="mt-4 text-blue-500">{dialogResult}</p>}
</>
</div>
);
}

@ -5,7 +5,7 @@ import { BadgeCheckIcon } from 'lucide-react';
export default function BadgePage() {
return (
<>
<div className="flex h-full w-full flex-1 flex-col items-center justify-center space-y-10">
<div className="flex items-center justify-center space-x-3">
<div className="flex flex-col items-center gap-2 rounded p-3 shadow-lg">
<Badge>Badge</Badge>
@ -26,6 +26,6 @@ export default function BadgePage() {
</Badge>
</div>
</div>
</>
</div>
);
}

@ -9,7 +9,7 @@ export default function CalendarPage() {
const [luxonDate, setLuxonDate] = useState<DateTime | undefined>(DateTime.now());
return (
<>
<div className="flex h-full w-full flex-1 flex-col items-center justify-center space-y-10">
<div className="flex items-center justify-center space-x-4">
<div>
<Calendar
@ -35,6 +35,6 @@ export default function CalendarPage() {
</p>
</div>
</div>
</>
</div>
);
}

@ -73,8 +73,8 @@ export default function SignupPage() {
};
return (
<div className="mx-auto w-full max-w-sm">
<Card className="shadow-lg">
<div className="flex h-full w-full flex-1 flex-col items-center justify-center space-y-10">
<Card className="min-w-sm shadow-lg">
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>

@ -109,12 +109,12 @@ export default function AppHeader() {
<div className="h-4 w-28 animate-pulse rounded bg-white/20" />
</div>
) : (
<div>
<div className="flex items-center gap-2">
<Link href="/me" className="flex items-center gap-2">
<Avatar className="h-8 w-8">
<AvatarFallback>{initial}</AvatarFallback>
</Avatar>
<span className="max-w-[140px] truncate">{displayName}</span>
<span className="max-w-[150px] truncate">{displayName}</span>
</Link>
<Button onClick={onLogout} variant="secondary">

@ -25,7 +25,7 @@ export function useSensorData(sensorId: number, opt?: SensorDataOptions) {
return useQuery({
queryKey: ['sensor-data', sensorId, opt],
queryFn: async (): Promise<SensorDataDto[]> => {
const res = await api.get(`/sensors/sensor/${sensorId}/data`, { params });
const res = await api.get(`/sensors/sensors/${sensorId}/data`, { params });
return res.data;
},
enabled: Number.isFinite(sensorId) && sensorId > 0,

Loading…
Cancel
Save