mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Implement Typescript (#2514)
* Add contact * Saving client and contacts * working on ts implementation * Need to pass into TS * client_edit.ts * Need to pass into TS * declare variables
This commit is contained in:
parent
0f66625cdf
commit
fa83ce10a3
@ -5,12 +5,21 @@ namespace App\Http\Controllers;
|
||||
use App\Http\Requests\Client\EditClientRequest;
|
||||
use App\Http\Requests\Client\UpdateClientRequest;
|
||||
use App\Models\Client;
|
||||
use App\Repositories\ClientRepository;
|
||||
use Illuminate\Http\Request;
|
||||
use Yajra\DataTables\Facades\DataTables;
|
||||
use Yajra\DataTables\Html\Builder;
|
||||
|
||||
class ClientController extends Controller
|
||||
{
|
||||
|
||||
protected $clientRepo;
|
||||
|
||||
public function __construct(ClientRepository $clientRepo)
|
||||
{
|
||||
$this->clientRepo = $clientRepo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
@ -122,14 +131,11 @@ class ClientController extends Controller
|
||||
public function edit(EditClientRequest $request, Client $client)
|
||||
{
|
||||
|
||||
$client->load('contacts', 'primary_contact');
|
||||
|
||||
$data = [
|
||||
'header' => $this->headerData(),
|
||||
'client' => $client,
|
||||
];
|
||||
|
||||
|
||||
return view('client.edit', $data);
|
||||
}
|
||||
|
||||
@ -140,16 +146,10 @@ class ClientController extends Controller
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(UpdateClientRequest $request, $id)
|
||||
public function update(UpdateClientRequest $request, Client $client)
|
||||
{
|
||||
|
||||
$client = $request->entity(Client::class, request('client'));
|
||||
|
||||
$client->fill($request->all())->save();
|
||||
|
||||
$client->contacts()->delete();
|
||||
$client->contacts()->create($request->input('contacts'));
|
||||
|
||||
$client = $this->clientRepo->save($request, $client);
|
||||
$client->load('contacts', 'primary_contact');
|
||||
|
||||
return response()->json($client, 200);
|
||||
|
@ -27,7 +27,6 @@ class ClientContact extends Authenticatable
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
'id',
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
8
app/Models/Presenters/ClientContactPresenter.php
Normal file
8
app/Models/Presenters/ClientContactPresenter.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Presenters;
|
||||
|
||||
class ClientContactPresenter extends EntityPresenter
|
||||
{
|
||||
|
||||
}
|
@ -29,7 +29,11 @@ class RouteServiceProvider extends ServiceProvider
|
||||
parent::boot();
|
||||
|
||||
Route::bind('client', function ($value) {
|
||||
return \App\Models\Client::where('id', $this->decodePrimaryKey($value))->first() ?? abort(404);
|
||||
$client = \App\Models\Client::where('id', $this->decodePrimaryKey($value))->first() ?? abort(404);
|
||||
$client->load('contacts', 'primary_contact');
|
||||
|
||||
return $client;
|
||||
|
||||
});
|
||||
|
||||
Route::bind('invoice', function ($value) {
|
||||
|
12
app/Repositories/BaseRepository.php
Normal file
12
app/Repositories/BaseRepository.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class BaseRepository
|
||||
{
|
||||
|
||||
|
||||
}
|
16
app/Repositories/ClientContactRepository.php
Normal file
16
app/Repositories/ClientContactRepository.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class ClientContactRepository extends BaseRepository
|
||||
{
|
||||
|
||||
public function save($data)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
25
app/Repositories/ClientRepository.php
Normal file
25
app/Repositories/ClientRepository.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Repositories\ClientContactRepository;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class ClientRepository extends BaseRepository
|
||||
{
|
||||
protected $clientContactRepository;
|
||||
|
||||
public function __construct(ClientContactRepository $clientContactRepository)
|
||||
{
|
||||
$this->clientContactRepository = $clientContactRepository;
|
||||
}
|
||||
|
||||
public function save($data)
|
||||
{
|
||||
$client->fill($request->all())->save();
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -33,6 +33,8 @@
|
||||
"laravel-echo": "^1.4.0",
|
||||
"quill": "^1.3.6",
|
||||
"socket.io-client": "^2.1.1",
|
||||
"ts-loader": "3.5.0",
|
||||
"typescript": "^3.1.6",
|
||||
"vue-i18n": "^8.3.0"
|
||||
}
|
||||
}
|
||||
|
1
public/js/client_edit.js
vendored
Normal file
1
public/js/client_edit.js
vendored
Normal file
File diff suppressed because one or more lines are too long
5
public/js/ninja.js
vendored
5
public/js/ninja.js
vendored
File diff suppressed because one or more lines are too long
5
public/js/ninja.min.js
vendored
5
public/js/ninja.min.js
vendored
File diff suppressed because one or more lines are too long
5
resources/js/app.js
vendored
5
resources/js/app.js
vendored
@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
require('./bootstrap');
|
||||
window.Vue = require('vue');
|
||||
//window.Vue = require('vue');
|
||||
/* Development only*/
|
||||
Vue.config.devtools = true;
|
||||
|
||||
@ -31,7 +31,7 @@ Vue.component('client-primary-address', require('./components/client/ClientPrima
|
||||
Vue.component('generic-address', require('./components/generic/Address.vue'));
|
||||
Vue.component('client-edit-form', require('./components/client/ClientEditForm.vue'));
|
||||
Vue.component('contact-edit', require('./components/client/ClientContactEdit.vue'));
|
||||
*/
|
||||
|
||||
window.onload = function () {
|
||||
|
||||
const app = new Vue({
|
||||
@ -39,3 +39,4 @@ window.onload = function () {
|
||||
});
|
||||
|
||||
}
|
||||
*/
|
@ -89,7 +89,6 @@ export default {
|
||||
submit() {
|
||||
this.errors = {};
|
||||
|
||||
|
||||
axios.put('/clients/' + this.client.hash_id, this.client).then(response => {
|
||||
this.client = response.data;
|
||||
console.dir(response);
|
||||
|
79
resources/js/ts/client/client_edit.ts
Normal file
79
resources/js/ts/client/client_edit.ts
Normal file
@ -0,0 +1,79 @@
|
||||
//import * as Vue from 'vue';
|
||||
import Vue from 'vue';
|
||||
import axios, { AxiosRequestConfig, AxiosPromise } from 'axios';
|
||||
|
||||
var VueApp: any = Vue;
|
||||
|
||||
declare var clientObject: any;
|
||||
|
||||
var App = new VueApp({
|
||||
el : '#client_edit',
|
||||
data: function () {
|
||||
return {
|
||||
'client': [],
|
||||
'errors': [],
|
||||
}
|
||||
},
|
||||
mounted(this: any) {
|
||||
//this.client = {!! $client !!};
|
||||
this.client = clientObject;
|
||||
console.dir(this.client);
|
||||
},
|
||||
beforeMount: function () {
|
||||
console.log('before mount')
|
||||
},
|
||||
created:function() {
|
||||
console.dir('created')
|
||||
},
|
||||
updated:function() {
|
||||
console.dir('updated')
|
||||
},
|
||||
methods:{
|
||||
remove(this: any, contact:any){
|
||||
let index = this.client.contacts.indexOf(contact);
|
||||
this.client.contacts.splice(index, 1);
|
||||
},
|
||||
add(this: any){
|
||||
console.dir('i will add a contact here')
|
||||
this.client.contacts.push({first_name: '', last_name: '', email: '', phone: ''});
|
||||
window.scrollTo(0, document.body.scrollHeight || document.documentElement.scrollHeight);
|
||||
this.$nextTick(() => {
|
||||
let index = this.client.contacts.length - 1;
|
||||
let input = this.$refs.first_name[index];
|
||||
input.focus();
|
||||
});
|
||||
},
|
||||
submit(this: any) {
|
||||
this.errors = {};
|
||||
|
||||
axios.put('/clients/', this.client).then(response => {
|
||||
// axios.put('/clients/' + {{ $client->present()->id }}, this.client).then(response => {
|
||||
this.client = response.data;
|
||||
}).catch(error => {
|
||||
if (error.response.status === 422) {
|
||||
this.errors = error.response.data.errors || {};
|
||||
}
|
||||
else if(error.response.status === 419) {
|
||||
//csrf token has expired, we'll need to force a page reload
|
||||
}
|
||||
});
|
||||
},
|
||||
copy(type: any) {
|
||||
if(type.includes('copy_billing')){
|
||||
this.client.shipping_address1 = this.client.address1;
|
||||
this.client.shipping_address2 = this.client.address2;
|
||||
this.client.shipping_city = this.client.city;
|
||||
this.client.shipping_state = this.client.state;
|
||||
this.client.shipping_postal_code = this.client.postal_code;
|
||||
this.client.shipping_country_id = this.client.country_id;
|
||||
}else {
|
||||
this.client.address1 = this.client.shipping_address1;
|
||||
this.client.address2 = this.client.shipping_address2;
|
||||
this.client.city = this.client.shipping_city;
|
||||
this.client.state = this.client.shipping_state;
|
||||
this.client.postal_code = this.client.shipping_postal_code;
|
||||
this.client.country_id = this.client.shipping_country_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
@ -2,29 +2,12 @@
|
||||
|
||||
@section('body')
|
||||
<main class="main" id="client_edit">
|
||||
|
||||
<!-- Breadcrumb-->
|
||||
{{ Breadcrumbs::render('clients.edit', $client) }}
|
||||
|
||||
<form>
|
||||
<form @submit.prevent="submit">
|
||||
<div class="container-fluid">
|
||||
<div class="row form-group">
|
||||
<div class="col-md-12">
|
||||
<span class="float-right">
|
||||
<div class="btn-group ml-2">
|
||||
<button class="btn btn-lg btn-success" type="button"><i class="fa fa-save"></i> {{ trans('texts.save') }}</button>
|
||||
<button class="btn btn-lg btn-success dropdown-toggle dropdown-toggle-split" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="sr-only">Toggle Dropdown</span>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a class="dropdown-item" href="#"><i class="fa fa-plus-circle"></i> {{ trans('texts.add_contact') }}</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="#">{{ trans('texts.archive_client') }}</a>
|
||||
<a class="dropdown-item" href="#">{{ trans('texts.delete_client') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Client Details and Address Column -->
|
||||
@ -42,7 +25,7 @@
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary2">{{ trans('texts.contact_information') }}
|
||||
<span class="float-right">
|
||||
<button type="button" class="btn btn-primary btn-sm"><i class="fa fa-plus-circle"></i> {{ trans('texts.add_contact') }}</button>
|
||||
<button type="button" class="btn btn-primary btn-sm" @click="add()"><i class="fa fa-plus-circle"></i> {{ trans('texts.add_contact') }}</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -54,55 +37,21 @@
|
||||
</div>
|
||||
<!-- End Contact Details Column -->
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-center">
|
||||
<button class="btn btn-lg btn-success" type="button" @click="submit"><i class="fa fa-save"></i> {{ trans('texts.save') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el : '#client_edit',
|
||||
data: function () {
|
||||
return {
|
||||
'client': [],
|
||||
'errors': [],
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log('Component mounted.')
|
||||
//this.getItinerary()
|
||||
this.client = {!! $client !!}
|
||||
},
|
||||
beforeMount: function () {
|
||||
console.log('before mount')
|
||||
},
|
||||
created:function() {
|
||||
console.dir('created')
|
||||
},
|
||||
updated:function() {
|
||||
console.dir('updated');
|
||||
},
|
||||
methods:{
|
||||
copy(type) {
|
||||
console.dir('inside ');
|
||||
if(type.includes('copy_billing')){
|
||||
this.client.shipping_address1 = this.client.address1;
|
||||
this.client.shipping_address2 = this.client.address2;
|
||||
this.client.shipping_city = this.client.city;
|
||||
this.client.shipping_state = this.client.state;
|
||||
this.client.shipping_postal_code = this.client.postal_code;
|
||||
this.client.shipping_country_id = this.client.country_id;
|
||||
}else {
|
||||
this.client.address1 = this.client.shipping_address1;
|
||||
this.client.address2 = this.client.shipping_address2;
|
||||
this.client.city = this.client.shipping_city;
|
||||
this.client.state = this.client.shipping_state;
|
||||
this.client.postal_code = this.client.shipping_postal_code;
|
||||
this.client.country_id = this.client.shipping_country_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
var clientObject = {!! $client !!};
|
||||
</script>
|
||||
|
||||
<script src=" {{ mix('/js/client_edit.js') }}"></script>
|
||||
|
||||
</main>
|
||||
|
||||
@endsection
|
@ -4,7 +4,8 @@
|
||||
<div class="form-group row">
|
||||
<label for="name" class="col-sm-3 col-form-label text-right">@lang('texts.client_name')</label>
|
||||
<div class="col-sm-9">
|
||||
<input name="name" placeholder="@lang('texts.name')" class="form-control" v-model="client.name" value="{{ $client->present()->name }}" id="name">
|
||||
<input name="name" placeholder="@lang('texts.name')" class="form-control" v-model="client.name" value="{{ $client->present()->name }}">
|
||||
<div v-if="errors && errors.name" class="text-danger">@{{ errors.name[0] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
<label for="name" class="col-sm-3 col-form-label text-right">@lang('texts.first_name')</label>
|
||||
<div class="col-sm-9">
|
||||
<input name="id" type="hidden" v-model="contact.id" value="{{ $client->present()->id }}">
|
||||
<input name="first_name" placeholder="@lang('texts.first_name')" class="form-control" v-model="contact.first_name">
|
||||
<input ref="first_name" name="first_name" placeholder="@lang('texts.first_name')" class="form-control" v-model="contact.first_name">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="float-right">
|
||||
<button type="button" class="btn btn-danger" v-on:click="$emit('remove',contact.id)"> {{ trans('texts.remove_contact') }}</button>
|
||||
<button type="button" class="btn btn-danger" v-on:click="remove(contact)"> {{ trans('texts.remove_contact') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
62
tsconfig.json
Normal file
62
tsconfig.json
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Basic Options */
|
||||
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', -> 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
"sourceMap": false, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
// "outDir": "./", /* Redirect output structure to the directory. */
|
||||
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": false, /* Enable all strict type-checking options. */
|
||||
"noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
},
|
||||
"include": [
|
||||
"resources/js/ts/**/*"
|
||||
]
|
||||
}
|
19
webpack.mix.js
vendored
19
webpack.mix.js
vendored
@ -11,11 +11,27 @@ const mix = require('laravel-mix');
|
||||
|
|
||||
*/
|
||||
|
||||
mix.webpackConfig({
|
||||
resolve: {
|
||||
extensions: ['.ts']
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
loader: 'ts-loader'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
mix.js('resources/js/ts/client/client_edit.ts', 'public/js');
|
||||
|
||||
mix.js('resources/js/app.js', 'public/js/vendor');
|
||||
|
||||
mix.scripts([
|
||||
'node_modules/@coreui/coreui/dist/js/coreui.js',
|
||||
'public/js/vendor/app.js'
|
||||
//'public/js/vendor/app.js'
|
||||
], 'public/js/ninja.js');
|
||||
|
||||
mix.minify('public/js/ninja.js');
|
||||
@ -31,5 +47,4 @@ mix.minify('public/css/ninja.css');
|
||||
|
||||
mix.copyDirectory('node_modules/font-awesome/fonts', 'public/fonts');
|
||||
|
||||
|
||||
mix.version();
|
||||
|
Loading…
x
Reference in New Issue
Block a user