basic frontend #3
30
frontend/package-lock.json
generated
30
frontend/package-lock.json
generated
@@ -9,8 +9,6 @@
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@types/node": "^16.18.0",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"autoprefixer": "^10.4.0",
|
||||
"axios": "^1.6.0",
|
||||
"date-fns": "^2.30.0",
|
||||
@@ -26,7 +24,9 @@
|
||||
"typescript": "^4.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.5.0"
|
||||
"@types/jest": "^27.5.0",
|
||||
"@types/react": "^19.2.0",
|
||||
"@types/react-dom": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@alloc/quick-lru": {
|
||||
@@ -3703,12 +3703,6 @@
|
||||
"integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/q": {
|
||||
"version": "1.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.8.tgz",
|
||||
@@ -3728,22 +3722,23 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.25.tgz",
|
||||
"integrity": "sha512-oSVZmGtDPmRZtVDqvdKUi/qgCsWp5IDY29wp8na8Bj4B3cc99hfNzvNhlMkVVxctkAOGUA3Km7MMpBHAnWfcIA==",
|
||||
"version": "19.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.0.tgz",
|
||||
"integrity": "sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "18.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
|
||||
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
|
||||
"version": "19.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.0.tgz",
|
||||
"integrity": "sha512-brtBs0MnE9SMx7px208g39lRmC5uHZs96caOJfTjFcYSLHNamvaSMfJNagChVNkup2SdtOxKX1FDBkRSJe1ZAg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.0.0"
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/resolve": {
|
||||
@@ -6348,6 +6343,7 @@
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/damerau-levenshtein": {
|
||||
|
||||
@@ -4,21 +4,19 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@types/node": "^16.18.0",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.8.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"typescript": "^4.9.0",
|
||||
"autoprefixer": "^10.4.0",
|
||||
"axios": "^1.6.0",
|
||||
"react-hook-form": "^7.48.0",
|
||||
"react-query": "^3.39.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"lucide-react": "^0.294.0",
|
||||
"postcss": "^8.4.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.48.0",
|
||||
"react-query": "^3.39.0",
|
||||
"react-router-dom": "^6.8.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"tailwindcss": "^3.3.0",
|
||||
"autoprefixer": "^10.4.0",
|
||||
"postcss": "^8.4.0"
|
||||
"typescript": "^4.9.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
@@ -45,7 +43,9 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.5.0"
|
||||
"@types/jest": "^27.5.0",
|
||||
"@types/react": "^19.2.0",
|
||||
"@types/react-dom": "^19.2.0"
|
||||
},
|
||||
"proxy": "http://localhost:8080"
|
||||
}
|
||||
|
||||
@@ -222,13 +222,13 @@ export const CounterDetail: React.FC = () => {
|
||||
Recent Entries
|
||||
</h2>
|
||||
|
||||
{entries.length === 0 ? (
|
||||
{(entries?.length || 0) === 0 ? (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
No entries yet. Start by incrementing your counter!
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{entries.slice(0, 10).map((entry) => (
|
||||
{entries?.slice(0, 10).map((entry) => (
|
||||
<div
|
||||
key={entry.id}
|
||||
className="flex justify-between items-center py-2 px-4 bg-gray-50 rounded-lg"
|
||||
|
||||
@@ -17,9 +17,9 @@ export const Dashboard: React.FC = () => {
|
||||
searchCounters(query);
|
||||
};
|
||||
|
||||
const totalCounters = counters.length;
|
||||
const totalValue = counters.reduce((sum, counter) => sum + counter.total_value, 0);
|
||||
const todayValue = counters.reduce((sum, counter) => sum + counter.today_value, 0);
|
||||
const totalCounters = counters?.length || 0;
|
||||
const totalValue = counters?.reduce((sum, counter) => sum + counter.total_value, 0) || 0;
|
||||
const todayValue = counters?.reduce((sum, counter) => sum + counter.today_value, 0) || 0;
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
@@ -109,7 +109,7 @@ export const Dashboard: React.FC = () => {
|
||||
<div className="flex justify-center items-center py-12">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600"></div>
|
||||
</div>
|
||||
) : counters.length === 0 ? (
|
||||
) : (counters?.length || 0) === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<TrendingUp className="mx-auto h-12 w-12 text-gray-400" />
|
||||
<h3 className="mt-2 text-sm font-medium text-gray-900">No counters</h3>
|
||||
@@ -128,7 +128,7 @@ export const Dashboard: React.FC = () => {
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{counters.map((counter) => (
|
||||
{counters?.map((counter) => (
|
||||
<CounterCard key={counter.id} counter={counter} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -110,7 +110,7 @@ export const useCounters = () => {
|
||||
} else {
|
||||
// Increment anonymous counter
|
||||
const today = localStorageService.getTodayString();
|
||||
const counter = anonymousCounters.find(c => c.id === id);
|
||||
const counter = anonymousCounters.find((c: AnonymousCounter) => c.id === id);
|
||||
if (counter) {
|
||||
const newTotal = counter.total_value + value;
|
||||
const newEntries = { ...counter.entries };
|
||||
@@ -146,9 +146,9 @@ export const useCounters = () => {
|
||||
// Convert anonymous counters to CounterWithStats format for consistent UI
|
||||
const getDisplayCounters = (): CounterWithStats[] => {
|
||||
if (isAuthenticated) {
|
||||
return counters;
|
||||
return counters || [];
|
||||
} else {
|
||||
return anonymousCounters.map(counter => ({
|
||||
return (anonymousCounters || []).map((counter: AnonymousCounter) => ({
|
||||
id: parseInt(counter.id), // Convert string ID to number for display
|
||||
name: counter.name,
|
||||
description: counter.description,
|
||||
@@ -156,23 +156,23 @@ export const useCounters = () => {
|
||||
updated_at: counter.updated_at,
|
||||
total_value: counter.total_value,
|
||||
today_value: counter.entries[localStorageService.getTodayString()] || 0,
|
||||
week_value: Object.entries(counter.entries)
|
||||
.filter(([date]) => {
|
||||
week_value: Object.keys(counter.entries)
|
||||
.filter((date) => {
|
||||
const entryDate = new Date(date);
|
||||
const weekAgo = new Date();
|
||||
weekAgo.setDate(weekAgo.getDate() - 7);
|
||||
return entryDate >= weekAgo;
|
||||
})
|
||||
.reduce((sum, [, value]) => sum + (value as number), 0),
|
||||
month_value: Object.entries(counter.entries)
|
||||
.filter(([date]) => {
|
||||
.reduce((sum, date) => sum + (counter.entries[date] as number), 0),
|
||||
month_value: Object.keys(counter.entries)
|
||||
.filter((date) => {
|
||||
const entryDate = new Date(date);
|
||||
const monthAgo = new Date();
|
||||
monthAgo.setMonth(monthAgo.getMonth() - 1);
|
||||
return entryDate >= monthAgo;
|
||||
})
|
||||
.reduce((sum, [, value]) => sum + (value as number), 0),
|
||||
entry_count: Object.values(counter.entries).reduce((sum: number, value: unknown) => sum + Math.abs(value as number), 0),
|
||||
.reduce((sum, date) => sum + (counter.entries[date] as number), 0),
|
||||
entry_count: Object.keys(counter.entries).reduce((sum: number, date: string) => sum + Math.abs(counter.entries[date] as number), 0),
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user