pharmacy-erp 1.0.0

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 (137) hide show
  1. package/.env.example +34 -0
  2. package/LICENSE +21 -0
  3. package/README.md +479 -0
  4. package/backend/Dockerfile +44 -0
  5. package/backend/app/__init__.py +1 -0
  6. package/backend/app/core/__init__.py +1 -0
  7. package/backend/app/core/audit.py +33 -0
  8. package/backend/app/core/config.py +16 -0
  9. package/backend/app/core/database.py +26 -0
  10. package/backend/app/core/deps.py +49 -0
  11. package/backend/app/core/exceptions.py +43 -0
  12. package/backend/app/core/security.py +32 -0
  13. package/backend/app/main.py +54 -0
  14. package/backend/app/modules/__init__.py +0 -0
  15. package/backend/app/modules/admin/__init__.py +1 -0
  16. package/backend/app/modules/admin/models.py +15 -0
  17. package/backend/app/modules/admin/router.py +353 -0
  18. package/backend/app/modules/auth/__init__.py +0 -0
  19. package/backend/app/modules/auth/models.py +16 -0
  20. package/backend/app/modules/auth/router.py +73 -0
  21. package/backend/app/modules/auth/schemas.py +29 -0
  22. package/backend/app/modules/cash_sessions/__init__.py +0 -0
  23. package/backend/app/modules/cash_sessions/models.py +19 -0
  24. package/backend/app/modules/cash_sessions/router.py +85 -0
  25. package/backend/app/modules/cash_sessions/schemas.py +31 -0
  26. package/backend/app/modules/customers/__init__.py +0 -0
  27. package/backend/app/modules/customers/models.py +37 -0
  28. package/backend/app/modules/customers/router.py +296 -0
  29. package/backend/app/modules/customers/schemas.py +72 -0
  30. package/backend/app/modules/inventory/__init__.py +0 -0
  31. package/backend/app/modules/inventory/models.py +16 -0
  32. package/backend/app/modules/inventory/router.py +150 -0
  33. package/backend/app/modules/inventory/schemas.py +24 -0
  34. package/backend/app/modules/medicines/__init__.py +0 -0
  35. package/backend/app/modules/medicines/models.py +54 -0
  36. package/backend/app/modules/medicines/router.py +631 -0
  37. package/backend/app/modules/medicines/schemas.py +153 -0
  38. package/backend/app/modules/medicines/template_generator.py +118 -0
  39. package/backend/app/modules/notifications/__init__.py +1 -0
  40. package/backend/app/modules/notifications/models.py +15 -0
  41. package/backend/app/modules/notifications/router.py +89 -0
  42. package/backend/app/modules/notifications/schemas.py +22 -0
  43. package/backend/app/modules/organizations/__init__.py +0 -0
  44. package/backend/app/modules/organizations/models.py +16 -0
  45. package/backend/app/modules/organizations/router.py +49 -0
  46. package/backend/app/modules/organizations/schemas.py +31 -0
  47. package/backend/app/modules/reports/__init__.py +0 -0
  48. package/backend/app/modules/reports/router.py +409 -0
  49. package/backend/app/modules/roles/__init__.py +1 -0
  50. package/backend/app/modules/roles/models.py +43 -0
  51. package/backend/app/modules/roles/router.py +89 -0
  52. package/backend/app/modules/roles/schemas.py +55 -0
  53. package/backend/app/modules/sales/__init__.py +0 -0
  54. package/backend/app/modules/sales/models.py +44 -0
  55. package/backend/app/modules/sales/router.py +63 -0
  56. package/backend/app/modules/sales/schemas.py +42 -0
  57. package/backend/app/modules/sales/service.py +128 -0
  58. package/backend/app/modules/suppliers/__init__.py +0 -0
  59. package/backend/app/modules/suppliers/models.py +14 -0
  60. package/backend/app/modules/suppliers/router.py +56 -0
  61. package/backend/app/modules/suppliers/schemas.py +27 -0
  62. package/backend/app/modules/users/__init__.py +0 -0
  63. package/backend/app/modules/users/router.py +74 -0
  64. package/backend/app/seed.py +275 -0
  65. package/backend/data/.gitkeep +1 -0
  66. package/backend/requirements.txt +17 -0
  67. package/bin/cli.js +288 -0
  68. package/docker-compose.dev.yml +42 -0
  69. package/docker-compose.yml +59 -0
  70. package/frontend/Dockerfile +56 -0
  71. package/frontend/next.config.ts +19 -0
  72. package/frontend/package.json +60 -0
  73. package/frontend/pnpm-lock.yaml +5678 -0
  74. package/frontend/pnpm-workspace.yaml +6 -0
  75. package/frontend/postcss.config.mjs +6 -0
  76. package/frontend/public/logo.png +0 -0
  77. package/frontend/src/app/[locale]/(app)/admin/page.tsx +1426 -0
  78. package/frontend/src/app/[locale]/(app)/catalog/products/[id]/page.tsx +505 -0
  79. package/frontend/src/app/[locale]/(app)/catalog/products/page.tsx +753 -0
  80. package/frontend/src/app/[locale]/(app)/customers/[id]/page.tsx +500 -0
  81. package/frontend/src/app/[locale]/(app)/customers/page.tsx +538 -0
  82. package/frontend/src/app/[locale]/(app)/dashboard/page.tsx +175 -0
  83. package/frontend/src/app/[locale]/(app)/inventory/page.tsx +765 -0
  84. package/frontend/src/app/[locale]/(app)/layout.tsx +60 -0
  85. package/frontend/src/app/[locale]/(app)/purchasing/page.tsx +1 -0
  86. package/frontend/src/app/[locale]/(app)/reports/page.tsx +794 -0
  87. package/frontend/src/app/[locale]/(app)/sales/page.tsx +296 -0
  88. package/frontend/src/app/[locale]/(auth)/login/page.tsx +100 -0
  89. package/frontend/src/app/[locale]/(pos)/layout.tsx +33 -0
  90. package/frontend/src/app/[locale]/(pos)/pos/page.tsx +463 -0
  91. package/frontend/src/app/[locale]/(pos)/pos/pos-data.ts +89 -0
  92. package/frontend/src/app/[locale]/layout.tsx +30 -0
  93. package/frontend/src/app/[locale]/page.tsx +6 -0
  94. package/frontend/src/app/globals.css +77 -0
  95. package/frontend/src/app/layout.tsx +23 -0
  96. package/frontend/src/app/print.css +18 -0
  97. package/frontend/src/components/pos/CartPanel.tsx +525 -0
  98. package/frontend/src/components/pos/CashSessionGuard.tsx +255 -0
  99. package/frontend/src/components/pos/CloseSessionModal.tsx +160 -0
  100. package/frontend/src/components/pos/PaymentModal.tsx +266 -0
  101. package/frontend/src/components/pos/PaymentSettingsModal.tsx +472 -0
  102. package/frontend/src/components/pos/ProductCatalog.tsx +140 -0
  103. package/frontend/src/components/pos/SaleReceipt.tsx +133 -0
  104. package/frontend/src/components/ui/badge.tsx +28 -0
  105. package/frontend/src/components/ui/bulk-import-modal.tsx +400 -0
  106. package/frontend/src/components/ui/button.tsx +60 -0
  107. package/frontend/src/components/ui/card.tsx +13 -0
  108. package/frontend/src/components/ui/confirm-dialog.tsx +50 -0
  109. package/frontend/src/components/ui/input.tsx +32 -0
  110. package/frontend/src/components/ui/notification-center.tsx +228 -0
  111. package/frontend/src/components/ui/select.tsx +59 -0
  112. package/frontend/src/components/ui/sidebar.tsx +91 -0
  113. package/frontend/src/components/ui/slide-over.tsx +43 -0
  114. package/frontend/src/components/ui/stat-card.tsx +40 -0
  115. package/frontend/src/components/ui/switch.tsx +38 -0
  116. package/frontend/src/components/ui/topbar.tsx +97 -0
  117. package/frontend/src/i18n/request.ts +13 -0
  118. package/frontend/src/i18n/routing.ts +9 -0
  119. package/frontend/src/lib/api.ts +240 -0
  120. package/frontend/src/lib/constants.ts +30 -0
  121. package/frontend/src/lib/utils.ts +32 -0
  122. package/frontend/src/messages/ar.json +106 -0
  123. package/frontend/src/messages/en.json +106 -0
  124. package/frontend/src/middleware.ts +8 -0
  125. package/frontend/src/store/app-store.ts +37 -0
  126. package/frontend/src/store/auth-store.ts +52 -0
  127. package/frontend/src/store/payment-methods-store.ts +144 -0
  128. package/frontend/src/store/pos-store.ts +344 -0
  129. package/frontend/tailwind.config.ts +85 -0
  130. package/frontend/tsconfig.json +27 -0
  131. package/img/backgraund-logo.png +0 -0
  132. package/img/full-logo.png +0 -0
  133. package/img/logo.png +0 -0
  134. package/nginx/nginx.conf +67 -0
  135. package/package.json +88 -0
  136. package/start.bat +90 -0
  137. package/start.sh +64 -0
