Kasir Grosir: starter Flutter POS (scaffold awal)
This commit is contained in:
66
lib/main.dart
Normal file
66
lib/main.dart
Normal file
@@ -0,0 +1,66 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'widgets/app_drawer.dart';
|
||||
import 'widgets/template_screen.dart';
|
||||
import 'screens/dashboard.dart';
|
||||
import 'screens/pos.dart';
|
||||
import 'screens/products.dart';
|
||||
import 'screens/customers.dart';
|
||||
import 'screens/suppliers.dart';
|
||||
import 'screens/purchase_orders.dart';
|
||||
import 'screens/accounts_receivable.dart';
|
||||
import 'screens/inventory.dart';
|
||||
import 'screens/reports.dart';
|
||||
import 'screens/settings.dart';
|
||||
import 'screens/users.dart';
|
||||
import 'screens/utilities.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Kasir Grosir - Starter',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.indigo,
|
||||
),
|
||||
home: const HomePage(),
|
||||
routes: {
|
||||
'/dashboard': (_) => const DashboardScreen(),
|
||||
'/pos': (_) => const PosScreen(),
|
||||
'/products': (_) => const ProductsScreen(),
|
||||
'/customers': (_) => const CustomersScreen(),
|
||||
'/suppliers': (_) => const SuppliersScreen(),
|
||||
'/purchase_orders': (_) => const PurchaseOrdersScreen(),
|
||||
'/ar': (_) => const AccountsReceivableScreen(),
|
||||
'/inventory': (_) => const InventoryScreen(),
|
||||
'/reports': (_) => const ReportsScreen(),
|
||||
'/settings': (_) => const SettingsScreen(),
|
||||
'/users': (_) => const UsersScreen(),
|
||||
'/utilities': (_) => const UtilitiesScreen(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HomePage extends StatelessWidget {
|
||||
const HomePage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TemplateScreen(
|
||||
title: 'Kasir Grosir - Home',
|
||||
drawer: const AppDrawer(),
|
||||
body: const Center(child: Text('Selamat datang di Kasir Grosir - pilih menu dari drawer')),
|
||||
actions: const [
|
||||
IconButton(onPressed: _noop, icon: Icon(Icons.search)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static void _noop() {}
|
||||
}
|
||||
26
lib/screens/accounts_receivable.dart
Normal file
26
lib/screens/accounts_receivable.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../widgets/template_screen.dart';
|
||||
import '../widgets/app_drawer.dart';
|
||||
|
||||
class AccountsReceivableScreen extends StatelessWidget {
|
||||
const AccountsReceivableScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const TemplateScreen(
|
||||
title: 'Accounts Receivable',
|
||||
drawer: AppDrawer(),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Piutang (AR)', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
|
||||
SizedBox(height: 12),
|
||||
Text('TODO: Invoices, partial payments, aging report.'),
|
||||
SizedBox(height: 12),
|
||||
Text('Catatan:'),
|
||||
Text('- Implement invoice model and payment entries.'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
27
lib/screens/customers.dart
Normal file
27
lib/screens/customers.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../widgets/template_screen.dart';
|
||||
import '../widgets/app_drawer.dart';
|
||||
|
||||
class CustomersScreen extends StatelessWidget {
|
||||
const CustomersScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const TemplateScreen(
|
||||
title: 'Customers',
|
||||
drawer: AppDrawer(),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Pelanggan', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
|
||||
SizedBox(height: 12),
|
||||
Text('TODO: Customer list, price tier, credit limit, contact info.'),
|
||||
SizedBox(height: 12),
|
||||
Text('Catatan:'),
|
||||
Text('- Simpan informasi credit limit & term_days untuk AR.'),
|
||||
Text('- Integrate quick select from POS.'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
31
lib/screens/dashboard.dart
Normal file
31
lib/screens/dashboard.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../widgets/template_screen.dart';
|
||||
import '../widgets/app_drawer.dart';
|
||||
|
||||
class DashboardScreen extends StatelessWidget {
|
||||
const DashboardScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const TemplateScreen(
|
||||
title: 'Dashboard',
|
||||
drawer: AppDrawer(),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Ringkasan Harian', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
|
||||
SizedBox(height: 12),
|
||||
Text('- Penjualan hari ini: TODO (implement)') ,
|
||||
Text('- Piutang terutang: TODO (implement)'),
|
||||
Text('- Stok kritis: TODO (implement)'),
|
||||
SizedBox(height: 24),
|
||||
Text('Catatan:', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
Text('1. Ambil data penjualan lokal (SQLite) dan agregasi untuk ringkasan.'),
|
||||
Text('2. Buat widget chart sederhana setelah data tersedia.'),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
26
lib/screens/inventory.dart
Normal file
26
lib/screens/inventory.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../widgets/template_screen.dart';
|
||||
import '../widgets/app_drawer.dart';
|
||||
|
||||
class InventoryScreen extends StatelessWidget {
|
||||
const InventoryScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const TemplateScreen(
|
||||
title: 'Inventory',
|
||||
drawer: AppDrawer(),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Stok & Warehousing', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
|
||||
SizedBox(height: 12),
|
||||
Text('TODO: Multi-warehouse, stock adjustment, transfers.'),
|
||||
SizedBox(height: 12),
|
||||
Text('Catatan:'),
|
||||
Text('- Keep stock movements log for audit.'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
51
lib/screens/pos.dart
Normal file
51
lib/screens/pos.dart
Normal file
@@ -0,0 +1,51 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../widgets/template_screen.dart';
|
||||
import '../widgets/app_drawer.dart';
|
||||
|
||||
class PosScreen extends StatelessWidget {
|
||||
const PosScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TemplateScreen(
|
||||
title: 'POS',
|
||||
drawer: const AppDrawer(),
|
||||
fab: FloatingActionButton(
|
||||
onPressed: () {},
|
||||
tooltip: 'New Transaction',
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Row(
|
||||
children: const [
|
||||
Icon(Icons.search),
|
||||
SizedBox(width: 8),
|
||||
Expanded(child: TextField(decoration: InputDecoration.collapsed(hintText: 'Search products'))),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Expanded(
|
||||
child: GridView.builder(
|
||||
itemCount: 12,
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 0.9),
|
||||
itemBuilder: (context, i) {
|
||||
return Card(
|
||||
child: InkWell(
|
||||
onTap: () {},
|
||||
child: Center(child: Text('Product ${i + 1}', textAlign: TextAlign.center)),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
27
lib/screens/products.dart
Normal file
27
lib/screens/products.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../widgets/template_screen.dart';
|
||||
import '../widgets/app_drawer.dart';
|
||||
|
||||
class ProductsScreen extends StatelessWidget {
|
||||
const ProductsScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const TemplateScreen(
|
||||
title: 'Products',
|
||||
drawer: AppDrawer(),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Produk / Katalog', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
|
||||
SizedBox(height: 12),
|
||||
Text('TODO: Implement CRUD produk (SKU, barcode, harga, stok, unit, pack).'),
|
||||
SizedBox(height: 12),
|
||||
Text('Catatan:'),
|
||||
Text('- Buat Room/SQLite entity untuk product.'),
|
||||
Text('- Tambahkan import CSV untuk bulk upload.'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
26
lib/screens/purchase_orders.dart
Normal file
26
lib/screens/purchase_orders.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../widgets/template_screen.dart';
|
||||
import '../widgets/app_drawer.dart';
|
||||
|
||||
class PurchaseOrdersScreen extends StatelessWidget {
|
||||
const PurchaseOrdersScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const TemplateScreen(
|
||||
title: 'Purchase Orders',
|
||||
drawer: AppDrawer(),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Purchase Orders', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
|
||||
SizedBox(height: 12),
|
||||
Text('TODO: Create PO, receive partial/full, update stock & cost.'),
|
||||
SizedBox(height: 12),
|
||||
Text('Catatan:'),
|
||||
Text('- Track expected_date & received_status.'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
26
lib/screens/reports.dart
Normal file
26
lib/screens/reports.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../widgets/template_screen.dart';
|
||||
import '../widgets/app_drawer.dart';
|
||||
|
||||
class ReportsScreen extends StatelessWidget {
|
||||
const ReportsScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const TemplateScreen(
|
||||
title: 'Reports',
|
||||
drawer: AppDrawer(),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Laporan', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
|
||||
SizedBox(height: 12),
|
||||
Text('TODO: Sales per day, per product, AR aging, inventory valuation.'),
|
||||
SizedBox(height: 12),
|
||||
Text('Catatan:'),
|
||||
Text('- Start with CSV export and simple list views.'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
26
lib/screens/settings.dart
Normal file
26
lib/screens/settings.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../widgets/template_screen.dart';
|
||||
import '../widgets/app_drawer.dart';
|
||||
|
||||
class SettingsScreen extends StatelessWidget {
|
||||
const SettingsScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const TemplateScreen(
|
||||
title: 'Settings',
|
||||
drawer: AppDrawer(),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Pengaturan', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
|
||||
SizedBox(height: 12),
|
||||
Text('TODO: Store profile, tax settings, printer config, backup/restore.'),
|
||||
SizedBox(height: 12),
|
||||
Text('Catatan:'),
|
||||
Text('- Add page to configure Bluetooth printer later.'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
26
lib/screens/suppliers.dart
Normal file
26
lib/screens/suppliers.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../widgets/template_screen.dart';
|
||||
import '../widgets/app_drawer.dart';
|
||||
|
||||
class SuppliersScreen extends StatelessWidget {
|
||||
const SuppliersScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const TemplateScreen(
|
||||
title: 'Suppliers',
|
||||
drawer: AppDrawer(),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Supplier', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
|
||||
SizedBox(height: 12),
|
||||
Text('TODO: Supplier master, PO creation and contact info.'),
|
||||
SizedBox(height: 12),
|
||||
Text('Catatan:'),
|
||||
Text('- Implement PO flow: create, send, receive.'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
26
lib/screens/users.dart
Normal file
26
lib/screens/users.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../widgets/template_screen.dart';
|
||||
import '../widgets/app_drawer.dart';
|
||||
|
||||
class UsersScreen extends StatelessWidget {
|
||||
const UsersScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const TemplateScreen(
|
||||
title: 'Users',
|
||||
drawer: AppDrawer(),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('User Management', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
|
||||
SizedBox(height: 12),
|
||||
Text('TODO: Roles & permissions (admin, kasir, gudang, sales).'),
|
||||
SizedBox(height: 12),
|
||||
Text('Catatan:'),
|
||||
Text('- Implement simple local PIN auth for kasir initially.'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
26
lib/screens/utilities.dart
Normal file
26
lib/screens/utilities.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../widgets/template_screen.dart';
|
||||
import '../widgets/app_drawer.dart';
|
||||
|
||||
class UtilitiesScreen extends StatelessWidget {
|
||||
const UtilitiesScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const TemplateScreen(
|
||||
title: 'Utilities',
|
||||
drawer: AppDrawer(),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Utilities', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
|
||||
SizedBox(height: 12),
|
||||
Text('TODO: Import/Export CSV, label printing, backup/restore.'),
|
||||
SizedBox(height: 12),
|
||||
Text('Catatan:'),
|
||||
Text('- Provide CSV templates for bulk import.'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
46
lib/widgets/app_drawer.dart
Normal file
46
lib/widgets/app_drawer.dart
Normal file
@@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'menu_tile.dart';
|
||||
|
||||
class AppDrawer extends StatelessWidget {
|
||||
const AppDrawer({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Drawer(
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
UserAccountsDrawerHeader(
|
||||
accountName: const Text('Toko UMKM'),
|
||||
accountEmail: const Text('user@contoh.com'),
|
||||
currentAccountPicture: const CircleAvatar(child: Text('TK')),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
children: const [
|
||||
MenuTile(icon: Icons.dashboard, title: 'Dashboard', route: '/dashboard'),
|
||||
MenuTile(icon: Icons.point_of_sale, title: 'POS', route: '/pos'),
|
||||
MenuTile(icon: Icons.inventory_2, title: 'Products', route: '/products'),
|
||||
MenuTile(icon: Icons.people, title: 'Customers', route: '/customers'),
|
||||
MenuTile(icon: Icons.local_shipping, title: 'Suppliers', route: '/suppliers'),
|
||||
MenuTile(icon: Icons.playlist_add_check, title: 'Purchase Orders', route: '/purchase_orders'),
|
||||
MenuTile(icon: Icons.money, title: 'Accounts Receivable', route: '/ar'),
|
||||
MenuTile(icon: Icons.warehouse, title: 'Inventory', route: '/inventory'),
|
||||
MenuTile(icon: Icons.bar_chart, title: 'Reports', route: '/reports'),
|
||||
MenuTile(icon: Icons.settings, title: 'Settings', route: '/settings'),
|
||||
MenuTile(icon: Icons.people_alt, title: 'Users', route: '/users'),
|
||||
MenuTile(icon: Icons.build, title: 'Utilities', route: '/utilities'),
|
||||
],
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.exit_to_app),
|
||||
title: const Text('Logout'),
|
||||
onTap: () {},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
28
lib/widgets/menu_tile.dart
Normal file
28
lib/widgets/menu_tile.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MenuTile extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String route;
|
||||
|
||||
const MenuTile({
|
||||
Key? key,
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.route,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
leading: Icon(icon),
|
||||
title: Text(title),
|
||||
onTap: () {
|
||||
Navigator.pop(context); // close drawer
|
||||
if (ModalRoute.of(context)?.settings.name != route) {
|
||||
Navigator.pushNamed(context, route);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
34
lib/widgets/template_screen.dart
Normal file
34
lib/widgets/template_screen.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TemplateScreen extends StatelessWidget {
|
||||
final String title;
|
||||
final Widget body;
|
||||
final List<Widget>? actions;
|
||||
final FloatingActionButton? fab;
|
||||
final Widget? drawer;
|
||||
|
||||
const TemplateScreen({
|
||||
Key? key,
|
||||
required this.title,
|
||||
required this.body,
|
||||
this.actions,
|
||||
this.fab,
|
||||
this.drawer,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(title),
|
||||
actions: actions,
|
||||
),
|
||||
drawer: drawer,
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: body,
|
||||
),
|
||||
floatingActionButton: fab,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user