create-steve-rogers 1.0.0 → 1.0.2
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.
- package/apps/SFMS/.env +9 -0
- package/apps/SFMS/README.md +0 -0
- package/apps/SFMS/backend/.env +9 -0
- package/apps/SFMS/backend/.env.example +9 -0
- package/apps/SFMS/backend/package-lock.json +1580 -0
- package/apps/SFMS/backend/package.json +23 -0
- package/apps/SFMS/backend/src/config/database.js +7 -0
- package/apps/SFMS/backend/src/config/env.js +35 -0
- package/apps/SFMS/backend/src/middleware/authMiddleware.js +32 -0
- package/apps/SFMS/backend/src/models/Payment.js +12 -0
- package/apps/SFMS/backend/src/models/Student.js +12 -0
- package/apps/SFMS/backend/src/models/User.js +13 -0
- package/apps/SFMS/backend/src/routes/authRoutes.js +93 -0
- package/apps/SFMS/backend/src/routes/paymentRoutes.js +117 -0
- package/apps/SFMS/backend/src/routes/reportRoutes.js +59 -0
- package/apps/SFMS/backend/src/routes/studentRoutes.js +79 -0
- package/apps/SFMS/backend/src/server.js +34 -0
- package/apps/SFMS/frontend/.env.example +8 -0
- package/apps/SFMS/frontend/dist/assets/index-B08X8imN.css +1 -0
- package/apps/SFMS/frontend/dist/assets/index-DVO0_wcb.js +67 -0
- package/apps/SFMS/frontend/dist/favicon.svg +4 -0
- package/apps/SFMS/frontend/dist/index.html +20 -0
- package/apps/SFMS/frontend/index.html +19 -0
- package/apps/SFMS/frontend/package-lock.json +2667 -0
- package/apps/SFMS/frontend/package.json +23 -0
- package/apps/SFMS/frontend/postcss.config.js +6 -0
- package/apps/SFMS/frontend/public/favicon.svg +4 -0
- package/apps/SFMS/frontend/src/App.jsx +41 -0
- package/apps/SFMS/frontend/src/api/apiClient.js +41 -0
- package/apps/SFMS/frontend/src/components/AppLayout.jsx +60 -0
- package/apps/SFMS/frontend/src/context/AuthContext.jsx +79 -0
- package/apps/SFMS/frontend/src/index.css +229 -0
- package/apps/SFMS/frontend/src/main.jsx +16 -0
- package/apps/SFMS/frontend/src/pages/DashboardPage.jsx +82 -0
- package/apps/SFMS/frontend/src/pages/LoginPage.jsx +142 -0
- package/apps/SFMS/frontend/src/pages/PaymentsPage.jsx +269 -0
- package/apps/SFMS/frontend/src/pages/ReportsPage.jsx +114 -0
- package/apps/SFMS/frontend/src/pages/StudentsPage.jsx +257 -0
- package/apps/SFMS/frontend/tailwind.config.js +21 -0
- package/apps/SFMS/frontend/vite.config.js +35 -0
- package/apps/SIMS/.env +4 -0
- package/apps/SIMS/README.md +138 -0
- package/apps/SIMS/backend/.env +4 -0
- package/apps/SIMS/backend/.env.example +4 -0
- package/apps/SIMS/backend/package-lock.json +1600 -0
- package/apps/SIMS/backend/package.json +22 -0
- package/apps/SIMS/backend/src/config/db.js +9 -0
- package/apps/SIMS/backend/src/controllers/authController.js +93 -0
- package/apps/SIMS/backend/src/controllers/simsReportController.js +94 -0
- package/apps/SIMS/backend/src/controllers/sparePartController.js +41 -0
- package/apps/SIMS/backend/src/controllers/stockInController.js +45 -0
- package/apps/SIMS/backend/src/controllers/stockOutController.js +123 -0
- package/apps/SIMS/backend/src/middleware/auth.js +8 -0
- package/apps/SIMS/backend/src/models/SparePart.js +17 -0
- package/apps/SIMS/backend/src/models/StockIn.js +16 -0
- package/apps/SIMS/backend/src/models/StockOut.js +18 -0
- package/apps/SIMS/backend/src/models/User.js +11 -0
- package/apps/SIMS/backend/src/routes/authRoutes.js +12 -0
- package/apps/SIMS/backend/src/routes/simsReportRoutes.js +8 -0
- package/apps/SIMS/backend/src/routes/sparePartRoutes.js +8 -0
- package/apps/SIMS/backend/src/routes/stockInRoutes.js +8 -0
- package/apps/SIMS/backend/src/routes/stockOutRoutes.js +10 -0
- package/apps/SIMS/backend/src/server.js +62 -0
- package/apps/SIMS/backend/src/utils/passwordPolicy.js +10 -0
- package/apps/SIMS/backend/src/utils/sparePartHelpers.js +5 -0
- package/apps/SIMS/frontend/dist/assets/index-3hv-vGL2.css +2 -0
- package/apps/SIMS/frontend/dist/assets/index-T8XT7M6y.js +19 -0
- package/apps/SIMS/frontend/dist/index.html +14 -0
- package/apps/SIMS/frontend/index.html +13 -0
- package/apps/SIMS/frontend/package-lock.json +3053 -0
- package/apps/SIMS/frontend/package.json +31 -0
- package/apps/SIMS/frontend/src/App.jsx +112 -0
- package/apps/SIMS/frontend/src/api/authApi.js +7 -0
- package/apps/SIMS/frontend/src/api/client.js +8 -0
- package/apps/SIMS/frontend/src/api/simsReportApi.js +5 -0
- package/apps/SIMS/frontend/src/api/sparePartsApi.js +4 -0
- package/apps/SIMS/frontend/src/api/stockInApi.js +4 -0
- package/apps/SIMS/frontend/src/api/stockOutApi.js +6 -0
- package/apps/SIMS/frontend/src/api/usersApi.js +3 -0
- package/apps/SIMS/frontend/src/components/AppLayout.jsx +60 -0
- package/apps/SIMS/frontend/src/index.css +737 -0
- package/apps/SIMS/frontend/src/main.jsx +13 -0
- package/apps/SIMS/frontend/src/pages/DashboardPage.jsx +179 -0
- package/apps/SIMS/frontend/src/pages/LoginPage.jsx +75 -0
- package/apps/SIMS/frontend/src/pages/RegisterPage.jsx +78 -0
- package/apps/SIMS/frontend/src/pages/ReportsPage.jsx +108 -0
- package/apps/SIMS/frontend/src/pages/ResetPasswordPage.jsx +75 -0
- package/apps/SIMS/frontend/src/pages/SparePartPage.jsx +128 -0
- package/apps/SIMS/frontend/src/pages/StockInPage.jsx +100 -0
- package/apps/SIMS/frontend/src/pages/StockOutPage.jsx +206 -0
- package/apps/SIMS/frontend/src/utils/passwordPolicy.js +8 -0
- package/apps/SIMS/frontend/vite.config.js +8 -0
- package/apps/config.js +13 -0
- package/package.json +1 -1
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { getSpareParts } from "../api/sparePartsApi";
|
|
3
|
+
import { getStockIn, createStockIn } from "../api/stockInApi";
|
|
4
|
+
|
|
5
|
+
function StockInPage() {
|
|
6
|
+
const [parts, setParts] = useState([]);
|
|
7
|
+
const [rows, setRows] = useState([]);
|
|
8
|
+
const [form, setForm] = useState({ sparePartId: "", stockInQuantity: "", stockInDate: "" });
|
|
9
|
+
const [message, setMessage] = useState("");
|
|
10
|
+
|
|
11
|
+
const load = async () => {
|
|
12
|
+
const [pr, inRows] = await Promise.all([getSpareParts(), getStockIn()]);
|
|
13
|
+
setParts(pr.data);
|
|
14
|
+
setRows(inRows.data);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
load().catch(() => setMessage("Load failed"));
|
|
19
|
+
}, []);
|
|
20
|
+
|
|
21
|
+
const onSubmit = async (e) => {
|
|
22
|
+
e.preventDefault();
|
|
23
|
+
setMessage("");
|
|
24
|
+
const q = Number(form.stockInQuantity);
|
|
25
|
+
if (!form.sparePartId || !form.stockInDate) {
|
|
26
|
+
setMessage("Select part and date");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
await createStockIn({
|
|
31
|
+
sparePartId: form.sparePartId,
|
|
32
|
+
stockInQuantity: q,
|
|
33
|
+
stockInDate: new Date(form.stockInDate).toISOString(),
|
|
34
|
+
});
|
|
35
|
+
setForm({ sparePartId: "", stockInQuantity: "", stockInDate: "" });
|
|
36
|
+
setMessage("Stock in saved");
|
|
37
|
+
load();
|
|
38
|
+
} catch (err) {
|
|
39
|
+
setMessage(err.response?.data?.message || "Save failed");
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className="grid gap-4 md:grid-cols-2">
|
|
45
|
+
<section className="rounded-lg border border-blue-200 bg-white p-4 shadow">
|
|
46
|
+
<h2 className="mb-3 text-lg font-semibold text-blue-900">Stock In (insert only)</h2>
|
|
47
|
+
<form onSubmit={onSubmit} className="space-y-2">
|
|
48
|
+
<select
|
|
49
|
+
className="w-full rounded border px-3 py-2"
|
|
50
|
+
value={form.sparePartId}
|
|
51
|
+
onChange={(e) => setForm((f) => ({ ...f, sparePartId: e.target.value }))}
|
|
52
|
+
required
|
|
53
|
+
>
|
|
54
|
+
<option value="">Spare part</option>
|
|
55
|
+
{parts.map((p) => (
|
|
56
|
+
<option key={p._id} value={p._id}>
|
|
57
|
+
{p.name} — {p.category}
|
|
58
|
+
</option>
|
|
59
|
+
))}
|
|
60
|
+
</select>
|
|
61
|
+
<input
|
|
62
|
+
type="number"
|
|
63
|
+
className="w-full rounded border px-3 py-2"
|
|
64
|
+
placeholder="Quantity in"
|
|
65
|
+
min="1"
|
|
66
|
+
value={form.stockInQuantity}
|
|
67
|
+
onChange={(e) => setForm((f) => ({ ...f, stockInQuantity: e.target.value }))}
|
|
68
|
+
required
|
|
69
|
+
/>
|
|
70
|
+
<input
|
|
71
|
+
type="date"
|
|
72
|
+
className="w-full rounded border px-3 py-2"
|
|
73
|
+
value={form.stockInDate}
|
|
74
|
+
onChange={(e) => setForm((f) => ({ ...f, stockInDate: e.target.value }))}
|
|
75
|
+
required
|
|
76
|
+
/>
|
|
77
|
+
<button
|
|
78
|
+
type="submit"
|
|
79
|
+
className="w-full rounded bg-blue-700 py-2 text-white hover:bg-blue-800"
|
|
80
|
+
>
|
|
81
|
+
Save
|
|
82
|
+
</button>
|
|
83
|
+
</form>
|
|
84
|
+
{message && <p className="mt-2 text-sm text-slate-600">{message}</p>}
|
|
85
|
+
</section>
|
|
86
|
+
<section className="rounded-lg border border-slate-200 bg-white p-4 shadow">
|
|
87
|
+
<h2 className="mb-3 text-lg font-semibold">History (read)</h2>
|
|
88
|
+
<div className="max-h-96 overflow-auto text-sm">
|
|
89
|
+
{rows.map((r) => (
|
|
90
|
+
<div key={r._id} className="border-b border-slate-100 py-2">
|
|
91
|
+
{r.sparePart?.name} +{r.stockInQuantity} @ {new Date(r.stockInDate).toLocaleDateString()}
|
|
92
|
+
</div>
|
|
93
|
+
))}
|
|
94
|
+
</div>
|
|
95
|
+
</section>
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export default StockInPage;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { getSpareParts } from "../api/sparePartsApi";
|
|
3
|
+
import { getStockOut, createStockOut, updateStockOut, deleteStockOut } from "../api/stockOutApi";
|
|
4
|
+
|
|
5
|
+
const empty = { sparePartId: "", stockOutQuantity: "", stockOutUnitPrice: "", stockOutDate: "" };
|
|
6
|
+
|
|
7
|
+
function StockOutPage() {
|
|
8
|
+
const [parts, setParts] = useState([]);
|
|
9
|
+
const [rows, setRows] = useState([]);
|
|
10
|
+
const [form, setForm] = useState(empty);
|
|
11
|
+
const [editing, setEditing] = useState(null);
|
|
12
|
+
const [message, setMessage] = useState("");
|
|
13
|
+
|
|
14
|
+
const load = async () => {
|
|
15
|
+
const [pr, out] = await Promise.all([getSpareParts(), getStockOut()]);
|
|
16
|
+
setParts(pr.data);
|
|
17
|
+
setRows(out.data);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
load().catch(() => setMessage("Load failed"));
|
|
22
|
+
}, []);
|
|
23
|
+
|
|
24
|
+
const onSubmit = async (e) => {
|
|
25
|
+
e.preventDefault();
|
|
26
|
+
setMessage("");
|
|
27
|
+
const q = Number(form.stockOutQuantity);
|
|
28
|
+
const u = Number(form.stockOutUnitPrice);
|
|
29
|
+
if (!editing) {
|
|
30
|
+
try {
|
|
31
|
+
await createStockOut({
|
|
32
|
+
sparePartId: form.sparePartId,
|
|
33
|
+
stockOutQuantity: q,
|
|
34
|
+
stockOutUnitPrice: u,
|
|
35
|
+
stockOutDate: new Date(form.stockOutDate).toISOString(),
|
|
36
|
+
});
|
|
37
|
+
setForm(empty);
|
|
38
|
+
setMessage("Saved");
|
|
39
|
+
load();
|
|
40
|
+
} catch (err) {
|
|
41
|
+
setMessage(err.response?.data?.message || "Failed");
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
try {
|
|
45
|
+
await updateStockOut(editing, {
|
|
46
|
+
stockOutQuantity: q,
|
|
47
|
+
stockOutUnitPrice: u,
|
|
48
|
+
stockOutDate: new Date(form.stockOutDate).toISOString(),
|
|
49
|
+
});
|
|
50
|
+
setEditing(null);
|
|
51
|
+
setForm(empty);
|
|
52
|
+
setMessage("Updated");
|
|
53
|
+
load();
|
|
54
|
+
} catch (err) {
|
|
55
|
+
setMessage(err.response?.data?.message || "Update failed");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const startEdit = (r) => {
|
|
61
|
+
setEditing(r._id);
|
|
62
|
+
setForm({
|
|
63
|
+
sparePartId: r.sparePart?._id || r.sparePart,
|
|
64
|
+
stockOutQuantity: r.stockOutQuantity,
|
|
65
|
+
stockOutUnitPrice: r.stockOutUnitPrice,
|
|
66
|
+
stockOutDate: r.stockOutDate
|
|
67
|
+
? new Date(r.stockOutDate).toISOString().slice(0, 10)
|
|
68
|
+
: "",
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const onDelete = async (id) => {
|
|
73
|
+
if (!window.confirm("Delete this stock out?")) return;
|
|
74
|
+
setMessage("");
|
|
75
|
+
try {
|
|
76
|
+
await deleteStockOut(id);
|
|
77
|
+
if (editing === id) {
|
|
78
|
+
setEditing(null);
|
|
79
|
+
setForm(empty);
|
|
80
|
+
}
|
|
81
|
+
setMessage("Deleted");
|
|
82
|
+
load();
|
|
83
|
+
} catch (err) {
|
|
84
|
+
setMessage(err.response?.data?.message || "Delete failed");
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div className="space-y-4">
|
|
90
|
+
<section className="rounded-lg border border-amber-200 bg-white p-4 shadow">
|
|
91
|
+
<h2 className="mb-3 text-lg font-semibold text-amber-900">Stock Out (create / list / update / delete)</h2>
|
|
92
|
+
<form onSubmit={onSubmit} className="grid gap-2 md:grid-cols-2 lg:grid-cols-5">
|
|
93
|
+
<select
|
|
94
|
+
className="rounded border px-2 py-2"
|
|
95
|
+
value={form.sparePartId}
|
|
96
|
+
disabled={Boolean(editing)}
|
|
97
|
+
onChange={(e) => setForm((f) => ({ ...f, sparePartId: e.target.value }))}
|
|
98
|
+
required={!editing}
|
|
99
|
+
>
|
|
100
|
+
<option value="">Spare part</option>
|
|
101
|
+
{parts.map((p) => (
|
|
102
|
+
<option key={p._id} value={p._id}>
|
|
103
|
+
{p.name}
|
|
104
|
+
</option>
|
|
105
|
+
))}
|
|
106
|
+
</select>
|
|
107
|
+
<input
|
|
108
|
+
type="number"
|
|
109
|
+
className="rounded border px-2 py-2"
|
|
110
|
+
min="1"
|
|
111
|
+
placeholder="Quantity"
|
|
112
|
+
value={form.stockOutQuantity}
|
|
113
|
+
onChange={(e) => setForm((f) => ({ ...f, stockOutQuantity: e.target.value }))}
|
|
114
|
+
required
|
|
115
|
+
/>
|
|
116
|
+
<input
|
|
117
|
+
type="number"
|
|
118
|
+
className="rounded border px-2 py-2"
|
|
119
|
+
min="0"
|
|
120
|
+
step="0.01"
|
|
121
|
+
placeholder="Unit price"
|
|
122
|
+
value={form.stockOutUnitPrice}
|
|
123
|
+
onChange={(e) => setForm((f) => ({ ...f, stockOutUnitPrice: e.target.value }))}
|
|
124
|
+
required
|
|
125
|
+
/>
|
|
126
|
+
<input
|
|
127
|
+
type="date"
|
|
128
|
+
className="rounded border px-2 py-2"
|
|
129
|
+
value={form.stockOutDate}
|
|
130
|
+
onChange={(e) => setForm((f) => ({ ...f, stockOutDate: e.target.value }))}
|
|
131
|
+
required
|
|
132
|
+
/>
|
|
133
|
+
<div className="flex gap-2">
|
|
134
|
+
<button
|
|
135
|
+
type="submit"
|
|
136
|
+
className="flex-1 rounded bg-amber-600 px-2 py-2 text-white hover:bg-amber-700"
|
|
137
|
+
>
|
|
138
|
+
{editing ? "Update" : "Save"}
|
|
139
|
+
</button>
|
|
140
|
+
{editing && (
|
|
141
|
+
<button
|
|
142
|
+
type="button"
|
|
143
|
+
className="rounded bg-slate-500 px-2 py-2 text-white"
|
|
144
|
+
onClick={() => {
|
|
145
|
+
setEditing(null);
|
|
146
|
+
setForm(empty);
|
|
147
|
+
}}
|
|
148
|
+
>
|
|
149
|
+
Cancel
|
|
150
|
+
</button>
|
|
151
|
+
)}
|
|
152
|
+
</div>
|
|
153
|
+
</form>
|
|
154
|
+
{message && <p className="mt-2 text-sm text-slate-600">{message}</p>}
|
|
155
|
+
</section>
|
|
156
|
+
<section className="rounded-lg border border-slate-200 bg-white p-4 shadow">
|
|
157
|
+
<h2 className="mb-3 font-semibold">All stock out records</h2>
|
|
158
|
+
<div className="overflow-x-auto">
|
|
159
|
+
<table className="w-full min-w-[640px] text-left text-sm">
|
|
160
|
+
<thead>
|
|
161
|
+
<tr className="bg-slate-100">
|
|
162
|
+
<th className="p-2">Part</th>
|
|
163
|
+
<th className="p-2">Qty</th>
|
|
164
|
+
<th className="p-2">Unit $</th>
|
|
165
|
+
<th className="p-2">Total</th>
|
|
166
|
+
<th className="p-2">Date</th>
|
|
167
|
+
<th className="p-2" />
|
|
168
|
+
</tr>
|
|
169
|
+
</thead>
|
|
170
|
+
<tbody>
|
|
171
|
+
{rows.map((r) => (
|
|
172
|
+
<tr key={r._id} className="border-b">
|
|
173
|
+
<td className="p-2">{r.sparePart?.name || ""}</td>
|
|
174
|
+
<td className="p-2">{r.stockOutQuantity}</td>
|
|
175
|
+
<td className="p-2">{r.stockOutUnitPrice}</td>
|
|
176
|
+
<td className="p-2">{r.stockOutTotalPrice}</td>
|
|
177
|
+
<td className="p-2">
|
|
178
|
+
{r.stockOutDate ? new Date(r.stockOutDate).toLocaleDateString() : ""}
|
|
179
|
+
</td>
|
|
180
|
+
<td className="p-2">
|
|
181
|
+
<button
|
|
182
|
+
type="button"
|
|
183
|
+
className="mr-1 rounded bg-amber-100 px-2 py-0.5 text-amber-900"
|
|
184
|
+
onClick={() => startEdit(r)}
|
|
185
|
+
>
|
|
186
|
+
Edit
|
|
187
|
+
</button>
|
|
188
|
+
<button
|
|
189
|
+
type="button"
|
|
190
|
+
className="rounded bg-red-100 px-2 py-0.5 text-red-800"
|
|
191
|
+
onClick={() => onDelete(r._id)}
|
|
192
|
+
>
|
|
193
|
+
Del
|
|
194
|
+
</button>
|
|
195
|
+
</td>
|
|
196
|
+
</tr>
|
|
197
|
+
))}
|
|
198
|
+
</tbody>
|
|
199
|
+
</table>
|
|
200
|
+
</div>
|
|
201
|
+
</section>
|
|
202
|
+
</div>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export default StockOutPage;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function strongPasswordError(password) {
|
|
2
|
+
const p = String(password || "");
|
|
3
|
+
if (p.length < 8) return "Use at least 8 characters";
|
|
4
|
+
if (!/[A-Z]/.test(p)) return "Include an uppercase letter";
|
|
5
|
+
if (!/[a-z]/.test(p)) return "Include a lowercase letter";
|
|
6
|
+
if (!/[0-9]/.test(p)) return "Include a number";
|
|
7
|
+
return null;
|
|
8
|
+
}
|
package/apps/config.js
CHANGED
|
@@ -5,4 +5,17 @@ export const apps = [
|
|
|
5
5
|
description: "Hospital management system by Steve Rogers",
|
|
6
6
|
folder: "HMSR",
|
|
7
7
|
},
|
|
8
|
+
{
|
|
9
|
+
title: "SIMS - Stock Inventory Management",
|
|
10
|
+
value: "sims",
|
|
11
|
+
description: "Stock inventory management by Steve Rogers",
|
|
12
|
+
folder: "SIMS",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
title: "SFMS - Student Fee Management System",
|
|
16
|
+
value: "sfms",
|
|
17
|
+
description: "Student Fee Management System by steve rogers",
|
|
18
|
+
folder: "SFMS"
|
|
19
|
+
|
|
20
|
+
}
|
|
8
21
|
];
|