Skip to content

Commit

Permalink
Merge pull request #22 from LabGraphTeam/refactoring/analytics_table
Browse files Browse the repository at this point in the history
feat: update Docker setup and add analytics pagination components
  • Loading branch information
LeonardoMeireles55 authored Jan 20, 2025
2 parents 0e0cb46 + 8f60db8 commit be687d5
Show file tree
Hide file tree
Showing 13 changed files with 468 additions and 313 deletions.
63 changes: 45 additions & 18 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,30 +1,57 @@
# Etapa 1: Build
FROM node:20-alpine AS builder
# syntax=docker.io/docker/dockerfile:1

FROM node:20-alpine AS base

# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app

# Copie apenas os arquivos necessários para instalar dependências
COPY package*.json ./
# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi

# Instale dependências apenas uma vez e use cache do Docker
RUN npm ci

# Copie os arquivos do projeto
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Execute o build da aplicação
RUN npm run build
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED=1

# Etapa 2: Produção
FROM node:20-alpine AS runner
RUN \
if [ -f yarn.lock ]; then yarn run build; \
elif [ -f package-lock.json ]; then npm run build; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
else echo "Lockfile not found." && exit 1; \
fi

# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app

# Copie apenas os arquivos necessários da etapa de build
COPY --from=builder /app/.next /app/.next
COPY --from=builder /app/package*.json /app/
COPY --from=builder /app/public /app/public
ENV NODE_ENV=production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED=1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

# Instale apenas dependências de produção
RUN npm ci --omit=dev
RUN npm prune --production
USER nextjs
23 changes: 23 additions & 0 deletions Dockerfile_OLD
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Etapa 1: Build
FROM node:20-alpine AS builder

WORKDIR /app

COPY package*.json ./

RUN npm ci

COPY . .

RUN npm run build

FROM node:20-alpine AS runner

WORKDIR /app

COPY --from=builder /app/.next /app/.next
COPY --from=builder /app/package*.json /app/
COPY --from=builder /app/public /app/public