@@ -0,0 +1,175 @@
1
+ 'use client';
2
+ import { useState, useEffect } from 'react';
3
+ import { useTranslations } from 'next-intl';
4
+ import { useParams } from 'next/navigation';
5
+ import { StatCard } from '@/components/ui/stat-card';
6
+ import { Card } from '@/components/ui/card';
7
+ import { useAppStore } from '@/store/app-store';
8
+ import { formatMoney } from '@/lib/utils';
9
+ import { api } from '@/lib/api';
10
+ import { CreditCard, Package, AlertTriangle, TrendingUp } from 'lucide-react';
11
+ import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
12
+
13
+
14
+ export default function DashboardPage() {
15
+ const t = useTranslations();
16
+ const params = useParams();
17
+ const locale = params.locale as string;
18
+ const { currency } = useAppStore();
19
+
20
+ const [stats, setStats] = useState({
21
+ todaySales: 0,
22
+ revenue: 0,
23
+ outOfStock: 0,
24
+ lowStock: 0,
25
+ expiring: 0
26
+ });
27
+ const [chartData, setChartData] = useState<{name: string; revenue: number}[]>([]);
28
+ const [recentSales, setRecentSales] = useState<any[]>([]);
29
+
30
+ useEffect(() => {
31
+ async function loadData() {
32
+ try {
33
+ const today = new Date();
34
+ const todayStr = today.toISOString().split('T')[0];
35
+
36
+ // Calculate start of current week (Sunday)
37
+ const startOfWeek = new Date(today);
38
+ startOfWeek.setDate(today.getDate() - today.getDay());
39
+ const weekStartStr = startOfWeek.toISOString().split('T')[0];
40
+
41
+ const [outOfStock, lowStock, expiring, recent, dailySales, weeklyRange] = await Promise.all([
42
+ api.getOutOfStock().catch(() => []),
43
+ api.getLowStock().catch(() => []),
44
+ api.getExpiring().catch(() => []),
45
+ api.getSales().catch(() => []),
46
+ api.getDailySales(todayStr).catch(() => ({ total_sales_count: 0, total_amount: 0 })),
47
+ api.getSalesRange(weekStartStr, todayStr).catch(() => null)
48
+ ]);
49
+
50
+ setStats({
51
+ todaySales: (dailySales as any)?.total_amount || 0,
52
+ revenue: (weeklyRange as any)?.total_revenue || (dailySales as any)?.total_amount || 0,
53
+ outOfStock: Array.isArray(outOfStock) ? outOfStock.length : 0,
54
+ lowStock: Array.isArray(lowStock) ? lowStock.length : 0,
55
+ expiring: Array.isArray(expiring) ? expiring.length : 0
56
+ });
57
+
58
+ const recentArray = Array.isArray(recent) ? recent : (recent as any)?.items || [];
59
+ setRecentSales(recentArray.slice(0, 5));
60
+
61
+ // Build weekly chart data
62
+ const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
63
+ const weekData = dayNames.map((name, i) => {
64
+ const dayDate = new Date(startOfWeek);
65
+ dayDate.setDate(startOfWeek.getDate() + i);
66
+ return { name, revenue: 0, date: dayDate.toISOString().split('T')[0] };
67
+ });
68
+
69
+ if ((weeklyRange as any)?.daily_breakdown) {
70
+ (weeklyRange as any).daily_breakdown.forEach((d: any) => {
71
+ const found = weekData.find(w => w.date === d.date);
72
+ if (found) found.revenue = d.revenue || d.total_revenue || 0;
73
+ });
74
+ }
75
+
76
+ setChartData(weekData.map(({ name, revenue }) => ({ name, revenue })));
77
+ } catch (error) {
78
+ console.error('Failed to load dashboard data', error);
79
+ }
80
+ }
81
+ loadData();
82
+ }, []);
83
+
84
+ return (
85
+ <div className="space-y-6">
86
+ <div className="flex justify-between items-end">
87
+ <div>
88
+ <h1 className="text-2xl font-bold text-foreground">Dashboard</h1>
89
+ <p className="text-gray-500">Welcome back. Here is what's happening today.</p>
90
+ </div>
91
+ </div>
92
+
93
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-4">
94
+ <StatCard
95
+ title={t('dashboard.todaySales')}
96
+ value={formatMoney(stats.todaySales, currency)}
97
+ icon={<CreditCard size={20} />}
98
+ />
99
+ <StatCard
100
+ title={t('dashboard.revenue')}
101
+ value={formatMoney(stats.revenue, currency)}
102
+ icon={<TrendingUp size={20} />}
103
+ />
104
+ <StatCard
105
+ title="Out of Stock"
106
+ value={`${stats.outOfStock} Items`}
107
+ icon={<Package size={20} className="text-danger" />}
108
+ />
109
+ <StatCard
110
+ title={t('dashboard.lowStock')}
111
+ value={`${stats.lowStock} Items`}
112
+ icon={<AlertTriangle size={20} className="text-warning" />}
113
+ />
114
+ <StatCard
115
+ title={t('dashboard.expiringMedicines')}
116
+ value={`${stats.expiring} Items`}
117
+ icon={<AlertTriangle size={20} className="text-danger" />}
118
+ />
119
+ </div>
120
+
121
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
122
+ <Card className="col-span-1 lg:col-span-2 p-4 md:p-6">
123
+ <h3 className="font-semibold text-lg mb-4">Sales Overview</h3>
124
+ <div className="h-64 md:h-72 w-full">
125
+ <ResponsiveContainer width="100%" height="100%">
126
+ <AreaChart data={chartData} margin={{ top: 10, right: 10, left: 0, bottom: 0 }}>
127
+ <defs>
128
+ <linearGradient id="colorSales" x1="0" y1="0" x2="0" y2="1">
129
+ <stop offset="5%" stopColor="var(--brand-primary)" stopOpacity={0.3}/>
130
+ <stop offset="95%" stopColor="var(--brand-primary)" stopOpacity={0}/>
131
+ </linearGradient>
132
+ </defs>
133
+ <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="var(--border)" />
134
+ <XAxis dataKey="name" axisLine={false} tickLine={false} tick={{fill: 'var(--foreground)', opacity: 0.5}} dy={10} />
135
+ <YAxis axisLine={false} tickLine={false} tick={{fill: 'var(--foreground)', opacity: 0.5}} tickFormatter={(value) => `${value}`} />
136
+ <Tooltip
137
+ contentStyle={{ backgroundColor: 'var(--background)', borderColor: 'var(--border)', borderRadius: '8px' }}
138
+ itemStyle={{ color: 'var(--brand-primary)' }}
139
+ formatter={(value: number) => formatMoney(value, currency)}
140
+ />
141
+ <Area type="monotone" dataKey="revenue" name="Revenue" stroke="var(--brand-primary)" strokeWidth={2} fillOpacity={1} fill="url(#colorSales)" />
142
+ </AreaChart>
143
+ </ResponsiveContainer>
144
+ </div>
145
+ </Card>
146
+
147
+ <Card className="p-6 flex flex-col">
148
+ <h3 className="font-semibold text-lg mb-4">{t('dashboard.recentTransactions')}</h3>
149
+ <div className="space-y-4 flex-1 overflow-y-auto">
150
+ {recentSales.length > 0 ? (
151
+ recentSales.map((sale: any) => (
152
+ <div key={sale.id} className="flex justify-between items-center p-3 hover:bg-surface rounded-lg transition-colors border border-transparent hover:border-border">
153
+ <div>
154
+ <div className="font-medium text-sm">{sale.invoice_number || sale.id.substring(0, 8)}</div>
155
+ <div className="text-xs text-gray-500">{sale.customer?.name || 'Walk-in Customer'}</div>
156
+ </div>
157
+ <div className="text-right">
158
+ <div className="font-medium text-sm">{formatMoney(sale.total_amount || 0, currency)}</div>
159
+ <div className="text-xs text-success">Completed</div>
160
+ </div>
161
+ </div>
162
+ ))
163
+ ) : (
164
+ <div className="flex-1 flex flex-col items-center justify-center text-center py-8">
165
+ <CreditCard size={32} className="text-foreground/20 mb-2" />
166
+ <p className="text-sm text-foreground/40">No recent transactions</p>
167
+ <p className="text-xs text-foreground/30 mt-1">Sales will appear here once processed</p>
168
+ </div>
169
+ )}
170
+ </div>
171
+ </Card>
172
+ </div>
173
+ </div>
174
+ );
175
+ }