diff --git a/.gitignore b/.gitignore index 557d98f..e97de80 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ # See http://help.github.com/ignore-files/ for more about ignoring files. # Compiled output -/dist /tmp /out-tsc /bazel-out diff --git a/package-lock.json b/package-lock.json index 5cfcdf1..f83c6d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "multer": "^1.4.5-lts.1", "ngx-cookie-service": "^16.1.0", "rxjs": "~7.8.0", + "stripe": "^14.19.0", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.0", "tslib": "^2.3.0", @@ -12573,6 +12574,18 @@ "node": ">=6" } }, + "node_modules/stripe": { + "version": "14.19.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-14.19.0.tgz", + "integrity": "sha512-Je2USTpUib3hApIgoHXViLoYkDLp+AXdUJvJ6aMQ/AcvZK1PcC7N8nTceh+0gpdotX8izlWN4QyVdMcptubHBQ==", + "dependencies": { + "@types/node": ">=8.1.0", + "qs": "^6.11.0" + }, + "engines": { + "node": ">=12.*" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", diff --git a/package.json b/package.json index 6180179..2843010 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "multer": "^1.4.5-lts.1", "ngx-cookie-service": "^16.1.0", "rxjs": "~7.8.0", + "stripe": "^14.19.0", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.0", "tslib": "^2.3.0", diff --git a/server/api/invoice.yaml b/server/api/invoice.yaml index b25ed90..a18fde4 100644 --- a/server/api/invoice.yaml +++ b/server/api/invoice.yaml @@ -123,4 +123,62 @@ paths: example: "Internal Server Error" error: type: string + /api/invoice/search: + get: + tags: + - Invoices + summary: Search invoices by customer's first and last name + description: API endpoint to search for invoices based on the customer's first and last name. + operationId: searchInvoicesByName + parameters: + - name: firstName + in: query + description: The first name of the customer + required: true + schema: + type: string + - name: lastName + in: query + description: The last name of the customer + required: true + schema: + type: string + responses: + '200': + description: Query successful + content: + application/json: + schema: + type: array + items: + type: object + properties: + userName: + type: string + lineItems: + type: array + items: + type: object + properties: + title: + type: string + price: + type: number + partsAmount: + type: number + laborAmount: + type: number + lineItemTotal: + type: number + total: + type: number + firstName: + type: string + lastName: + type: string + '404': + description: No invoices found + '500': + description: Internal server error + diff --git a/server/models/invoice.js b/server/models/invoice.js index dac65e9..cbc6642 100644 --- a/server/models/invoice.js +++ b/server/models/invoice.js @@ -22,7 +22,8 @@ const invoiceSchema = new Schema({ orderDate: { type: Date, default: new Date() }, firstName: { type: String }, lastName: { type: String }, - status: { type: String, default: "Pending" } + status: { type: String, default: "Pending" }, + payStatus: { type: String, default: "Unpaid" } }); // Export Schema diff --git a/server/routes/invoiceApi.js b/server/routes/invoiceApi.js index 9d146bf..629f85e 100644 --- a/server/routes/invoiceApi.js +++ b/server/routes/invoiceApi.js @@ -105,10 +105,15 @@ router.get("/all", async (req, res) => { // Update invoices router.put("/:invoiceId", async (req, res) => { const { invoiceId } = req.params; - const { status } = req.body; + const { status, payStatus } = req.body; try { - const updatedInvoice = await Invoice.findByIdAndUpdate(invoiceId, { status }, { new: true }); + const updatedInvoice = await Invoice.findByIdAndUpdate( + invoiceId, + { $set: { status: status, payStatus: payStatus } }, + { new: true } + ); + res.status(200).json({ status: "200", message: "Invoice status updated successfully", @@ -124,5 +129,46 @@ router.put("/:invoiceId", async (req, res) => { } }); +// Search for Invoices by First Name and Last Name +router.get("/search", async (req, res) => { + const { firstName, lastName } = req.query; + + // Check if both firstName and lastName are provided + if (!firstName || !lastName) { + return res.status(400).json({ + status: "400", + message: "Both firstName and lastName are required" + }); + } + + try { + // Use the find method to search for invoices with the given firstName and lastName + const invoices = await Invoice.find({ firstName: firstName, lastName: lastName }); + + // Check if any invoices are found + if (!invoices.length) { + return res.status(404).json({ + status: "404", + message: "No invoices found with the provided firstName and lastName" + }); + } + + // Return the found invoices + res.status(200).json({ + status: "200", + message: "Query successful", + data: invoices + }); + + } catch (e) { + console.error(e); + res.status(500).json({ + status: "500", + message: "Internal Server Error", + error: e.message + }); + } +}); + module.exports = router; \ No newline at end of file diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index b8a5fa9..54414a0 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -28,6 +28,8 @@ import { MyProfileComponent } from './my-profile/my-profile.component'; import { ServiceRepairComponent } from './service-repair/service-repair.component'; import { InvoiceSummaryComponent } from './invoice-summary/invoice-summary.component'; import { ViewInvoicesComponent } from './view-invoices/view-invoices.component'; +import { InvoiceSearchComponent } from './invoice-search/invoice-search.component'; +import { ServicesComponent } from './services/services.component'; // routes array with a path, component, and title for each route in the application (e.g. home, about, contact, etc.) const routes: Routes = [ @@ -55,6 +57,11 @@ const routes: Routes = [ component: AboutComponent, title: 'BCRS: About' }, + { + path: 'services', + component: ServicesComponent, + title: 'BCRS: Services' + }, { path: 'security/signin', component: SigninComponent, @@ -65,6 +72,11 @@ const routes: Routes = [ component: FaqComponent, title: 'BCRS: FAQ' }, + { + path: 'invoice-search', + component: InvoiceSearchComponent, + title: 'BCRS: Invoice Search', + }, { path: 'employee-landing', component: EmployeeLandingComponent, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 00d1a8b..3eeef88 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -34,6 +34,8 @@ import { MyProfileComponent } from './my-profile/my-profile.component'; import { ServiceRepairComponent } from './service-repair/service-repair.component'; import { InvoiceSummaryComponent } from './invoice-summary/invoice-summary.component'; import { ViewInvoicesComponent } from './view-invoices/view-invoices.component'; +import { InvoiceSearchComponent } from './invoice-search/invoice-search.component'; +import { ServicesComponent } from './services/services.component'; // @NgModule assign declarations and imports with bootstrap value @NgModule({ @@ -55,7 +57,9 @@ import { ViewInvoicesComponent } from './view-invoices/view-invoices.component'; MyProfileComponent, ServiceRepairComponent, InvoiceSummaryComponent, - ViewInvoicesComponent + ViewInvoicesComponent, + InvoiceSearchComponent, + ServicesComponent ], imports: [ BrowserModule, diff --git a/src/app/invoice-search/invoice-search.component.css b/src/app/invoice-search/invoice-search.component.css new file mode 100644 index 0000000..1b68408 --- /dev/null +++ b/src/app/invoice-search/invoice-search.component.css @@ -0,0 +1,39 @@ +.btn { + border-radius: 0 !important; + color:#f8b400 !important; + border-color: #f8b400 !important; +} + +/* Override theme on hover */ +.btn:hover { + background-color: #f8b400 !important; + color: black !important; + border-color: #f8b400 !important; +} + +/* Remove border on input field and override color */ +input { + border: none; + border-radius: 0; + background-color: transparent; + border-bottom: 2px solid #f8b400; + outline: none; + color: white; +} + +/* Remove white background from input field when focused */ +input:focus { + background-color: transparent !important; + outline: none !important; + box-shadow: none !important; + color: white; +} + +/* Change placeholder color due to dark background */ +input::placeholder { + color: white; +} + +.input { + color: white; +} \ No newline at end of file diff --git a/src/app/invoice-search/invoice-search.component.html b/src/app/invoice-search/invoice-search.component.html new file mode 100644 index 0000000..227cf50 --- /dev/null +++ b/src/app/invoice-search/invoice-search.component.html @@ -0,0 +1,61 @@ +
+
+

