diff --git a/.vs/ProjectSettings.json b/.vs/ProjectSettings.json
new file mode 100644
index 0000000..f8b4888
--- /dev/null
+++ b/.vs/ProjectSettings.json
@@ -0,0 +1,3 @@
+{
+ "CurrentProjectSetting": null
+}
\ No newline at end of file
diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json
new file mode 100644
index 0000000..2698c3b
--- /dev/null
+++ b/.vs/VSWorkspaceState.json
@@ -0,0 +1,10 @@
+{
+ "ExpandedNodes": [
+ "",
+ "\\public",
+ "\\src",
+ "\\src\\pages"
+ ],
+ "SelectedNode": "\\src\\App.tsx",
+ "PreviewInSolutionExplorer": false
+}
\ No newline at end of file
diff --git a/.vs/logistics-app/FileContentIndex/07dfedf3-4b38-4be1-b6d6-5119e0f01a73.vsidx b/.vs/logistics-app/FileContentIndex/07dfedf3-4b38-4be1-b6d6-5119e0f01a73.vsidx
new file mode 100644
index 0000000..3872d21
Binary files /dev/null and b/.vs/logistics-app/FileContentIndex/07dfedf3-4b38-4be1-b6d6-5119e0f01a73.vsidx differ
diff --git a/.vs/logistics-app/FileContentIndex/7e5bbd21-0bd4-47e0-8ee6-9684a8c31103.vsidx b/.vs/logistics-app/FileContentIndex/7e5bbd21-0bd4-47e0-8ee6-9684a8c31103.vsidx
new file mode 100644
index 0000000..796f7a6
Binary files /dev/null and b/.vs/logistics-app/FileContentIndex/7e5bbd21-0bd4-47e0-8ee6-9684a8c31103.vsidx differ
diff --git a/.vs/logistics-app/FileContentIndex/c5a6c98d-7f4d-4b71-b220-310ab288f298.vsidx b/.vs/logistics-app/FileContentIndex/c5a6c98d-7f4d-4b71-b220-310ab288f298.vsidx
new file mode 100644
index 0000000..607184a
Binary files /dev/null and b/.vs/logistics-app/FileContentIndex/c5a6c98d-7f4d-4b71-b220-310ab288f298.vsidx differ
diff --git a/.vs/logistics-app/FileContentIndex/f8f06d21-6130-4351-a6b3-f29d63051768.vsidx b/.vs/logistics-app/FileContentIndex/f8f06d21-6130-4351-a6b3-f29d63051768.vsidx
new file mode 100644
index 0000000..ade0caa
Binary files /dev/null and b/.vs/logistics-app/FileContentIndex/f8f06d21-6130-4351-a6b3-f29d63051768.vsidx differ
diff --git a/.vs/logistics-app/FileContentIndex/feeecddc-f0ee-4bfe-9db7-a14e32700516.vsidx b/.vs/logistics-app/FileContentIndex/feeecddc-f0ee-4bfe-9db7-a14e32700516.vsidx
new file mode 100644
index 0000000..8cb7ff0
Binary files /dev/null and b/.vs/logistics-app/FileContentIndex/feeecddc-f0ee-4bfe-9db7-a14e32700516.vsidx differ
diff --git a/.vs/logistics-app/config/applicationhost.config b/.vs/logistics-app/config/applicationhost.config
new file mode 100644
index 0000000..269dc55
--- /dev/null
+++ b/.vs/logistics-app/config/applicationhost.config
@@ -0,0 +1,1021 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.vs/logistics-app/v17/.wsuo b/.vs/logistics-app/v17/.wsuo
new file mode 100644
index 0000000..d82f322
Binary files /dev/null and b/.vs/logistics-app/v17/.wsuo differ
diff --git a/.vs/logistics-app/v17/DocumentLayout.json b/.vs/logistics-app/v17/DocumentLayout.json
new file mode 100644
index 0000000..c78ecf0
--- /dev/null
+++ b/.vs/logistics-app/v17/DocumentLayout.json
@@ -0,0 +1,248 @@
+{
+ "Version": 1,
+ "WorkspaceRootPath": "C:\\Users\\KhasanovAM\\logistics-app\\",
+ "Documents": [
+ {
+ "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\KhasanovAM\\logistics-app\\src\\App.tsx||{0F2454B1-A556-402D-A7D0-1FDE7F99DEE0}",
+ "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:src\\App.tsx||{0F2454B1-A556-402D-A7D0-1FDE7F99DEE0}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\KhasanovAM\\logistics-app\\tsconfig.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}",
+ "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:tsconfig.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\KhasanovAM\\logistics-app\\src\\pages\\OrdersPage.tsx||{0F2454B1-A556-402D-A7D0-1FDE7F99DEE0}",
+ "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:src\\pages\\OrdersPage.tsx||{0F2454B1-A556-402D-A7D0-1FDE7F99DEE0}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\KhasanovAM\\logistics-app\\public\\index.html||{40D31677-CBC0-4297-A9EF-89D907823A98}",
+ "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:public\\index.html||{40D31677-CBC0-4297-A9EF-89D907823A98}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\KhasanovAM\\logistics-app\\package.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}",
+ "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:package.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\KhasanovAM\\logistics-app\\src\\pages\\VehicleDetailPage.tsx||{8B382828-6202-11D1-8870-0000F87579D2}",
+ "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:src\\pages\\VehicleDetailPage.tsx||{8B382828-6202-11D1-8870-0000F87579D2}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\KhasanovAM\\logistics-app\\src\\services\\api.ts||{0F2454B1-A556-402D-A7D0-1FDE7F99DEE0}",
+ "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:src\\services\\api.ts||{0F2454B1-A556-402D-A7D0-1FDE7F99DEE0}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\KhasanovAM\\logistics-app\\src\\index.tsx||{0F2454B1-A556-402D-A7D0-1FDE7F99DEE0}",
+ "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:src\\index.tsx||{0F2454B1-A556-402D-A7D0-1FDE7F99DEE0}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\KhasanovAM\\logistics-app\\src\\index.css||{A5401142-F49D-43DB-90B1-F57BA349E55C}",
+ "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:src\\index.css||{A5401142-F49D-43DB-90B1-F57BA349E55C}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\KhasanovAM\\logistics-app\\src\\setupTests.ts||{0F2454B1-A556-402D-A7D0-1FDE7F99DEE0}",
+ "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:src\\setupTests.ts||{0F2454B1-A556-402D-A7D0-1FDE7F99DEE0}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\KhasanovAM\\logistics-app\\src\\App.css||{A5401142-F49D-43DB-90B1-F57BA349E55C}",
+ "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:src\\App.css||{A5401142-F49D-43DB-90B1-F57BA349E55C}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\KhasanovAM\\logistics-app\\src\\components\\WaybillWidget.tsx||{0F2454B1-A556-402D-A7D0-1FDE7F99DEE0}",
+ "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:src\\components\\WaybillWidget.tsx||{0F2454B1-A556-402D-A7D0-1FDE7F99DEE0}"
+ }
+ ],
+ "DocumentGroupContainers": [
+ {
+ "Orientation": 0,
+ "VerticalTabListWidth": 256,
+ "DocumentGroups": [
+ {
+ "DockedWidth": 200,
+ "SelectedChildIndex": 15,
+ "Children": [
+ {
+ "$type": "Bookmark",
+ "Name": "ST:129:0:{13b12e3e-c1b4-4539-9371-4fe9a0d523fc}"
+ },
+ {
+ "$type": "Bookmark",
+ "Name": "ST:130:0:{13b12e3e-c1b4-4539-9371-4fe9a0d523fc}"
+ },
+ {
+ "$type": "Bookmark",
+ "Name": "ST:129:0:{1fc202d4-d401-403c-9834-5b218574bb67}"
+ },
+ {
+ "$type": "Bookmark",
+ "Name": "ST:128:0:{1fc202d4-d401-403c-9834-5b218574bb67}"
+ },
+ {
+ "$type": "Bookmark",
+ "Name": "ST:0:0:{4a9b7e51-aa16-11d0-a8c5-00a0c921a4d2}"
+ },
+ {
+ "$type": "Bookmark",
+ "Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}"
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 1,
+ "Title": "tsconfig.json",
+ "DocumentMoniker": "C:\\Users\\KhasanovAM\\logistics-app\\tsconfig.json",
+ "RelativeDocumentMoniker": "tsconfig.json",
+ "ToolTip": "C:\\Users\\KhasanovAM\\logistics-app\\tsconfig.json",
+ "RelativeToolTip": "tsconfig.json",
+ "ViewState": "AgIAAAAAAAAAAAAAAAAAABoAAAAAAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|",
+ "WhenOpened": "2025-11-21T04:11:35.007Z",
+ "EditorCaption": ""
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 3,
+ "Title": "index.html",
+ "DocumentMoniker": "C:\\Users\\KhasanovAM\\logistics-app\\public\\index.html",
+ "RelativeDocumentMoniker": "public\\index.html",
+ "ToolTip": "C:\\Users\\KhasanovAM\\logistics-app\\public\\index.html",
+ "RelativeToolTip": "public\\index.html",
+ "ViewState": "AgIAAAAAAAAAAAAAAAAAAB4AAAAZAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001512|",
+ "WhenOpened": "2025-11-21T04:08:55.71Z",
+ "EditorCaption": ""
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 2,
+ "Title": "OrdersPage.tsx",
+ "DocumentMoniker": "C:\\Users\\KhasanovAM\\logistics-app\\src\\pages\\OrdersPage.tsx",
+ "RelativeDocumentMoniker": "src\\pages\\OrdersPage.tsx",
+ "ToolTip": "C:\\Users\\KhasanovAM\\logistics-app\\src\\pages\\OrdersPage.tsx",
+ "RelativeToolTip": "src\\pages\\OrdersPage.tsx",
+ "ViewState": "AgIAAAwAAAAAAAAAAAAAACsAAAAcAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003213|",
+ "WhenOpened": "2025-11-21T04:02:02.174Z",
+ "EditorCaption": ""
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 6,
+ "Title": "api.ts",
+ "DocumentMoniker": "C:\\Users\\KhasanovAM\\logistics-app\\src\\services\\api.ts",
+ "RelativeDocumentMoniker": "src\\services\\api.ts",
+ "ToolTip": "C:\\Users\\KhasanovAM\\logistics-app\\src\\services\\api.ts",
+ "RelativeToolTip": "src\\services\\api.ts",
+ "ViewState": "AgIAAAAAAAAAAAAAAAAAAA0AAAACAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003213|",
+ "WhenOpened": "2025-11-21T04:00:40.26Z",
+ "EditorCaption": ""
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 7,
+ "Title": "index.tsx",
+ "DocumentMoniker": "C:\\Users\\KhasanovAM\\logistics-app\\src\\index.tsx",
+ "RelativeDocumentMoniker": "src\\index.tsx",
+ "ToolTip": "C:\\Users\\KhasanovAM\\logistics-app\\src\\index.tsx",
+ "RelativeToolTip": "src\\index.tsx",
+ "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003213|",
+ "WhenOpened": "2025-11-21T04:00:23.973Z",
+ "EditorCaption": ""
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 8,
+ "Title": "index.css",
+ "DocumentMoniker": "C:\\Users\\KhasanovAM\\logistics-app\\src\\index.css",
+ "RelativeDocumentMoniker": "src\\index.css",
+ "ToolTip": "C:\\Users\\KhasanovAM\\logistics-app\\src\\index.css",
+ "RelativeToolTip": "src\\index.css",
+ "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003000|",
+ "WhenOpened": "2025-11-21T04:00:18.151Z",
+ "EditorCaption": ""
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 4,
+ "Title": "package.json",
+ "DocumentMoniker": "C:\\Users\\KhasanovAM\\logistics-app\\package.json",
+ "RelativeDocumentMoniker": "package.json",
+ "ToolTip": "C:\\Users\\KhasanovAM\\logistics-app\\package.json",
+ "RelativeToolTip": "package.json",
+ "ViewState": "AgIAABUAAAAAAAAAAAAAACEAAAAFAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|",
+ "WhenOpened": "2025-11-21T03:58:43.953Z",
+ "EditorCaption": ""
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 9,
+ "Title": "setupTests.ts",
+ "DocumentMoniker": "C:\\Users\\KhasanovAM\\logistics-app\\src\\setupTests.ts",
+ "RelativeDocumentMoniker": "src\\setupTests.ts",
+ "ToolTip": "C:\\Users\\KhasanovAM\\logistics-app\\src\\setupTests.ts",
+ "RelativeToolTip": "src\\setupTests.ts",
+ "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003213|",
+ "WhenOpened": "2025-11-21T03:56:29.748Z",
+ "EditorCaption": ""
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 10,
+ "Title": "App.css",
+ "DocumentMoniker": "C:\\Users\\KhasanovAM\\logistics-app\\src\\App.css",
+ "RelativeDocumentMoniker": "src\\App.css",
+ "ToolTip": "C:\\Users\\KhasanovAM\\logistics-app\\src\\App.css",
+ "RelativeToolTip": "src\\App.css",
+ "ViewState": "AgIAAAAAAAAAAAAAAAAAABYAAAAgAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003000|",
+ "WhenOpened": "2025-11-21T03:54:33.459Z",
+ "EditorCaption": ""
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 0,
+ "Title": "App.tsx",
+ "DocumentMoniker": "C:\\Users\\KhasanovAM\\logistics-app\\src\\App.tsx",
+ "RelativeDocumentMoniker": "src\\App.tsx",
+ "ToolTip": "C:\\Users\\KhasanovAM\\logistics-app\\src\\App.tsx",
+ "RelativeToolTip": "src\\App.tsx",
+ "ViewState": "AgIAAAAAAAAAAAAAAAAAADYAAAAGAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003213|",
+ "WhenOpened": "2025-11-21T03:50:44.515Z",
+ "EditorCaption": ""
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 5,
+ "Title": "VehicleDetailPage.tsx",
+ "DocumentMoniker": "C:\\Users\\KhasanovAM\\logistics-app\\src\\pages\\VehicleDetailPage.tsx",
+ "RelativeDocumentMoniker": "src\\pages\\VehicleDetailPage.tsx",
+ "ToolTip": "C:\\Users\\KhasanovAM\\logistics-app\\src\\pages\\VehicleDetailPage.tsx",
+ "RelativeToolTip": "src\\pages\\VehicleDetailPage.tsx",
+ "ViewState": "AgIAAAAAAAAAAAAAAAAAAB4AAAA/AAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003213|",
+ "WhenOpened": "2025-11-21T03:50:29.56Z",
+ "EditorCaption": ""
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 11,
+ "Title": "WaybillWidget.tsx",
+ "DocumentMoniker": "C:\\Users\\KhasanovAM\\logistics-app\\src\\components\\WaybillWidget.tsx",
+ "RelativeDocumentMoniker": "src\\components\\WaybillWidget.tsx",
+ "ToolTip": "C:\\Users\\KhasanovAM\\logistics-app\\src\\components\\WaybillWidget.tsx",
+ "RelativeToolTip": "src\\components\\WaybillWidget.tsx",
+ "ViewState": "AgIAALAAAAAAAAAAAAAIwMgAAAA1AAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003213|",
+ "WhenOpened": "2025-11-21T03:50:12.585Z",
+ "EditorCaption": ""
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite
new file mode 100644
index 0000000..4e2397e
Binary files /dev/null and b/.vs/slnx.sqlite differ
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..42b8b46
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,17 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+
+
+ {
+ "name": "Debug React App",
+ "type": "firefox",
+ "request": "launch",
+ "url": "http://localhost:3000",
+ "webRoot": "${workspaceFolder}/src",
+ "sourceMapPathOverrides": {
+ "webpack:///src/*": "${webRoot}/*"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/db.json b/db.json
new file mode 100644
index 0000000..5cf59bf
--- /dev/null
+++ b/db.json
@@ -0,0 +1,212 @@
+{
+ "orders": [
+ {
+ "id": "1",
+ "clientName": "Иванов Иван Иванович",
+ "orderCost": 15000,
+ "orderDate": "2025-01-15",
+ "status": "in_progress",
+ "address": "ул. Ленина, 10",
+ "description": "Доставка строительных материалов"
+ },
+ {
+ "id": "2",
+ "clientName": "ООО 'СтройМир'",
+ "orderCost": 25000,
+ "orderDate": "2025-01-16",
+ "status": "in_progress",
+ "address": "пр. Мира, 25",
+ "description": "Перевозка оборудования"
+ },
+ {
+ "id": "3",
+ "clientName": "ЗАО 'ТехноПром'",
+ "orderCost": 18000,
+ "orderDate": "2025-01-17",
+ "status": "completed",
+ "address": "ул. Промышленная, 45",
+ "description": "Доставка офисной мебели"
+ },
+ {
+ "id": "4",
+ "clientName": "Сергеев Алексей Петрович",
+ "orderCost": 12000,
+ "orderDate": "2025-01-18",
+ "status": "pending",
+ "address": "пр. Космонавтов, 78",
+ "description": "Переезд квартиры"
+ },
+ {
+ "id": "5",
+ "clientName": "ИП Козлова Марина Сергеевна",
+ "orderCost": 22000,
+ "orderDate": "2025-01-19",
+ "status": "in_progress",
+ "address": "ул. Торговая, 12",
+ "description": "Доставка товаров для магазина"
+ },
+ {
+ "id": "6",
+ "clientName": "ООО 'СтройГрад'",
+ "orderCost": 35000,
+ "orderDate": "2025-01-20",
+ "status": "pending",
+ "address": "ул. Строителей, 33",
+ "description": "Перевозка кирпича"
+ },
+ {
+ "id": "7",
+ "clientName": "Федоров Дмитрий Иванович",
+ "orderCost": 8000,
+ "orderDate": "2025-01-21",
+ "status": "completed",
+ "address": "ул. Садовая, 15",
+ "description": "Перевозка бытовой техники"
+ },
+ {
+ "id": "8",
+ "clientName": "ООО 'ПромСнаб'",
+ "orderCost": 28000,
+ "orderDate": "2025-01-22",
+ "status": "cancelled",
+ "address": "пр. Заводской, 67",
+ "description": "Доставка станков"
+ },
+ {
+ "id": "9",
+ "clientName": "Александрова Ольга Викторовна",
+ "orderCost": 9500,
+ "orderDate": "2025-01-23",
+ "status": "pending",
+ "address": "ул. Центральная, 89",
+ "description": "Перевозка личных вещей"
+ },
+ {
+ "id": "10",
+ "clientName": "ООО 'ТоргСервис'",
+ "orderCost": 19500,
+ "orderDate": "2025-01-24",
+ "status": "in_progress",
+ "address": "ул. Коммерческая, 56",
+ "description": "Доставка продуктов питания"
+ },
+ {
+ "id": "11",
+ "clientName": "Васильев Павел Сергеевич",
+ "orderCost": 11000,
+ "orderDate": "2025-01-25",
+ "status": "completed",
+ "address": "ул. Молодежная, 23",
+ "description": "Переезд на новую квартиру"
+ },
+ {
+ "id": "12",
+ "clientName": "ЗАО 'ПромИнвест'",
+ "orderCost": 42000,
+ "orderDate": "2025-01-26",
+ "status": "pending",
+ "address": "пр. Индустриальный, 44",
+ "description": "Перевозка промышленного оборудования"
+ }
+ ],
+ "vehicles": [
+ {
+ "id": "1",
+ "driverName": "Петров Петр Петрович",
+ "vehicleType": "Газель",
+ "licensePlate": "А123БВ77"
+ },
+ {
+ "id": "2",
+ "driverName": "Сидоров Алексей Владимирович",
+ "vehicleType": "Камаз",
+ "licensePlate": "В456ГД77"
+ },
+ {
+ "id": "3",
+ "driverName": "Кузнецов Михаил Иванович",
+ "vehicleType": "Газель",
+ "licensePlate": "С789ЕЖ77"
+ },
+ {
+ "id": "4",
+ "driverName": "Николаев Андрей Сергеевич",
+ "vehicleType": "ЗИЛ",
+ "licensePlate": "Д321ФГ77"
+ },
+ {
+ "id": "5",
+ "driverName": "Морозов Виктор Павлович",
+ "vehicleType": "Камаз",
+ "licensePlate": "Е654ХЦ77"
+ },
+ {
+ "id": "6",
+ "driverName": "Орлов Денис Александрович",
+ "vehicleType": "Газель",
+ "licensePlate": "Ж987ЧШ77"
+ },
+ {
+ "id": "7",
+ "driverName": "Белов Артем Игоревич",
+ "vehicleType": "Фургон",
+ "licensePlate": "З159ЩР77"
+ },
+ {
+ "id": "8",
+ "driverName": "Громов Сергей Викторович",
+ "vehicleType": "ЗИЛ",
+ "licensePlate": "И753ЪЫ77"
+ }
+ ],
+ "waybillEntries": [
+ {
+ "id": "cdf9",
+ "vehicleId": "6",
+ "orderId": "1",
+ "startTime": "20:00",
+ "endTime": "22:00",
+ "date": "2025-11-21"
+ },
+ {
+ "id": "9c84",
+ "vehicleId": "6",
+ "orderId": "4",
+ "startTime": "14:00",
+ "endTime": "16:00",
+ "date": "2025-11-21"
+ },
+ {
+ "id": "c4a6",
+ "vehicleId": "6",
+ "orderId": "2",
+ "startTime": "10:00",
+ "endTime": "12:00",
+ "date": "2025-11-21"
+ },
+ {
+ "id": "e320",
+ "vehicleId": "1",
+ "orderId": "1",
+ "startTime": "08:00",
+ "endTime": "10:00",
+ "date": "2025-11-21"
+ },
+ {
+ "id": "08a1",
+ "vehicleId": "2",
+ "orderId": "2",
+ "startTime": "20:00",
+ "endTime": "22:00",
+ "date": "2025-11-21"
+ },
+ {
+ "id": "27df",
+ "vehicleId": "2",
+ "orderId": "4",
+ "startTime": "08:00",
+ "endTime": "09:00",
+ "date": "2025-11-21"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index aa22123..b8680da 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,11 +16,19 @@
"@types/node": "^16.18.126",
"@types/react": "^19.2.6",
"@types/react-dom": "^19.2.3",
+ "axios": "^1.13.2",
+ "bootstrap": "^5.3.8",
"react": "^19.2.0",
"react-dom": "^19.2.0",
+ "react-router-dom": "^7.9.6",
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
+ },
+ "devDependencies": {
+ "@types/bootstrap": "^5.2.10",
+ "@types/react-router-dom": "^5.3.3",
+ "json-server": "^1.0.0-beta.3"
}
},
"node_modules/@adobe/css-tools": {
@@ -2977,6 +2985,23 @@
}
}
},
+ "node_modules/@polka/url": {
+ "version": "1.0.0-next.29",
+ "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
+ "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@popperjs/core": {
+ "version": "2.11.8",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
"node_modules/@rollup/plugin-babel": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@@ -3421,6 +3446,327 @@
"@testing-library/dom": ">=7.21.4"
}
},
+ "node_modules/@tinyhttp/accepts": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/@tinyhttp/accepts/-/accepts-2.2.3.tgz",
+ "integrity": "sha512-9pQN6pJAJOU3McmdJWTcyq7LLFW8Lj5q+DadyKcvp+sxMkEpktKX5sbfJgJuOvjk6+1xWl7pe0YL1US1vaO/1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime": "4.0.4",
+ "negotiator": "^0.6.3"
+ },
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/tinyhttp/tinyhttp?sponsor=1"
+ }
+ },
+ "node_modules/@tinyhttp/accepts/node_modules/mime": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.4.tgz",
+ "integrity": "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/broofa"
+ ],
+ "license": "MIT",
+ "bin": {
+ "mime": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@tinyhttp/app": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/@tinyhttp/app/-/app-2.5.2.tgz",
+ "integrity": "sha512-DcB3Y8GQppLQlO2VxRYF7LzTEAoZb+VRQXuIsErcu2fNaM1xdx6NQZDso5rlZUiaeg6KYYRfU34N4XkZbv6jSA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tinyhttp/cookie": "2.1.1",
+ "@tinyhttp/proxy-addr": "2.2.1",
+ "@tinyhttp/req": "2.2.5",
+ "@tinyhttp/res": "2.2.5",
+ "@tinyhttp/router": "2.2.3",
+ "header-range-parser": "1.1.3",
+ "regexparam": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=14.21.3"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/tinyhttp/tinyhttp?sponsor=1"
+ }
+ },
+ "node_modules/@tinyhttp/content-disposition": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/@tinyhttp/content-disposition/-/content-disposition-2.2.2.tgz",
+ "integrity": "sha512-crXw1txzrS36huQOyQGYFvhTeLeG0Si1xu+/l6kXUVYpE0TjFjEZRqTbuadQLfKGZ0jaI+jJoRyqaWwxOSHW2g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/tinyhttp/tinyhttp?sponsor=1"
+ }
+ },
+ "node_modules/@tinyhttp/content-type": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/@tinyhttp/content-type/-/content-type-0.1.4.tgz",
+ "integrity": "sha512-dl6f3SHIJPYbhsW1oXdrqOmLSQF/Ctlv3JnNfXAE22kIP7FosqJHxkz/qj2gv465prG8ODKH5KEyhBkvwrueKQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.4"
+ }
+ },
+ "node_modules/@tinyhttp/cookie": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@tinyhttp/cookie/-/cookie-2.1.1.tgz",
+ "integrity": "sha512-h/kL9jY0e0Dvad+/QU3efKZww0aTvZJslaHj3JTPmIPC9Oan9+kYqmh3M6L5JUQRuTJYFK2nzgL2iJtH2S+6dA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/tinyhttp/tinyhttp?sponsor=1"
+ }
+ },
+ "node_modules/@tinyhttp/cookie-signature": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@tinyhttp/cookie-signature/-/cookie-signature-2.1.1.tgz",
+ "integrity": "sha512-VDsSMY5OJfQJIAtUgeQYhqMPSZptehFSfvEEtxr+4nldPA8IImlp3QVcOVuK985g4AFR4Hl1sCbWCXoqBnVWnw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/@tinyhttp/cors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@tinyhttp/cors/-/cors-2.0.1.tgz",
+ "integrity": "sha512-qrmo6WJuaiCzKWagv2yA/kw6hIISfF/hOqPWwmI6w0o8apeTMmRN3DoCFvQ/wNVuWVdU5J4KU7OX8aaSOEq51A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tinyhttp/vary": "^0.1.3"
+ },
+ "engines": {
+ "node": ">=12.20 || 14.x || >=16"
+ }
+ },
+ "node_modules/@tinyhttp/encode-url": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@tinyhttp/encode-url/-/encode-url-2.1.1.tgz",
+ "integrity": "sha512-AhY+JqdZ56qV77tzrBm0qThXORbsVjs/IOPgGCS7x/wWnsa/Bx30zDUU/jPAUcSzNOzt860x9fhdGpzdqbUeUw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/@tinyhttp/etag": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@tinyhttp/etag/-/etag-2.1.2.tgz",
+ "integrity": "sha512-j80fPKimGqdmMh6962y+BtQsnYPVCzZfJw0HXjyH70VaJBHLKGF+iYhcKqzI3yef6QBNa8DKIPsbEYpuwApXTw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/@tinyhttp/forwarded": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@tinyhttp/forwarded/-/forwarded-2.1.2.tgz",
+ "integrity": "sha512-9H/eulJ68ElY/+zYpTpNhZ7vxGV+cnwaR6+oQSm7bVgZMyuQfgROW/qvZuhmgDTIxnGMXst+Ba4ij6w6Krcs3w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/@tinyhttp/logger": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@tinyhttp/logger/-/logger-2.1.0.tgz",
+ "integrity": "sha512-Ma1fJ9CwUbn9r61/4HW6+nflsVoslpOnCrfQ6UeZq7GGIgwLzofms3HoSVG7M+AyRMJpxlfcDdbH5oFVroDMKA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "colorette": "^2.0.20",
+ "dayjs": "^1.11.13",
+ "http-status-emojis": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=14.18 || >=16.20"
+ }
+ },
+ "node_modules/@tinyhttp/proxy-addr": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/@tinyhttp/proxy-addr/-/proxy-addr-2.2.1.tgz",
+ "integrity": "sha512-BicqMqVI91hHq2BQmnqJUh0FQUnx7DncwSGgu2ghlh+JZG2rHK2ZN/rXkfhrx1rrUw6hnd0L36O8GPMh01+dDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tinyhttp/forwarded": "2.1.2",
+ "ipaddr.js": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/@tinyhttp/req": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/@tinyhttp/req/-/req-2.2.5.tgz",
+ "integrity": "sha512-trfsXwtmsNjMcGKcLJ+45h912kLRqBQCQD06ams3Tq0kf4gHLxjHjoYOC1Z9yGjOn81XllRx8wqvnvr+Kbe3gw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tinyhttp/accepts": "2.2.3",
+ "@tinyhttp/type-is": "2.2.4",
+ "@tinyhttp/url": "2.1.1",
+ "header-range-parser": "^1.1.3"
+ },
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/@tinyhttp/res": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/@tinyhttp/res/-/res-2.2.5.tgz",
+ "integrity": "sha512-yBsqjWygpuKAVz4moWlP4hqzwiDDqfrn2mA0wviJAcgvGiyOErtlQwXY7aj3aPiCpURvxvEFO//Gdy6yV+xEpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tinyhttp/content-disposition": "2.2.2",
+ "@tinyhttp/cookie": "2.1.1",
+ "@tinyhttp/cookie-signature": "2.1.1",
+ "@tinyhttp/encode-url": "2.1.1",
+ "@tinyhttp/req": "2.2.5",
+ "@tinyhttp/send": "2.2.3",
+ "@tinyhttp/vary": "^0.1.3",
+ "es-escape-html": "^0.1.1",
+ "mime": "4.0.4"
+ },
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/@tinyhttp/res/node_modules/mime": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.4.tgz",
+ "integrity": "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/broofa"
+ ],
+ "license": "MIT",
+ "bin": {
+ "mime": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@tinyhttp/router": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/@tinyhttp/router/-/router-2.2.3.tgz",
+ "integrity": "sha512-O0MQqWV3Vpg/uXsMYg19XsIgOhwjyhTYWh51Qng7bxqXixxx2PEvZWnFjP7c84K7kU/nUX41KpkEBTLnznk9/Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/@tinyhttp/send": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/@tinyhttp/send/-/send-2.2.3.tgz",
+ "integrity": "sha512-o4cVHHGQ8WjVBS8UT0EE/2WnjoybrfXikHwsRoNlG1pfrC/Sd01u1N4Te8cOd/9aNGLr4mGxWb5qTm2RRtEi7g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tinyhttp/content-type": "^0.1.4",
+ "@tinyhttp/etag": "2.1.2",
+ "mime": "4.0.4"
+ },
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/@tinyhttp/send/node_modules/mime": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.4.tgz",
+ "integrity": "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/broofa"
+ ],
+ "license": "MIT",
+ "bin": {
+ "mime": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@tinyhttp/type-is": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/@tinyhttp/type-is/-/type-is-2.2.4.tgz",
+ "integrity": "sha512-7F328NheridwjIfefBB2j1PEcKKABpADgv7aCJaE8x8EON77ZFrAkI3Rir7pGjopV7V9MBmW88xUQigBEX2rmQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tinyhttp/content-type": "^0.1.4",
+ "mime": "4.0.4"
+ },
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/@tinyhttp/type-is/node_modules/mime": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.4.tgz",
+ "integrity": "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/broofa"
+ ],
+ "license": "MIT",
+ "bin": {
+ "mime": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@tinyhttp/url": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@tinyhttp/url/-/url-2.1.1.tgz",
+ "integrity": "sha512-POJeq2GQ5jI7Zrdmj22JqOijB5/GeX+LEX7DUdml1hUnGbJOTWDx7zf2b5cCERj7RoXL67zTgyzVblBJC+NJWg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/@tinyhttp/vary": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@tinyhttp/vary/-/vary-0.1.3.tgz",
+ "integrity": "sha512-SoL83sQXAGiHN1jm2VwLUWQSQeDAAl1ywOm6T0b0Cg1CZhVsjoiZadmjhxF6FHCCY7OHHVaLnTgSMxTPIDLxMg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20"
+ }
+ },
"node_modules/@tootallnate/once": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
@@ -3505,6 +3851,16 @@
"@types/node": "*"
}
},
+ "node_modules/@types/bootstrap": {
+ "version": "5.2.10",
+ "resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.2.10.tgz",
+ "integrity": "sha512-F2X+cd6551tep0MvVZ6nM8v7XgGN/twpdNDjqS1TUM7YFNEtQYWk+dKAnH+T1gr6QgCoGMPl487xw/9hXooa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@popperjs/core": "^2.9.2"
+ }
+ },
"node_modules/@types/connect": {
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
@@ -3595,6 +3951,13 @@
"@types/node": "*"
}
},
+ "node_modules/@types/history": {
+ "version": "4.7.11",
+ "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
+ "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/html-minifier-terser": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@@ -3731,6 +4094,29 @@
"@types/react": "^19.2.0"
}
},
+ "node_modules/@types/react-router": {
+ "version": "5.1.20",
+ "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz",
+ "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/history": "^4.7.11",
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/react-router-dom": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz",
+ "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/history": "^4.7.11",
+ "@types/react": "*",
+ "@types/react-router": "*"
+ }
+ },
"node_modules/@types/resolve": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
@@ -4830,6 +5216,33 @@
"node": ">=4"
}
},
+ "node_modules/axios": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
+ "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.4",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/axios/node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/axobject-query": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@@ -5224,6 +5637,25 @@
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"license": "ISC"
},
+ "node_modules/bootstrap": {
+ "version": "5.3.8",
+ "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz",
+ "integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/twbs"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/bootstrap"
+ }
+ ],
+ "license": "MIT",
+ "peerDependencies": {
+ "@popperjs/core": "^2.11.8"
+ }
+ },
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
@@ -6385,6 +6817,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/dayjs": {
+ "version": "1.11.19",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz",
+ "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -6727,6 +7166,35 @@
"tslib": "^2.0.3"
}
},
+ "node_modules/dot-prop": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz",
+ "integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^4.18.2"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/dot-prop/node_modules/type-fest": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
+ "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/dotenv": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz",
@@ -6957,6 +7425,16 @@
"node": ">= 0.4"
}
},
+ "node_modules/es-escape-html": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/es-escape-html/-/es-escape-html-0.1.1.tgz",
+ "integrity": "sha512-yUx1o+8RsG7UlszmYPtks+dm6Lho2m8lgHMOsLJQsFI0R8XwUJwiMhM1M4E/S8QLeGyf6MkDV/pWgjQ0tdTSyQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.x"
+ }
+ },
"node_modules/es-iterator-helpers": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz",
@@ -7704,6 +8182,19 @@
"node": ">=0.10.0"
}
},
+ "node_modules/eta": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/eta/-/eta-3.5.0.tgz",
+ "integrity": "sha512-e3x3FBvGzeCIHhF+zhK8FZA2vC5uFn6b4HJjegUbIWrDb4mJ7JjTGMJY9VGIbRVpmSwHopNiaJibhjIr+HfLug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/eta-dev/eta?sponsor=1"
+ }
+ },
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@@ -8751,6 +9242,16 @@
"he": "bin/he"
}
},
+ "node_modules/header-range-parser": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/header-range-parser/-/header-range-parser-1.1.3.tgz",
+ "integrity": "sha512-B9zCFt3jH8g09LR1vHL4pcAn8yMEtlSlOUdQemzHMRKMImNIhhszdeosYFfNW0WXKQtXIlWB+O4owHJKvEJYaA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.22.0"
+ }
+ },
"node_modules/hoopy": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
@@ -8994,6 +9495,13 @@
}
}
},
+ "node_modules/http-status-emojis": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/http-status-emojis/-/http-status-emojis-2.2.0.tgz",
+ "integrity": "sha512-ompKtgwpx8ff0hsbpIB7oE4ax1LXoHmftsHHStMELX56ivG3GhofTX8ZHWlUaFKfGjcGjw6G3rPk7dJRXMmbbg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
@@ -9139,6 +9647,16 @@
"node": ">=8"
}
},
+ "node_modules/inflection": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/inflection/-/inflection-3.0.2.tgz",
+ "integrity": "sha512-+Bg3+kg+J6JUWn8J6bzFmOWkTQ6L/NHfDRSYU+EVvuKHDxUDHAXgqixHfVlzuBQaPOTac8hn43aPhMNk6rMe3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -10845,6 +11363,77 @@
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"license": "MIT"
},
+ "node_modules/json-server": {
+ "version": "1.0.0-beta.3",
+ "resolved": "https://registry.npmjs.org/json-server/-/json-server-1.0.0-beta.3.tgz",
+ "integrity": "sha512-DwE69Ep5ccwIJZBUIWEENC30Yj8bwr4Ax9W9VoIWAYnB8Sj4ReptscO8/DRHv/nXwVlmb3Bk73Ls86+VZdYkkA==",
+ "dev": true,
+ "license": "SEE LICENSE IN ./LICENSE",
+ "dependencies": {
+ "@tinyhttp/app": "^2.4.0",
+ "@tinyhttp/cors": "^2.0.1",
+ "@tinyhttp/logger": "^2.0.0",
+ "chalk": "^5.3.0",
+ "chokidar": "^4.0.1",
+ "dot-prop": "^9.0.0",
+ "eta": "^3.5.0",
+ "inflection": "^3.0.0",
+ "json5": "^2.2.3",
+ "lowdb": "^7.0.1",
+ "milliparsec": "^4.0.0",
+ "sirv": "^2.0.4",
+ "sort-on": "^6.1.0"
+ },
+ "bin": {
+ "json-server": "lib/bin.js"
+ },
+ "engines": {
+ "node": ">=18.3"
+ }
+ },
+ "node_modules/json-server/node_modules/chalk": {
+ "version": "5.6.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
+ "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/json-server/node_modules/chokidar": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14.16.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/json-server/node_modules/readdirp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.18.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
"node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
@@ -11110,6 +11699,22 @@
"loose-envify": "cli.js"
}
},
+ "node_modules/lowdb": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-7.0.1.tgz",
+ "integrity": "sha512-neJAj8GwF0e8EpycYIDFqEPcx9Qz4GUho20jWFR7YiFeXzF1YMLdxB36PypcTSPMA+4+LvgyMacYhlr18Zlymw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "steno": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/typicode"
+ }
+ },
"node_modules/lower-case": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
@@ -11261,6 +11866,16 @@
"node": ">=8.6"
}
},
+ "node_modules/milliparsec": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/milliparsec/-/milliparsec-4.0.0.tgz",
+ "integrity": "sha512-/wk9d4Z6/9ZvoEH/6BI4TrTCgmkpZPuSRN/6fI9aUHOfXdNTuj/VhLS7d+NqG26bi6L9YmGXutVYvWC8zQ0qtA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
@@ -11371,6 +11986,16 @@
"mkdirp": "bin/cmd.js"
}
},
+ "node_modules/mrmime": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
+ "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -13447,6 +14072,12 @@
"node": ">= 0.10"
}
},
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
"node_modules/psl": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
@@ -13738,6 +14369,53 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-router": {
+ "version": "7.9.6",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.6.tgz",
+ "integrity": "sha512-Y1tUp8clYRXpfPITyuifmSoE2vncSME18uVLgaqyxh9H35JWpIfzHo+9y3Fzh5odk/jxPW29IgLgzcdwxGqyNA==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.9.6",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.6.tgz",
+ "integrity": "sha512-2MkC2XSXq6HjGcihnx1s0DBWQETI4mlis4Ux7YTLvP67xnGxCvq+BcCQSO81qQHVUTM1V53tl4iVVaY5sReCOA==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.9.6"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
+ "node_modules/react-router/node_modules/cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/react-scripts": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
@@ -13943,6 +14621,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/regexparam": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/regexparam/-/regexparam-2.0.2.tgz",
+ "integrity": "sha512-A1PeDEYMrkLrfyOwv2jwihXbo9qxdGD3atBYQA9JJgreAx8/7rC6IUkWOw2NQlOxLp2wL0ifQbh1HuidDfYA6w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/regexpu-core": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz",
@@ -14632,6 +15320,12 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "license": "MIT"
+ },
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -14795,6 +15489,21 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"license": "ISC"
},
+ "node_modules/sirv": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz",
+ "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@polka/url": "^1.0.0-next.24",
+ "mrmime": "^2.0.0",
+ "totalist": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
@@ -14821,6 +15530,22 @@
"websocket-driver": "^0.7.4"
}
},
+ "node_modules/sort-on": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/sort-on/-/sort-on-6.1.1.tgz",
+ "integrity": "sha512-PB8pVvXAoRBijBCvuKJnmo06D8mSnQlLij0abfB2VdOpfFm29sPGYD4ft2prUPo1AZXTnkn3pP48AppRWyMkrw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dot-prop": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/source-list-map": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
@@ -15071,6 +15796,19 @@
"node": ">= 0.8"
}
},
+ "node_modules/steno": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/steno/-/steno-4.0.2.tgz",
+ "integrity": "sha512-yhPIQXjrlt1xv7dyPQg2P17URmXbuM5pdGkpiMB3RenprfiBlvK415Lctfe0eshk90oA7/tNq7WEiMK8RSP39A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/typicode"
+ }
+ },
"node_modules/stop-iteration-iterator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
@@ -15928,6 +16666,16 @@
"node": ">=0.6"
}
},
+ "node_modules/totalist": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
+ "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/tough-cookie": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
diff --git a/package.json b/package.json
index 9d43cdf..123c660 100644
--- a/package.json
+++ b/package.json
@@ -11,8 +11,11 @@
"@types/node": "^16.18.126",
"@types/react": "^19.2.6",
"@types/react-dom": "^19.2.3",
+ "axios": "^1.13.2",
+ "bootstrap": "^5.3.8",
"react": "^19.2.0",
"react-dom": "^19.2.0",
+ "react-router-dom": "^7.9.6",
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
@@ -21,7 +24,8 @@
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
- "eject": "react-scripts eject"
+ "eject": "react-scripts eject",
+ "server": "json-server --watch db.json --port 3001"
},
"eslintConfig": {
"extends": [
@@ -40,5 +44,10 @@
"last 1 firefox version",
"last 1 safari version"
]
+ },
+ "devDependencies": {
+ "@types/bootstrap": "^5.2.10",
+ "@types/react-router-dom": "^5.3.3",
+ "json-server": "^1.0.0-beta.3"
}
}
diff --git a/src/App.tsx b/src/App.tsx
index a53698a..1faee8c 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,26 +1,58 @@
import React from 'react';
-import logo from './logo.svg';
+import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
+import OrdersPage from './pages/OrdersPage';
+import VehiclesPage from './pages/VehiclesPage';
+import VehicleDetailPage from './pages/VehicleDetailPage';
+import 'bootstrap/dist/css/bootstrap.min.css';
import './App.css';
-function App() {
+const App: React.FC = () => {
return (
-
- );
-}
+
+
+ {/* Навигация */}
+
-export default App;
+ {/* Основной контент */}
+
+
+ } />
+ } />
+ } />
+ } />
+
+
+
+
+ );
+};
+
+export default App;
\ No newline at end of file
diff --git a/src/components/ErrorAlert.tsx b/src/components/ErrorAlert.tsx
new file mode 100644
index 0000000..bb66a84
--- /dev/null
+++ b/src/components/ErrorAlert.tsx
@@ -0,0 +1,22 @@
+import React from 'react';
+
+interface ErrorAlertProps {
+ message: string;
+ onRetry?: () => void;
+}
+
+const ErrorAlert: React.FC = ({ message, onRetry }) => {
+ return (
+
+
Ошибка!
+
{message}
+ {onRetry && (
+
+ )}
+
+ );
+};
+
+export default ErrorAlert;
\ No newline at end of file
diff --git a/src/components/Loading.tsx b/src/components/Loading.tsx
new file mode 100644
index 0000000..2c94502
--- /dev/null
+++ b/src/components/Loading.tsx
@@ -0,0 +1,14 @@
+import React from 'react';
+
+const Loading: React.FC = () => {
+ return (
+
+
+ Загрузка...
+
+
Загрузка данных...
+
+ );
+};
+
+export default Loading;
\ No newline at end of file
diff --git a/src/components/OrderFilters.tsx b/src/components/OrderFilters.tsx
new file mode 100644
index 0000000..122c1df
--- /dev/null
+++ b/src/components/OrderFilters.tsx
@@ -0,0 +1,97 @@
+import React from 'react';
+
+interface OrderFiltersProps {
+ filters: {
+ clientName: string;
+ minCost: string;
+ maxCost: string;
+ orderDate: string;
+ status: string;
+ };
+ onFiltersChange: (filters: any) => void;
+}
+
+const OrderFilters: React.FC = ({ filters, onFiltersChange }) => {
+ const handleChange = (field: string, value: string) => {
+ onFiltersChange({
+ ...filters,
+ [field]: value
+ });
+ };
+
+ return (
+
+
+
Фильтры заказов
+
+
+
+ );
+};
+
+export default OrderFilters;
\ No newline at end of file
diff --git a/src/components/VehicleFilters.tsx b/src/components/VehicleFilters.tsx
new file mode 100644
index 0000000..b7fae7e
--- /dev/null
+++ b/src/components/VehicleFilters.tsx
@@ -0,0 +1,68 @@
+import React from 'react';
+
+interface VehicleFiltersProps {
+ filters: {
+ driverName: string;
+ vehicleType: string;
+ licensePlate: string;
+ };
+ onFiltersChange: (filters: any) => void;
+}
+
+const VehicleFilters: React.FC = ({ filters, onFiltersChange }) => {
+ const handleChange = (field: string, value: string) => {
+ onFiltersChange({
+ ...filters,
+ [field]: value
+ });
+ };
+
+ return (
+
+ );
+};
+
+export default VehicleFilters;
\ No newline at end of file
diff --git a/src/components/WaybillWidget.tsx b/src/components/WaybillWidget.tsx
new file mode 100644
index 0000000..e911814
--- /dev/null
+++ b/src/components/WaybillWidget.tsx
@@ -0,0 +1,311 @@
+import React, { useState, useEffect } from 'react';
+import { Order, WaybillEntry } from '../types';
+import { waybillApi, ordersApi } from '../services/api';
+import Loading from '../components/Loading';
+import ErrorAlert from '../components/ErrorAlert';
+
+interface WaybillWidgetProps {
+ vehicleId: number;
+ date: string;
+}
+
+let durationOrder;
+
+const WaybillWidget: React.FC = ({ vehicleId, date }) => {
+ const [entries, setEntries] = useState([]);
+ const [orders, setOrders] = useState([]);
+ const [availableOrders, setAvailableOrders] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [selectedOrder, setSelectedOrder] = useState(null);
+ const [selectedTimeSlot, setSelectedTimeSlot] = useState(null);
+ const [duration, setDuration] = useState(2);
+
+ // Часовые интервалы с 8:00 до 20:00
+ const timeSlots = Array.from({ length: 13 }, (_, i) => {
+ const hour = i + 8;
+ return `${hour.toString().padStart(2, '0')}:00`;
+ });
+
+ const loadData = async () => {
+ try {
+ const response = await ordersApi.getOrders();
+ setOrders(response.data);
+ setLoading(true);
+ setError(null);
+
+ // Загружаем записи путевого листа
+ const entriesResponse = await waybillApi.getEntries();
+ const vehicleEntries = entriesResponse.data.filter(
+ entry => entry.vehicleId === vehicleId && entry.date === date
+ );
+ setEntries(vehicleEntries);
+
+ // Загружаем доступные заказы
+ const ordersResponse = await ordersApi.getOrders();
+ const assignedOrderIds = new Set(vehicleEntries.map(entry => entry.orderId));
+ const available = ordersResponse.data.filter(
+ order => !assignedOrderIds.has(order.id) && order.status !== 'completed'
+ );
+ setAvailableOrders(available);
+
+ } catch (err) {
+ setError('Не удалось загрузить данные путевого листа');
+ console.error('Error loading waybill data:', err);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ loadData();
+ }, [vehicleId, date]);
+
+ const getOrderForTimeSlot = (timeSlot: string): WaybillEntry | undefined => {
+ return entries.find(entry => {
+ const startHour = parseInt(entry.startTime.split(':')[0]);
+ const endHour = parseInt(entry.endTime.split(':')[0]);
+ const slotHour = parseInt(timeSlot.split(':')[0]);
+ return slotHour >= startHour && slotHour < endHour;
+ });
+ };
+
+ const getOrderInfo = (orderId: number, allOrders: Order[]): Order | undefined => {
+ // Ищем заказ среди всех заказов
+ const foundOrder = allOrders.find(order => order.id === orderId);
+ if (foundOrder) {
+ return foundOrder;
+ }
+
+ // Если заказ не найден, но есть в путевом листе - создаем базовый объект
+ const waybillEntry = entries.find(entry => entry.orderId === orderId);
+ if (waybillEntry) {
+ const basicOrder: Order = {
+ id: waybillEntry.orderId,
+ clientName: `Заказ #${waybillEntry.orderId}`,
+ orderCost: 0,
+ orderDate: new Date().toISOString().split('T')[0],
+ status: 'in_progress' as const,
+ address: 'Адрес не указан',
+ description: 'Заказ запланирован в путевом листе'
+ };
+ return basicOrder;
+ }
+
+ // Если заказ не найден нигде
+ return undefined;
+ };
+
+ const handleTimeSlotClick = async (timeSlot: string) => {
+ if (selectedOrder) {
+ try {
+ const startHour = parseInt(timeSlot.split(':')[0]);
+ const endHour = startHour + duration; // Используем выбранную длительность
+
+ const newEntry: Omit = {
+ vehicleId,
+ orderId: selectedOrder.id,
+ startTime: timeSlot,
+ endTime: `${endHour.toString().padStart(2, '0')}:00`,
+ date
+ };
+
+ await waybillApi.createEntry(newEntry);
+ setSelectedOrder(null);
+ setSelectedTimeSlot(null);
+ await loadData();
+
+ } catch (err) {
+ setError('Не удалось добавить заказ в путевой лист');
+ console.error('Error creating waybill entry:', err);
+ }
+ } else {
+ setSelectedTimeSlot(timeSlot);
+ }
+ };
+
+ const handleOrderSelect = async (order: Order) => {
+ if (selectedTimeSlot) {
+ try {
+ const startHour = parseInt(selectedTimeSlot.split(':')[0]);
+ const endHour = startHour + duration; // Используем выбранную длительность
+
+ const newEntry: Omit = {
+ vehicleId,
+ orderId: order.id,
+ startTime: selectedTimeSlot,
+ endTime: `${endHour.toString().padStart(2, '0')}:00`,
+ date
+ };
+
+ await waybillApi.createEntry(newEntry);
+ setSelectedTimeSlot(null);
+ setSelectedOrder(null);
+ await loadData();
+
+ } catch (err) {
+ setError('Не удалось добавить заказ в путевой лист');
+ console.error('Error creating waybill entry:', err);
+ }
+ } else {
+ setSelectedOrder(order);
+ }
+ };
+
+ const handleRemoveEntry = async (entryId: number) => {
+ try {
+ await waybillApi.deleteEntry(entryId);
+ await loadData();
+ } catch (err) {
+ setError('Не удалось удалить запись из путевого листа');
+ console.error('Error deleting waybill entry:', err);
+ }
+ };
+
+ if (loading) return ;
+ if (error) return ;
+
+ return (
+
+
+
+
Путевой лист на {new Date(date).toLocaleDateString()}
+
+
+ {/* Состояние выбора */}
+
+ {selectedOrder && !selectedTimeSlot && (
+
+
Выбран заказ: {selectedOrder.clientName}. Теперь выберите время.
+
+
+ setDuration(parseInt(e.target.value) || 1)}
+ style={{ width: '100px', display: 'inline-block', marginLeft: '10px' }}
+ />
+
+
+ )}
+ {selectedTimeSlot && !selectedOrder && (
+
+
Выбрано время: {selectedTimeSlot}. Теперь выберите заказ.
+
+
+ setDuration(parseInt(e.target.value) || 1)}
+ style={{ width: '100px', display: 'inline-block', marginLeft: '10px' }}
+ />
+
+
+ )}
+ {!selectedOrder && !selectedTimeSlot && (
+
Выберите время или заказ для планирования доставки.
+ )}
+ {selectedOrder && selectedTimeSlot && (
+
+ Готово к добавлению: {selectedOrder.clientName} на время {selectedTimeSlot}
+ продолжительностью {duration} час(ов)
+
+ )}
+
+ {/* Таблица временных интервалов */}
+
+
+
+
+ | Время |
+ Заказ |
+ Действия |
+
+
+
+ {timeSlots.map(timeSlot => {
+ const entry = getOrderForTimeSlot(timeSlot);
+ const order = entry ? getOrderInfo(entry.orderId, orders) : undefined;
+ return (
+
+ | {timeSlot} |
+
+ {order ? (
+
+ {order.clientName}
+
+ {order.address}
+
+ ) : (
+ Свободно
+ )}
+ |
+
+ {order && entry ? (
+
+ ) : (
+
+ )}
+ |
+
+ );
+ })}
+
+
+
+
+ {/* Список доступных заказов */}
+
+
Доступные заказы:
+
+ {availableOrders.map(order => (
+
+
handleOrderSelect(order)}
+ >
+
+
{order.clientName}
+
+ {order.address}
+
+
+
+ {order.orderCost.toLocaleString()} руб.
+
+
+
+
+
+ ))}
+
+
+
+
+
+ );
+};
+
+export default WaybillWidget;
\ No newline at end of file
diff --git a/src/pages/OrdersPage.tsx b/src/pages/OrdersPage.tsx
new file mode 100644
index 0000000..829805d
--- /dev/null
+++ b/src/pages/OrdersPage.tsx
@@ -0,0 +1,185 @@
+import React, { useState, useEffect } from 'react';
+import { Order } from '../types';
+import { ordersApi } from '../services/api';
+import OrderFilters from '../components/OrderFilters';
+import Loading from '../components/Loading';
+import ErrorAlert from '../components/ErrorAlert';
+
+const OrdersPage: React.FC = () => {
+ const [orders, setOrders] = useState([]);
+ const [filteredOrders, setFilteredOrders] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ const [filters, setFilters] = useState({
+ clientName: '',
+ minCost: '',
+ maxCost: '',
+ orderDate: '',
+ status: ''
+ });
+
+ // Загрузка данных
+ const loadOrders = async () => {
+ try {
+ setLoading(true);
+ setError(null);
+ const response = await ordersApi.getOrders();
+ setOrders(response.data);
+ setFilteredOrders(response.data);
+ } catch (err) {
+ setError('Не удалось загрузить список заказов');
+ console.error('Error loading orders:', err);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ // Функция изменения статуса заказа
+ const updateOrderStatus = async (orderId: number, newStatus: Order['status']) => {
+ try {
+ // Находим заказ для обновления
+ const orderToUpdate = orders.find(order => order.id === orderId);
+ if (!orderToUpdate) return;
+
+ // Создаем обновленный заказ
+ const updatedOrder = {
+ ...orderToUpdate,
+ status: newStatus
+ };
+
+ // Отправляем запрос на сервер
+ await ordersApi.updateOrder(orderId, updatedOrder);
+
+ // Обновляем локальное состояние
+ setOrders(prevOrders =>
+ prevOrders.map(order =>
+ order.id === orderId ? updatedOrder : order
+ )
+ );
+
+ console.log(`Статус заказа ${orderId} изменен на: ${newStatus}`);
+ } catch (err) {
+ setError('Не удалось обновить статус заказа');
+ console.error('Error updating order status:', err);
+ }
+ };
+
+ useEffect(() => {
+ loadOrders();
+ }, []);
+
+ // Применение фильтров
+ useEffect(() => {
+ let result = orders;
+
+ if (filters.clientName) {
+ result = result.filter(order =>
+ order.clientName.toLowerCase().includes(filters.clientName.toLowerCase())
+ );
+ }
+
+ if (filters.minCost) {
+ result = result.filter(order =>
+ order.orderCost >= parseFloat(filters.minCost)
+ );
+ }
+
+ if (filters.maxCost) {
+ result = result.filter(order =>
+ order.orderCost <= parseFloat(filters.maxCost)
+ );
+ }
+
+ if (filters.orderDate) {
+ result = result.filter(order =>
+ order.orderDate === filters.orderDate
+ );
+ }
+
+ if (filters.status) {
+ result = result.filter(order =>
+ order.status === filters.status
+ );
+ }
+
+ setFilteredOrders(result);
+ }, [filters, orders]);
+
+ if (loading) return ;
+ if (error) return ;
+
+ return (
+
+
Список заказов
+
+
+
+
+
+
+ Найдено заказов: {filteredOrders.length}
+
+
+
+ {filteredOrders.length === 0 ? (
+
Заказы не найдены
+ ) : (
+
+
+
+
+ | ID |
+ Клиент |
+ Стоимость |
+ Дата заказа |
+ Статус |
+ Адрес |
+ Действия |
+
+
+
+ {filteredOrders.map(order => (
+
+ | {order.id} |
+ {order.clientName} |
+ {order.orderCost.toLocaleString()} руб. |
+ {new Date(order.orderDate).toLocaleDateString()} |
+
+
+ {order.status === 'pending' ? 'Ожидает' :
+ order.status === 'in_progress' ? 'В процессе' :
+ order.status === 'completed' ? 'Завершен' : 'Отменен'}
+
+ |
+ {order.address} |
+
+
+ |
+
+ ))}
+
+
+
+ )}
+
+
+
+ );
+};
+
+export default OrdersPage;
\ No newline at end of file
diff --git a/src/pages/VehicleDetailPage.tsx b/src/pages/VehicleDetailPage.tsx
new file mode 100644
index 0000000..cd9112e
--- /dev/null
+++ b/src/pages/VehicleDetailPage.tsx
@@ -0,0 +1,132 @@
+import React, { useState, useEffect } from 'react';
+import { useParams, Link } from 'react-router-dom';
+import { Vehicle, Order, WaybillEntry } from '../types';
+import { vehiclesApi, ordersApi, waybillApi } from '../services/api';
+import WaybillWidget from '../components/WaybillWidget';
+import Loading from '../components/Loading';
+import ErrorAlert from '../components/ErrorAlert';
+
+const VehicleDetailPage: React.FC = () => {
+ const { id } = useParams<{ id: string }>();
+ const [vehicle, setVehicle] = useState(null);
+ const [completedOrders, setCompletedOrders] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [selectedDate, setSelectedDate] = useState(
+ new Date().toISOString().split('T')[0]
+ );
+
+ const loadVehicleData = async () => {
+ if (!id) return;
+
+ try {
+ setLoading(true);
+ setError(null);
+
+ // Загружаем данные машины
+ const vehicleResponse = await vehiclesApi.getVehicle(parseInt(id));
+ setVehicle(vehicleResponse.data);
+
+ // Загружаем историю выполненных заказов
+ const ordersResponse = await ordersApi.getOrders();
+ const completed = ordersResponse.data.filter(
+ order => order.status === 'completed'
+ );
+ setCompletedOrders(completed);
+
+ } catch (err) {
+ setError('Не удалось загрузить данные машины');
+ console.error('Error loading vehicle data:', err);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ loadVehicleData();
+ }, [id]);
+
+ if (loading) return ;
+ if (error) return ;
+ if (!vehicle) return Машина не найдена
;
+
+ return (
+
+
+
+
+
+
+
+
Информация о машине
+
+
+
Тип машины: {vehicle.vehicleType}
+
Водитель: {vehicle.driverName}
+
Гос. номер: {vehicle.licensePlate}
+
+
+
+
+
+
История выполненных заказов
+
+
+ {completedOrders.length === 0 ? (
+
Нет выполненных заказов
+ ) : (
+
+ {completedOrders.map(order => (
+
+
{order.clientName}
+
{order.address}
+
+ {new Date(order.orderDate).toLocaleDateString()} - {' '}
+ {order.orderCost.toLocaleString()} руб.
+
+
+ ))}
+
+ )}
+
+
+
+
+
+
+
+
Путевой лист
+
+
+ setSelectedDate(e.target.value)}
+ />
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default VehicleDetailPage;
\ No newline at end of file
diff --git a/src/pages/VehiclesPage.tsx b/src/pages/VehiclesPage.tsx
new file mode 100644
index 0000000..b6ab0fe
--- /dev/null
+++ b/src/pages/VehiclesPage.tsx
@@ -0,0 +1,111 @@
+import React, { useState, useEffect } from 'react';
+import { Link } from 'react-router-dom';
+import { Vehicle } from '../types';
+import { vehiclesApi } from '../services/api';
+import VehicleFilters from '../components/VehicleFilters';
+import Loading from '../components/Loading';
+import ErrorAlert from '../components/ErrorAlert';
+
+const VehiclesPage: React.FC = () => {
+ const [vehicles, setVehicles] = useState([]);
+ const [filteredVehicles, setFilteredVehicles] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ const [filters, setFilters] = useState({
+ driverName: '',
+ vehicleType: '',
+ licensePlate: ''
+ });
+
+ const loadVehicles = async () => {
+ try {
+ setLoading(true);
+ setError(null);
+ const response = await vehiclesApi.getVehicles();
+ setVehicles(response.data);
+ setFilteredVehicles(response.data);
+ } catch (err) {
+ setError('Не удалось загрузить список машин');
+ console.error('Error loading vehicles:', err);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ loadVehicles();
+ }, []);
+
+ useEffect(() => {
+ let result = vehicles;
+
+ if (filters.driverName) {
+ result = result.filter(vehicle =>
+ vehicle.driverName.toLowerCase().includes(filters.driverName.toLowerCase())
+ );
+ }
+
+ if (filters.vehicleType) {
+ result = result.filter(vehicle =>
+ vehicle.vehicleType.toLowerCase().includes(filters.vehicleType.toLowerCase())
+ );
+ }
+
+ if (filters.licensePlate) {
+ result = result.filter(vehicle =>
+ vehicle.licensePlate.toLowerCase().includes(filters.licensePlate.toLowerCase())
+ );
+ }
+
+ setFilteredVehicles(result);
+ }, [filters, vehicles]);
+
+ if (loading) return ;
+ if (error) return ;
+
+ return (
+
+
Список машин
+
+
+
+
+
+
+ Найдено машин: {filteredVehicles.length}
+
+
+
+ {filteredVehicles.length === 0 ? (
+
Машины не найдены
+ ) : (
+
+ {filteredVehicles.map(vehicle => (
+
+
+
+
{vehicle.vehicleType}
+
+ Водитель: {vehicle.driverName}
+ Гос. номер: {vehicle.licensePlate}
+
+
+ Подробнее
+
+
+
+
+ ))}
+
+ )}
+
+
+
+ );
+};
+
+export default VehiclesPage;
\ No newline at end of file
diff --git a/src/services/api.ts b/src/services/api.ts
new file mode 100644
index 0000000..4da3102
--- /dev/null
+++ b/src/services/api.ts
@@ -0,0 +1,34 @@
+import axios from 'axios';
+import { Order, Vehicle, WaybillEntry } from '../types';
+
+const API_BASE = 'http://localhost:3001';
+
+const api = axios.create({
+ baseURL: API_BASE,
+});
+
+// Сервис для работы с заказами
+// Сервис для работы с заказами
+export const ordersApi = {
+ getOrders: () => api.get('/orders'),
+ getOrder: (id: number) => api.get(`/orders/${id}`),
+ updateOrder: (id: number, order: Order) => api.put(`/orders/${id}`, order),
+};
+
+// Сервис для работы с машинами
+export const vehiclesApi = {
+ getVehicles: () => api.get('/vehicles'),
+ getVehicle: (id: number) => api.get(`/vehicles/${id}`),
+};
+
+// Сервис для работы с путевыми листами
+export const waybillApi = {
+ getEntries: () => api.get('/waybillEntries'),
+ createEntry: (entry: Omit) =>
+ api.post('/waybillEntries', entry),
+ updateEntry: (id: number, entry: Partial) =>
+ api.put(`/waybillEntries/${id}`, entry),
+ deleteEntry: (id: number) => api.delete(`/waybillEntries/${id}`),
+};
+
+export default api;
\ No newline at end of file
diff --git a/src/types/index.ts b/src/types/index.ts
new file mode 100644
index 0000000..9876871
--- /dev/null
+++ b/src/types/index.ts
@@ -0,0 +1,32 @@
+// Основные типы данных
+export interface Order {
+ id: number;
+ clientName: string;
+ orderCost: number;
+ orderDate: string;
+ status: 'pending' | 'in_progress' | 'completed' | 'cancelled';
+ address: string;
+ description: string;
+}
+
+export interface Vehicle {
+ id: number;
+ driverName: string;
+ vehicleType: string;
+ licensePlate: string;
+}
+
+export interface WaybillEntry {
+ id: number;
+ vehicleId: number;
+ orderId: number;
+ startTime: string;
+ endTime: string;
+ date: string;
+}
+
+export interface Waybill {
+ vehicleId: number;
+ date: Date;
+ entries: WaybillEntry[];
+}
\ No newline at end of file