jac-client 0.2.6__py3-none-any.whl → 0.2.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. jac_client/examples/all-in-one/app.jac +573 -0
  2. jac_client/examples/all-in-one/components/CategoryFilter.jac +35 -0
  3. jac_client/examples/all-in-one/components/Header.jac +13 -0
  4. jac_client/examples/all-in-one/components/ProfitOverview.jac +50 -0
  5. jac_client/examples/all-in-one/components/Summary.jac +53 -0
  6. jac_client/examples/all-in-one/components/TransactionForm.jac +158 -0
  7. jac_client/examples/all-in-one/components/TransactionItem.jac +55 -0
  8. jac_client/examples/all-in-one/components/TransactionList.jac +37 -0
  9. jac_client/examples/all-in-one/components/navigation.jac +132 -0
  10. jac_client/examples/all-in-one/constants/categories.jac +37 -0
  11. jac_client/examples/all-in-one/constants/clients.jac +13 -0
  12. jac_client/examples/all-in-one/context/BudgetContext.jac +28 -0
  13. jac_client/examples/all-in-one/hooks/useBudget.jac +116 -0
  14. jac_client/examples/all-in-one/hooks/useLocalStorage.jac +36 -0
  15. jac_client/examples/all-in-one/pages/BudgetPlanner.cl.jac +70 -0
  16. jac_client/examples/all-in-one/pages/BudgetPlanner.jac +126 -0
  17. jac_client/examples/all-in-one/pages/FeaturesTest.cl.jac +552 -0
  18. jac_client/examples/all-in-one/pages/FeaturesTest.jac +126 -0
  19. jac_client/examples/all-in-one/pages/LandingPage.jac +101 -0
  20. jac_client/examples/all-in-one/pages/loginPage.jac +132 -0
  21. jac_client/examples/all-in-one/pages/nestedDemo.jac +61 -0
  22. jac_client/examples/all-in-one/pages/notFound.jac +24 -0
  23. jac_client/examples/all-in-one/pages/signupPage.jac +133 -0
  24. jac_client/examples/all-in-one/utils/formatters.jac +52 -0
  25. jac_client/examples/asset-serving/css-with-image/src/app.jac +3 -3
  26. jac_client/examples/asset-serving/image-asset/src/app.jac +3 -3
  27. jac_client/examples/asset-serving/import-alias/src/app.jac +3 -3
  28. jac_client/examples/basic/src/app.jac +3 -3
  29. jac_client/examples/basic-auth/src/app.jac +31 -37
  30. jac_client/examples/basic-auth-with-router/src/app.jac +16 -16
  31. jac_client/examples/basic-full-stack/src/app.jac +24 -30
  32. jac_client/examples/css-styling/js-styling/src/app.jac +5 -5
  33. jac_client/examples/css-styling/material-ui/src/app.jac +5 -5
  34. jac_client/examples/css-styling/pure-css/src/app.jac +5 -5
  35. jac_client/examples/css-styling/sass-example/src/app.jac +5 -5
  36. jac_client/examples/css-styling/styled-components/src/app.jac +5 -5
  37. jac_client/examples/css-styling/tailwind-example/src/app.jac +5 -5
  38. jac_client/examples/full-stack-with-auth/src/app.jac +16 -16
  39. jac_client/examples/ts-support/src/app.jac +4 -4
  40. jac_client/examples/with-router/src/app.jac +4 -4
  41. jac_client/plugin/cli.jac +160 -203
  42. jac_client/plugin/client.jac +8 -15
  43. jac_client/plugin/client_runtime.cl.jac +18 -14
  44. jac_client/plugin/impl/client.impl.jac +85 -26
  45. jac_client/plugin/impl/client_runtime.impl.jac +27 -9
  46. jac_client/plugin/plugin_config.jac +11 -11
  47. jac_client/plugin/src/compiler.jac +2 -1
  48. jac_client/plugin/src/impl/babel_processor.impl.jac +22 -17
  49. jac_client/plugin/src/impl/compiler.impl.jac +55 -18
  50. jac_client/plugin/src/impl/vite_bundler.impl.jac +215 -102
  51. jac_client/plugin/src/package_installer.jac +1 -1
  52. jac_client/plugin/src/vite_bundler.jac +9 -1
  53. jac_client/tests/conftest.py +10 -8
  54. jac_client/tests/fixtures/spawn_test/app.jac +15 -18
  55. jac_client/tests/fixtures/with-ts/app.jac +4 -4
  56. jac_client/tests/test_cli.py +105 -49
  57. jac_client/tests/test_it.py +297 -82
  58. {jac_client-0.2.6.dist-info → jac_client-0.2.8.dist-info}/METADATA +16 -7
  59. jac_client-0.2.8.dist-info/RECORD +97 -0
  60. jac_client/examples/all-in-one/src/app.jac +0 -841
  61. jac_client-0.2.6.dist-info/RECORD +0 -74
  62. /jac_client/examples/all-in-one/{src/button.jac → button.jac} +0 -0
  63. /jac_client/examples/all-in-one/{src/components → components}/button.jac +0 -0
  64. {jac_client-0.2.6.dist-info → jac_client-0.2.8.dist-info}/WHEEL +0 -0
  65. {jac_client-0.2.6.dist-info → jac_client-0.2.8.dist-info}/entry_points.txt +0 -0
  66. {jac_client-0.2.6.dist-info → jac_client-0.2.8.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,53 @@
1
+ # Summary component - displays budget totals with business/personal breakdown
2
+ # Demonstrates: context usage, conditional rendering, ternary operator, 6-card layout
3
+
4
+ cl import from ..context.BudgetContext { useBudgetContext }
5
+ cl import from ..utils.formatters { formatCurrency }
6
+
7
+ cl {
8
+ def:pub Summary() -> any {
9
+ budget = useBudgetContext();
10
+
11
+ # Business/Personal breakdown
12
+ businessIncome = budget["businessIncome"];
13
+ businessExpenses = budget["businessExpenses"];
14
+ personalIncome = budget["personalIncome"];
15
+ personalExpenses = budget["personalExpenses"];
16
+ taxReserve = budget["taxReserve"];
17
+ netProfit = budget["netProfit"];
18
+
19
+ return <div className="summary">
20
+ <div className="summary-card business-income">
21
+ <span className="summary-label">Business Income</span>
22
+ <span className="summary-value">{formatCurrency(businessIncome)}</span>
23
+ </div>
24
+
25
+ <div className="summary-card business-expenses">
26
+ <span className="summary-label">Business Expenses</span>
27
+ <span className="summary-value">{formatCurrency(businessExpenses)}</span>
28
+ </div>
29
+
30
+ <div className="summary-card personal-income">
31
+ <span className="summary-label">Personal Income</span>
32
+ <span className="summary-value">{formatCurrency(personalIncome)}</span>
33
+ </div>
34
+
35
+ <div className="summary-card personal-expenses">
36
+ <span className="summary-label">Personal Expenses</span>
37
+ <span className="summary-value">{formatCurrency(personalExpenses)}</span>
38
+ </div>
39
+
40
+ <div className="summary-card tax-reserve">
41
+ <span className="summary-label">Tax Reserve (20%)</span>
42
+ <span className="summary-value">{formatCurrency(taxReserve)}</span>
43
+ </div>
44
+
45
+ <div className={("summary-card net-profit positive") if netProfit >= 0 else ("summary-card net-profit negative")}>
46
+ <span className="summary-label">Net Profit</span>
47
+ <span className="summary-value">
48
+ {("+") if netProfit > 0 else ("")}{formatCurrency(netProfit)}
49
+ </span>
50
+ </div>
51
+ </div>;
52
+ }
53
+ }
@@ -0,0 +1,158 @@
1
+ # Transaction form component
2
+ # Demonstrates: form handling, useState, events, select options
3
+
4
+ cl import from ..context.BudgetContext { useBudgetContext }
5
+ cl import from ..constants.categories { CATEGORIES, CATEGORY_LABELS }
6
+ cl import from ..constants.clients { CLIENTS }
7
+
8
+ cl {
9
+ def:pub TransactionForm() -> any {
10
+ [description, setDescription] = useState("");
11
+ [amount, setAmount] = useState("");
12
+ [category, setCategory] = useState("OTHER");
13
+ [txType, setTxType] = useState("expense");
14
+ [isBusiness, setIsBusiness] = useState(false);
15
+ [clientName, setClientName] = useState("");
16
+ budget = useBudgetContext();
17
+
18
+ def handleSubmit(e: any) -> None {
19
+ e.preventDefault();
20
+
21
+ # Validate inputs
22
+ trimmedDesc = description.trim();
23
+ if trimmedDesc == "" or amount == "" {
24
+ return;
25
+ }
26
+
27
+ parsedAmount = parseFloat(amount);
28
+ if isNaN(parsedAmount) or parsedAmount <= 0 {
29
+ return;
30
+ }
31
+
32
+ # Add the transaction
33
+ budget["addTransaction"](trimmedDesc, parsedAmount, category, txType, isBusiness, clientName);
34
+
35
+ # Reset form
36
+ setDescription("");
37
+ setAmount("");
38
+ setCategory("OTHER");
39
+ setIsBusiness(false);
40
+ setClientName("");
41
+ }
42
+
43
+ # Filter categories based on type (income only has INCOME category)
44
+ availableCategories = CATEGORIES.filter(lambda cat: str -> bool {
45
+ if txType == "income" {
46
+ return cat == "INCOME";
47
+ }
48
+ return cat != "INCOME";
49
+ });
50
+
51
+ # Show client dropdown only for business income
52
+ showClientDropdown = (txType == "income" and isBusiness);
53
+
54
+ return <form className="transaction-form" onSubmit={handleSubmit}>
55
+ <div className="form-row">
56
+ <div className="form-group type-toggle">
57
+ <button
58
+ type="button"
59
+ className={("toggle-btn active") if txType == "expense" else ("toggle-btn")}
60
+ onClick={lambda: setTxType("expense")}
61
+ >
62
+ Expense
63
+ </button>
64
+ <button
65
+ type="button"
66
+ className={("toggle-btn active income") if txType == "income" else ("toggle-btn")}
67
+ onClick={lambda: setTxType("income")}
68
+ >
69
+ Income
70
+ </button>
71
+ </div>
72
+
73
+ <div className="form-group business-toggle">
74
+ <button
75
+ type="button"
76
+ className={("toggle-btn active") if isBusiness else ("toggle-btn")}
77
+ onClick={lambda: setIsBusiness(true)}
78
+ >
79
+ Business
80
+ </button>
81
+ <button
82
+ type="button"
83
+ className={("toggle-btn active") if not isBusiness else ("toggle-btn")}
84
+ onClick={lambda: setIsBusiness(false)}
85
+ >
86
+ Personal
87
+ </button>
88
+ </div>
89
+ </div>
90
+
91
+ <div className="form-row">
92
+ <div className="form-group">
93
+ <label htmlFor="description">Description</label>
94
+ <input
95
+ id="description"
96
+ type="text"
97
+ value={description}
98
+ onChange={lambda e: any -> None { setDescription(e.target.value); }}
99
+ placeholder="Enter description..."
100
+ className="form-input"
101
+ />
102
+ </div>
103
+
104
+ <div className="form-group">
105
+ <label htmlFor="amount">Amount</label>
106
+ <input
107
+ id="amount"
108
+ type="number"
109
+ value={amount}
110
+ onChange={lambda e: any -> None { setAmount(e.target.value); }}
111
+ placeholder="0.00"
112
+ min="0"
113
+ step="0.01"
114
+ className="form-input"
115
+ />
116
+ </div>
117
+
118
+ <div className="form-group">
119
+ <label htmlFor="category">Category</label>
120
+ <select
121
+ id="category"
122
+ value={category}
123
+ onChange={lambda e: any -> None { setCategory(e.target.value); }}
124
+ className="form-select"
125
+ >
126
+ {availableCategories.map(lambda cat: str -> any {
127
+ return <option key={cat} value={cat}>
128
+ {CATEGORY_LABELS[cat]}
129
+ </option>;
130
+ })}
131
+ </select>
132
+ </div>
133
+
134
+ {showClientDropdown and <div className="form-group">
135
+ <label htmlFor="client">Client</label>
136
+ <select
137
+ id="client"
138
+ value={clientName}
139
+ onChange={lambda e: any -> None { setClientName(e.target.value); }}
140
+ className="form-select"
141
+ >
142
+ <option value="">Select Client (Optional)</option>
143
+ {CLIENTS.map(lambda client: str -> any {
144
+ return <option key={client} value={client}>{client}</option>;
145
+ })}
146
+ </select>
147
+ </div>}
148
+
149
+ <div className="form-group">
150
+ <label>Action</label>
151
+ <button type="submit" className="submit-btn">
152
+ Add {(txType[0].toUpperCase() + txType.slice(1))}
153
+ </button>
154
+ </div>
155
+ </div>
156
+ </form>;
157
+ }
158
+ }
@@ -0,0 +1,55 @@
1
+ # Single transaction item component
2
+ # Demonstrates: NEW PROPS PATTERN (direct parameters, NOT props dict)
3
+
4
+ cl import from ..utils.formatters { formatCurrency, formatDate }
5
+ cl import from ..constants.categories { CATEGORY_COLORS, CATEGORY_LABELS }
6
+
7
+ cl {
8
+ # NEW WAY: Direct parameters instead of props: dict
9
+ def:pub TransactionItem(
10
+ id: str,
11
+ description: str,
12
+ amount: float,
13
+ category: str,
14
+ txType: str,
15
+ date: str,
16
+ isBusiness: bool,
17
+ clientName: any,
18
+ onDelete: any
19
+ ) -> any {
20
+ isIncome = txType == "income";
21
+ color = CATEGORY_COLORS[category];
22
+ label = CATEGORY_LABELS[category];
23
+
24
+ return <div className="transaction-item">
25
+ <div className="tx-left">
26
+ <span
27
+ className="tx-category"
28
+ style={{"backgroundColor": color}}
29
+ >
30
+ {label}
31
+ </span>
32
+ <div className="tx-details">
33
+ <span className="tx-description">
34
+ {description}
35
+ {(clientName != None and isIncome) and <span className="tx-client"> • {clientName}</span>}
36
+ </span>
37
+ <span className="tx-date">{formatDate(date)}</span>
38
+ </div>
39
+ </div>
40
+
41
+ <div className="tx-right">
42
+ <span className={("tx-amount income") if isIncome else ("tx-amount expense")}>
43
+ {("+") if isIncome else ("-")}{formatCurrency(amount)}
44
+ </span>
45
+ <button
46
+ className="delete-btn"
47
+ onClick={lambda:onDelete(id)}
48
+ title="Delete transaction"
49
+ >
50
+ X
51
+ </button>
52
+ </div>
53
+ </div>;
54
+ }
55
+ }
@@ -0,0 +1,37 @@
1
+ # Transaction list component
2
+ # Demonstrates: rendering lists with map, passing props to child components
3
+
4
+ cl import from .TransactionItem { TransactionItem }
5
+
6
+ cl {
7
+ # Props: transactions list and delete handler
8
+ def:pub TransactionList(transactions: list, onDelete: any) -> any {
9
+ if transactions.length == 0 {
10
+ return <div className="empty-state">
11
+ <p>No transactions yet.</p>
12
+ <p>Add your first income or expense above!</p>
13
+ </div>;
14
+ }
15
+
16
+ return <div className="transaction-list">
17
+ <h3 className="list-title">
18
+ Transactions ({transactions.length})
19
+ </h3>
20
+
21
+ {transactions.map(lambda tx: dict -> any {
22
+ return <TransactionItem
23
+ key={tx["id"]}
24
+ id={tx["id"]}
25
+ description={tx["description"]}
26
+ amount={tx["amount"]}
27
+ category={tx["category"]}
28
+ txType={tx["type"]}
29
+ date={tx["date"]}
30
+ isBusiness={tx["isBusinessTransaction"] || false}
31
+ clientName={tx["clientName"] || None}
32
+ onDelete={onDelete}
33
+ />;
34
+ })}
35
+ </div>;
36
+ }
37
+ }
@@ -0,0 +1,132 @@
1
+ cl import from "@jac-client/utils" {
2
+
3
+ Link,
4
+ useNavigate,
5
+ useLocation,
6
+ jacLogout,
7
+ jacIsLoggedIn
8
+ }
9
+ cl {
10
+ def:pub Navigation -> any {
11
+ location = useLocation();
12
+ isLoggedIn = jacIsLoggedIn();
13
+ navigate = useNavigate();
14
+
15
+ def linkStyle(path: str) -> dict {
16
+ isActive = location.pathname == path;
17
+ return {
18
+ "padding": "0.5rem 1rem",
19
+ "textDecoration": "none",
20
+ "color": "#0066cc" if isActive else "#333",
21
+ "fontWeight": "bold" if isActive else "normal",
22
+ "backgroundColor": "#e3f2fd" if isActive else "transparent",
23
+ "borderRadius": "4px",
24
+ "display": "inline-block"
25
+ };
26
+ }
27
+
28
+ def handleLogout(e: any) -> None {
29
+ e.preventDefault();
30
+ jacLogout();
31
+ navigate("/login");
32
+ }
33
+
34
+ authButtons = None;
35
+ if isLoggedIn {
36
+ authButtons = <button
37
+ onClick={handleLogout}
38
+ style={{
39
+ "padding": "0.5rem 1rem",
40
+ "background": "#ef4444",
41
+ "color": "#ffffff",
42
+ "border": "none",
43
+ "borderRadius": "4px",
44
+ "cursor": "pointer",
45
+ "fontWeight": "600"
46
+ }}
47
+ >
48
+ Logout
49
+ </button>;
50
+ } else {
51
+ authButtons = <>
52
+ <Link
53
+ to="/login"
54
+ style={linkStyle("/login")}
55
+ >
56
+ Login
57
+ </Link>
58
+ <Link
59
+ to="/signup"
60
+ style={linkStyle("/signup")}
61
+ >
62
+ Sign Up
63
+ </Link>
64
+ </>;
65
+ }
66
+
67
+ return <nav
68
+ style={{
69
+ "padding": "1rem",
70
+ "backgroundColor": "#f5f5f5",
71
+ "marginBottom": "2rem",
72
+ "boxShadow": "0 2px 4px rgba(0,0,0,0.1)"
73
+ }}
74
+ >
75
+ <div
76
+ style={{
77
+ "maxWidth": "1200px",
78
+ "margin": "0 auto",
79
+ "display": "flex",
80
+ "gap": "1rem",
81
+ "alignItems": "center",
82
+ "justifyContent": "space-between"
83
+ }}
84
+ >
85
+ <div
86
+ style={{"display": "flex", "gap": "1rem", "alignItems": "center"}}
87
+ >
88
+ {isLoggedIn
89
+ and (
90
+ <>
91
+ <Link
92
+ to="/"
93
+ style={linkStyle("/")}
94
+ >
95
+ Home
96
+ </Link>
97
+ <Link
98
+ to="/nested"
99
+ style={linkStyle("/nested")}
100
+ >
101
+ Nested Imports
102
+ </Link>
103
+ <Link
104
+ to="/features-test"
105
+ style={linkStyle("/features-test")}
106
+ >
107
+ Features Test
108
+ </Link>
109
+ <Link
110
+ to="/landing"
111
+ style={linkStyle("/landing")}
112
+ >
113
+ Landing Page
114
+ </Link>
115
+ <Link
116
+ to="/budget-planner"
117
+ style={linkStyle("/budget-planner")}
118
+ >
119
+ Budget Planner
120
+ </Link>
121
+ </>
122
+ )}
123
+ </div>
124
+ <div
125
+ style={{"display": "flex", "gap": "1rem", "alignItems": "center"}}
126
+ >
127
+ {authButtons}
128
+ </div>
129
+ </div>
130
+ </nav>;
131
+ }
132
+ }
@@ -0,0 +1,37 @@
1
+ # Category constants and enums for Budget Planner
2
+ # Demonstrates: enum:pub, glob:pub exports
3
+
4
+ cl {
5
+ # Category enum - demonstrates enum:pub export
6
+ enum:pub Category {
7
+ INCOME,
8
+ FOOD,
9
+ TRANSPORT,
10
+ UTILITIES,
11
+ ENTERTAINMENT,
12
+ OTHER
13
+ }
14
+
15
+ # Category colors - demonstrates glob:pub export
16
+ glob:pub CATEGORY_COLORS: dict = {
17
+ "INCOME": "#10b981",
18
+ "FOOD": "#f59e0b",
19
+ "TRANSPORT": "#3b82f6",
20
+ "UTILITIES": "#8b5cf6",
21
+ "ENTERTAINMENT": "#ec4899",
22
+ "OTHER": "#6b7280"
23
+ };
24
+
25
+ # Category labels for display
26
+ glob:pub CATEGORY_LABELS: dict = {
27
+ "INCOME": "Income",
28
+ "FOOD": "Food & Dining",
29
+ "TRANSPORT": "Transport",
30
+ "UTILITIES": "Utilities",
31
+ "ENTERTAINMENT": "Entertainment",
32
+ "OTHER": "Other"
33
+ };
34
+
35
+ # All category keys as a list
36
+ glob:pub CATEGORIES: list = ["INCOME", "FOOD", "TRANSPORT", "UTILITIES", "ENTERTAINMENT", "OTHER"];
37
+ }
@@ -0,0 +1,13 @@
1
+ # Client list for freelancer income tracking
2
+ # Demonstrates: glob exports, array constants
3
+
4
+ cl {
5
+ glob:pub CLIENTS = [
6
+ "Acme Corp",
7
+ "TechStart Inc",
8
+ "Design Studio Co",
9
+ "Marketing Agency",
10
+ "Freelance Marketplace",
11
+ "Other"
12
+ ];
13
+ }
@@ -0,0 +1,28 @@
1
+ # Budget Context for global state management
2
+ # Demonstrates: createContext, useContext, Context.Provider
3
+
4
+ cl import from react { createContext, useContext }
5
+ cl import from ..hooks.useBudget { useBudget }
6
+
7
+ cl {
8
+ # Create the context - demonstrates glob for context
9
+ glob:pub BudgetContext = createContext(None);
10
+
11
+ # Provider component - wraps app and provides budget state
12
+ def:pub BudgetProvider(children: any) -> any {
13
+ budget = useBudget();
14
+
15
+ return <BudgetContext.Provider value={budget}>
16
+ {children}
17
+ </BudgetContext.Provider>;
18
+ }
19
+
20
+ # Custom hook to access budget context
21
+ def:pub useBudgetContext() -> dict {
22
+ context = useContext(BudgetContext);
23
+ if context == None {
24
+ console.error("useBudgetContext must be used within BudgetProvider");
25
+ }
26
+ return context;
27
+ }
28
+ }
@@ -0,0 +1,116 @@
1
+ # Custom hook for budget operations
2
+ # Demonstrates: custom hooks, useState, array methods
3
+
4
+ cl import from react { useCallback, useMemo }
5
+
6
+ cl {
7
+ # Main budget management hook
8
+ def:pub useBudget() -> dict {
9
+ # Simple useState - no complex localStorage hook
10
+ [transactions, setTransactions] = useState([]);
11
+
12
+ # Add a new transaction
13
+ def addTransaction(description: str, amount: float, category: str, txType: str, isBusiness: bool, clientName: str) -> None {
14
+ newTx = {
15
+ "id": Date.now().toString(),
16
+ "description": description,
17
+ "amount": amount,
18
+ "category": category,
19
+ "type": txType,
20
+ "date": Reflect.construct(Date, []).toISOString(),
21
+ "isBusinessTransaction": isBusiness,
22
+ "clientName": clientName if clientName != "" else None
23
+ };
24
+ setTransactions(transactions.concat([newTx]));
25
+ }
26
+
27
+ # Delete a transaction by ID
28
+ def deleteTransaction(id: str) -> None {
29
+ filtered = transactions.filter(lambda tx: dict -> bool {
30
+ return tx["id"] != id;
31
+ });
32
+ setTransactions(filtered);
33
+ }
34
+
35
+ # Calculate totals
36
+ totalIncome = 0.0;
37
+ totalExpenses = 0.0;
38
+ for tx in transactions {
39
+ if tx["type"] == "income" {
40
+ totalIncome = totalIncome + tx["amount"];
41
+ } else {
42
+ totalExpenses = totalExpenses + tx["amount"];
43
+ }
44
+ }
45
+ balance = totalIncome - totalExpenses;
46
+
47
+ # Calculate business/personal breakdown
48
+ businessIncome = 0.0;
49
+ businessExpenses = 0.0;
50
+ personalIncome = 0.0;
51
+ personalExpenses = 0.0;
52
+
53
+ for tx in transactions {
54
+ isBusiness = tx["isBusinessTransaction"] || false;
55
+ amount = tx["amount"];
56
+
57
+ if tx["type"] == "income" {
58
+ if isBusiness {
59
+ businessIncome = businessIncome + amount;
60
+ } else {
61
+ personalIncome = personalIncome + amount;
62
+ }
63
+ } else {
64
+ if isBusiness {
65
+ businessExpenses = businessExpenses + amount;
66
+ } else {
67
+ personalExpenses = personalExpenses + amount;
68
+ }
69
+ }
70
+ }
71
+
72
+ # Tax calculations
73
+ TAX_RATE = 0.20;
74
+ taxReserve = businessIncome * TAX_RATE;
75
+ netProfit = businessIncome - businessExpenses - taxReserve;
76
+
77
+ # Get expense breakdown by category (for chart)
78
+ def getExpensesByCategory() -> list {
79
+ categoryTotals = {};
80
+ for tx in transactions {
81
+ if tx["type"] == "expense" {
82
+ cat = tx["category"];
83
+ if categoryTotals[cat] {
84
+ categoryTotals[cat] = categoryTotals[cat] + tx["amount"];
85
+ } else {
86
+ categoryTotals[cat] = tx["amount"];
87
+ }
88
+ }
89
+ }
90
+ result = [];
91
+ for key in Object.keys(categoryTotals) {
92
+ result = result.concat([{
93
+ "name": key,
94
+ "value": categoryTotals[key]
95
+ }]);
96
+ }
97
+ return result;
98
+ }
99
+
100
+ return {
101
+ "transactions": transactions,
102
+ "addTransaction": addTransaction,
103
+ "deleteTransaction": deleteTransaction,
104
+ "totalIncome": totalIncome,
105
+ "totalExpenses": totalExpenses,
106
+ "balance": balance,
107
+ "expensesByCategory": getExpensesByCategory(),
108
+ "businessIncome": businessIncome,
109
+ "businessExpenses": businessExpenses,
110
+ "personalIncome": personalIncome,
111
+ "personalExpenses": personalExpenses,
112
+ "taxReserve": taxReserve,
113
+ "netProfit": netProfit
114
+ };
115
+ }
116
+ }
@@ -0,0 +1,36 @@
1
+ # Custom hook for localStorage persistence
2
+ # Demonstrates: custom hooks, try/except error handling, useEffect
3
+
4
+ cl import from react { useEffect }
5
+
6
+ cl {
7
+ # Custom hook for syncing state with localStorage
8
+ def useLocalStorage(key: str, initialValue: list) -> list {
9
+ # Initialize state with value from localStorage or default
10
+ [storedValue, setStoredValue] = useState(lambda -> any {
11
+ try {
12
+ item = window.localStorage.getItem(key);
13
+ if item {
14
+ return JSON.parse(item);
15
+ }else{
16
+ return initialValue;
17
+ }
18
+
19
+ } except Exception as e {
20
+ console.log("Error reading from localStorage");
21
+ return initialValue;
22
+ }
23
+ });
24
+
25
+ # Update localStorage when state changes
26
+ useEffect(lambda -> None {
27
+ try {
28
+ window.localStorage.setItem(key, JSON.stringify(storedValue));
29
+ } except Exception as e {
30
+ console.log("Error saving to localStorage");
31
+ }
32
+ }, [key, storedValue]);
33
+
34
+ return [storedValue, setStoredValue];
35
+ }
36
+ }