MODEL
class Place {
int? id; // Nullable, because the ID will be auto-generated by the database
String name;
int city_id;
// Constructor to create a Place object directly with a name
Place({required this.name, required this.city_id});
// Constructor to create a Place from a Map (e.g., from a database row)
Place.fromMap(Map<String, dynamic> map)
: id = map['id'],
name = map['name'],
city_id = map['city_id'];
// Method to convert a City object back to a Map (useful for inserting/updating)
Map<String, dynamic> toMap() {
return {'name': name, 'city_id': city_id};
API
class API {
static final String _baseurl = 'http://127.0.0.1:4321';
Future<http.Response> getAllCities() async {
String url =
'$_baseurl/city'; // Assuming the endpoint for cities is /cities
var response = await http.get(Uri.parse(url));
return response;
Future<http.Response> getAllplaces(String name) async {
String url =
'$_baseurl/place?name=$name'; // Assuming 'name' is a query parameter
try {
var response = await http.get(
Uri.parse(url),
headers: {"Content-Type": "application/json"},
);
return response;
} catch (e) {
throw Exception('Error fetching places: $e');
Future<bool> deleteplace(String placename, String cityname) async {
String url = '$_baseurl/deleteplace';
try {
var response = await http.delete(
Uri.parse(url),
headers: {"Content-Type": "application/json"},
body: json.encode({
'placename': placename,
'cityname': cityname
}), // Send the city data
);
if (response.statusCode == 201) {
return true; // Success
} else {
return false; // Failed to add city
} catch (e) {
throw Exception('Error Deleting city: $e');
Future<bool> addplace(String placename, String cityname) async {
String url = '$_baseurl/addplace';
try {
var response = await http.post(
Uri.parse(url),
headers: {"Content-Type": "application/json"},
body: json.encode({
'placename': placename,
'cityname': cityname
}), // Send the city data
);
if (response.statusCode == 200) {
return true; // Success
} else {
return false; // Failed to add city
} catch (e) {
throw Exception('Error adding city: $e');
Add Place Screen
class AddPLace extends StatefulWidget {
const AddPLace({super.key});
@override
State<AddPLace> createState() => _AddPLaceState();
class _AddPLaceState extends State<AddPLace> {
API api = API();
bool _isLoading = false;
List<City> cityList = [];
final TextEditingController _placecontroller = TextEditingController();
String? selectedCity;
Future<void> _addplace() async {
if (selectedCity == null || _placecontroller.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Please Select city name or Enter Place Name')),
);
return;
}
setState(() {
_isLoading = true;
});
try {
// Call the API method to save the city
bool success = await api.addplace(_placecontroller.text, selectedCity!);
if (success) {
// Show success message
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Place added successfully')),
);
_placecontroller.clear();
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to add Place')),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')),
);
} finally {
setState(() {
_isLoading = false;
});
// Fetching cities from the API
Future<void> _getCities() async {
setState(() {
_isLoading = true;
});
try {
var response = await api.getAllCities();
if (response.statusCode == 200) {
List<dynamic> mapList = jsonDecode(response.body);
cityList = mapList.map((e) => City.fromMap(e)).toList();
// Set the first city as the selected city by default
selectedCity = cityList.isNotEmpty ? cityList[0].name : null;
// Fetch places for the default selected city
// if (selectedCity != null) {
// _getplaces(selectedCity!);
// }
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Cities fetched successfully')),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to fetch cities')),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')),
);
} finally {
setState(() {
_isLoading = false;
});
@override
void initState() {
super.initState();
_getCities(); // Fetch the cities when the screen loads
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
preferredSize:
const Size.fromHeight(150), // Adjusted height for better spacing
child: AppBar(
automaticallyImplyLeading: false, // Removes default back button
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.teal.shade700, Colors.teal.shade400],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Center(
// Ensures content is centered
child: Column(
mainAxisAlignment:
MainAxisAlignment.center, // Centers content vertically
children: [
const Text(
"Add Place",
style: TextStyle(
color: Colors.white,
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
Text(
"Set up and manage a new traffic control post location",
textAlign: TextAlign.center, // Centers text horizontally
style: TextStyle(
color: Colors
.grey[200], // Use light grey for better visibility
fontSize: 16,
fontWeight: FontWeight.normal,
),
),
],
),
),
),
),
),
body: Stack(
children: [
// Main content
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Enter place name:'),
const SizedBox(height: 10),
TextFormField(
controller: _placecontroller, // Connect the controller
decoration: const InputDecoration(
labelText: 'Enter place name',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.location_city_rounded),
),
),
const SizedBox(height: 10),
// Dropdown for Place
const Text('Select City:'),
const SizedBox(height: 10),
Container(
width: double.infinity,
child: DropdownButton<String?>(
isExpanded: true,
hint: const Text(
'Choose City',
style: TextStyle(fontSize: 12),
),
value: selectedCity,
items: cityList.isEmpty
?[
const DropdownMenuItem(
value: null,
child: Text('No cities available'),
),
] : cityList.map((e) => DropdownMenuItem<String>(
value: e
.name, // The value should be a String, representing the city name
child: Text(e.name),
))
.toList(),
onChanged: (String? value) {
setState(() {
selectedCity =
value; // Update the selected city to the string value
});
}, ), ), ], ), ),
// Save Button Positioned at bottom
Positioned(
bottom: 90, // Adjust the distance from the bottom
left: 16,
right: 16,
child: SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.teal,
minimumSize: const Size.fromHeight(50),
elevation: 5,
),
onPressed: () {
_addplace();
setState(() {});
print('Save button pressed');
},
child: const Text(
'Save',
style: TextStyle(fontSize: 16, color: Colors.white),
), ), ), ),
// Back Button Positioned at bottom
Positioned(
bottom: 30, // Adjust the distance from the bottom
left: 16,
right: 16,
child: SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.teal,
minimumSize: const Size.fromHeight(50),
elevation: 5,
),
onPressed: () {
// Add functionality for 'Back'
Navigator.pop(context); // Go back to the previous screen
},
child: const Text(
'Back',
style: TextStyle(fontSize: 16, color: Colors.white),
), ), ), ), ],
),
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.notifications),
label: 'Notifications',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'Profile',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'Settings',
),
],
currentIndex: 1,
selectedItemColor: Colors.teal,
unselectedItemColor: Colors.grey,
showUnselectedLabels: true,
onTap: (index) {
setState(() {});
}, ), ); }}
PlaceDetailScreen
class PlaceDetailScreen extends StatefulWidget {
const PlaceDetailScreen({super.key});
@override
State<PlaceDetailScreen> createState() => _PlaceDetailScreenState();
class _PlaceDetailScreenState extends State<PlaceDetailScreen> {
API api = API();
bool _isLoading = false;
List<Place> placeList = [];
List<City> cityList = [];
String? selectedCity; // To hold the selected city for the dropdown
// Fetching the places based on the selected city
Future<void> _getplaces(String name) async {
if (name.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Please Pass a city name')),
);
return;
setState(() {
_isLoading = true;
});
try {
var response = await api.getAllplaces(name);
if (response.statusCode == 200) {
List<dynamic> placemap = json.decode(response.body);
placeList = placemap.map((e) => Place.fromMap(e)).toList();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Place fetched successfully')),
);
} else if (response.statusCode == 404) {
placeList = [];
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('No places found for the specified city')),
);
} else {
placeList = [];
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to fetch places')),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')),
);
} finally {
setState(() {
_isLoading = false;
});
} }
Future<void> _deleteplace(String placename, String cityname) async {
if (placename.isEmpty || cityname.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Please Pass a city name and placename')),
);
return;
setState(() {
_isLoading = true;
});
try {
// Call the API method to delete the city
bool success = await api.deleteplace(placename, cityname);
if (success) {
// Show success message
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Place deleted successfully')),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to Delete Place')),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')),
);
} finally {
setState(() {
_isLoading = false;
});
} }
// Fetching cities from the API
Future<void> _getCities() async {
setState(() {
_isLoading = true;
});
try {
var response = await api.getAllCities();
if (response.statusCode == 200) {
List<dynamic> mapList = jsonDecode(response.body);
cityList = mapList.map((e) => City.fromMap(e)).toList();
// Set the first city as the selected city by default
selectedCity = cityList.isNotEmpty ? cityList[0].name : null;
// Fetch places for the default selected city
if (selectedCity != null) {
_getplaces(selectedCity!);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Cities fetched successfully')),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to fetch cities')),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')),
);
} finally {
setState(() {
_isLoading = false;
});
} }
@override
void initState() {
super.initState();
_getCities(); // Fetch the cities when the screen loads
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
preferredSize: const Size.fromHeight(150),
child: AppBar(
automaticallyImplyLeading: false,
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.teal.shade700, Colors.teal.shade400],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
), ),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
"Place",
style: TextStyle(
color: Colors.white,
fontSize: 22,
fontWeight: FontWeight.bold,
), ),
const SizedBox(height: 10),
Text(
"Set up and manage Places",
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.grey[200],
fontSize: 16,
fontWeight: FontWeight.normal,
), ), ], ), ), ), ),
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.teal,
onPressed: () async {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => AddPLace()),
);
},
child: const Text(
'+',
style: TextStyle(
fontSize: 30,
color: Colors.white,
), ), ),
body: Stack(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
// Dropdown for selecting a city
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.black, // Set the border color
),
color: Colors.white, // Set the background color
),
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 5), // Add padding inside the container
child: DropdownButton<String>(
value: selectedCity,
hint: Text("Select City",
style: TextStyle(color: Colors.black)),
onChanged: (newValue) {
setState(() {
selectedCity = newValue;
});
if (selectedCity != null) {
_getplaces(
selectedCity!); // Fetch places for selected city
},
isExpanded:
true, // Make the dropdown expand to fit the container width
items: cityList.map((city) {
return DropdownMenuItem<String>(
value: city.name,
child: Text(
city.name,
style:
TextStyle(color: Colors.black), // Set text color
),
);
}).toList(),
),
),
// Show loading indicator while fetching data
if (_isLoading)
const Center(child: CircularProgressIndicator()),
// Display the list of places
Expanded(
child: placeList.length > 0
? ListView.builder(
itemCount: placeList.length,
itemBuilder: (context, index) {
Place place = placeList[index];
return Card(
margin: const EdgeInsets.all(8.0),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
'Place ID: ${place.id} \nPlace Name: ${place.name}',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
), ),
SizedBox(height: 8),
], ),
SizedBox(
width: 50,
),
IconButton(
icon: const Icon(Icons.delete,
color: Colors.teal),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title:
const Text("Delete place"),
content: const Text(
"Are you sure you want to Delete Place ?",
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text("Cancel"), ),
TextButton(
onPressed: () {
_deleteplace(place.name,
selectedCity!);
setState(() {
_getplaces(
selectedCity!);
});
Navigator.pop(context);
},
child: const Text(
"Delete",
style: TextStyle(
color: Colors.red),
), ), ], ); }, ); }, ), ], ), ),
); },
: Center(
child: Text('No Place Found'),
)), ], ),
),
Positioned(
bottom: 30,
left: 16,
right: 16,
child: SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.teal,
minimumSize: const Size.fromHeight(50),
elevation: 5,
),
onPressed: () {
Navigator.pop(context);
},
child: const Text( 'Back',style: TextStyle(fontSize: 16, color: Colors.white),), ), ), ), ],
),