add rudimentary adding and listing of objects
This commit is contained in:
parent
cead02abe8
commit
e3dd339ffe
Binary file not shown.
5
lab2/src-tauri/Cargo.lock
generated
5
lab2/src-tauri/Cargo.lock
generated
@ -45,9 +45,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.69"
|
||||
version = "1.0.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
|
||||
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
|
||||
|
||||
[[package]]
|
||||
name = "async-attributes"
|
||||
@ -1406,6 +1406,7 @@ checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
|
||||
name = "ice-production"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
"dotenv",
|
||||
"serde",
|
||||
|
@ -19,6 +19,7 @@ sqlx = { version = "0.6.2", features = ["runtime-async-std-native-tls", "mysql",
|
||||
async-std = {version = "1.12.0", features = ["attributes"] }
|
||||
serde_json = "1.0"
|
||||
dotenv = "0.15.0"
|
||||
anyhow = "1.0.70"
|
||||
|
||||
[features]
|
||||
# this feature is used for production builds or when `devPath` points to the filesystem
|
||||
|
@ -5,11 +5,16 @@ use std::{env, process::exit};
|
||||
use dotenv::dotenv;
|
||||
|
||||
use serde::{Serialize, Deserialize};
|
||||
use sqlx::{SqlitePool, sqlite::SqlitePoolOptions, Pool, Sqlite};
|
||||
use sqlx::{SqlitePool, sqlite::SqlitePoolOptions, Pool, Sqlite, Row};
|
||||
use tauri::State;
|
||||
|
||||
// TODO: use transaction to revert changes, if failed to insert rows
|
||||
|
||||
type Id = i64;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Manager {
|
||||
id: i64,
|
||||
id: Id,
|
||||
first_name: String,
|
||||
surname: String,
|
||||
phone_number: Option<String>,
|
||||
@ -17,42 +22,114 @@ struct Manager {
|
||||
email: Option<String>
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ManagerData {
|
||||
first_name: String,
|
||||
surname: String,
|
||||
phone_number: Option<String>,
|
||||
title: String,
|
||||
email: Option<String>
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Factory {
|
||||
id: Id,
|
||||
name: String,
|
||||
location: String,
|
||||
floor_size: f64,
|
||||
manager_id: Id
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
struct FactoryData {
|
||||
name: String,
|
||||
location: String,
|
||||
floor_size: f64
|
||||
}
|
||||
|
||||
struct Database {
|
||||
pool: Pool<Sqlite>
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
enum AddResponse {
|
||||
Success(i64)
|
||||
struct EntryID {
|
||||
id: Id
|
||||
}
|
||||
|
||||
async fn add_manager(db: &Database, manager: &ManagerData) -> anyhow::Result<Id> {
|
||||
let entry = sqlx::query_as!(
|
||||
EntryID,
|
||||
r#"
|
||||
INSERT INTO manager
|
||||
(`FIRST_NAME`, `SURNAME`, `PHONE_NUMBER`, `TITLE`, `EMAIL`)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?)
|
||||
RETURNING ID as id
|
||||
"#,
|
||||
manager.first_name,
|
||||
manager.surname,
|
||||
manager.phone_number,
|
||||
manager.title,
|
||||
manager.email
|
||||
).fetch_one(&db.pool).await?;
|
||||
Ok(entry.id)
|
||||
}
|
||||
|
||||
async fn add_factory(db: &Database, manager_id: Id, factory: &FactoryData) -> anyhow::Result<Id> {
|
||||
let entry = sqlx::query_as!(
|
||||
EntryID,
|
||||
r#"
|
||||
INSERT INTO factory
|
||||
(`NAME`, `LOCATION`, `FLOOR_SIZE`, `FK_MANAGER_ID`)
|
||||
VALUES
|
||||
(?, ?, ?, ?)
|
||||
RETURNING ID as id
|
||||
"#,
|
||||
factory.name,
|
||||
factory.location,
|
||||
factory.floor_size,
|
||||
manager_id
|
||||
).fetch_one(&db.pool).await?;
|
||||
Ok(entry.id)
|
||||
}
|
||||
|
||||
async fn list_factories_db(db: &Database) -> anyhow::Result<Vec<Factory>> {
|
||||
let factories = sqlx::query_as!(
|
||||
Factory,
|
||||
r#"
|
||||
SELECT ID as id, NAME as name, LOCATION as location, FLOOR_SIZE as floor_size, FK_MANAGER_ID as manager_id FROM factory
|
||||
"#,
|
||||
).fetch_all(&db.pool).await?;
|
||||
Ok(factories)
|
||||
}
|
||||
|
||||
async fn list_managers_db(db: &Database) -> anyhow::Result<Vec<Manager>> {
|
||||
let managers = sqlx::query_as!(
|
||||
Manager,
|
||||
r#"
|
||||
SELECT ID as id, FIRST_NAME as first_name, SURNAME as surname, TITLE as title, EMAIL as email, PHONE_NUMBER as phone_number FROM manager
|
||||
"#,
|
||||
).fetch_all(&db.pool).await?;
|
||||
Ok(managers)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn add_manager(
|
||||
first_name: &str,
|
||||
surname: &str,
|
||||
phone_number: Option<&str>,
|
||||
title: &str,
|
||||
email: Option<&str>,
|
||||
database: State<'_, Database>
|
||||
) -> Result<AddResponse, ()> {
|
||||
let id = sqlx::query_as!(
|
||||
Manager,
|
||||
r#"
|
||||
INSERT INTO manager
|
||||
(`FIRST_NAME`, `SURNAME`, `PHONE_NUMBER`, `TITLE`, `EMAIL`)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?)
|
||||
"#,
|
||||
first_name,
|
||||
surname,
|
||||
phone_number,
|
||||
title,
|
||||
email
|
||||
).execute(&database.pool)
|
||||
.await.map_err(|_| ())?
|
||||
.last_insert_rowid();
|
||||
async fn add_manager_factory(
|
||||
factory: FactoryData,
|
||||
manager: ManagerData,
|
||||
db: State<'_, Database>
|
||||
) -> Result<(Id, Id), ()> {
|
||||
let manager_id = add_manager(&db, &manager).await.unwrap();
|
||||
let factory_id = add_factory(&db, manager_id, &factory).await.unwrap();
|
||||
Ok((factory_id, manager_id))
|
||||
}
|
||||
|
||||
Ok(AddResponse::Success(id))
|
||||
#[tauri::command]
|
||||
async fn list_managers(db: State<'_, Database>) -> Result<Vec<Manager>, ()> {
|
||||
Ok(list_managers_db(&db).await.unwrap()) // TODO: handle .unwrap()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn list_factories(db: State<'_, Database>) -> Result<Vec<Factory>, ()> {
|
||||
Ok(list_factories_db(&db).await.unwrap()) // TODO: handle .unwrap()
|
||||
}
|
||||
|
||||
#[async_std::main]
|
||||
@ -76,7 +153,9 @@ async fn main() {
|
||||
tauri::Builder::default()
|
||||
.manage(Database { pool })
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
add_manager
|
||||
list_factories,
|
||||
list_managers,
|
||||
add_manager_factory
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
@ -1,11 +1,12 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { Form, TextInput, NumberInput } from "carbon-components-svelte";
|
||||
import type { FactoryData } from "./api"
|
||||
|
||||
export const factory = {
|
||||
export let factory: FactoryData = {
|
||||
name: "",
|
||||
location: "",
|
||||
floorSize: 1
|
||||
floor_size: 1
|
||||
}
|
||||
|
||||
// Name validation
|
||||
@ -24,13 +25,13 @@
|
||||
export const validate = () => {
|
||||
validateName()
|
||||
validateLocation()
|
||||
return !showLocationInvalid && !showNameInvalid && factory.floorSize >= 1
|
||||
return !showLocationInvalid && !showNameInvalid && factory.floor_size >= 1
|
||||
};
|
||||
|
||||
export const reset = () => {
|
||||
factory.name = ""
|
||||
factory.location = ""
|
||||
factory.floorSize = 1
|
||||
factory.floor_size = 1
|
||||
showNameInvalid = false
|
||||
showLocationInvalid = false
|
||||
};
|
||||
@ -54,7 +55,7 @@
|
||||
labelText="Location"
|
||||
/>
|
||||
<NumberInput
|
||||
bind:value={factory.floorSize}
|
||||
bind:value={factory.floor_size}
|
||||
required
|
||||
hideSteppers
|
||||
min={1}
|
||||
|
@ -1,18 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { Form, TextInput } from "carbon-components-svelte";
|
||||
import type { ManagerData } from "./api";
|
||||
|
||||
export let manager = {
|
||||
firstName: "",
|
||||
surname: "",
|
||||
phoneNumber: "",
|
||||
title: "",
|
||||
email: ""
|
||||
export let manager: ManagerData = {
|
||||
first_name: "",
|
||||
surname: "",
|
||||
phone_number: "",
|
||||
title: "",
|
||||
email: ""
|
||||
}
|
||||
|
||||
// First name validation
|
||||
let showFirstNameInvalid = false;
|
||||
function validateFirstName() {
|
||||
showFirstNameInvalid = manager.firstName === ""
|
||||
showFirstNameInvalid = manager.first_name === ""
|
||||
}
|
||||
|
||||
// Surname validation
|
||||
@ -36,9 +37,9 @@
|
||||
};
|
||||
|
||||
export const reset = () => {
|
||||
manager.firstName = ""
|
||||
manager.first_name = ""
|
||||
manager.surname = ""
|
||||
manager.phoneNumber = ""
|
||||
manager.phone_number = ""
|
||||
manager.title = ""
|
||||
manager.email = ""
|
||||
showFirstNameInvalid = false
|
||||
@ -50,7 +51,7 @@
|
||||
|
||||
<Form class="flex flex-col gap-1rem" title="Manager">
|
||||
<TextInput
|
||||
bind:value={manager.firstName}
|
||||
bind:value={manager.first_name}
|
||||
invalidText={"First name can't be empty"}
|
||||
bind:invalid={showFirstNameInvalid}
|
||||
on:input={validateFirstName}
|
||||
@ -74,7 +75,7 @@
|
||||
labelText="Title"
|
||||
/>
|
||||
<TextInput
|
||||
bind:value={manager.phoneNumber}
|
||||
bind:value={manager.phone_number}
|
||||
labelText="Phone number"
|
||||
/>
|
||||
<TextInput
|
||||
|
72
lab2/src/lib/api.ts
Normal file
72
lab2/src/lib/api.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
import { writable } from 'svelte/store'
|
||||
|
||||
export interface Manager {
|
||||
id: number,
|
||||
first_name: string,
|
||||
surname: string,
|
||||
phone_number?: string,
|
||||
title: string,
|
||||
email?: string
|
||||
}
|
||||
export interface ManagerData {
|
||||
first_name: string,
|
||||
surname: string,
|
||||
phone_number?: string,
|
||||
title: string,
|
||||
email?: string
|
||||
}
|
||||
|
||||
export interface Factory {
|
||||
id: number,
|
||||
name: string,
|
||||
location: string,
|
||||
floor_size: number,
|
||||
manager_id: number
|
||||
}
|
||||
export interface FactoryData {
|
||||
name: string,
|
||||
location: string,
|
||||
floor_size: number
|
||||
}
|
||||
|
||||
export let factories = writable<Factory[]>([])
|
||||
export let managers = writable<Manager[]>([])
|
||||
|
||||
export async function list_factories(): Promise<Factory[]> {
|
||||
return invoke("list_factories")
|
||||
}
|
||||
|
||||
export async function list_managers(): Promise<Manager[]> {
|
||||
return invoke("list_managers")
|
||||
}
|
||||
|
||||
// Result -> Promise<[factory_id, manager_id]>
|
||||
export async function add_manager_factory(factory: FactoryData, manager: ManagerData): Promise<[number, number]> {
|
||||
// TODO: For now always assume success
|
||||
// TODO: handle error
|
||||
let [factory_id, manager_id] = await invoke<[number, number]>("add_manager_factory", { factory, manager })
|
||||
|
||||
factories.update((factories) => {
|
||||
factories.push({
|
||||
id: factory_id,
|
||||
manager_id,
|
||||
...factory
|
||||
})
|
||||
return factories
|
||||
})
|
||||
managers.update((managers) => {
|
||||
managers.push({ id: manager_id, ...manager })
|
||||
return managers
|
||||
})
|
||||
|
||||
return [factory_id, manager_id]
|
||||
}
|
||||
|
||||
export async function edit_manager(id: number, manager: ManagerData): Promise<void> {
|
||||
// TODO: invoke("edit_manager")
|
||||
}
|
||||
|
||||
export async function edit_factory(id: number, factory: FactoryData): Promise<void> {
|
||||
// TODO: invoke("edit_factory")
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import {
|
||||
DataTable,
|
||||
Toolbar,
|
||||
@ -8,40 +8,73 @@
|
||||
ToolbarSearch,
|
||||
Pagination,
|
||||
Modal,
|
||||
} from "carbon-components-svelte";
|
||||
import TrashCan from "carbon-icons-svelte/lib/TrashCan.svelte";
|
||||
import Edit from "carbon-icons-svelte/lib/Edit.svelte";
|
||||
import ManagerForm from "../lib/ManagerForm.svelte";
|
||||
import FactoryForm from "../lib/FactoryForm.svelte";
|
||||
} from "carbon-components-svelte"
|
||||
import TrashCan from "carbon-icons-svelte/lib/TrashCan.svelte"
|
||||
import Edit from "carbon-icons-svelte/lib/Edit.svelte"
|
||||
import ManagerForm from "../lib/ManagerForm.svelte"
|
||||
import FactoryForm from "../lib/FactoryForm.svelte"
|
||||
import { add_manager_factory, edit_factory, edit_manager, factories, list_factories, list_managers, managers } from "../lib/api"
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
let rows = [
|
||||
{ id: "a", name: "Load Balancer 3", port: 3000, rule: "Round robin" },
|
||||
{ id: "b", name: "Load Balancer 1", port: 443, rule: "Round robin" },
|
||||
{ id: "c", name: "Load Balancer 2", port: 80, rule: "DNS delegation" },
|
||||
{ id: "d", name: "Load Balancer 6", port: 3000, rule: "Round robin" },
|
||||
{ id: "e", name: "Load Balancer 4", port: 443, rule: "Round robin" },
|
||||
{ id: "f", name: "Load Balancer 5", port: 80, rule: "DNS delegation" },
|
||||
];
|
||||
var rows = []
|
||||
|
||||
onMount(async () => {
|
||||
$managers = await list_managers()
|
||||
$factories = await list_factories()
|
||||
console.log($managers)
|
||||
})
|
||||
|
||||
$: {
|
||||
console.log($factories)
|
||||
rows = $factories.map(factory => {
|
||||
let manager = $managers.find(m => m.id == factory.manager_id)
|
||||
return {
|
||||
id: factory.id,
|
||||
factory_name: factory.name,
|
||||
factory_location: factory.location,
|
||||
factory_floor_size: factory.floor_size,
|
||||
manager_fullname: `${manager.first_name} ${manager.surname}`
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let isModalShown = false
|
||||
let resetFactoryForm, validateFactoryForm, factoryData
|
||||
let resetManagerForm, validateManagerForm, managerData
|
||||
|
||||
let activeDelete = false
|
||||
let activeUpdate = false
|
||||
let selectedRowIds = []
|
||||
let filteredRowIds = []
|
||||
|
||||
let resetFactoryForm, validateFactoryForm;
|
||||
let resetManagerForm, validateManagerForm;
|
||||
let showCreateModal = false;
|
||||
$: if (showCreateModal) {
|
||||
resetFactoryForm()
|
||||
resetManagerForm()
|
||||
let currentlyEditingId = undefined
|
||||
let showUpdateModal = factory_id => {
|
||||
currentlyEditingId = factory_id
|
||||
|
||||
let factory = $factories.find(factory => factory.id == factory_id)
|
||||
let manager = $managers.find(manager => manager.id == factory.manager_id)
|
||||
|
||||
factoryData.name = factory.name
|
||||
factoryData.location = factory.location
|
||||
factoryData.floor_size = factory.floor_size
|
||||
managerData.first_name = manager.first_name
|
||||
managerData.surname = manager.surname
|
||||
managerData.phone_number = manager.phone_number
|
||||
managerData.title = manager.title
|
||||
managerData.email = manager.email
|
||||
|
||||
isModalShown = true
|
||||
}
|
||||
|
||||
let headers = [
|
||||
{ key: "name", value: "Name" },
|
||||
{ key: "port", value: "Port" },
|
||||
{ key: "rule", value: "Rule" },
|
||||
{ key: "update-btn", empty: true, width: "5rem" },
|
||||
]
|
||||
let showCreateModal = () => {
|
||||
currentlyEditingId = undefined
|
||||
resetFactoryForm()
|
||||
resetManagerForm()
|
||||
isModalShown = true
|
||||
}
|
||||
|
||||
let closeModal = () => {
|
||||
isModalShown = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<DataTable
|
||||
@ -50,7 +83,13 @@
|
||||
batchSelection={activeDelete}
|
||||
sortable
|
||||
bind:selectedRowIds
|
||||
{headers}
|
||||
headers={[
|
||||
{ key: "factory_name", value: "Factory" },
|
||||
{ key: "factory_location", value: "Location" },
|
||||
{ key: "factory_floor_size", value: "Floor size" },
|
||||
{ key: "manager_fullname", value: "Manager" },
|
||||
{ key: "update_btn", empty: true, width: "5rem" },
|
||||
]}
|
||||
{rows}
|
||||
>
|
||||
<Toolbar>
|
||||
@ -78,13 +117,19 @@
|
||||
shouldFilterRows
|
||||
bind:filteredRowIds
|
||||
/>
|
||||
<Button on:click={() => showCreateModal = true}>Create</Button>
|
||||
<Button on:click={() => (activeDelete = true)}>Delete</Button>
|
||||
<Button on:click={showCreateModal}>Create</Button>
|
||||
<Button on:click={() => activeDelete = true}>Delete</Button>
|
||||
</ToolbarContent>
|
||||
</Toolbar>
|
||||
<svelte:fragment slot="cell" let:cell>
|
||||
{#if cell.key === "update-btn"}
|
||||
<Button kind="ghost" size="field" iconDescription={"Edit"} icon={Edit} />
|
||||
<svelte:fragment slot="cell" let:row let:cell>
|
||||
{#if cell.key === "update_btn"}
|
||||
<Button
|
||||
kind="ghost"
|
||||
size="field"
|
||||
iconDescription={"Edit"}
|
||||
icon={Edit}
|
||||
on:click={() => showUpdateModal(row.id)}
|
||||
/>
|
||||
{:else}
|
||||
{cell.value}
|
||||
{/if}
|
||||
@ -92,21 +137,30 @@
|
||||
</DataTable>
|
||||
|
||||
<Modal
|
||||
bind:open={showCreateModal}
|
||||
modalHeading="Create factory & manager"
|
||||
bind:open={isModalShown}
|
||||
modalHeading={currentlyEditingId ? "Edit factory & manager" : "Create factory & manager"}
|
||||
primaryButtonText="Confirm"
|
||||
secondaryButtonText="Cancel"
|
||||
on:open
|
||||
on:close={() => showCreateModal = false}
|
||||
on:click:button--secondary={() => showCreateModal = false}
|
||||
on:submit={() => {
|
||||
on:click:button--secondary={closeModal}
|
||||
on:submit={async () => {
|
||||
let isFactoryValid = validateFactoryForm()
|
||||
let isManagerValid = validateManagerForm()
|
||||
if (!(isFactoryValid && isManagerValid)) {
|
||||
return false
|
||||
}
|
||||
|
||||
showCreateModal = false
|
||||
if (currentlyEditingId) {
|
||||
// TODO: Handle if factory is not found
|
||||
let manager_id = $factories.find(factory => factory.id == currentlyEditingId).manager_id
|
||||
|
||||
await edit_factory(currentlyEditingId, factoryData) // TODO: handle if failed
|
||||
await edit_manager(manager_id, managerData) // TODO: handle if failed
|
||||
} else {
|
||||
await add_manager_factory(factoryData, managerData) // TODO: handle error
|
||||
}
|
||||
|
||||
closeModal()
|
||||
return true
|
||||
}}
|
||||
>
|
||||
@ -114,6 +168,7 @@
|
||||
<div class="flex-grow">
|
||||
<p>Factory</p>
|
||||
<FactoryForm
|
||||
bind:factory={factoryData}
|
||||
bind:validate={validateFactoryForm}
|
||||
bind:reset={resetFactoryForm}
|
||||
/>
|
||||
@ -121,6 +176,7 @@
|
||||
<div class="flex-grow-2">
|
||||
<p>Manager</p>
|
||||
<ManagerForm
|
||||
bind:manager={managerData}
|
||||
bind:validate={validateManagerForm}
|
||||
bind:reset={resetManagerForm}
|
||||
/>
|
||||
|
Loading…
Reference in New Issue
Block a user