Compare commits
1 Commits
main
...
feat/impor
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a181e0867 |
@ -50,6 +50,7 @@ pnpm i
|
||||
- Must: DATABASE_URL
|
||||
```bash
|
||||
pnpm db:dev # init schema
|
||||
pnpm prisma migrate deploy # A new database 'splitpro' created
|
||||
```
|
||||
- Option: R2 related
|
||||
|
||||
|
||||
@ -49,6 +49,7 @@
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"cmdk": "^0.2.0",
|
||||
"csv-parse": "^5.6.0",
|
||||
"date-fns": "^3.3.1",
|
||||
"framer-motion": "^11.0.3",
|
||||
"geist": "^1.2.1",
|
||||
@ -62,6 +63,7 @@
|
||||
"nextra": "^2.13.4",
|
||||
"nextra-theme-blog": "^2.13.4",
|
||||
"nodemailer": "^6.9.8",
|
||||
"papaparse": "^5.5.2",
|
||||
"react": "18.2.0",
|
||||
"react-day-picker": "^8.10.0",
|
||||
"react-dom": "18.2.0",
|
||||
@ -78,10 +80,12 @@
|
||||
"zustand": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/csv-parse": "^1.2.5",
|
||||
"@types/eslint": "^8.44.7",
|
||||
"@types/next-pwa": "^5.6.9",
|
||||
"@types/node": "^18.17.0",
|
||||
"@types/nodemailer": "^6.4.15",
|
||||
"@types/papaparse": "^5.3.15",
|
||||
"@types/react": "^18.2.37",
|
||||
"@types/react-dom": "^18.2.15",
|
||||
"@types/web-push": "^3.6.3",
|
||||
|
||||
33
pnpm-lock.yaml
generated
33
pnpm-lock.yaml
generated
@ -83,6 +83,9 @@ dependencies:
|
||||
cmdk:
|
||||
specifier: ^0.2.0
|
||||
version: 0.2.0(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0)
|
||||
csv-parse:
|
||||
specifier: ^5.6.0
|
||||
version: 5.6.0
|
||||
date-fns:
|
||||
specifier: ^3.3.1
|
||||
version: 3.3.1
|
||||
@ -122,6 +125,9 @@ dependencies:
|
||||
nodemailer:
|
||||
specifier: ^6.9.8
|
||||
version: 6.9.8
|
||||
papaparse:
|
||||
specifier: ^5.5.2
|
||||
version: 5.5.2
|
||||
react:
|
||||
specifier: 18.2.0
|
||||
version: 18.2.0
|
||||
@ -166,6 +172,9 @@ dependencies:
|
||||
version: 4.5.0(@types/react@18.2.48)(react@18.2.0)
|
||||
|
||||
devDependencies:
|
||||
'@types/csv-parse':
|
||||
specifier: ^1.2.5
|
||||
version: 1.2.5
|
||||
'@types/eslint':
|
||||
specifier: ^8.44.7
|
||||
version: 8.56.2
|
||||
@ -178,6 +187,9 @@ devDependencies:
|
||||
'@types/nodemailer':
|
||||
specifier: ^6.4.15
|
||||
version: 6.4.15
|
||||
'@types/papaparse':
|
||||
specifier: ^5.3.15
|
||||
version: 5.3.15
|
||||
'@types/react':
|
||||
specifier: ^18.2.37
|
||||
version: 18.2.48
|
||||
@ -4348,6 +4360,13 @@ packages:
|
||||
'@types/estree': 1.0.5
|
||||
dev: false
|
||||
|
||||
/@types/csv-parse@1.2.5:
|
||||
resolution: {integrity: sha512-3PoFyWeuFGqale09vFydLQ6IGdvD+mizcXcB8s6ImWv+830IF0HckvewgcGVfGnTFImqvfvhpYZYod2QqGGGdg==}
|
||||
deprecated: This is a stub types definition. csv-parse provides its own type definitions, so you do not need this installed.
|
||||
dependencies:
|
||||
csv-parse: 5.6.0
|
||||
dev: true
|
||||
|
||||
/@types/d3-scale-chromatic@3.0.3:
|
||||
resolution: {integrity: sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==}
|
||||
dev: false
|
||||
@ -4481,6 +4500,12 @@ packages:
|
||||
'@types/node': 18.19.8
|
||||
dev: true
|
||||
|
||||
/@types/papaparse@5.3.15:
|
||||
resolution: {integrity: sha512-JHe6vF6x/8Z85nCX4yFdDslN11d+1pr12E526X8WAfhadOeaOTx5AuIkvDKIBopfvlzpzkdMx4YyvSKCM9oqtw==}
|
||||
dependencies:
|
||||
'@types/node': 18.19.8
|
||||
dev: true
|
||||
|
||||
/@types/prop-types@15.7.11:
|
||||
resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==}
|
||||
|
||||
@ -4781,6 +4806,7 @@ packages:
|
||||
|
||||
/acorn-import-assertions@1.9.0(acorn@8.11.3):
|
||||
resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==}
|
||||
deprecated: package has been renamed to acorn-import-attributes
|
||||
peerDependencies:
|
||||
acorn: ^8
|
||||
dependencies:
|
||||
@ -5554,6 +5580,9 @@ packages:
|
||||
/csstype@3.1.3:
|
||||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||
|
||||
/csv-parse@5.6.0:
|
||||
resolution: {integrity: sha512-l3nz3euub2QMg5ouu5U09Ew9Wf6/wQ8I++ch1loQ0ljmzhmfZYrH9fflS22i/PQEvsPvxCwxgz5q7UB8K1JO4Q==}
|
||||
|
||||
/cytoscape-cose-bilkent@4.1.0(cytoscape@3.28.1):
|
||||
resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==}
|
||||
peerDependencies:
|
||||
@ -8993,6 +9022,10 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/papaparse@5.5.2:
|
||||
resolution: {integrity: sha512-PZXg8UuAc4PcVwLosEEDYjPyfWnTEhOrUfdv+3Bx+NuAb+5NhDmXzg5fHWmdCh1mP5p7JAZfFr3IMQfcntNAdA==}
|
||||
dev: false
|
||||
|
||||
/parent-module@1.0.1:
|
||||
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
@ -15,6 +15,7 @@ import { DownloadCloud } from 'lucide-react';
|
||||
import { LoadingSpinner } from '~/components/ui/spinner';
|
||||
import { useRouter } from 'next/router';
|
||||
import { PaperClipIcon } from '@heroicons/react/24/solid';
|
||||
import Papa from 'papaparse';
|
||||
|
||||
const ImportSpliwisePage: NextPageWithUser = () => {
|
||||
const [usersWithBalance, setUsersWithBalance] = useState<Array<SplitwiseUser>>([]);
|
||||
@ -35,42 +36,61 @@ const ImportSpliwisePage: NextPageWithUser = () => {
|
||||
setUploadedFile(file);
|
||||
|
||||
try {
|
||||
const json = JSON.parse(await file.text()) as Record<string, unknown>;
|
||||
const friendsWithOutStandingBalance: Array<SplitwiseUser> = [];
|
||||
for (const friend of json.friends as Array<Record<string, unknown>>) {
|
||||
const balance = friend.balance as Array<{ currency_code: string; amount: string }>;
|
||||
if (balance.length && friend.registration_status === 'confirmed') {
|
||||
friendsWithOutStandingBalance.push(friend as SplitwiseUser);
|
||||
}
|
||||
}
|
||||
const text = await file.text();
|
||||
|
||||
setUsersWithBalance(friendsWithOutStandingBalance);
|
||||
setSelectedUsers(
|
||||
friendsWithOutStandingBalance.reduce(
|
||||
(acc, user) => {
|
||||
if (file.name.endsWith('.json')) {
|
||||
const json = JSON.parse(text) as Record<string, unknown>;
|
||||
const friendsWithOutStandingBalance: Array<SplitwiseUser> = [];
|
||||
|
||||
for (const friend of json.friends as Array<Record<string, unknown>>) {
|
||||
const balance = friend.balance as Array<{ currency_code: string; amount: string }>;
|
||||
if (balance.length && friend.registration_status === 'confirmed') {
|
||||
friendsWithOutStandingBalance.push(friend as SplitwiseUser);
|
||||
}
|
||||
}
|
||||
|
||||
setUsersWithBalance(friendsWithOutStandingBalance);
|
||||
setSelectedUsers(
|
||||
friendsWithOutStandingBalance.reduce((acc, user) => {
|
||||
acc[user.id] = true;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, boolean>,
|
||||
),
|
||||
);
|
||||
}, {} as Record<string, boolean>)
|
||||
);
|
||||
|
||||
const _groups = (json.groups as Array<SplitwiseGroup>).filter(
|
||||
(g) => g.members.length > 0 && g.id !== 0,
|
||||
);
|
||||
const _groups = (json.groups as Array<SplitwiseGroup>).filter(
|
||||
(g) => g.members.length > 0 && g.id !== 0
|
||||
);
|
||||
|
||||
setGroups(_groups);
|
||||
setSelectedGroups(
|
||||
_groups.reduce(
|
||||
(acc, group) => {
|
||||
setGroups(_groups);
|
||||
setSelectedGroups(
|
||||
_groups.reduce((acc, group) => {
|
||||
acc[group.id] = true;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, boolean>,
|
||||
),
|
||||
);
|
||||
}, {} as Record<string, boolean>)
|
||||
);
|
||||
|
||||
console.log('Friends with outstanding balance', friendsWithOutStandingBalance);
|
||||
console.log('Parsed JSON: Friends and groups imported');
|
||||
} else if (file.name.endsWith('.csv')) {
|
||||
const result = Papa.parse(text, {
|
||||
header: true,
|
||||
skipEmptyLines: true,
|
||||
});
|
||||
|
||||
if (result.errors.length > 0) {
|
||||
console.error(result.errors);
|
||||
toast.error('Error parsing CSV file');
|
||||
return;
|
||||
}
|
||||
|
||||
const parsed = result.data as Array<Record<string, string>>;
|
||||
|
||||
console.log('Parsed CSV data:', parsed.slice(0, 5)); // 預覽前 5 筆
|
||||
|
||||
// ⚠️ TODO:你可以根據 parsed 建立對應的 users / groups 結構
|
||||
toast.success('CSV loaded, please implement parsing logic.');
|
||||
} else {
|
||||
toast.error('Unsupported file format. Please upload .json or .csv');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
toast.error('Error importing file');
|
||||
@ -124,7 +144,7 @@ const ImportSpliwisePage: NextPageWithUser = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 flex items-center gap-4">
|
||||
<label htmlFor="splitwise-json" className="w-full cursor-pointer rounded border">
|
||||
<label htmlFor="splitwise-data" className="w-full cursor-pointer rounded border">
|
||||
<div className="flex cursor-pointer px-3 py-[6px] ">
|
||||
<div className="flex items-center border-r pr-4 ">
|
||||
<PaperClipIcon className="mr-2 h-4 w-4" />{' '}
|
||||
@ -136,9 +156,9 @@ const ImportSpliwisePage: NextPageWithUser = () => {
|
||||
</div>
|
||||
<Input
|
||||
onChange={handleFileChange}
|
||||
id="splitwise-json"
|
||||
id="splitwise-data"
|
||||
type="file"
|
||||
accept=".json"
|
||||
accept=".json,.csv,text/csv"
|
||||
className="hidden"
|
||||
/>
|
||||
</label>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user