RUN npm ci --omit=dev
RUN npm prune --production
3 changes: 2 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ services:
- qualitylab_public:/app/public
stdin_open: true
tty: true
command: ['npm', 'start']
# command: ['npm', 'start']
command: ["node", "server.js"]
networks:
- qualitylab-net

Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"build": "next build && cp -r public .next/standalone/ && cp -r .next/static .next/standalone/.next/",
"start": "node .next/standalone/server.js",
"start-old": "next start",
"lint": "next lint"
},
"dependencies": {
Expand Down
File renamed without changes.
137 changes: 137 additions & 0 deletions src/components/analytics-table/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import ListingTable from '@/components/analytics-table/listing-table';
import useDateSelector from '@/components/shared/date-selector/hooks/useDateSelector';
import Footer from '@/components/ui/footer';
import NavBar from '@/components/ui/navigation-bar';
import Head from 'next/head';
import { useEffect, useState } from 'react';
import { useAnalyticsData } from '@/components/analytics-table/hooks/useAnalyticsData';
import useWindowDimensions from '@/components/ui/hooks/useWindowDimensions';
import AnalyticsFilters from './util/analytics-filters';
import AnalyticsPagination from './util/analytics-pagination';

const AnalyticsTableIndex = () => {
const dateSelector = useDateSelector();
const [currentPage, setCurrentPage] = useState(0);
const [itemsPerPage, setItemsPerPage] = useState(7);
const [analyticsType, setAnalyticsType] = useState('biochemistry-analytics');
const [level, setLevel] = useState('0');
const [isFiltered, setFiltered] = useState(false);


const { width } = useWindowDimensions();



const {
data: dataFetched,
links: _links,
isLoading,
error,
fetchData,
buildUrl,
totalPages,
} = useAnalyticsData({
analyticsType,
level,
startDate: {
day: dateSelector.startDay,
month: dateSelector.startMonth,
year: dateSelector.startYear,
},
endDate: {
day: dateSelector.endDay,
month: dateSelector.endMonth,
year: dateSelector.endYear,
},
itemsPerPage,
currentPage,
});

useEffect(() => {
const url = buildUrl(isFiltered);
fetchData(url);
}, [
isFiltered,
analyticsType,
level,
itemsPerPage,
currentPage,
dateSelector.startDay,
dateSelector.startMonth,
dateSelector.startYear,
dateSelector.endDay,
dateSelector.endMonth,
dateSelector.endYear,
]);

const handlePageChange = async (url: string): Promise<void> => {
await fetchData(url);
};

useEffect(() => {
setItemsPerPage(width >= 1800 ? 12 : 8);
console.log(width);
}, [fetchData, width]);

const analyticsOptions = [
{ value: 'biochemistry-analytics', label: 'BIOCHEMISTRY' },
{ value: 'hematology-analytics', label: 'HEMATOLOGY' },
{ value: 'coagulation-analytics', label: 'COAGULATION' },
];

const levelOptions = [
{ value: '0', label: '-' },
{ value: '1', label: '1' },
{ value: '2', label: '2' },
{ value: '3', label: '3' },
]

return (
<div className='min-h bg-background'>
<div className='min-h flex flex-col content-center items-center justify-center'>
<Head>
<title>{`LabGraph - ${analyticsType || 'Quality-Lab-Pro'}`}</title>
</Head>
<NavBar jsonData={dataFetched} fileName={analyticsType} />
<div className='w-full max-w-7xl'>
<AnalyticsFilters
dateSelector={dateSelector}
analyticsOptions={analyticsOptions}
analyticsType={analyticsType}
setAnalyticsType={setAnalyticsType}
levelOptions={levelOptions}
level={level}
setLevel={setLevel}
setFiltered={setFiltered}
/>
{isLoading ? (
<div className='flex content-center items-center justify-center'>
<div className='h-10 w-10 animate-spin rounded-full border-t-2 border-primary'></div>
</div>
) : error ? (
<div className='mtrelative rounded bg-danger px-4 py-3 text-white' role='alert'>
{error}
</div>
) : (
<ListingTable
items={dataFetched}
pageInfos={_links}
onPageChange={handlePageChange}
/>
)}
</div>
<AnalyticsPagination
currentPage={currentPage}
totalPages={totalPages}
dataFetched={dataFetched}
setCurrentPage={setCurrentPage}
/>
<div className='flex flex-col items-center justify-end'>
<Footer />
</div>
</div>
</div>
);
};

export default AnalyticsTableIndex;
61 changes: 61 additions & 0 deletions src/components/analytics-table/listing-table/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';
import { ListingTableProps } from '../../features/types/ListiningTable';
import TableRow from './table-row';
import MobileItemCard from './mobile-item-card';

const ListingTable: React.FC<ListingTableProps> = ({ items }) => {
return (
<div className='flex flex-col justify-between w-full h-min rounded-lg shadow-xl'>
<table className='hidden bg-background md:table'>
<thead className='bg-muted rounded-lg'>
<tr>
<th className='border-b border-border px-2 py-1 text-left text-[10px] font-semibold uppercase tracking-wider text-textSecondary md:text-xs'>
Date
</th>
<th className='border-b border-border px-2 py-1 text-left text-[10px] font-semibold uppercase tracking-wider text-textSecondary md:text-xs'>
Test
</th>
<th className='border-b border-border px-2 py-1 text-left text-[10px] font-semibold uppercase tracking-wider text-textSecondary md:text-xs'>
Level
</th>
<th className='border-b border-border px-2 py-1 text-left text-[10px] font-semibold uppercase tracking-wider text-textSecondary md:text-xs'>
Lot
</th>
<th className='border-b border-border px-2 py-1 text-left text-[10px] font-semibold uppercase tracking-wider text-textSecondary md:text-xs'>
Standard Deviation
</th>
<th className='border-b border-border px-2 py-1 text-left text-[10px] font-semibold uppercase tracking-wider text-textSecondary md:text-xs'>
Mean
</th>
<th className='border-b border-border px-2 py-1 text-left text-[10px] font-semibold uppercase tracking-wider text-textSecondary md:text-xs'>
Values
</th>
<th className='border-b border-border px-2 py-1 text-left text-[10px] font-semibold uppercase tracking-wider text-textSecondary md:text-xs'>
Unit
</th>
<th className='border-b border-border px-2 py-1 text-left text-[10px] font-semibold uppercase tracking-wider text-textSecondary md:text-xs'>
Rules
</th>
</tr>
</thead>
<tbody>
{items.map((item, index) => (
<TableRow key={index} item={item} />
))}
</tbody>
</table>

<div className='grid content-center justify-center grid-cols-4 px-2 text-center md:hidden'>
{items.map((item, index) => (
<MobileItemCard key={index} item={item} />
))}
</div>

{items.length === 0 && (
<div className='py-2 text-center bg-background text-muted'>No items to display</div>
)}
</div>
);
};

export default ListingTable;
40 changes: 40 additions & 0 deletions src/components/analytics-table/listing-table/mobile-item-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

import React from 'react';
import { ListingItem } from '@/components/charts/types/Chart';

interface MobileItemCardProps {
item: ListingItem;
}

const MobileItemCard: React.FC<MobileItemCardProps> = ({ item }) => {
return (
<div className='p-2 border rounded-md shadow-md border-border bg-surface'>
<p className='text-[6px] font-semibold text-textSecondary'>
Test: <span className='text-textPrimary'>{item.name}</span>
</p>
<p className='text-[6px] font-semibold text-textSecondary'>
Level: <span className='text-textPrimary'>{item.level}</span>
</p>
<p className='text-[6px] font-semibold text-textSecondary'>
Standard Deviation: <span className='text-textPrimary'>{item.sd.toFixed(2)}</span>
</p>
<p className='text-[6px] font-semibold text-textSecondary'>
Mean: <span className='text-textPrimary'>{item.mean.toFixed(2)}</span>
</p>
<p className='text-[6px] font-semibold text-textSecondary'>
Date: <span className='text-textPrimary'>{item.date}</span>
</p>
<p className='text-[6px] font-semibold text-textSecondary'>
Values: <span className='text-textPrimary'>{item.value.toFixed(2)}</span>
</p>
<p className='text-[6px] font-semibold text-textSecondary'>
Unit: <span className='text-textPrimary'>{item.unit_value}</span>
</p>
<p className='text-[6px] font-semibold text-textSecondary'>
Rules: <span className='text-textPrimary'>{item.rules}</span>
</p>
</div>
);
};

export default MobileItemCard;
Loading

0 comments on commit be687d5

Please sign in to comment.