Search for Invoice

+
+
+
+
+ + + +
{{ errorMessage }}
+
+
+
+
+
+ + + +
+
+
Search Results
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
#CustomerTotalOrder DateServicesPay StatusStatusActions
{{ i + 1 }}{{ invoice.firstName }} {{ invoice.lastName }}{{ invoice.total | currency }}{{ invoice.orderDate | date }} +
+ {{ item.title }}: {{ item.price | currency }} +
+
{{ invoice.payStatus }}{{ invoice.status }} + +
+
+
+
+
diff --git a/src/app/invoice-search/invoice-search.component.spec.ts b/src/app/invoice-search/invoice-search.component.spec.ts new file mode 100644 index 0000000..c41fe01 --- /dev/null +++ b/src/app/invoice-search/invoice-search.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InvoiceSearchComponent } from './invoice-search.component'; + +describe('InvoiceSearchComponent', () => { + let component: InvoiceSearchComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [InvoiceSearchComponent] + }); + fixture = TestBed.createComponent(InvoiceSearchComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/invoice-search/invoice-search.component.ts b/src/app/invoice-search/invoice-search.component.ts new file mode 100644 index 0000000..e829518 --- /dev/null +++ b/src/app/invoice-search/invoice-search.component.ts @@ -0,0 +1,51 @@ +// Name: Justin Barlowe +// Date: 03/04/2024 +// Description: This is the invoice search component file. This file will allow the user to search for invoices by first and last name. +// File: invoice-search.component.ts + +import { Component, OnInit } from '@angular/core'; +import { FormGroup, FormBuilder } from '@angular/forms'; +import { InvoiceService } from '../invoice.service'; + +@Component({ + selector: 'app-invoice-search', + templateUrl: './invoice-search.component.html', + styleUrls: ['./invoice-search.component.css'] +}) +export class InvoiceSearchComponent implements OnInit { + invoiceSearchForm!: FormGroup; + searchResults: any[] = []; + errorMessage: string | null = null; + + // constructor + constructor(private fb: FormBuilder, private invoiceService: InvoiceService) { } + + // ngOnInit + ngOnInit(): void { + this.invoiceSearchForm = this.fb.group({ + firstName: [''], + lastName: [''] + }); + } + + // searchInvoices function to search invoices by first and last name. + searchInvoices(): void { + const { firstName, lastName } = this.invoiceSearchForm.value; + this.invoiceService.searchInvoices(firstName, lastName).subscribe( + data => { + if (data && data.data.length > 0) { + this.searchResults = data.data; + this.errorMessage = null; // Reset error message + } else { + this.errorMessage = 'No invoices found for the given name.'; // Set error message + this.searchResults = []; // Clear previous results + } + }, + error => { + console.error('There was an error!', error); + this.errorMessage = 'No invoices found. Please try again.'; + } + ); + } +} + diff --git a/src/app/invoice.service.ts b/src/app/invoice.service.ts index ccaf469..5b34635 100644 --- a/src/app/invoice.service.ts +++ b/src/app/invoice.service.ts @@ -46,7 +46,13 @@ export class InvoiceService { } //Update invoice status function. - updateInvoiceStatus(invoiceId: string, status: string): Observable { - return this.http.put(`${this.apiUrl3}/${invoiceId}`, { status }); + updateInvoiceStatus(invoiceId: string, status: string, payStatus: string): Observable { + return this.http.put(`${this.apiUrl3}/${invoiceId}`, { status, payStatus }); } + + // Find invoice by first and last name function. + searchInvoices(firstName: string, lastName: string): Observable { + return this.http.get(`${this.apiUrl3}/search`, { params: { firstName, lastName } }); + } + } diff --git a/src/app/layouts/nav/nav.component.html b/src/app/layouts/nav/nav.component.html index 953d9a7..a5f4cae 100644 --- a/src/app/layouts/nav/nav.component.html +++ b/src/app/layouts/nav/nav.component.html @@ -25,6 +25,9 @@ + +